magic-script/magic-script-components

Localization support for Components

Opened this issue · 0 comments

Results of the research for the Localization support on Lumin, iOS and Android.

TL;DR in my opinion we have two options for Lumin:

  1. (In big shortcut) Use magic-script-cli to send JSON files with translations to cloud and receive *.dat file with ICU translations that will be used by Lumin 'automagically' + add magic-script-components-lumin support for LocalizedText
  2. Use JS library to utilize translations in the runtime (need to have some discussion on this one)

And two for React Native:

  1. Use react-native-localize underneath and add LocalizedText node
  2. Use JS library to utilize translations in the runtime (need to have some discussion on this one)

Idea no. 1

Localization on Lumin

Right now, according to the project that @kpiascik shared with me last week, there are few steps we need to follow, to create properly defined locale.dat file that will be used by Lumin to provide localization support based on the System Locale.
To create such file, properly formatted with ICU standard, the project uses several Linux available tools between *.xlf <-> locale.dat:

Building & Compilation

  1. First of all we have to have *.xliff files, where * stands for ICU country code, such as en, de etc. We also need (probably) the root.xlf file with default translation
  2. Then, we need to execute XLIFF2ICU.jar tool, which converts *.xlf to *.txt files with ICU standard
  3. Next, the genrb tool converts *.txt files into *.res files, and those are packaged with pkgdata tool into locale.dat file. (genrb and pkgdata are Linux tools)

Source code

  1. The Lumin SDK provide special factory methods for UiText node, CreateLocalized and CreateLocalizedEclipseLabel which have Prism, Key and LocaleHelper.Param parameters
  2. The LocaleHelper class is initialized by default with corresponding values:
  • locale: String = 'en'
  • path: String = 'res'
  • file: String = 'locale'
  1. The LocaleHelper.Param is used for passing replaceable values for texts, f.e.
let param = {};
param["QUANTITY"] = new utils.LocaleHelper.Param(10);
new ui.UiText.CreateLocalized(prism, "apples", param)

where apples is key for translated String in locale.dat file, f.e. Today I bought {{QUANTITY}} apples;

Lumin components

Also the Lumin Magic Script Components need to have support for localization, so either we have a new <LocalizedString /> component, or some kind of property flag translatable: true/false for Text, EditText etc.

ReactNative components

If we follow this idea, we should also add support for ReactNative platform with this approach, so having we have to utilize JSON translation files and add middle layer to provide proper Component and properties handling

Conclusion

After digging more into it, I haven't found easy way to utilize several steps for generating locale.dat file with MagicScript CLI with ICU support. Even if we find several equivalent tools we can use on Windows & MacOS, we can't expect developers to have all of that installed. On the other hand, if we somehow add the tools to the MagicScript CLI itself, the amount of the code and unit tests to cover will be a huge deal.

If we want to follow this path, my idea is to add mechanism for generating the locale.dat file in the cloud and download this file to res/ directory. After that we still need to add the support for MagicScript Components Lumin and MagicScript Components React Native

Idea no. 2

i18next

After finding the bottleneck in the first solution, I started looking into JS libraries and what possibilities we have. The i18next library has one mechanism that brought my attention - Creating own plugins

With this approach, there is a chance that we can provide own plugin with MagicScript Components Lumin and MagicScript Components ReactNative (probably we can utilize already created one). Also there is ICU plugin available: So the Localization architecture with this library could be something like this:

MagicScript Components

import i18next from 'i18next';
import ICU from "i18next-icu";

class MagicLocalization {

    constructor(nativeBackend) {
        this.nativeBackend = nativeBackend; // here we are providing proper plugin from the magic-script-components-* library and we are creating an object in either 
    }

    static init(translations) {
        i18next
            .use(ICU)
            .use(nativeBackend)
            .init({...})
    }

    static t(key, params) {
        return i18next.t(key, params)
    }

    static changeLanguage(lng, callback) {
        i18next.changeLanguage(lng, callback)
    }
}

App source code

import React from 'react';
import { View, Text, MagicLocalization } from 'magic-script-components';
import { en, fr, de } from './localization.js'

export default class MyApp extends React.Component {
  constructor (props) {
    super(props);

    this.state = {
      message: props.message
    };
    MagicLocalization.init({ en, fr, de })
  }

  componentDidMount() {
    this.state = {
        apples: 10
    }
  }

  changeLanguage() {
      MagicLocalization.changeLanguage('de', (err, t) => {
        this.state = { germanText: t("key", { apples: 10 })}
      });
  }

  render () {
    const { apples } = state;
    return (
      <View name="main-view">
        <Text textSize={0.1} localPosition={[-0.3, 0, 0]}>
          MagicLocalization.t("key", { param: apples })
        </Text>
        <Text textSize={0.1} localPosition={[-0.3, 1, 0]}>
          {this.state.germanText}
        </Text>
      </View>
    );
  }
}

Conclusion

With this approach we have full support of the framework, probably ready to use plugin for ReactNative, the one thing we need to write is the plugin for Lumin platform to retreive the language.

But also we have to consider if we want to support changing the system language in the runtime and going back to the app (both RN and Lumin). There are callbacks in Lumin SDK to onLocalChanged() and hooks to subscribe/unsubscribe to that event. If we consider this functionality, I will check RN support as well.