StringDatabase.GetLocalizedStringAsync() doesn't work with ISource from project settings?

In our project, there are some cases we want to use StringDatabase.GetLocalizedStringAsync() rather than LocalizedString. Also we created some ISource classes like below, and assign them into ProjectSettings/Localization.

  public class LocalizationAccountDataSource : ISource
    {
        public bool TryEvaluateSelector(ISelectorInfo selectorInfo)
        {
            if (selectorInfo.CurrentValue == null)
            {
                if (selectorInfo.SelectorText == "AccountData")
                {
                    selectorInfo.Result = UserData.AccountData;
                    return true;
                }
              
                return false;
            }

            if (selectorInfo.CurrentValue is UserAccount userAccountData)
            {
                if (selectorInfo.SelectorText == "NickName")
                {
                    if (string.IsNullOrEmpty(userAccountData.NickName))
                    {
                        selectorInfo.Result = ConstantStrings.DefaultName;
                    }
                    else
                    {
                        selectorInfo.Result = userAccountData.NickName;  
                    }

                    return true;
                }

                return false;
            }
          
            return false;
        }
    }

When we called GetLocalizedStringAsync() from LocalizedString or StringTableEntry type, {AccountData} from smart string work well. But when we called GetLocalizedStringAsync() from StringDatabase, selectors marked with SmartString doesn’t work.

private async UniTask StartAsync()
{
    await LocalizationSettings.InitializationOperation;

    // raw value : "Hello {AccountData.NickName}!"

    var localizedText = await new LocalizedString(tableRef, tableEntryRef).GetLocalizedStringAsync();
    Debug.Log(localizedText); // "Hello Commander64!"

    var getTableEntryResult =
        await LocalizationSettings.StringDatabase.GetTableEntryAsync(tableRef, tableEntryKey);
    Debug.Log(getTableEntryResult.Entry.GetLocalizedString()); // "Hello Commander64!"
   
    localizedText = await LocalizationSettings.StringDatabase.GetLocalizedStringAsync(tableRef, tableEntryRef);
    Debug.Log(localizedText); // "Hello {AccountData.NickName}!"
}

Is this the intended behavior? or there is something I missed?

That looks like a bug, im not sure why it would not work. Are you using the latest version 1.3.2?
If its still happening in that could you please file a bug report?

1 Like

I will file a bug report!

I’m using 1.3.2 now, but I remember it was also happening before 1.3 and 1.2.

Weird thing is, I can re-format result of StringDatabase.GetLocalizedStringAsync() by StringDatabase.SmartFormatter.FormatWithCache() with null instance of FormatCache type. That successfully gave me expected value.

var localizedString = await LocalizationSettings.StringDatabase.GetLocalizedStringAsync("TableName", "TableEntryName");
            FormatCache formatCache = null;
            localizedString = LocalizationSettings.StringDatabase.SmartFormatter.FormatWithCache(ref formatCache, localizedString, null);
1 Like

just sent bug report! IN-9501-StringDatabase.GetLocalizedStringAsync() failed to evaluate SmartString using Custom ISource.

Also, I attached example project here.

Expected :

LocalizationTestScript on GameObject print two logs on console

“Hello Jemin!”
“Hello Jemin!”

What actually happens:

StringDatabase.GetLocalizedStringAsync() failed to evaluate {AccountData.NickName}, throwing exception. And LocalizationTestScript print logs like this

“Hello Jemin”
“Null”

8259594–1081353–Localization Bug Sample.zip (90.3 KB)

1 Like

Found the reasons!

Call LocalizedString.GetLocalizedStringAsync()

  • calls m_Database.GenerateLocalizedString() passing arguments as null (m_arguments is null).

Call StringDatabase.GetLocalizedStringAsync()

  • it calls m_Database.GenerateLocalizedString() passing arguments with zero size array object.

both of them ends up calling SmartFormatter.FormatWithCache().

public string FormatWithCache(ref FormatCache cache, string format, IFormatProvider formatProvider, IList<object> args)
{
    args = args ?? k_Empty;
    using (StringOutputPool.Get(format.Length + args.Count * 8, out var output))
    {
        if (cache == null)
            cache = FormatCachePool.Get(Parser.ParseFormat(format, GetNotEmptyFormatterExtensionNames()));
        var current = args.Count > 0 ? args[0] : args; // The first item is the default.
        var formatDetails = FormatDetailsPool.Get(this, cache.Format, args, cache, formatProvider, output);
        Format(formatDetails, cache.Format, current);
        FormatDetailsPool.Release(formatDetails);

        return output.ToString();
    }
}

In first case, args is null. So it’s value will be replaced with k_Empty. k_Empty is 1-size array holding null as item.
And value of variable ‘current’ will be null, which Format method can handle.

But in second case, args remains as zero size array. So value of ‘current’ will be zero size array. And Format method couldn’t handle that.

1 Like