【Wubade】go-i18n/v2的操作示例(2021年03月版)【国际化支持】

国际化简称 i18n

我想要一个能够运行的 go-i18n v2.x 示例。而且需要在 Go v1.15 或以上版本上经过测试。

在 GitHub 上,可以找到 github.com/nicksnyder/go-i18n

由于我对 TOML 没有太大兴趣,所以更喜欢使用 JSON。

2021年3月7日,目前的 go-i18n 版本是 v2.1.1,但即使看官方的示例,我认为它似乎更适合“熟悉旧版本(v1.x)的人”,因为前提条件有些混乱不清。

为了展示 “Golang go-i18n v2 动作示例”,即使在搜索 “Golang go-i18n v2 动作示例” 时出现了很多旧版本的 go-i18n v1.x 或无法在 Go v1.15 以上版本中运行或由于拼写错误而无法工作的示例,这是作为自己搜索的一个参考样本。

? 本文中介绍的 go-i18n 是一个第三方包。Go(以下简称 golang)原生模块中有一个专门处理文本的模块叫做 golang.org/x/text。其中的 golang.org/x/text/message 包能提供本地化功能,例如将数字 1000 根据不同地区的规范转换为日本就是 1,000,法国就是 1 000。
实际上,通过阅读 golang.org/x/text 的文档中的 Translation 部分,我们可以使用这个本地化功能和 catalog.Builder 类来创建自己的 catalog(对应字典),并且通过 plural.Selectf() 函数还能应对复数形式。但不过,这个文档也不是很清楚如何使用ʕ◔ϖ◔ʔ
总之,本文主要关注 go-i18n,一旦我们掌握了利用 golang.org/x/text 进行国际化的方法,则会在另一篇文章中进行介绍。如果收到 10 个以上的”赞”,我们会督促自己加快进度。

太长不看(今北产百业)

Bundle が辞書を保持するもの。

Localizer が辞書を引くもの。

入力(メッセージ ID や設定)から、対応するメッセージを辞書から引っ張ってくる関数が Localize() です。MustLocalize() のように Must* が付く関数は、エラー発生時にエラーを返さずパニクるタイプです。

go-i18n のバージョンによって Bundle やLocalizer に渡す言語指定方法が異なる。

この記事は go-i18n v2.1 のサンプル。
とりま、動くサンプルをオンラインでみる @ Go Playground

? 关于语言标签的指定格式
在指定语言时,使用的 en-US 和 ja-JP 被称为标签(语言标签),通常指的是 “IETF 语言标签”。
IETF 语言标签是由互联网标准化组织 IETF 在 RFC 5646 和 RFC 4647 中制定的语言标签。但习惯上也使用了 BCP47 等说法。BCP 是指在制定前被认为是“当前最佳”的 BCP(最佳现行实践)中的第47号“语言标签指定最佳实践”,因此只是保留了当时的习惯。如果要参考,请使用 RFC。
另外,IETF 语言标签采用的是类似 “en-US” 和 “ja-JP” 这样的连字符(横线)结构,所以在使用操作系统的区域设置时,需要注意与下划线(underscore)的记法如 “ja_JP” 或 “ja_JP.UTF-8” 的差异。 ←(我

老师,请给我一个在 Go v1.15 及以上版本可运行的东西。

嗯?国际化?

我认为当被告知“需要进行国际化适应”时,尽管我使用英语开发了应用程序并吸引了日本人的需求,但我会感到一种不愉快的味道。

但是即使说“国际化应对”,

    1. 为每种语言准备术语字典。

 

    1. 通过操作系统的Locale设置,在运行环境中选择使用的语言字典。

 

    1. 指定术语的识别符(消息ID),将返回相应术语的指定语言字典。

 

    然后将其打印出来。

只不过如此罢了。

总的来说,可以将其类比为类似于关联数组的东西。可以这样考虑:为每种语言准备一个数组(映射),当指定相应语言数组的键时,可以获得相应的值。

以下是用map(Go的关联数组)简单实现的。

以下是只需一种选择的原生中文释义:这里以map(Go的关联数组)简单实现为例。

en := map[string]string{
	"helloworld": "hello, world",
}

ja := map[string]string{
	"helloworld": "こんにちは, 世界",
}

fmt.Println(en["helloworld"])
fmt.Println(ja["helloworld"])

// Output:
// hello, world
// こんにちは, 世界

オンラインで動作をみる @ Go Playground

国际化和本地化的区别

当谈到将应用程序进行多语言支持时,涉及到的术语通常是国际化和本地化。

国际化的日语翻译是“Internationalization”,本土化的日语翻译是“Localization”。在应用中,这两个都是“可用其他语言显示的功能”。他们有什么不同呢?

如果将”多言语表示支持”改为”国际化支持”,基本上是相同的意思。在这种情况下,Internationalization表示准备国际化支持,而Localization表示应用国际化支持。

换句话说,Internationalization 是指“准备(实现功能)以在其他语言环境下显示”,而 Localization 是指“将英文显示适配到日文显示等地区特定优化(语言实现)”。

由于国际化工作更具通用性,因此也被称为“全球化”。将“国际化”称为“全球化”,只是为了更好地表达“能够更全面(通用)地适应全球”的意思而已。

举个例子,在重构中,国际化的意思是“尽管显示方式保持原样,但只要准备好词典,就能随时切换显示语言”,而创建特定语言的词典和确认显示方式则称为本地化。

在不需要IME的ASCII码领域(尤其是英语区域)的代码库中,可能会发生国际化问题的骚动。在这种情况下,使其支持UTF-8等就是国际化,而对日语输入法进行支持则称为本地化。

要推动国际化和地域化,我们可以使用关联数组来实现”我国际化对应”,就像之前的例子一样。但我们也可以利用专用的库来节省时间,将空闲时间用于创建词典。

Go语言的国际化套件

Linux上有一个老牌的C语言库叫做GNU Gettext。对于PHP(尤其是WordPress)用户来说,我想你可能见过翻译函数”_()”,那就是它的起源。

在 Go 语言中,有一个著名的 go-i18n 包,用于实现国际化(internationalization)功能。

go-i18n パッケージ

github.com/nicksnyder/go-i18n @ GitHub

只需要将它作为模块嵌入,并将文档输出的部分替换为翻译函数,就可以简单地通过准备词典来准备其他语言的支持。

然而,这个包装上的官方示范很难理解。

因此,我認為直接看到一個實際運作的簡單示例可能更為快速,所以,請先參考以下的源代碼。

使用 JSON 格式定义并使用固定消息,例如帮助信息。

package main

import (
	"encoding/json"
	"fmt"

	"github.com/nicksnyder/go-i18n/v2/i18n"
	"golang.org/x/text/language"
)

func main() {
	// デフォルト言語を英語に設定
	bundle := i18n.NewBundle(language.English)

	// パーサーエンジンを JSON にセット
	bundle.RegisterUnmarshalFunc("json", json.Unmarshal)

	// JSON の辞書を登録
	bundle.MustParseMessageFileBytes([]byte(`{"MSG001": "Hello World!"}`), "en.json")
	bundle.MustParseMessageFileBytes([]byte(`{"MSG001": "Hola Mundo!"}`), "es.json")
	bundle.MustParseMessageFileBytes([]byte(`{"MSG001": "ようこそ、世界!"}`), "ja.json")

	// 辞書から引きたい内容(MSG001 を引きたい)
	referTo := &i18n.LocalizeConfig{MessageID: "MSG001"}

	// 辞書を引く
	{
		// 英語の辞書を使う
		localizer := i18n.NewLocalizer(bundle, "en-US")
		fmt.Println(localizer.MustLocalize(referTo))
	}
	{
		// スペイン語の辞書を使う
		localizer := i18n.NewLocalizer(bundle, "es-ES")
		fmt.Println(localizer.MustLocalize(referTo))
	}
	{
		// 日本語の辞書を使う
		localizer := i18n.NewLocalizer(bundle, "ja-JP")
		fmt.Println(localizer.MustLocalize(referTo))
	}
	{
		// 未定義の辞書を使う(デフォルトの英語を使う)
		localizer := i18n.NewLocalizer(bundle, "ko-KR")
		fmt.Println(localizer.MustLocalize(referTo))
	}
}
// Output:
// Hello World!
// Hola Mundo!
// ようこそ、世界!
// Hello World!

オンラインで動作をみる @ Go Playground

复数形 和 单数形 的变化选项

通常情况下,像上述的使用 JSON 的例子一样,我们通常认为“标题、帮助信息等简单替换就足够了”是比较常见的情况。

然而,如果说「有●条消息」,根据语言的不同,有些语言可能会不考虑单数或复数形式而显得不自然。

以下是一个根据 PluralCount 值在翻译(本土化)时使用单数形或复数形的示例。

说实话,只有一个注册术语,并且使用了结构体而不是JSON,所以你可能会觉得用”if”语句分支会更快。

然而,请注意在“仅通过消息ID进行简单替换”的限制达到时,可以给消息(术语)引入变化的可能性。

package main

import (
	"fmt"

	"github.com/nicksnyder/go-i18n/v2/i18n"
	"golang.org/x/text/language"
)

func main() {
	// 辞書セットを作成しデフォルトを英語に設定
	bundle := i18n.NewBundle(language.English)
	// 使う辞書の言語設定
	loc := i18n.NewLocalizer(bundle, language.English.String())

	// 辞書用語を設定(単数形・複数形対応)
	//   i18n.Message 型は Localize から呼び出された際の個数情報(PluralCount)
    //   によって単数(One:)、複数(Other:)でメッセージ内容が変わる。
    //   "One" と "Other" にある "{{.Name}}" と "{{.Count}}" は、
    //   Localize() から呼び出された際に渡される map のキー名。
	message := &i18n.Message{
        // メッセージ ID(この設定を呼び出すキー)
		ID: "Emails",
        // メモ
		Description: "The number of unread emails a user has",
        // 単数形用の構文定義(いわるゆテンプレート)
		One: "{{.Name}} has {{.Count}} email.",
        // 複数形用の構文定義(いわるゆテンプレート)
		Other: "{{.Name}} has {{.Count}} emails.",
	}

	// 個数を指定(単数・複数でメッセージ内容が変わるかの確認)
    emailCount := 2

	// 辞書翻訳の実行
    // MustLocalize と Localize は同じですが、MustLocalize はエラー時に
    // パニックになります。
	translation := loc.MustLocalize(&i18n.LocalizeConfig{
		// デフォルトで使う辞書用語を定義
		DefaultMessage: message,
		// 辞書の用語(のテンプレート)に流し込むデータ。
        // "Name", "Count" を持つ辞書登録(用語)すべてに "KEINOS" と個数
        // をテンプレートとしてマッピング(割り当てを)する。
		TemplateData: map[string]interface{}{
			"Name":  "KEINOS",
			"Count": emailCount,
		},
		// データの個数情報
		PluralCount: emailCount,
	})

	fmt.Println(translation)
}

// Output: KEINOS has 2 emails.

オンラインで動作をみる @ Go Playground(Go version: 1.16)

相關文獻

【Golang】JSON などの外部ファイルをバイナリに埋め込むサンプル【Go 1.16+】 @ Qiita

bannerAds