Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduced the minify_key feature for i18n! and added support for format specifiers in t! #73

Merged
merged 39 commits into from
Jan 31, 2024

Conversation

varphone
Copy link
Contributor

@varphone varphone commented Jan 20, 2024

What's New:

  • Added a new minify_key attribute to the i18n! macro to support short hashed keys, optimizing memory usage and lookup speed.
// Enable short hashed keys using the default options.
i18n!("locales", minify_key = true);

// Enable short hashed keys using custom options.
i18n!("locales",
      minify_key = true,
      minify_key_len = 12,
      minify_key_prefix = "t_",
      minify_key_thresh = 64
);
  • Added a new metadata attribute to the i18n! macro to load configuration from the [package.metadata.i18n] section in Cargo.toml, similar to rust-i18n-cli.
i8n!(metadata = true);
  • Added support for format specifiers in the t! macro. You can now use std::fmt syntax to format variables.
t!("Hello, %{name}, you serial number is: %{sn}", name = "Jason", sn = 123 : {:08});
// => "Hello, Jason, you serial number is: 000000123"
  • Add tkv! macro to generates a translation key and corresponding value pair from a given input value.

  • Added -t option to rust-i18n-cli to manually add a translation to the localization file.

# Manually add a translation to the localization file.
$ cargo i18n -t "Hello, world!"

# Or provide a translated message
$ cargo i18n -t "Hello, world! => Hola, world!"
  • Added the log-miss-tr feature to log missing translations at the warning level. This feature requires the log crate.
[2024-01-28T08:42:47Z WARN  rust-i18n] missing: T.V1OgaWWKTAtfIo23z0g24 => "Zero padded number: %{count}" @ examples\app-minify-key\src\main.rs:54

@varphone varphone force-pushed the develop branch 2 times, most recently from a9f2014 to 0ab5a22 Compare January 20, 2024 12:09
@huacnlee
Copy link
Member

huacnlee commented Jan 22, 2024

我代码拉下来看了一下。

看起来 tr! 做的事情与 t! 几乎是一样的,本身目前已经有提取未完成的翻译功能。看起来就 tr! 会自动生成 key。
但实际上 cargo i18n 也会做提取的事情,只是因为之前这部分实现不是太好,暂时将内容提取到 TODO.yml 里面的。

这种情况下为什么还需要 tr! 函数呢?我没有看明白额外函数的区别。

我的意思是,如果想改进自动提取,我们可以试着让 t! 做到目前你改动的这些动作,另外 key 用 Base62 这种,而不是直接用原始的字符串,我有一些犹豫,感觉不需要转换,就用原始输入的内容作为 I18n key 也是可行的,这样阅读 YAML 文件的时候更容易识别(包括三方平台使用)

@varphone
Copy link
Contributor Author

我代码拉下来看了一下。

看起来 tr! 做的事情与 t! 几乎是一样的,本身目前已经有提取未完成的翻译功能。看起来就 tr! 会自动生成 key。 但实际上 cargo i18n 也会做提取的事情,只是因为之前这部分实现不是太好,暂时将内容提取到 TODO.yml 里面的。

这种情况下为什么还需要 tr! 函数呢?我没有看明白额外函数的区别。

我的意思是,如果想改进自动提取,我们可以试着让 t! 做到目前你改动的这些动作,另外 key 用 Base62 这种,而不是直接用原始的字符串,我有一些犹豫,感觉不需要转换,就用原始输入的内容作为 I18n key 也是可行的,这样阅读 YAML 文件的时候更容易识别(包括三方平台使用)

还是有区别的,tr! 的主要目标的是把要翻译的原文保存在代码中,这样即使没有翻译文件,程序也能按照默认设定的内容来显示; t! 虽然也支持输入原文 t!("Hello %{name}", name = "world") 这种方式,但是它会将整个原文作为 key 保存在 yml 文件里,在匹配时每次都需要对其进行 hash,当内容比较短时不会有什么问题,但是对一些比较长的文本,那就会增加不少额外的消耗,即使现代 CPU 性能已经足够强大,可以无视这点损耗,但是并不是所有程序到运行在这种强大的 CPU 上,还有很多比较弱的嵌入式设备,比如我们公司的就有很多程序就就运行在 ARM 800MHz 这样的性能的设备上,在这些设备上,每一个时钟周期都异常宝贵,这也是 tr! 为什么选折预先生成一个 tr_key 来做映射,因为 tr_key 长度是固定,无论原文有多长,其在查找时的耗时都固定的。

另外当前业界 GNU 系的 gettext 用的是 _("Hello %s", "world") 这种方式,而另一个比较流行的 Qt 用的是 tr("Hello %1").arg("world") 这种方式;他们都不约而同的选择将原文放在代码中,其好处就是你可以不用依赖任何代码编辑器和插件就可以在代码中看到要显示的内容,同时修改起来也很方便,原文和参数可以同时修改。

t! 的主要使用方式就是 t!("key.to.value") 这种,这会给开发人员带来一种割裂感,因为要显示的内容和代码是分离的,
不能很直观的从 key.to.value 上知道到底要显示的什么、有什么参数等等(虽然有插件可以显示对应的内容,但是并不是所有的开发人员都会采用),而且像在 github 这种平台上查看代码会审核代码,那基本上是没有插件可用的,只能到 locale 文件里面去查找才能得知。

增加 tr! 是为了给开发人员多一种选择,同时在性能上对一些低端设备更加友好一点。

@huacnlee
Copy link
Member

huacnlee commented Jan 22, 2024

Benchmark:

t                       time:   [56.047 ns 59.288 ns 65.797 ns]
t_with_locale           time:   [45.901 ns 48.876 ns 51.631 ns
t_with_args             time:   [169.95 ns 174.22 ns 180.03 ns]
t_with_args (str)       time:   [166.44 ns 168.63 ns 171.55 ns]
t_with_args (many)      time:   [440.43 ns 441.52 ns 442.81 ns]
t_with_threads          time:   [62.287 ns 63.044 ns 63.862 ns]

tr                      time:   [46.165 ns 47.372 ns 48.785 ns]
tr_with_locale          time:   [43.361 ns 46.185 ns 49.943 ns]
tr_with_args            time:   [173.32 ns 177.81 ns 183.22 ns]
tr_with_args (str)      time:   [218.36 ns 231.81 ns 247.14 ns]
tr_with_args (many)     time:   [505.64 ns 543.56 ns 591.72 ns]
tr_with_threads         time:   [57.546 ns 59.506 ns 61.530 ns]

@@ -27,6 +29,22 @@ struct I18nArgs {
/// Extract all untranslated I18n texts from source code
#[arg(default_value = "./")]
source: Option<String>,
/// Add a translation to the localize file for `tr!`
#[arg(long, default_value = None, name = "TEXT")]
tr: Option<Vec<String>>,
Copy link
Member

@huacnlee huacnlee Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--tr 这个参数怎么使用的?这个 TEXT 是传什么,可以在 README.md 后面 CLI 部分补充一下例子。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个参数目前传的是要翻译的原文,目的是用来将一些没在代码里存在文本(例如:运行时从其他地方获取的或者是动态生成的),添加到 locale 文件里面,因为 tr_key 是要通过计算才能得出的。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个如果目前用不用到的话,可以先不加吗?避免不常用的功能以后很难维护。其实我之前设计 rust-i18n 的时候,就是因为目前 Rust 社区其他的 I18n 工具都太复杂了。

#[proc_macro]
pub fn key(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
pub fn vakey(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not keep key?

Copy link
Contributor Author

@varphone varphone Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

key 的含义比较泛化,在没有 tr! 之前是没问题的,但是在加了 tr! 之后,就可能会与 tr_key 产生混淆。

Copy link
Member

@huacnlee huacnlee Jan 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那为啥用 vakey 这个比较奇怪。这里是已有的 pub 函数,我不想做不必要的改动。

以 t 为主要的功能,所以叫 key 也没问题的。

@huacnlee
Copy link
Member

huacnlee commented Jan 23, 2024

另外早上查了一下,tr 似乎与 t 含义是差不多的:

在国际化(i18n)功能中,t 和 tr 函数通常表示翻译(translate)的不同方式。通常而言

  • t 函数是"translate"的缩写,用于翻译文本。
  • tr 函数是"translate with replacements"的缩写,除了翻译文本外,它还支持替换文本中的占位符或变量。
    例如,在使用tr函数时,你可以在翻译的字符串中包含占位符,然后在运行时通过提供相应的值进行替换。这有助于处理动态内容或需要个性化的翻译场景。

我有几个犹豫的地方:

  1. t 与 tr 似乎是相同意思,然而 tr 现在做的事情是其他的,而非 “translate with replacements”,最好有参考链接,我查了一些其他的库,没看到类似的做法。
  2. 单独看为了简化 I18n key 的长度,用 Base62 这个事情,好像可以给 t 增加参数或在 i18n 初始化的时候加参数来决定统一用 Base62 Key 生成的方式,这样就不需要存在 t 和 tr 两个函数了,同时也不需要写对他们做解释差别。
    例如:
rust_i18n::i18n!("locales", short_key: true)
  1. tr_4Cct6Q289b12SkvF47dXIx 这种 Key 的命名方式是否是一个常见的做法,如果你有相关参考可以发一下。这个功能加上去以后这里就无法变更了,后面变更会严重破坏已有项目的 I18n 文件导致完全无法用,所以要考虑好。
  2. Dynamic string are also supported:Numeric literals are also supported 这部分,我感觉这个库做了太多的事情,这些完全可以让用的人自己做的 tr!(9) 写成 tr!("9")
  3. 目前 t! 其实本身也是可以用原始英文文字作为 key 直接用的。其实直接在用原始英文作为 key 实际应用起来有不少问题的,例如我们可能会优化语句、标点、用词,导致修改 text,每一次改动就会导致之前的翻译对不上,这不一定是好用。而直接写别名 key 的方式现在也有很多工具配合让提取 Key 与展示文本变得很方便(vscode-i18n-ally):
image

Base62 这样简化 key 这个动作,使得一些要求更高的场景可以能获取简短的 key 这个我赞同也希望引入进来。不过或许可以考虑两种方案:

  1. 在长度短的情况下(例如少于 255 个字符),保持原有的 text 作为 key,过长的时候,用缩短的 key 代替。
  2. i18n! 增加可选参数,设置所有 key 用短 key 代替,保持都用 t! 函数。

以上我的目的是想保持 rust-i18n 是一个简单、易用的设计,让使用的人只需要阅读 README 就能上手(这也是最初我开启这个项目的原因,我查了一大堆 Rust 社区已有的 I18n 库,他们都非常强大,然而也非常复杂,使用起来也不方便)。

@varphone
Copy link
Contributor Author

另外早上查了一下,tr 似乎与 t 含义是差不多的:

在国际化(i18n)功能中,t 和 tr 函数通常表示翻译(translate)的不同方式。通常而言

  • t 函数是"translate"的缩写,用于翻译文本。
  • tr 函数是"translate with replacements"的缩写,除了翻译文本外,它还支持替换文本中的占位符或变量。
    例如,在使用tr函数时,你可以在翻译的字符串中包含占位符,然后在运行时通过提供相应的值进行替换。这有助于处理动态内容或需要个性化的翻译场景。

我有几个犹豫的地方:

  1. t 与 tr 似乎是相同意思,然而 tr 现在做的事情是其他的,而非 “translate with replacements”,最好有参考链接,我查了一些其他的库,没看到类似的做法。
  2. 单独看为了简化 I18n key 的长度,用 Base62 这个事情,好像可以给 t 增加参数或在 i18n 初始化的时候加参数来决定统一用 Base62 Key 生成的方式,这样就不需要存在 t 和 tr 两个函数了,同时也不需要写对他们做解释差别。
    例如:
rust_i18n::i18n!("locales", short_key: true)
  1. tr_4Cct6Q289b12SkvF47dXIx 这种 Key 的命名方式是否是一个常见的做法,如果你有相关参考可以发一下。这个功能加上去以后这里就无法变更了,后面变更会严重破坏已有项目的 I18n 文件导致完全无法用,所以要考虑好。
  2. Dynamic string are also supported:Numeric literals are also supported 这部分,我感觉这个库做了太多的事情,这些完全可以让用的人自己做的 tr!(9) 写成 tr!("9")
  3. 目前 t! 其实本身也是可以用原始英文文字作为 key 直接用的。其实直接在用原始英文作为 key 实际应用起来有不少问题的,例如我们可能会优化语句、标点、用词,导致修改 text,每一次改动就会导致之前的翻译对不上,这不一定是好用。而直接写别名 key 的方式现在也有很多工具配合让提取 Key 与展示文本变得很方便(vscode-i18n-ally):
image Base62 这样简化 key 这个动作,使得一些要求更高的场景可以能获取简短的 key 这个我赞同也希望引入进来。不过或许可以考虑两种方案:
  1. 在长度短的情况下(例如少于 255 个字符),保持原有的 text 作为 key,过长的时候,用缩短的 key 代替。
  2. i18n! 增加可选参数,设置所有 key 用短 key 代替,保持都用 t! 函数。

以上我的目的是想保持 rust-i18n 是一个简单、易用的设计,让使用的人只需要阅读 README 就能上手(这也是最初我开启这个项目的原因,我查了一大堆 Rust 社区已有的 I18n 库,他们都非常强大,然而也非常复杂,使用起来也不方便)。

  1. tr_4Cct6Q289b12SkvF47dXIx 这个命名格式我随手取的,因为我也没有见其他库有到类似的设计,但是直接用4Cct6Q289b12SkvF47dXIx 作为 key 又可能因为有数字开头可能会与一些配置文件格式有冲突,所以加了个前缀。
  2. tr!(9) 这种方式有一些做游戏的朋友想要的,因为他们有很多数字编号的对象,直接作为参数传递对于开发人员而言会比较便捷,可有可无。
  3. 直接加到 t! 的里面应该是最好的,但是我为了保持向后兼容性,尽量不改变原有的用户行为,所以加了个新 tr,也算是体验版吧。
  4. 就易用性而已,目前这个库在 Rust 生态中应该是属于 Top 的,像 fluent-rs 用起来就比较麻烦,gettext-rs 虽然能承接原有 gettext 的用户习惯,但它不是纯 Rust 编写的,在编译时会遇到不少问题,其他一些库用的人比较少,年久失修,很多功能、特性缺失,整个生态中虽然看似有好多库可用,但其实能满足各种环境、各种开发团队需求的并不多。

@huacnlee
Copy link
Member

huacnlee commented Jan 24, 2024

确实简化 key 挺有必要的,例如 wasm 的场景,应该可以减少 wasm 的体积。

就目前的改动,我感觉或许可以应用到 t! 上面,我的想法大概是这样:

为 i18n 初始化新增 minify_key 参数

  • minify_key: 用简化的 Key,采用 Base62 编码 t_${Base62} 的格式 (或者可以用 md5 能有效控制 key 的长度)
rust_i18n::i18n!("locales", minify_key = true)

这样的话,就可以在 t! 所有的 key 均采用简化的 key,而不是原来的 key,这样的话,就可以减少 key 的长度。

minify_key 这个词目前我还不是太满意,也没找到合适的参考。

@varphone varphone changed the title Add new tr! to get translation with literals Introduced the minify_key feature for i18n! and added support for format specifiers in t! Jan 28, 2024
@varphone varphone force-pushed the develop branch 5 times, most recently from d380b27 to 952c7ff Compare January 28, 2024 11:10

// Configuration using the `[package.metadata.i18n]` section in `Cargo.toml`,
// Useful for the `cargo i18n` command line tool.
i18n!(metadata = true);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[package.metadata.i18n] 读取配置这个我老早就想做了。

不过我觉得这个可以不用增加参数,默认就会读配置,按优先级处理,i18n! 如果有参数,会覆盖配置。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里改一下,让 metadata 默认就是 true 不用传

) {
let I18nAttrs {
let I18nConfig {
minify_key,
minify_key_len,
minify_key_prefix,
minify_key_thresh,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你先不要暴露 minify_key_len, minify_key_prefix, minify_key_thresh 几个参数,不一定非得需要。等后面有需要的时候再说。以免后面没想好有 API 改动。

另外我倒是觉得这么多参数,不如让 miniify_key 支持 true 或一个函数,如果是函数,由使用者自行实现,这样可定制度更高。我们只需要给出一个默认的实现就好了。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这几个参数没有暴露给最终用户,但是生成本地化文件时是需要的,不然没法和代码里的配置相一致。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

我说的是这里,先不要把这几个参数暴露出来,先只有 minify_key 一项,后面如果有要定制,可以让这个支持 true 或一个函数。

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

用函数给用户定制的话有几个问题:

  1. 传进来的在编译期无法执行,造成无法在编译期生成短键。
  2. 如果用户可以自己定制短键生成算法,那就会造成标准不统一,生成的本地化文件就无法通用,影响生态的发展;如果用都同一个算法,大家只是在长度、前缀上做些调整,这样要引用他人项目里的已经翻译好的文件过来也是比较简单的。
  3. 命令行工具 rust-i18n-cli 也是需要这些参数来扫出要翻译的内容及生成短键,如果用函数来定制,那也没法放在 metadata 里面给程序读取。

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

那就留着

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不过长度有没有必要提供,我觉得可以想好以后,给一个方案,这里参数稍微一改动,原来的国际化就对不上了(并且人工对也很难对应上)

@huacnlee
Copy link
Member

你再调整一下,其他我觉得都可以了。

@huacnlee huacnlee merged commit 37bb5de into longbridgeapp:main Jan 31, 2024
2 checks passed
@huacnlee
Copy link
Member

我先合并了,我说的问题,我来稍微改改

huacnlee added a commit that referenced this pull request Jan 31, 2024
- Renamed `mikey` to `minify_key`.
- Removed unused `vakey`.
- Removed `t!((key, msg))` support, this need to think about it.
- Hidden (mark `#[doc(hidden)]) some private method, even it pub, it only provided for rust_i18n itself.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants