Go语言的国际化支持(基于gettext-go)
博客专区 > chai2010 的博客 > 博客详情
Go语言的国际化支持(基于gettext-go)
chai2010 发表于4年前
Go语言的国际化支持(基于gettext-go)
  • 发表于 4年前
  • 阅读 3836
  • 收藏 79
  • 点赞 11
  • 评论 17

标题:腾讯云 新注册用户域名抢购1元起>>>   

本文在 Golang中国博客 的地址: http://blog.go-china.org/07-gettext

hello, world!

假设有以下的程序, 输出: "Hello, world!".

package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello, world!")
}

现在要让改程序支持不同语言的用户, 然后以本地语言输出相同意思的信息. 这就是很多程序面临的国际化问题.

Go语言的国际化思路

程序的国际化一般涉及到编码和翻译两个概念. 其中编码一般采用UTF8编码标准, Go语言已经完美支持. 而目前常见翻译技术是Qt的tr函数和GNU gettext提供的gettext函数, 另外微软的MFC也有自己的多国语言支持方式.

Go语言目前还没有标准的多国语言翻译方式. 不过笔者已经初步将gettext的运行时环境移植到了Go语言(采用纯Go实现, 无其他依赖).

Go语言版的gettext名字为gettext-go, 项目地址在: http://code.google.com/p/gettext-go.

gettext-go 同时也借鉴了 Qt 的翻译上下文特性. 在 GNU gettext 的 pomo 翻译文件中都是含有 msgctxt 上下文信息的, 但是 C/C++ 的翻译接口函数并没有上下文的参数, 因此 传统的 gettext 函数没有设置上下文的参数.

可以去 godoc.orggowalker.org 查看 gettext-go 的文档.

Go语言的多国语言支持

基于 gettext-go , 我们可以很容易给Go程序增加多国语言的支持:

package main

import (
	"fmt"

	"code.google.com/p/gettext-go/gettext"
)

func main() {
	gettext.BindTextdomain("hello", "local", nil)
	gettext.Textdomain("hello")

	fmt.Println(gettext.Gettext("Hello, world!"))
}

其中 gettext.BindTextdomain 是绑定翻译的空间, 其中 "hello" 是对应翻译一类信息的翻译, "local" 为翻译文件的所在路径(这里当前目录下的"local")子目录.

按照 GNU gettext 的习惯, 简体中文对应的翻译文件为 "local/zh_CN/LC_MESSAGES/hello.mo". 不同语言的命名有一个国际规范, 比如繁体中文对应"zh_TW", 美国英文对应"en_US"等等. 但是 gettext-go 对名字并没有特殊的要求.

gettext.BindTextdomain 可以绑定多个翻译空间, 但是同一个时刻只能使用一个翻译空间.

这里我们使用 gettext.Textdomain 指定当前的翻译空间为 "hello" .

运行新的程序程序, 发现输出还是: "Hello, world!".

这是因为缺少翻译文件...

生成翻译文件

未来, gettext-go 会开发一个 GNU gettext 工具集 中 的 xgettext 类似工具, 用于从程序中提取要翻译的字符串.

不过目前, 我们只能手工支持翻译文件了(还好这个例子只有一个字符串需要翻译).

创建 "local/zh_CN/LC_MESSAGES/hello.po" 文件, 内容如下:

msgid ""
msgstr ""

msgctxt "main.main"
msgid "Hello, world!"
msgstr "你好, 世界!"

保存为UTF8编码格式.

然后用 GNU gettext 工具集中的 msgfmt 命令将 hello.po 文件编译为 hello.mo 文件(注:如果缺少mo文件的话,也尝试用同名的po文件代替.):

msgfmt -o hello.mo hello.po

如果是Windows用户, 可以下载 poedit 翻译工具. 然后用 poedit 打开 hello.po 文件, 点击保存后会自动生成 hello.mo 文件(也是poedit的bin目录下自带的msgfmt 命令生成的).

重新运行新的程序程序, 还是输出: "Hello, world!" ?

本地的语言环境

在上一节, 我们已经制作了简体中文的翻译文件 "local/zh_CN/LC_MESSAGES/hello.mo", 然后输出依然是英文.

这是因为 gettext-go 翻译时不仅要依赖对应语言的翻译文件, 还需要知道要范围为哪种语言(和网上翻译类似, 需要知道翻译的目标语言).

如果没有指定翻译语言, gettext-go 会尝试获取本地的默认语言环境, 主要是通过检查 $(LC_MESSAGES)$(LANG) 两个环境变量. 如果两个环境变量都没有设置, 那么默认是不进行翻译的.

我们设置环境变量后重新运行程序(Windows):

set LANG=zh_CN
go run hello.go

这里时候应该可以输出中文了.

动态切换语言

如果不想使用默认的本地语言环境, 也可以用 gettext.SetLocale 接口设置本地语言环境.

func main() {
	gettext.BindTextdomain("hello", "local", nil)
	gettext.Textdomain("hello")

	// 切换到简体中文
	gettext.SetLocale("zh_CN")
	fmt.Println(gettext.Gettext("Hello, world!"))
	// 切换到繁体中文
	gettext.SetLocale("zh_TW")
	fmt.Println(gettext.Gettext("Hello, world!"))
}

这样可以根据需要采用合适的语言翻译文件.

翻译的上下文

Go语言版的 gettext-go 的每个 gettext.Gettext 调用都有一个隐含的上下文信息(如果想自己指定上下文可以使用gettext.PGettext).

默认的上下文为包含gettext.Gettext调用的函数名称, 比如:

  • 如果是main包的全局函数初始化调用, 则为 main.init
  • 如果是main包的init函数调用, 则为 main.init
  • 如果是main包的main函数调用, 则为 main.main
  • 如果是main包中的闭包调用, 则为 main.func
  • 如果是非main包的函数, 则还需要包含包的完全路径名

上下文对应Go的运行时调用者名称, 具体实现在这里: caller.go .

练习题

  1. 给前面的程序增加 繁体/日文/韩文/克林贡语 等语言的支持
  2. 增加一个 -local 参数, 用于设置本地语言
  3. 提交改进建议或其他反馈意见
共有 人打赏支持
chai2010
粉丝 407
博文 94
码字总数 79948
作品 8
评论 (17)
狗头666
程序的国际化可能还要考虑资源文件的翻译,根据不同语言环境加载不同的图片、语音等等
chai2010

引用来自“羊半仙”的评论

程序的国际化可能还要考虑资源文件的翻译,根据不同语言环境加载不同的图片、语音等等

要深度支持的话, 最好是在程序设计的时候定制了. 作为一个通用的库, 很难支持全部的需求.
立Q
有道理
五杀联盟
碉堡了,博主威武!
狗头666

引用来自“chai2010”的评论

引用来自“羊半仙”的评论

程序的国际化可能还要考虑资源文件的翻译,根据不同语言环境加载不同的图片、语音等等

要深度支持的话, 最好是在程序设计的时候定制了. 作为一个通用的库, 很难支持全部的需求.

嗯 毕竟大多数需求是文字资源
revol
gottext
郑柯
mark
苗哥
为什么这里的资源文件不能使用Java中的properties文件那种key-value格式呢,这样移植或者维护起来不是更加简单,用mo格式的资源文件,每次还要多壹個步骤来生成新的资源文件。
chai2010

引用来自“苗哥”的评论

为什么这里的资源文件不能使用Java中的properties文件那种key-value格式呢,这样移植或者维护起来不是更加简单,用mo格式的资源文件,每次还要多壹個步骤来生成新的资源文件。

采用的文件格式没有统一的标准. 用mo只是因为格式比较简单, 而且poedit也足够用(比Qt的翻译工具轻量很多).
如果只是key-value, 信息量还不如mo文件, 因为mo文件支持上下文和复数.
如果只是为了维护简单, 用po文件就可以了(以后版本会在mo缺少时用对应的po代替).
chai2010

引用来自“羊半仙”的评论

程序的国际化可能还要考虑资源文件的翻译,根据不同语言环境加载不同的图片、语音等等

计划增加一个 Getdata 函数, 用于支持资源文件的翻译.
以后domain还计划增加对zip文件和内置的zip数据的支持.
主要涉及 BindTextdomain 和 Getdata 两个函数:
http://gowalker.org/code.google.com/p/gettext-go/gettext#BindTextdomain
http://gowalker.org/code.google.com/p/gettext-go/gettext#Getdata

狗头666

引用来自“chai2010”的评论

引用来自“羊半仙”的评论

程序的国际化可能还要考虑资源文件的翻译,根据不同语言环境加载不同的图片、语音等等

计划增加一个 Getdata 函数, 用于支持资源文件的翻译.
以后domain还计划增加对zip文件和内置的zip数据的支持.
主要涉及 BindTextdomain 和 Getdata 两个函数:
http://gowalker.org/code.google.com/p/gettext-go/gettext#BindTextdomain
http://gowalker.org/code.google.com/p/gettext-go/gettext#Getdata

Good news,这个项目真的很超前.....
chai2010
已经支持资源文件的翻译和zip数据(可内嵌到程序中), 具体可参考这个文件:
http://code.google.com/p/gettext-go/source/browse/gettext/gettext_test.go

@羊半仙
Orzogc
上下文信息不能支持指定文件行数这种方式吗?这样的话好像可以直接用xgettext生成pot吧
chai2010

引用来自“Orzogc”的评论

上下文信息不能支持指定文件行数这种方式吗?这样的话好像可以直接用xgettext生成pot吧

不支持. 文件行数容易变化, 而且不同的源文件的行数很容易重名.
我觉得采用pkg路径和函数名组合作为上下文是最靠谱的方式.
(在Qt中, 是采用的类的名字作为上下文)
Orzogc

引用来自“chai2010”的评论

引用来自“Orzogc”的评论

上下文信息不能支持指定文件行数这种方式吗?这样的话好像可以直接用xgettext生成pot吧

不支持. 文件行数容易变化, 而且不同的源文件的行数很容易重名.
我觉得采用pkg路径和函数名组合作为上下文是最靠谱的方式.
(在Qt中, 是采用的类的名字作为上下文)

我的意思是指定文件及其行数,然后gettext会搜索指定位置附近范围的字符串。
或者上下文干脆只用指定文件,然后gettext搜索整个文件。
这样的话就可以用xgettext生成pot。
chai2010

引用来自“Orzogc”的评论

引用来自“chai2010”的评论

引用来自“Orzogc”的评论

上下文信息不能支持指定文件行数这种方式吗?这样的话好像可以直接用xgettext生成pot吧

不支持. 文件行数容易变化, 而且不同的源文件的行数很容易重名.
我觉得采用pkg路径和函数名组合作为上下文是最靠谱的方式.
(在Qt中, 是采用的类的名字作为上下文)

我的意思是指定文件及其行数,然后gettext会搜索指定位置附近范围的字符串。
或者上下文干脆只用指定文件,然后gettext搜索整个文件。
这样的话就可以用xgettext生成pot。

对于po文件, 注释部分是支持文件名和行号的:
http://gowalker.org/code.google.com/p/gettext-go/gettext/po#Comment

但是我觉得用作Go的上下文就不太适合了.
因为Go的pkg路径组合函数名就基本保证了唯一性了(init和闭包除外).
而且按照Go的习惯, pkg内文件名的调整和行号的变化都是不应该影响运行结果的
(因为语法树结构并没有发送变化).

当然, 目前缺少xgettext工具的支持是一个问题.
我计划以后做一个针对Go语言的xgettext类似的工具.
因为Go语言除了运行时翻译需求外, godoc的文档提取也是一个需求(估计xgettext不会支持这个特性的).
我希望提前工具能够支持Go语言的文档提取,
这样也便于以后中文文档的翻译.

感谢你的反馈, 新年快乐 :)
Orzogc

引用来自“chai2010”的评论

引用来自“Orzogc”的评论

引用来自“chai2010”的评论

引用来自“Orzogc”的评论

上下文信息不能支持指定文件行数这种方式吗?这样的话好像可以直接用xgettext生成pot吧

不支持. 文件行数容易变化, 而且不同的源文件的行数很容易重名.
我觉得采用pkg路径和函数名组合作为上下文是最靠谱的方式.
(在Qt中, 是采用的类的名字作为上下文)

我的意思是指定文件及其行数,然后gettext会搜索指定位置附近范围的字符串。
或者上下文干脆只用指定文件,然后gettext搜索整个文件。
这样的话就可以用xgettext生成pot。

对于po文件, 注释部分是支持文件名和行号的:
http://gowalker.org/code.google.com/p/gettext-go/gettext/po#Comment

但是我觉得用作Go的上下文就不太适合了.
因为Go的pkg路径组合函数名就基本保证了唯一性了(init和闭包除外).
而且按照Go的习惯, pkg内文件名的调整和行号的变化都是不应该影响运行结果的
(因为语法树结构并没有发送变化).

当然, 目前缺少xgettext工具的支持是一个问题.
我计划以后做一个针对Go语言的xgettext类似的工具.
因为Go语言除了运行时翻译需求外, godoc的文档提取也是一个需求(估计xgettext不会支持这个特性的).
我希望提前工具能够支持Go语言的文档提取,
这样也便于以后中文文档的翻译.

感谢你的反馈, 新年快乐 :)

新年快乐0
×
chai2010
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: