This content originally appeared on DEV Community and was authored by Vic Shóstak
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
- Prepare the project for translation
- Extracting the original language
- Launch the application and playing with languages
- Afterword
Source code of the project
Yeah, for those who like to see the code first, I created a repository on GitHub:
koddr / tutorial-go-i18n
? Tutorial: An easy way to translate your Golang application
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!
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"))
}
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>
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
andtranslate.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
andactive.ru.toml
and place to the./lang
folder.That's it!
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:
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 have0
, notnull
orNone
as in JavaScript or Python. That's why if we don't specify theunread
parameter in a query, the template will be set it to0
.
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:
And, go to another language, ?? Russian:
? 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:
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! ?
Photos and videos by
- Vic Shóstak https://shostak.dev
P.S.
If you want more → write a comment below & follow me. Thanks! ?
This content originally appeared on DEV Community and was authored by Vic Shóstak
Vic Shóstak | Sciencx (2021-04-12T22:23:08+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/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.