简介
有两个容易混淆的概念:A) 本地化 L10n,将软件翻译为本地语言的过程,通常是翻译人员的职责;B) 国际化 i18n,使得软件可以被本地化,通常是开发人员的职责。
代码中有很多需要与人交互的内容,一般需要做一些国际化的处理,通常步骤如下:
- 从源码中获取需要翻译的字符串。
- 翻译字符串,有很多的平台支持,甚至有些是免费的。
- 应用已经翻译的字符串,可以是通过数据库、配置文件方式读取,也可以编译成二进制甚至生成代码。
- 使用已经翻译字符,包括了 Web 使用
Accept-Language
以及主机上的LANGUAGE
环境变量。
实际上已经有个 https://golang.org/x/text 代码包支持,官方声明如下。
a repository of text-related packages related to internationalization (i18n) and localization (l10n) 。
其中的 message
库主要用于上述的步骤三,以类似 fmt
的接口来输出已经翻译的字符串,如下是简单示例。
package main
import (
"golang.org/x/text/message"
"golang.org/x/text/language"
)
func main() {
p := message.NewPrinter(language.BritishEnglish)
p.Printf("There are %v flowers in our garden.\n", 1500)
p = message.NewPrinter(language.Greek)
p.Printf("There are %v flowers in our garden.", 1500)
}
可以看到代码输出的数字分别为 1,500
和 1.500
,其中 NewPrinter()
函数的入参称为 Tag
,标识了语言、地域,更多的 Tag 可以参考 pkg.go.dev 中的介绍。
语言设置
以中文为例,通常通过 zh-CN
表示,其代表了 language-contry/region
,也就是汉语-大陆地区,主要是因为,同样是简体中文,大陆和新加坡会相差很大,而繁体中文对应的台湾和香港也有差异。
所以,通常除了语言外还会指定地区,这在 GoLang 中被成为 Tag,如上,使用 NewPrinter()
函数的时候需要指定一个有效 Tag 才可以,如下有多种方式创建。
// 内部定义常量
p := message.NewPrinter(language.BritishEnglish)
// 通过字符串解析
lang := language.MustParse("zh-CN") // en-GB
p := message.NewPrinter(lang)
翻译集
也称为 Catalog,定义了翻译后字符串的集合,是一组各个语言的词典,每个词典包含了键及其译文,使用时需要先生成。
package main
import (
"golang.org/x/text/message"
"golang.org/x/text/language"
)
func main() {
message.SetString(language.Chinese, "%s went to %s.", "%s去了%s。")
message.SetString(language.AmericanEnglish, "%s went to %s.", "%s is in %s.")
message.SetString(language.Chinese, "%s has been stolen.", "%s被偷走了。")
message.SetString(language.AmericanEnglish, "%s has been stolen.", "%s has been stolen.")
message.SetString(language.Chinese, "How are you?", "你好吗?.")
p := message.NewPrinter(language.Chinese)
p.Printf("%s went to %s.", "彼得", "英格兰")
fmt.Println()
p.Printf("%s has been stolen.", "宝石")
fmt.Println()
p = message.NewPrinter(language.AmericanEnglish)
p.Printf("%s went to %s.", "Peter", "England")
fmt.Println()
p.Printf("%s has been stolen.", "The Gem")
fmt.Println()
}
通过 SetString()
指定,区分大小写,也包括换行。上述是手动创建的翻译集,也可以通过 catalog.Builder
让程序生成。
单复数
一些语言中需要处理单复数情况,此时就需要用到 golang.org/x/text/feature/plural
子包,里面存在 SelectF()
函数来处理复数情况。
package main
import (
"golang.org/x/text/message"
"golang.org/x/text/language"
"golang.org/x/text/feature/plural"
)
func main() {
message.Set(language.English, "I have %d apples.",
plural.Selectf(1, "%d",
"=1", "I have an apple.",
"=2", "I have two apples.",
"other", "I have %[1]d apples.",
),
)
message.Set(language.English, "%d days left.",
plural.Selectf(1, "%d",
"one", "One day left.",
"other", "%[1]d days left.",
),
)
p := message.NewPrinter(language.English)
p.Printf("I have %d apples.", 1)
fmt.Println()
p.Printf("I have %d apples.", 2)
fmt.Println()
p.Printf("I have %d apples.", 5)
fmt.Println()
p.Printf("%d days left.", 1)
fmt.Println()
p.Printf("%d days left.", 10)
fmt.Println()
}
上述的 Selectf()
可以识别其它的量词,例如 zero
、one
、two
、few
等,或者匹配比较符,例如 >x
、<x
等。
另外,还可以通过占位符变量进一步处理消息中的量词。
package main
import (
"golang.org/x/text/message"
"golang.org/x/text/language"
"golang.org/x/text/feature/plural"
)
func main() {
message.Set(language.English, "You are %d minutes late.",
catalog.Var("m", plural.Selectf(1, "%d",
"one", "minute",
"other", "minutes")
),
catalog.String("You are %[1]d ${m} late."),
)
p := message.NewPrinter(language.English)
p.Printf("You are %d minutes late.", 1)
fmt.Println()
p.Printf("You are %d minutes late.", 10)
fmt.Println()
}
其中 catalog.Var()
的第一个参数为字符串,会根据具体的值进行翻译。
最佳实践
多数本地化方案都是将语言的译文分别存于文件里,这些文件被动态加载,可以使用 gotext
命令行,通过如下命令更新、安装。
$ go get -u golang.org/x/text/cmd/gotext
$ go install golang.org/x/text/cmd/gotext
使用时基本分成了两步:
- 从代码中提取中需要翻译的键,并写入到文件中。
- 更新代码,使得程序可以加载对应的键到翻译集中使用。
如下是一个简单的示例。
package main
//go:generate gotext -srclang=en update -out=catalog/catalog.go -lang=en,zh
import (
"golang.org/x/text/message"
"golang.org/x/text/language"
_ "example.com/foobar/catalog"
)
func main() {
p := message.NewPrinter(language.Chinese)
p.Printf("Hello World!")
p.Println()
}
执行 mkdir catalog && go generate
命令会在当前目录下生成 locales
目录,包含了英文和中文的键,一般文件名为 locales/cn/out.gotext.json
,将其复制为 messages.gotext.json
然后修改为如下。
{
"language": "zh",
"messages": [
{
"id": "Hello World!",
"message": "Hello World!",
"translation": "你好!"
}
]
}
也就是添加了翻译字段,接着重新执行 go generate
命令,然后运行即可,注意,需要引入生成的 catalog/catalog.go
文件,只需要调用 init()
函数即可。
其它
gotext
如上已经安装了 gotext 命令,可以直接查看帮助文档,最常使用的是 update
命令,其它的还有 extract
、generate
等,如下是常见的参数:
-srclang
应用使用 BCP 47 标签作为基础语言。-out
产生的消息目录所在路径,使用的是文件相对路径。-lang
通过逗号分割指定多个语言的标签列表。
参数的最后通过完全限定模块路径指定想要翻译的包,例如 example.com/foo/bar
,如果多个可以通过空格分割。
注意,该命令只查找代码中的 messge.Printer
相关的几个函数,例如 Printf()
Fsprintf()
Sprintf()
三个基础函数,其它的如 Sprint()
或 Print()
函数会忽略。