#localization #gettext #translation

include-po

Convert message catalogs (PO files) into Rust modules

1 unstable release

0.1.0 Aug 14, 2024

#142 in Internationalization (i18n)

29 downloads per month
Used in easy-imgui-filechooser

MIT license

30KB
709 lines

include-po

Crate to include gettext message catalogs (PO files) directly into your Rust programs, as compilable Rust modules.

To be used together with the tr crate.

If you want to use a different translation engine, just open an issue or a pull request and I'll do my best.

Read the docs for details.


lib.rs:

This crate contains functions to parse PO files (gettext message catalogs) and use them in Rust.

Main usage

The recommended use is to use it to build a Rust module with the translations.

First build your PO files into the locales directory, in the root of your project.

Then this the crate as a [build-dependencies] and tr as a normal one: in your Cargo.toml:

[dev-dependencies]
include-po = "0.1"

[dependencies]
tr = { version = "0.1.10", default-features = false }

Write a build.rs script:

fn main() {
    let output_dir = std::env::var("OUT_DIR").unwrap();
    let out = std::path::PathBuf::from(&output_dir).join("locale/translators.rs");
    include_po::generate_locales_from_dir("locales", out).unwrap();
}

And finally in your main.rs or lib.rs:

include!(concat!(env!("OUT_DIR"), "/locale/translators.rs"));

That's it! Now you can call translators::set_locale("es"); to switch to Spanish!

If you are writing a lib crate, as a convention you should have a public function in the root namespace:

fn set_locale(locale: &str) {
    translators::set_locale(locale);
}

This way you can chain the locale of multiple translatable libraries.

Other functions

If you prefer to do the translations yourself, you can also use this crate to parse the PO file and obtain the messages. But note that currently the messages will be unescaped, that is a '"' will be a '\n' and so on.

Why PO instead of MO?

Most solutions based on gettext read the message catalog from the MO file, instead of the PO. A MO file is a compiled message catalog. The reason original gettext uses MO files is to optimize start-up time: when the gettext library wants to use a MO, it just locates the file, opens it and memory-maps it. It does very little parse, because everything is designed to be used from that memory map. It even contains a precomputed hash table!

In Rust, I see little reason to distribute MO files separated from the executable. Some people try to use include_bytes! and then parse the binary data into a BTreeMap... but that defeats the purpose of the MO existence in the first place.

If you are going to embed the message catalog into the executable you may as well go all the way and include it as code: once again the catalog is memory mapped (as it is most executable code) and with zero parsing at runtime. But if you are going to parse the catalog and convert it to Rust at build time, why read the MO and not the PO that is simpler and saves a compiler step?

But what about the hash table? you are probably asking... Well, currently this crate is building a giant match string for each source PO file. This seems to be good enough, but if needed we can transparently upgrade it to a cleverer algoritm. My hope is that the code generated by the compiler will get better faster than the needs of this crate.

Dependencies

~1–1.8MB
~35K SLoC