Is it okay to have hundreds/thousands of String Tables?

Following up on a previous thread , I have a Weapon ScriptableObject with hundreds of instances (Club, Pickaxe, Scimitar, etc.) and multiple localized fields (name, description, etc.).

Currently, these fields live in the "Weapons" string table, and the key format is "ID.fieldName" (for example, the Pickaxe’s description string lives in the Weapons table at "PICKAXE.description". However, I’m evaluating an alternative structure, where each weapon would have its own String Table, always with the same entry keys (like the "Pickaxe" table would have "name" and "description" keys, as would the "Scimitar" table).

My concern here is that we have hundreds of Weapons, and hundreds of other ScriptableObject instances (spells, potions, etc.) that would also need their own string tables under this architecture. I wouldn’t be surprised if this left us with over 2000 string tables.

Is this a total misuse of the localization system? Would it incur some very serious costs? Doing it this way would make things much nicer for us, but it’s not a road I want to go down if it seems overly problematic to people with lots of Unity localization knowledge.

You could do this however that’s quite an overhead to manage and building the Addressable assets could get to be a long process. You may also find a noticeable delay when loading all the String Tables. I would not recommend this approach. A smaller number of String Tables with lots of entries is more performantt and easier to manage. What is the reason you want a string table per weapon? Maybe we can find an alternative that gives you the same benefits?
When you say it would be much nicer for you how do you mean? Is this because you are putting everything together into its own areas?

2 Likes

Thanks Karl! Really appreciate the reply. There’s a couple things that we would’ve liked about the many string tables approach:

  1. In our current system, each weapon has a unique ID field that’s solely used to associate it with its entries in the “Weapons” string table. This system feels a little fragile; the connection will break if someone changes a weapon’s ID without updating the string table keys, or vice versa. We could put LocalizedString fields on the Weapon ScriptableObject instead to form a more robust connection, but it’ll be very tedious to go through and manually point thousands of fields at string table entries (and kind of seems like it defeats the purpose of having string table entry keys). Either way, the string-table-per-weapon approach would’ve eliminated the need for a unique weapon ID (and the fragility that comes with it), so we could’ve just pointed each weapon at its own string table and grabbed each field by its fieldName key.

  2. We want to make it possible for a modder to add a new Weapon with its own strings, and I don’t think it’s possible for modders to add new entries to existing string tables (let me know if I’m wrong about that). I’m sure there are other solutions here, but it would’ve been very straightforward if a modder could just add a new string table for their new Weapon.

If you know of an alternative that gives us both of these benefits, I’d really appreciate it!

Also, side note: I absolutely love the Unity localization package so far! It’s been a joy to work with, and its flexibility has allowed us to integrate it very seamlessly with our systems. Thanks to you and the team for all your hard work on it!

You can set up a LocalizedString through a script. If you are using a String Table per weapon you would also need to keep that String Table in sync, if you change the id/name then you would have to also update the String Table name. I think using a LocalizedString that is linked to the entry is safer as you can then change the values on the scriptable object directly including changing the StringTable entries name. You could even create a custom editor to do it for you.
Following the PICKAXE.description name pattern seems appropriate and I think this would work better in the long run. If you have 2000 weapons and support 5 languages then that’s an extra 14,000 assets in your project as opposed to 7 (5 tables, 1 collection, 1 shared table data). Managing all those assets and the time it takes to import them will become frustrating and slow. It should be relatively simpe to create a custom editor that can keep those ids in sync when you edit your Scriptable Objects

Theres a few ways to handle this now.
You can create a custom addressable integration like this https://discussions.unity.com/t/815428

If we took the single string table approach then you could patch the table, by adding/updating entries at the start, when the game loads, other users do this. You could let users create a simple text file, json, csv etc. Then load that and copy the data into a String Table. Like this https://gist.github.com/karljj1/f2707f9eee7f4cff37fe90493d9098c4
You could even take this approach and solve your first issue by merging all the localization data into the String Table when you start the game although I would suggest just keeping it in the String Tables or you will have to find a way to handle all of those files and manage the translations. We do have some improvements we are working on for improved support for loading tables from custom sources and for patching tables but for now the above is fine and you can always switch it out for the newer features at a later date.

2 Likes

Thanks Karl! I’d actually started experimenting with an auto-syncing LocalizedString setup early on, but it felt like I might be using the system in a way it wasn’t meant to be used. Let me run my experimental approach by you and see if it seems right to you.

Along with my existing string ID field, I add two LocalizedString fields to the Weapon ScriptableObject:

public class Weapon : ScriptableObject
{
    // ...

    string id;
    LocalizedString name;
    LocalizedString description;

    // ...
}

Then, in my Weapon.OnValidate() method, I check to make sure each LocalizedString is pointing to the right table and entry, and update it if its not:

void OnValidate()
{
    // (pseudocode)
    string table = name.TableReference.TableCollectionName;
    string entryKey = name.TableEntryReference; // (this line has some stuff missing)

    if (table != "Weapons" || entryKey != $"{id}.name")
    {
        name.SetReference("Weapons", $"{id}.name")
    }

    // (same thing for description)
}

I also probably need some code in OnValidate to check for ID changes and update the entry keys accordingly.

Is this roughly how you’d imagine writing it? Like I said, this strategy felt a little like I was fighting against how the system was meant to be used, but it’d be good to know if my instincts were wrong here.

This looks fine and much simpler to manage than thousands of string tables :wink:
If you need to change something then LocalizationEditorSettings will likely be what you need, it’s an Editor only class so put it into an #if UNITY_EDITOR block inside of the OnValidate.
You can update an entry key like so:

  1. Use GetStringTableCollection to get the collection.
  2. Use the SharedData that is part of the collection.
    3a. RemapId can be used to change its integer id.
    3b. RenameKey can be used to change the key.
  3. As you are making changes to an asset you also need to mark the asset dirty or the changes wont be saved. so call EditorUtility.SetDirty on the SharedData.
2 Likes

Awesome, thanks Karl! Really appreciate the thorough discussion. I’ll revisit this strategy with your suggestions!

1 Like