Search Unity

Feedback Nested localization working only with persistent variable source.

Discussion in 'Localization Tools' started by karliss_coldwild, Nov 28, 2022.

  1. karliss_coldwild

    karliss_coldwild

    Joined:
    Oct 1, 2020
    Posts:
    602
    From what I understand by reading previous posts in this forum and also a day debugging why it works in the examples but not in my code turns out nested localization (like the stuff used in item color exmple "{item} es {item:{color}}") works only with persistent variable source, but not with any other variable source like the positional arguments "{0} es {0:{1}}" .

    This is especially surprising with something like dictionary source or reflection source where the translation string with placeholders can look identical to how it looks with persistent variable source. For example which words in "{item} {item2}" will be translated depend what source each of them use even when both of them refer to the same LocalizedString, not the way you write them inside smart string. This counters basic composable interface rule that the expression used for obtaining a value doesn't matter as long as the value is the same. It also seems like a wasted potential since while Unity Localization has complex mechanism for various variable source, if you try to use them you loose functionality.

    As temporary solution I made a source which can be used as modifier to other source for making references to LocalizedString behave more like it does when using persistent variable source. The way you use it is by adding ".loc" suffix after the positional or otherwise passed placeholder variable which refers to a LocalizedString. Like this "{0.loc}", "{0.loc:{1.loc}}" or even "{item.loc}" and "{item.foo.bar.loc}" in situations where item doesn't refer to persistent variable.

    Code (CSharp):
    1.  
    2.     // WARNING 1 ! Hacky solution! Depends on certain Unity Localization implementation details, may easily break after UnityLocalization package update.
    3.    // WARNING 2! This probably doesn't support  the automatic value change tracking, and you will need to manually trigger update after placeholders change.
    4.     // WARNING 3! I have no idea how this behaves in situations where required table entries aren't already loaded or switching languages.
    5.     public class LocStringSource : ISource
    6.     {
    7.         public LocStringSource(SmartFormatter formatter)
    8.         {
    9.             formatter.Parser.AddOperators(".");
    10.             // not sure how exactly variable source parser works. This currently probably depends on other sources
    11.             // adding more  stuff to Parser config. Proper solution would require more stuff here.
    12.         }
    13.  
    14.         public bool TryEvaluateSelector(ISelectorInfo selectorInfo)
    15.         {
    16.             var current = selectorInfo.CurrentValue;
    17.             var selector = selectorInfo.SelectorText;
    18.  
    19.             if (current is LocalizedString ls &&
    20.                 selector == "loc" &&
    21.                 selectorInfo is FormattingInfo formatInfo && // hack: casting ISelectorInfo to FormattingInfo
    22.                 formatInfo.Parent != null)
    23.             {
    24.                 var parentFormat = formatInfo.Parent;
    25.                 var tmp = selectorInfo.CurrentValue;
    26.                 formatInfo.CurrentValue = parentFormat.CurrentValue; // hack: manipulation of CurrentValue
    27.                 selectorInfo.Result = ls.GetSourceValue(selectorInfo);
    28.                 formatInfo.CurrentValue = tmp;
    29.                 return true;
    30.             }
    31.  
    32.             return false;
    33.         }
    34.     }
    I have seen others trying to achieve similar thing by subclassing LocalizedString and overriding ToString() method. I don't think that approach supports context based placeholder variables inside the nested translation.

    I also considered custom formatter instead of source for this. That probably wouldn't need the hacky ISelectorInfo->FormattingInfo cast. But it wouldn't support adding formatter on top of nested translation string.


    After spending a day digging through the internals of Localization I kind of understand where Unity devs where going focusing on the approach which supports automatic refresh when dependencies get modified, and also avoiding the reflection which can have performance penalty (depending on your overall constraints). I also don't encourage excessive digging through the properties using reflection source, as that's just unnecessary coupling between translation text and code details which can potentially introduce unnecessary work when refactoring code.

    But at the same time not every game has such tight performance constraints. And having to implement a bunch of boilerplate code for interfaces used by translation system can be more work than the benefits they provide over dumb approach of being able to do
    string.GetLocalizedString(arg1, arg2, arg3)
     
  2. karl_jones

    karl_jones

    Unity Technologies

    Joined:
    May 5, 2015
    Posts:
    8,282
    Interesting idea. Nesting can be tricky ;)
    Improving the nesting support is something we would like to do in the future. Thanks for the feedback.