传统的解决方案

i18n是软件开发中常见的一个需求, 很多流行的开发框架如 ruby on rails, springboot 对它有内置的支持。 rails 的 i18n 是其中的佼佼者, 我们看看它解决了哪些问题,是如何解决的。

i18n 资源文件

通常开发者需要按照框架的约定创建资源文件,一般会在目录或文件名中包含如 es, enlocale 信息,方便框架按需加载。

|---books
|-----es.yml
|-----en.yml
|---users
|-----es.yml
|-----en.yml

key 的定义和查找优先级

通常每个翻译文本会对应一个有意义的key,通过 key + locale 的组合得到当前 locale 下的翻译文本.

I18n.t 'activerecord.models.user'  # => 'Customer'
I18n.t 'activerecord.models.user'  # => '客户'
en:
  activerecord:
    models:
      user: "Customer"
    attributes:
      user:
        login: "Login"
zh:
  activerecord:
    models:
      user: "客户"
    attributes:
      user:
        login: "登陆"

当项目到一定规模, key 的数量很多时,key 的命名就显得格外重要,否则重用 key 时很难查找。 而且除了用户自定义的 key, 框架一些内置的 key 如日期, 错误消息等, 也需要用户提供翻译。 所以 key 的命名 rails 也做了约定,如下

activerecord.errors.models.[model_name].attributes.[attribute_name]
activerecord.errors.models.[model_name]
activerecord.errors.messages
errors.attributes.[attribute_name]
errors.messages

总结下来,需要关注的点主要有:

  1. 翻译文本的获取; 定义 key 并与文本建立映射关系
  2. 归类创建资源文件
  3. 对资源文件进行翻译
  4. 显示翻译文本

其中,需要开发人员处理的,主要是 1, 2, 4, 第 3 步可能需要专门的翻译或者借助第三方平台。

golang 的 i18n 方式

golang 处理 i18n 的包主要是 go get -u golang.org/x/text, 它对第 1, 2 步有较大的改进。
该包中提供了一个工具 gotext, 可以分析源码, 将待翻译文本抽取出来(如fmt.Println),直接将它作为 key, 并归类创建好资源文件。

package main

//go:generate gotext -srclang=en update -out=catalog.go -lang=en,zh

import (
	"fmt"
	"golang.org/x/text/language"
	"golang.org/x/text/message"
	_ "golang.org/x/text/message/catalog"
)

func main() {
	p(language.English)

	p(language.Chinese)
}

func p(t language.Tag) {
	p := message.NewPrinter(t)
	p.Printf("Hello, %s", "world")
	fmt.Println()
	p.Printf("I want %d apple", 1)
	fmt.Println()
}

以上面的代码为例, 运行 go generate, 会自动为你创建如下的资源文件,

locales
|--en
|-----out.gotext.json
|--zh
|-----out.gotext.json
|-----message.gotext.json

你将 out.gotext.json 重命名为 message.gotext.json, 做好翻译, 再运行 go generate 更新翻译后的资源到 catalog.go, go build 后运行,即可看到翻译:

Hello, world
I want 1 apple
你好, world
我想要 1 个苹果

你可以在 https://github.com/feitian124/i9n.gitgotext 分支找到一个可以工作的例子.

目前该包 gotext 还在开发状态, 文档也比较缺乏, 但上述命令基本可以用, 可能会随着 go 1.17 或 1.18 正式发布。