?️ An easy way to translate your Golang application

Introduction

よう皆!✌️ Let’s talk about one of the important topics, if you’re preparing a Go application for a multilingual audience or just need support for different languages in the REST APIs.

This topic is not as simple as it seems. Beca…



Introduction

よう皆!✌️ Let’s talk about one of the important topics, if you’re preparing a Go application for a multilingual audience or just need support for different languages in the REST APIs.

This topic is not as simple as it seems. Because each language has its own special elements in terms of the word form when using numerals. For example, in Russian there are 3 different variants for items quantities:

  • one, 1 item;
  • few, 2 items;
  • many, 3+ items;

? And this must be understood when translating the application!

Don’t worry, everything will soon fall into place.



? Table of contents



Source code of the project

Yeah, for those who like to see the code first, I created a repository on GitHub:

GitHub logo

koddr
/
tutorial-go-i18n

? Tutorial: An easy way to translate your Golang application

↑ Table of contents



Prepare the project for translation

I’ve looked at many packages for this operation (including the one built into the Go core), but nicksnyder/go-i18n was the only one I enjoyed working with in my projects. We will create our demo application using this particular package.

? Please write in the comments which package for i18n you use and why!

↑ Table of contents



Website application

Yes, let’s take the Fiber web framework as the core for our application, which has excellent template support (with smoothly reload function) and is easy to write and read code.

? Please read comments in code!

// ./main.go

package main

import (
    "log"
    "strconv"

    "github.com/BurntSushi/toml"
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/template/html"
    "github.com/nicksnyder/go-i18n/v2/i18n"
    "golang.org/x/text/language"
)

func main() {
    // Create a new i18n bundle with default language.
    bundle := i18n.NewBundle(language.English)

    // Register a toml unmarshal function for i18n bundle.
    bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)

    // Load translations from toml files for non-default languages.
    bundle.MustLoadMessageFile("./lang/active.es.toml")
    bundle.MustLoadMessageFile("./lang/active.ru.toml")

    // Create a new engine by passing the template folder
    // and template extension.
    engine := html.New("./templates", ".html")

    // Reload the templates on each render, good for development.
    // Optional, default is false.
    engine.Reload(true)

    // After you created your engine, you can pass it
    // to Fiber's Views Engine.
    app := fiber.New(fiber.Config{
        Views: engine,
    })

    // Register a new route.
    app.Get("/", func(c *fiber.Ctx) error {
        lang := c.Query("lang")            // parse language from query
        accept := c.Get("Accept-Language") // or, parse from Header

        // Create a new localizer.
        localizer := i18n.NewLocalizer(bundle, lang, accept)

        // Set title message.
        helloPerson := localizer.MustLocalize(&i18n.LocalizeConfig{
            DefaultMessage: &i18n.Message{
                ID:    "HelloPerson",     // set translation ID
                Other: "Hello {{.Name}}", // set default translation
            },
            TemplateData: &fiber.Map{
                "Name": "John",
            },
        })

        // Parse and set unread count of emails.
        unreadEmailCount, _ := strconv.ParseInt(c.Query("unread"), 10, 64)

        // Config for translation of email count.
        unreadEmailConfig := &i18n.LocalizeConfig{
            DefaultMessage: &i18n.Message{
                ID:    "MyUnreadEmails",
                One:   "You have {{.PluralCount}} unread email.",
                Other: "You have {{.PluralCount}} unread emails.",
            },
            PluralCount: unreadEmailCount,
        }

        // Set localizer for unread emails.
        unreadEmails := localizer.MustLocalize(unreadEmailConfig)

        // Return data as JSON.
        if c.Query("format") == "json" {
            return c.JSON(&fiber.Map{
                "name":          helloPerson,
                "unread_emails": unreadEmails,
            })
        }

        // Return rendered template.
        return c.Render("index", fiber.Map{
            "Title":        helloPerson,
            "UnreadEmails": unreadEmails,
        })
    })

    // Start server on port 3000.
    log.Fatal(app.Listen(":3000"))
}

↑ Table of contents



Template for display

Normally, I don’t like to take pre-made CSS libraries, but for the simplicity and nice look of this demo, I took the Bootstrap 5 (v5.0.0-beta3) library:

<!-- ./templates/index.html -->

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>{{.Title}}</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-eOJMYsd53ii+scO/bJGFsiCZc+5NDVN2yr8+0RDqr0Ql0h+rP48ckxlpbzKgwra6"
      crossorigin="anonymous"
    />
    <style>
      * {
        font-family: sans-serif;
        color: #333333;
      }
    </style>
  </head>
  <body>
    <div class="col-lg-8 mx-auto p-3 py-md-5">
      <h1>{{.Title}}</h1>
      <br />
      <div class="row g-5">
        <div class="col-md-6">
          <ul class="icon-list">
            <li>{{.UnreadEmails}}</li>
          </ul>
        </div>
      </div>
      <footer class="pt-5 my-5 text-muted border-top">
        Switch to ?? <a href="/">English</a>, ??
        <a href="/?lang=es">Español</a>, ?? <a href="/?lang=ru">Русский</a>.
      </footer>
    </div>
  </body>
</html>

↑ Table of contents



Extracting the original language

  • First, install goi18n CLI:
go get -u github.com/nicksnyder/go-i18n/v2/goi18n
  • Extract all i18n.Message struct literals in our Go source files to a message file for translation (by default, active.en.toml):
goi18n extract
# ./active.en.toml

HelloPerson = "Hello {{.Name}}"

[MyUnreadEmails]
one = "You have {{.PluralCount}} unread email."
other = "You have {{.PluralCount}} unread emails."
  • Create an empty messages files for the language that you want to add (in this example, translate.es.toml and translate.ru.toml).
touch translate.es.toml translate.ru.toml
  • Run goi18n merge command with this messages files to be translated:
# For Español:
goi18n merge active.en.toml translate.es.toml

# For Russian:
goi18n merge active.en.toml translate.ru.toml
  • Open messages files and do the lines translation. As you remember from the beginning of this tutorial, the Russian language has its own peculiarities for displaying the number of objects. Therefore, I will give an example of translation for this language:
# ./translate.ru.toml

[HelloPerson]
hash = "sha1-5b49bfdad81fedaeefb224b0ffc2acc58b09cff5"
other = "Привет, {{.Name}}"

[MyUnreadEmails]
hash = "sha1-6a65d17f53981a3657db1897630e9cb069053ea8"
one = "У вас есть {{.PluralCount}} непрочитанное письмо."
other = "У вас есть {{.PluralCount}} непрочитанных писем."
few = "У вас есть {{.PluralCount}} непрочитанных письма." # <-- new row for "few" count
many = "У вас есть {{.PluralCount}} непрочитанных писем." # <-- new row for "many" count
  • When all have been translated, rename them to active.es.toml and active.ru.toml and place to the ./lang folder.

  • That’s it!

↑ Table of contents



Launch the application and playing with languages

We’re finally ready to launch our application:

go run main.go

# ┌───────────────────────────────────────────────────┐ 
# │                    Fiber v2.7.1                   │ 
# │               http://127.0.0.1:3000               │ 
# │       (bound on host 0.0.0.0 and port 3000)       │ 
# │                                                   │ 
# │ Handlers ............. 2  Processes ........... 1 │ 
# │ Prefork ....... Disabled  PID ............. 64479 │ 
# └───────────────────────────────────────────────────┘

OK. Open http://localhost:3000/ page:

go i18n en

As you can see, by default the website will always open in ?? English, as specified in the application settings.

? In Golang unset int values will always have 0, not null or None as in JavaScript or Python. That’s why if we don’t specify the unread parameter in a query, the template will be set it to 0.

Next, let’s switch language to the ?? Español. Click to the link at the page bottom and add query parameter unread with some integer:

go i18n es

And, go to another language, ?? Russian:

go i18n ru

? You can play around with the value of unread to see how the word form automatically changes after a numeral for these languages.

Also, to demonstrate how JSON works, please add format=json parameter to the query to see how Fiber web framework will give you the same content, but in JSON format:

go i18n json

↑ Table of contents



Afterword

In real web applications, you can create different variants of REST API methods to deliver translations to the frontend. But the main thing to remember is that if you do international projects, think about the specifics of the language of those countries in the first place.

And Golang will help with everything else! ?

↑ Table of contents



Photos and videos by



P.S.

If you want more → write a comment below & follow me. Thanks! ?


Print Share Comment Cite Upload Translate
APA
Vic Shóstak | Sciencx (2024-03-28T20:58:16+00:00) » ?️ An easy way to translate your Golang application. Retrieved from https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/.
MLA
" » ?️ An easy way to translate your Golang application." Vic Shóstak | Sciencx - Monday April 12, 2021, https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/
HARVARD
Vic Shóstak | Sciencx Monday April 12, 2021 » ?️ An easy way to translate your Golang application., viewed 2024-03-28T20:58:16+00:00,<https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/>
VANCOUVER
Vic Shóstak | Sciencx - » ?️ An easy way to translate your Golang application. [Internet]. [Accessed 2024-03-28T20:58:16+00:00]. Available from: https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/
CHICAGO
" » ?️ An easy way to translate your Golang application." Vic Shóstak | Sciencx - Accessed 2024-03-28T20:58:16+00:00. https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/
IEEE
" » ?️ An easy way to translate your Golang application." Vic Shóstak | Sciencx [Online]. Available: https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/. [Accessed: 2024-03-28T20:58:16+00:00]
rf:citation
» ?️ An easy way to translate your Golang application | Vic Shóstak | Sciencx | https://www.scien.cx/2021/04/12/%f0%9f%88%82%ef%b8%8f-an-easy-way-to-translate-your-golang-application/ | 2024-03-28T20:58:16+00:00
https://github.com/addpipe/simple-recorderjs-demo