Page 1 of 1

Localization / Translation approaches

Posted: 25 Jan 2019, 22:46
by ai_enabled
Hi guys,

I'm starting my work on CryoFall localization soon and researching the localization approaches. I've checked the forums (outdated topic) and NoesisGUI localization sample on Github and here are my thoughts on each approach I've thought about:

1. NoesisGUI approach from the sample which is using {Binding MyLocalizationKey} is not applicable for applications using MVVM pattern as DataContext is already used for ViewModels. Moving localization text data into the ViewModels is not a great idea as in many cases the text entries could be reused in multiple places but ViewModel is usually done for a few specific cases - also I don't like the idea of polluting the ViewModels with localization stuff.


2. XAML Markup extensions (example implementation for localization):
<TextBlock Text="{loc:Traslate MyLocalizationKey}" />
could be a good idea to inject localization but it's not supported in NoesisGUI C# API (#1401).


3. Approach with attached properties (custom localization service) and strong-typed enum usage:
<TextBlock locService.Localize="MyLocalizationEnum_SomeKey" />
I've used it in the previous game (VoidExpanse) and I don't like it for multiple reasons:
  • it's too verbose/lengthy
  • the attached property service should recognize various controls and know how to properly inject the localization (e.g. in case of TextBlock it should use the Text property but in case of ContentControls it should use Content property). It also means that injecting localization into tooltip is hard (all localizatible tooltips should be redone as explicit Tooltip entry with a TextBlock using the localization service).
  • there could be only a single enum type (though it's possible to use x:Static to support different enum types (which is already supported) but then the syntax is even more lengthy!)
  • not compatible with {Binding StringFormat=...} which is a very useful feature
  • introduces a level of indirection as we cannot simply Ctrl+Click to navigate to the original text - the original text is stored in a separate file (could be a simple text file as in VoidExpanse) where each string has a key matching the enum key (which is also very fragile and error-prone - easy to forget to rename the key in text file after renaming the key in enum, etc).

4. x:Static approach to reference C# const string:
<TextBlock Text="{x:Static MyClass.MyTextConstName}" />
alas, it's not yet supported (#1035).


5. Simply define all localizable data in a XAML resource dictionary (one per language, e.g. "UI.en_us.xaml" for US English, "UI.ru_ru.xaml" for Russian, etc) and use StaticResources to reference localizable strings (and probably other resources such as images). Depending on the language the application can (re)load NoesisGUI and provide the localization resource dictionary for the selected language.
I see a potential issue with XAML loading as the localization strings resource dictionary should be loaded as a global resource dictionary (like in WPF App-wide resources) before loading any other resource dictionaries and controls which require these strings but this feature is not yet supported (#1379).
This approach is also requires some extra effort as I don't want to edit localization data by hand - it's expected to be delivered in CSV format to the game publisher for translation so an import-export tool is required.


My current best idea - if I could use x:Static in XAML to reference C# const string entries it will be sufficient for XAML localization and I will not need to spend extra time on implementing tooling for XAML localization import/export. I can just write the required C# localization tooling (which will use Roslyn compiler to rewrite string constants and localizable C# properties - something I can do relatively easily as the C# code is already pre-processed and compiled by the game with Roslyn!). So with this approach, it will work for both C# and XAML localization (as XAML will reference C# string const's) and will save me a lot of time and effort. But it requires support from NoesisGUI (#1035) I'm curious if it could be done before NoesisGUI 2.2 release.

Would love to hear your ideas regarding the better localization approach.

Regards!

Re: Localization / Translation approaches

Posted: 27 Jan 2019, 21:51
by nokola
This is my approach, has been working great and solves most of the issues you mentioned (except string formatting)
 <TextBlock Text="fullscreen" t:Translate.Text="fullscreen" 
     Margin="2,4,2,2" HorizontalAlignment="Center" TextWrapping="Wrap" TextAlignment="Center"/>

<Button x:Name="btnOK" Content="Hello World" t:Translate.Content="helloWorld" 
     HorizontalAlignment="Center" MinWidth="130" MinHeight="60" Margin="0,10,0,0"/>

I use attached properties similar like your solution 3, however I do the following in addition:
1. the .Content vs .Text property are exactly the same except they set .Text and .Content respectively
2. I always put original english string in XAML - easy to reference during development; used as fall back in case translation does not load at runtime for whatever reason
3. Having the static property name as a verb command e.g. "Translate.Content" is intuitive since it translates the Content property - easy for others to pick up and reason about it.
So far works great. 40+ languages and 500+ resource strings that I load dynamically at startup. Hope it helps!

Edit: the t.Translate.Text and .Content contain a key that is used in a Dictionary<string, string> to find the actual translation at runtime. Updated example above (with "Hello World" "helloWorld" to better illustrate.)

Re: Localization / Translation approaches

Posted: 30 Jan 2019, 11:06
by sfernandez
Hi, these are my notes on some of the approaches:

1. Even if you are using MVVM is it possible to use bindings for localization, if you have access to a global Localization object from all of your view models. I know this is not very clean, but I just want it to mention so no one discards this option because it cannot be used. In the following snippet I'm assuming that the base class for all my view models exposes a Loc property, which returns an object that implements the this[string key] to return localized strings.
<TextBlock Text="{Binding Loc[AppTitlte]}"/>
4. Alhough there is no support for static members in Noesis yet, there is a non standard workaround I mentioned in other posts. In Noesis you can use the default value of a DependencyProperty with the x:Static extension. So until we implement fully support for static members, you can define a class with all your const strings and DP using those strings as default values. It requires more code but I think it is easy to create a snippet to automatically generate it:
namespace Localization
{
    public class Translate : DependencyObject
    {
        public const string OkButton = "Ok";
        private static readonly DependencyProperty OkButtonProperty = DependencyProperty.Register(
            "OkButton", typeof(string), typeof(Translate), new PropertyMetadata(OkButton));
    }
}
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:loc="clr-namespace:Localization">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBlock Text="{x:Static loc:Translate.OkButton}"/>
        <Button Content="{x:Static loc:Translate.OkButton}"/>
    </StackPanel>
</Grid>

Re: Localization / Translation approaches

Posted: 06 Mar 2019, 01:26
by ai_enabled
Thank you, guys, for your replies!

I've thought a lot on the best approach and settled with the approach #4 ("x:Static approach to reference C# const string") from my first post.
We also need to localize strings in C# code and I really dislike .NET resources files (I mean *.resx; if we have chosen them, imagine pain which game modders will experience while trying to localize their mods). With this approach, I've just moved all localizable XAML strings into C# public string constants (there are just a few hundreds of them!).

I've made a small ReSharper extension (actually, I've completely redone my old extension used for our previous game's localization) which provides very useful features to make XAML and C# localization easy. Basically, localization with this tool is just going through the ReSharper detected errors/warnings in the VS project and executing context action "extract this string to a const string in this class or shared UI strings class" (for XAML or C# code) or "ignore this string". It's also smart enough to ignore logger calls and some other stuff (though most of it is specific for our game). I will share the ReSharper extension code soon on my Github and will post a link here.

Regarding x:Static feature which is not yet implemented in NoesisGUI - fortunately, NoesisGUI SDK XamlProvider approach allows to preprocess the XAML files before providing them to NoesisGUI. So, the game loads each XAML file into a string object, detects all the places where x:Static is linking a string from a C# class via Reflection - and simply injecting the required string into XAML replacing the {x:Static ...} entry. Even though there are more than a hundred of localizable XAML files, the performance is practically not affected as initial parsing is quick (thanks to Reflection cache) and I've also implemented a file cache so the subsequent access for the same XAML file will return the already preprocessed result (the cache is invalidated in case the user have changed the language; whole UI is reloaded in this case; all C# code is also recompiled to inject the translated const strings - thanks to the Roslyn compiler integrated into the game engine, such a life saver!).

It's not that simple for multiline text (or text containing " quotes) as injecting it could break XAML structure - but in my case, it's not a problem as the game also has a "BBCode"-like parser so I simply replace all \n with a [br] tag - and the custom textblock control uses the parser to populate the Inlines collection with the rich text. If you're curious, you will be able to look on the code for BBCode parser and custom textblock control implementation in the next version of the game (in its open source part as it's not hardcoded into the game engine).

Regards!

Re: Localization / Translation approaches

Posted: 08 Mar 2019, 11:53
by jsantos
So, at the end you are not following the approach #4 right? Because you remove all x:Static entries at runtime.
It's not that simple for multiline text (or text containing " quotes) as injecting it could break XAML structure - but in my case, it's not a problem as the game also has a "BBCode"-like parser so I simply replace all \n with a [br] tag - and the custom textblock control uses the parser to populate the Inlines collection with the rich text. If you're curious, you will be able to look on the code for BBCode parser and custom textblock control implementation in the next version of the game (in its open source part as it's not hardcoded into the game engine).
Oh, that BBCode parser looks interesting! Awesome job.

Re: Localization / Translation approaches

Posted: 08 Mar 2019, 12:20
by ai_enabled
So, at the end you are not following the approach #4 right? Because you remove all x:Static entries at runtime.
That's correct. In the runtime they're replaced with the actual string (a const value acquired via Reflection) but this is a hack until you implement an x:Static support for const/static public fields :-) .
Oh, that BBCode parser looks interesting! Awesome job.
It was the most surprising part - we never considered to have it and all the XAML text was usually formatted by using Run, LineBreak, etc. Once we needed to display a formatted text via data binding from ViewModel - and the text was provided as a plain string. So I've quickly hacked a very basic BBCode parser supporting most basic tags (such as b/i/u and br). Turns out it was so useful that we've actually started using it everywhere and I've even replaced all the TextBlock LineBreak/Run and other stuff with plain strings using BBCode for formatting! With a custom textblock control it's so easy - all the parsing and formatting is done automatically.
As a happy coincidence localizing such text in XAML is also extremely simple as they're plain strings.

Usage example:
Image
Image

Regards!

Re: Localization / Translation approaches

Posted: 08 Mar 2019, 23:09
by skeletorXVI
For my WPF applications I chose a binding oriented approach. I am using a localization class, that uses a custom serialization logic and attributes to serialize the localization to JSON. To improve organization I create a nested class with public string properties for each view that requires localization. The class is only instantiated once by the view model locator and then used in any view like this:
{Binding Localization.View.Label, Source={StaticResource Locator}}
Although I have not tried my code with Noesis yet, according to the documentation the Binding.Source property is supported. The advantage of my approach is that the language can be changed at runtime in real time. In addition JSON allows any user to modify the localization without special tools or knowledge.