Unreal Engine & Localization

The Unreal Engine has some rather nifty functionality for dealing with localization. We have also made numerous modifications to facilitate our workflow, but I’ll stick to what is in vanilla Unreal (this is based on 4.25).

There are three different string types to use within Unreal:

  • FString: These are traditional strings most people are used to. When creating a new string it will allocate memory to store the data. Equality comparisons need to check the actual string data.
  • FName: When you create a name, it is stored in a table in memory permanently (there is no clean up of unused names). Two FNames with the same value point to the same index, which makes equality comparisons very fast: they just compare indices. But the memory usage can grow without bound.
  • FText: These are localized strings that go through a process of being gathered, translated, and having their values stored in a separate file called a locres file. When evaluating an FText, there is a lookup for the actual value to use.

Epic provides a tokenization system using curly braces, { and }, where you can provide parameters (either positional or named) for an FText format string.

However, we added another one using square brackets, [ & ], where we would take the token within and look up the value as an FName in various tables that were provided to look up information.

The kind of values we would look up are:

  • Behemoth information
  • Weapon or armour information
  • Keyboard or controller inputs to use

This was particularly useful for keyboard or controller inputs as it supported looking up the keyboard or controller customizations the player may have set so we can display that on screen.

What we had originally implemented was to resolve the tokens as an FString and then convert to an FText. However, we discovered an issue with this. There is a debug command using tilde (~) to change the culture/language of the game. When we do the transformation as an FString to FText these strings bypass getting updated to reflect a culture change so they remain the same language. This was less than ideal for testing. The other was that we added a means to highlight invariant strings so we could identify strings that were missing getting marked as for translation. These were showing up erroneously as invariant.

Our original implementation took an array of interfaces that implemented a method for converting an FName to the corresponding string substitution (returning true or false if successful).

The change was to implement ITextGenerator interface as a class and it would take the array and convert them into an array of TWeakInterfacePtr of the given interface that did the substitution with additional validity checks and stored the text with tokens to substitute in OriginalText. Then we implemented the virtual functions:

	/**
	 * Produces the display string. This can be called multiple times if the language changes.
	 */
	virtual FString BuildLocalizedDisplayString() const override
	{
		return ReplaceTokens(OriginalText.ToString());
	}

	/**
	 * Produces the display string for the invariant culture.
	 */
	virtual FString BuildInvariantDisplayString() const override
	{
		return ReplaceTokens(OriginalText.BuildSourceString());
	}


Then we replaced the code based on converting an FString to FText to the following:

	TSharedRef<ITextGenerator> Generator = MakeShared<FArchonTokenSubstitutionTextGenerator>(OriginalText, Containers);
	FText NewText = FText::FromTextGenerator(Generator);
	return NewText;

We hope others can save themselves time by learning from our own misadventures in Text!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.