【rust-i18n】readme.md文件

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

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

该 crate 的 API 灵感来源于 ruby-i18nRails I18n

特性

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

使用方法

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

toml 复制代码
[dependencies]
rust-i18n = "3"

lib.rsmain.rs 中加载宏并初始化翻译:

rust,compile_fail,no_run 复制代码
// 加载 I18n 宏,允许你在任何地方使用 `t!` 宏。
#[macro_use]
extern crate rust_i18n;

// 为当前 crate 初始化翻译。
// 如果 `Cargo.toml` 中存在 `[package.metadata.i18n]` 配置,这将加载该配置。
// 或者,你可以通过 `i18n!` 传递参数来覆盖它。
i18n!("locales");

// 配置将缺失的翻译回退到 "en" 语言环境。
// 使用 `fallback` 选项设置回退语言环境。
//
i18n!("locales", fallback = "en");

// 或者设置多个具有优先级的回退语言环境。
//
i18n!("locales", fallback = ["en", "es"]);

// 对于长字符串字面量,使用短哈希键作为标识符,
// 以优化内存使用和查找速度。
// 密钥生成算法为 `${Prefix}${Base62(SipHash13("msg"))}`。
i18n!("locales", minify_key = true);
//
// 或者,你可以自定义短哈希键的密钥长度、前缀和阈值。
i18n!("locales",
      minify_key = true,
      minify_key_len = 12,
      minify_key_prefix = "t_",
      minify_key_thresh = 64
);
// 现在,如果消息长度超过 64,`t!` 宏将自动为它生成一个带有 "t_" 前缀的 12 字节短哈希键,
// 否则,它将使用原始消息。

// 如果没有参数,则使用 Cargo.toml 中的配置或默认配置。
i18n!();

或者你也可以直接导入使用:

rust,no_run 复制代码
// 当你想要使用 `t!` 宏时,必须在每个文件中导入。
use rust_i18n::t;

rust_i18n::i18n!("locales");

fn main() {
    // 使用手动提供的键 `hello` 查找字符串字面量 `Hello` 的翻译。
    println!("{}", t!("hello"));

    // 使用 `available_locales!` 方法获取所有可用的语言环境。
    println!("{:?}", rust_i18n::available_locales!());
}

语言环境文件

你可以使用 _version 键来指定语言环境文件的版本(此版本是语言环境文件的版本,而不是 rust-i18n 的版本),默认值为 1

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

  • _version: 1 - 将每个语言环境拆分为不同的文件,当你的项目想要拆分翻译工作时,这很有用。
  • _version: 2 - 将所有本地化文本放在同一个文件中,这样可以方便地通过 AI(例如:GitHub Copilot)快速翻译。当你编写原始文本时,只需按 Enter 键,AI 就会为你建议其他语言的翻译文本。

你可以根据需要选择。

将本地化文本拆分到不同文件中

_version: 1

你也可以将每种语言拆分为不同的文件,并且可以选择(YAML、JSON、TOML),例如:en.json

bash 复制代码
.
├── Cargo.lock
├── Cargo.toml
├── locales
│   ├── zh-CN.yml
│   ├── en.yml
└── src
│   └── main.rs
yml 复制代码
_version: 1
hello: "Hello world"
messages.hello: "Hello, %{name}"
t_4Cct6Q289b12SkvF47dXIx: "Hello, %{name}"

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

json 复制代码
{
  "_version": 1,
  "hello": "Hello world",
  "messages.hello": "Hello, %{name}",
  "t_4Cct6Q289b12SkvF47dXIx": "Hello, %{name}"
}
toml 复制代码
hello = "Hello world"
t_4Cct6Q289b12SkvF47dXIx = "Hello, %{name}"

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

所有本地化文本放在一个文件中

_version: 2

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

bash 复制代码
.
├── 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 中:

yml 复制代码
_version: 2
hello:
  en: Hello world
  zh-CN: 你好世界
messages.hello:
  en: Hello, %{name}
  zh-CN: 你好,%{name}
# 使用 `minify_key=true, minify_key_thresh=10` 生成短哈希键
t_4Cct6Q289b12SkvF47dXIx:
  en: Hello, %{name}
  zh-CN: 你好,%{name}

当你使用 GitHub Copilot 时,这很有用,你写完第一个翻译文本后,Copilot 会自动为你生成其他语言环境的翻译。

在 Rust 中获取本地化字符串

从该 crate 中将 [t!] 宏导入当前作用域:

rust,no_run 复制代码
use rust_i18n::t;

然后,在需要本地化字符串的地方直接使用它:

rust,no_run 复制代码
# 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 复制代码
rust_i18n::set_locale("zh-CN");

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

扩展后端

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

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

rust,no_run 复制代码
# 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 std::borrow::Cow;
use rust_i18n::Backend;

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

impl RemoteI18n {
    fn new() -> Self {
        // 从远程 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<Cow<'_, str>> {
        return self.trs.keys().map(|k| Cow::from(k.as_str())).collect();
    }

    fn translate(&self, locale: &str, key: &str) -> Option<Cow<'_, str>> {
        // 在这里编写你自己的查找逻辑。
        // 例如,从数据库加载
        return self.trs.get(locale)?.get(key).map(|k| Cow::from(k.as_str()));
    }

    fn messages_for_locale(&self, locale: &str) -> Option<Vec<(Cow<'_, str>, Cow<'_, str>)>> {
        None
    }
}

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

rust,no_run 复制代码
# use std::borrow::Cow;
# struct RemoteI18n;
# impl RemoteI18n {
#   fn new() -> Self { todo!() }
# }
# impl rust_i18n::Backend for RemoteI18n {
#   fn available_locales(&self) -> Vec<std::borrow::Cow<'_, str>> { todo!() }
#   fn translate(&self, locale: &str, key: &str) -> Option<std::borrow::Cow<'_, str>> { todo!() }
    fn messages_for_locale(&self, locale: &str) -> Option<Vec<(Cow<'_, str>, Cow<'_, 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 命令。

bash 复制代码
$ cargo install rust-i18n-cli

提取器配置

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

toml 复制代码
[package.metadata.i18n]
# 你的应用程序可用的语言环境,默认值:["en"]。
# available-locales = ["en", "zh-CN"]

# 默认语言环境,默认值:"en"。
# default-locale = "en"

# 你的翻译 YAML 文件的路径,默认值:"locales"。
# 此配置用于让 `cargo i18n` 命令行工具知道在哪里找到你的翻译。
# 你必须保持此路径与传递给 `rust_i18n::i18n!` 方法的路径一致。
# load-path = "locales"

Rust I18n 提供了一个 i18n 二进制文件,帮助你从源代码中提取未翻译的文本,然后写入 YAML 文件。

bash 复制代码
$ cargo install rust-i18n-cli
# 现在你有了 `cargo i18n` 命令

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

你还可以使用 --locale 选项指定语言环境:

bash 复制代码
$ 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 查看详细信息。

bash 复制代码
$ cargo i18n -h
cargo-i18n 3.1.0
---------------------------------------
Rust I18n 命令,帮助你从源代码中提取所有未翻译的文本。

它将遍历源目录中的所有 Rust 文件,并提取所有使用 `t!` 宏的未翻译文本。然后它将生成一个 YAML 文件并与现有翻译合并。

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

用法:cargo i18n [OPTIONS] [-- <SOURCE>]

参数:
  [SOURCE]
          从源代码中提取所有未翻译的 I18n 文本

          [默认值:./]

选项:
  -t, --translate <TEXT>...
          手动向本地化文件添加翻译。

          这对于 `t!` 宏中的非字面量值很有用。

          例如,如果你的代码中有 `t!(format!("Hello, {}!", "world"))`,
          你可以使用 `-t "Hello, world!"` 为其添加翻译,
          或者使用 `-t "Hello, world! => Hola, world!"` 提供翻译后的消息。

          注意:键和值前后的空白将被修剪。

  -h, --help
          打印帮助信息(使用 '-h' 查看摘要)

  -V, --version
          打印版本

调试代码生成过程

可以使用 RUST_I18N_DEBUG 环境变量在编译时打印一些调试信息。

bash 复制代码
$ RUST_I18N_DEBUG=1 cargo build

基准测试

对 [t!] 方法进行基准测试,结果在 MacBook Pro(2023,Apple M3)上:

bash 复制代码
t                       时间:[32.637 ns 33.139 ns 33.613 ns]
t_with_locale           时间:[24.616 ns 24.812 ns 25.071 ns]
t_with_args             时间:[128.70 ns 128.97 ns 129.24 ns]
t_with_args (字符串)    时间:[129.48 ns 130.08 ns 130.76 ns]
t_with_args (多个)      时间:[370.28 ns 374.46 ns 380.56 ns]
t_with_threads          时间:[38.619 ns 39.506 ns 40.419 ns]
t_lorem_ipsum           时间:[33.867 ns 34.286 ns 34.751 ns]

结果 101 ns (0.0001 ms) 意味着如果有 10K 个翻译文本,将花费 1ms

使用案例

以下是一些使用 rust-i18n 的热门项目:

相关推荐
锅包一切20 小时前
在蓝桥杯边练边学Rust:2.原生类型
开发语言·学习·蓝桥杯·rust
@atweiwei1 天前
Rust 实现 LangChain
开发语言·算法·rust·langchain·llm·agent·rag
Hello.Reader1 天前
Tauri 开发环境 Prerequisites 桌面 + 移动端)
rust·tauri
Source.Liu1 天前
【rust-i18n】简介
rust·rust-i18n
鸿乃江边鸟1 天前
Spark Datafusion Comet 向量化Rust Native--Native算子ScanExec以及涉及到的Selection Vectors
大数据·rust·spark·arrow
Hello.Reader1 天前
Tauri 用“系统 WebView + 原生能力”构建更小更快的跨平台应用
rust·tauri
RoyLin2 天前
Rust 编写的 40MB 大小 MicroVM 运行时,完美替代 Docker 作为 AI Agent Sandbox
后端·架构·rust
班公湖里洗过脚2 天前
《通过例子学Rust》第20章 标准库更多介绍
rust
班公湖里洗过脚2 天前
《通过例子学Rust》第21章 测试
rust