Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Feature Request Specify the Addressable catalog to use when searching for a translation

Discussion in 'Localization Tools' started by Homolus, Nov 4, 2022.

  1. Homolus

    Homolus

    Joined:
    Dec 21, 2020
    Posts:
    9
    Hello everyone,

    I'm working on a game in which I want to allow players to import their own content during runtime (modding).

    I did a lot of searches and finally managed to load an external catalog using this:

    Code (CSharp):
    1. Addressables.LoadContentCatalogAsync(path)
    Then, I have a list of catalogs, one per mod loaded + the base game one. Then, in the code, when I'm searching for an asset from a string key, I loop over all loaded catalogs to check if the asset exists somewhere:

    Code (CSharp):
    1. private static IResourceLocation FindAssetInCatalogs<TObject>(object key)
    2. {
    3.     foreach (var catalog in CATALOGS)
    4.     {
    5.         if (catalog.Locate(key, typeof(TObject), out IList<IResourceLocation> locs))
    6.         {
    7.             return locs[0];
    8.         }
    9.     }
    10.  
    11.     return null;
    12. }
    This is working well, and it's possible to easily override any asset of the game (that uses the Addressables) by using the same key in a mod.

    But now I would like to allow players to override localization strings too, and there it becomes a little bit more complicated...

    I would like to follow the same logic: when I want to get a translation from a key, I want to look in the localization tables of the mods before the base game one. So I did something like that:

    Code (CSharp):
    1. public string Translate(string key, string table, params object[] arguments)
    2. {
    3.     foreach (var localizationDatabase in LOCALIZATION_DATABASES)
    4.     {
    5.         var translation = localizationDatabase.StringDatabase.GetLocalizedString(table, key, arguments);
    6.  
    7.         if (translation != null)
    8.         {
    9.             return translation;
    10.         }
    11.     }
    12.  
    13.     return null;
    14. }
    But even if I properly loaded the catalog of the mod first (that should contain the localization tables), it seems that "GetLocalizedString" will look at the base game catalog through the "AddressablesInterface.ResourceManager".

    Is there another way to do what I'm looking for?
     
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,223
    I'm not sure about catalogue selection but it may have already loaded the table before you updated them.


    We added support in 1.4.2 to make this easier.
    A table provider can be used to provide a table without using addressables, so you could for example load a CSV file from a mod. https://docs.unity3d.com/Packages/c...ine.Localization.Settings.ITableProvider.html

    A table post processor can be used to update/modify the table after loading
    https://docs.unity3d.com/Packages/c...ocalization.Settings.ITablePostprocessor.html
     
    Homolus likes this.
  3. Noxalus

    Noxalus

    Joined:
    Jan 9, 2018
    Posts:
    80
    That sounds really great, I will try that, thanks a lot!
     
  4. Homolus

    Homolus

    Joined:
    Dec 21, 2020
    Posts:
    9
    OK, I had time to perform some tests, and I tried to use the
    ITablePostprocessor
    doing something like that:

    Code (CSharp):
    1. public class ModsTablePatcher : ITablePostprocessor
    2. {
    3.     public void PostprocessTable(LocalizationTable table)
    4.     {
    5.         if (table is StringTable stringTable)
    6.         {
    7.             foreach (var mod in MODS)
    8.             {
    9.                 var localizationTable = mod.GetTable(stringTable.TableCollectionName);
    10.  
    11.                 if (localizationTable == null)
    12.                 {
    13.                     continue;
    14.                 }
    15.  
    16.                 foreach (var localizationKey in localizationTable.LocalizationKeys)
    17.                 {
    18.                     var existingEntry = stringTable.GetEntry(localizationKey);
    19.                     var modEntry = localizationTable.GetEntry(localizationKey);
    20.                     var modEntryValue = modEntry.GetValue(table.LocaleIdentifier.Code);
    21.  
    22.                     if (existingEntry != null)
    23.                     {
    24.                         Debug.Log($"Mod {mod.Id} just overrided \"{localizationKey}\" key!");
    25.                         existingEntry.Value = modEntryValue;
    26.                     }
    27.                     else
    28.                     {
    29.                         Debug.Log($"Mod {mod.Id} just added \"{localizationKey}\" key!");
    30.                         stringTable.AddEntry(localizationKey, modEntryValue);
    31.                     }
    32.                 }
    33.             }
    34.         }
    35.     }
    36. }
    And it's working, but I have a problem with this approach: I would like to be able to easily revert these changes when I unload a mod, is there a way to do that?

    EDIT: Sorry, I post my message too quickly, I've been able to solve my problem by calling "ResetStates" on the StringTables:

    Code (CSharp):
    1. LocalizationSettings.StringDatabase.ResetState();
     
    Last edited: Nov 8, 2022
  5. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,223
    Ah nice. You may be better to call Dispose instead as that will release all the operations, ResetState can cause operations to leak.
     
    Homolus likes this.
  6. Homolus

    Homolus

    Joined:
    Dec 21, 2020
    Posts:
    9
    Thank for the info, I will call
    Dispose
    instead ;)

    EDIT:
    LocalizedStringDatabase
    class doesn't have a
    Dispose
    , on which object should I call this method instead?
     
    Last edited: Nov 9, 2022
  7. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,223
    Are you using 1.4.2?
    I think you need to cast it as IDisposable first.
     
  8. unity_Arkuqj1MdCzc4Q

    unity_Arkuqj1MdCzc4Q

    Joined:
    May 10, 2022
    Posts:
    2
    Hi, I would like to use `ITableProvider` to load my mods as well. I was taking a look at the provided example from the documentation, and I was wondering whether it is possible to add smart strings to the table. e.g.,
    Code (CSharp):
    1. [Serializable]
    2. public class CustomTableProvider : ITableProvider
    3. {
    4. public string customTableCollectionName = "My Custom Table";
    5.  
    6. public AsyncOperationHandle<TTable> ProvideTableAsync<TTable>(string tableCollectionName, Locale locale) where TTable : LocalizationTable
    7. {
    8.     Debug.Log($"Requested {locale.LocaleName} {typeof(TTable).Name} with the name `{tableCollectionName}`.");
    9.  
    10.     // Provide a custom string table only with the name "My Custom Table".
    11.     if (typeof(TTable) == typeof(StringTable) && tableCollectionName == customTableCollectionName)
    12.     {
    13.         // Create the table and its shared table data.
    14.         var table = ScriptableObject.CreateInstance<StringTable>();
    15.         table.SharedData = ScriptableObject.CreateInstance<SharedTableData>();
    16.         table.SharedData.TableCollectionName = customTableCollectionName;
    17.         table.LocaleIdentifier = locale.Identifier;
    18.  
    19.         // Add some values
    20.         table.AddEntry("My Entry 1", "My localized value 1");
    21.         table.AddEntry("My Entry 2", "My localized value 2");
    22.  
    23.         // It would be nice to have something like this.
    24.         StringTableEntry s = table.CreateTableEntry();
    25.         s.Value = "My localized name is {name}";
    26.         s.IsSmart = true;
    27.         s.Key = "My Entry 1";
    28.         table.AddEntry(s);
    29.  
    30.  
    31.         return Addressables.ResourceManager.CreateCompletedOperation(table as TTable, null);
    32.     }
    33.  
    34.     // Fallback to default table loading.
    35.     return default;
    36. }
    37. }
    Is there a way to add a smart string to a StringTable?
    Thanks in advance.
     
  9. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,223
    Yes the code you posted should work. Is it not working?
    AddEntry returns the new string table entry. You can then set the IsSmart field to true.
    E.g
    Code (csharp):
    1. var entry = table.AddEntry("My Entry", "smart string");
    2. entry.IsSmart = true;
     
    unity_Arkuqj1MdCzc4Q likes this.
  10. unity_Arkuqj1MdCzc4Q

    unity_Arkuqj1MdCzc4Q

    Joined:
    May 10, 2022
    Posts:
    2
    My code didn't work, but your example does, thank you!
     
    karl_jones likes this.