{"id":19774000,"url":"https://github.com/kaptinlin/go-i18n","last_synced_at":"2025-04-30T18:32:49.066Z","repository":{"id":176690233,"uuid":"657936315","full_name":"kaptinlin/go-i18n","owner":"kaptinlin","description":"A Go library for adding different languages to your app. It works with text or codes for translations and can load translations from files or directly from your code. It also supports the ICU Message Format for complex translations.","archived":false,"fork":false,"pushed_at":"2024-08-27T03:16:14.000Z","size":77,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-08-27T04:33:27.622Z","etag":null,"topics":["i18n","icu-messageformat","translation"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kaptinlin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-24T09:04:19.000Z","updated_at":"2024-08-27T04:33:30.511Z","dependencies_parsed_at":null,"dependency_job_id":"537ec99b-c5e6-40ec-a169-5651d2bb0d4c","html_url":"https://github.com/kaptinlin/go-i18n","commit_stats":null,"previous_names":["kaptinlin/go-i18n"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaptinlin%2Fgo-i18n","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaptinlin%2Fgo-i18n/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaptinlin%2Fgo-i18n/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kaptinlin%2Fgo-i18n/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kaptinlin","download_url":"https://codeload.github.com/kaptinlin/go-i18n/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224219555,"owners_count":17275477,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["i18n","icu-messageformat","translation"],"created_at":"2024-11-12T05:11:42.686Z","updated_at":"2024-11-12T05:11:43.297Z","avatar_url":"https://github.com/kaptinlin.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# i18n (Go)\r\n\r\n`kaptinlin/i18n` is a simple, easy to use localization and internationalization support for Go.\r\n\r\n- Token-based (`hello_world`) and Text-based (`Hello, world!`) translation.\r\n- Load translations from a map, files or `go:embed` supported.\r\n- Translations with [ICU Message Format](https://unicode-org.github.io/icu/userguide/format_parse/messages/) syntax are supported.\r\n\r\n## Index\r\n-   [Installation](#installation)\r\n-   [Getting Started](#getting-started)\r\n    -   [Load from Go map](#load-from-go-map)\r\n    -   [Load from Files](#load-from-files)\r\n    -   [Load from Glob Matching Files](#load-from-glob-matching-files)\r\n    -   [Load from Embedded Files](#load-from-embedded-files)\r\n-   [Translations](#translations)\r\n    -   [Passing Data to Translation](#passing-data-to-translation)\r\n-   [Pluralization](#pluralization)\r\n-   [Text-based Translations](#text-based-translations)\r\n    -   [Disambiguation by context](#disambiguation-by-context)\r\n    -   [Act as fallback](#act-as-fallback)\r\n-   [Fallbacks](#fallbacks)\r\n-   [Custom Unmarshaler](#custom-unmarshaler)\r\n    -   [YAML Unmarshaler](#yaml-unmarshaler)\r\n    -   [TOML Unmarshaler](#toml-unmarshaler)\r\n    -   [INI Unmarshaler](#ini-unmarshaler)\r\n-   [Parse Accept-Language](#parse-accept-language)\r\n\r\n\u0026nbsp;\r\n\r\n## Installation\r\n\r\n```bash\r\n$ go get github.com/kaptinlin/go-i18n@latest\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Getting started\r\n\r\nCreate a folder named `./locales` and put some `YAML`, `TOML`, `INI` or `JSON` files.\r\n\r\n```sh\r\n│   main.go\r\n└───locales\r\n    ├───en.json\r\n    └───zh-Hans.json\r\n```\r\n\r\nNow, put the key-values content for each locale, e.g. \r\n\r\n**locales/en.json** \r\n```json\r\n{\r\n  \"hello\": \"Hello, {name}\"\r\n}\r\n```\r\n\r\n**locales/zh-Hans.json**\r\n```json\r\n{\r\n  \"hello\": \"你好, {name}\"\r\n}\r\n```\r\n\r\n**main.go**\r\n\r\n```go\r\npackage main\r\n\r\nimport (\r\n    \"github.com/kaptinlin/go-i18n\"\r\n    \"fmt\"\r\n)\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n\r\n    err := bundle.LoadFiles(\"./locales/zh-Hans.json\", \"./locales/en.json\")\r\n    if err != nil {\r\n        fmt.Println(err)\r\n    }\r\n\r\n    localizer := bundle.NewLocalizer(\"zh-Hans\")\r\n\r\n    // Output: 你好, John\r\n    fmt.Println(localizer.Get(\"hello\", i18n.Vars{\r\n        \"name\": \"John\",\r\n    }))\r\n}\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n\r\n## Load from Go map\r\n\r\n```go\r\npackage main\r\n\r\nimport \"github.com/kaptinlin/go-i18n\"\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n\r\n    bundle.LoadMessages(map[string]map[string]string{\r\n        \"en\": map[string]string{\r\n            \"hello_world\": \"hello, world\",\r\n        },\r\n        \"zh-Hans\": map[string]string{\r\n            \"hello_world\": \"你好，世界\",\r\n        },\r\n    })\r\n}\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Load from Files\r\n\r\n```go\r\npackage main\r\n\r\nimport \"github.com/kaptinlin/go-i18n\"\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n\r\n    bundle.LoadFiles(\"./locales/en.json\", \"./locales/zh-Hans.json\")\r\n}\r\n```\r\n\r\nFilenames like `zh-Hans.json` `zh-Hans.user.json` will be combined to a single `zh-Hans` translation.\r\n\r\nNo matter if you are naming them like `zh_CN`, `zh-Hans` or `ZH_CN`, they will always be converted to `zh-Hans`.\r\n\r\n\u0026nbsp;\r\n\r\n## Load from Glob Matching Files\r\n\r\n```go\r\npackage main\r\n\r\nimport \"github.com/kaptinlin/go-i18n\"\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n\r\n    bundle.LoadGlob(\"./locales/*.json\")\r\n}\r\n```\r\n\r\nThe glob pattern adds all files within `locales` directory with the `.json` extension\r\n\r\n\u0026nbsp;\r\n\r\n## Load from Embedded Files\r\n\r\nUse `LoadFS` if you are using `go:embed` to compile your translations to the program.\r\n\r\n```go\r\npackage main\r\n\r\nimport \"github.com/kaptinlin/go-i18n\"\r\n\r\n//go:embed locales/*.json\r\nvar localesFS embed.FS\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n\r\n    // Load all json files under `locales` folder from the filesystem.\r\n    bundle.LoadFS(localesFS, \"locales/*.json\")\r\n}\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Translations\r\n\r\nTranslations named like `welcome_message`, `button_create`, `button_buy` are token-based translations. For text-based, check the chapters below.\r\n\r\n```json\r\n{\r\n    \"hello_world\": \"你好，世界\"\r\n}\r\n```\r\n\r\n```go\r\nlocalizer := bundle.NewLocalizer(\"zh-Hans\")\r\n\r\n// Output: 你好，世界\r\nlocalizer.Get(\"hello_world\")\r\n\r\n// Output: message_what_is_this\r\nlocalizer.Get(\"message_what_is_this\")\r\n```\r\n\r\nLanguages named like `zh_cn`, `zh-Hans` or `ZH_CN`, `NewLocalizer` will always convert them to `zh-Hans`.\r\n\r\n\u0026nbsp;\r\n\r\n### Passing Data to Translation\r\n\r\nIt's possible to pass the data to translations. [ICU MessageFormat](https://unicode-org.github.io/icu/userguide/format_parse/messages/) is used to parse the text, the templates will be parsed and cached after the translation was loaded.\r\n\r\n```json\r\n{\r\n    \"message_vars\": \"你好，{Name}\"\r\n}\r\n```\r\n\r\n```go\r\n// Output: 你好，Yami\r\nlocalizer.Get(\"message_vars\", i18n.Vars{\r\n    \"Name\": \"Yami\",\r\n})\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Pluralization\r\n\r\nUsing language specific plural forms (`one`, `other`)\r\n\r\n```json\r\n{\r\n    \"message\": \"{count, plural, one {Message} other {Messages}}\"\r\n}\r\n```\r\n\r\n```go\r\n// Output: Message\r\nlocalizer.Get(\"message\", i18n.Vars{\r\n    \"count\": 1,\r\n})\r\n\r\n// Output: Messages\r\nlocalizer.Get(\"message\", i18n.Vars{\r\n    \"count\": 2,\r\n})\r\n```\r\n\r\nUsing exact matches (`=0`):\r\n```json\r\n{\r\n    \"messages\": \"{count, plural, =0 {No messages} one {1 message} other {# messages}}\"\r\n}\r\n```\r\n\r\n```go\r\n// Output: No messages\r\nlocalizer.Get(\"messages\", i18n.Vars{\r\n    \"count\": 0,\r\n})\r\n\r\n// Output: 1 message\r\nlocalizer.Get(\"messages\", i18n.Vars{\r\n    \"count\": 1,\r\n})\r\n\r\n// Output: 2 messages\r\nlocalizer.Get(\"messages\", i18n.Vars{\r\n    \"count\": 2,\r\n})\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Text-based Translations\r\n\r\nTranslations can also be named with sentences so it will act like fallbacks when the translation was not found.\r\n\r\n```json\r\n{\r\n    \"I'm fine.\": \"我过得很好。\",\r\n    \"How about you?\": \"你如何呢？\"\r\n}\r\n```\r\n\r\n```go\r\n// Output: 我过得很好。\r\nlocalizer.Get(\"I'm fine.\")\r\n\r\n// Output: 你如何呢？\r\nlocalizer.Get(\"How about you?\")\r\n\r\n// Output: Thank you!\r\nlocalizer.Get(\"Thank you!\")\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n### Disambiguation by context\r\n\r\nIn English a \"Post\" can be \"Post something (verb)\" or \"A post (noun)\". With token-based translation, you can easily separating them to `post_verb` and `post_noun`.\r\n\r\nWith text-based translation, you will need to use `GetX` (X stands for context), and giving the translation a `\u003ccontext\u003e` suffix.\r\n\r\nThe space before the `\u003c` is **REQUIRED**.\r\n\r\n```json\r\n{\r\n    \"Post \u003cverb\u003e\": \"发表文章\",\r\n    \"Post \u003cnoun\u003e\": \"一篇文章\"\r\n}\r\n```\r\n\r\n```go\r\n// Output: 发表文章\r\nlocalizer.GetX(\"Post\", \"verb\")\r\n\r\n// Output: 一篇文章\r\nlocalizer.GetX(\"Post\", \"noun\")\r\n\r\n// Output: Post\r\nlocalizer.GetX(\"Post\", \"adjective\")\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n### Act as fallback\r\n\r\nRemember, if a translation was not found, the token name will be output directly. The token name can also be used as template content.\r\n\r\n```go\r\n// Output: Hello, World\r\nlocalizer.Get(\"Hello, {Name}\", i18n.Vars{\r\n    \"Name\": \"World\",\r\n})\r\n\r\n// Output: 2 Posts\r\nlocalizer.Get(\"{count, plural, =0 {No Post} one {1 Post} other {# Posts}}\", i18n.Vars{\r\n    \"Count\": 2,\r\n})\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Fallbacks\r\n\r\nA fallback language will be used when a translation is missing from the current language. If it's still missing from the fallback language, it will lookup from the default language.\r\n\r\nIf a translation cannot be found from any language, the token name will be output directly.\r\n\r\n```go\r\n// `ja-jp` is the default language\r\nbundle :=i18n.New(\r\n    i18n.WithDefaultLocale(\"ja-JP\"),\r\n    i18n.WithFallback(map[string][]string{\r\n        // `zh-Hans` uses `zh`, `zh-Hant` as fallbacks.\r\n        // `en-GB` uses `en-US` as fallback.\r\n        \"zh-Hans\": []string{\"zh\", \"zh-Hant\"},\r\n        \"en-GB\": []string{\"en-US\"},\r\n    },\r\n))\r\n```\r\n\r\nLookup path looks like this with the example above:\r\n\r\n```\r\nzh-Hans -\u003e zh -\u003e zh-Hant -\u003e ja-JP\r\nen-GB -\u003e en-US -\u003e ja-JP\r\n```\r\n\r\nRecursive fallback is also supported. If `zh-Hans` has a `zh` fallback, and `zh` has a `zh-Hant` fallback, `zh-Hans` will have either `zh` and `zh-Hant` fallbacks.\r\n\r\nFallback only works if the translation exists in default language.\r\n\r\n\u0026nbsp;\r\n\r\n## Custom Unmarshaler\r\n\r\nTranslations are JSON format because `encoding/json` is the default unmarshaler. Change it by calling `WithUnmarshaler`.\r\n\r\n### YAML Unmarshaler\r\nUses [`go-yaml/yaml`](https://github.com/go-yaml/yaml) to read the files, so you can write the translation files in YAML format.\r\n\r\n```go\r\npackage main\r\n\r\nimport \"gopkg.in/yaml.v3\"\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n        i18n.WithUnmarshaler(yaml.Unmarshal),\r\n    )\r\n}\r\n```\r\n\r\nYour `zh-Hans.yaml` should look like this:\r\n\r\n```yaml\r\nhello_world: \"你好，世界\"\r\n\"How are you?\": \"你过得如何？\"\r\n\"mobile_interface.button\": \"按钮\"\r\n```\r\n\r\nNested translations are not supported, you will need to name them like `\"mobile_interface.button\"` as key and quote them in double quotes.\r\n\r\n\u0026nbsp;\r\n\r\n### TOML Unmarshaler\r\n\r\nUses [`pelletier/go-toml`](https://github.com/pelletier/go-toml) to read the files, so you can write the translation files in TOML format.\r\n\r\n```go\r\npackage main\r\n\r\nimport \"github.com/pelletier/go-toml/v2\"\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n        i18n.WithUnmarshaler(toml.Unmarshal),\r\n    )\r\n}\r\n```\r\n\r\nYour `zh-Hans.toml` should look like this:\r\n\r\n```toml\r\nhello_world = \"你好, 世界\"\r\nhello_name = \"你好, {name}\"\r\nmessage = \"{count, plural, one {消息} other {消息}}\"\r\nmessage_with_number = \"{count, plural, =0 {没有消息} one {1 条消息} other {# 条消息}}\"\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n### INI Unmarshaler\r\n\r\nUses [`go-ini/ini`](https://github.com/go-ini/ini) to read the files, so you can write the translation files in INI format.\r\n\r\n```go\r\npackage main\r\n\r\nimport \"gopkg.in/ini.v1\"\r\n\r\nfunc unmarshalINI(data []byte, v interface{}) error {\r\n\tf, err := ini.LoadSources(ini.LoadOptions{\r\n\t\tSpaceBeforeInlineComment: true,\r\n\t\tIgnoreInlineComment:      true,\r\n\t}, data)\r\n\tif err != nil {\r\n\t\treturn err\r\n\t}\r\n\r\n\tm := *v.(*map[string]string)\r\n\r\n\tfor _, section := range f.Sections() {\r\n\t\tkeyPrefix := \"\"\r\n\t\tif name := section.Name(); name != ini.DefaultSection {\r\n\t\t\tkeyPrefix = name + \".\"\r\n\t\t}\r\n\r\n\t\tfor _, key := range section.Keys() {\r\n\t\t\tm[keyPrefix+key.Name()] = key.Value()\r\n\t\t}\r\n\t}\r\n\r\n\treturn nil\r\n}\r\n\r\nfunc main() {\r\n    bundle := i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"en\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n        i18n.WithUnmarshaler(unmarshalINI),\r\n    )\r\n}\r\n```\r\n\r\nYour `zh-Hans.ini` should look like this:\r\n\r\n```ini\r\nhello_world=你好, 世界\r\nhello_name=你好, {name}\r\nmessage={count, plural, one {消息} other {消息}}\r\n\r\n[message]\r\nwith_number=\"{count, plural, =0 {没有消息} one {1 条消息} other {# 条消息}}\"\r\n```\r\n\r\n\u0026nbsp;\r\n\r\n## Parse Accept-Language\r\n\r\nThe built-in `MatchAvailableLocale` function helps you to parse the `Accept-Language` from HTTP Header.\r\n\r\n```go\r\nfunc(w http.ResponseWriter, r *http.Request) {\r\n    // Initialize i18n.\r\n    bundle :=i18n.NewBundle(\r\n        i18n.WithDefaultLocale(\"zh-Hans\"),\r\n        i18n.WithLocales(\"en\", \"zh-Hans\"),\r\n    )\r\n    bundle.LoadFiles(\"zh-Hans.json\", \"en.json\")\r\n\r\n    // Get `Accept-Language` from request header.\r\n    accept := r.Header.Get(\"Accept-Language\")\r\n\r\n    // Use the locale.\r\n    localizer := bundle.NewLocalizer(bundle.MatchAvailableLocale(accept))\r\n    localizer.Get(\"hello_world\")\r\n}\r\n```\r\n\r\nOrders of the languages that passed to `NewLocalizer` won't affect the fallback priorities, it will use the first language that was found in loaded translations.\r\n\r\n\u0026nbsp;\r\n\r\n## Thanks\r\n\r\n- https://github.com/teacat/i18n\r\n- https://github.com/kataras/i18n\r\n- https://github.com/nicksnyder/go-i18n\r\n- https://github.com/vorlif/spreak\r\n- https://github.com/oblq/i18n\r\n\r\n## License\r\n\r\n`kaptinlin/i18n` is free and open-source software licensed under the [MIT License](https://tldrlegal.com/license/mit-license).\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaptinlin%2Fgo-i18n","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkaptinlin%2Fgo-i18n","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkaptinlin%2Fgo-i18n/lists"}