RedMser / godot-fluent-translation

Fluent Translation as Godot extension
Other
12 stars 0 forks source link
fluent godot godot-engine i18n localization translation

Godot Fluent Translation

Logo of Godot Fluent Translation

Fluent Translation for Godot via a Rust GDExtension.

Demo project

Features

Available Versions

If you simply wish to download and install this extension, keep reading.

If you are a developer and wish to build this extension yourself (e.g. to use a specific Godot version), go to BUILDING to learn more about your choices.

This extension can be downloaded in two different versions, each with their own benefits and downsides:

Default

Forked

### Why two versions? Due to Godot's translation system being very inflexible, it is not possible for an extension to modify certain parts about it. While I'd love for all features to work with the official version of Godot, it is unlikely for [all of my changes](https://github.com/RedMser/godot-fluent-translation/issues/11) to be included in any upcoming version. This is why you have the choice between a version that has better engine support, or one that "just works".

Installation

Code Sample

func _init():
    # Four ways to load FTL translations:
    # 1. load(path) with locale in file name (Portuguese).
    var tr_filename = load("res://test.pt_PT.ftl")

    # 2. load(path) with locale in folder name (German).
    var tr_foldername = load("res://de/german-test.ftl")

    # 3. Manually create a TranslationFluent resource.
    var tr_inline = TranslationFluent.new()
    # Ensure that you fill the locale before adding any contents (English).
    tr_inline.locale = "en"

    # 4. Forked only - [Project Settings -> Localization -> Translations] and add a .ftl file there.
    # You may need to change the file filter to "All Files" to see .ftl files in the file selector dialog.

    # Godot automatically converts spaces to tabs for multi-line strings, but tabs are invalid in
    # FTL syntax. So convert tabs to four spaces. Returns an error that you should handle.
    var err_inline = tr_inline.append_from_text("""
-term = email
HELLO =
    { $unreadEmails ->
        [one] You have one unread { -term }.
       *[other] You have { $unreadEmails } unread { -term }s.
    }
    .meta = An attr.
""".replace("\t", "    "))

    # Define custom functions to use in messages.
    # positional is an array, named is a dictionary.
    tr_filename.add_function("STRLEN", func(positional, named):
        if positional.is_empty():
            return 0
        return len(str(positional[0]))
    )

    # Register via TranslationServer.
    TranslationServer.add_translation(tr_filename)
    TranslationServer.add_translation(tr_foldername)
    TranslationServer.add_translation(tr_inline)

func _notification(what: int) -> void:
    if what == NOTIFICATION_TRANSLATION_CHANGED:
        # Fluent supports $variables, which can be filled when translating a message.

        # Default version: use a wrapper function to pass arguments:
        $Label.text = atr(TranslationFluent.args("HELLO", { "unreadEmails": $SpinBox.value }))

        # Forked version: pass arguments directly to tr() and friends:
        $Label.text = atr("HELLO", { "unreadEmails": $SpinBox.value })

        # The context field is used to retrieve .attributes of a message.
        $Label2.text = atr("HELLO", "meta") # Default
        $Label2.text = atr("HELLO", {}, "meta") # Forked

Project Settings

[!TIP] If you don't see some of these settings, make sure you have Advanced Settings enabled.

General

Loader

These settings only apply to translation files loaded by load() or via project settings. For manually created TranslationFluent instances, custom logic can be implemented to emulate these settings.

Generator

These settings apply to the FluentGenerator singleton:

FTL Generator

You can automatically extract message IDs from your scene files!

  1. Edit the internationalization/fluent/generator/locales project setting to define a list of locales to generate.
  2. Edit the internationalization/fluent/generator/file_patterns project setting to define how files should be generated:
    • Both the key and the value should be type String.
    • The key represents a regular expression to locale a set of source files. For example (.+)\.tscn would find all scene files in your project.
      • Note that capture groups can be used later, so make sure to make good use of non-capturing groups to ensure your group indices are consistent.
    • The value represents the path to the generated FTL file. It can contain placeholders that get replaced:
      • {$locale} is replaced with each of the locales listed in the locales project setting (creating multiple files).
      • {$n} is replaced with the n-th capture group (so {$1} would contain the first capture group that matched).
      • For example, with the above regex, res://i18n/{$1}.{$locale}.ftl would create files like i18n/my_scene.en.ftl in your project root.
    • If a FTL file already exists (or is matched multiple times, e.g. by different patterns), it will be merged with the existing file. No messages are ever deleted, and existing messages will remain untouched.
  3. Run the generator by creating a tool script such as this one:
@tool
extends EditorScript

func _run() -> void:
    var generator = FluentGenerator.create()
    generator.generate()

[!TIP] To run an EditorScript, open it in the script editor and go to File -> Run.

This system provides maximal flexibility and very little maintenance once set up properly.

Currently, only .tscn files are properly handled (similarly to the POT generator feature built into Godot). A plug-in system to customize message extraction is planned but currently not possible to implement.

About this Project

This is not a production-ready project and will likely have breaking API changes without warning. Please consider this if you intend on using this library.

Due to Godot needing breaking API changes to have this extension work, it is unlikely to become easily usable out-of-the-box. Not much I can do besides wait for another major release that would accept this breaking change.

Any help in continuing development for this library is welcome!