go-i18n is a Go package and a command that helps you translate Go programs into multiple languages.

Overview

go-i18n Build status Report card codecov Sourcegraph

go-i18n is a Go package and a command that helps you translate Go programs into multiple languages.

Package i18n

GoDoc

The i18n package provides support for looking up messages according to a set of locale preferences.

import "github.com/nicksnyder/go-i18n/v2/i18n"

Create a Bundle to use for the lifetime of your application.

bundle := i18n.NewBundle(language.English)

Load translations into your bundle during initialization.

bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
bundle.LoadMessageFile("es.toml")

Create a Localizer to use for a set of language preferences.

func(w http.ResponseWriter, r *http.Request) {
    lang := r.FormValue("lang")
    accept := r.Header.Get("Accept-Language")
    localizer := i18n.NewLocalizer(bundle, lang, accept)
}

Use the Localizer to lookup messages.

localizer.Localize(&i18n.LocalizeConfig{
    DefaultMessage: &i18n.Message{
        ID: "PersonCats",
        One: "{{.Name}} has {{.Count}} cat.",
        Other: "{{.Name}} has {{.Count}} cats.",
    },
    TemplateData: map[string]interface{}{
        "Name": "Nick",
        "Count": 2,
    },
    PluralCount: 2,
}) // Nick has 2 cats.

Command goi18n

GoDoc

The goi18n command manages message files used by the i18n package.

go get -u github.com/nicksnyder/go-i18n/v2/goi18n
goi18n -help

Extracting messages

Use goi18n extract to extract all i18n.Message struct literals in Go source files to a message file for translation.

# active.en.toml
[PersonCats]
description = "The number of cats a person has"
one = "{{.Name}} has {{.Count}} cat."
other = "{{.Name}} has {{.Count}} cats."

Translating a new language

  1. Create an empty message file for the language that you want to add (e.g. translate.es.toml).

  2. Run goi18n merge active.en.toml translate.es.toml to populate translate.es.toml with the messages to be translated.

    # translate.es.toml
    [HelloPerson]
    hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
    other = "Hello {{.Name}}"
  3. After translate.es.toml has been translated, rename it to active.es.toml.

    # active.es.toml
    [HelloPerson]
    hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
    other = "Hola {{.Name}}"
  4. Load active.es.toml into your bundle.

    bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
    bundle.LoadMessageFile("active.es.toml")

Translating new messages

If you have added new messages to your program:

  1. Run goi18n extract to update active.en.toml with the new messages.
  2. Run goi18n merge active.*.toml to generate updated translate.*.toml files.
  3. Translate all the messages in the translate.*.toml files.
  4. Run goi18n merge active.*.toml translate.*.toml to merge the translated messages into the active message files.

For more information and examples:

License

go-i18n is available under the MIT license. See the LICENSE file for more info.

Comments
  • Release v2.0.0

    Release v2.0.0

    I have started to prototype what v2 of this library might look like.

    The overall goals of v2 are to

    1. Address all issues that are currently open on the project
    2. Apply some Go best practices to the codebase (e.g. remove global state, explicit error returns)
    3. Revisit API design and make breaking changes which are necessary to accomplish (1) and (2).

    My development plan is to work on this in a branch until I am happy with the results. Then, I will merge this into master under a subfolder called v2-beta and tag a 1.x.0 release. Projects can then begin to try out the new API and provide feedback. APIs under v2-beta subfolder will be subject to change pending community feedback. Once I am happy with the results, I will make a final 1.x.0 release that contains the v2 api under the v2 subfolder. ~~Immediately following that, I will delete the v1 package files, move v2-beta to the root of the repository, and tag 2.0.0.~~

    I do not have an estimate for when this work will be complete as I am working on it in spare time here and there.

    If you have general feedback about this process it can be shared here. If there are other things you would want to see in a world where breaking API changes are possible, please create a new issue and I will consider it.

    opened by nicksnyder 18
  • How do you handle l10n ?

    How do you handle l10n ?

    How do you handle the format for dates, numbers, etc.. ?

    How about stealing this: https://github.com/jbnicolai/closure-library/blob/20702eff5afe0fd9d8c5a42254879fc18c34253b/closure/goog/i18n/datetimepatternsext.js ?

    For example, the angular folks leverage it through their https://docs.angularjs.org/api/ng/filter/date filter.. same with number and currency filters..

    enhancement 
    opened by abourget 15
  • Allow use custom language codes

    Allow use custom language codes

    Fix #72 Fix https://github.com/spf13/hugo/issues/3564

    go-i18n doesn't return an error now, if user provides non-registered language code.

    /cc @bep @nicksnyder

    opened by bogem 14
  • bundle: runtime-reflect structs to map[string]interface{}

    bundle: runtime-reflect structs to map[string]interface{}

    Fixes #24.

    @nicksnyder The structs package is quite lightweight. It is also licensed under MIT. I can attempt to extract the Struct.structFields() and Struct.Map() methods if you would prefer.

    opened by parkr 12
  • Odd behaviour with DefaultMessage

    Odd behaviour with DefaultMessage

    Working on https://github.com/gohugoio/hugo/pull/5243 and trying to find a workaround for #124 I found some odd behaviour with DefaultMessage

    translated, err := localizer.Localize(&i18n.LocalizeConfig{
    				//DefaultMessage: defaultMessage,
    				MessageID:    translationID,
    				TemplateData: templateData,
    			})
    

    I have a test case which returns translated as expected and no error with the code above. If I uncomment the DefaultMessage, I always get the default message in return.

    opened by bep 11
  • The command

    The command "goi18n constants" is missing

    Hello! I use "github.com/nicksnyder/go-i18n/v2/i18n" and I want to use the command "goi18n constants", but for some reason it is unavailable in the 2nd version of this package.

    I tried to use the first version of the package to generate constants, but it seems that it is incompatible with the format of the 2nd version:

    goi18n constants es.toml
    failed to load translation file es.toml because unable to parse translation #0 because invalid plural category description
    map[id:PersonCats translation:map[description:The number of cats a person has one:{{.Name}} has {{.Count}} cat. other:{{.Name}} has {{.Count}} cats.]]
    
    # es.toml
    [PersonCats]
    description = "The number of cats a person has"
    one = "{{.Name}} has {{.Count}} cat."
    other = "{{.Name}} has {{.Count}} cats."
    

    Can you explain why this command is not available in the second version of the package and do you have plans to return it?

    question 
    opened by VadimKulagin 10
  • [Feature] Command goi18n generate dynamic struct R.go

    [Feature] Command goi18n generate dynamic struct R.go

    Working with Android/Java the IDE generates a dynamic class R.java with all strings ids, this method avoid a lot of bugs. In a big project is easy to reference an invalid id or to have a lot of unused ids.

    What do you think the command goi18n generates R.go ? Example:

    en-US.all.json

    [
      {
        "id": "settings_title",
        "translation": "Settings"
      }
    ]
    
    $ goi18n -genconst en-US.all.json -outdir ./
    

    this command generates R.go

    package R
    const SettingsTitle string = "settings_title"
    

    In code the new way will be:

    T(R.SettingsTitle)
    

    This is only a suggestion, let me know what do you think. I can help with this change thanks

    enhancement 
    opened by rodcorsi 10
  • Question: {{T

    Question: {{T "messageID" .Param1 .Param2}} in v2

    Hi, just found this package today, played around with v2 a little bit, and it works great. Great package.

    My goal is to use translations from html/template files only, like this:

    {{T "messageID" .Param1 .Param2}}
    

    I see that there isn't a Tfunc() in v2 as opposed to v1 so I wrote a wrapper:

    lang := r.FormValue("lang")
    accept := r.Header.Get("Accept-Language")
    localizer := i18n.NewLocalizer(bundle, lang, accept)
    T := func(messageID string, args ...interface{}) string {
    	lc := i18n.LocalizeConfig{MessageID: messageID}
    	if len(args) > 1 {
    		lc.PluralCount = args[0]
    		td := make(map[string]interface{})
    		td["PluralCount"] = lc.PluralCount
    		for i := 1; i < len(args); i++ {
    			td["P"+strconv.Itoa(i)] = args[i]
    		}
    		lc.TemplateData = td
    	}
    	return localizer.MustLocalize(&lc)
    }
    t.Funcs(map[string]interface{}{
    	"T": T,
    })
    ...
    

    This makes it possbile to have a translation like this:

    [PersonUnreadEmails]
    description = "The number of unread emails a person has"
    one = "{{.P1}} has {{.PluralCount}} unread email."
    other = "{{.P1}} has {{.PluralCount}} unread emails."
    

    So the first argument is always translated to PluralCount and the rest is numbered, like P1, P2 and so on, and then I can use it like this:

    {{T "PersonUnreadEmails" .NumberOfUnreadEmail .PersonName}}
    

    This works perfectly but is this also how you would do it? Is there a better way?

    opened by zengabor 9
  • Preferences don't cascade on translation

    Preferences don't cascade on translation

    Hey, @nicksnyder! Ran into an issue today you might find interesting. Here's my situation.

    I have three files, all for English: en.all.json, en-US.all.json, and en-GB.all.json. The first holds all English translations that are common between all variations of English we support. The second and third files hold translations specific to their respective locales.

    A user requests something from us, and sends us the locale en-US. We make a TranslationFunc with preferences en-US, en. Now here's where we run into a problem: if I ask for a translation that is not in en-US.all.json, it returns the translationID instead of looking inside en.all.json for a relevant translation.

    This is due to the way bundle.TfuncAndLanguage handles language preferences. When I ask for en-US and the language has any translations, it limits the search to this map, ignoring the remaining language preferences.

    I'd like to support the preference fallback at the translation level, instead of the language level. In pseudocode, this is:

    translation_id = "my_string"
    preferences = %[en-US en]
    
    # Upon request for a translation, iterate through each & return
    # if any of the preferences contain the translation.
    preferences.each do |pref|
      if translations[pref] && translations[pref][translation_id]
        # A translation was found.
        return translations[pref][translation_id]
      end
    end
    # If no langs have matching translations, return the translation ID.
    return translation_id
    

    What do you think?

    enhancement 
    opened by parkr 9
  • example ?

    example ?

    Hello

    I am looking for a way to build translation into a web-based project. I came across this and it really looks impressive. However, I have no idea where to start; I am puzzled how the whole framework is to be integrated in a project. It would be great if there was a minimum sample project somewhere, showing how this is done ?

    opened by Gys 9
  • Export SupportedLanguage

    Export SupportedLanguage

    I'd like to know which language go-i18n decided to use among the candidates caller provided to Tfunc, for example save the language into database for pushing localized content afterwards.

    opened by mash 9
  • Easy wrapper around this library

    Easy wrapper around this library

    Obviously the examples provided are way to descriptive for a real world use. With already so much code we have to write in go, I don't think, I would like to see 10 line implementations per localized string.

    Here is an example wrapper you can use . Please feel free to improve it.

    package locales
    
    import (
    	"encoding/json"
    	"fmt"
    	"strings"
    
    	"github.com/nicksnyder/go-i18n/v2/i18n"
    	"golang.org/x/text/language"
    )
    
    type Localizer struct {
    	bundle *i18n.Bundle
    }
    
    var localizer Localizer
    
    func NewLocalizer(path string, languages ...string) Localizer {
    	path = strings.TrimSuffix(path, "/")
    
    	bundle := i18n.NewBundle(language.English)
    	bundle.RegisterUnmarshalFunc("json", json.Unmarshal)
    
    	for _, lang := range languages {
    		bundle.MustLoadMessageFile(fmt.Sprintf("%s/translations/%s.json", path, lang))
    	}
    
    	return Localizer{bundle: bundle}
    }
    
    func (l Localizer) Get(lang string, id string) string {
    	localizer := i18n.NewLocalizer(l.bundle, lang)
    
    	cfg := &i18n.LocalizeConfig{
    		DefaultMessage: &i18n.Message{
    			ID:    id,
    			Other: id,
    			One:   id,
    		},
    	}
    
    	str, err := localizer.Localize(cfg)
    	if err != nil {
    		return id
    	}
    
    	return str
    }
    
    func (l Localizer) GetWithData(lang, id string, data map[string]interface{}) string {
    	localizer := i18n.NewLocalizer(l.bundle, lang)
    
    	cfg := &i18n.LocalizeConfig{
    		DefaultMessage: &i18n.Message{
    			ID:    id,
    			Other: id,
    		},
    		TemplateData: data,
    	}
    	str, err := localizer.Localize(cfg)
    	if err != nil {
    		return id
    	}
    
    	return str
    }
    

    and use it as such

    lc := locales.NewLocalizer(dotConfig.LocaleRootPath, "en", "es")
    
    fmt.Println(lc.Get("en", "time_up"))
    fmt.Println(lc.Get("es", "time_up"))
    fmt.Println(lc.Get("es", "time up"))
    

    translations/en.json

    {
        "time_up": "Retry limit exceeded"
    }
    

    translations/es.json

    {
        "time_up": "demasiados reintentos"
    }
    

    The immediate improvement is to make sure, the languages exists.

    opened by amitavaghosh1 0
  • Hook to commonly catch errors

    Hook to commonly catch errors

    hey. First of all, Thanks for maintaining this library which helped me localize go serviceseaily.

    I'm using this lib in couple of instances. I want to track all the missing translation (MessageNotFound) error for future reporting and fixes. I searched through the docs and cannot find a onError kinda hook something similar to react-intl's onError hook. Is there any recommendations on this? I would be happy to build one :) Thanks

    opened by gokatz 4
  • Cound you export this function or when translation has error get the original value

    Cound you export this function or when translation has error get the original value

    code: https://github.com/nicksnyder/go-i18n/blob/639caa7eb5031c3fc275d5b3bf1f44fe3c3f2623/v2/i18n/localizer.go#L181

    repeat issue: https://github.com/nicksnyder/go-i18n/issues/275#issuecomment-1179462892

    language.yaml:

    base:
      foo: "foo {{ bar }} "
    

    when I want to translate base.foo, it will be get error tips: function "bar" not defined. I wish the key base.foo is exists, I'll get the string value foo {{ bar }} for translate result,and the err tip. if the key is not exists, I'll get the err and empty result.

    opened by kumfo 5
  • `goi18n extract` doesn't seem to work for source file that use generics

    `goi18n extract` doesn't seem to work for source file that use generics

    When trying to use goi18n extract on my project that uses generics I'm getting the following error:

    10:17: expected '(', found '[' (and 3 more errors)
    

    I'm guessing that is due to the generics type parameters that start with a [ when a regular function would directly have a ( before listing the regular parameters.

    I'm guessing this issue comes from /v2/goi18n/extract_command.go:122 parser.ParseFile(fset, "", buf, parser.AllErrors)

    Would increasing the version of golang used in this project from go1.12 to go1.18 fix this issue? I'm guessing then the package go/parser would be able to parse generics correctly.

    opened by CorentinClabaut 3
  • How to generate translations for the toml files

    How to generate translations for the toml files

    Hi, I was looking at the examples. One thing I am missing is - How to exactly translate all the messages in the translate.*.toml files? For po files there are some tools like poedit. Similary do they exist for json or toml files?

    opened by sujatapatnaik52 3
Releases(v2.2.1)
Owner
Nick Snyder
Nick Snyder
Split multiple Kubernetes files into smaller files with ease. Split multi-YAML files into individual files.

Split multiple Kubernetes files into smaller files with ease. Split multi-YAML files into individual files.

Patrick D'appollonio 204 Dec 29, 2022
Split multiple Kubernetes files into smaller files with ease. Split multi-YAML files into individual files.

kubectl-slice: split Kubernetes YAMLs into files kubectl-slice is a neat tool that allows you to split a single multi-YAML Kubernetes manifest into mu

Patrick D'appollonio 205 Jan 3, 2023
simple i18n support that relies on standard go libraries

The i18n package mainly includes a set of methods for managing the data. Start by creating a en.json file.

Alvaro Jose Lobaton Restrepo 4 Jun 29, 2021
Tiny Go tool for running multiple functions concurrently and collecting their results into an error slice.

Overview Short for "ConCurrent". Tiny Go tool for running multiple functions concurrently and collecting their results into an error slice. Dependency

Nelo Mitranim 0 Nov 22, 2021
gofu aims to provide a flexible toolkit for creating custom scripting languages in Go.

gofu a scripting language toolkit in Go intro gofu aims to provide a flexible toolkit for creating custom scripting languages in Go. functions Functio

Andreas Nilsson 10 Nov 14, 2022
Small tool for splitting files found in a path into multiple groups

Small tool for splitting files found in a path into multiple groups. Usefull for parallelisation of whatever can be paralleled with multiple files.

Antonio Martinović 0 Jan 30, 2022
Simple project to demonstrate the loading of eBPF programs via florianl/go-tc.

tc-skeleton Simple project to demonstrate the loading of eBPF programs via florianl/go-tc.

Florian Lehner 32 Dec 23, 2022
Generate Go clients from anchor IDLs for Solana blockchain programs

usage anchor-go --src=/path/to/idl.json Generated Code will be generated and saved to ./generated/. TODO instructions accounts types events errors han

Slavomir 88 Jan 6, 2023
This Go package allows you to set handler functions that run when named events occur

This Go package allows you to set handler functions that run when named events occur

James 1 Feb 10, 2022
A tool to find redirection chains in multiple URLs

UnChain A tool to find redirection chains in multiple URLs Introduction UnChain automates process of finding and following `30X` redirects by extracti

RedCode Labs 79 Dec 12, 2022
gProfiler combines multiple sampling profilers to produce unified visualization of what your CPU

gProfiler combines multiple sampling profilers to produce unified visualization of what your CPU is spending time on, displaying stack traces of your processes across native programs1 (includes Golang), Java and Python runtimes, and kernel routines.

Granulate 598 Dec 27, 2022
LogAnalyzer - Analyze logs with custom regex patterns.Can search for particular patterns on multiple files in a directory.

LogAnalyzer Analyze logs with custom regex patterns.Can search for particular patterns on multiple files in a directory

Johnson Simon 6 May 31, 2022
A package for running subprocesses in Go, similar to Python's subprocesses package.

A package for running subprocesses in Go, similar to Python's subprocesses package.

Esteban Garcia 5 Jul 28, 2022
Utility to restrict which package is allowed to import another package.

go-import-rules Utility to restrict which package is allowed to import another package. This tool will read import-rules.yaml or import-rules.yml in t

PAYFAZZ 0 Jan 7, 2022
Go library for decoding generic map values into native Go structures and vice versa.

mapstructure mapstructure is a Go library for decoding generic map values to structures and vice versa, while providing helpful error handling. This l

Mitchell Hashimoto 6.4k Dec 28, 2022
A port of the parser from graphql-js into golang

gqlparser This is a parser for graphql, written to mirror the graphql-js reference implementation as closely while remaining idiomatic and easy to use

Adam Scarr 402 Dec 27, 2022
simple GitHub action to parse Markdown Links into a .yaml file for Hugo

Obsidian Link Scrapper Used by Quartz This repository comes to you in two parts. GitHub Action (scrapes links into a .yml file) Hugo Partial (turns .y

Jacky Zhao 86 Dec 30, 2022
Chaos Engineering tool for introducing failure into syscalls

Syscall monkey Chaos Engineering tool for tampering with syscalls.

null 7 Jun 11, 2022
A Go utility to convert Go example tests into jupyter notebooks.

go2colab Scientists (my main project's users) love jupyter notebook tutorials pkg.dev.go's runnable playground doesn't support file IO but I love exam

Tim 1 Jul 10, 2022