kellpossible / cargo-i18n

A Rust Cargo sub-command and libraries to extract and build localization resources to embed in your application/library
MIT License
125 stars 26 forks source link
cargo fluent gettext i18n internationalization localization localization-management rust translation

cargo-i18n crates.io badge license badge github actions badge dependency status

This crate is a Cargo sub-command cargo i18n which can be used to extract and build, and verify localization resources at compile time for your crate. The i18n-embed library has been created to allow you to conveniently embed these localizations into your application or library, and have them selected at runtime. Different systems can be used simultaneously.

i18n-embed supports both the following localization systems:

You can install this tool using the command: cargo install cargo-i18n.

The cargo i18n command reads the configuration file (by default called i18n.toml) in the root directory of your crate, and then proceeds to extract localization resources from your source files, and build them.

The i18n-build library contains most of the implementation for this tool. It has been published separately to allow its direct use within project build scripts if required.

Changelog

Projects Using cargo-i18n or i18n-embed

Usage with Fluent

Fluent support is now available in i18n-embed. See the examples for that crate, and the documentation for i18n-embed for example of how to use it.

See also the fl!() macro for a convenient compile-time checked way to access fluent messages when using i18n-embed.

Currently there are no validations performed by the cargo-i18n tool when using the fluent localization system, however there are some planned (see tracking issue #31). If you have any more ideas for this, please feel free to contribute to the issue discussion.

Usage with Gettext

This is an example for how to use the cargo-i18n tool, and i18n-embed the gettext localization tool system. Please note that the gettext localization system is technically inferior to fluent in a number of ways, however there are always legacy reasons, and the developer/translator ecosystem around gettext is more mature.

Firstly, ensure you have the required utilities installed on your system. See Gettext System Requirements and install the necessary utilities and commands for the localization system you will be using.

Defining Localized Strings

You will need to ensure that your strings in your source code that you want localized are using the tr!() macro from the tr crate.

You can add comments to your strings which will be available to translators to add context, and ensure that they understand what the string is for.

For example:

use tr::tr;

fn example(file: String) {
    let my_string = tr!(
        // {0} is a file path
        // Example message: Printing this file: "file.doc"
        "Printing this file: \"{0}\"",
        file
    );
}

Minimal Configuration

You will need to create an i18n.toml configuration in the root directory of your crate. A minimal configuration for a binary crate to be localized to Spanish and Japanese using the gettext system would be:

# (Required) The language identifier of the language used in the
# source code for gettext system, and the primary fallback language
# (for which all strings must be present) when using the fluent
# system.
fallback_language = "en"

[gettext]
# (Required) The languages that the software will be translated into.
target_languages = ["es", "ja"]

# (Required) Path to the output directory, relative to `i18n.toml` of the crate
# being localized.
output_dir = "i18n"

See Configuration for a description of all the available configuration options.

Running cargo i18n

Open your command line/terminal and navigate to your crate directory, and run cargo i18n. You may be prompted to enter some email addresses to use for contact points for each of the language's po files. At the end there should be a new directory in your crate called i18n, and inside will be pot, po and mo directories.

The pot directory contains pot files which were extracted from your source code using the xtr tool, and there should be a single pot file with the name of your crate in here too, which is the result of merging all the other pot files.

The po directory contains the language specific message files.

The mo directory contains the compiled messages, which will later be embedded into your application.

At this point it could be a good idea to add the following to your crate's .gitignore (if you are using git):

/i18n/pot
/i18n/mo

If you want your crate to be able to build without requiring this tool to be present on the system, then you can leave the /i18n/mo directory out of the .gitignore, and commit the files inside.

Embedding Translations

Now that you have compiled your translations, you can embed them within your application. For this purpose the i18n-embed crate was created.

Add the following to your Cargo.toml dependencies:

[dependencies]
i18n-embed = "VERSION"

A minimal example for how to embed the compiled translations into your application could be:

use i18n_embed::{DesktopLanguageRequester,
    gettext::gettext_language_loader};
use rust_embed::RustEmbed;

#[derive(RustEmbed)]
#[folder = "i18n/mo"] // path to the compiled localization resources
struct Translations;

fn main() {
    let translations = Translations {};
    let language_loader = gettext_language_loader!();

    // Use the language requester for the desktop platform (linux, windows, mac).
    // There is also a requester available for the web-sys WASM platform called
    // WebLanguageRequester, or you can implement your own.
    let requested_languages = DesktopLanguageRequester::requested_languages();

    i18n_embed::select(&language_loader, &translations, &requested_languages);

    // continue with your application
}

You can see the i18n-embed documentation for more detailed examples of how this library can be used.

Distributing to Translators

Now you need to send of the po files to your translators, or provide them access to edit them. Some desktop tools which can be used for the translation include:

Or you could also consider setting up a translation management website for your project to allow translators to edit translations without requiring them to interact with source control or mess around with sending files and installing applications. Some examples:

Self Hosted:

Cloud:

Updating Translations

Once you have some updated po files back from translators, or you want to update the po files with new or edited strings, all you need to do is run cargo i18n to update the po files, and recompile updated mo files, then rebuild your application with cargo build.

For some projects using build scripts, with complex pipelines, and with continuous integration, you may want to look into using the i18n-build for automation as an alternative to the cargo i18n command line tool.

Configuration

Available configuration options for i18n.toml:

# (Required) The language identifier of the language used in the
# source code for gettext system, and the primary fallback language
# (for which all strings must be present) when using the fluent
# system.
fallback_language = "en-US"

# (Optional) Specify which subcrates to perform localization within. If the
# subcrate has its own `i18n.toml` then, it will have its localization
# performed independently (rather than being incorporated into the parent
# project's localization).
subcrates = ["subcrate1", "subcrate2"]

# (Optional) Use the gettext localization system.
[gettext]
# (Required) The languages that the software will be translated into.
target_languages = ["es", "ru", "cz"]

# (Required) Path to the output directory, relative to `i18n.toml` of the crate
# being localized.
output_dir = "i18n"

# (Optional) The reporting address for msgid bugs. This is the email address or
# URL to which the translators shall report bugs in the untranslated
# strings.
msg_bugs_address = "example@example.com"

# (Optional) Set the copyright holder for the generated files.
copyright_holder = "You?"

# (Optional) If this crate is being localized as a subcrate, store the final
# localization artifacts (the module pot and mo files) with the parent crate's
# output. Currently crates which contain subcrates with duplicate names are not
# supported. By default this is false.
extract_to_parent = false

# (Optional) If a subcrate has extract_to_parent set to true, then merge the
# output pot file of that subcrate into this crate's pot file. By default this
# is false.
collate_extracted_subcrates = false

# (Optional) How much message location information to include in the output.
# If the type is ‘full’ (the default), it generates the lines with both file
# name and line number: ‘#: filename:line’. If it is ‘file’, the line number
# part is omitted: ‘#: filename’. If it is ‘never’, nothing is generated.
# [possible values: full, file, never].
add_location = "full"

# (Optional) Whether or not to perform string extraction using the `xtr` tool.
xtr = true

# (Optional )Path to where the pot files will be written to by `xtr` command,
# and were they will be read from by the `msginit` and `msgmerge` commands. By
# default this is `output_dir/pot`.
pot_dir = "i18n/pot"

# (Optional) Path to where the po files will be stored/edited with the
# `msgmerge` and `msginit` commands, and where they will be read from with the
# `msgfmt` command. By default this is `output_dir/po`.
po_dir = "i18n/po"

# (Optional) Path to where the mo files will be written to by the `msgfmt`
# command. By default this is `output_dir/mo`.
mo_dir = "i18n/mo"

# (Optional) Enable the `--use-fuzzy` option for the `msgfmt` command. By
# default this is false. If your .po file are copied from another project, you
# may need to enable it.
use_fuzzy = false

# (Optional) Use the fluent localization system.
[fluent]
# (Required) The path to the assets directory.
# The paths inside the assets directory should be structured like so:
# `assets_dir/{language}/{domain}.ftl`
assets_dir = "i18n"

System Requirements

Gettext Requirements

Using the gettext localization system with this tool requires you to have gettext installed on your system.

The msginit, msgfmt, msgmerge and msgcat commands all need to be installed and present in your path.

You also need to ensure that you have the xtr string extraction command installed, which can be achieved using cargo install xtr.

Contributing

Pull-requests are welcome, but for design changes it is preferred that you create a GitHub issue first to discuss it before implementation. You can also contribute to the localization of this tool via:

Or you can also use your favourite po editor directly to help with localizing the files located in i18n/po and i18n-build/i18n/po.

To add a new language, you can make a request via a GitHub issue, or submit a pull request adding the new locale to i18n.toml and generating the associated new po files using cargo i18n.

Translations of this README.md file are also welcome, and can be submitted via pull request. Just name it README.md.lang, where lang is the locale code (see List of ISO 639-1 codes).

Authors