DEV Community

Gerasimos (Makis) Maropoulos
Gerasimos (Makis) Maropoulos

Posted on

2 1

Internationalization and localization with Go

i18n article logo

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:

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

$ go mod init myapp
$ go get github.com/kataras/iris/v12@master
Enter fullscreen mode Exit fullscreen mode

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:

│   main.go
└───locales
    ├───el-GR
    │       home.yml
    ├───en-US
    │       home.yml
    └───zh-CN
            home.yml
Enter fullscreen mode Exit fullscreen mode

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

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")
Enter fullscreen mode Exit fullscreen mode

Or if you load all languages by:

app.I18n.Load("./locales/*/*")
// Then set the default language using:
app.I18n.SetDefault("en-US")
Enter fullscreen mode Exit fullscreen mode

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/...

  1. embed local files to your application

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

  1. use the LoadAssets method to initialize and load the languages

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

ap.I18n.LoadAssets(AssetNames, Asset, "en-US", "el-GR", "zh-CN")
Enter fullscreen mode Exit fullscreen mode

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

hi: "Hi %s!"
Enter fullscreen mode Exit fullscreen mode
ctx.Tr("Hi", "John")
// Outputs: Hi John!
Enter fullscreen mode Exit fullscreen mode

Template

hi: "Hi {{.Name}}!"
Enter fullscreen mode Exit fullscreen mode
ctx.Tr("Hi", iris.Map{"Name": "John"})
// Outputs: Hi John!
Enter fullscreen mode Exit fullscreen mode

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:

Vars:
  - Minutes:
      one: "minute"
      other: "minutes"
  - Houses:
      one: "house"
      other: "houses"
Enter fullscreen mode Exit fullscreen mode

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

# 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}."
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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.

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
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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

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."
Enter fullscreen mode Exit fullscreen mode
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.
Enter fullscreen mode Exit fullscreen mode

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 (.).

Welcome:
  Message: "Welcome {{.Name}}"
Enter fullscreen mode Exit fullscreen mode
ctx.Tr("Welcome.Message", iris.Map{"Name": "John"})
// Outputs: Welcome John
Enter fullscreen mode Exit fullscreen mode

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:

func(ctx iris.Context) {
    locale := ctx.GetLocale()
    // [...]
}
Enter fullscreen mode Exit fullscreen mode

The Locale interface looks like this.

// 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
}
Enter fullscreen mode Exit fullscreen mode

Retrieving Translation

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

func(ctx iris.Context) {
    text := ctx.Tr("hi", "name")
    // [...]
}
Enter fullscreen mode Exit fullscreen mode

Inside Views

func(ctx iris.Context) {
    ctx.View("index.html", iris.Map{
        "tr": ctx.Tr,
    })
}
Enter fullscreen mode Exit fullscreen mode

Thanks for reading 🙏 Have fun and be safe indoors 💪

Follow my GitHub and Twitter.

Special thanks to Damon Blais

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up