4个稳定版本

新版本 3.1.1 2024年8月9日
3.0.0 2024年1月19日
2.4.0 2024年1月19日
2.3.0 2023年11月14日

145国际化(i18n) 中排名

Download history 5/week @ 2024-05-26 8/week @ 2024-06-02 1/week @ 2024-06-09 3/week @ 2024-06-30 11/week @ 2024-07-07 26/week @ 2024-07-28 186/week @ 2024-08-04

每月212次下载

MIT许可证

100KB
1.5K SLoC

Rust I18n

CI Docs Crates.io

🎯 让I18n变得简单!

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库。 (自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 的最小示例可以在 这里 找到。

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!] 方法,在 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]

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

许可协议

MIT

依赖关系

~10–21MB
~299K SLoC