当服务需要应对多语言场景时,我们应该如何组织代码?
本文不探讨诸如单复数变换等复杂情况,如有需要,请参见这里。
示例代码如下:
package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
// 此处`und`为undefined,用于语言无对应的翻译时的显示
// 请根据翻译文件准备好相应的key与翻译字符串
var msg = map[string]string{"zh": "你好,%s", "en": "Hello, %s", "und": "Hello, %s"}
var key = "HelloString" // key是字典用于查找翻译字符串的键;当然这里也可以使用"Hello, %s"
func register(k string, d map[string]string) {
for lang, translation := range d {
// 根据语言字符串解析语言标签
tag, err := language.Parse(lang)
if err != nil {
// 语言注册建议在程序初始化阶段完成,此时出错可能直接panic较好
panic(err)
}
// 根据语言tag、key与翻译字符串进行设置
err = message.SetString(tag, key, translation)
if err != nil {
panic(err)
}
}
}
func getTranslation(k, lang string, content []interface{}) string {
tag, err := language.Parse(lang)
if err != nil {
// 指定解析出错时希望返回的语言
tag = language.Und
}
// 根据语言获取Printer
p := message.NewPrinter(tag)
return p.Sprintf(k, content...)
}
func main() {
register(key, msg)
println(getTranslation(key, "en", []interface{}{"en"}))
println(getTranslation(key, "en-US", []interface{}{"en-US"}))
println(getTranslation(key, "zh", []interface{}{"zh"}))
// zh-CN 和 zh-SG 的 parent 都是 zh,因此会根据 zh 进行返回
println(getTranslation(key, "zh-CN", []interface{}{"zh-CN"}))
println(getTranslation(key, "zh-SG", []interface{}{"zh-SG"}))
// zh-TW 的 parent 并不是 zh,因此会根据 und tag 返回相应的翻译
println(getTranslation(key, "zh-TW", []interface{}{"zh-TW"}))
// 解析语言出错时的处理
println(getTranslation(key, "???", []interface{}{"???"}))
}
/* 输出:
Hello, en
Hello, en-US
你好,zh
你好,zh-CN
你好,zh-SG
Hello, zh-TW
Hello, ???
*/
我们通常可以在HTTP header里看见类似于Accept-Language: zh-CN
或是Accept-Language: en
之类的值,这里header里对应的值就是语言标签。类似地,zh-cmn-Hans-CN
也是语言标签。
我们需要关注的语言标签的语法:
主语言子标签
-扩展语言子标签
-文字子标签
-地区子标签
zh-cmn-Hans-CN
因此,zh-cmn-Hans-CN
被解读为:
汉语(zhongwen)-普通话(simplified mandarin)-简体(Han Simplified)-中国大陆
另外,该语言标签在2009年后就不再被推荐使用了,因为扩展语言标签cmn
蕴含该语言是zh
(汉语)。当然,目前为了兼容,建议使用zh
而非cmn
。
Go通过调用message.SetString(tag language.Tag, key string, msg string)
来存储翻译字符串;其中,tag
为语言标签,key
为该字符串的键,msg
为该字符串在该语言下的值。例如
// tag: en
message.SetString(language.English, "HelloString", "Hello, %s")
// tag: zh-Hans
message.SetString(language.SimplifiedChinese, "HelloString", "你好,%s")
// tag: und
message.SetString(language.Und, "HelloString", "Hi, %s")
Go通过调用message.NewPrinter(tag language.Tag)
来获取翻译字符串字典,通过调用printer.Sprintf(key string, content ...interface{})
(或其他类似于fmt
中的方法)来生成(或打印)翻译字符串。下文我们统一使用Sprintf
作为示例。
打印的时候,使用key并根据语言标签查找相应的字典,如果在该语言标签中找不到该key,则依次在其祖先节点中继续查找;如果找到根节点(und
)仍未找到,则效果同直接调用fmt.Sprintf
相同。因此,在简单场景下,建议直接在en
、zh
和und
下增加翻译语句;und
用于处理无法解析的语言标签(例如,???
)或意料之外的语言标签(例如,zh-TW
的祖先节点依次为zh-Hant
、und
)。
例如
message.NewPrinter(language.English, "en") // Hello, en
message.NewPrinter(language.AmericanEnglish, "en-US") //* Hello, en-US
message.NewPrinter(language.Chinese, "zh") //** Hi, zh
message.NewPrinter(language.SimplifiedChinese, "zh-Hans") // 你好,zh-Hans
/*
* 这里,因为en-US的父节点为en,因此可以找到对应翻译字符串
** 这里,因为zh的父节点是und,因此找到了und内的翻译字符串(zh-Hans是zh的子节点)
*/
建议将各语言的翻译字符串与语言标签准备在单独的文本文件里,通过读取文件、解析标签与字符串,调用message.SetString
设置。
根据语言标签调用language.Parse
获取tag,使用该tag获取message.Printer
,使用该printer根据key调用printer.Sprintf()
生成翻译字符串。
gRPC Gateway会将HTTP headers存储在context中,调用metadata.FromIncomingContext(ctx context.Context)
可获取到。
注意,获取到的是map[string][]string
的结构,map的键为HTTP headers的各名称,因此可能是全小写(accept-language
)也可能是首字母大写(Accept-Language
),因此需要使用strings.EqualFold(k, "accept-language")
来忽略大小写地比较。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有