Internationalization and localization with Go
Tags: go, localization, webdev, tutorial
Published: 2020-10-07 16:01:48

![i18n article logo](https://dev-to-uploads.s3.amazonaws.com/i/s3ybqpd6su1hw3bfzx77.png)

Hello Gophers!

In this article we will learn how to convert a single language web application to a multi language one.

Tools used in this article:

- [iris](https://iris-go.com)
- [go-bindata](github.com/go-bindata/go-bindata) (optional)

First of all, create a directory that hosts your application, e.g. `myapp`, and install Iris:

```bash
$ go mod init myapp
$ go get github.com/kataras/iris/v12@master
```

Follow the tutorial below.

# Internationalization and localization

Localization features provide a convenient way to retrieve strings in various languages, allowing you to easily support multiple languages within your application. Language strings are stored in files within the `./locales` directory. Within this directory there should be a subdirectory for each language supported by the application:

```bash
│   main.go
└───locales
    ├───el-GR
    │       home.yml
    ├───en-US
    │       home.yml
    └───zh-CN
            home.yml
```

The default language for your application is the first registered language.

```go
app := iris.New()

// First parameter: Glob filpath patern,
// Second variadic parameter: Optional language tags,
// the first one is the default/fallback one.
app.I18n.Load("./locales/*/*", "en-US", "el-GR", "zh-CN")
```

Or if you load all languages by:

```go
app.I18n.Load("./locales/*/*")
// Then set the default language using:
app.I18n.SetDefault("en-US")
```

## Load embedded locales

You may want to embed locales with a go-bindata tool within your application executable.

1. install a go-bindata tool, e.g.

   `$ go get -u github.com/go-bindata/go-bindata/...`

2. embed local files to your application

   `$ go-bindata  -o locales.go ./locales/...`

3. use the `LoadAssets` method to initialize and load the languages

   ^ The `AssetNames` and `Asset` functions are generated by `go-bindata`

```go
ap.I18n.LoadAssets(AssetNames, Asset, "en-US", "el-GR", "zh-CN")
```

## Defining Translations

Locale files can be written at YAML(recommended), JSON, TOML or INI form.

Each file should contain keys. Keys can have sub-keys(we call them "sections") too.

Each key's value should be of form `string` or `map` containing by its translated text (or **template**) or/and its pluralized key-values.

Iris i18n module supports **pluralization** out-of-the-box, see below.

### Fmt Style

```yaml
hi: "Hi %s!"
```

```go
ctx.Tr("Hi", "John")
// Outputs: Hi John!
```

### Template

```yaml
hi: "Hi {{.Name}}!"
```

```go
ctx.Tr("Hi", iris.Map{"Name": "John"})
// Outputs: Hi John!
```

### Pluralization

Iris i18n supports plural variables. To define a per-locale variable you must
define a new section of `Vars` key.

The acceptable keys for variables are:

- `one`
- `"=x"` where x is a number
- `"<x"`
- `other`
- `format`

Example:

```yaml
Vars:
  - Minutes:
      one: "minute"
      other: "minutes"
  - Houses:
      one: "house"
      other: "houses"
```

Then, each message can use this variable, here's how:

```yaml
# Using variables in raw string
YouLate: "You are %[1]d ${Minutes} late."
# [x] is the argument position,
# variables always have priority other fmt-style arguments,
# that's why we see [1] for houses and [2] for the string argument.
HouseCount: "%[2]s has %[1]d ${Houses}."
```

```go
ctx.Tr("YouLate", 1)
// Outputs: You are 1 minute late.
ctx.Tr("YouLate", 10)
// Outputs: You are 10 minutes late.

ctx.Tr("HouseCount", 2, "John")
// Outputs: John has 2 houses.
```

You can select what message will be shown based on a given plural count.

Except variables, each message can also have its plural form too!

Acceptable keys:

- `zero`
- `one`
- `two`
- `"=x"`
- `"<x"`
- `">x"`
- `other`

Let's create a simple plural-featured message, it can use the Minutes variable we created above too.

```yaml
FreeDay:
  "=3": "You have three days and %[2]d ${Minutes} off." # "FreeDay" 3,15
  one:  "You have a day off." # "FreeDay", 1
  other: "You have %[1]d free days." # "FreeDay", 5
```

```go
ctx.Tr("FreeDay", 3, 15)
// Outputs: You have three days and 15 minutes off.
ctx.Tr("FreeDay", 1)
// Outputs: You have a day off.
ctx.Tr("FreeDay", 5)
// Outputs: You have 5 free days.
```

Let's continue with a bit more advanced example, using template text + functions + plural + variables.

```yaml
Vars:
  - Houses:
      one: "house"
      other: "houses"
  - Gender:
      "=1": "She"
	  "=2": "He"

VarTemplatePlural:
  one: "${Gender} is awesome!"
  other: "other (${Gender}) has %[3]d ${Houses}."
  "=5": "{{call .InlineJoin .Names}} are awesome."
```

```go
const (
	female = iota + 1
	male
)

ctx.Tr("VarTemplatePlural", iris.Map{
	"PluralCount": 5,
	"Names":       []string{"John", "Peter"},
	"InlineJoin": func(arr []string) string {
		return strings.Join(arr, ", ")
	},
})
// Outputs: John, Peter are awesome

ctx.Tr("VarTemplatePlural", 1, female)
// Outputs: She is awesome!

ctx.Tr("VarTemplatePlural", 2, female, 5)
// Outputs: other (She) has 5 houses.
```

### Sections

If the key is not a reserved one (e.g. one, two...) then it acts as a sub section. The sections are separated by dot characters (`.`).

```yaml
Welcome:
  Message: "Welcome {{.Name}}"
```

```go
ctx.Tr("Welcome.Message", iris.Map{"Name": "John"})
// Outputs: Welcome John
```

## Determining The Current Locale

You may use the `context.GetLocale` method to determine the
current locale or check if the locale is a given value:

```go
func(ctx iris.Context) {
    locale := ctx.GetLocale()
    // [...]
}
```

The **Locale** interface looks like this.

```go
// Locale is the interface which returns from a
// `Localizer.GetLocale` metod.
// It serves the transltions based on "key" or format. See `GetMessage`.
type Locale interface {
    // Index returns the current locale index from the languages list.
    Index() int
    // Tag returns the full language Tag attached tothis Locale,
    // it should be uniue across different Locales.
    Tag() *language.Tag
    // Language should return the exact languagecode of this `Locale`
    //that the user provided on `New` function.
    //
    // Same as `Tag().String()` but it's static.
    Language() string
    // GetMessage should return translated text based n the given "key".
    GetMessage(key string, args ...interface{}) string
}
```

## Retrieving Translation

Use of `context.Tr` method as a shortcut to get a translated text for this request.

```go
func(ctx iris.Context) {
    text := ctx.Tr("hi", "name")
    // [...]
}
```

## Inside Views

```go
func(ctx iris.Context) {
    ctx.View("index.html", iris.Map{
        "tr": ctx.Tr,
    })
}
```

-----------

Thanks for reading 🙏 Have fun and be safe indoors 💪

> Follow my [GitHub](https://github.com/kataras) and [Twitter](https://twitter.com/MakisMaropoulos).

> Special thanks to [Damon Blais](https://dev.to/albinogeek)