educative.io

Rust - how to build a multiLanguage programm?

Hi,
how to build a multiLanguage programm in rust.

for example:

environment english: output: Hello World!

environment german: output: Hall Welt!

environment french: output: Bonjour le monde!

thanks

Hi @DS2k5!!
To build a multi-language program in Rust, you can use the gettext library to manage translations for different languages.Hereā€™s a example using gettext-rs, which is a widely adopted gettext library for Rust:

  1. Add Dependencies:

    In your Cargo.toml file, add the gettext and lazy_static crates as dependencies:

    [dependencies]
    gettext = "0.7"
    lazy_static = "1.4"
    
  2. Create a Translations Directory:

    Create a directory named locales to store your translation files. Inside this directory, create subdirectories for each language you want to support (e.g., en_US, de_DE, fr_FR). Place .po and .mo files containing translations for each language in their respective subdirectories.

  3. Initialize Gettext:

    In your Rust code (e.g., main.rs), initialize gettext and specify the domain and language to use:

    #[macro_use]
    extern crate gettext;
    
    use gettext_macros::include_i18n_functions;
    
    include_i18n_functions!();
    
    fn main() {
        // Initialize gettext with the specified domain and language.
        gettext::bindtextdomain("myapp", "./locales").unwrap();
        gettext::textdomain("myapp").unwrap();
    
        // Set the desired language (e.g., "en_US" for English, "de_DE" for German, "fr_FR" for French).
        gettext::set_locale("en_US").unwrap();
    
        // Use the `_()` macro to mark translatable strings.
        let greeting = _("Hello World!");
    
        println!("{}", greeting);
    }
    
  4. Generate Translation Files:

    To generate .mo files from your .po files, you can use the msgfmt tool from the gettext utilities. For example, to generate the .mo file for English (en_US):

    msgfmt locales/en_US/myapp.po -o locales/en_US/myapp.mo
    
  5. Run Your Program:

    To run your program in different languages, change the locale using gettext::set_locale("language_code"), where language_code corresponds to the language you want to use (e.g., "de_DE" for German, "fr_FR" for French).

This example demonstrates how to create a multi-language Rust program using the gettext library, which is a more standard approach for internationalization in Rust. Youā€™ll need to create and manage .po and .mo translation files for each language you want to support in the locales directory.
I hope it helps. Happy Learning :blush:

Hi @Javeria_Tariq
Thank you so much! but did not get it work

  1. cargo new multilang
  2. cd multilang
  3. mkdir -p locales/en_US/
  4. cargo add gettext && cargo add lazy_static
  5. codium .
  6. paste your code

  1. $ msgfmt locales/en_US/myapp.po -o locales/en_US/myapp.mo
    msgfmt: error while opening ā€œlocales/en_US/myapp.poā€ for reading: No such file or directory

How to create the .po file ? what needs to add to this file ?

what did I worng ?

It seems there might be a misunderstanding regarding the creation of .po files. Hereā€™s a corrected step-by-step guide on how to create .po files and work with them in a multi-language Rust project using gettext. Letā€™s start from scratch:

  1. Create a New Rust Project:

    If you havenā€™t already, create a new Rust project:

    cargo new multilang
    cd multilang
    
  2. Add Dependencies:

    Open your Cargo.toml file and add the gettext crate as a dependency:

    [dependencies]
    gettext = "0.8"
    lazy_static = "1.4"
    

    Then, save the file.

  3. Create a Translations Directory and .pot File:

    Create a directory named locales in the root of your project to store your translation files. Inside this directory, create a subdirectory for your default language (e.g., en_US). In this subdirectory, create a .pot file (Portable Object Template) that will serve as a template for translations:

    mkdir -p locales/en_US/
    touch locales/en_US/myapp.pot
    

    You can use a tool like xgettext to extract translatable strings from your Rust code and populate the .pot file. Hereā€™s a simplified example of how you can extract strings from your Rust code:

    xgettext --language=Rust --output=locales/en_US/myapp.pot main.rs
    

    This command will extract translatable strings from the main.rs file and create the .pot file.

  4. Initialize Gettext:

    In your Rust code (e.g., main.rs), initialize gettext as follows:

    #[macro_use]
    extern crate gettext;
    
    use std::env;
    use gettext_macros::include_i18n_functions;
    
    include_i18n_functions!();
    
    fn main() {
        // Specify the directory containing your translation files.
        let locales_dir = "locales";
    
        // Determine the desired language.
        let language_code = env::var("LANG").unwrap_or_else(|_| "en_US".to_string());
    
        // Initialize gettext with the specified domain and language.
        gettext::bindtextdomain("myapp", locales_dir).unwrap();
        gettext::textdomain("myapp").unwrap();
        gettext::set_locale(&language_code).unwrap();
    
        // Use the `_()` macro to mark translatable strings.
        let greeting = _("Hello World!");
    
        println!("{}", greeting);
    }
    
  5. Generate .po Files from the .pot Template:

    To generate .po files (Portable Object) for each language, you can use a tool like msginit:

    msginit --locale=de_DE --input=locales/en_US/myapp.pot --output=locales/de_DE/myapp.po
    msginit --locale=fr_FR --input=locales/en_US/myapp.pot --output=locales/fr_FR/myapp.po
    

    This will create .po files for German and French translations. You can repeat this step for other languages you want to support.

  6. Edit .po Files:

    Open the generated .po files (e.g., locales/de_DE/myapp.po, locales/fr_FR/myapp.po) in a text editor and provide translations for the strings. For example:

    msgid ""
    msgstr ""
    "Content-Type: text/plain; charset=UTF-8\n"
    
    msgid "Hello World!"
    msgstr "Hallo Welt!"
    
  7. Compile .po Files to .mo Files:

    To compile the .po files to binary .mo files, you can use the msgfmt tool:

    msgfmt locales/de_DE/myapp.po -o locales/de_DE/myapp.mo
    msgfmt locales/fr_FR/myapp.po -o locales/fr_FR/myapp.mo
    

    Repeat this step for all the languages you want to support.

  8. Run Your Program:

    To run your multi-language program in different languages, set the LANG environment variable to the desired language code before running your program:

    export LANG=de_DE.utf8   # For German
    cargo run
    

    Adjust the LANG variable and run your program for different languages as needed.

This revised guide should help you set up a multi-language Rust program with proper .po and .mo files for translations.

@Javeria_Tariq
Thanks so muchā€¦

[dependencies]
gettext = ā€œ0.8ā€

but I got only 0.4.0 not 0.8.0

error: failed to select a version for the requirement gettext = "^0.8.0"
candidate versions found which didnā€™t match: 0.4.0, 0.3.0, 0.2.0, ā€¦

xgettext --language=Rust --output=locales/en_US/myapp.pot src/main.rs
xgettext: language ā€˜Rustā€™ unknown

but xgettext did not know about Rustā€¦ did not found a package for xgettext

# apt-cache search gettext | grep -i rust
librust-gettext-dev - Gettext translation framework for Rust - Rust source code
librust-gettext-rs-dev - Safe bindings for gettext - Rust source code
librust-gettext-sys-dev - Raw FFI bindings for gettext - Rust source code
librust-i18n-embed-impl-dev - Macro implementations for i18n-embed - Rust source code

xgettext --version
xgettext (GNU gettext-tools) 0.21

using Debian 12 (bookworm)

$ rustc --version
rustc 1.72.0 (5680fa18f 2023-08-23)

It appears that the gettext crate for Rust has not reached version 0.8.0 as of your environment, which is why you encountered version 0.4.0 when adding the dependency. In that case, you should use the available version in your environment.

Regarding the xgettext issue, the xgettext tool does not have built-in support for Rust like it does for languages like C, C++, and Python. Youā€™ll need to use a workaround to extract translatable strings from your Rust code.

One common approach is to use a comment-based extraction method. You can create a custom script that parses your Rust source code and extracts translatable strings marked with specific comments. For example, you can use // gettext: Your translatable string comments in your code.

Hereā€™s a simplified example of how you can create a custom script to extract translatable strings:

  1. Create a Rust source file (e.g., main.rs) with translatable strings marked with comments:

    fn main() {
        // gettext: Hello World!
        let greeting = "Hello, World!";
        println!("{}", greeting);
    }
    
  2. Create a custom script (e.g., extract_strings.rs) to parse your Rust code and generate a .pot template file:

    use std::fs::File;
    use std::io::{BufRead, BufReader, Write};
    
    fn main() -> std::io::Result<()> {
        let input_file = File::open("src/main.rs")?;
        let reader = BufReader::new(input_file);
    
        let mut output_file = File::create("locales/en_US/myapp.pot")?;
        writeln!(output_file, "msgid \"\"")?;
        writeln!(output_file, "msgstr \"\"")?;
        writeln!(output_file, "\"Content-Type: text/plain; charset=UTF-8\\n\"")?;
    
        for line in reader.lines() {
            let line = line?;
            if let Some(start) = line.find("// gettext: ") {
                let text = &line[start + 12..];
                writeln!(output_file, "msgid \"{}\"", text)?;
                writeln!(output_file, "msgstr \"\"")?;
            }
        }
    
        Ok(())
    }
    
  3. Run the custom script to extract translatable strings and generate the .pot file:

    cargo run --bin extract_strings
    

    This will create or update the locales/en_US/myapp.pot file with the translatable strings.

  4. Follow the previous steps to create .po files for specific languages and compile them to .mo files as mentioned in the previous responses.

I understand that this is a workaround, but it should help you extract translatable strings from your Rust code when using gettext with Rust, even if xgettext does not have built-in support for Rust.
Happy Learning :blush:

1 Like

@Javeria_Tariq

thanks again for your helpā€¦ and your patience

could now create the .mo files !

$ cat locales/de_DE/myapp.mo 
ļæ½ļæ½,<P
     QL^
        ļæ½Hello World!Content-Type: text/plain; charset=UTF-8
Project-Id-Version: PACKAGE VERSION
PO-Revision-Date: 2023-09-08 10:13+0200
Last-Translator: developer <developer@w541>
Language-Team: German <translation-team-de@lists.sourceforge.net>
Language: de
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=2; plural=(n != 1);
Hallo Welt!

But the Code will not compile

$ cargo run
   Compiling new_multilang v0.1.0 (/home/developer/rust/new_multilang)
error[E0432]: unresolved import `gettext_macros::include_i18n_functions`
 --> src/main.rs:4:5
  |
4 | use gettext_macros::include_i18n_functions;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no `include_i18n_functions` in the root

error: cannot determine resolution for the macro `include_i18n_functions`
 --> src/main.rs:6:1
  |
6 | include_i18n_functions!();
  | ^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: import resolution is stuck, try simplifying macro imports

error[E0425]: cannot find function `bindtextdomain` in crate `gettext`
  --> src/main.rs:16:14
   |
16 |     gettext::bindtextdomain("myapp", locales_dir).unwrap();
   |              ^^^^^^^^^^^^^^ not found in `gettext`

error[E0425]: cannot find function `textdomain` in crate `gettext`
  --> src/main.rs:17:14
   |
17 |     gettext::textdomain("myapp").unwrap();
   |              ^^^^^^^^^^ not found in `gettext`

error[E0425]: cannot find function `set_locale` in crate `gettext`
  --> src/main.rs:18:14
   |
18 |     gettext::set_locale(&language_code).unwrap();
   |              ^^^^^^^^^^ not found in `gettext`
   |
help: consider importing this function
   |
3  + use rust_i18n::set_locale;
   |
help: if you import `set_locale`, refer to it directly
   |
18 -     gettext::set_locale(&language_code).unwrap();
18 +     set_locale(&language_code).unwrap();
   |

error: in expressions, `_` can only be used on the left-hand side of an assignment
  --> src/main.rs:21:20
   |
21 |     let greeting = _("Hello World!");
   |                    ^ `_` not allowed here

Some errors have detailed explanations: E0425, E0432.
For more information about an error, try `rustc --explain E0425`.
error: could not compile `new_multilang` (bin "new_multilang") due to 6 previous errors

Cargo.toml

[dependencies]
gettext = "0.4.0"
lazy_static = "1.4.0"

The issues are related to using the gettext crate version 0.4.0, which differs from the examples provided earlier that assumed a higher version (0.8.0).

To resolve these issues, you should make adjustments to your code to work with the version of the gettext crate you have. Please follow these steps to resolve these errors:

  1. Remove the unnecessary import of gettext_macros::include_i18n_functions. This import is not required for version 0.4.0 of the gettext crate.

  2. Modify the import for functions like bindtextdomain, textdomain, and set_locale to work with version 0.4.0. You should use the functions provided by the gettext crate directly.

  3. Replace the use of the _() macro with the gettext::gettext function for string translation.

Hereā€™s the modified code:

use std::env;
use gettext::Catalog;

fn main() {
    // Specify the directory containing your translation files.
    let locales_dir = "locales";

    // Determine the desired language.
    let language_code = env::var("LANG").unwrap_or_else(|_| "en_US".to_string());

    // Initialize the catalog with the specified domain and language.
    let catalog = Catalog::configure()
        .locales(locales_dir)
        .domain("myapp")
        .build()
        .unwrap();
    
    // Set the desired locale.
    catalog.set_locale(language_code.clone()).unwrap();

    // Translate the string using gettext::gettext.
    let greeting = catalog.gettext("Hello World!");

    println!("{}", greeting);
}

Make sure you have the correct version of the gettext crate (0.4.0) in your Cargo.toml file:

[dependencies]
gettext = "0.4.0"
lazy_static = "1.4.0"

With these modifications, your code should work with the version of the gettext crate that you have installed. You can now try running your program again:

export LANG=de_DE.utf8   # For German
cargo run

Repeat this for other languages as needed, changing the LANG environment variable accordingly.

@Javeria_Tariq

$ echo $LANG
de_DE.UTF-8

Cargo.toml

[dependencies]
gettext = "0.4.0"
lazy_static = "1.4.0"


$ cat locales/de_DE/myapp.mo 
ļæ½ļæ½,<P
     QL^
        ļæ½Hello World!Content-Type: text/plain; charset=UTF-8
Project-Id-Version: PACKAGE VERSION
PO-Revision-Date: 2023-09-08 10:13+0200
Last-Translator: developer <developer@w541>
Language-Team: German <translation-team-de@lists.sourceforge.net>
Language: de
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Plural-Forms: nplurals=2; plural=(n != 1);
Hallo Welt!

but Catalog did not know about ā€œconfigureā€

Checking new2_multilang v0.1.0 (/home/developer/rust/new2_multilang)
error[E0599]: no function or associated item named configure found for struct Catalog in the current scope
ā†’ src/main.rs:12:28
|
12 | let catalog = Catalog::configure()
| ^^^^^^^^^ function or associated item not found in Catalog

For more information about this error, try rustc --explain E0599.
error: could not compile new2_multilang (bin ā€œnew2_multilangā€) due to previous error
Checking new2_multilang v0.1.0 (/home/developer/rust/new2_multilang)
error[E0599]: no function or associated item named configure found for struct Catalog in the current scope
ā†’ src/main.rs:12:28
|
12 | let catalog = Catalog::configure()
| ^^^^^^^^^ function or associated item not found in Catalog

For more information about this error, try rustc --explain E0599.
error: could not compile new2_multilang (bin ā€œnew2_multilangā€) due to previous error

found a solution with gettext-ng (thanks to Graham)

cargo new multilaguage
cd multilaguage
cargo add gettext-ng

vi src/main.rs

use std::fs::File;
use gettext_ng::Catalog;
fn main() {
    let lang_code = std::env::var("LANG").unwrap();
    let filename = format!("{}.mo", lang_code);
    let f = File::open(filename).expect("could not open the catalog");
    let catalog = Catalog::parse(f).expect("could not parse the catalog");

    // Will print out the French translation
    // if it is found in the parsed file
    // or "Name" otherwise.
    println!("{}", catalog.gettext("Name"));
}

vi en_US.UTF-8.po

msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"PO-Revision-Date: 2023-09-08 21:39+0200\n"
"Last-Translator: developer <developer@w541>\n"
"Language-Team: English\n"
"Language: en_EN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Name"
msgstr "Hello World!"

vi de_DE.UTF-8.po

msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"PO-Revision-Date: 2023-09-08 21:46+0200\n"
"Last-Translator: developer <developer@w541>\n"
"Language-Team: German <translation-team-de@lists.sourceforge.net>\n"
"Language: de\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
msgid "Name"
msgstr "Hallo Welt!"

vi fr_FR.UTF-8.po

msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"PO-Revision-Date: 2023-09-08 21:34+0200\n"
"Last-Translator: developer <developer@w541>\n"
"Language-Team: French <traduc@traduc.org>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=ASCII\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
msgid "Name"
msgstr "Bonjour le monde!"

msgfmt en_EN.UTF-8.po -o en_US.UTF-8.mo
msgfmt de_DE.UTF-8.po -o de_DE.UTF-8.mo
msgfmt fr_FR.UTF-8.po -o fr_FR.UTF-8.mo

cargo run

export LANG=de_DE.UTF-8

cargo run

export LANG=fr_FR.UTF-8

cargo run
1 Like