cargo-i18n 0.1.4

Cargo sub-command to extract and build localization resources to embed in your application/library
Documentation

cargo-i18n crates.io badge license badge

This crate is a Cargo sub-command cargo i18n which can be used to extract and build localization resources 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.

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.

Currently this tool only supports localizing via gettext with the tr library and using the xtr tool for string extraction. It has been designed to be tool agnostic, with plans to add support for fluent, and any other workable system that becomes available for the Rust ecosystem.

Changelog

Usage

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

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 locale/language identifier of the language used in the source
# code.
src_locale = "en"

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

[gettext]
# (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:

i18n-embed = "0.3.1"

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

use i18n_embed::{I18nEmbed, LanguageLoader, DesktopLanguageRequester};
use rust_embed::RustEmbed;

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

#[derive(LanguageLoader)]
struct MyLanguageLoader;

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

    // 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, including having translations live updated based on system preferences (currently incomplete), and how to embed translations within libraries that can be consumed.

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:

  • poeditor - free for open source projects, currently being used for this project.
  • crowdin - free for popular open source projects.

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.

Example Projects

For a complete example usage, including localizing sub-crates as libraries, you can see the source code for this project, which localizes itself. This project was originally created to aid in the localization for the coster (work in progress) self-hosted web application.

Configuration

Available configuration options for i18n.toml:

# (Required) The locale/language identifier of the language used in the source
# code.
src_locale = "en-US"

# (Required) The locales that the software will be translated into.
target_locales = ["es", "ru", "cz"]

# (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) 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) 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"

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