Unreal UIs and Localization

As a UI programmer, you must be aware of localization and support it, or you’re going to have some very frustrated translators and players. Localization is a huge topic, but for the purposes of this article we will only talk about the aspects that affect UI design and implementation:

  • Making sure that all in-game text is localizable.
  • Avoiding practices that break localization.
  • Creating a localization-friendly UI.

Making Sure All In-game Text is Localizable

There are a few different variable types in Unreal that are used for text. Each of them has its own specific purpose, and you need to be careful about which you use from the start of your project. It’s a lot harder to change these later on, so it’s worth taking the time to understand the differences between the three.

FText vs FString vs FName

FName FString FText
Case-insensitive Case-sensitive Case-sensitive
Not localized Not localized Localized
Use for IDs ??? Use for all text shown to players

Anything that you want to display to players must be localizable, therefore it must be stored in an FText variable.

You need to be a real zealot about this. If other developers on the team are adding data classes, and they use FName or FString,

Skipping Placeholder Text

There are a lot of places in UIs where your UserWidgets will contain TextBlock widgets, the contents of which will be replaced at run-time from a data asset.

Example of placeholder text that is replaced in-game. It should be marked
as 'culture independent' as shown below.
▲ Example of placeholder text that is replaced in-game. It should be marked as 'culture independent' as shown below.

In this case, by default all of this placeholder text will end up in your localization database, and translators will not know if “Weapon Name” is something to be translated, or something that should be skipped.

Thinkfully it’s possible to mark any FText fields that shouldn’t be translated as Culture Independent.

Setting a button's placeholder text as not
localizable will remove it from localization results, simplifying the
translators' job.
▲ Setting a button's placeholder text as not localizable will remove it from localization results, simplifying the translators' job.

Dealing wth Enums

If you are using Enums and you want to display them in-game, there is a EnumToString function but these will not be localized. You need to create some other way of creating an FText that is associated with them. Usually the best way is to create a helper library that returns an FText when passed the enum.

UFUNCTION(BlueprintImplementableEvent, BlueprintCallable, Category = "Weather")
FText GetTextForWeather(EWeatherType Weather) const;

If you have a centralised Blueprint subclass instance that has this function, you will be able to call it from both C++ and Blueprints, and get the right text for your Enum values. And most importantly these will be localized.

Localizable Text in Code

This is covered pretty well in the official documentation for FText but it bears repeating:

There are two different ways of creating localizable text in code:

  • LOCTEXT("Key", "Text") requires that you define a namespace somewhere else in the file using LOCTEXT_NAMESPACE "MyNamespace"
  • NSLOCTEXT("MyNamespace", "Key", "Text") lets you specify the namespace in-line
// Top of File
#define LOCTEXT_NAMESPACE "FarmGame"

FText TestHUDText = LOCTEXT("Your Key", "Your Text");

#undef LOCTEXT_NAMESPACE
// Bottom of File

Tricks of the Trade

As much as possible you should put text in a central place for designers to edit. Opening hundreds of assets is a nightmare.

At time of writing, the 4.16 has a new String Table asset that might help.

Don’t break localization

So you’ve made sure all your text data is stored in FText variables, but there are still some things you that can accidentally break localization or make it very hard for translators to do their job well.

String Concatenation

The most obvious and easy way to mess up localization is to concatenate (join together) strings. The problem with joining together strings is that the contents of each string is not always guaranteed to be the same between languages. For example in your game about cleaning an apartment, your character’s log of things they did could have entries like “Washed dishes”, “Ate apple”. You could structure your UI so you have a Verb TextBlock followed by an Thing TextBlock.

However in Japanese, the verb follows the noun, so your game would be impossible to localize for Japanese without changing the order of UI elements.

This might seem like a contrived example, but there are a few ways to get into the same situation by accident.

Blueprint String Concatenation

The FString-using node Concatenate might seem
logical for sticking two string variables together. However it is not
localizable.
▲ The FString-using node Concatenate might seem logical for sticking two string variables together. However it is not localizable.

In Blueprints it’s super easy to convert between string types (FText, FString and FName) and it’s equally easy to start concatenating text. Especially when you’re rushing to hit a deadline.

UMG Widget Implicit Concatenation

Putting two text widgets in a horizontal box can make life hard for
translators, as the two words can be in different orders in other languages.
▲ Putting two text widgets in a horizontal box can make life hard for translators, as the two words can be in different orders in other languages.

By adding FirstName and LastName TextBlock widgets to a HorizontalBox, you’re implictly creating concatenation in the same way as above.

Localizable String Concatenation with FormatText

The solution to all of this concatenation mess is to use FormatText. FormatText lets you create a piece of localizable text to define how some text should be displayed. Using our previous level-up text, we would create an FText “{CharacterName} is now Level {LevelNum}!”.

▲ FormatText is the correct way of concatenating these two variables. The format text itself is localizable and so the word order can be changed.

FormatText helps you solve the age-old pluralization problem in text localization, and supports different forms for gendered languages like French

"There {NumCats}|plural(one=is,other=are) {NumCats} {NumCats}|plural(one=cat,other=cats)"

Note that FormatText is not “free” in terms of performance. It’s still the only way to make localizable strings, but you should avoid doing it every frame. See the article on UMG and performance for more details.

Making a Localization-friendly UI

At this point if you’ve followed the advice above, you should have all of the player-facing text in your game localizable, and you should have set up your UI in such a way that localization efforts will not be broken by your UI layout.

The final step is to make some efforts to make your UI work really well in all languages.

Text Length

When designing your game’s UI, it was probably done in a single language. Artists may have designed buttons to fit the size of the text that they contain. For example a “Start” button might only be the size of 5 characters plus a little bit of margin.

To make your UI work with multiple languages, you will need to consider how your TextBlock widgets and their surrounding widgets will react when your default text is replaced by much longer or much shorter text.

There are a few behaviours you can implement to handle this:

Scale Text to Fit

This is often the simplest to implement, but can lead to the ugliest results. Wrapping your TextBlock with a ScaleBox will force the text to be shown in a smaller size if it does not fit the container.

  • + All text is visible
  • In extreme cases text will not be readable
  • A large variety of texts sizes in a UI can look ugly

Wrap Text

Set an explicit width on the container element using a SizeBox and set your widget to scale to content but only vertically.

  • + Text size is preserved
  • Container element’s size can change significantly
  • Generally only works for larger blocks of text

Clip Text With Ellipsis (…)

When the text is longer than the container allows, you replace the last word or last few characters of the word with an ellipsis character “…” to show it has been clipped.

This is not supported by Unreal by default and would require you to write some custom C++ code to do it.

  • + Preserve text size
  • + Preserve container widget size
  • Can cut off important information
  • What text is cut off is unknown to translators unless they play the game
  • Can look kind of ugly

Marquee

Scrolling or bouncing text within a box, so some of it is clipped. Think of scrolling text that’s shown on a LED billboard.

This is not supported by Unreal by default and would require you to write some custom C++ code to do it.

If your UI is sci-fi or futuristic, this could fit quite well with the aesthetic. But for a game about elves and wizards, a marquee would probably be out of place.

  • + Preserve container widget size
  • Only works with very short text
  • Depending on UI style, could not fit

Right-to-Left languages

If you’ve achieved all of this, you could consider how you will support the holy grail of localization, right-to-left languages.

This is not just a simple case of replacing text as with left-to-right languages. To have a truly natural-feeling UI, you will need to flip entire elements or entire screens for your UI to feel natural to right-to-left language users.

Google’s UI guideline documents on Bidirectionality give a good starter as to what you would need to change in your UI to support right-to-left.