Recently while writing an API in Go, I faced issues in localizing my application. Currently, Go’s standard package does not provide localization support, so I decided to write a simple blog post on some steps we need to implement it.
What do we want ?
I simply want one method to which if I pass a key and a language as an argument. It should return a message in that language. Lets see one example for better understanding:
Lets define some data first:
en-US.yaml
|
1 |
welcome: "Hello" |
es-AR.yaml
|
1 |
welcome: "Hola" |
Time for some action:
translate(“welcome”, “en-US”) => “Hello”
translate(“welcome”, “es-AR”) => “Hola”
Whoa! that is exactly our requirement, lets see how can we do this.
Lets pickup a package
Lets not re-invent the wheel and look over for some package which we can use to implement localization. After some research, I decided to use nicksnyder/go-i18n package. Reason being that it is the most popular one and is actively maintained. So my post will be using examples very specific to the library which I have chosen.
Cheer up ! We are already one step towards success.
Now What ? Lets plan and code!
The package documentation seems quite full of knowledge and different implementations of how to use the library. Lets jump right on to the steps which are relevant to our use case.
|
1 2 3 |
1. Create YAML files containing the translations of each language our application will support 2. Register all YAML files using the go-i18n package 3. Use the library to translate message in any defined language |
Create YAML files containing data
Create some YAML files in root directory of your application:
en-US.yaml (English US)
|
1 2 3 |
welcome: "Hello" good_morning: "Good Morning" good_day: "Have a good day" |
es-AR.yaml (Spanish Argentina)
|
1 2 3 |
welcome: "Hola" good_morning: "Buenos días" good_day: "Tenga un buen día" |
In our main function define a slice of strings containing path to all language translation files which our application will support.
|
1 2 3 4 5 6 7 |
package main import "fmt" func main() { langFiles := []string{"en-US.yaml", "es-AR.yaml"} } |
Please note that I have given only file names because translation files are in same directory of my main.go file. If this is not the case with you, please give complete path to yaml files.
Register the above translations using go-i18n package
Whenever our application starts, we will load all translations in memory so that they can later be used for translating a key. Have a look at the code below:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
package main import ( "fmt" "io/ioutil" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" yaml "gopkg.in/yaml.v2" ) func main() { langFiles := []string{"en-US.yaml", "es-AR.yaml"} // Create a Bundle to use for the lifetime of your application bundle, err := CreateLocalizerBundle(langFiles) if err != nil { fmt.Errorf("Error initialising localization, %v", err) panic(err) } } // CreateLocalizerBundle reads language files and registers them in i18n bundle func CreateLocalizerBundle(langFiles []string) (*i18n.Bundle, error) { // Bundle stores a set of messages bundle := &i18n.Bundle{DefaultLanguage: language.English} // Enable bundle to understand yaml bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) var translations []byte var err error for _, file := range langFiles { // Read our language yaml file translations, err = ioutil.ReadFile(file) if err != nil { fmt.Errorf("Unable to read translation file %s", file) return nil, err } // It parses the bytes in buffer to add translations to the bundle bundle.MustParseMessageFileBytes(translations, file) } return bundle, nil } |
Oh that is a lot of code. I added few comments to help you understand it better. In short the CreateLocalizerBundle function will read all our translation files and load them so that they can be used later. I am keeping the explanation shorter as everything in code is library method, if you are curious to know what is going inside please go through the source code of library.
Time for some magic
Now we have our bundle ready which is capable of translating a key to message of any defined language. Lets write some code to get this working.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
func main() { langFiles := []string{"en-US.yaml", "es-AR.yaml"} bundle, err := CreateLocalizerBundle(langFiles) if err != nil { fmt.Errorf("Error initialising localization, %v", err) panic(err) } // create a localizer using bundle for english language engLocalizer := i18n.NewLocalizer(bundle, "en-US") // use localizer to translate message msg, err := engLocalizer.Localize( &i18n.LocalizeConfig{ MessageID: "good_day", }, ) if err != nil { fmt.Println("ERROR: ", err) } fmt.Println(msg) // create a spanish language localizer spanishLocalizer := i18n.NewLocalizer(bundle, "es-AR") msg, err = spanishLocalizer.Localize( &i18n.LocalizeConfig{ MessageID: "good_day", }, ) if err != nil { fmt.Println("ERROR: ", err) } fmt.Println(msg) } |
|
1 2 |
=> Have a good day => Tenga un buen día |
So as you can see in the code above, whenever we need to translate a message, we can create a localizer for the current language and use the localizer for translation.
Now lets refactor our code and write a translate function which will accept a key and locale and return a message as per locale.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
var bundle *i18n.Bundle func translate(key, locale string) (string, error) { localizer := i18n.NewLocalizer(bundle, locale) msg, err := localizer.Localize( &i18n.LocalizeConfig{ MessageID: key, }, ) return msg, err } func main() { langFiles := []string{"en-US.yaml", "es-AR.yaml"} var err error bundle, err = CreateLocalizerBundle(langFiles) if err != nil { fmt.Errorf("Error initialising localization, %v", err) panic(err) } msg, err := translate("good_day", "en-US") if err != nil { fmt.Println("ERROR: ", err) } fmt.Println(msg) msg, err = translate("good_day", "es-AR") if err != nil { fmt.Println("ERROR: ", err) } fmt.Println(msg) } |
|
1 2 |
=> Have a good day => Tenga un buen día |
Now we can use our translate method to translate a key to a message of current language. The complete source code can be found here. In part-II of this post I will be writing how to use localisation in a Go API. So whenever a request will come to our application we will read the user’s current language from request header and return all response messages in that language only.
Conclusion
Thanks for reading. I hope this post helps you to implement localisation in your Go application. I have shown a very basic implementation of localisation, If you have any queries, feel free to comment here and I will love to help you out.
