2.9.16
Coherent GT
A modern user interface library for games
Sample - Localization

This sample demonstrates how to use Coherent GT's localization facility and have the text of certain elements in your HTML page translated according to your own needs.

Note
Supplied code is not representative for a well-designed, high-performance solution. It's written for simplicity and its purpose is to demonstrate features of Coherent GT.

Building the sample

The sample solution is based on a minimal game framework that provides basic functionality. The sample is already configured and you only have to compile and run it.

The output will be in the Coherent/bin directory.

Prerequisites

This sample builds upon the HelloGT sample and assumes that you understand it.

Key points

  1. CSVLocalizationManager is an custom implementation of Coherent::UIGT::LocalizationManager that knows how to read the data out of several csv files.
  2. There is a _.csv_ file containing the translations for each language. In a real application a comma-separated list would not be the best format since it does not allow for new lines and commas but suffices for demonstrating purposes. For production, you might want to use JSON for example.
  3. Changing the language of the UI dynamically.

Sample walk-through

The sample shows a simple vertical menu, each button of which has the data-l10n-id attribute as well as dynamically inserting one more button in the same menu. This demonstrates how Coherent GT uses the custom CSVLocalizationManager to set the text content of each button. Additionally, the sample shows how to change the language at runtime and update the UI to match.

We start off by creating each of the localization files. For instance localization/en.csv contains:

New Game, Start a new game
Load Game, Load game
Settings, Settings
Quit, Quit

The other files under localization exhibit the same structure. Note that each file must be encoded in UTF-8 so that all symbols are preserved.

Next, we add the data-l10n-id attribute to any element that needs translation:

<button data-l10n-id="New Game"></button>
<button data-l10n-id="Load Game"></button>
<button data-l10n-id="Settings"></button>
<button data-l10n-id="Quit"></button>

We will implement the CSVLocalizationManager step by step.

Start by adding some data members that hold our translation table, the directory containing the localization files and an extra string that lets the strings returned from Translate outlive the method.

// Coherent GT requires that this class is thread-safe
class CSVLocalizationManager : public Coherent::UIGT::LocalizationManager
{
private:
const std::string m_LocalizationDirectory;
std::unordered_map<std::string, std::wstring> m_CurrentTranslations;
// The string returned from Translate must outlive the function so
// we'll use a data member to make sure this happens.
std::string m_CurrentTextContainer;
};

Initially, we load the translation table for English and store the directory containing our CSVs.

CSVLocalizationManager::CSVLocalizationManager()
: m_LocalizationDirectory("localizations/")
{
// If you change the default language, you'll see that the page
// will load with the proper translations the next time you start the sample
ChangeLanguage("en");
}

We expose a ChangeLanguage method that causes a new translation table to be loaded from the matching CSV file. We take special care to load all data in wide strings to preserve the Unicode encoding.

void CSVLocalizationManager::ChangeLanguage(const std::string& language)
{
const std::string validLanguages[] = { "en", "fr", "bg", "ch", "mi" };
auto isValid = std::find(std::begin(validLanguages), std::end(validLanguages), language) != std::end(validLanguages);
assert(isValid);
std::string localizationFilename = language + ".csv";
LoadTranslations(localizationFilename);
}
void CSVLocalizationManager::LoadTranslations(const std::string& localizationFilename)
{
std::string localizationFilepath = m_LocalizationDirectory + localizationFilename;
std::wifstream file(localizationFilepath);
assert(file);
// Take special care to handle UTF-8
const std::locale empty_locale = std::locale::empty();
// the locale is responsible for calling the matching delete from its own destructor
const std::codecvt_utf8<wchar_t>* converter = new std::codecvt_utf8<wchar_t>;
const std::locale utf8_locale = std::locale(empty_locale, converter);
file.imbue(utf8_locale);
std::wstring line;
while (std::getline(file, line))
{
auto firstCommaIndex = line.find(',');
if (firstCommaIndex == std::wstring::npos)
{
continue;
}
auto key = line.substr(0, firstCommaIndex);
// It's safe to assume that the keys are ASCII-encoded
// so conversion via std::string's consturctor is acceptable
std::string keyStr(key.begin(), key.end());
auto value = line.substr(firstCommaIndex + 1);
m_CurrentTranslations[keyStr] = value;
}
}

Finally, override Coherent::UIGT::LocalizationManager::Translate so that it lookups the required translation in the current translation table and returns it UTF8-encoded.

const char* CSVLocalizationManager::Translate(const char* text)
{
auto translation = m_CurrentTranslations.find(text);
assert(translation != m_CurrentTranslations.end());
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
m_CurrentTextContainer.assign(converter.to_bytes(translation->second));
return m_CurrentTextContainer.c_str();
}

We now need to setup the localization manager and our system:

Options options = {0};
CSVLocalizationManager localizationManager;
options.LocalizationManagerInstance = &localizationManager;
// System initialization is hidden inside app.Initialize
Application app(options);
App.initialize(width, height, "Sample_Localization", false);

To finish the sample, we need to setup our JavaScript code to call CSVLocalizationManager::ChangeLanguage.

Bind the method:

view->BindCall("ChangeLanguage",
Coherent::UIGT::MakeHandler(&localizationManager, &CSVLocalizationManager::ChangeLanguage));

Next, add the buttons that will let us change the language. We'll store the language the button sets in the data-language attribute.

<div id="change-localization-menu">
<h3>Select language:</h3></br>
<button data-language="en">English</button>
<button data-language="fr">Français (French)</button>
<button data-language="bg">Български (Bulgarian)</button>
<button data-language="ch">中国 (Mandarin)</button>
<button data-language="mi">Maori</button>
</div>

When any of the buttons is clicked, call ChangeLanguage and when that's done, update all localized elements using engine.reloadLocalization.

var changeLanguage = function (language) {
engine.call('ChangeLanguage', language).then(function () {
engine.reloadLocalization();
});
};
var onLanguageChanged = function (eventArgs) {
var button = eventArgs.target;
// Change the current language to the value of
// data-language of the current button
changeLanguage(button.dataset.language);
};
var localizationChangers = document.querySelectorAll('#change-localization-menu button');
for (var i = 0; i < localizationChangers.length; i++) {
var button = localizationChangers.item(i);
button.addEventListener('click', onLanguageChanged);
}

This completes the tutorial - clicking on the language buttons will change the active language and update the translated text.