2025-05-01 01:48:08 -07:00

793 lines
38 KiB
C#

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using UnityEditor.AddressableAssets;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Localization.Addressables;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Pseudo;
using UnityEngine.Localization.Settings;
using UnityEngine.Localization.Tables;
using Object = UnityEngine.Object;
namespace UnityEditor.Localization
{
/// <summary>
/// Provides methods for configuring Localization settings including tables and Locales.
/// </summary>
public class LocalizationEditorSettings
{
static readonly char[] k_UnityInvalidFileNameChars = { '/', '?', '<', '>', '\\', ':', '|', '\"' };
static readonly IEnumerable<char> k_InvalidFileNameChars = Path.GetInvalidFileNameChars().Concat(k_UnityInvalidFileNameChars);
internal const string k_GameViewPref = "Localization-ShowLocaleMenuInGameView";
internal const string k_StringPicker = "Localization-UseSearchStringPicker";
internal const string k_AssetPicker = "Localization-UseSearchAssetPicker";
internal const string k_TableRefMethod = "Localization-TableRefMethod";
internal const string k_EntryRefMethod = "Localization-EntryRefMethod";
static LocalizationEditorSettings s_Instance;
// Cached searches to help performance.
ReadOnlyCollection<Locale> m_ProjectLocales;
ReadOnlyCollection<PseudoLocale> m_ProjectPseudoLocales;
// Allows for overriding the default behavior, used for testing.
internal static LocalizationEditorSettings Instance
{
get => s_Instance ?? (s_Instance = new LocalizationEditorSettings());
set => s_Instance = value;
}
internal virtual LocalizationTableCollectionCache TableCollectionCache { get; set; } = new LocalizationTableCollectionCache();
/// <summary>
/// The <see cref="LocalizationSettings"/> used for this project and available in the player and editor.
/// </summary>
public static LocalizationSettings ActiveLocalizationSettings
{
get => Instance.ActiveLocalizationSettingsInternal;
set => Instance.ActiveLocalizationSettingsInternal = value;
}
/// <summary>
/// During play mode, in the editor a menu can be shown to allow for quickly changing the <see cref="LocalizationSettings.SelectedLocale"/>.
/// </summary>
public static bool ShowLocaleMenuInGameView
{
get => EditorPrefs.GetBool(k_GameViewPref, true);
set => EditorPrefs.SetBool(k_GameViewPref, value);
}
/// <summary>
/// When <see langword="true"/> the advanced Unity Search picker will be used when selecting <see cref="LocalizedString"/> table entries.
/// When <see langword="false"/> the tree view picker will be used.
/// </summary>
public static bool UseLocalizedStringSearchPicker
{
get => EditorPrefs.GetBool(k_StringPicker, true);
set => EditorPrefs.SetBool(k_StringPicker, value);
}
/// <summary>
/// When <see langword="true"/> the advanced Unity Search picker will be used when selecting <see cref="LocalizedAsset{TObject}"/> table entries.
/// When <see langword="false"/> the tree view picker will be used.
/// </summary>
public static bool UseLocalizedAssetSearchPicker
{
get => EditorPrefs.GetBool(k_AssetPicker, true);
set => EditorPrefs.SetBool(k_AssetPicker, value);
}
/// <summary>
/// Adds a reference to a table in the Editor.
/// </summary>
public static TableReferenceMethod TableReferenceMethod
{
get => (TableReferenceMethod)EditorPrefs.GetInt(k_TableRefMethod, (int)TableReferenceMethod.Guid);
set => EditorPrefs.SetInt(k_TableRefMethod, (int)value);
}
/// <summary>
/// Adds a reference to a table entry in the Editor.
/// </summary>
public static EntryReferenceMethod EntryReferenceMethod
{
get => (EntryReferenceMethod)EditorPrefs.GetInt(k_EntryRefMethod, (int)EntryReferenceMethod.Id);
set => EditorPrefs.SetInt(k_EntryRefMethod, (int)value);
}
/// <summary>
/// Localization modification events that can be used when building editor components.
/// </summary>
public static LocalizationEditorEvents EditorEvents { get; internal set; } = new LocalizationEditorEvents();
internal LocalizationEditorSettings()
{
EditorEvents.LocaleSortOrderChanged += (sender, locale) => SortLocales();
Undo.undoRedoPerformed += UndoRedoPerformed;
}
~LocalizationEditorSettings()
{
Undo.undoRedoPerformed -= UndoRedoPerformed;
}
/// <summary>
/// Add the Locale so that it can be used by the Localization system.
/// </summary>
/// <example>
/// This shows how to create a Locale and add it to the project.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="add-locale"/>
/// </example>
/// <param name="locale">The <see cref="Locale"/> to add to the project so it can be used by the Localization system.</param>
/// <param name="createUndo">Used to indicate if an Undo operation should be created.</param>
public static void AddLocale(Locale locale, bool createUndo = false) => Instance.AddLocaleInternal(locale, createUndo);
/// <summary>
/// Removes the locale from the Localization system.
/// </summary>
/// <example>
/// This shows how to remove a Locale from the project.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="remove-locale"/>
/// </example>
/// <param name="locale">The <see cref="Locale"/> to remove so that it is no longer used by the Localization system.</param>
/// <param name="createUndo">Used to indicate if an Undo operation should be created.</param>
public static void RemoveLocale(Locale locale, bool createUndo = false) => Instance.RemoveLocaleInternal(locale, createUndo);
/// <summary>
/// Returns all <see cref="Locale"/> that are part of the Localization system and will be included in the player.
/// To Add Locales use <seealso cref="AddLocale"/> and <seealso cref="RemoveLocale"/> to remove them.
/// Note this does not include <see cref="PseudoLocale"/> which can be retrieved by using <seealso cref="GetPseudoLocales"/>.
/// </summary>
/// <example>
/// This example prints the names of the Locales.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-locales"/>
/// </example>
/// <returns>A collection of all Locales in the project.</returns>
public static ReadOnlyCollection<Locale> GetLocales() => Instance.GetLocalesInternal();
/// <summary>
/// Returns all <see cref="PseudoLocale"/> that are part of the Localization system and will be included in the player.
/// </summary>
/// <returns></returns>
public static ReadOnlyCollection<PseudoLocale> GetPseudoLocales() => Instance.GetPseudoLocalesInternal();
/// <summary>
/// Returns the locale that matches the <see cref="LocaleIdentifier"/>"/> in the project.
/// </summary>
/// <param name="localeId"></param>
/// <example>
/// This example shows how to find a <see cref="Locale"/> using a [SystemLanguage](https://docs.unity3d.com/ScriptReference/SystemLanguage.html) or code.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-locale"/>
/// </example>
/// <returns>The found <see cref="Locale"/> or null if one could not be found.</returns>
public static Locale GetLocale(LocaleIdentifier localeId) => Instance.GetLocaleInternal(localeId.Code);
/// <summary>
/// Returns all <see cref="StringTableCollection"/> that are in the project.
/// </summary>
/// <example>
/// This example shows how to print out the contents of all the <see cref="StringTableCollection"/> in the project.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-string-tables"/>
/// </example>
/// <returns></returns>
public static ReadOnlyCollection<StringTableCollection> GetStringTableCollections() => Instance.GetStringTableCollectionsInternal();
/// <summary>
/// Returns a <see cref="StringTableCollection"/> with the matching <see cref="TableReference"/>.
/// </summary>
/// <param name="tableNameOrGuid"></param>
/// <example>
/// This example shows how to update a collection by adding support for a new Locale.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-string-table"/>
/// </example>
/// <returns>Found collection or null if one could not be found.</returns>
public static StringTableCollection GetStringTableCollection(TableReference tableNameOrGuid) => Instance.TableCollectionCache.FindStringTableCollection(tableNameOrGuid);
/// <summary>
/// Returns all <see cref="AssetTableCollection"/> assets that are in the project.
/// </summary>
/// <example>
/// This example shows how to print out the contents of all the <see cref="AssetTableCollection"/> in the project.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-asset-tables"/>
/// </example>
/// <returns></returns>
public static ReadOnlyCollection<AssetTableCollection> GetAssetTableCollections() => Instance.TableCollectionCache.AssetTableCollections.AsReadOnly();
/// <summary>
/// Returns a <see cref="AssetTableCollection"/> with the matching <see cref="TableReference"/>.
/// </summary>
/// <param name="tableNameOrGuid"></param>
/// <example>
/// This example shows how to update a collection by adding a new localized asset.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-asset-table"/>
/// </example>
/// <returns>Found collection or null if one could not be found.</returns>
public static AssetTableCollection GetAssetTableCollection(TableReference tableNameOrGuid) => Instance.TableCollectionCache.FindAssetTableCollection(tableNameOrGuid);
/// <summary>
/// Returns the <see cref="LocalizationTableCollection"/> that the table is part of or null if the table has no collection.
/// </summary>
/// <param name="table">The table to find the collection for.</param>
/// <returns>The found collection or null if one could not be found.</returns>
public static LocalizationTableCollection GetCollectionFromTable(LocalizationTable table) => Instance.TableCollectionCache.FindCollectionForTable(table);
/// <summary>
/// Returns the <see cref="LocalizationTableCollection"/> that the <see cref="SharedTableData"/> is part of or null if one could not be found.
/// </summary>
/// <param name="sharedTableData">The shared table data to match against a collection.</param>
/// <returns>The found collection or null if one could not be found.</returns>
public static LocalizationTableCollection GetCollectionForSharedTableData(SharedTableData sharedTableData) => Instance.TableCollectionCache.FindCollectionForSharedTableData(sharedTableData);
/// <summary>
/// If a table does not belong to a <see cref="LocalizationTableCollection"/> then it is considered to be loose, it has no parent collection and will be ignored.
/// This returns all loose tables that use the same <see cref="SharedTableData"/>, they could then be converted into a <see cref="LocalizationTableCollection"/> using <seealso cref="CreateCollectionFromLooseTables"/>.
/// </summary>
/// <param name="sharedTableData"></param>
/// <param name="foundTables"></param>
public static void FindLooseStringTablesUsingSharedTableData(SharedTableData sharedTableData, IList<LocalizationTable> foundTables) => Instance.TableCollectionCache.FindLooseTablesUsingSharedTableData(sharedTableData, foundTables);
/// <summary>
/// Creates a <see cref="StringTableCollection"/> or <see cref="AssetTableCollection"/> from the provided loose tables.
/// </summary>
/// <param name="looseTables">Tables to create the collection from. All tables must be of the same type.</param>
/// <param name="path">The path to save the new assets to.</param>
/// <returns>The created <see cref="StringTableCollection"/> or <see cref="AssetTableCollection"/>.</returns>
public static LocalizationTableCollection CreateCollectionFromLooseTables(IList<LocalizationTable> looseTables, string path) => Instance.CreateCollectionFromLooseTablesInternal(looseTables, path);
/// <summary>
/// Creates a <see cref="StringTableCollection"/> using the project Locales.
/// </summary>
/// <example>
/// This example shows how to create a new <see cref="StringTableCollection"/> and add some English values.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="create-string-collection-1"/>
/// </example>
/// <param name="tableName">The name of the new collection. Cannot be blank or whitespace, cannot contain invalid filename characters, and cannot contain "[]".</param>
/// <param name="assetDirectory">The directory to save the generated assets, must be in the project Assets directory.</param>
/// <returns>The created <see cref="StringTableCollection"/> collection.</returns>
public static StringTableCollection CreateStringTableCollection(string tableName, string assetDirectory) => CreateStringTableCollection(tableName, assetDirectory, GetLocales());
/// <summary>
/// Creates a <see cref="StringTableCollection"/> using the provided Locales.
/// </summary>
/// <example>
/// This example shows how to create a new <see cref="StringTableCollection"/> which contains an English and Japanese <see cref="StringTable"/>.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="create-string-collection-2"/>
/// </example>
/// <param name="tableName">The name of the new collection. Cannot be blank or whitespace, cannot contain invalid filename characters, and cannot contain "[]".</param>
/// <param name="assetDirectory">The directory to save the generated assets, must be in the project Assets directory.</param>
/// <param name="selectedLocales">The locales to generate the collection with. A <see cref="StringTable"/> will be created for each Locale.</param>
/// <returns>The created <see cref="StringTableCollection"/> collection.</returns>
public static StringTableCollection CreateStringTableCollection(string tableName, string assetDirectory, IList<Locale> selectedLocales) => Instance.CreateCollection(typeof(StringTableCollection), tableName, assetDirectory, selectedLocales) as StringTableCollection;
/// <summary>
/// Creates a <see cref="AssetTableCollection"/> using the project Locales.
/// </summary>
/// <example>
/// This example shows how to update a collection by adding a new localized asset.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-asset-table"/>
/// </example>
/// <param name="tableName">The Table Collection Name to use.</param>
/// <param name="assetDirectory">The directory to store the generated assets.</param>
/// <returns>The created <see cref="AssetTableCollection"/> collection.</returns>
public static AssetTableCollection CreateAssetTableCollection(string tableName, string assetDirectory) => CreateAssetTableCollection(tableName, assetDirectory, GetLocales());
/// <summary>
/// Creates a <see cref="AssetTableCollection"/> using the provided Locales.
/// </summary>
/// <param name="tableName">The name of the new collection. Cannot be blank or whitespace, cannot contain invalid filename characters, and cannot contain "[]".</param>
/// <param name="assetDirectory">The directory to store the generated assets.</param>
/// <param name="selectedLocales">The locales to generate the collection with. A <see cref="AssetTable"/> will be created for each Locale</param>
/// <returns>The created <see cref="AssetTableCollection"/> collection.</returns>
public static AssetTableCollection CreateAssetTableCollection(string tableName, string assetDirectory, IList<Locale> selectedLocales) => Instance.CreateCollection(typeof(AssetTableCollection), tableName, assetDirectory, selectedLocales) as AssetTableCollection;
/// <summary>
/// Adds or Remove the preload flag for the selected table.
/// </summary>
/// <example>
/// This example shows how to set the preload flag for a single collection.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="set-preload-flag"/>
/// </example>
/// <param name="table">The table to mark as preload.</param>
/// <param name="preload"><see langword="true"/> ifd the table should be preloaded or <see langword="false"/> if it should be loaded on demand.</param>
/// <param name="createUndo">Should an Undo record be created?</param>
public static void SetPreloadTableFlag(LocalizationTable table, bool preload, bool createUndo = false)
{
Instance.SetPreloadTableInternal(table, preload, createUndo);
}
/// <summary>
/// Returns <see langword="true"/> if the table is marked for preloading.
/// </summary>
/// <example>
/// This example shows how to query if a table is marked as preload.
/// <code source="../../DocCodeSamples.Tests/LocalizationEditorSettingsSamples.cs" region="get-preload-flag"/>
/// </example>
/// <param name="table">The table to query.</param>
/// <returns><see langword="true"/> if preloading is enable otherwise <see langword="false"/>.</returns>
public static bool GetPreloadTableFlag(LocalizationTable table)
{
// TODO: We could just use the instance id so we dont need to load the whole table
return Instance.GetPreloadTableFlagInternal(table);
}
/// <summary>
/// Returns the <see cref="AssetTableCollection"/> that contains a table entry with the closest match to the provided text.
/// Uses the Levenshtein distance method.
/// </summary>
/// <param name="keyName"></param>
/// <returns></returns>
[Obsolete("FindSimilarKey will be removed in the future, please use Unity Search. See TableEntrySearchData class for further details.")]
public static (StringTableCollection collection, SharedTableData.SharedTableEntry entry, int matchDistance) FindSimilarKey(string keyName)
{
throw new NotSupportedException("FindSimilarKey is obsolete. please use Unity Search instead.");
}
internal static void RefreshEditorPreview()
{
// Only update the preview in edit mode (LOC-1025)
if (LocalizationSettings.Instance.IsPlayingOrWillChangePlaymode)
return;
if (ActiveLocalizationSettings != null && ActiveLocalizationSettings.GetSelectedLocale() != null)
{
LocalizationPropertyDriver.UnregisterProperties();
VariantsPropertyDriver.UnregisterProperties();
ActiveLocalizationSettings.SendLocaleChangedEvents(LocalizationSettings.SelectedLocale);
}
}
internal string GetUniqueTableCollectionName(Type collectionType, string name)
{
int suffix = 1;
var nameToTest = name;
while (true)
{
if (TableCollectionCache.FindTableCollection(collectionType, nameToTest) == null)
return nameToTest;
nameToTest = $"{name} {suffix}";
suffix++;
}
}
internal virtual LocalizationTableCollection CreateCollection(Type collectionType, string tableName, string assetDirectory, IList<Locale> selectedLocales)
{
if (!typeof(LocalizationTableCollection).IsAssignableFrom(collectionType))
throw new ArgumentException($"{collectionType.Name} Must be derived from {nameof(LocalizationTableCollection)}", nameof(collectionType));
if (string.IsNullOrEmpty(assetDirectory))
throw new ArgumentException("Must not be null or empty.", nameof(assetDirectory));
var tableNameError = IsTableNameValid(collectionType, tableName);
if (tableNameError != null)
{
throw new ArgumentException(tableNameError, nameof(tableName));
}
var collection = ScriptableObject.CreateInstance(collectionType) as LocalizationTableCollection;
AssetDatabase.StartAssetEditing();
// TODO: Check that no tables already exist with the same name, locale and type.
var relativePath = PathHelper.MakePathRelative(assetDirectory);
Directory.CreateDirectory(relativePath);
var sharedDataPath = Path.Combine(relativePath, AddressHelper.GetSharedTableAddress(tableName) + ".asset");
var sharedTableData = ScriptableObject.CreateInstance<SharedTableData>();
sharedTableData.TableCollectionName = tableName;
CreateAsset(sharedTableData, sharedDataPath);
collection.SharedData = sharedTableData;
collection.AddSharedTableDataToAddressables();
// Extract the SharedTableData Guid and assign it so we can use it as a unique id for the table collection name.
var sharedDataGuid = GetAssetGuid(sharedTableData);
sharedTableData.TableCollectionNameGuid = Guid.Parse(sharedDataGuid);
EditorUtility.SetDirty(sharedTableData); // We need to set it dirty so the change to TableCollectionNameGuid is saved.
if (selectedLocales?.Count > 0)
{
var createdTables = new List<LocalizationTable>(selectedLocales.Count);
foreach (var locale in selectedLocales)
{
var table = ScriptableObject.CreateInstance(collection.TableType) as LocalizationTable;
table.SharedData = sharedTableData;
table.LocaleIdentifier = locale.Identifier;
table.name = AddressHelper.GetTableAddress(tableName, locale.Identifier);
createdTables.Add(table);
}
for (int i = 0; i < createdTables.Count; ++i)
{
var tbl = createdTables[i];
var assetPath = Path.Combine(relativePath, tbl.name + ".asset");
assetPath = AssetDatabase.GenerateUniqueAssetPath(assetPath);
CreateAsset(tbl, assetPath);
collection.AddTable(tbl, postEvent: false);
}
}
// Save the collection
collection.name = tableName;
var collectionPath = Path.Combine(relativePath, collection.name + ".asset");
CreateAsset(collection, collectionPath);
AssetDatabase.StopAssetEditing();
return collection;
}
internal virtual LocalizationTableCollection CreateCollectionFromLooseTablesInternal(IList<LocalizationTable> looseTables, string path)
{
if (looseTables == null || looseTables.Count == 0)
return null;
var isStringTable = looseTables[0] is StringTable;
var collectionType = isStringTable ? typeof(StringTableCollection) : typeof(AssetTableCollection);
var collection = ScriptableObject.CreateInstance(collectionType) as LocalizationTableCollection;
collection.SharedData = looseTables[0].SharedData;
foreach (var table in looseTables)
{
if (table.SharedData != collection.SharedData)
{
Debug.LogError($"Table {table.name} does not share the same Shared Table Data and can not be part of the new collection", table);
continue;
}
collection.AddTable(table, postEvent: false); // Don't post the event, we will send the Collection added only event
}
var relativePath = PathHelper.MakePathRelative(path);
CreateAsset(collection, relativePath);
EditorEvents.RaiseCollectionAdded(collection);
return collection;
}
internal virtual LocalizationSettings ActiveLocalizationSettingsInternal
{
get
{
EditorBuildSettings.TryGetConfigObject(LocalizationSettings.ConfigName, out LocalizationSettings settings);
return settings;
}
set
{
if (value == null)
{
EditorBuildSettings.RemoveConfigObject(LocalizationSettings.ConfigName);
}
else
{
EditorBuildSettings.AddConfigObject(LocalizationSettings.ConfigName, value, true);
}
}
}
internal virtual AddressableAssetSettings GetAddressableAssetSettings(bool create)
{
var settings = AddressableAssetSettingsDefaultObject.GetSettings(create);
if (settings != null)
return settings;
// By default Addressables wont return the settings if updating or compiling. This causes issues for us, especially if we are trying to get the Locales.
// We will just ignore this state and try to get the settings regardless.
if (EditorApplication.isUpdating || EditorApplication.isCompiling)
{
// Legacy support
if (EditorBuildSettings.TryGetConfigObject(AddressableAssetSettingsDefaultObject.kDefaultConfigAssetName, out settings))
{
return settings;
}
AddressableAssetSettingsDefaultObject so;
if (EditorBuildSettings.TryGetConfigObject(AddressableAssetSettingsDefaultObject.kDefaultConfigObjectName, out so))
{
// Extract the guid
var serializedObject = new SerializedObject(so);
var guid = serializedObject.FindProperty("m_AddressableAssetSettingsGuid")?.stringValue;
if (!string.IsNullOrEmpty(guid))
{
var path = AssetDatabase.GUIDToAssetPath(guid);
return AssetDatabase.LoadAssetAtPath<AddressableAssetSettings>(path);
}
}
}
return null;
}
internal virtual AddressableAssetEntry GetAssetEntry(Object asset) => GetAssetEntry(asset.GetInstanceID());
internal virtual AddressableAssetEntry GetAssetEntry(int instanceId)
{
var settings = GetAddressableAssetSettings(false);
if (settings == null)
return null;
var guid = GetAssetGuid(instanceId);
return settings.FindAssetEntry(guid);
}
internal virtual string FindUniqueAssetAddress(string address)
{
var aaSettings = GetAddressableAssetSettings(false);
if (aaSettings == null)
return address;
var validAddress = address;
var index = 1;
var foundExisting = true;
while (foundExisting)
{
if (index > 1000)
{
Debug.LogError("Unable to create valid address for new Addressable Asset.");
return address;
}
foundExisting = false;
foreach (var g in aaSettings.groups)
{
if (g.Name == validAddress)
{
foundExisting = true;
validAddress = address + index;
index++;
break;
}
}
}
return validAddress;
}
internal virtual void AddLocaleInternal(Locale locale, bool createUndo)
{
if (locale == null)
throw new ArgumentNullException(nameof(locale));
if (!EditorUtility.IsPersistent(locale))
throw new AssetNotPersistentException(locale);
var aaSettings = GetAddressableAssetSettings(true);
if (aaSettings == null)
return;
using (new UndoScope("Add Locale", createUndo))
{
var assetEntry = AddressableGroupRules.AddLocaleToGroup(locale, aaSettings, createUndo);
assetEntry.address = locale.LocaleName;
// Clear the locales cache.
m_ProjectLocales = null;
m_ProjectPseudoLocales = null;
if (!LocalizationSettings.Instance.IsPlayingOrWillChangePlaymode)
LocalizationSettings.Instance.ResetState();
if (!assetEntry.labels.Contains(LocalizationSettings.LocaleLabel))
{
if (createUndo)
Undo.RecordObjects(new Object[] { aaSettings, assetEntry.parentGroup }, "Add locale");
assetEntry.SetLabel(LocalizationSettings.LocaleLabel, true, true);
EditorEvents.RaiseLocaleAdded(locale);
}
}
}
internal virtual void RemoveLocaleInternal(Locale locale, bool createUndo)
{
// Clear the locale cache
m_ProjectLocales = null;
m_ProjectPseudoLocales = null;
if (!LocalizationSettings.Instance)
LocalizationSettings.Instance.ResetState();
var aaSettings = GetAddressableAssetSettings(false);
if (aaSettings == null)
return;
var localeAssetEntry = GetAssetEntry(locale);
if (localeAssetEntry == null)
return;
using (new UndoScope("Remove locale", createUndo))
{
if (createUndo)
Undo.RecordObjects(new Object[] { aaSettings, localeAssetEntry.parentGroup }, "Remove locale");
aaSettings.RemoveAssetEntry(localeAssetEntry.guid);
EditorEvents.RaiseLocaleRemoved(locale);
}
}
internal virtual ReadOnlyCollection<Locale> GetLocalesInternal()
{
if (m_ProjectLocales != null)
return m_ProjectLocales;
var foundLocales = new List<Locale>();
var aaSettings = GetAddressableAssetSettings(false);
if (aaSettings == null)
return new ReadOnlyCollection<Locale>(foundLocales);
var foundAssets = new List<AddressableAssetEntry>();
aaSettings.GetAllAssets(foundAssets, false, group => group != null, entry =>
{
return entry.labels.Contains(LocalizationSettings.LocaleLabel);
});
foreach (var localeAddressable in foundAssets)
{
if (!string.IsNullOrEmpty(localeAddressable.guid))
{
var locale = AssetDatabase.LoadAssetAtPath<Locale>(AssetDatabase.GUIDToAssetPath(localeAddressable.guid));
if (locale != null && !(locale is PseudoLocale)) // Dont include Pseudo locales.
foundLocales.Add(locale);
}
}
foundLocales.Sort();
m_ProjectLocales = new ReadOnlyCollection<Locale>(foundLocales);
return m_ProjectLocales;
}
internal virtual ReadOnlyCollection<PseudoLocale> GetPseudoLocalesInternal()
{
if (m_ProjectPseudoLocales == null)
CollectProjectLocales();
return m_ProjectPseudoLocales;
}
void CollectProjectLocales()
{
var foundLocales = new List<Locale>();
var foundPseudoLocales = new List<PseudoLocale>();
var aaSettings = GetAddressableAssetSettings(false);
if (aaSettings != null)
{
var foundAssets = new List<AddressableAssetEntry>();
aaSettings.GetAllAssets(foundAssets, false, group => group != null, entry =>
{
return entry.labels.Contains(LocalizationSettings.LocaleLabel);
});
foreach (var localeAddressable in foundAssets)
{
if (localeAddressable.MainAsset != null && localeAddressable.MainAsset is Locale locale)
{
if (locale is PseudoLocale pseudoLocale)
{
foundPseudoLocales.Add(pseudoLocale);
}
else
{
foundLocales.Add(locale);
}
}
}
}
foundLocales.Sort();
foundPseudoLocales.Sort();
m_ProjectLocales = foundLocales.AsReadOnly();
m_ProjectPseudoLocales = foundPseudoLocales.AsReadOnly();
}
internal virtual ReadOnlyCollection<StringTableCollection> GetStringTableCollectionsInternal() => Instance.TableCollectionCache.StringTableCollections.AsReadOnly();
Locale GetLocaleInternal(string code) => GetLocalesInternal()?.FirstOrDefault(loc => loc.Identifier.Code == code);
void SortLocales()
{
if (m_ProjectLocales != null)
{
var localesList = m_ProjectLocales.ToList();
localesList.Sort();
m_ProjectLocales = localesList.AsReadOnly();
}
if (m_ProjectPseudoLocales != null)
{
var pseudoLocalesList = m_ProjectPseudoLocales.ToList();
pseudoLocalesList.Sort();
m_ProjectPseudoLocales = pseudoLocalesList.AsReadOnly();
}
}
internal virtual void SetPreloadTableInternal(LocalizationTable table, bool preload, bool createUndo = false)
{
if (table == null)
throw new ArgumentNullException(nameof(table), "Can not set preload flag on a null table");
var aaSettings = GetAddressableAssetSettings(true);
if (aaSettings == null)
return;
var tableEntry = GetAssetEntry(table);
if (tableEntry == null)
throw new AddressableEntryNotFoundException(table);
if (createUndo)
Undo.RecordObjects(new Object[] { aaSettings, tableEntry.parentGroup }, "Set Preload flag");
tableEntry.SetLabel(LocalizationSettings.PreloadLabel, preload, preload);
}
internal virtual bool GetPreloadTableFlagInternal(LocalizationTable table)
{
if (table == null)
throw new ArgumentNullException(nameof(table), "Can not get preload flag from a null table");
var aaSettings = GetAddressableAssetSettings(false);
if (aaSettings == null)
return false;
var tableEntry = GetAssetEntry(table);
if (tableEntry == null)
throw new AddressableEntryNotFoundException(table);
return tableEntry.labels.Contains(LocalizationSettings.PreloadLabel);
}
void UndoRedoPerformed()
{
// Reset the locales as adding/removing a locale may have been undone.
m_ProjectLocales = null;
}
internal virtual void CreateAsset(Object asset, string path)
{
AssetDatabase.CreateAsset(asset, path);
}
internal string GetAssetGuid(int instanceId)
{
Debug.Assert(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(instanceId, out string guid, out long _), "Failed to extract the asset Guid");
return guid;
}
internal string GetAssetGuid(Object asset)
{
Debug.Assert(AssetDatabase.TryGetGUIDAndLocalFileIdentifier(asset, out string guid, out long _), "Failed to extract the asset Guid", asset);
return guid;
}
internal string IsTableNameValid(Type collectionType, string tableName)
{
if (string.IsNullOrWhiteSpace(tableName))
{
return "Table collection name cannot be blank or whitespace";
}
if (tableName != tableName.Trim())
{
return "Table collection name cannot contain leading or trailing whitespace";
}
// Addressables restriction
if (tableName.Contains('[') && tableName.Contains(']'))
{
return "Table collection name cannot contain both '[' and ']'";
}
var values = k_InvalidFileNameChars.Intersect(tableName).ToList();
if (values.Any())
{
return $"Table collection name cannot contain invalid filename characters but contains '{string.Join(", ", values)}'";
}
if (TableCollectionCache.FindTableCollection(collectionType, tableName) != null)
{
return $"{collectionType.Name} with name '{tableName}' already exists";
}
return null;
}
}
}