#translation #localization #gettext #编译时 #文本文件 #命令行工具

rust-i18n

Rust I18n 通过 Rust 代码生成,在编译时加载 YAML 文件存储的翻译,并提供了 t! 宏以简化获取翻译文本的过程

40 个版本 (21 个稳定版)

3.1.2 2024 年 8 月 20 日
3.0.1 2024 年 1 月 23 日
2.4.0 2024 年 1 月 19 日
2.3.0 2023 年 11 月 14 日
0.5.1 2021 年 12 月 10 日

#7 in 国际化 (i18n)

Download history 4039/week @ 2024-05-04 3646/week @ 2024-05-11 3043/week @ 2024-05-18 3400/week @ 2024-05-25 3921/week @ 2024-06-01 3534/week @ 2024-06-08 3769/week @ 2024-06-15 4118/week @ 2024-06-22 3694/week @ 2024-06-29 3661/week @ 2024-07-06 2953/week @ 2024-07-13 3357/week @ 2024-07-20 4649/week @ 2024-07-27 4553/week @ 2024-08-03 3468/week @ 2024-08-10 2589/week @ 2024-08-17

每月 15,891 次下载
16 个包中使用 (15 个直接使用)

MIT 许可证

79KB
1K SLoC

Rust I18n

CI Docs Crates.io

🎯 让国际化变得简单!

Rust I18n 是一个用于从一组 (YAML、JSON 或 TOML) 映射文件加载本地化文本的包。这些映射在编译时转换为 Rust 程序可读的数据,然后可以通过调用提供的 [t!] 宏来简单地加载本地化文本。

与其他 I18n 库不同,Rust I18n 的目标是提供简单易用的 API。

此包的 API 受 ruby-i18nRails I18n 的启发。

特性

  • 编译时代码生成,将翻译包含到二进制文件中。
  • 全局 [t!] 宏,用于在所有地方加载本地化文本。
  • 使用 YAML(默认)、JSON 或 TOML 格式映射本地化文本,并支持多文件合并。
  • cargo i18n 命令行工具,用于检查和提取未翻译文本到 YAML 文件。
  • 支持在一个文件中包含所有本地化文本,或按区域拆分到不同的文件中。
  • 支持指定缺失翻译的回退区域链。
  • 支持自动查找回退区域的语言区域。例如,如果 zh-CN 不可用,它将回退到 zh。 (自 v2.4.0)
  • 支持短哈希键以优化内存使用和查找速度。 (自 v3.1.0)
  • 支持在 [t!] 中使用格式变量,并支持使用 std::fmt 语法进行格式变量。 (自 v3.1.0)
  • 支持在警告级别使用 log-miss-tr 功能记录缺失的翻译,该功能需要 log crate。 (自 v3.1.0 版本起)

用法

在 Cargo.toml 中添加 crate 依赖并设置 I18n 配置

[dependencies]
rust-i18n = "3"

lib.rsmain.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!();

或者您可以直接导入使用

// 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!());
}

地区文件

您可以使用 _version 键来指定地区文件的版本(此版本是地区文件的版本,而不是 rust-i18n 的版本),默认值为 1

rust-i18n 支持两种配置文件风格,这些版本将始终保留。

  • _version: 1 - 将每个地区拆分为不同的文件,当您的项目需要拆分翻译工作时很有用。
  • _verison: 2 - 将所有本地化文本放入同一文件中,使用 AI(例如:GitHub Copilot)快速翻译非常容易。当您编写原始文本时,只需按 Enter 键,然后 AI 将为您建议其他语言的翻译文本。

您可以根据自己的喜好选择。

将本地化文本拆分为不同的文件

_version: 1

您也可以将每种语言拆分为不同的文件,您可以选择 YAML、JSON 或 TOML 格式,例如: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}"

或者使用 JSON 或 TOML 格式,只需将文件重命名为 en.jsonen.toml,内容如下

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

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

在一个文件中包含所有本地化文本

_version: 2

确保所有包含本地化映射的本地化文件都位于项目根目录下的 locales/ 文件夹中

.
├── Cargo.lock
├── Cargo.toml
├── locales
   ├── app.yml
   ├── some-module.yml
└── src
   └── main.rs
└── sub_app
   └── locales
   │   └── app.yml
   └── src
   │   └── main.rs
   └── Cargo.toml

在本地化文件中,指定本地化键及其对应的值,例如,在 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}

当您使用 GitHub Copilot 时,这非常有用。在您编写第一个翻译文本后,Copilot 将自动为您生成其他语言的翻译。

在 Rust 中获取本地化字符串

将此 crate 中的 [t!] 宏导入到当前作用域中

use rust_i18n::t;

然后,在需要本地化字符串的地方简单地使用它

# 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"
# }

当前地区

您可以使用 rust_i18n::set_locale() 在运行时设置全局地区,这样您就不必在每个 [t!] 调用中指定地区。

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

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

扩展后端

自 v2.0.0 版本起,rust-i18n 支持扩展后端以自定义您的翻译实现。

例如,您可以使用 HTTP API 从远程服务器加载翻译

# 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_yml::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());
    }
}

现在您可以通过扩展自己的后端来初始化 rust_i18n

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

这也会从 ./locales 路径加载本地翻译,但您的 RemoteI18n 将具有优先权。

现在您调用 [t!] 将首先从您自己的后端查找翻译,如果未找到,则从本地文件中查找。

示例

可以在这里找到使用 rust-i18n 的最小示例 here

I18n Ally

I18n Ally 是一个 VS Code 扩展,可帮助您翻译 Rust 项目。

您可以将i18n-ally-custom-framework.yml添加到您的项目.vscode目录中,然后使用I18n Ally可以解析t!宏来在VS Code编辑器中显示翻译文本。

提取器

实验性

我们提供了一个cargo i18n命令行工具,帮助您从源代码中提取未翻译的文本,并将其写入YAML文件。

当前仅输出YAML,并使用_version: 2格式。

您可以通过cargo install rust-i18n-cli安装它,然后您将获得cargo i18n命令。

$ cargo install rust-i18n-cli

提取器配置

💡 注意:在Cargo.toml中的package.metadata.i18n配置部分仅适用于cargo i18n命令,如果您不使用它,则不需要此配置。

[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提供了一个i18n二进制文件,帮助您从源代码中提取未翻译的文本,并将其写入YAML文件。

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

之后,未翻译的文本将被提取并保存到locales/TODO.en.yml文件中。

您也可以通过使用--locale选项来指定特定的区域设置

$ 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

运行cargo i18n -h以查看详细信息。

$ 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

代码生成过程的调试

当在编译时生成代码时,可以使用RUST_I18N_DEBUG环境变量来打印一些调试信息。

$ RUST_I18N_DEBUG=1 cargo build

基准测试

基准测试 [t!] 方法,结果在2023年MacBook Pro (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]

结果101 ns (0.0001 ms)表示如果有10K翻译文本,它将花费1ms

许可证

MIT

依赖关系

~9–20MB
~292K SLoC