Для большинства 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