16 个版本 (9 个稳定版)
新版本 3.1.2 | 2024 年 8 月 20 日 |
---|---|
3.0.1 | 2024 年 1 月 23 日 |
2.4.0 |
|
2.3.0 | 2023 年 11 月 14 日 |
0.3.0 | 2021 年 12 月 9 日 |
#90 在 国际化 (i18n) 中
每月 17,885 次下载
在 22 个库中(5 个直接使用) 使用
49KB
864 行
Rust I18n
🎯 让国际化变得简单!
Rust I18n 是一个库,用于从一组 (YAML、JSON 或 TOML) 映射文件中加载本地化文本。映射在编译时转换为 Rust 程序可读的数据,然后可以通过简单地调用提供的 [t!
] 宏来加载本地化文本。
与其他 I18n 库不同,Rust I18n 的目标是提供一个简单易用的 API。
此库的 API 启发于 ruby-i18n 和 Rails 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 中添加库依赖项并设置 I18n 配置
[dependencies]
rust-i18n = "3"
在 lib.rs
或 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!();
或者您可以直接使用 import
// 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.json
或 en.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 中获取本地化字符串
将此包中的 [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
依赖关系
~9–20MB
~288K SLoC