Cannot create LocalizedString with multiple Arguments in script

I wanted to be sure I am not missing something here :

I am trying to set the StringReference of a LocalizeStringEvent in script, with multiple Arguments.
My table entry with “Smart” option enabled looks like this: “{FullName} : {Value}”

_myLocalizeStringEvent.StringReference = new LocalizedString("my_table_key", "my_entry_key")
                {
                    Arguments = new List<object> { user, message }
                };

Where the “user” variable contains a “FullName” properry, ans the “message” variable contains a “Value” property.

I am getting the exception :
Exception

Exception [UnityEngine.Localization.SmartFormat.Core.Formatting.FormattingException: Error parsing format string: Could not evaluate the selector “Value” at 14
{FullName} : {Value}
--------------^

If I invert the arguments like so :

_myLocalizeStringEvent.StringReference = new LocalizedString("my_table_key", "my_entry_key")
                {
                    Arguments = new List<object> { message, user }
                };

I am getting the exception :
Exception

Exception [UnityEngine.Localization.SmartFormat.Core.Formatting.FormattingException: Error parsing format string: Could not evaluate the selector “FullName” at 1
{FullName} : {Value}
-^

So it looks like only the first Argument in the list is used for interpolation.

I looked a bit in the code and it looks like this is exactly what is done in SmartFormatter.cs :

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 this line:

        var current = args.Count > 0 ? args[0] : args; // The first item is the default.

So I am wondering what is the point of passing a List for the LocalizedString Arguments when only the first element should contain the values for Interpolation ?
Please tell me if I missed anything here.

Here is the workaround I found :

  • Change my table entry to : “{Values[0].FullName} : {Values[1].Value}”
  • Create a class containing a list of values :
public class LocalizationInterpolationValueList
{
   public IList<object> Values { get; }

   public LocalizationInterpolationValueList(IList<object> values)
   {
      Values = values;
   }
}
  • Create the LocalizedString like so :
var values = new List<object> { user, message };
_localizedMessageText.StringReference = new LocalizedString(LocalizationUtils.UI, notification.Message)
{
    Arguments = new List<object> { new LocalizationInterpolationValueList(values) }
};

And now the 2 values in my string are correcly interpolated and localized, but it is still a bit weird that you must have only one item in the list of Arguments, and I have found no documentation stating this.

Best Regards

The first item in the list is considered the default, so when you don’t provide an index it uses that.
So your example “{FullName} : {Value}”
Is doing “{0.FullName} : {0.Value}”

So if you want to examine item 2 you can do

“{FullName} : {1.Value}”

You can also pass in a tuple as an argument and it will examine all the values as if they were default.

E.g
Arguments = new List { (user, message) }

Thank you for the quick reply !

I tried the Tuple solution but I get an error saying the ValueTupleSource’s “_formatter” attribute is null (line 9 in code below).

        public bool TryEvaluateSelector(ISelectorInfo selectorInfo)
        {
            if (!(selectorInfo is FormattingInfo formattingInfo)) return false;
            if (!(formattingInfo.CurrentValue != null && formattingInfo.CurrentValue.IsValueTuple())) return false;

            var savedCurrentValue = formattingInfo.CurrentValue;
            foreach (var obj in formattingInfo.CurrentValue.GetValueTupleItemObjectsFlattened())
            {
                foreach (var sourceExtension in _formatter.SourceExtensions)
                {
                    formattingInfo.CurrentValue = obj;
                    var handled = sourceExtension.TryEvaluateSelector(formattingInfo);
                    if (handled)
                    {
                        formattingInfo.CurrentValue = savedCurrentValue;
                        return true;
                    }
                }
            }

            formattingInfo.CurrentValue = savedCurrentValue;

            return false;
        }

How can I add or create this Formatter ?

Also, will their be a conflict if some items in the Tuple contain properties with the same name ? What would happen then ?

Regards

Could you please file a bug report for that error so we can look into it?
Is the formatter added in the Localization settings? It is under String Database/Smart Format/Sources. The list should have a TupleFormatter in it, probably just under the List Formatter would be a good place.

Yes there could be a conflict, it will check them in order starting with the first item so if the one before happens to have the same field then it may use the wrong one. I would use indexes to be safe instead of tuples.
In the next release we have support for local variables which work like global variables so you can define each one with a name and reference them that way. We hope to have the next release (1.0.0) out within the next 2 weeks.

Attached are my Localization Settings. There is a Tuple Value Source but I cannot find any “TupleFormatter” anywhere :confused:

Regards

7470386--917801--Capture.JPG

That looks correct, there is no TupleFormatter. That should be all you need.
Can you share an example project with the error?

@karl_jones I had a lot of issues trying to get the Tuple approach to work in the 1.3.1 package (have not tested earlier versions right now) - I debugged this in a fresh project and found that when the ValueTupleSource was trying to evaluate the Selector, the m_Formatter did not have any SourceExtensions to evaluate against?

At the moment I’ve found that I can hack my way around this by removing the ValueTupleSource at runtime and replacing it with a new ValueTupleSource like this…

        LocalizedStringDatabase db = LocalizationSettings.StringDatabase;

        db.SmartFormatter.SourceExtensions.RemoveAll(s => s is ValueTupleSource);
        db.SmartFormatter.AddExtensions(new ValueTupleSource(LocalizationSettings.StringDatabase.SmartFormatter));

Now, the ValueTuple is evaluated correctly and I can check both an object and a dictionary for values. For context, this is what I’m doing to get convert this string “This does both {ObjectValue} and {DictValue}”. The ObjectArg class has a Property called ObjectValue.

        string dblTest = db.GetLocalizedString(TableReference, DblString, arguments: new List<object>
        {
            (new ObjectArg(), new Dictionary<string, string>{{"DictValue", "BLAH"}})
        });

Is there a bug here with the ValueTupleSource or is something likely to be setup incorrectly? Any advise you have here would be greatly appreciated.

EDIT: I went back to my original approach and the Tuple solution seems to work fine there, without any hacks required. I’m not sure what the issue is with my test project except maybe I’m trying to do the string conversion too early? Perhaps things aren’t initialised in time. Either way, it seems the Tuple approach is fine for now but I’ll keep an eye on it

Update: this issue returned, testing in 1.3.2 it seems as though the tuple works fine in editor but breaks in build. In 1.4.2 the Editor throws the same error when trying to use a tuple with an object and a dictionary as its type values. Solution remains to modify the source extensions.

Hmm. Could you please file a bug report so we can look into this?