Go 语言 应用 国际化与本地化(i18n)¶
在软件开发过程中,如果您的软件用户面向全球,那么对于国际化的支持则必不可少。
什么是软件国际化与本地化?¶
在信息技术领域,国际化与本地化(英文:internationalization and localization) 是指修改软件使之能适应目标市场的语言、地区差异以及其他需求。
国际化是指在设计软件时,将软件与特定语言及地区脱钩的过程。 当软件被移植到不同的语言及地区时,软件本身不用做内部工程上的改变或修正。 本地化则是指当移植软件时,加上与特定区域设置有关的信息和翻译文件的过程。
国际化和本地化之间的区别虽然微妙,但却很重要。 用一项产品来说,国际化通常只需做一次,但本地化则要针对不同的区域各做一次(最常见的是需要翻译多种语言)。 这两者之间是互补的,并且两者合起来才能让一个系统适用于各地。
国际化工具¶
gettext 0 是 GNU国际化与本地化(i18n)函数库。
它被广泛应用于编写多语言程序,提供了几乎对所有编程语言的支持
备注
gettext 支持的语言包括但不限于:
C/C++
Pascal
Python
Lua
Ruby
Bash
Java
PHP
Perl
Go 语言国际化¶
在 Go 语言中开发面向命令行的国际化(i18n)应用,建议使用: https://pkg.go.dev/golang.org/x/text 国际化库。
备注
gettext 在 Go 语言的国际化应用中主要有一个缺点:
gettext 需要使用(翻译结果)编译之后的
mo
二进制文件, Go 语言的软件一般都是单一文件,虽然有办法把翻译结果嵌入进可执行文件,但是多了一个步骤。
golang.org/x/text
库会把翻译之后的文本编译成 Go 语言代码,使用正常的 Go 语言编译流程即可。 直接编译还具有更高的运行时效率。
golang.org/x/text¶
本教程会使用 golang.org/x/text
库创建一个简单命令行工具(支持: 中文、英文)以帮助您理解。
备注
此命令行工具源码: go_i18n_cli
备注
为了方便后面的翻译字符串的提取和处理, 我们可以从 golang.org/x/text/cmd/gotext
下载 gotext
go install golang.org/x/text/cmd/gotext
如果您使用 go_i18n_cli
还可以使用如下命令自动下载
make i18n-install-gotext
gotext 使用¶
gotext
使用 BCP47 语言标签。
通常您可以使用如下函数自动获取当前系统所使用的语言:
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
"os"
)
// GetLanguage 获取运行此任务使用的语言
func GetLanguage() language.Tag {
for _, key := range []string{"LANGUAGE", "LC_ALL", "LANG"} {
if tag, err := language.Parse(os.Getenv(key)); err == nil {
return tag
}
}
return language.English
}
// GetPrinter 获取 i18n 翻译 Printer
func GetPrinter() *message.Printer {
return message.NewPrinter(GetLanguage())
}
任何需要翻译的字符串都需要使用 message.Printer.X**f
函数来获取翻译结果。
func I18nHello(name string) {
i18n := GetPrinter()
println(i18n.Sprintf("Hello: %s", name))
}
其中的: Hello: %s
会被 gotext
工具自动提取。
警告
必须使用 Printer
中以 f
结尾的函数,
使用其他函数字符串不会被 gotext
自动提取。
提取需要翻译的字符串 & 更新翻译编译代码¶
使用 gotext
自动提取需要翻译的字符,并且自动编译翻译内容到 Go 语言文件。
gotext -srclang=en update -out=translations.go -lang=en,zh go_i18n_cli
备注
-srclang
指定源语言
update
是更新命令 (我们直接抽取翻译并且自动更新翻译内容到 Go 语言文件)
-out
参数是翻译文本编译后的保存到的 Go 语言文件
-lang
翻译目标语言(也就是应用程序需要支持的语言列表)
go_i18n_cli
Go 包名(也就是我们需要提取此包的所有需要翻译的文本)
在您第一次使用 gotext
提取的时候,可能会遇到如下信息:
zh: Missing entry for "i18n".
zh: Missing entry for "Hello: {Name}".
这是因为 gotext
提取出来的文本并没有被翻译成中文,这是正常的。
执行上面的命令之后此时 cli
目录结构应该为:
$ tree cli
cli
├── demo.go
├── locales
│ ├── en
│ │ └── out.gotext.json
│ └── zh
│ └── out.gotext.json
└── translations.go
其中: locales
目录保存了我们自动提取出来需要翻译的字符串。
translations.go
文件则保存了翻译文本编译之后的 Go 语言代码。
人工翻译¶
在提取出 locales
目录之后,我们需要人工对其中的内容进行翻译,
翻译之后的文件对应的名称为: messages.gotext.json
例如:
> $ tree cli/locales
cli/locales
├── en
│ ├── messages.gotext.json
│ └── out.gotext.json
└── zh
├── messages.gotext.json
└── out.gotext.json
2 directories, 4 files
编译翻译文本为 Go 语言代码¶
重新更新编译文件 make i18n-update
或者 cd cli && gotext -srclang=en update -out=translations.go -lang=en,zh go_i18n_cli
此时您可以看到 cli/translations.go
已经保存翻译文本编译的 Go 语言代码:
> $ cat cli/translations.go
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
package cli
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
type dictionary struct {
index []uint32
data string
}
func (d *dictionary) Lookup(key string) (data string, ok bool) {
p, ok := messageKeyToIndex[key]
if !ok {
return "", false
}
start, end := d.index[p], d.index[p+1]
if start == end {
return "", false
}
return d.data[start:end], true
}
func init() {
dict := map[string]catalog.Dictionary{
"en": &dictionary{index: enIndex, data: enData},
"zh": &dictionary{index: zhIndex, data: zhData},
}
fallback := language.MustParse("en")
cat, err := catalog.NewFromMap(dict, catalog.Fallback(fallback))
if err != nil {
panic(err)
}
message.DefaultCatalog = cat
}
var messageKeyToIndex = map[string]int{
"Hello: %s": 1,
"i18n": 0,
}
var enIndex = []uint32{ // 3 elements
0x00000000, 0x00000005, 0x00000012,
} // Size: 36 bytes
const enData string = "\x02i18n\x02Hello: %[1]s"
var zhIndex = []uint32{ // 3 elements
0x00000000, 0x0000000a, 0x00000018,
} // Size: 36 bytes
const zhData string = "\x02国际化\x02您好: %[1]s"
// Total table size 114 bytes (0KiB); checksum: 262FC1A6
编译 & 运行测试¶
编译(make build
)之后, 运行测试:
> $ LANGUAGE=zh ./main
您好: 国际化
> $ LANGUAGE=en ./main
Hello: i18n
总结¶
使用 gotext
完成命令行应用的国际化是一个很好的选择(对于 Web 应用可能并不适合)。
主要有以下步骤:
引入
gotext
库所有对用户展示的字符使用
gotext.message.Printer.X***f
函数获取使用
gotext
命令行工具提取出需要翻译的字符人工翻译需要翻译的字符
使用
gotext
命令行工具编译翻译之后的字符串为 Go 语言文件重新编译 Go 语言应用