pax_global_header00006660000000000000000000000064135623414000014510gustar00rootroot0000000000000052 comment=f87b1403479a348651dbf5f07f5cc6e5fcf07008 universal-translator-0.17.0/000077500000000000000000000000001356234140000157745ustar00rootroot00000000000000universal-translator-0.17.0/.gitignore000066400000000000000000000004301356234140000177610ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.coverprofileuniversal-translator-0.17.0/.travis.yml000066400000000000000000000010271356234140000201050ustar00rootroot00000000000000language: go go: - 1.13.4 - tip matrix: allow_failures: - go: tip notifications: email: recipients: dean.karn@gmail.com on_success: change on_failure: always before_install: - go install github.com/mattn/goveralls # Only clone the most recent commit. git: depth: 1 script: - go test -v -race -covermode=atomic -coverprofile=coverage.coverprofile ./... after_success: | [ $TRAVIS_GO_VERSION = 1.13.4 ] && goveralls -coverprofile=coverage.coverprofile -service travis-ci -repotoken $COVERALLS_TOKENuniversal-translator-0.17.0/LICENSE000066400000000000000000000020701356234140000170000ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Go Playground Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. universal-translator-0.17.0/README.md000066400000000000000000000110771356234140000172610ustar00rootroot00000000000000## universal-translator ![Project status](https://img.shields.io/badge/version-0.17.0-green.svg) [![Build Status](https://travis-ci.org/go-playground/universal-translator.svg?branch=master)](https://travis-ci.org/go-playground/universal-translator) [![Coverage Status](https://coveralls.io/repos/github/go-playground/universal-translator/badge.svg)](https://coveralls.io/github/go-playground/universal-translator) [![Go Report Card](https://goreportcard.com/badge/github.com/go-playground/universal-translator)](https://goreportcard.com/report/github.com/go-playground/universal-translator) [![GoDoc](https://godoc.org/github.com/go-playground/universal-translator?status.svg)](https://godoc.org/github.com/go-playground/universal-translator) ![License](https://img.shields.io/dub/l/vibe-d.svg) [![Gitter](https://badges.gitter.im/go-playground/universal-translator.svg)](https://gitter.im/go-playground/universal-translator?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) Universal Translator is an i18n Translator for Go/Golang using CLDR data + pluralization rules Why another i18n library? -------------------------- Because none of the plural rules seem to be correct out there, including the previous implementation of this package, so I took it upon myself to create [locales](https://github.com/go-playground/locales) for everyone to use; this package is a thin wrapper around [locales](https://github.com/go-playground/locales) in order to store and translate text for use in your applications. Features -------- - [x] Rules generated from the [CLDR](http://cldr.unicode.org/index/downloads) data, v30.0.3 - [x] Contains Cardinal, Ordinal and Range Plural Rules - [x] Contains Month, Weekday and Timezone translations built in - [x] Contains Date & Time formatting functions - [x] Contains Number, Currency, Accounting and Percent formatting functions - [x] Supports the "Gregorian" calendar only ( my time isn't unlimited, had to draw the line somewhere ) - [x] Support loading translations from files - [x] Exporting translations to file(s), mainly for getting them professionally translated - [ ] Code Generation for translation files -> Go code.. i.e. after it has been professionally translated - [ ] Tests for all languages, I need help with this, please see [here](https://github.com/go-playground/locales/issues/1) Installation ----------- Use go get ```shell go get github.com/go-playground/universal-translator ``` Usage & Documentation ------- Please see https://godoc.org/github.com/go-playground/universal-translator for usage docs ##### Examples: - [Basic](https://github.com/go-playground/universal-translator/tree/master/_examples/basic) - [Full - no files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-no-files) - [Full - with files](https://github.com/go-playground/universal-translator/tree/master/_examples/full-with-files) File formatting -------------- All types, Plain substitution, Cardinal, Ordinal and Range translations can all be contained withing the same file(s); they are only separated for easy viewing. ##### Examples: - [Formats](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats) ##### Basic Makeup NOTE: not all fields are needed for all translation types, see [examples](https://github.com/go-playground/universal-translator/tree/master/_examples/file-formats) ```json { "locale": "en", "key": "days-left", "trans": "You have {0} day left.", "type": "Cardinal", "rule": "One", "override": false } ``` |Field|Description| |---|---| |locale|The locale for which the translation is for.| |key|The translation key that will be used to store and lookup each translation; normally it is a string or integer.| |trans|The actual translation text.| |type|The type of translation Cardinal, Ordinal, Range or "" for a plain substitution(not required to be defined if plain used)| |rule|The plural rule for which the translation is for eg. One, Two, Few, Many or Other.(not required to be defined if plain used)| |override|If you wish to override an existing translation that has already been registered, set this to 'true'. 99% of the time there is no need to define it.| Help With Tests --------------- To anyone interesting in helping or contributing, I sure could use some help creating tests for each language. Please see issue [here](https://github.com/go-playground/locales/issues/1) for details. License ------ Distributed under MIT License, please see license file in code for more details. universal-translator-0.17.0/_examples/000077500000000000000000000000001356234140000177515ustar00rootroot00000000000000universal-translator-0.17.0/_examples/basic/000077500000000000000000000000001356234140000210325ustar00rootroot00000000000000universal-translator-0.17.0/_examples/basic/main.go000066400000000000000000000055631356234140000223160ustar00rootroot00000000000000package main import ( "fmt" "github.com/go-playground/locales" "github.com/go-playground/locales/en" "github.com/go-playground/locales/en_CA" "github.com/go-playground/locales/fr" "github.com/go-playground/locales/nl" "github.com/go-playground/universal-translator" ) // only one instance as translators within are shared + goroutine safe var universalTraslator *ut.UniversalTranslator func main() { // NOTE: this example is omitting a lot of error checking for brevity e := en.New() universalTraslator = ut.New(e, e, en_CA.New(), nl.New(), fr.New()) en, _ := universalTraslator.GetTranslator("en") // generally used after parsing an http 'Accept-Language' header // and this will try to find a matching locale you support or // fallback locale. // en, _ := ut.FindTranslator([]string{"en", "en_CA", "nl"}) // this will help fmt.Println("Cardinal Plural Rules:", en.PluralsCardinal()) fmt.Println("Ordinal Plural Rules:", en.PluralsOrdinal()) fmt.Println("Range Plural Rules:", en.PluralsRange()) // add basic language only translations // last param indicates if it's ok to override the translation if one already exists en.Add("welcome", "Welcome {0} to our test", false) // add language translations dependant on cardinal plural rules en.AddCardinal("days", "You have {0} day left to register", locales.PluralRuleOne, false) en.AddCardinal("days", "You have {0} days left to register", locales.PluralRuleOther, false) // add language translations dependant on ordinal plural rules en.AddOrdinal("day-of-month", "{0}st", locales.PluralRuleOne, false) en.AddOrdinal("day-of-month", "{0}nd", locales.PluralRuleTwo, false) en.AddOrdinal("day-of-month", "{0}rd", locales.PluralRuleFew, false) en.AddOrdinal("day-of-month", "{0}th", locales.PluralRuleOther, false) // add language translations dependant on range plural rules // NOTE: only one plural rule for range in 'en' locale en.AddRange("between", "It's {0}-{1} days away", locales.PluralRuleOther, false) // now lets use the translations we just added, in the same order we added them fmt.Println(en.T("welcome", "Joeybloggs")) fmt.Println(en.C("days", 1, 0, en.FmtNumber(1, 0))) // you'd normally have variables defined for 1 and 0 fmt.Println(en.C("days", 2, 0, en.FmtNumber(2, 0))) fmt.Println(en.C("days", 10456.25, 2, en.FmtNumber(10456.25, 2))) fmt.Println(en.O("day-of-month", 1, 0, en.FmtNumber(1, 0))) fmt.Println(en.O("day-of-month", 2, 0, en.FmtNumber(2, 0))) fmt.Println(en.O("day-of-month", 3, 0, en.FmtNumber(3, 0))) fmt.Println(en.O("day-of-month", 4, 0, en.FmtNumber(4, 0))) fmt.Println(en.O("day-of-month", 10456.25, 0, en.FmtNumber(10456.25, 0))) fmt.Println(en.R("between", 0, 0, 1, 0, en.FmtNumber(0, 0), en.FmtNumber(1, 0))) fmt.Println(en.R("between", 1, 0, 2, 0, en.FmtNumber(1, 0), en.FmtNumber(2, 0))) fmt.Println(en.R("between", 1, 0, 100, 0, en.FmtNumber(1, 0), en.FmtNumber(100, 0))) } universal-translator-0.17.0/_examples/file-formats/000077500000000000000000000000001356234140000223415ustar00rootroot00000000000000universal-translator-0.17.0/_examples/file-formats/cardinal.json000066400000000000000000000005111356234140000250060ustar00rootroot00000000000000[ { "locale": "en", "key": "cardinal_test", "trans": "You have {0} day left.", "type": "Cardinal", "rule": "One" }, { "locale": "en", "key": "cardinal_test", "trans": "You have {0} days left.", "type": "Cardinal", "rule": "Other" } ]universal-translator-0.17.0/_examples/file-formats/ordinal.json000066400000000000000000000010341356234140000246620ustar00rootroot00000000000000[ { "locale": "en", "key": "day", "trans": "{0}st", "type": "Ordinal", "rule": "One" }, { "locale": "en", "key": "day", "trans": "{0}nd", "type": "Ordinal", "rule": "Two" }, { "locale": "en", "key": "day", "trans": "{0}rd", "type": "Ordinal", "rule": "Few" }, { "locale": "en", "key": "day", "trans": "{0}th", "type": "Ordinal", "rule": "Other" } ]universal-translator-0.17.0/_examples/file-formats/plain-substitution.json000066400000000000000000000007661356234140000271220ustar00rootroot00000000000000[ { "locale": "en", "key": "test_trans4", "trans": "{0}{1}" }, { "locale": "en", "key": "test_trans", "trans": "Welcome {0} to the {1}." }, { "locale": "en", "key": -1, "trans": "Welcome {0}" }, { "locale": "en", "key": "test_trans2", "trans": "{0} to the {1}." }, { "locale": "en", "key": "test_trans3", "trans": "Welcome {0} to the {1}" } ]universal-translator-0.17.0/_examples/file-formats/range.json000066400000000000000000000004651356234140000243350ustar00rootroot00000000000000[ { "locale": "nl", "key": "day", "trans": "er {0}-{1} dag vertrokken", "type": "Range", "rule": "One" }, { "locale": "nl", "key": "day", "trans": "er zijn {0}-{1} dagen over", "type": "Range", "rule": "Other" } ]universal-translator-0.17.0/_examples/full-no-files/000077500000000000000000000000001356234140000224255ustar00rootroot00000000000000universal-translator-0.17.0/_examples/full-no-files/home.tmpl000066400000000000000000000033311356234140000242530ustar00rootroot00000000000000{{ define "home" }} Home

Locale: {{ .Trans.Locale }}

Trans1: {{ .Trans.C "days-left" 1 0 "1" }}

Trans2: {{ .Trans.C "days-left" 2 0 "2" }}

FmtNumber Positive: {{ .Trans.FmtNumber .PositiveNum 2 }}

FmtNumber Negative: {{ .Trans.FmtNumber .NegativeNum 2 }}

FmtPercent Negative: {{ .Trans.FmtPercent .Percent 2 }}

FmtCurrency Negative: {{ .Trans.FmtCurrency .PositiveNum 2 .Trans.Currency }}

FmtCurrency Negative: {{ .Trans.FmtCurrency .NegativeNum 2 .Trans.Currency }}

FmtAccounting Negative: {{ .Trans.FmtAccounting .PositiveNum 2 .Trans.Currency }}

FmtAccounting Negative: {{ .Trans.FmtAccounting .NegativeNum 2 .Trans.Currency }}

FmtDateShort: {{ .Trans.FmtDateShort .Now }}

FmtDateMedium: {{ .Trans.FmtDateMedium .Now }}

FmtDateLong: {{ .Trans.FmtDateLong .Now }}

FmtDateFull: {{ .Trans.FmtDateFull .Now }}

FmtTimeShort: {{ .Trans.FmtTimeShort .Now }}

FmtTimeMedium: {{ .Trans.FmtTimeMedium .Now }}

FmtTimeLong: {{ .Trans.FmtTimeLong .Now }}

FmtTimeFull: {{ .Trans.FmtTimeFull .Now }}

MonthsAbbreviated: {{ .Trans.MonthsAbbreviated }}

MonthsNarrow: {{ .Trans.MonthsNarrow }}

MonthsWide: {{ .Trans.MonthsWide }}

WeekdaysAbbreviated: {{ .Trans.WeekdaysAbbreviated }}

WeekdaysNarrow: {{ .Trans.WeekdaysNarrow }}

WeekdaysShort: {{ .Trans.WeekdaysShort }}

WeekdaysWide: {{ .Trans.WeekdaysWide }}

{{ end }}universal-translator-0.17.0/_examples/full-no-files/main.go000066400000000000000000000133411356234140000237020ustar00rootroot00000000000000package main import ( "context" "html/template" "log" "net/http" "time" "github.com/go-playground/locales" "github.com/go-playground/locales/currency" "github.com/go-playground/locales/en" "github.com/go-playground/locales/fr" "github.com/go-playground/pure/v5" "github.com/go-playground/pure/v5/_examples/middleware/logging-recovery" "github.com/go-playground/universal-translator" ) var ( tmpls *template.Template utrans *ut.UniversalTranslator transKey = struct { name string }{ name: "transKey", } ) // Translator wraps ut.Translator in order to handle errors transparently // it is totally optional but recommended as it can now be used directly in // templates and nobody can add translations where they're not supposed to. type Translator interface { locales.Translator // creates the translation for the locale given the 'key' and params passed in. // wraps ut.Translator.T to handle errors T(key interface{}, params ...string) string // creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments // and param passed in. // wraps ut.Translator.C to handle errors C(key interface{}, num float64, digits uint64, param string) string // creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments // and param passed in. // wraps ut.Translator.O to handle errors O(key interface{}, num float64, digits uint64, param string) string // creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and // 'digit2' arguments and 'param1' and 'param2' passed in // wraps ut.Translator.R to handle errors R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string // Currency returns the type used by the given locale. Currency() currency.Type } // implements Translator interface definition above. type translator struct { locales.Translator trans ut.Translator } var _ Translator = (*translator)(nil) func (t *translator) T(key interface{}, params ...string) string { s, err := t.trans.T(key, params...) if err != nil { log.Printf("issue translating key: '%v' error: '%s'", key, err) } return s } func (t *translator) C(key interface{}, num float64, digits uint64, param string) string { s, err := t.trans.C(key, num, digits, param) if err != nil { log.Printf("issue translating cardinal key: '%v' error: '%s'", key, err) } return s } func (t *translator) O(key interface{}, num float64, digits uint64, param string) string { s, err := t.trans.C(key, num, digits, param) if err != nil { log.Printf("issue translating ordinal key: '%v' error: '%s'", key, err) } return s } func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string { s, err := t.trans.R(key, num1, digits1, num2, digits2, param1, param2) if err != nil { log.Printf("issue translating range key: '%v' error: '%s'", key, err) } return s } func (t *translator) Currency() currency.Type { // choose your own locale. The reason it isn't mapped for you is because many // countries have multiple currencies; it's up to you and you're application how // and which currencies to use. I recommend adding a function it to to your custon translator // interface like defined above. switch t.Locale() { case "en": return currency.USD case "fr": return currency.EUR default: return currency.USD } } func main() { en := en.New() utrans = ut.New(en, en, fr.New()) setup() tmpls, _ = template.ParseFiles("home.tmpl") r := pure.New() r.Use(middleware.LoggingAndRecovery(true), translatorMiddleware) r.Get("/", home) log.Println("Running on Port :8080") log.Println("Try me with URL http://localhost:8080/?locale=en") log.Println("and then http://localhost:8080/?locale=fr") http.ListenAndServe(":8080", r.Serve()) } func home(w http.ResponseWriter, r *http.Request) { // get locale translator ( could be wrapped into a helper function ) t := r.Context().Value(transKey).(Translator) s := struct { Trans Translator Now time.Time PositiveNum float64 NegativeNum float64 Percent float64 }{ Trans: t, Now: time.Now(), PositiveNum: 1234576.45, NegativeNum: -35900394.34, Percent: 96.76, } if err := tmpls.ExecuteTemplate(w, "home", s); err != nil { log.Fatal(err) } } func translatorMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // there are many ways to check, this is just checking for query param & // Accept-Language header but can be expanded to Cookie's etc.... params := r.URL.Query() locale := params.Get("locale") var t ut.Translator if len(locale) > 0 { var found bool if t, found = utrans.GetTranslator(locale); found { goto END } } // get and parse the "Accept-Language" http header and return an array t, _ = utrans.FindTranslator(pure.AcceptedLanguages(r)...) END: // I would normally wrap ut.Translator with one with my own functions in order // to handle errors and be able to use all functions from translator within the templates. r = r.WithContext(context.WithValue(r.Context(), transKey, &translator{trans: t, Translator: t.(locales.Translator)})) next(w, r) } } func setup() { en, _ := utrans.FindTranslator("en") en.AddCardinal("days-left", "There is {0} day left", locales.PluralRuleOne, false) en.AddCardinal("days-left", "There are {0} days left", locales.PluralRuleOther, false) fr, _ := utrans.FindTranslator("fr") fr.AddCardinal("days-left", "Il reste {0} jour", locales.PluralRuleOne, false) fr.AddCardinal("days-left", "Il reste {0} jours", locales.PluralRuleOther, false) err := utrans.VerifyTranslations() if err != nil { log.Fatal(err) } } universal-translator-0.17.0/_examples/full-with-files/000077500000000000000000000000001356234140000227645ustar00rootroot00000000000000universal-translator-0.17.0/_examples/full-with-files/home.tmpl000066400000000000000000000033311356234140000246120ustar00rootroot00000000000000{{ define "home" }} Home

Locale: {{ .Trans.Locale }}

Trans1: {{ .Trans.C "days-left" 1 0 "1" }}

Trans2: {{ .Trans.C "days-left" 2 0 "2" }}

FmtNumber Positive: {{ .Trans.FmtNumber .PositiveNum 2 }}

FmtNumber Negative: {{ .Trans.FmtNumber .NegativeNum 2 }}

FmtPercent Negative: {{ .Trans.FmtPercent .Percent 2 }}

FmtCurrency Negative: {{ .Trans.FmtCurrency .PositiveNum 2 .Trans.Currency }}

FmtCurrency Negative: {{ .Trans.FmtCurrency .NegativeNum 2 .Trans.Currency }}

FmtAccounting Negative: {{ .Trans.FmtAccounting .PositiveNum 2 .Trans.Currency }}

FmtAccounting Negative: {{ .Trans.FmtAccounting .NegativeNum 2 .Trans.Currency }}

FmtDateShort: {{ .Trans.FmtDateShort .Now }}

FmtDateMedium: {{ .Trans.FmtDateMedium .Now }}

FmtDateLong: {{ .Trans.FmtDateLong .Now }}

FmtDateFull: {{ .Trans.FmtDateFull .Now }}

FmtTimeShort: {{ .Trans.FmtTimeShort .Now }}

FmtTimeMedium: {{ .Trans.FmtTimeMedium .Now }}

FmtTimeLong: {{ .Trans.FmtTimeLong .Now }}

FmtTimeFull: {{ .Trans.FmtTimeFull .Now }}

MonthsAbbreviated: {{ .Trans.MonthsAbbreviated }}

MonthsNarrow: {{ .Trans.MonthsNarrow }}

MonthsWide: {{ .Trans.MonthsWide }}

WeekdaysAbbreviated: {{ .Trans.WeekdaysAbbreviated }}

WeekdaysNarrow: {{ .Trans.WeekdaysNarrow }}

WeekdaysShort: {{ .Trans.WeekdaysShort }}

WeekdaysWide: {{ .Trans.WeekdaysWide }}

{{ end }}universal-translator-0.17.0/_examples/full-with-files/main.go000066400000000000000000000126361356234140000242470ustar00rootroot00000000000000package main import ( "context" "html/template" "log" "net/http" "time" "github.com/go-playground/locales" "github.com/go-playground/locales/currency" "github.com/go-playground/locales/en" "github.com/go-playground/locales/fr" "github.com/go-playground/pure/v5" "github.com/go-playground/pure/v5/_examples/middleware/logging-recovery" "github.com/go-playground/universal-translator" ) var ( tmpls *template.Template utrans *ut.UniversalTranslator transKey = struct { name string }{ name: "transKey", } ) // Translator wraps ut.Translator in order to handle errors transparently // it is totally optional but recommended as it can now be used directly in // templates and nobody can add translations where they're not supposed to. type Translator interface { locales.Translator // creates the translation for the locale given the 'key' and params passed in. // wraps ut.Translator.T to handle errors T(key interface{}, params ...string) string // creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments // and param passed in. // wraps ut.Translator.C to handle errors C(key interface{}, num float64, digits uint64, param string) string // creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments // and param passed in. // wraps ut.Translator.O to handle errors O(key interface{}, num float64, digits uint64, param string) string // creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and // 'digit2' arguments and 'param1' and 'param2' passed in // wraps ut.Translator.R to handle errors R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string // Currency returns the type used by the given locale. Currency() currency.Type } // implements Translator interface definition above. type translator struct { locales.Translator trans ut.Translator } var _ Translator = (*translator)(nil) func (t *translator) T(key interface{}, params ...string) string { s, err := t.trans.T(key, params...) if err != nil { log.Printf("issue translating key: '%v' error: '%s'", key, err) } return s } func (t *translator) C(key interface{}, num float64, digits uint64, param string) string { s, err := t.trans.C(key, num, digits, param) if err != nil { log.Printf("issue translating cardinal key: '%v' error: '%s'", key, err) } return s } func (t *translator) O(key interface{}, num float64, digits uint64, param string) string { s, err := t.trans.C(key, num, digits, param) if err != nil { log.Printf("issue translating ordinal key: '%v' error: '%s'", key, err) } return s } func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) string { s, err := t.trans.R(key, num1, digits1, num2, digits2, param1, param2) if err != nil { log.Printf("issue translating range key: '%v' error: '%s'", key, err) } return s } func (t *translator) Currency() currency.Type { // choose your own locale. The reason it isn't mapped for you is because many // countries have multiple currencies; it's up to you and you're application how // and which currencies to use. I recommend adding a function it to to your custon translator // interface like defined above. switch t.Locale() { case "en": return currency.USD case "fr": return currency.EUR default: return currency.USD } } func main() { en := en.New() utrans = ut.New(en, en, fr.New()) setup() tmpls, _ = template.ParseFiles("home.tmpl") r := pure.New() r.Use(middleware.LoggingAndRecovery(true), translatorMiddleware) r.Get("/", home) log.Println("Running on Port :8080") log.Println("Try me with URL http://localhost:8080/?locale=en") log.Println("and then http://localhost:8080/?locale=fr") http.ListenAndServe(":8080", r.Serve()) } func home(w http.ResponseWriter, r *http.Request) { // get locale translator ( could be wrapped into a helper function ) t := r.Context().Value(transKey).(Translator) s := struct { Trans Translator Now time.Time PositiveNum float64 NegativeNum float64 Percent float64 }{ Trans: t, Now: time.Now(), PositiveNum: 1234576.45, NegativeNum: -35900394.34, Percent: 96.76, } if err := tmpls.ExecuteTemplate(w, "home", s); err != nil { log.Fatal(err) } } func translatorMiddleware(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // there are many ways to check, this is just checking for query param & // Accept-Language header but can be expanded to Cookie's etc.... params := r.URL.Query() locale := params.Get("locale") var t ut.Translator if len(locale) > 0 { var found bool if t, found = utrans.GetTranslator(locale); found { goto END } } // get and parse the "Accept-Language" http header and return an array t, _ = utrans.FindTranslator(pure.AcceptedLanguages(r)...) END: // I would normally wrap ut.Translator with one with my own functions in order // to handle errors and be able to use all functions from translator within the templates. r = r.WithContext(context.WithValue(r.Context(), transKey, &translator{trans: t, Translator: t.(locales.Translator)})) next(w, r) } } func setup() { err := utrans.Import(ut.FormatJSON, "translations") if err != nil { log.Fatal(err) } err = utrans.VerifyTranslations() if err != nil { log.Fatal(err) } } universal-translator-0.17.0/_examples/full-with-files/translations/000077500000000000000000000000001356234140000255055ustar00rootroot00000000000000universal-translator-0.17.0/_examples/full-with-files/translations/en/000077500000000000000000000000001356234140000261075ustar00rootroot00000000000000universal-translator-0.17.0/_examples/full-with-files/translations/en/home.json000066400000000000000000000005001356234140000277250ustar00rootroot00000000000000[ { "locale": "en", "key": "days-left", "trans": "There is {0} day left", "type": "Cardinal", "rule": "One" }, { "locale": "en", "key": "days-left", "trans": "There are {0} days left", "type": "Cardinal", "rule": "Other" } ]universal-translator-0.17.0/_examples/full-with-files/translations/fr/000077500000000000000000000000001356234140000261145ustar00rootroot00000000000000universal-translator-0.17.0/_examples/full-with-files/translations/fr/home.json000066400000000000000000000004671356234140000277460ustar00rootroot00000000000000[ { "locale": "fr", "key": "days-left", "trans": "Il reste {0} jour", "type": "Cardinal", "rule": "One" }, { "locale": "fr", "key": "days-left", "trans": "Il reste {0} jours", "type": "Cardinal", "rule": "Other" } ]universal-translator-0.17.0/benchmarks_test.go000066400000000000000000000037341356234140000215060ustar00rootroot00000000000000package ut import ( "testing" "github.com/go-playground/locales/en" ) func BenchmarkBasicTranslation(b *testing.B) { en := en.New() ut := New(en, en) loc, found := ut.FindTranslator("en") if !found { b.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string expected error override bool }{ { key: "welcome", trans: "Welcome to the site", expected: nil, }, { key: "welcome-user", trans: "Welcome to the site {0}", expected: nil, }, { key: "welcome-user2", trans: "Welcome to the site {0}, your location is {1}", expected: nil, }, } for _, tt := range translations { if err := loc.Add(tt.key, tt.trans, tt.override); err != nil { b.Fatalf("adding translation '%s' failed with key '%s'", tt.trans, tt.key) } } var err error b.ResetTimer() b.Run("", func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err = loc.T("welcome"); err != nil { b.Error(err) } } }) b.Run("Parallel", func(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err = loc.T("welcome"); err != nil { b.Error(err) } } }) }) b.Run("With1Param", func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err = loc.T("welcome-user", "Joeybloggs"); err != nil { b.Error(err) } } }) b.Run("ParallelWith1Param", func(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err = loc.T("welcome-user", "Joeybloggs"); err != nil { b.Error(err) } } }) }) b.Run("With2Param", func(b *testing.B) { for i := 0; i < b.N; i++ { if _, err = loc.T("welcome-user2", "Joeybloggs", "/dev/tty0"); err != nil { b.Error(err) } } }) b.Run("ParallelWith2Param", func(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { if _, err = loc.T("welcome-user2", "Joeybloggs", "/dev/tty0"); err != nil { b.Error(err) } } }) }) } universal-translator-0.17.0/errors.go000066400000000000000000000111021356234140000176320ustar00rootroot00000000000000package ut import ( "errors" "fmt" "github.com/go-playground/locales" ) var ( // ErrUnknowTranslation indicates the translation could not be found ErrUnknowTranslation = errors.New("Unknown Translation") ) var _ error = new(ErrConflictingTranslation) var _ error = new(ErrRangeTranslation) var _ error = new(ErrOrdinalTranslation) var _ error = new(ErrCardinalTranslation) var _ error = new(ErrMissingPluralTranslation) var _ error = new(ErrExistingTranslator) // ErrExistingTranslator is the error representing a conflicting translator type ErrExistingTranslator struct { locale string } // Error returns ErrExistingTranslator's internal error text func (e *ErrExistingTranslator) Error() string { return fmt.Sprintf("error: conflicting translator for locale '%s'", e.locale) } // ErrConflictingTranslation is the error representing a conflicting translation type ErrConflictingTranslation struct { locale string key interface{} rule locales.PluralRule text string } // Error returns ErrConflictingTranslation's internal error text func (e *ErrConflictingTranslation) Error() string { if _, ok := e.key.(string); !ok { return fmt.Sprintf("error: conflicting key '%#v' rule '%s' with text '%s' for locale '%s', value being ignored", e.key, e.rule, e.text, e.locale) } return fmt.Sprintf("error: conflicting key '%s' rule '%s' with text '%s' for locale '%s', value being ignored", e.key, e.rule, e.text, e.locale) } // ErrRangeTranslation is the error representing a range translation error type ErrRangeTranslation struct { text string } // Error returns ErrRangeTranslation's internal error text func (e *ErrRangeTranslation) Error() string { return e.text } // ErrOrdinalTranslation is the error representing an ordinal translation error type ErrOrdinalTranslation struct { text string } // Error returns ErrOrdinalTranslation's internal error text func (e *ErrOrdinalTranslation) Error() string { return e.text } // ErrCardinalTranslation is the error representing a cardinal translation error type ErrCardinalTranslation struct { text string } // Error returns ErrCardinalTranslation's internal error text func (e *ErrCardinalTranslation) Error() string { return e.text } // ErrMissingPluralTranslation is the error signifying a missing translation given // the locales plural rules. type ErrMissingPluralTranslation struct { locale string key interface{} rule locales.PluralRule translationType string } // Error returns ErrMissingPluralTranslation's internal error text func (e *ErrMissingPluralTranslation) Error() string { if _, ok := e.key.(string); !ok { return fmt.Sprintf("error: missing '%s' plural rule '%s' for translation with key '%#v' and locale '%s'", e.translationType, e.rule, e.key, e.locale) } return fmt.Sprintf("error: missing '%s' plural rule '%s' for translation with key '%s' and locale '%s'", e.translationType, e.rule, e.key, e.locale) } // ErrMissingBracket is the error representing a missing bracket in a translation // eg. This is a {0 <-- missing ending '}' type ErrMissingBracket struct { locale string key interface{} text string } // Error returns ErrMissingBracket error message func (e *ErrMissingBracket) Error() string { return fmt.Sprintf("error: missing bracket '{}', in translation. locale: '%s' key: '%v' text: '%s'", e.locale, e.key, e.text) } // ErrBadParamSyntax is the error representing a bad parameter definition in a translation // eg. This is a {must-be-int} type ErrBadParamSyntax struct { locale string param string key interface{} text string } // Error returns ErrBadParamSyntax error message func (e *ErrBadParamSyntax) Error() string { return fmt.Sprintf("error: bad parameter syntax, missing parameter '%s' in translation. locale: '%s' key: '%v' text: '%s'", e.param, e.locale, e.key, e.text) } // import/export errors // ErrMissingLocale is the error representing an expected locale that could // not be found aka locale not registered with the UniversalTranslator Instance type ErrMissingLocale struct { locale string } // Error returns ErrMissingLocale's internal error text func (e *ErrMissingLocale) Error() string { return fmt.Sprintf("error: locale '%s' not registered.", e.locale) } // ErrBadPluralDefinition is the error representing an incorrect plural definition // usually found within translations defined within files during the import process. type ErrBadPluralDefinition struct { tl translation } // Error returns ErrBadPluralDefinition's internal error text func (e *ErrBadPluralDefinition) Error() string { return fmt.Sprintf("error: bad plural definition '%#v'", e.tl) } universal-translator-0.17.0/go.mod000066400000000000000000000001601356234140000170770ustar00rootroot00000000000000module github.com/go-playground/universal-translator go 1.13 require github.com/go-playground/locales v0.13.0 universal-translator-0.17.0/go.sum000066400000000000000000000005661356234140000171360ustar00rootroot00000000000000github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= universal-translator-0.17.0/import_export.go000066400000000000000000000131721356234140000212420ustar00rootroot00000000000000package ut import ( "encoding/json" "fmt" "io/ioutil" "os" "path/filepath" "io" "github.com/go-playground/locales" ) type translation struct { Locale string `json:"locale"` Key interface{} `json:"key"` // either string or integer Translation string `json:"trans"` PluralType string `json:"type,omitempty"` PluralRule string `json:"rule,omitempty"` OverrideExisting bool `json:"override,omitempty"` } const ( cardinalType = "Cardinal" ordinalType = "Ordinal" rangeType = "Range" ) // ImportExportFormat is the format of the file import or export type ImportExportFormat uint8 // supported Export Formats const ( FormatJSON ImportExportFormat = iota ) // Export writes the translations out to a file on disk. // // NOTE: this currently only works with string or int translations keys. func (t *UniversalTranslator) Export(format ImportExportFormat, dirname string) error { _, err := os.Stat(dirname) fmt.Println(dirname, err, os.IsNotExist(err)) if err != nil { if !os.IsNotExist(err) { return err } if err = os.MkdirAll(dirname, 0744); err != nil { return err } } // build up translations var trans []translation var b []byte var ext string for _, locale := range t.translators { for k, v := range locale.(*translator).translations { trans = append(trans, translation{ Locale: locale.Locale(), Key: k, Translation: v.text, }) } for k, pluralTrans := range locale.(*translator).cardinalTanslations { for i, plural := range pluralTrans { // leave enough for all plural rules // but not all are set for all languages. if plural == nil { continue } trans = append(trans, translation{ Locale: locale.Locale(), Key: k.(string), Translation: plural.text, PluralType: cardinalType, PluralRule: locales.PluralRule(i).String(), }) } } for k, pluralTrans := range locale.(*translator).ordinalTanslations { for i, plural := range pluralTrans { // leave enough for all plural rules // but not all are set for all languages. if plural == nil { continue } trans = append(trans, translation{ Locale: locale.Locale(), Key: k.(string), Translation: plural.text, PluralType: ordinalType, PluralRule: locales.PluralRule(i).String(), }) } } for k, pluralTrans := range locale.(*translator).rangeTanslations { for i, plural := range pluralTrans { // leave enough for all plural rules // but not all are set for all languages. if plural == nil { continue } trans = append(trans, translation{ Locale: locale.Locale(), Key: k.(string), Translation: plural.text, PluralType: rangeType, PluralRule: locales.PluralRule(i).String(), }) } } switch format { case FormatJSON: b, err = json.MarshalIndent(trans, "", " ") ext = ".json" } if err != nil { return err } err = ioutil.WriteFile(filepath.Join(dirname, fmt.Sprintf("%s%s", locale.Locale(), ext)), b, 0644) if err != nil { return err } trans = trans[0:0] } return nil } // Import reads the translations out of a file or directory on disk. // // NOTE: this currently only works with string or int translations keys. func (t *UniversalTranslator) Import(format ImportExportFormat, dirnameOrFilename string) error { fi, err := os.Stat(dirnameOrFilename) if err != nil { return err } processFn := func(filename string) error { f, err := os.Open(filename) if err != nil { return err } defer f.Close() return t.ImportByReader(format, f) } if !fi.IsDir() { return processFn(dirnameOrFilename) } // recursively go through directory walker := func(path string, info os.FileInfo, err error) error { if info.IsDir() { return nil } switch format { case FormatJSON: // skip non JSON files if filepath.Ext(info.Name()) != ".json" { return nil } } return processFn(path) } return filepath.Walk(dirnameOrFilename, walker) } // ImportByReader imports the the translations found within the contents read from the supplied reader. // // NOTE: generally used when assets have been embedded into the binary and are already in memory. func (t *UniversalTranslator) ImportByReader(format ImportExportFormat, reader io.Reader) error { b, err := ioutil.ReadAll(reader) if err != nil { return err } var trans []translation switch format { case FormatJSON: err = json.Unmarshal(b, &trans) } if err != nil { return err } for _, tl := range trans { locale, found := t.FindTranslator(tl.Locale) if !found { return &ErrMissingLocale{locale: tl.Locale} } pr := stringToPR(tl.PluralRule) if pr == locales.PluralRuleUnknown { err = locale.Add(tl.Key, tl.Translation, tl.OverrideExisting) if err != nil { return err } continue } switch tl.PluralType { case cardinalType: err = locale.AddCardinal(tl.Key, tl.Translation, pr, tl.OverrideExisting) case ordinalType: err = locale.AddOrdinal(tl.Key, tl.Translation, pr, tl.OverrideExisting) case rangeType: err = locale.AddRange(tl.Key, tl.Translation, pr, tl.OverrideExisting) default: return &ErrBadPluralDefinition{tl: tl} } if err != nil { return err } } return nil } func stringToPR(s string) locales.PluralRule { switch s { case "One": return locales.PluralRuleOne case "Two": return locales.PluralRuleTwo case "Few": return locales.PluralRuleFew case "Many": return locales.PluralRuleMany case "Other": return locales.PluralRuleOther default: return locales.PluralRuleUnknown } } universal-translator-0.17.0/import_export_test.go000066400000000000000000000460361356234140000223060ustar00rootroot00000000000000package ut import ( "fmt" "path/filepath" "testing" "os" "github.com/go-playground/locales" "github.com/go-playground/locales/en" "github.com/go-playground/locales/nl" ) // NOTES: // - Run "go test" to run tests // - Run "gocov test | gocov report" to report on test converage by file // - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called // // or // // -- may be a good idea to change to output path to somewherelike /tmp // go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html // func TestExportImportBasic(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string expected error expectedError bool override bool }{ { key: "test_trans", trans: "Welcome {0}", expected: nil, }, { key: -1, trans: "Welcome {0}", expected: nil, }, { key: "test_trans2", trans: "{0} to the {1}.", expected: nil, }, { key: "test_trans3", trans: "Welcome {0} to the {1}", expected: nil, }, { key: "test_trans4", trans: "{0}{1}", expected: nil, }, { key: "test_trans", trans: "{0}{1}", expected: &ErrConflictingTranslation{locale: en.Locale(), key: "test_trans", text: "{0}{1}"}, expectedError: true, }, { key: -1, trans: "{0}{1}", expected: &ErrConflictingTranslation{locale: en.Locale(), key: -1, text: "{0}{1}"}, expectedError: true, }, { key: "test_trans", trans: "Welcome {0} to the {1}.", expected: nil, override: true, }, } for _, tt := range translations { err := en.Add(tt.key, tt.trans, tt.override) if err != tt.expected { if !tt.expectedError { t.Errorf("Expected '%s' Got '%s'", tt.expected, err) } else { if err.Error() != tt.expected.Error() { t.Errorf("Expected '%s' Got '%s'", tt.expected.Error(), err.Error()) } } } } dirname := "testdata/translations" defer os.RemoveAll(dirname) err := uni.Export(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } uni = New(e, e) err = uni.Import(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } en, found = uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } tests := []struct { key interface{} params []string expected string expectedError bool }{ { key: "test_trans", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test.", }, { key: "test_trans2", params: []string{"Joeybloggs", "The Test"}, expected: "Joeybloggs to the The Test.", }, { key: "test_trans3", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test", }, { key: "test_trans4", params: []string{"Joeybloggs", "The Test"}, expected: "JoeybloggsThe Test", }, // bad translation { key: "non-existant-key", params: []string{"Joeybloggs", "The Test"}, expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.T(tt.key, tt.params...) if s != tt.expected { if !tt.expectedError || (tt.expectedError && err != ErrUnknowTranslation) { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } } func TestExportImportCardinal(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "cardinal_test", trans: "You have a day left.", rule: locales.PluralRuleOne, expected: &ErrCardinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddCardinal'. locale: '%s' key: '%v' text: '%s'", paramZero, en.Locale(), "cardinal_test", "You have a day left.")}, expectedError: true, }, { key: "cardinal_test", trans: "You have {0} day", rule: locales.PluralRuleOne, expected: nil, }, { key: "cardinal_test", trans: "You have {0} days left.", rule: locales.PluralRuleOther, expected: nil, }, { key: "cardinal_test", trans: "You have {0} days left.", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: en.Locale(), key: "cardinal_test", rule: locales.PluralRuleOther, text: "You have {0} days left."}, expectedError: true, }, { key: "cardinal_test", trans: "You have {0} day left.", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := en.AddCardinal(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '%s' Got '%s'", tt.expected, err) } } } dirname := "testdata/translations" defer os.RemoveAll(dirname) err := uni.Export(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } uni = New(e, e) err = uni.Import(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } en, found = uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } tests := []struct { key interface{} num float64 digits uint64 param string expected string expectedError bool }{ { key: "cardinal_test", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "You have 1 day left.", }, // bad translation key { key: "non-existant", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.C(tt.key, tt.num, tt.digits, tt.param) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestExportImportOrdinal(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "day", trans: "st", rule: locales.PluralRuleOne, expected: &ErrOrdinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddOrdinal'. locale: '%s' key: '%v' text: '%s'", paramZero, en.Locale(), "day", "st")}, expectedError: true, }, { key: "day", trans: "{0}sfefewt", rule: locales.PluralRuleOne, expected: nil, }, { key: "day", trans: "{0}nd", rule: locales.PluralRuleTwo, expected: nil, }, { key: "day", trans: "{0}rd", rule: locales.PluralRuleFew, expected: nil, }, { key: "day", trans: "{0}th", rule: locales.PluralRuleOther, expected: nil, }, // bad translation { key: "day", trans: "{0}th", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: en.Locale(), key: "day", rule: locales.PluralRuleOther, text: "{0}th"}, expectedError: true, }, { key: "day", trans: "{0}st", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := en.AddOrdinal(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '' Got '%s'", err) } } } dirname := "testdata/translations" defer os.RemoveAll(dirname) err := uni.Export(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } uni = New(e, e) err = uni.Import(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } en, found = uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } tests := []struct { key interface{} num float64 digits uint64 param string expected string expectedError bool }{ { key: "day", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "1st", }, { key: "day", num: 2, digits: 0, param: string(en.FmtNumber(2, 0)), expected: "2nd", }, { key: "day", num: 3, digits: 0, param: string(en.FmtNumber(3, 0)), expected: "3rd", }, { key: "day", num: 4, digits: 0, param: string(en.FmtNumber(4, 0)), expected: "4th", }, { key: "day", num: 10258.43, digits: 0, param: string(en.FmtNumber(10258.43, 0)), expected: "10,258th", }, // bad translation { key: "d-day", num: 10258.43, digits: 0, param: string(en.FmtNumber(10258.43, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.O(tt.key, tt.num, tt.digits, tt.param) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestExportImportRange(t *testing.T) { n := nl.New() uni := New(n, n) // dutch nl, found := uni.GetTranslator("nl") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "day", trans: "er -{1} dag vertrokken", rule: locales.PluralRuleOne, expected: &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, are you sure you're adding a Range Translation? locale: '%s' key: '%s' text: '%s'", paramZero, nl.Locale(), "day", "er -{1} dag vertrokken")}, expectedError: true, }, // bad translation { key: "day", trans: "er {0}- dag vertrokken", rule: locales.PluralRuleOne, expected: &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, a Range Translation requires two parameters. locale: '%s' key: '%s' text: '%s'", paramOne, nl.Locale(), "day", "er {0}- dag vertrokken")}, expectedError: true, }, { key: "day", trans: "er {0}-{1} dag", rule: locales.PluralRuleOne, expected: nil, }, { key: "day", trans: "er zijn {0}-{1} dagen over", rule: locales.PluralRuleOther, expected: nil, }, // bad translation { key: "day", trans: "er zijn {0}-{1} dagen over", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: nl.Locale(), key: "day", rule: locales.PluralRuleOther, text: "er zijn {0}-{1} dagen over"}, expectedError: true, }, { key: "day", trans: "er {0}-{1} dag vertrokken", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := nl.AddRange(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '%#v' Got '%s'", tt.expected, err) } } } dirname := "testdata/translations" defer os.RemoveAll(dirname) err := uni.Export(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } uni = New(n, n) err = uni.Import(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } nl, found = uni.GetTranslator("nl") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } tests := []struct { key interface{} num1 float64 digits1 uint64 num2 float64 digits2 uint64 param1 string param2 string expected string expectedError bool }{ { key: "day", num1: 1, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(1, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "er zijn 1-2 dagen over", }, { key: "day", num1: 0, digits1: 0, num2: 1, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(1, 0)), expected: "er 0-1 dag vertrokken", }, { key: "day", num1: 0, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "er zijn 0-2 dagen over", }, // bad translations from here { key: "d-day", num1: 0, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := nl.R(tt.key, tt.num1, tt.digits1, tt.num2, tt.digits2, tt.param1, tt.param2) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestImportRecursive(t *testing.T) { e := en.New() uni := New(e, e) dirname := "testdata/nested1" err := uni.Import(FormatJSON, dirname) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } en, found := uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } tests := []struct { key interface{} params []string expected string expectedError bool }{ { key: "test_trans", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test.", }, { key: "test_trans2", params: []string{"Joeybloggs", "The Test"}, expected: "Joeybloggs to the The Test.", }, { key: "test_trans3", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test", }, { key: "test_trans4", params: []string{"Joeybloggs", "The Test"}, expected: "JoeybloggsThe Test", }, // bad translation { key: "non-existant-key", params: []string{"Joeybloggs", "The Test"}, expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.T(tt.key, tt.params...) if s != tt.expected { if !tt.expectedError || (tt.expectedError && err != ErrUnknowTranslation) { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } } func TestBadImport(t *testing.T) { // test non existant file e := en.New() uni := New(e, e) filename := "testdata/non-existant-file.json" expected := "stat testdata/non-existant-file.json: no such file or directory" err := uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test bad parameter basic translation filename = "testdata/bad-translation1.json" expected = "error: bad parameter syntax, missing parameter '{0}' in translation. locale: 'en' key: 'test_trans3' text: 'Welcome {lettersnotpermitted} to the {1}'" err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test missing bracket basic translation filename = "testdata/bad-translation2.json" expected = "error: missing bracket '{}', in translation. locale: 'en' key: 'test_trans3' text: 'Welcome {0 to the {1}'" err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test missing locale basic translation filename = "testdata/bad-translation3.json" expected = "error: locale 'nl' not registered." err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test bad plural definition filename = "testdata/bad-translation4.json" expected = "error: bad plural definition 'ut.translation{Locale:\"en\", Key:\"cardinal_test\", Translation:\"You have {0} day left.\", PluralType:\"NotAPluralType\", PluralRule:\"One\", OverrideExisting:false}'" err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test bad plural rule for locale filename = "testdata/bad-translation5.json" expected = "error: cardinal plural rule 'Many' does not exist for locale 'en' key: 'cardinal_test' text: 'You have {0} day left.'" err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test invalid JSON filename = "testdata/bad-translation6.json" expected = "invalid character ']' after object key:value pair" err = uni.Import(FormatJSON, filename) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test bad io.Reader f, err := os.Open(filename) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } f.Close() expected = "read testdata/bad-translation6.json: file already closed" err = uni.ImportByReader(FormatJSON, f) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } } func TestBadExport(t *testing.T) { // test readonly directory e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } dirname := "testdata/readonly" err := os.Mkdir(dirname, 0444) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } defer os.RemoveAll(dirname) en.Add("day", "this is a day", false) expected := "open testdata/readonly/en.json: permission denied" err = uni.Export(FormatJSON, dirname) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } // test exporting into directory inside readonly directory expected = "stat testdata/readonly/inner: permission denied" err = uni.Export(FormatJSON, filepath.Join(dirname, "inner")) if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } } universal-translator-0.17.0/logo.png000066400000000000000000000403261356234140000174470ustar00rootroot00000000000000PNG  IHDREFPLTE32뼹þİ圚2752$"2)'$~=)&%$2u23! 0,).:sD"{h i4]3n.:8=3=R2.-/,bt ria1$)9$^*xgw){x&H65`4H2ػT6D4޵M#5w+w}EuuqO',;'[P4`&T [l5A,鉆Pd3ьsSbRPCJ-4)no}{spjl_][^B?5癙?<6gpzl2Nƾᲀ8= 9`y{vطAzV&+χѧ/쫩ʥ눆sG;˴ޖca̶]UHWW1ˠd_d=vUk}idUوkbPk}VRҿf)Npػ]Νұ=IDATxڼOhP&jU[MخMI(e-ړ)2`xVPPC1c q"a*z/:tVŹ "~_^ޒ9O]R>{u[8G{G#EQ,g>/D^ر# % ЊHZq\ ~F@A8'z#H.e99zAɱ1 [a2(Onvda%zo,:Ǭҫ{USܜ!vuBѣǏ JO>_|o @n6_=[#gz1ڻ3v31u s>"O`)gF> ǯ^]Zj\%mF: V -o o- RJVw/߻8W,a+vr_G,$TlUruEClr5f=vc}<_4?5v>ޗɔ/]t,%Q5I{w2;Q%osQHH6wiQ]2csK917ƻQ8 󲐷J7]Cx,Lz|'v[t7.>}\ʢb\ Imۓ^׏^USjYVbwW5TP3u/ S]_C3Nw| a7'SO><'!''+W>{=vȾ}.H}I'69y[^>{]_NTYM#ۜ˺&kJB1.2䄾h4n<%1O^oõ /ȁ;wy3j~3WHؐ֓tz+!)ЈqA7y0fU}g"P.#̥C@뛐 ??v#?C_ ZO'v:r-4O0SIʼn9\ .0`{B]]B];^拠Zϝ [0wnG[:;xN.OXDm|r j__~bݶ cr:ˬz@#I[8![w% y9.. FC<;ǵv5?Y5iFqjj&=玏]}Rm~ 9ߖZ r|3 @FXVVf, *҃|Mn"EҌ~Bo8qO9llOw 3ܞw. _#LqnrX)˲JtH}6d~eNV.iއzj793q%Cm-΁f)l&' GG"kU8F`YZ033sDǟElg1 X7wU_A$K_o~.J$R֘sx(-0)-\2u^ We)?YY)wHc|6np2uhMsgݵuʢ$ ðTZگ)SvR۫yf;q DGG-->Ptp9.]Pp&XDm6umxWPqЬ+nhoR7o<@6,7):mST|[[uHʙcR5T|)8/11QUeʰS}byQן2B l*z?":ъ o0OΛCnm7g 1{R-:מT4|kۛ !R_c燓Θ&SdT64){\\^^{"(a 9>n;e݃M f@nYWypCNmmӮАӋn5k ȝM=VzdNL]$. 4qGc}?:11ΞC9fSٚH͛=`OۙVSr ӋλdwFȟDX=}|KV"_nnƒCnл|~"j/!K qb䏎|EЗ;JIQWUVj|s(~EE)o@ߟNuB푌&΃:2Yhq9%.g/BeyloHU?&ObɌArXTUIO(r9ulVꔚJܤ*w(*e0O!G߄JN{ GW趶fe'ѾLnOTuZT^{hIw&?Qz[P0>ȁ.PO˩q:uJUOImL!1}+%GY2 {~m&ס)9InKJZ2,sNN蜈Yfݦpfr w8,JقڤOA-[]vvJM -nEi›;:2:L֖͙ƋwkUi<֭[ѳ*<:_8ēoN|G? T]K[[=PR6=b]] OVz0ں ϩtו}MM.^*[5{)X df{i{oQ_t"".^W7e4v%VM& 'ĭAs&ŠJAC-0f;xY402*D7-! = t> E^:k,?ܵi_8n%iJҥ={~ &K?zr#zFW'*+7̦ li6{ケ;w@EEY4x|u=1]T 4@>᎝uĺEn߷^I@g}@g5l5*&(R)RԣUȕ`)(m3R+5QŚq![!ȖwzUU~Vwfx8[^K19=ߋȓ>zb;gk%VD=QPw~vi<7&7 0yqC8s}|['vOV*:OO FvS||ʛh"oxtXi滗It ;ceƤFefZz7Uqvہ6 '.EEi:B3وb];[]1rWU[ A E15yFڃZN\%B8M59-٫<_~eRsQeBTd@;s4mQp Z88Vq~yĞNny=c流T7 A޿Nt :c67]'Z:f ɖ_E+#ԛ'Sx6tE4N\rs3VU폍ݼU;Cc]\7SS__6v9?/-aWYO_!O2L3+fͨlZΫO[,WY9>Gg$(eݗ݋_M;]Br a񷅒mQs)%Cs=zr9Bb} ; ŤOj8]=6E}\`Ar oE}C9})*+km*eMiQi,TӤmKm~)K˫1-/^HsX{FuDǞ`ĩdba0 Q O_Ah;AЗ g;D/Dv =?o6JKJ97@[hit X9| :+:ȁIVmxkxD$E;ơA_@pw rR‹\ñxS&ZFJ.쓊a!#9#W};jrPRpv\Twt5[M]55C]?^ކ1)wbq/sMqE| Rx՘i6멵f NށxzZiii5wsܐ սΪӀ!뺳˾Y$Bx ;E?̐eB 01p-S47 JYynï}ڊ9K\ܜB$a=JRB:#1J%6>`9g+Gn ORkh6 <"CvKTh1 &IVa>'fr-Nw5[#Wƴړ+4,tgX`@0P*Ɂ$u!էӮhod1 L/u4\܎~}~=aCy7DwjptꉰrxxVG#zj%d Lt12l~qX?$i(VOLe}89ُ+_0̜Gbψ/ψq;% ;<~eqG.w|wʁ0@ɦY[mm?/9W۷/:h9FV" {r;,;,8s0_5z~/%_lCP= &.oy{ȹkN ʰ.!'m^_v?F/ POpd,'qƊ\Y.]{$:q%;K՘Sf+鯤 mئqUyykۿ*9kz`z>$ow&|zĠ\GLjÃ>O6,pQ2}X#?](pǶys5Yтє]m;9Ӽ3^?a~nN]+{~GȢm<>Y>?R<*)_#9?X.Gy}J'-־֑.NGPiM+b&E ϚehΠ~~6Zq?%A?g\:;yG_Ԧ`dV'՜Ԍũ]L3 _4}FC!i1*jj؛q`!X m B~̽׻'L؀_ঀ{:v#9M>Wo=-bӲ3gx a8ze^]KI33Ya69s<]fm !!/![;/_;3_/ܸwIҒAy˱Us ^UHAoWGkNxz/AI2o~y`B߄yй), to#/$웏>ͨc$I9`d[s 14t _Amc^ck(;Cvboh__o۾ `@FIΎ,c ::;r)Vrq\0`r )MW(=B$df*21HF"3"*.Rc!fDR6X^ƈ9:'맿;}ڳi׏UU'KU~5E.ϫ,l:l H_S/82A]!P; B[J{sf*%7߰10@'УiM9IKN%-фpT]N)#ڷ=1ҠN%w;0k RB˿\55%T***Ϧjyyyoiu*C} hKq/0Ӱn0*if!-~ta':cE!z%uGEE1Ճ)Z]cy;F_l9j:>:1uӾXQ*r_|j8Jj>i '< ط9@wd":In\ ظ}w6pd3u|opb'(7c]HnlSGWnn`'M-ݽI&mywbI3 n4>JW$5Ukn詘ԹFÜd6Ltac6w `awׯôyOnSI zy,3RwfMa2q3$y#Ϝwʹs##8 .% _O{5×]ghSgh샃HmO}VRW"쀯5y#bba@B͖O^}#\?6Wێvc*fe]uȻ3j勇K%k^Y Xz Gx[GGAx_0,.DE{,L=#Wȓ r-T,ZZ*8Et]*~U08ob$g$ۭ/pvh]UݪvCzN-Q+\CÜO.æ!S@n G)y:!$hllr#mzӞ[Zi77ց.TV-o/RךțW_Vky zFvEc,޷xKt9uSü&Ҡ:y`O t;)+w5:P^@1?ͫkGjxyz?n ^8ٳfOt"fhS64嶨5[@NQ́_uSjzX.xwZg>b5r]!{tsx,:lAPt}w;z3Wh*^^;03=]5Egᰲ`Q՗RO(uSthna4؂DtB.01{PszCgNZߜ C.2:wtM K;T":n@hSƿIxvv6OoݻҒhoO\U)P˴aZ]U(FGm~q6w1\@C oZc4t[w.$\w,yrt*fC|אS; :R9[sA^Pq}I3.Y ?(c@3j_xBEbVeг imj۾jmsu #+hϘ-\o=nM,IoGВ\9>ޚ38&tTG6BqVzn0٦|ް* ^lX8Zt(]puK#t9+OkKPZ^*iF"@NNʖ_ =DrR\oE=?".[gStݣo(@P߬B]_VV5]%J!NTMӻKpiR E2xeқe/1y:AҙCw‮Kw`Xߊmk$M&R -M#ӽN9YxNN7PKWecIK97~ѫ~f/fQCxL9MD%ja9r8p\Jk+ݿjF:tG]JI0պUC<'/т)+V@<U,sa֩s׸)kЊ$M ';qKAf.˔4V6nG̒ T7[ìoNʓ\ȂCF7_ngɷ\ò^9 ),M1:gY?}#msЙҿA4HJǠݔ]'erNџt$%  LiXVoûxf#F58,?w!Jueߗg_(++6Lc,{=17AY1ݭ2V@XPr; :?@HHC?<*2~:ZNԎ&+ݥ"]p@}x ҘSxG㈈.۠COFZ1 Wzs',d @VFbw.73@hvjjbQOt(.,⾅ EeABQlš2JaA96 stgϥۃyʖͣ ǧì)g%ǣ\w 9` >⼋$.{f9)UA4 ]|:OwS;T|*N5G+ԅFhS7r&&M;KӹKkE*Ƹ+$#%F\H.{^a(ΑCnݤ:?IWz D&[9 p$r}=]\a"xlBQP\h<4;vјfT~tiX(3%MʞJ%tGv bĒ&\'A9 {xGlSN\=n:yStu>i#>dn}YJ=^~(M].]\w+M ʹF>¦KMľI)\ GбG,gF0#ػ7Lq0'%;Q+Ӓ֎W«=MF&6-3zX#:Fa#d6^V^t)_-5OuB+81tAv>mQ_oS'l0X~%H  U,5{+!ªDb98gk!HjD('=U\*U-P%rP wfw2ZT^\Q/͛7\UA_Pp 6}wXk#drG3`N{9R| +G[V*%\V˖>:UCre l.3gn)pHoi= s :b\/]4mV0ڹ~{.|3'>&1#<=mGeLv]G'tR=?Qm9\ۛHF0*~!˭1eJx2=; W`LUܭ]`d] kϟL{eVˆM[i6 eKTm'sYNp׷W_Z5RqKǸ!Ʊ~C7| 8Gާ#:f3oy| Q.tҖa26jSrk0Fkp%' Q~+<*źň✜&<X|1N/]{zH蔝ΪҌkwhʲ|}ajՌ)#Ak-c9MБ}\&9MC/Bs#SlOPA䂻% ;g.;T'6֢%[.(%{jiԶf˘XqJx7̊DUgl!% MjZ6yE8zd1]͝AsP,(ؐ&~.iG 'S֊5mLr,aLZژ2Ja#%VYz5R$.\ÃJax$Z;9ww&q"'k_|b\zab-e|z ]t@\[p@ju[VjU[E%`1a>:̭{Til`rfomtuz#ey]:͵o@>z' ;~͉R^%ۼ^X1")@:>њXVֲ֫֓f|VJ0AgY9)rhezNfW3i;1":'Ɂ-'#mA>* 4a!;a7Yy~+o淒-jJjZ%-UJӏZ\Nu qL/9nV*f]N: ͌0g63 'Er1ac H^x|zkqHDY*Rcsg]J^ZD"[׫y4@j4)׌ʚ$?rBRek&NcЃ/$.i>[W{ârl#Lv2ܯtoUW6Qr9KtKmZaŰpzV< xhA>xx#^XU2t'jid\Hܜm,D2VژV ^k%Sv' 1麗0գbVfԞi8-s^r$39ȕ2>+=W56:;KWAJܨmtutÚmMb^9=8fv׈FWHY6(%!ԽUKyEgQ4е:ިB6Sݺsa훜 }x<{3v!篣'M=$<,q`zW!{,Sgԟg+MA>П l|CGMm^߬s>tJz2 3㚮=TXpHdQ!tpV&K! eKPt=ktSvϴoԢ*D8Lߨ=}5q|q>ucb6GsWt3x?CkѬ~jME hdh/|vYJ eQsDF]{p:/wީ3u9?,H?TޣDo2ҦVn΃r @=ctH;JSx:y4XR~9,Sip|8L <=k*wP3pBղǹ@IrZԬ| `~7 }ZI~HBҁ<ۘ Z ưEn |]^99}1Am&fyn1~"xQ#P㑝UH5?<ĜN+nw ggƨ#sࢯ3g)s͇O+c. އ 0 && tarr[rule] != nil && !override { return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} } } else { tarr = make([]*transText, 7, 7) t.cardinalTanslations[key] = tarr } trans := &transText{ text: text, indexes: make([]int, 2, 2), } tarr[rule] = trans idx := strings.Index(text, paramZero) if idx == -1 { tarr[rule] = nil return &ErrCardinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddCardinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} } trans.indexes[0] = idx trans.indexes[1] = idx + len(paramZero) return nil } // AddOrdinal adds an ordinal plural translation for a particular language/locale // {0} is the only replacement type accepted and only one variable is accepted as // multiple cannot be used for a plural rule determination, unless it is a range; // see AddRange below. // eg. in locale 'en' one: '{0}st day of spring' other: '{0}nd day of spring' - 1st, 2nd, 3rd... func (t *translator) AddOrdinal(key interface{}, text string, rule locales.PluralRule, override bool) error { var verified bool // verify plural rule exists for locale for _, pr := range t.PluralsOrdinal() { if pr == rule { verified = true break } } if !verified { return &ErrOrdinalTranslation{text: fmt.Sprintf("error: ordinal plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)} } tarr, ok := t.ordinalTanslations[key] if ok { // verify not adding a conflicting record if len(tarr) > 0 && tarr[rule] != nil && !override { return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} } } else { tarr = make([]*transText, 7, 7) t.ordinalTanslations[key] = tarr } trans := &transText{ text: text, indexes: make([]int, 2, 2), } tarr[rule] = trans idx := strings.Index(text, paramZero) if idx == -1 { tarr[rule] = nil return &ErrOrdinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddOrdinal'. locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} } trans.indexes[0] = idx trans.indexes[1] = idx + len(paramZero) return nil } // AddRange adds a range plural translation for a particular language/locale // {0} and {1} are the only replacement types accepted and only these are accepted. // eg. in locale 'nl' one: '{0}-{1} day left' other: '{0}-{1} days left' func (t *translator) AddRange(key interface{}, text string, rule locales.PluralRule, override bool) error { var verified bool // verify plural rule exists for locale for _, pr := range t.PluralsRange() { if pr == rule { verified = true break } } if !verified { return &ErrRangeTranslation{text: fmt.Sprintf("error: range plural rule '%s' does not exist for locale '%s' key: '%v' text: '%s'", rule, t.Locale(), key, text)} } tarr, ok := t.rangeTanslations[key] if ok { // verify not adding a conflicting record if len(tarr) > 0 && tarr[rule] != nil && !override { return &ErrConflictingTranslation{locale: t.Locale(), key: key, rule: rule, text: text} } } else { tarr = make([]*transText, 7, 7) t.rangeTanslations[key] = tarr } trans := &transText{ text: text, indexes: make([]int, 4, 4), } tarr[rule] = trans idx := strings.Index(text, paramZero) if idx == -1 { tarr[rule] = nil return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, are you sure you're adding a Range Translation? locale: '%s' key: '%v' text: '%s'", paramZero, t.Locale(), key, text)} } trans.indexes[0] = idx trans.indexes[1] = idx + len(paramZero) idx = strings.Index(text, paramOne) if idx == -1 { tarr[rule] = nil return &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, a Range Translation requires two parameters. locale: '%s' key: '%v' text: '%s'", paramOne, t.Locale(), key, text)} } trans.indexes[2] = idx trans.indexes[3] = idx + len(paramOne) return nil } // T creates the translation for the locale given the 'key' and params passed in func (t *translator) T(key interface{}, params ...string) (string, error) { trans, ok := t.translations[key] if !ok { return unknownTranslation, ErrUnknowTranslation } b := make([]byte, 0, 64) var start, end, count int for i := 0; i < len(trans.indexes); i++ { end = trans.indexes[i] b = append(b, trans.text[start:end]...) b = append(b, params[count]...) i++ start = trans.indexes[i] count++ } b = append(b, trans.text[start:]...) return string(b), nil } // C creates the cardinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in func (t *translator) C(key interface{}, num float64, digits uint64, param string) (string, error) { tarr, ok := t.cardinalTanslations[key] if !ok { return unknownTranslation, ErrUnknowTranslation } rule := t.CardinalPluralRule(num, digits) trans := tarr[rule] b := make([]byte, 0, 64) b = append(b, trans.text[:trans.indexes[0]]...) b = append(b, param...) b = append(b, trans.text[trans.indexes[1]:]...) return string(b), nil } // O creates the ordinal translation for the locale given the 'key', 'num' and 'digit' arguments and param passed in func (t *translator) O(key interface{}, num float64, digits uint64, param string) (string, error) { tarr, ok := t.ordinalTanslations[key] if !ok { return unknownTranslation, ErrUnknowTranslation } rule := t.OrdinalPluralRule(num, digits) trans := tarr[rule] b := make([]byte, 0, 64) b = append(b, trans.text[:trans.indexes[0]]...) b = append(b, param...) b = append(b, trans.text[trans.indexes[1]:]...) return string(b), nil } // R creates the range translation for the locale given the 'key', 'num1', 'digit1', 'num2' and 'digit2' arguments // and 'param1' and 'param2' passed in func (t *translator) R(key interface{}, num1 float64, digits1 uint64, num2 float64, digits2 uint64, param1, param2 string) (string, error) { tarr, ok := t.rangeTanslations[key] if !ok { return unknownTranslation, ErrUnknowTranslation } rule := t.RangePluralRule(num1, digits1, num2, digits2) trans := tarr[rule] b := make([]byte, 0, 64) b = append(b, trans.text[:trans.indexes[0]]...) b = append(b, param1...) b = append(b, trans.text[trans.indexes[1]:trans.indexes[2]]...) b = append(b, param2...) b = append(b, trans.text[trans.indexes[3]:]...) return string(b), nil } // VerifyTranslations checks to ensures that no plural rules have been // missed within the translations. func (t *translator) VerifyTranslations() error { for k, v := range t.cardinalTanslations { for _, rule := range t.PluralsCardinal() { if v[rule] == nil { return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "plural", rule: rule, key: k} } } } for k, v := range t.ordinalTanslations { for _, rule := range t.PluralsOrdinal() { if v[rule] == nil { return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "ordinal", rule: rule, key: k} } } } for k, v := range t.rangeTanslations { for _, rule := range t.PluralsRange() { if v[rule] == nil { return &ErrMissingPluralTranslation{locale: t.Locale(), translationType: "range", rule: rule, key: k} } } } return nil } universal-translator-0.17.0/translator_test.go000066400000000000000000000505171356234140000215630ustar00rootroot00000000000000package ut import ( "fmt" "testing" "github.com/go-playground/locales" "github.com/go-playground/locales/en" "github.com/go-playground/locales/en_CA" "github.com/go-playground/locales/nl" ) // NOTES: // - Run "go test" to run tests // - Run "gocov test | gocov report" to report on test converage by file // - Run "gocov test | gocov annotate -" to report on all code and functions, those ,marked with "MISS" were never called // // or // // -- may be a good idea to change to output path to somewherelike /tmp // go test -coverprofile cover.out && go tool cover -html=cover.out -o cover.html // func TestBasicTranslation(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") // or fallback if fails to find 'en' if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string expected error expectedError bool override bool }{ { key: "test_trans", trans: "Welcome {0}", expected: nil, }, { key: -1, trans: "Welcome {0}", expected: nil, }, { key: "test_trans2", trans: "{0} to the {1}.", expected: nil, }, { key: "test_trans3", trans: "Welcome {0} to the {1}", expected: nil, }, { key: "test_trans4", trans: "{0}{1}", expected: nil, }, { key: "test_trans", trans: "{0}{1}", expected: &ErrConflictingTranslation{locale: en.Locale(), key: "test_trans", text: "{0}{1}"}, expectedError: true, }, { key: -1, trans: "{0}{1}", expected: &ErrConflictingTranslation{locale: en.Locale(), key: -1, text: "{0}{1}"}, expectedError: true, }, { key: "test_trans", trans: "Welcome {0} to the {1}.", expected: nil, override: true, }, } for _, tt := range translations { err := en.Add(tt.key, tt.trans, tt.override) if err != tt.expected { if !tt.expectedError { t.Errorf("Expected '%s' Got '%s'", tt.expected, err) } else { if err.Error() != tt.expected.Error() { t.Errorf("Expected '%s' Got '%s'", tt.expected.Error(), err.Error()) } } } } tests := []struct { key interface{} params []string expected string expectedError bool }{ { key: "test_trans", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test.", }, { key: "test_trans2", params: []string{"Joeybloggs", "The Test"}, expected: "Joeybloggs to the The Test.", }, { key: "test_trans3", params: []string{"Joeybloggs", "The Test"}, expected: "Welcome Joeybloggs to the The Test", }, { key: "test_trans4", params: []string{"Joeybloggs", "The Test"}, expected: "JoeybloggsThe Test", }, // bad translation { key: "non-existant-key", params: []string{"Joeybloggs", "The Test"}, expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.T(tt.key, tt.params...) if s != tt.expected { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } } func TestCardinalTranslation(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "cardinal_test", trans: "You have a day left.", rule: locales.PluralRuleOne, expected: &ErrCardinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddCardinal'. locale: '%s' key: '%v' text: '%s'", paramZero, en.Locale(), "cardinal_test", "You have a day left.")}, expectedError: true, }, // bad translation { key: "cardinal_test", trans: "You have a day left few.", rule: locales.PluralRuleFew, expected: &ErrCardinalTranslation{text: fmt.Sprintf("error: cardinal plural rule '%s' does not exist for locale '%s' key: '%s' text: '%s'", locales.PluralRuleFew, en.Locale(), "cardinal_test", "You have a day left few.")}, expectedError: true, }, { key: "cardinal_test", trans: "You have {0} day", rule: locales.PluralRuleOne, expected: nil, }, { key: "cardinal_test", trans: "You have {0} days left.", rule: locales.PluralRuleOther, expected: nil, }, { key: "cardinal_test", trans: "You have {0} days left.", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: en.Locale(), key: "cardinal_test", rule: locales.PluralRuleOther, text: "You have {0} days left."}, expectedError: true, }, { key: "cardinal_test", trans: "You have {0} day left.", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := en.AddCardinal(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '' Got '%s'", err) } } } tests := []struct { key interface{} num float64 digits uint64 param string expected string expectedError bool }{ { key: "cardinal_test", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "You have 1 day left.", }, // bad translation key { key: "non-existant", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.C(tt.key, tt.num, tt.digits, tt.param) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestOrdinalTranslation(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "day", trans: "st", rule: locales.PluralRuleOne, expected: &ErrOrdinalTranslation{text: fmt.Sprintf("error: parameter '%s' not found, may want to use 'Add' instead of 'AddOrdinal'. locale: '%s' key: '%v' text: '%s'", paramZero, en.Locale(), "day", "st")}, expectedError: true, }, // bad translation { key: "day", trans: "st", rule: locales.PluralRuleMany, expected: &ErrOrdinalTranslation{text: fmt.Sprintf("error: ordinal plural rule '%s' does not exist for locale '%s' key: '%s' text: '%s'", locales.PluralRuleMany, en.Locale(), "day", "st")}, expectedError: true, }, { key: "day", trans: "{0}st", rule: locales.PluralRuleOne, expected: nil, }, { key: "day", trans: "{0}nd", rule: locales.PluralRuleTwo, expected: nil, }, { key: "day", trans: "{0}rd", rule: locales.PluralRuleFew, expected: nil, }, { key: "day", trans: "{0}th", rule: locales.PluralRuleOther, expected: nil, }, // bad translation { key: "day", trans: "{0}th", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: en.Locale(), key: "day", rule: locales.PluralRuleOther, text: "{0}th"}, expectedError: true, }, { key: "day", trans: "{0}st", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := en.AddOrdinal(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '' Got '%s'", err) } } } tests := []struct { key interface{} num float64 digits uint64 param string expected string expectedError bool }{ { key: "day", num: 1, digits: 0, param: string(en.FmtNumber(1, 0)), expected: "1st", }, { key: "day", num: 2, digits: 0, param: string(en.FmtNumber(2, 0)), expected: "2nd", }, { key: "day", num: 3, digits: 0, param: string(en.FmtNumber(3, 0)), expected: "3rd", }, { key: "day", num: 4, digits: 0, param: string(en.FmtNumber(4, 0)), expected: "4th", }, { key: "day", num: 10258.43, digits: 0, param: string(en.FmtNumber(10258.43, 0)), expected: "10,258th", }, // bad translation { key: "d-day", num: 10258.43, digits: 0, param: string(en.FmtNumber(10258.43, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := en.O(tt.key, tt.num, tt.digits, tt.param) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestRangeTranslation(t *testing.T) { n := nl.New() uni := New(n, n) // dutch nl, found := uni.GetTranslator("nl") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ // bad translation { key: "day", trans: "er -{1} dag vertrokken", rule: locales.PluralRuleOne, expected: &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, are you sure you're adding a Range Translation? locale: '%s' key: '%s' text: '%s'", paramZero, nl.Locale(), "day", "er -{1} dag vertrokken")}, expectedError: true, }, // bad translation { key: "day", trans: "er {0}- dag vertrokken", rule: locales.PluralRuleMany, expected: &ErrRangeTranslation{text: fmt.Sprintf("error: range plural rule '%s' does not exist for locale '%s' key: '%s' text: '%s'", locales.PluralRuleMany, nl.Locale(), "day", "er {0}- dag vertrokken")}, expectedError: true, }, // bad translation { key: "day", trans: "er {0}- dag vertrokken", rule: locales.PluralRuleOne, expected: &ErrRangeTranslation{text: fmt.Sprintf("error: parameter '%s' not found, a Range Translation requires two parameters. locale: '%s' key: '%s' text: '%s'", paramOne, nl.Locale(), "day", "er {0}- dag vertrokken")}, expectedError: true, }, { key: "day", trans: "er {0}-{1} dag", rule: locales.PluralRuleOne, expected: nil, }, { key: "day", trans: "er zijn {0}-{1} dagen over", rule: locales.PluralRuleOther, expected: nil, }, // bad translation { key: "day", trans: "er zijn {0}-{1} dagen over", rule: locales.PluralRuleOther, expected: &ErrConflictingTranslation{locale: nl.Locale(), key: "day", rule: locales.PluralRuleOther, text: "er zijn {0}-{1} dagen over"}, expectedError: true, }, { key: "day", trans: "er {0}-{1} dag vertrokken", rule: locales.PluralRuleOne, expected: nil, override: true, }, } for _, tt := range translations { err := nl.AddRange(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '%#v' Got '%s'", tt.expected, err) } } } tests := []struct { key interface{} num1 float64 digits1 uint64 num2 float64 digits2 uint64 param1 string param2 string expected string expectedError bool }{ { key: "day", num1: 1, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(1, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "er zijn 1-2 dagen over", }, { key: "day", num1: 0, digits1: 0, num2: 1, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(1, 0)), expected: "er 0-1 dag vertrokken", }, { key: "day", num1: 0, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "er zijn 0-2 dagen over", }, // bad translations from here { key: "d-day", num1: 0, digits1: 0, num2: 2, digits2: 0, param1: string(nl.FmtNumber(0, 0)), param2: string(nl.FmtNumber(2, 0)), expected: "", expectedError: true, }, } for _, tt := range tests { s, err := nl.R(tt.key, tt.num1, tt.digits1, tt.num2, tt.digits2, tt.param1, tt.param2) if err != nil { if !tt.expectedError && err != ErrUnknowTranslation { t.Errorf("Expected '' Got '%s'", err) } } if s != tt.expected { t.Errorf("Expected '%s' Got '%s'", tt.expected, s) } } } func TestFallbackTranslator(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } if en.Locale() != "en" { t.Errorf("Expected '%s' Got '%s'", "en", en.Locale()) } fallback, _ := uni.GetTranslator("nl") if fallback.Locale() != "en" { t.Errorf("Expected '%s' Got '%s'", "en", fallback.Locale()) } en, _ = uni.FindTranslator("nl", "en") if en.Locale() != "en" { t.Errorf("Expected '%s' Got '%s'", "en", en.Locale()) } fallback, _ = uni.FindTranslator("nl") if fallback.Locale() != "en" { t.Errorf("Expected '%s' Got '%s'", "en", fallback.Locale()) } } func TestAddTranslator(t *testing.T) { e := en.New() n := nl.New() uni := New(e, n) tests := []struct { trans locales.Translator expected error expectedError bool override bool }{ { trans: en_CA.New(), expected: nil, override: false, }, { trans: n, expected: &ErrExistingTranslator{locale: n.Locale()}, expectedError: true, override: false, }, { trans: e, expected: &ErrExistingTranslator{locale: e.Locale()}, expectedError: true, override: false, }, { trans: e, expected: nil, override: true, }, } for _, tt := range tests { err := uni.AddTranslator(tt.trans, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '%s' Got '%s'", tt.expected, err) } } } } func TestVerifyTranslations(t *testing.T) { n := nl.New() // dutch uni := New(n, n) loc, _ := uni.GetTranslator("nl") if loc.Locale() != "nl" { t.Errorf("Expected '%s' Got '%s'", "nl", loc.Locale()) } // cardinal checks err := loc.AddCardinal("day", "je {0} dag hebben verlaten", locales.PluralRuleOne, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // fail cardinal rules expected := &ErrMissingPluralTranslation{locale: loc.Locale(), translationType: "plural", rule: locales.PluralRuleOther, key: "day"} err = loc.VerifyTranslations() if err == nil || err.Error() != expected.Error() { t.Errorf("Expected '%s' Got '%s'", expected, err) } // success cardinal err = loc.AddCardinal("day", "je {0} dagen hebben verlaten", locales.PluralRuleOther, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } err = loc.VerifyTranslations() if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // range checks err = loc.AddRange("day", "je {0}-{1} dagen hebben verlaten", locales.PluralRuleOther, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // fail range rules expected = &ErrMissingPluralTranslation{locale: loc.Locale(), translationType: "range", rule: locales.PluralRuleOne, key: "day"} err = loc.VerifyTranslations() if err == nil || err.Error() != expected.Error() { t.Errorf("Expected '%s' Got '%s'", expected, err) } // success range err = loc.AddRange("day", "je {0}-{1} dag hebben verlaten", locales.PluralRuleOne, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } err = loc.VerifyTranslations() if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // ok so 'nl' aka dutch, ony has one plural rule for ordinals, so going to switch to english from here which has 4 err = uni.AddTranslator(en.New(), false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } loc, _ = uni.GetTranslator("en") if loc.Locale() != "en" { t.Errorf("Expected '%s' Got '%s'", "en", loc.Locale()) } // ordinal checks err = loc.AddOrdinal("day", "{0}st", locales.PluralRuleOne, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } err = loc.AddOrdinal("day", "{0}rd", locales.PluralRuleFew, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } err = loc.AddOrdinal("day", "{0}th", locales.PluralRuleOther, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // fail ordinal rules expected = &ErrMissingPluralTranslation{locale: loc.Locale(), translationType: "ordinal", rule: locales.PluralRuleTwo, key: "day"} err = loc.VerifyTranslations() if err == nil || err.Error() != expected.Error() { t.Errorf("Expected '%s' Got '%s'", expected, err) } // success ordinal err = loc.AddOrdinal("day", "{0}nd", locales.PluralRuleTwo, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } err = loc.VerifyTranslations() if err != nil { t.Fatalf("Expected '' Got '%s'", err) } } func TestVerifyTranslationsWithNonStringKeys(t *testing.T) { n := nl.New() // dutch uni := New(n, n) loc, _ := uni.GetTranslator("nl") if loc.Locale() != "nl" { t.Errorf("Expected '%s' Got '%s'", "nl", loc.Locale()) } // cardinal checks err := loc.AddCardinal(-1, "je {0} dag hebben verlaten", locales.PluralRuleOne, false) if err != nil { t.Fatalf("Expected '' Got '%s'", err) } // fail cardinal rules expected := &ErrMissingPluralTranslation{locale: loc.Locale(), translationType: "plural", rule: locales.PluralRuleOther, key: -1} err = loc.VerifyTranslations() if err == nil || err.Error() != expected.Error() { t.Errorf("Expected '%s' Got '%s'", expected, err) } } func TestGetFallback(t *testing.T) { // dutch n := nl.New() e := en.New() uni := New(e, n) trans := uni.GetFallback() expected := "en" if trans.Locale() != expected { t.Errorf("Expected '%s' Got '%s'", expected, trans.Locale()) } } func TestVerifyUTTranslations(t *testing.T) { e := en.New() uni := New(e, e) en, found := uni.GetTranslator("en") if !found { t.Fatalf("Expected '%t' Got '%t'", true, found) } translations := []struct { key interface{} trans string rule locales.PluralRule expected error expectedError bool override bool }{ { key: "day", trans: "{0}st", rule: locales.PluralRuleOne, expected: nil, }, { key: "day", trans: "{0}nd", rule: locales.PluralRuleTwo, expected: nil, }, { key: "day", trans: "{0}rd", rule: locales.PluralRuleFew, expected: nil, }, // intentionally leaving out plural other // { // key: "day", // trans: "{0}th", // rule: locales.PluralRuleOther, // expected: nil, // }, } for _, tt := range translations { err := en.AddOrdinal(tt.key, tt.trans, tt.rule, tt.override) if err != tt.expected { if !tt.expectedError || err.Error() != tt.expected.Error() { t.Errorf("Expected '' Got '%s'", err) } } } expected := "error: missing 'ordinal' plural rule 'Other' for translation with key 'day' and locale 'en'" err := uni.VerifyTranslations() if err == nil || err.Error() != expected { t.Fatalf("Expected '%s' Got '%s'", expected, err) } err = en.AddOrdinal("day", "{0}th", locales.PluralRuleOther, false) if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } err = uni.VerifyTranslations() if err != nil { t.Fatalf("Expected '%v' Got '%s'", nil, err) } } universal-translator-0.17.0/universal_translator.go000066400000000000000000000055371356234140000226160ustar00rootroot00000000000000package ut import ( "strings" "github.com/go-playground/locales" ) // UniversalTranslator holds all locale & translation data type UniversalTranslator struct { translators map[string]Translator fallback Translator } // New returns a new UniversalTranslator instance set with // the fallback locale and locales it should support func New(fallback locales.Translator, supportedLocales ...locales.Translator) *UniversalTranslator { t := &UniversalTranslator{ translators: make(map[string]Translator), } for _, v := range supportedLocales { trans := newTranslator(v) t.translators[strings.ToLower(trans.Locale())] = trans if fallback.Locale() == v.Locale() { t.fallback = trans } } if t.fallback == nil && fallback != nil { t.fallback = newTranslator(fallback) } return t } // FindTranslator trys to find a Translator based on an array of locales // and returns the first one it can find, otherwise returns the // fallback translator. func (t *UniversalTranslator) FindTranslator(locales ...string) (trans Translator, found bool) { for _, locale := range locales { if trans, found = t.translators[strings.ToLower(locale)]; found { return } } return t.fallback, false } // GetTranslator returns the specified translator for the given locale, // or fallback if not found func (t *UniversalTranslator) GetTranslator(locale string) (trans Translator, found bool) { if trans, found = t.translators[strings.ToLower(locale)]; found { return } return t.fallback, false } // GetFallback returns the fallback locale func (t *UniversalTranslator) GetFallback() Translator { return t.fallback } // AddTranslator adds the supplied translator, if it already exists the override param // will be checked and if false an error will be returned, otherwise the translator will be // overridden; if the fallback matches the supplied translator it will be overridden as well // NOTE: this is normally only used when translator is embedded within a library func (t *UniversalTranslator) AddTranslator(translator locales.Translator, override bool) error { lc := strings.ToLower(translator.Locale()) _, ok := t.translators[lc] if ok && !override { return &ErrExistingTranslator{locale: translator.Locale()} } trans := newTranslator(translator) if t.fallback.Locale() == translator.Locale() { // because it's optional to have a fallback, I don't impose that limitation // don't know why you wouldn't but... if !override { return &ErrExistingTranslator{locale: translator.Locale()} } t.fallback = trans } t.translators[lc] = trans return nil } // VerifyTranslations runs through all locales and identifies any issues // eg. missing plural rules for a locale func (t *UniversalTranslator) VerifyTranslations() (err error) { for _, trans := range t.translators { err = trans.VerifyTranslations() if err != nil { return } } return }