Code Monkey home page Code Monkey logo

rust-i18n's Introduction

Rust I18n

CI Docs Crates.io

🎯 Let's make I18n things to easy!

Rust I18n is a crate for loading localized text from a set of (YAML, JSON or TOML) mapping files. The mappings are converted into data readable by Rust programs at compile time, and then localized text can be loaded by simply calling the provided [t!] macro.

Unlike other I18n libraries, Rust I18n's goal is to provide a simple and easy-to-use API.

The API of this crate is inspired by ruby-i18n and Rails I18n.

Features

  • Codegen on compile time for includes translations into binary.
  • Global [t!] macro for loading localized text in everywhere.
  • Use YAML (default), JSON or TOML format for mapping localized text, and support mutiple files merging.
  • cargo i18n Command line tool for checking and extract untranslated texts into YAML files.
  • Support all localized texts in one file, or split into difference files by locale.
  • Supports specifying a chain of fallback locales for missing translations.
  • Supports automatic lookup of language territory for fallback locale. For instance, if zh-CN is not available, it will fallback to zh. (Since v2.4.0)
  • Support short hashed keys for optimize memory usage and lookup speed. (Since v3.1.0)
  • Support format variables in [t!], and support format variables with std::fmt syntax. (Since v3.1.0)
  • Support for log missing translations at the warning level with log-miss-tr feature, the feature requires the log crate. (Since v3.1.0)

Usage

Add crate dependencies in your Cargo.toml and setup I18n config:

[dependencies]
rust-i18n = "3"

Load macro and init translations in lib.rs or main.rs:

// Load I18n macro, for allow you use `t!` macro in anywhere.
#[macro_use]
extern crate rust_i18n;

// Init translations for current crate.
// This will load Configuration using the `[package.metadata.i18n]` section in `Cargo.toml` if exists.
// Or you can pass arguments by `i18n!` to override it.
i18n!("locales");

// Config fallback missing translations to "en" locale.
// Use `fallback` option to set fallback locale.
//
i18n!("locales", fallback = "en");

// Or more than one fallback with priority.
//
i18n!("locales", fallback = ["en", "es"]);

// Use a short hashed key as an identifier for long string literals
// to optimize memory usage and lookup speed.
// The key generation algorithm is `${Prefix}${Base62(SipHash13("msg"))}`.
i18n!("locales", minify_key = true);
//
// Alternatively, you can customize the key length, prefix,
// and threshold for the short hashed key.
i18n!("locales",
      minify_key = true,
      minify_key_len = 12,
      minify_key_prefix = "t_",
      minify_key_thresh = 64
);
// Now, if the message length exceeds 64, the `t!` macro will automatically generate
// a 12-byte short hashed key with a "t_" prefix for it, if not, it will use the original.

// If no any argument, use config from Cargo.toml or default.
i18n!();

Or you can import by use directly:

// You must import in each files when you wants use `t!` macro.
use rust_i18n::t;

rust_i18n::i18n!("locales");

fn main() {
    // Find the translation for the string literal `Hello` using the manually provided key `hello`.
    println!("{}", t!("hello"));

    // Use `available_locales!` method to get all available locales.
    println!("{:?}", rust_i18n::available_locales!());
}

Locale file

You can use _version key to specify the version (This version is the locale file version, not the rust-i18n version) of the locale file, and the default value is 1.

rust-i18n supports two style of config file, and those versions will always be keeping.

  • _version: 1 - Split each locale into difference files, it is useful when your project wants to split to translate work.
  • _verison: 2 - Put all localized text into same file, it is easy to translate quickly by AI (e.g.: GitHub Copilot). When you write original text, just press Enter key, then AI will suggest you the translation text for other languages.

You can choose as you like.

Split Localized Texts into Difference Files

_version: 1

You can also split the each language into difference files, and you can choise (YAML, JSON, TOML), for example: en.json:

.
β”œβ”€β”€ Cargo.lock
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ locales
β”‚   β”œβ”€β”€ zh-CN.yml
β”‚   β”œβ”€β”€ en.yml
└── src
β”‚   └── main.rs
_version: 1
hello: "Hello world"
messages.hello: "Hello, %{name}"
t_4Cct6Q289b12SkvF47dXIx: "Hello, %{name}"

Or use JSON or TOML format, just rename the file to en.json or en.toml, and the content is like this:

{
  "_version": 1,
  "hello": "Hello world",
  "messages.hello": "Hello, %{name}",
  "t_4Cct6Q289b12SkvF47dXIx": "Hello, %{name}"
}
hello = "Hello world"
t_4Cct6Q289b12SkvF47dXIx = "Hello, %{name}"

[messages]
hello = "Hello, %{name}"

All Localized Texts in One File

_version: 2

Make sure all localized files (containing the localized mappings) are located in the locales/ folder of the project root directory:

.
β”œβ”€β”€ Cargo.lock
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ locales
β”‚   β”œβ”€β”€ app.yml
β”‚   β”œβ”€β”€ some-module.yml
└── src
β”‚   └── main.rs
└── sub_app
β”‚   └── locales
β”‚   β”‚   └── app.yml
β”‚   └── src
β”‚   β”‚   └── main.rs
β”‚   └── Cargo.toml

In the localized files, specify the localization keys and their corresponding values, for example, in app.yml:

_version: 2
hello:
  en: Hello world
  zh-CN: δ½ ε₯½δΈ–η•Œ
messages.hello:
  en: Hello, %{name}
  zh-CN: δ½ ε₯½οΌŒ%{name}
# Generate short hashed keys using `minify_key=true, minify_key_thresh=10`
t_4Cct6Q289b12SkvF47dXIx:
  en: Hello, %{name}
  zh-CN: δ½ ε₯½οΌŒ%{name}

This is useful when you use GitHub Copilot, after you write a first translated text, then Copilot will auto generate other locale's translations for you.

Get Localized Strings in Rust

Import the [t!] macro from this crate into your current scope:

use rust_i18n::t;

Then, simply use it wherever a localized string is needed:

# macro_rules! t {
#    ($($all_tokens:tt)*) => {}
# }
# fn main() {
// use rust_i18n::t;
t!("hello");
// => "Hello world"

t!("hello", locale = "zh-CN");
// => "δ½ ε₯½δΈ–η•Œ"

t!("messages.hello", name = "world");
// => "Hello, world"

t!("messages.hello", "name" => "world");
// => "Hello, world"

t!("messages.hello", locale = "zh-CN", name = "Jason", count = 2);
// => "δ½ ε₯½οΌŒJason (2)"

t!("messages.hello", locale = "zh-CN", "name" => "Jason", "count" => 3 + 2);
// => "δ½ ε₯½οΌŒJason (5)"

t!("Hello, %{name}, you serial number is: %{sn}", name = "Jason", sn = 123 : {:08});
// => "Hello, Jason, you serial number is: 000000123"
# }

Current Locale

You can use rust_i18n::set_locale() to set the global locale at runtime, so that you don't have to specify the locale on each [t!] invocation.

rust_i18n::set_locale("zh-CN");

let locale = rust_i18n::locale();
assert_eq!(&*locale, "zh-CN");

Extend Backend

Since v2.0.0 rust-i18n support extend backend for cusomize your translation implementation.

For example, you can use HTTP API for load translations from remote server:

# pub mod reqwest {
#  pub mod blocking {
#    pub struct Response;
#    impl Response {
#       pub fn text(&self) -> Result<String, Box<dyn std::error::Error>> { todo!() }
#    }
#    pub fn get(_url: &str) -> Result<Response, Box<dyn std::error::Error>> { todo!() }
#  }
# }
# use std::collections::HashMap;
use rust_i18n::Backend;

pub struct RemoteI18n {
    trs: HashMap<String, HashMap<String, String>>,
}

impl RemoteI18n {
    fn new() -> Self {
        // fetch translations from remote URL
        let response = reqwest::blocking::get("https://your-host.com/assets/locales.yml").unwrap();
        let trs = serde_yaml::from_str::<HashMap<String, HashMap<String, String>>>(&response.text().unwrap()).unwrap();

        return Self {
            trs
        };
    }
}

impl Backend for RemoteI18n {
    fn available_locales(&self) -> Vec<&str> {
        return self.trs.keys().map(|k| k.as_str()).collect();
    }

    fn translate(&self, locale: &str, key: &str) -> Option<&str> {
        // Write your own lookup logic here.
        // For example load from database
        return self.trs.get(locale)?.get(key).map(|k| k.as_str());
    }
}

Now you can init rust_i18n by extend your own backend:

# struct RemoteI18n;
# impl RemoteI18n {
#   fn new() -> Self { todo!() }
# }
# impl rust_i18n::Backend for RemoteI18n {
#   fn available_locales(&self) -> Vec<&str> { todo!() }
#   fn translate(&self, locale: &str, key: &str) -> Option<&str> { todo!() }
# }
rust_i18n::i18n!("locales", backend = RemoteI18n::new());

This also will load local translates from ./locales path, but your own RemoteI18n will priority than it.

Now you call [t!] will lookup translates from your own backend first, if not found, will lookup from local files.

Example

A minimal example of using rust-i18n can be found here.

I18n Ally

I18n Ally is a VS Code extension for helping you translate your Rust project.

You can add i18n-ally-custom-framework.yml to your project .vscode directory, and then use I18n Ally can parse t! marco to show translate text in VS Code editor.

Extractor

Experimental

We provided a cargo i18n command line tool for help you extract the untranslated texts from the source code and then write into YAML file.

In current only output YAML, and use _version: 2 format.

You can install it via cargo install rust-i18n-cli, then you get cargo i18n command.

$ cargo install rust-i18n-cli

Extractor Config

πŸ’‘ NOTE: package.metadata.i18n config section in Cargo.toml is just work for cargo i18n command, if you don't use that, you don't need this config.

[package.metadata.i18n]
# The available locales for your application, default: ["en"].
# available-locales = ["en", "zh-CN"]

# The default locale, default: "en".
# default-locale = "en"

# Path for your translations YAML file, default: "locales".
# This config for let `cargo i18n` command line tool know where to find your translations.
# You must keep this path same as the one you pass to method `rust_i18n::i18n!`.
# load-path = "locales"

Rust I18n providered a i18n bin for help you extract the untranslated texts from the source code and then write into YAML file.

$ cargo install rust-i18n-cli
# Now you have `cargo i18n` command

After that the untranslated texts will be extracted and saved into locales/TODO.en.yml file.

You also can special the locale by use --locale option:

$ cd your_project_root_directory
$ cargo i18n

Checking [en] and generating untranslated texts...
Found 1 new texts need to translate.
----------------------------------------
Writing to TODO.en.yml

Checking [fr] and generating untranslated texts...
Found 11 new texts need to translate.
----------------------------------------
Writing to TODO.fr.yml

Checking [zh-CN] and generating untranslated texts...
All thing done.

Checking [zh-HK] and generating untranslated texts...
Found 11 new texts need to translate.
----------------------------------------
Writing to TODO.zh-HK.yml

Run cargo i18n -h to see details.

$ cargo i18n -h
cargo-i18n 3.1.0
---------------------------------------
Rust I18n command to help you extract all untranslated texts from source code.

It will iterate all Rust files in the source directory and extract all untranslated texts that used `t!` macro. Then it will generate a YAML file and merge with the existing translations.

https://github.com/longbridgeapp/rust-i18n

Usage: cargo i18n [OPTIONS] [-- <SOURCE>]

Arguments:
  [SOURCE]
          Extract all untranslated I18n texts from source code

          [default: ./]

Options:
  -t, --translate <TEXT>...
          Manually add a translation to the localization file.

          This is useful for non-literal values in the `t!` macro.

          For example, if you have `t!(format!("Hello, {}!", "world"))` in your code,
          you can add a translation for it using `-t "Hello, world!"`,
          or provide a translated message using `-t "Hello, world! => Hola, world!"`.

          NOTE: The whitespace before and after the key and value will be trimmed.

  -h, --help
          Print help (see a summary with '-h')

  -V, --version
          Print version

Debugging the Codegen Process

The RUST_I18N_DEBUG environment variable can be used to print out some debugging infos when code is being generated at compile time.

$ RUST_I18N_DEBUG=1 cargo build

Benchmark

Benchmark [t!] method, result on MacBook Pro (2023, Apple M3):

t                       time:   [32.637 ns 33.139 ns 33.613 ns]
t_with_locale           time:   [24.616 ns 24.812 ns 25.071 ns]
t_with_args             time:   [128.70 ns 128.97 ns 129.24 ns]
t_with_args (str)       time:   [129.48 ns 130.08 ns 130.76 ns]
t_with_args (many)      time:   [370.28 ns 374.46 ns 380.56 ns]
t_with_threads          time:   [38.619 ns 39.506 ns 40.419 ns]
t_lorem_ipsum           time:   [33.867 ns 34.286 ns 34.751 ns]

The result 101 ns (0.0001 ms) means if there have 10K translate texts, it will cost 1ms.

License

MIT

rust-i18n's People

Contributors

ajantti avatar arne91 avatar eyeplum avatar huacnlee avatar jackzhp avatar kijewski avatar mjoaaquin avatar mslxl avatar pure-peace avatar sunli829 avatar urkle avatar varphone avatar wendajiang avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-i18n's Issues

Incompatibility between "support" v2.1.0 and "macro" v2.0.0 silently breaks v2 builds.

A crate of mine now fails to build, and it took me a while to figure out why.

My project depends on rust-i18n with version selector 2. That pulls the support sub-crate version 2.1.0 and the macro sub-crate version 2.0.0, since these are the latest versions in the registry.

See, SimpleBackend::new takes no arguments in support version 2.1.0, but the i18n macro in macro version 2.0.0 generates a call to it that passes two.

Therefore, the macro crate, when on version 2.0.0, shoud require that support also be version 2.0.0, since in that version SimpleBackend::new takes two arguments.

I've never published a package to the registry, so I don't know how one would go about amending past versions' dependencies, but publishing version 2.2.0 for the other sub-crates should work for now. I say that because the macro crate on 2.2.0 generates a proper call to SimpleBackend::new with no arguments.

Are there any plans to implement pluralization like rails?

Such as:

# config/locales/en.yml

en:
  notification:
    one: You have 1 notification
    other: You have %{count} notifications
#app/views/notifications/index.html.erb

<%= t("notification", count: current_user.notifications.count) %>

From watching the tests and doing some tests myself I cant see that, this library is doing like the the above?

allow for value processor

Not sure how or where I would implement it, but it would be nice if we could process our values prior to being stored in our in-memory hashmap. This could allow translators to use a minimal formatting language (e.g. markdown) so that I can store it as html snippets rather than still having to do this on the fly. I guess I could also do it manually of course, but that would mean I have the data twice.

CLI doesn't seem to create TODO.x.yml

I used this project many times.
Since why, I am creating this issue.

I had forgotten what extensions were used for the YAML, .yml or .yaml.
So I installed the CLI and tried both with always only one of the list of available locales, but never did it create the appropriate TODO file, and I still wonder why.

Here are the files I created.

locales/app.yml

_version: 2
introduction:
  en: |
    Welcome! It appears to be your first time using our services. 
    
    It is a pleasure to meet you. 
    
    Before we can collaborate and grant you access to our services, please take the time to review our terms and conditions and privacy policy. 
    
    Before accepting, we recommend reading our policies.

Cargo.toml

[package]
name = "purpura_handler"
version = "0.1.0"
edition = "2021"

[package.metadata.i18n]
available-locales = ["en", "de", "es", "fr", "it", "ja", "ko", "pt", "ru", "zh"]
default-locale = "en"

[dependencies]
chrono = { version = "0.4.32", features = ["serde"] }
lapin = { version = "2.3.1", features = ["codegen", "vendored-openssl"] }
rust-i18n = "3.0.1"
scylla = { version = "0.11.1", features = ["chrono", "time"] }
serde = { version = "1.0.195", features = ["derive"] }
serde_yaml = "0.9.30"
serenity = { version = "0.12.0", features = ["full"] }
totp-rs = { version = "5.5.1", features = ["qr", "serde", "zeroize", "rand"] }
tracing = { version = "0.1.40", features = ["async-await"] }

Thank you for having read this.
Have a great day.

Please document app.yml / app.yaml _version: 2 to work / find keys properly

After frustration, I found closed issue #60 to solve my problem.

If _version: 1 in app.yml / app.yaml, the locale parameter of t!("key", locale = "en") is prepended, resulting in an invalid key like: en.key. Thus, the resolution fails.

I found I must set _version: 2 in the file per issue #60.

And here I thought that _version was to specify my "version" of the translations, but it is in fact the rust-i18n API version.

Can you please add this to the README right in the app.yml section so that in the future, people know they must use _version: 2 to get this format to work?

Thank you.

Allow accessing the raw translation file values

My locale file contains a bunch of keys that I would like to randomly pick from. To do that, I need to have access to the list of keys. In the spirit of a single source of truth, I'd prefer not to hard-code the list of keys. Is it possible to add a function to get a raw JSON value under a certain key?

t! macro doesn't work inside mod

I have rust-i18n loaded in Cargo.toml as follows
rust-i18n = "3.0.1"

when I call it inside main.rs it works fine:

use rust_i18n::*;
rust_i18n::i18n!("locales");

async fn main (){
    rust_i18n::set_locale("en");
   println!("locale {} test", t!("register", locale = "it-IT"))
}

I get the correct value printed.

But it doesn't work inside a module. I have a module called mytest, impoted in lib.rs.
Inside the module there is this code:

use rust_i18n::t;

pub fn test() {
    rust_i18n::set_locale("it-IT");
    println!("locale {} test", t!("register", locale = "it-IT"));
}

but when i try to compile I get this error:

error[E0425]: cannot find function `_rust_i18n_translate` in the crate root
 --> src/mytest/mytest.rs:9:33
  |
9 |     println!("locale {} test", t!("register", locale = "it-IT"));
  |                                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in the crate root
  |
  = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)

I can't see what I'm doing wrong.
Thanks

Multiple definitions of same key shows no error

Hey, thanks for this crate! πŸ™

When moving one of my project to use it, I added the same key twice by accident. Obviously the end result of what was displayed was not as expected πŸ˜“

I don't know how feasible it is, but it would be awesome if this could be caught at compile time with a clear message :)

Separate `cargo i18n` to separate crate

Including the i18n cargo cli tool in the main crate causes the whole crate to depend on packages such as clap (v2) when the main crate does not need it for use in an application.

I would recommend splitting that binary to a separate crate (rust-i18n-cli, rust-i18n-cargo, cargo-i18n?) so that the dependencies can be reduced.

Recompile target if language files change

When the language files change, the changes are not applied and compiled.
I always have to change the .rs file where I use the macro 't!' to get the language changes in the binfile.

Seems the `load-path`is not working properly in workspace context

Hi,

My current setup is myworkspace where I an run myserver, myclient and mylib
I set it in my sub project myclient but it point to myworkspce folder locales
I tried to set load-path in myworkspace toml as "myclient/locales" but no avail. It still point to myworkspace folder locales

edit: I manage to make it work if I put full absolute path into i18n!()

Thanks

[BUG] cannot find function `translate` in the crate root

Hello, i am trying to use this crate, but when i use the macro t, i have this error:

error[E0425]: cannot find function `translate` in the crate root
  --> src\libs\i18n.rs:13:9
   |
13 |   dbg!(&t!("hello"));
   |         ^^^^^^^^^^^ not found in the crate root
   |
   = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider importing this function
   |
1  | use crate::i18n::translate;
   |

error[E0425]: cannot find function `translate` in the crate root
  --> src\libs\i18n.rs:14:9
   |
14 |   dbg!(&t!("hello"));
   |         ^^^^^^^^^^^ not found in the crate root
   |
   = note: this error originates in the macro `t` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider importing this function
   |
1  | use crate::i18n::translate;
   |

even if the compiler tells me to import the function, it doesn't exist !
My code:

use rust_i18n::{ i18n, t };
use crate::utils;


pub fn load(logs: bool){
  i18n!("locales");
  if logs {
    utils::success("i18n", "Language modules ready to use")
  }
}

pub fn test(){
  dbg!(&t!("hello"));
  dbg!(&t!("hello"));
}

rust-i18n breaks rust-analyzer

Hello, dear developers! I'm getting the following error in rust-analyzer logs:

[ERROR rust_analyzer::main_loop] FetchBuildDataError:
error: failed to run custom build command for rust-i18n v1.1.4
note: To improve backtraces for build dependencies, set the CARGO_PROFILE_DEV_BUILD_OVERRIDE_DEBUG=true environment variable to enable debug information generation.

Caused by:
process didn't exit successfully: /home/garrus/Projects/xmldos/backend/target/debug/build/rust-i18n-05a56b0ea4b88325/build-script-build (exit status: 101)
--- stderr
thread 'main' panicked at 'called Result::unwrap() on an Err value: GlobError { path: "/home/garrus/.cache/yay/sqlitestudio/pkg", error: Os { code: 13, kind: PermissionDenied, message: "Permission denied" } }', /home/garrus/.cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.1.4/build.rs:30:31
stack backtrace:
0: rust_begin_unwind
at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/std/src/panicking.rs:579:5
1: core::panicking::panic_fmt
at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/panicking.rs:64:14
2: core::result::unwrap_failed
at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library/core/src/result.rs:1750:5
3: core::result::Result<T,E>::unwrap
4: build_script_build::main
5: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with RUST_BACKTRACE=full for a verbose backtrace.

If I remove the crate from dependencies then everything is OK.
I don't know why it's trying to visit /home/garrus/.cache/yay/sqlitestudio/pkg directory that doesn't have any attitude to my project or to .cargo cache itself

[BUG] Error while trying to cross compile

I am using this crate in my project, and when trying to cross compile with cross I get the following error:
error: failed to run custom build command for rust-i18n v1.0.1

Caused by:
process didn't exit successfully: /target/release/build/rust-i18n-5c11b3d47303e377/build-script-build (exit status: 101)
--- stdout
cargo:rerun-if-changed=/cargo/git/checkouts/bifrost-2b7c66c891beea9d/0aeeb1e/crates/bifrost_cli/locales/en.yml
cargo:rerun-if-changed=/cargo/git/checkouts/bifrost-2b7c66c891beea9d/0aeeb1e/crates/bifrost_cli/locales/es.yml
cargo:rerun-if-changed=/cargo/git/checkouts/bifrost-2b7c66c891beea9d/af52f60/crates/bifrost_cli/locales/en.yml
cargo:rerun-if-changed=/cargo/git/checkouts/bifrost-2b7c66c891beea9d/af52f60/crates/bifrost_cli/locales/es.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-0.6.1/examples/app/locales/en.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-0.6.1/examples/app/locales/fr.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-0.6.1/examples/app/locales/view.en.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-0.6.1/examples/app/locales/view.fr.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.0.1/examples/app/locales/en.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.0.1/examples/app/locales/fr.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.0.1/examples/app/locales/view.en.yml
cargo:rerun-if-changed=/cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.0.1/examples/app/locales/view.fr.yml

--- stderr
thread 'main' panicked at 'called Result::unwrap() on an Err value: GlobError { path: "/etc/ssl/private", error: Os { code: 13, kind: PermissionDenied, message: "Permission denied" } }', /cargo/registry/src/github.com-1ecc6299db9ec823/rust-i18n-1.0.1/build.rs:33:27

AtomicStr is unsound, causes use-after-free

Minimal example:

use std::thread::spawn;

use rust_i18n::{t, set_locale};

rust_i18n::i18n!("locales", fallback = "en");

fn main() {
    set_locale("fr");
    spawn(|| {
        let mut i = 0u32;
        loop {
            i = i.wrapping_add(1);
            if i % 2 == 0 {
                set_locale(&format!("en-{i}"));
            } else {
                set_locale(&format!("fr-{i}"));
            }
        }
    });
    spawn(|| {
        loop {
            t!("hello");
        }
    });
}

cargo r -r

malloc(): unaligned fastbin chunk detected
Aborted

cargo +nightly miri r

error: Undefined Behavior: not granting access to tag <108954> because that would remove [Unique for <111597>] which is strongly protected because it is an argument of call 53836
   --> /home/kijewski/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/non_null.rs:399:18
    |
399 |         unsafe { &*self.as_ptr().cast_const() }
    |                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <108954> because that would remove [Unique for <111597>] which is strongly protected because it is an argument of call 53836
    |
    = help: this indicates a potential bug in the program: it performed an invalid operation, but the Stacked Borrows rules it violated are still experimental
    = help: see https://github.com/rust-lang/unsafe-code-guidelines/blob/master/wip/stacked-borrows.md for further information

Cannot find function `_rust_i18n_translate` in the crate root

Good morning, I was wondering if I did something wrong, which I'm sure of it. But sadly, I wasn't able to put the finger on what is wrong.
In my main.rs I put

#[macro_use]
extern crate rust_i18n;

and in main()

#[tokio::main]
async fn main() {
  // lots of config checking 

  i18n!("locales", fallback = "en");
  
  // and more config
  }

but I keep getting

image

If this can help, this is my src tree. Just in case the file organization was the problem? I don't know, so I put it here.

image

I hope that someone smarter than me (easy due to only my 2 brain cells) can help me.

Thank you very much for your time.

`t!` should return `&'static str` instead of `String`

Returning probably have a program-wide lifetime hardly encoded string as String is not a good way I think.

Every time I called the t!() it creates and gives me ownership of the same string again and again.

I also can't use it where I need to temporarily create a String and take it's reference .as_str() to the function:

set_toast_message(t!("Saving settings failed").as_str());

Error:

set_toast_message(t!("Saving settings failed").as_str());
    |                         -----------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------- temporary value is freed at the end of this statement
    |                         |                      |
    |                         |                      creates a temporary value which is freed while still in use

I think returning &'static str instead of String for program-wide available translations is good way to go.

Why t! macro doers not print the translated text at once

I have this

   println!("{}", t!("errors.min_length", field = "username", min = 8)); 

but the result is

en.errors.min_length

which is not the value in the json file

here is the locales/en.json file :

{
  "_version": 1,
  "hello": "Hello world",
  "errors.required": "The %{field} field is required.",
  "errors.min_length": "The %{field} field must be at least %{min} characters long.",
  "errors.max_length": "The %{field} field must not exceed %{max} characters."
}

The weird thing is the text print Ok after some time ..
it seems like the crate take minutes to get the locale ?

i dunno but now it started to print ok without changing something!
yesterday also the same !!?

i tried to change the text in the json file and it does not change in the terminal ..
it took time or until something happened , I do not know which is

I have also tried to set the locale to en manually .

Add to support all translated text in one file better for GitHub Copilot

For example:

welcome:
  en: Welcome to use Rust I18n.
  zh-CN: ζ¬’θΏŽδ½Ώη”¨ Rust I18n.
  zh-HK: ζ­‘θΏŽδ½Ώη”¨ Rust I18n.
login:
  en: Login
  zh-CN: 登录
  zh-HK: η™»ε…₯

After that, when we write en, the GitHub Copilot or other AI tool will give us a suggestion of other translations.

And we need to support both translation's struct.

More than one fallback with priority

Is it possible to set more than one fallback in order of priority?

For example, I want the fallback to be:

English, and then Spanish if not found in English.

Something like:

i18n!(fallback = ["en", "es"]);

Method to retrieve available languages

A method to retrieve the available languages is needed. This is needed when displaying the language dropdown on the UI.
The current workaround is to manually look inside the locales folder, but this feels like a hack.

[INVALID] Deeply nested keys are not working

This library supports usage of keys with one level of nesting, like editor.title, or main.message, but once I try to go one more level deep, the library stops recognizing keys. Examples are dbe.title.open_database or dbe.title.open_folder.

Readme clashes with Backend trait contract

The Backend trait contract returns Option<&str> in version 2.0, however the README shows the example of the RemoveI18n that returns Option instead.

Currently I'm trying to create a "reloadable backend". where I can setup a watcher via the Notify create to reload the translations from disk. However I am running into issues due to the contract change of Backend as I am unable to return a reference to a string.

struct ReloadableBackend {
    translations: Arc<RwLock<HashMap<String, HashMap<String, String>>>>,
}

impl Backend for ReloadableBackend {
   fn available_locales(&self) -> Vec<&str> {
        let l = self.i18n.read();
        if let Ok(h) = l {
            let mut locales = h.keys().map(|k| k.as_str()).collect::<Vec<_>>();
            locales.sort();
            locales // Error cannot return value referencing local variable `h` [E0515] returns a value referencing data owned by the current function
        } else {
            Vec::new()
        }
    }

    fn translate(&self, locale: &str, key: &str) -> Option<&'_ str> {
        let l = self.i18n.read();
        if let Ok(h) = l {
            return h
                .get(locale)
                .and_then(|trs| trs.get(key))
                .and_then(|k| Some(k.as_str())); // cannot return value referencing local variable `h` [E0515] returns a value referencing data owned by the current function
        } else {
            None
        }
    }
}

This, of course, makes perfect sense since it has to return a reference from the locked data in the Arc.

If the contract were still returning String then this would not be an issue as I could just return a cloned string and move on.

Single app.yml file doesn't work.

I have a single app.yml file in locales folder like this:

Settings:
  en: Settings
  tr: Ayarlar
Country:
  en: Country
  tr: Ülke
City:
  en: City
  tr: Şehir

I initialized with i18n!() and I set the labels of the program like t!("Settings").

But the strings I got on the labels like:

en.Settings
en.Country
en.City

Which is not I expected:

Settings
Country
City

Also tried running the app with LANG=tr_TR.UTF-8 myapp and I still see en.Settings not tr.Settings or translated Ayarlar.

Also:
The output of the println!("{:?}", rust_i18n::available_locales!()); is ["app"]

It seems single file is not supported yet in version = "2"

How to set the locale

I've

available-locales = ["en", "de"]

Setting a locale should be possible via autodetect - did I miss something here?

I tried
if let Ok(lang) = env::var("LANG") {
rust_i18n::set_locale(&lang);
}

However that won't work on windows. How is the recommended way of setting a locale to the system setting?

But ended up with:

image

allow for a fallback of missing translations

I might have missed it but it seems that the behaviour of missing keys is to simply show the key? Could it be possible to customize this such that we can default to another language? Might be the default language, but could also be suitable to be able to setup a chain fo fallbacks such that for example pt-BR falls back to pt which falls back to en?

Any option to load translation files at runtime?

I'm looking for a translations solution for a WASM web frontend project, written in Rust.

I like the t! macro and this is awesome for development and for producing the correct translations etc.
However I don't want all of the translations bundled into the WASM file. This would make it far too large.
What I want instead is to allow the user to select their language and then download translations for the selected language from S3 or similar.

Would that be possible using this crate?

Better docs

This crate looks nice but before attempting to use it I'd like to see better documentation.

From what I can tell:

  • i18n! must be called from the crate root; it generates items which are expected to be found in the crate root
  • t! returns a String
  • t!("foo", x = 1) is equivalent to t!("foo", x => 1) (why support both?)
  • t!("foo", locale = "en") β€” parameter locale has special handling
  • Unused parameters like t!("foo", unused_param=5) are allowed and ignored
  • Passing tuples or structs as parameters is not supported
  • Every passed parameter must support std::fmt::Display (even if unused)

Release `2.4.0` breaks semver

I updated to 2.4.0 and my code stopped compiling.

What I expected

Minor bump should not lead to compilation failures.

Semver recommends that after version 1.0.0, bumps to the minor version (in this case from 2.3.0 to 2.4.0) should be backward compatible.

Backward compatibility has been broken in 2.4.0. The release note even mentions a breaking change https://github.com/longbridgeapp/rust-i18n/releases/tag/v2.4.0

Why Semver maters

cargo uses semver to deduplicate dependencies, if a crate's release number doesn't follow semver, this will break cargo, resulting in confusion for the end-users, see the cargo reference for explanation https://doc.rust-lang.org/cargo/reference/resolver.html#semver-compatibility

Cargo also promises that a simple cargo update won't break your code. If a crate doesn't follow semver, running cargo update will result in user code breaking.

Loading the translation fails with version 1.0.1

In my project, I use your package which displayed the translated texts until yesterday.
Maybe there is another configuration setting missing ?

For discovering the issue I created a separate most simple project:
https://github.com/liquidnight2/test-cases/tree/main/translationtest


# cat locales/en.yml
en:
  hello : Hello_World-English!
  
  
#  cargo run
   Compiling translationtest v0.1.0 (/mnt/stripe/workspace1/test-cases/translationtest)
    Finished dev [unoptimized + debuginfo] target(s) in 34.39s
     Running `target/debug/translationtest`
translated=en.hello
thread 'main' panicked at 'assertion failed: `(left == right)`
  left: `"en.hello"`,
 right: `"Hello_World-English!"`', src/main.rs:8:5

Missing Docs for i18n! macro

The i18n! macro has a missing doc via rust doc. Since we cannot add allow to the macro this becomes an issue if the crate uses #![deny(missing_docs)].

missing docs for macro

Add to support string var name

Current:

t!("hello", foo = "123", foo_bar = "Foo Bar")

Expected:

t!("hello", "foo" = "123", "foo-bar" = "Foo Bar")
// Or
t!("hello", "foo" => "123", "foo-bar" => "Foo Bar")

Compile time checks

I didn't find any information about compile-time checks. If message in locale file is missing then string passed to macro will be displayed:

if locale.is_empty() {
return key.to_string();
}
return format!("{}.{}", locale, key);

Will it be hard to create a separate macro or add option for t!() to make sure the message I use exists in the locale file at compile time (like sqlx query! does)? This can be tricky, so maybe an easier way is to create xtask (or test) to check that all the messages I'm using in the code are defined. This way it can be easily integrated with CI.
All I want to achieve is if my app has passed CI, the locale files will have the appropriate content and the user won't see "en.wron_mesag".

Probably internationalization-rs can be used to see how they achieve compile time verification (using build.rs).

Language code inference doesn't work

Hi, I updated my project to v1.0.1 of rust-i18n and I have a problem after removing the locale code from the files.

Here's a sample file, located at locales/en.yml:

run:
  script-not-found: "the script `%{name}` was not found."

However, when I use:

t!("run.script-not-found", name = "hi")

It comes out as en.run.script-not-found.

I have to put:

en:
  run:
    script-not-found: "the script `%{name}` was not found."

In order to make it work properly.

Does not see locale in a docker container.

// main.rs
rust_i18n::i18n!("locales");

// message_service.rs
use rust_i18n::t;

t!("invalidRequest", locale = locale.as_str()) return en.invalidRequest

If run not in a docker container all works fine.

How to configure it for docker container? Of course i copy locales folder with .yml files to container.

% can not be the first character

in *.yml file,

n_files_deleted: %{n} files deleted. # this line does not work.
n_files_deleted: files deleted:%{n}. #this line works.

about locale

On mac os, with sys-locale = "0.2.4", I got zh-Hans-US for the current system locale.
but my files is defined as zh-CN.yml.

Locale related stuff, how to resolve this conflict?

t! return previous value in the file

Does it can detect file changed and compile newer version of locale files? It seems not work.

I just modified the value with the same key locale file en.toml and it just showed me previous value of it.

How to tell rust-i18n to file is changed?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.