Splitting string tables from the same language into different asset groups

I recently started playing around with this localization package, targeting Android / IOS.
I currently have my string tables loading from remote assets, so I can update them without having to rebuild a new client. but I would like to be able to display some strings (error messages) in case the user is offline or addressables fails to download for whatever reason.

I’ve tried splitting some of the string tables into multiple asset groups manually, but the addressable analyzer rules then subsequently fail. And I don’t see anything in the AddressableGroupRules scriptable object that can help me change this behavior. Is this possible to do? Does anyone else have experience with this?

You can create a custom group resolver to determine where assets should go https://docs.unity3d.com/Packages/com.unity.localization@1.2/api/UnityEditor.Localization.Addressables.GroupResolver.html
This should work with the analyzer

Thanks. It’s not the most elegant solution, but looks like its working. Thank you for making the important functions virtual. I had to copy-paste a few bits of code from the base class, and due to protection levels, I can’t use the Locale groups without modifying the package, but luckily I don’t need that feature.

If anyone else wants the code, here it is:
The Code

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Localization;
using UnityEditor.Localization.Addressables;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.SmartFormat;
using UnityEngine.Localization.Tables;
using UnityEngine.UI;

public class LocalizationCustomGroupResolver : GroupResolver
{
    [SerializeField] string m_SharedGroupName_Embedded = "Localization-Assets-Shared-Packed";
    [SerializeField] string m_LocaleGroupNamePattern_Embedded = "Localization-String-Tables-Packed-{LocaleName}";
    [SerializeField] List<string> m_EmbeddedTablesWhitelist = new List<string>();

    [MenuItem("Tools/Assets/Set Custom Loclization Settings")]
    public static void SetCustomGroupResolver()
    {
        var guids = AssetDatabase.FindAssets("t:AddressableGroupRules");
        if (guids.Length > 0)
        {
            var path = AssetDatabase.GUIDToAssetPath(guids[0]);
            var asset = AssetDatabase.LoadAssetAtPath<AddressableGroupRules>(path);
            asset.StringTablesResolver = new LocalizationCustomGroupResolver();
            EditorUtility.SetDirty(asset);
            UnityEngine.Debug.Log("set " + path + " dirty");
        }
    }

    protected override string GetExpectedSharedGroupName(IList<LocaleIdentifier> locales, UnityEngine.Object asset, AddressableAssetSettings aaSettings)
    {
        if (IsEmbeddedAsset(asset.name))
        {
            return m_SharedGroupName_Embedded;
        }

        return base.GetExpectedSharedGroupName(locales, asset, aaSettings);
    }

    public override string GetExpectedGroupName(IList<LocaleIdentifier> locales, UnityEngine.Object asset, AddressableAssetSettings aaSettings)
    {
        if (IsEmbeddedAsset(asset.name))
        {
            if (locales == null || locales.Count == 0)
                return GetExpectedSharedGroupName(locales, asset, aaSettings);

            Locale locale;
            if (asset is Locale l && l.Identifier == locales[0])
                locale = l;
            else
                locale = LocalizationEditorSettings.GetLocale(locales[0].Code) ?? Locale.CreateLocale(locales[0]);

            var expectedGroupName = Smart.Format(m_LocaleGroupNamePattern_Embedded, locale, asset);
            for (var i = 1; i < locales.Count; ++i)
            {
                locale = LocalizationEditorSettings.GetLocale(locales[i]);
                var groupName = Smart.Format(m_LocaleGroupNamePattern_Embedded, locale, asset);
                if (expectedGroupName != groupName)
                {
                    // Use shared group
                    return GetExpectedSharedGroupName(locales, asset, aaSettings);
                }
            }

            return expectedGroupName;
        }

        return base.GetExpectedGroupName(locales, asset, aaSettings);
    }

    private bool IsEmbeddedAsset(string name)
    {
        foreach (var entry in m_EmbeddedTablesWhitelist)
        {
            if (name.Contains(entry))
            {
                return true;
            }
        }

        return false;
    }
}

using System;
using System.Collections.Generic;
using UnityEditor.AddressableAssets.Settings;
using UnityEditor.Localization.Addressables;
using UnityEditor.Localization.UI.Toolkit;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;

    [CustomPropertyDrawer(typeof(LocalizationCustomGroupResolver))]
    class LocalizationCustomGroupResolverPropertyDrawer : PropertyDrawer
    {
        public override VisualElement CreatePropertyGUI(SerializedProperty property)
        {
            var root = new Foldout { value = property.isExpanded, text = property.displayName };
            root.RegisterValueChangedCallback(evt => property.isExpanded = evt.newValue);

            var groupNameEmbedded = new TextField("Shared Group Name Embedded");
            groupNameEmbedded.bindingPath = "m_SharedGroupName_Embedded";
            root.Add(groupNameEmbedded);

            var localeNameEmbedded = new TextField("Locale Group Name Embedded");
            localeNameEmbedded.bindingPath = "m_LocaleGroupNamePattern_Embedded";
            root.Add(localeNameEmbedded);

            var embeddedInclude = new ListView();
            embeddedInclude.headerTitle = "Embedded Table Names";
            embeddedInclude.showFoldoutHeader = true;
            embeddedInclude.bindingPath = "m_EmbeddedTablesWhitelist";
            root.Add(embeddedInclude);

            var name = new TextField("Shared Group Name");
            name.bindingPath = "m_SharedGroupName";
            root.Add(name);

            var group = new ObjectField("Shared Group") { allowSceneObjects = false, objectType = typeof(AddressableAssetGroup) };
            var groupProperty = property.FindPropertyRelative("m_SharedGroup");
            group.bindingPath = "m_SharedGroup";
            group.RegisterValueChangedCallback(evt => name.style.display = evt.newValue != null ? DisplayStyle.None : DisplayStyle.Flex);
            name.style.display = groupProperty.objectReferenceValue != null ? DisplayStyle.None : DisplayStyle.Flex;
            root.Add(group);

            var localeName = new TextField("Locale Group Name");
            localeName.bindingPath = "m_LocaleGroupNamePattern";
            root.Add(localeName);

            var readOnly = new Toggle("Read Only");
            readOnly.bindingPath = "m_MarkEntriesReadOnly";
            root.Add(readOnly);

            return root;
        }
    }
2 Likes