Localization Guide

Localization of Prysm resources in Unreal Engine

The purpose of this tutorial is to show you how to use the built-in localization feature of UE4 with Prysm in a few simple steps. For a localization-ready sample, you can check our localized menu map - MenuLocalization, located in the CoherentSample sample project. At the end of this tutorial, you will know everything you need to create a localized UI with Prysm.

The localization workflow is the following:

  1. In the UI HTML page, declare the text that will be translated.
  2. Generate Translation manifest and archive files.
  3. Enter the translation strings for texts that will be translated.
  4. Generate the translation manifest and archive files again to rebuild the binary localization file.
  5. Change the engine’s current culture and do the C++/HTML binding.
  6. Enable Prysm’s localization facility.

Step 1: Declaring strings that will be translated

In this step, we will tag the buttons that will have their text translated in HTML and declare that we want a translation for a text entry.

The Menu map loads the coui://UIResources/LocalizedMenu/menu.html page, which will be our starting point. On the page you can find some CSS includes, JavaScript includes and most importantly – the 3 menu buttons. In the button tag of the start button, we will add a property called data-l10n-id and set its key to MenuNamespace.NewGameButton e.g.

<button id="play" class="btn btn-large btn-primary" type="button"data-l10n-id="MenuNamespace.NewGameButton">Play</button>

Now we have to declare that we want a translation for the “NewGameButton” key. In the menu.html file we will add a comment containing the following declaration:

<!--NSLOCTEXT("MenuNamespace", "NewGameButton", "NewGameButton translation in every language")-->

It doesn’t matter where on the HTML page the comment is located. Now, what is this C++-like declaration and why do we need it? NSLOCTEXT is a C++ macro, used by Unreal that declares a new localization text. It takes 3 parameters:

  1. Localization Namespace – it allows localization texts to be grouped together, and thus allows for easier bookkeeping – e.g. all button names can be in the “MenuNamespace”.
  2. Key – a unique identifier for a namespace, used together with a namespace to identify a localized text entry.
  3. Text Literal – the text literal is a sample text that is used to describe the key. If a translation for the current Culture is not found, the Text Literal will be used instead.

Step 2: Generate translation manifest and archive files

The Unreal Engine localization system works with three types of files:

  • .manifest - contains JSON code describing namespaces, keys, text literals and the file/line where the localized text is. Most probably they are used to generate .archive files.
  • .archive - it has a very similar structure to the .manifest file, but instead of the location of the localized text, it contains the translation of the localized text for a specific language.
  • .po - it does the same job as the .archive files but in another well-known localization format. It also contains the translation of the localized text for a specific language.
  • .locres - a binary file that contains the binary representation of the localized texts for a specific language. The Unreal localization system can only read from .locres files.

Now, back to the practical part. The generation of the manifest, archive and locres files happens with the help of an Unreal Engine commandlet called GatherText. Commandlets are mini-programs with a specific purpose. In this case, the commandlet reads a configuration file and executes the steps defined in it. The commandlet is called with:

PathToEditor\UE4Editor.exe FullPathToYourGame\YourGame.uproject -run=GatherText -config=FullPathToYourGame\Config\Localization\YourGame.ini

or in our case:

E:\unreal\Engine\UE4\Engine\Binaries\Win64\UE4Editor.exe E:\unreal\CoherentSample\CoherentSample.uproject -run=GatherText -config="E:\unreal\CoherentSample\Config\Localization\CoherentSample.ini" -log=localization.log

The configuration Localization/CoherentSample.ini file contains a list of settings and series of steps that will be executed by the GatherText commandlet. The configuration that I have used is the following:

[CommonSettings]
SourcePath=./Content/Localization/CoherentSample
DestinationPath=./Content/Localization/CoherentSample
ManifestName=CoherentSample.manifest
ArchiveName=CoherentSample.archive
ResourceName=CoherentSample.locres
PortableObjectName=CoherentSample.po
NativeCulture=en
SourceCulture=en
CulturesToGenerate=en
CulturesToGenerate=fr

;Get all FTexts from source code
[GatherTextStep0]
CommandletClass=GatherTextFromSource
SearchDirectoryPaths=./Source/CoherentSample
SearchDirectoryPaths=./Content/uiresources
;File extensions that will be parsed
FileNameFilters=*.cpp
FileNameFilters=*.h
FileNameFilters=*.html

;Gather text from assets in content.
[GatherTextStep1]
CommandletClass=GatherTextFromAssets
IncludePaths=./Content/
ExcludePaths=*/Content/Localization/*
PackageExtensions=*.umap
PackageExtensions=*.uasset

;Create manifest with all gathered source text.
[GatherTextStep2]
CommandletClass=GenerateGatherManifest

;Generate Archives
[GatherTextStep3]
CommandletClass=GenerateGatherArchive
bPurgeOldEmptyEntries=true

;Export new source from existing archives into PO (portable object) files.
[GatherTextStep5]
CommandletClass=InternationalizationExport
bExportLoc=true

;Import new translations from PO (portable object) files into existing archives.
[GatherTextStep6]
CommandletClass=InternationalizationExport
bImportLoc=true

;Compile source text and translations into binary form for use by the application.
[GatherTextStep7]
CommandletClass=GenerateTextLocalizationResource

Most of the options in the configuration file are self-explanatory, but there are some things that should be pointed out:

  • CulturesToGenerate=en, CulturesToGenerate=fr and CulturesToGenerate=bg – used to generate English and French localizations. For every language/locale you want to have localization for, you must add a row in the config file with the language/region code.
  • SourceFileSearchFilters=*.html – used to tell the parser to look inside .html files.
  • SourcePath and DestinationPath pointing to ProjectName/Content/Localization/CoherentSample – this is a default path used to find .locres files by the engine. If your files are in it, they should be automatically loaded by the engine.
  • The search filters for this samples are set to .h, .cpp, .html.

Step 3: Enter the translation strings for texts that will be translated

After you have run the commandlet, in

`ProjectName/Content/Localization/CoherentSample`

In order for these translations to take effect in your game, in DefaultGame.ini you should add:

[Internationalization]
+LocalizationPaths  /Content/Localization/CoherentSample

[/Script/UnrealEd.ProjectPackagingSettings]
InternationalizationPreset=English
+CulturesToStage=en
+CulturesToStage=fr
+CulturesToStage=bg
DefaultCulture=en

Now we would like to translate the key for NewGameButton (from section 1) into French. In order to do so, navigate to ProjectName/Content/Localization/CoherentSample/fr and open CoherentSample.archive. In the Translation field, you should enter the translation of Start Button in French.

"Subnamespaces": [
    {
        "Namespace": "MenuNamespace",
        "Children": [
            {
                "Source":
                    {
                        "Text": "Start new game"
                    },
                    "Translation":
                    {
                        "Text": "Commencer une nouvelle partie"
                    },
                    "Key": "NewGameButton"
            }
        ]
    },
]

Step 4: Generate translation manifest and archive files again

After we have filled the empty translation fields, we have to run the same commandlet again:

E:\unreal\Engine\UE4\Engine\Binaries\Win64\UE4Editor.exe E:\unreal\CoherentSample\CoherentSample.uproject -run=GatherText -config="E:\unreal\CoherentSample\Config\Localization\CoherentSample.ini" -log=loc.log

Once the translation strings are filled, re-running the commandlet will not erase the existing translation entries and will re-generate the binary .locres file. If you are not sure if the French translation made it to the .locres file, you can open the ProjectName/Content/Localization/CoherentSample/fr/CoherentSample.locres file with a hex editor and inspect its contents.

Step 5: Change the engine’s current culture and do the C++/HTML binding.

After we have generated the .locres files with the localized tests, it is time to head to the UI again and bind it to the game.

HTML

Prysm will automatically translate the elements tagged with data-l10n-id using the default locale. To dynamically change the language, we need to create a few buttons:

// buttons for changing localization in menu.html
<input type="button" id="en" class="btn language-menu-item selected-language" value ="English">
<input type="button" id="fr" class="btn language-menu-item" value="Francais (French)">

And then add JS code to add binding and localization reloading:

// js for changing localization in menu.html
var changeLanguage = function (language) {
    engine.call('ChangeLanguage', language).then(function () {
        engine.reloadLocalization();
    });
};

// Attach click handlers
var onLanguageChanged = function (eventArgs) {
    var button = eventArgs.target;
    // Change the current language to the value of the data-language of the current button
    changeLanguage(button.id);
    // Move the selected-language class to the current button

    var buttons = document.getElementsByClassName('selected-language');
    for (var i = 0; i < buttons.length; i++) {
        var prevbutton = buttons.item(i);
        prevbutton.classList.remove('selected-language');
    }

    button.classList.add('selected-language');
};

var localizationChangers = document.getElementsByClassName('language-menu-item');
for (var i = 0; i < localizationChangers.length; i++) {
    var button = localizationChangers.item(i);
    button.addEventListener('click', onLanguageChanged);
}

C++

  • Setup methods for changing the current culture. You can find them in CoherentSampleLocalizationHUD
    void ACoherentSampleLocalizationHUD::ChangeLanguage(FString Language)
    {
        FInternationalization::Get().SetCurrentCulture(Language);
    }
  • Bind these methods to the UI
    // In the HUD's constructor
    CohtmlHUD->ReadyForBindings.AddDynamic(this,
       &ACoherentSampleLocalizationHUD::BindUI);

    void ACoUIGTTestFPSMenuHUD::BindUI()
    {
        CohtmlHUD->GetView()->BindCall("ChangeLanguage",
            cohtml::MakeHandler(this, &ACoherentSampleLocalizationHUD::ChangeLanguage));
    }

Step 6: Enable Prysm’s localization facility.

Localization is disabled by default in Prysm. To enable it, navigate to Prysm -> Options inside the Unreal Engine Editor and check Enable Localization. By doing this, Prysm will automatically translate any elements tagged with the data-l10n-id attribute.