Для большинства web приложений требуется локализация, но не стоит забывать о правилах языка. Например не правильно будет просто использовать ключ и подставлять выражение в зависимости от языка, тогда получится что-то вида у вас осталось 5 минут(а/ы). Для того чтобы описать такие правила можно использовать свой формат как это было раньше в symfony/translate или использовать icu формат. Для других языков есть готовые решение например icu4c.
В Golang есть несколько вариантов:
- написать свой, хорошее решение но долно, необходимо учитывать множество вариантов.
- использовать библиотеку от uber. Вы получаете зависимость от cgo, что не всегда удобно. Также там нет переводом в только общие правила для формирования даты, валюты и тп.
- есть и другие библиотеки, например go-i18n. Но там можно уже писать переводы, с возможными вариантами, поддерживается toml.
- Также есть библиотека от команды го text тоже поддерживает не все, например нет формирования даты в зависимости от локали, но как минимум нет зависимости от cgo.
package main
import (
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
func trans(){
lang, _ := language.Parse("ru")
_ = message.Set(language.Russian, "Hello {name}", catalog.String("Привет %s"))
hello := message.NewPrinter(lang).Sprintf("Hello {name}","Andrey")
//Output: Привет Andrey
}
В первой строке мы получаем данные о языке и регионе, например из url. Вторая строка использует глобальный каталог и описывает перевод по ключу, я специально для ключа использовал формат {name} обычно так описывают правила для icu, но для формата необходимо использовать формат fmt. В итоге мы получаем перевод используя ключ для перевода и переменную. Учитывайте что ключи должны быть идентичны.
package main
import (
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func main(){
lang, _ := language.Parse("ru")
_ = message.Set(language.Russian, "You are {minute} minute(s) late.",
plural.Selectf(1, "",
plural.One, "Вы опоздали на одну минуту.",
plural.Few, "Вы опоздали на %v минуты.",
plural.Other, "Вы опоздали на %v минут.",
"=1", "Вы опоздали на одну минуту.",
),
)
_ = message.Set(language.English, "You are {minute} minute(s) late.",
plural.Selectf(1, "",
plural.One, "You are 1 minute late.",
plural.Other, "You are %v minutes late."
),
)
hello := message.NewPrinter(lang).Sprintf("You are {minute} minute(s) late.",1)
// Output: Вы опоздали на одну минуту.
}
В данном варианте мы уже используем выбор перевода в зависимсоти от значения. В английском варианте используется меньше вариантов чем в русском. Можно использовать специальные значения например plural.One для вариантов заканчивающихся на 1 например 101,1001 и тп, или указывать точные значения =1,. Подробнее можно прочитать в Plural Rules.
Также данная библиотека поддерживает валюту и числа с разными форматами. Один из минусов я считаю что библиотека плохо расширяема, очень много интерфейсов и типов internal и не самый удобный интерфейс. Также можно добавить свой словарь переводов для этого достаточно реализовать метод Lookup(key string) (data string, ok bool) и заменить каталог по умолчанию.
package translate
import (
"context"
"fmt"
"log"
"gitoa.ru/go-4devs/translation"
"gitoa.ru/go-4devs/translation/arg"
"gitoa.ru/go-4devs/translation/icu"
"golang.org/x/text/language"
"golang.org/x/text/message"
"golang.org/x/text/message/catalog"
)
func main() {
err := message.Set(language.Russian, "Hello {city}", catalog.String("Привет %s"))
if err != nil {
log.Fatal(err)
}
err = message.Set(language.Russian, "It costs {cost}", catalog.String("Это стоит %.2f."))
if err != nil {
log.Fatal(err)
}
lang, err := language.Parse("ru")
if err != nil {
log.Fatal(err)
}
ctx := translation.WithLanguage(context.Background(), lang)
tr := icu.Trans(ctx, "Hello {city}", translation.WithArgs("Москва"))
fmt.Println(tr)
tr = icu.Trans(ctx, "It costs {cost}", translation.WithNumber("cost", 1000.00, arg.WithNumberFormat(arg.NumberFormatDecimal)))
fmt.Println(tr)
// Output:
// Привет Москва
// Это стоит 1 000,00.
}
Чтобы каждый раз не определять язык можно использовать библиотеку translation, можно передать язык в контексте или использовать указанный в самом методе. Она использует библиотеку text как один из провайдеров, но вы можете подменить его и использовать с другой библиотекой.
Выводы
В golang уже ведется работа над библиотеками позволяющим локализировать приложения. Есть свои минусы, одним из на мое мнение существенных - не поддерживается стандарт icu message, большинство переводов пишут не программисты и есть уже готовые инструменты работающие с данным форматом.
Дополнительные материалы
- ICU Formatting Messages
- A Step-by-Step Guide to Go Internationalization (i18n) & Localization (l10n)
- Локализация в Go с помощью базовых пакетов
- nicksnyder/go-i18n