Skip to content

Commit

Permalink
Merge branch 'locales-based'
Browse files Browse the repository at this point in the history
  • Loading branch information
Dean Karn authored and Dean Karn committed Aug 23, 2016
2 parents 367e66a + 478fb72 commit fe3f367
Show file tree
Hide file tree
Showing 2,242 changed files with 1,251 additions and 44,156 deletions.
200 changes: 68 additions & 132 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,23 @@ Universal Translator is an i18n Translator for Go/Golang using CLDR data + plura

Why another i18n library?
--------------------------
I noticed that most libraries out there use static files for translations, which I'm not against just there is not option for coding it inline,
as well as having formats which do not handle all plural rules, or are overcomplicated. There is also very little in the way of helping the user
know about what plural translations are needed for each language, no easy grouping to say, display all translations for a page...
Because none of the plural rules seem to be correct out there, including the previous implimentation 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 added from [CLDR](http://cldr.unicode.org/index/downloads) data
- [x] Use fmt.Sprintf() for translation string parsing
- [x] Add Translations in code
- [x] Prints the supported plural rules for a given translators locale using translator.PrintPluralRules()
- [x] Plural Translations
- [x] Date, Time & DateTime formatting
- [x] Number, Whole Number formatting
- [x] Currency both standard & accounting, formatting i.e. -$1,234.50 vs ($1,234.50)
- [x] Handles BC and AD Dates. i.e. January 2, 300 BC
- [x] Rules generated from the latest [CLDR](http://cldr.unicode.org/index/downloads) data, v29
- [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 )
- [ ] Support loading translations from files
- [ ] Exporting translations to file, mainly for getting them professionally translated
- [ ] Code Generation for translation files -> Go code.. i.e. after it has been professionally translated
- [ ] Printing of grouped translations, i.e. all transations for the homepage
- [ ] Tests for all languages, I need help with this one see below

Full Language tests
--------------------
I could sure use your help adding tests for every language, it is a huge undertaking and I just don't have the free time to do it all at the moment;
any help would be **greatly appreciated!!!!** please see [issue](https://github.com/go-playground/universal-translator/issues/1) for details.
- [ ] Tests for all languages, I need help with this, please see [here](https://github.com/go-playground/locales/issues/1)

Installation
-----------
Expand All @@ -44,13 +36,7 @@ Use go get

```go
go get github.com/go-playground/universal-translator
```

or to update

```go
go get -u github.com/go-playground/universal-translator
```
```

Usage
-------
Expand All @@ -59,120 +45,70 @@ package main

import (
"fmt"
"time"

"github.com/go-playground/locales"
"github.com/go-playground/universal-translator"

// DONE this way to avoid huge compile times + memory for all languages, although it would
// be nice for all applications to support all languages... that's not reality
_ "github.com/go-playground/universal-translator/resources/locales"
)

// only one instance as translators within are shared + goroutine safe
var universalTraslator *ut.UniversalTranslator

func main() {
trans, _ := ut.GetTranslator("en")

trans.PrintPluralRules()
// OUTPUT:
// Translator locale 'en' supported rules:
//- PluralRuleOne
//- PluralRuleOther

// add a singular translation
trans.Add(ut.PluralRuleOne, "homepage", "welcome_msg", "Welcome to site %s")

// add singular + plural translation(s)
trans.Add(ut.PluralRuleOne, "homepage", "day_warning", "You only have %d day left in your trial")
trans.Add(ut.PluralRuleOther, "homepage", "day_warning", "You only have %d day's left in your trial")

// translate singular
translated := trans.T("welcome_msg", "Joey Bloggs")
fmt.Println(translated)
// OUTPUT: Welcome to site Joey Bloggs

// What if something went wrong? then translated would output "" (blank)
// How do I catch errors?
translated, err := trans.TSafe("welcome_m", "Joey Bloggs")
fmt.Println(translated)
// OUTPUT: ""
fmt.Println(err)
// OUTPUT: ***** WARNING:***** Translation Key 'welcome_m' Not Found

// NOTE: there is a Safe variant of most of the Translation and Formatting functions if you need them,
// for brevity will be using the non safe ones for the rest of this example

// The second parameter below, count, is needed as the final variable is a varadic and would not
// know which one to use in applying the plural rules.
// translate singular/plural
translated = trans.P("day_warning", 3, 3)
fmt.Println(translated)
// OUTPUT: You only have 3 day's left in your trial

translated = trans.P("day_warning", 1, 1)
fmt.Println(translated)
// OUTPUT: You only have 1 day left in your trial

// There are Full, Long, Medium and Short function for each of the following
dtString := "Jan 2, 2006 at 3:04:05pm"
dt, _ := time.Parse(dtString, dtString)

formatted := trans.FmtDateFull(dt)
fmt.Println(formatted)
// OUTPUT: Monday, January 2, 2006

formatted = trans.FmtDateShort(dt)
fmt.Println(formatted)
// OUTPUT: 1/2/06

formatted = trans.FmtTimeFull(dt)
fmt.Println(formatted)
// OUTPUT: 3:04:05 PM

formatted = trans.FmtDateTimeFull(dt)
fmt.Println(formatted)
// OUTPUT: Monday, January 2, 2006 at 3:04:05 PM

formatted = trans.FmtCurrency(ut.CurrencyStandard, "USD", 1234.50)
fmt.Println(formatted)
// OUTPUT: $1,234.50

formatted = trans.FmtCurrency(ut.CurrencyStandard, "USD", -1234.50)
fmt.Println(formatted)
// OUTPUT: -$1,234.50

formatted = trans.FmtCurrency(ut.CurrencyAccounting, "USD", -1234.50)
fmt.Println(formatted)
// OUTPUT: ($1,234.50)

formatted = trans.FmtCurrencyWhole(ut.CurrencyStandard, "USD", -1234.50)
fmt.Println(formatted)
// OUTPUT: -$1,234

formatted = trans.FmtNumber(1234.50)
fmt.Println(formatted)
// OUTPUT: 1,234.5

formatted = trans.FmtNumberWhole(1234.50)
fmt.Println(formatted)
// OUTPUT: 1,234

// NOTE: this example is omitting allot of error checking for brevity

universalTraslator, _ = ut.New("en", "en", "en_CA", "nl", "fr")

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
en.Add("welcome", "Welcome {0} to our test")

// add language translations dependant on cardinal plural rules
en.AddCardinal("days", "You have {0} day left to register", locales.PluralRuleOne)
en.AddCardinal("days", "You have {0} days left to register", locales.PluralRuleOther)

// add language translations dependant on ordinal plural rules
en.AddOrdinal("day-of-month", "{0}st", locales.PluralRuleOne)
en.AddOrdinal("day-of-month", "{0}nd", locales.PluralRuleTwo)
en.AddOrdinal("day-of-month", "{0}rd", locales.PluralRuleFew)
en.AddOrdinal("day-of-month", "{0}th", locales.PluralRuleOther)

// 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)

// 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, string(en.FmtNumber(1, 0)))) // you'd normally have variables defined for 1 and 0
fmt.Println(en.C("days", 2, 0, string(en.FmtNumber(2, 0))))
fmt.Println(en.C("days", 10456.25, 2, string(en.FmtNumber(10456.25, 2))))

fmt.Println(en.O("day-of-month", 1, 0, string(en.FmtNumber(1, 0))))
fmt.Println(en.O("day-of-month", 2, 0, string(en.FmtNumber(2, 0))))
fmt.Println(en.O("day-of-month", 3, 0, string(en.FmtNumber(3, 0))))
fmt.Println(en.O("day-of-month", 4, 0, string(en.FmtNumber(4, 0))))
fmt.Println(en.O("day-of-month", 10456.25, 0, string(en.FmtNumber(10456.25, 0))))

fmt.Println(en.R("between", 0, 0, 1, 0, string(en.FmtNumber(0, 0)), string(en.FmtNumber(1, 0))))
fmt.Println(en.R("between", 1, 0, 2, 0, string(en.FmtNumber(1, 0)), string(en.FmtNumber(2, 0))))
fmt.Println(en.R("between", 1, 0, 100, 0, string(en.FmtNumber(1, 0)), string(en.FmtNumber(100, 0))))
}
```

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/universal-translator/issues/1) for details.

Thanks to some help, the following languages have tests:

- [x] en - English US
- [x] th - Thai thanks to @prideloki

Special Thanks
--------------
Special thanks to the following libraries that not only inspired, but that I borrowed a bunch of code from to create this.. ultimately there were many changes made and more to come, but without them would have taken forever to just get started.
* [cldr](https://github.com/theplant/cldr) - A golang i18n tool using CLDR data
* [i18n](https://github.com/vube/i18n) - golang package for basic i18n features, including message translation and number formatting

Misc
-------
Library is not at 1.0 yet, but don't forsee any major API changes; will raise to 1.0 once I've used it completely in at least one project without issue.
Please see issue [here](https://github.com/go-playground/locales/issues/1) for details.
60 changes: 60 additions & 0 deletions benchmarks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ut

import "testing"

func BenchmarkBasicTranslation(b *testing.B) {

ut, _ := New("en", "en")
loc := ut.FindTranslator("en")
loc.Add("welcome", "Welcome to the site")
loc.Add("welcome-user", "Welcome to the site {0}")
loc.Add("welcome-user2", "Welcome to the site {0}, your location is {1}")

b.Run("", func(b *testing.B) {
for i := 0; i < b.N; i++ {
loc.T("welcome")
}
})

b.Run("Parallel", func(b *testing.B) {

b.RunParallel(func(pb *testing.PB) {

for pb.Next() {
loc.T("welcome")
}
})
})

b.Run("With1Param", func(b *testing.B) {
for i := 0; i < b.N; i++ {
loc.T("welcome-user", "Joeybloggs")
}
})

b.Run("ParallelWith1Param", func(b *testing.B) {

b.RunParallel(func(pb *testing.PB) {

for pb.Next() {
loc.T("welcome-user", "Joeybloggs")
}
})
})

b.Run("With2Param", func(b *testing.B) {
for i := 0; i < b.N; i++ {
loc.T("welcome-user2", "Joeybloggs", "/dev/tty0")
}
})

b.Run("ParallelWith2Param", func(b *testing.B) {

b.RunParallel(func(pb *testing.PB) {

for pb.Next() {
loc.T("welcome-user2", "Joeybloggs", "/dev/tty0")
}
})
})
}
Loading

0 comments on commit fe3f367

Please sign in to comment.