.. _doc_internationalizing_games: Internationalizing games ======================== Introduction ------------ While indie or niche games usually do not need localization, games targeting a more massive market often require localization. Godot offers many tools to make this process more straightforward, so this tutorial is more like a collection of tips and tricks. Localization is usually done by specific studios hired for the job. Despite the huge amount of software and file formats available for this, the most common way to do localization to this day is still with spreadsheets. The process of creating the spreadsheets and importing them is already covered in the :ref:`doc_importing_translations` tutorial. If you haven't read the Importing translations page before, we recommend you give it a read before reading this page. .. note:: We will be using the official demo as an example; you can `download it from the Asset Library `_. Configuring the imported translation ------------------------------------ Translations can get updated and re-imported when they change, but they still have to be added to the project. This is done in **Project → Project Settings → Localization**: .. image:: img/localization_dialog.png The above dialog is used to add or remove translations project-wide. Localizing resources -------------------- It is also possible to instruct Godot to use alternate versions of assets (resources) depending on the current language. This can be used for localized images such as in-game billboards or localized voices. The **Remaps** tab can be used for this: .. image:: img/localization_remaps.png Select the resource to be remapped then add some alternatives for each locale. .. note:: The resource remapping system isn't supported for DynamicFonts. To use different fonts depending on the language's script, use the DynamicFont fallback system instead, which lets you define as many fallback fonts as you want. The upside of the DynamicFont fallback system is that it works regardless of the current language, making it ideal for things like multiplayer chat where the text language may not match the client's language. Automatically setting a language -------------------------------- It is recommended to default to the user's preferred language which can be obtained via :ref:`OS.get_locale_language() `. If your game is not available in that language, it will fall back to the :ref:`Fallback ` in **Project Settings > Internationalization > Locale**, or to ``en`` if empty. Nevertheless letting players change the language in game is recommended for various reasons (e.g. translation quality or player preference). .. tabs:: .. code-tab:: gdscript var language = "automatic" # Load here language from the user settings file if language == "automatic": var preferred_language = OS.get_locale_language() TranslationServer.set_locale(preferred_language) else: TranslationServer.set_locale(language) Locale vs. language ------------------- A :ref:`locale ` is commonly a combination of a language with a region or country, but can also contain information like a script or a variant. Examples: - ``en``: English language - ``en_GB``: English in Great Britain / British English - ``en_US``: English in the USA / American English - ``en_DE``: English in Germany Indie games generally only need to care about language, but read on for more information. Why locales exist can be illustrated through the USA and Great Britain. Both speak the same language (English), yet differ in many aspects: - Spelling: E.g. gray (USA), grey (GB) - Use of words: E.g. eggplant (USA), aubergine (GB) - Units or currencies: E.g. feet/inches (USA), metres/cm (GB) It can get more complex however. Imagine you offer different content in Europe and in China (e.g. in an MMO). You will need to translate each of those content variations into many languages and store and load them accordingly. Converting keys to text ----------------------- Some controls, such as :ref:`Button ` and :ref:`Label `, will automatically fetch a translation if their text matches a translation key. For example, if a label's text is "MAIN_SCREEN_GREETING1" and that key exists in the current translation, then the text will automatically be translated. This automatic translation behavior may be undesirable in certain cases. For instance, when using a Label to display a player's name, you most likely don't want the player's name to be translated if it matches a translation key. To disable automatic translation on a specific node, disable **Localization > Auto Translate** in the inspector. In code, the :ref:`Object.tr() ` function can be used. This will just look up the text in the translations and convert it if found: .. tabs:: .. code-tab:: gdscript level.text = tr("LEVEL_5_NAME") status.text = tr("GAME_STATUS_%d" % status_index) .. code-tab:: csharp level.Text = Tr("LEVEL_5_NAME"); status.Text = Tr($"GAME_STATUS_{statusIndex}"); .. note:: If no text is displayed after changing the language, try to use a different font. The default project font only supports a subset of the Latin-1 character set, which cannot be used to display languages like Russian or Chinese. A good resource for multilingual fonts is `Noto Fonts `__. Make sure to download the correct variation if you're using a less common language. Once you've downloaded the font, load the TTF file into a DynamicFont resource and use it as a custom font of your Control node. For better reusability, associate a new a Theme resource to your root Control node and define the DynamicFont as the Default Font in the theme. Placeholders ~~~~~~~~~~~~ To feature placeholders in your translated strings, use :ref:`doc_gdscript_printf` or the equivalent feature in C#. This lets translators move the location of the placeholder in the string freely, which allows translations to sound more natural. Named placeholders with the ``String.format()`` function should be used whenever possible, as they also allow translators to choose the *order* in which placeholders appear: .. tabs:: .. code-tab:: gdscript # The placeholder's locations can be changed, but not their order. # This will probably not suffice for some target languages. message.text = tr("%s picked up the %s") % ["Ogre", "Sword"] # The placeholder's locations and order can be changed. # Additionally, this form gives more context for translators to work with. message.text = tr("{character} picked up the {weapon}").format({character = "Ogre", weapon = "Sword"}) Translation contexts ~~~~~~~~~~~~~~~~~~~~ If you're using plain English as source strings (rather than message codes ``LIKE_THIS``), you may run into ambiguities when you have to translate the same English string to different strings in certain target languages. You can optionally specify a *translation context* to resolve this ambiguity and allow target languages to use different strings, even though the source string is identical: .. tabs:: .. code-tab:: gdscript # "Close", as in an action (to close something). button.set_text(tr("Close", "Actions")) # "Close", as in a distance (opposite of "far"). distance_label.set_text(tr("Close", "Distance")) .. code-tab:: csharp // "Close", as in an action (to close something). GetNode