使用基本软件包在Go中进行本地化

创建一个好的应用程序并不容易。 无论您编写什么独特而有用的应用程序,如果用户不喜欢它,那么就像他们所说的那样,您就会遇到很大的问题。 大多数人不喜欢并吓跑他们不了解的一切。 通常,用户界面和字母是应用程序的冰山一角,用户可以据此对其进行评估。 因此,用户看到的所有内容的本地化非常重要。


记得十年前,互联网刚刚开始进入大众生活,如今许多IT巨头正处于起步阶段,只有几十名员工,这是按顺序向用户发送英文字母的。 用户对此表示同情。 如今,当Internet上的所有内容都存在并且您的额头不必跨越七个范围,接受高等教育或不懂英语时,不支持本地化应用程序被认为是不好的形式。 顺便说一下,在我们公司中,所有UI文本的本地化已经以20种语言进行,并且支持的语言列表也在不断增长。


在Go语言中,就像一种相当年轻的语言一样,所有现代Web开发趋势都是在基本软件包的级别上实现的,不需要额外的“手鼓跳舞”。 (我几年前开始学习Go,但是我仍然记得学习这种语言后的第一天就感受到了“发现的超级大国”的感觉。现在看来,我只要写几行就可以实现任何任务。)


当然,他们也没有绕过Go中的本地化。 使用以下基本软件包实际上可以“开箱即用”地进行本地化: golang.org/x/text/language,golang.org/x/text/messagegolang.org/x/text/feature/plural 。 让我们看一下使用这些包在半小时内在Go中有多么容易,您可以实现诸如将字母本地化这样的简单任务。


展望未来,我要说的是,本文的主要目的是展示Go的强大功能和美丽之处,并重点介绍用于本地化的消息包的基本功能。 如果您正在寻找生产应用程序的解决方案,则可能需要一个更好的现成库go-i18n的优点是github上有很多明星(其中有我的明星)和很大的灵活性。 但是,有反对使用它的说法:您可能不需要所有的灵活性和功能; 当一切都已经以语言本身实现时,为什么要使用外部库; 如果您已经拥有自己的具有自己格式的翻译系统,则该库“原样”很可能无法使用,并且无论如何您都必须对其进行修改; 最后,使用第三方库并不像如何自己做一样有趣和有益。


我们为正在执行的任务制定基本要求。 有:a)yaml格式的标签: “ label_name:翻译文本” ; 在文件名中指定翻译语言,例如ru.yml; b)html中的电子邮件模板。 根据输入参数:语言环境和数据数组,有必要生成消息的本地化文本。


让我们开始吧...但是,首先,关于消息包的更多信息(golang.org/x/text/message)。 它旨在格式化本地化字符串的输出。 Message实现了标准fmt包的接口,可以替换它。 用法示例:

message.SetString(language.Russian, "toxic", "") message.SetString(language.Japanese, "toxic", "毒性") message.NewPrinter(language.Russian).Println(“toxic”) message.NewPrinter(language.Japanese).Println(“toxic”) //: // //毒性 

为了使包装“看到”标签,必须首先声明它。 在示例中,SetString函数用于此目的。 接下来,将为所选语言创建打印机,并直接显示本地化的字符串。

为了解决我们的问题,我们可以生成带有所有标签的go-file,但这不是很方便,因为添加新标签时,您每次都必须重新生成该文件并重新构建应用程序。 告诉我们有关标签的消息的另一种方法是使用词典。 字典是一种实现标签搜索接口Lookup(键字符串)(数据字符串,确定的布尔值)的结构

字典选项适合我们。 首先,我们定义字典的结构并为其实现Lookup接口:

 type dictionary struct { Data map[string]string } func (d *dictionary) Lookup(key string) (data string, ok bool) { if value, ok := d.Data[key]; ok { return "\x02" + value, true } return "", false } 

将yaml文件中的所有标签分散到字典的集合中,这是一个地图[lang] *字典地图 ,其中langBCP47格式的语言标签。

 func parseYAMLDict() (map[string]catalog.Dictionary, error) { dir := "./translations" files, err := ioutil.ReadDir(dir) if err != nil { return nil, err } translations := map[string]catalog.Dictionary{} for _, f := range files { yamlFile, err := ioutil.ReadFile(dir + "/" + f.Name()) if err != nil { return nil, err } data := map[string]string{} err = yaml.Unmarshal(yamlFile, &data) if err != nil { return nil, err } lang := strings.Split(f.Name(), ".")[0] translations[lang] = &dictionary{Data: data} } return translations, nil } 

我们将字典集合安装在init函数中,以便应用程序启动时消息包可以使用字典。

 func init() { dict, err := parseYAMLDict() if err != nil { panic(err) } cat, err := catalog.NewFromMap(dict) if err != nil { panic(err) } message.DefaultCatalog = cat } 

因此,目前,我们已经在程序中任何位置实现了文件中标签本地化的可用性:

 message.NewPrinter(language.Russian).Println(“label_name”) 

现在该继续执行任务的第二部分,并在电子邮件模板中替换我们的本地化标签。 例如,考虑一个简单的消息-注册用户时的欢迎信:
你好,比尔·史密斯!


为了进行解析,我们使用了另一个标准包html / template 。 在模板中解析模板时,可以通过.Funcs()设置功能:

 template.New(tplName).Funcs(fmap).ParseFiles(tplName) 

向模板添加一个函数,该函数将翻译标签并替换其中的变量,并将其称为翻译 。 模板解析代码:

 //  lang:=language.Russian //  tplName:=”./templates/hello.html” //   data := &struct { Name string LastName string }{Name: "Bill", LastName: "Smith"} fmap := template.FuncMap{ //   "translate": message.NewPrinter(lang).Sprintf, } t, err := template.New(tplName).Funcs(fmap).ParseFiles(tplName) if err != nil { panic(err) } buf := bytes.NewBuffer([]byte{}) if err := t.Execute(buf, data); err != nil { panic(err) } fmt.Println(buf.String()) 

生成的信函模板./templates/hello.html:

 <!DOCTYPE html> <head> <title>{{translate "hello_subject"}}</title> </head> <body> {{translate "hello_msg" .Name .LastName}} </body> </html> 

由于在翻译中我们使用Sprintf函数进行本地化,因此将使用该函数的语法来缝制标签文本中的变量。 例如, %s是字符串, %d是整数。
标记文件
英文

 hello_subject: Greeting mail hello_msg: Hello, %s %s! 

ru.yml

 hello_subject:   hello_msg: , %s %s! 

就此而言,原则上就是这样,字母的本地化已准备就绪! 仅编写了几十行代码,我们就获得了强大的功能,可以将数十种语言的任何复杂字母本地化。


如果您喜欢此示​​例,则可以继续自己实现复数形式,使用标签中变量的变量名称代替%s,并在标签中使用函数。 我故意没有这样做是为了给您的想象空间。


示例中的代码是专门为演示消息包的功能而编写的,并不声称是理想的;完整的代码清单可在github上找到

Source: https://habr.com/ru/post/zh-CN431604/


All Articles