1
0
mirror of https://github.com/kataras/iris.git synced 2026-01-07 12:07:28 +00:00

New: i18n pluralization and variables support and more...

fixes: #1649, #1648, #1641, #1650

relative to: #1597
This commit is contained in:
Gerasimos (Makis) Maropoulos
2020-09-29 19:19:19 +03:00
parent f224ded740
commit 4065819688
63 changed files with 2054 additions and 684 deletions

View File

@@ -176,8 +176,9 @@
* [Client-Side](compression/client/main.go)
* [Client-Side (using Iris)](compress/client-using-iris/main.go)
* Localization and Internationalization
* [i18n](i18n)
* [i18n templates and functions](i18n/i18n-template)
* [Basic](i18n/basic)
* [Ttemplates and Functions](i18n/template)
* [Pluralization and Variables](i18n/plurals)
* Authentication, Authorization & Bot Detection
* [Basic Authentication](auth/basicauth/main.go)
* [CORS](auth/cors)

View File

@@ -122,7 +122,7 @@ func generateTokenPair(accessJWT, refreshJWT *jwt.JWT) (TokenPair, error) {
refreshClaims := refreshJWT.Expiry(jwt.Claims{
ID: "refresh_kataras",
// For example, the User ID,
// this is nessecary to check against the database
// this is necessary to check against the database
// if the user still exist or has credentials to access our page.
Subject: "53afcf05-38a3-43c3-82af-8bbbe0e4a149",
})

View File

@@ -0,0 +1 @@
key1: "αυτό είναι μια τιμή από το πρώτο αρχείο: locale_multi_first"

View File

@@ -0,0 +1 @@
key1: "this is a value from the first file: locale_multi_first"

View File

@@ -1,2 +0,0 @@
Dog: "σκυλί"
HiDogs: Γειά {{plural (tr "Dog") .count }}

View File

@@ -1,2 +0,0 @@
Dog: "dog"
HiDogs: Hi {{plural (tr "Dog") .count }}

View File

@@ -1 +0,0 @@
key1 = αυτό είναι μια τιμή από το πρώτο αρχείο: locale_multi_first

View File

@@ -1 +0,0 @@
key1 = this is a value from the first file: locale_multi_first

View File

@@ -0,0 +1,5 @@
[message]
Encrypted = Encrypted
Message = Message
EncryptedMessage = {{tr "message.Encrypted"}} {{tr "message.Message"}}
HostResult = Store {{tr "message.EncryptedMessage"}} Online

View File

@@ -0,0 +1,89 @@
# Locale variables
#
# Unlike normal keys, the variables
# have limitations of: no ">x", "zero", "two" and template functions are supported.
# This is done to force developers to use small and easy to read variables for easier maintain process.
Vars:
- Minutes:
# possible keys:
# one
# "=x" - where x is a number
# "<x"
# other
# format - to customize the format, which defaults to %d .
one: "minute"
other: "minutes"
format: "%d" # defaults to that.
- Dogs:
"=5": "dogsssss"
one: "dog"
other: "dogs"
- Houses:
one: "house"
other: "houses"
- Gender:
"=1": "She" # 1 for female
"=2": "He" # 2 for male
# Using variables in raw string
YouLate: "You are %[1]d ${Minutes} late."
# Just a simple raw value
Classic: "classic"
# Pluralization, translate based on the plural count
# including the variables and their counts
FreeDay:
# possible keys:
# zero
# one
# two
# "=x"
# "<x"
# ">x"
# other
"=3": "You have three days and %[2]d ${Minutes} off." # "FreeDay" 3, 15 (plurals + variable pluralization)
one: "You have a day off" # "FreeDay", 1
other: "You have %[1]d free days" # "FreeDay", 5
# Sprintf-like raw translation
HeIsHome: "%s is home"
# Value without plural of its self but variables except pluralization
HouseCount: "${Gender} (%[3]s) has %[2]d ${Houses}"
# Same as above but with a template instead
VarTemplate: (${Gender}) {{tr "HeIsHome" .Name}}
# Template and non template with variables in the same plural key
VarTemplatePlural:
one: "${Gender} is awesome"
other: "other (${Gender}) has %[3]d ${Houses}"
"=5": "{{call .InlineJoin .Names}} are awesome"
TemplatePlural:
one: "{{.Name}} is unique"
"=5": "{{call .InlineJoin .Names}} are awesome"
# Same as above but it takes the variable counting through the map argument
TemplateVarTemplatePlural:
other: "These {{.PluralCount}} are wonderful, feeding {{.DogsCount}} ${Dogs} in total!"
# Local variables and section.
LocalVarsHouseCount:
Text: "${Gender} has %[2]d ${Houses}"
Vars:
- Gender:
"=3": "She"
"=4": "He"
- Houses:
one: "house"
other: "houses"
# Sections:
root:
user: Account
nav:
home: Home # nav.home
user: '{{tr "root.user"}}' # nav.user
more:
what: "this" # nav.more.what
even:
more: "yes" # nav.more.even.more
aplural: "You are %[1]d ${Minutes} late." # Tr("nav.more.even.aplural", 15)

View File

@@ -0,0 +1,175 @@
package main
import (
"strings"
"github.com/kataras/iris/v12"
)
const (
female = iota + 1
male
)
const tableStyle = `
<style>
a {
padding: 8px 8px;
text-decoration:none;
cursor:pointer;
color: #10a2ff;
}
table {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
border: 1px solid #cbcbcb;
}
table caption {
color: #000;
font: italic 85%/1 arial, sans-serif;
padding: 1em 0;
text-align: center;
}
table td,
table th {
border-left: 1px solid #cbcbcb;
border-width: 0 0 0 1px;
font-size: inherit;
margin: 0;
overflow: visible;
padding: 0.5em 1em;
}
table thead {
background-color: #10a2ff;
color: #fff;
text-align: left;
vertical-align: bottom;
}
table td {
background-color: transparent;
}
.table-odd td {
background-color: #f2f2f2;
}
.table-bordered td {
border-bottom: 1px solid #cbcbcb;
}
.table-bordered tbody > tr:last-child > td {
border-bottom-width: 0;
}
</style>
`
/*
$ go run .
Visit http://localhost:8080
*/
func main() {
app := iris.New()
err := app.I18n.Load("./locales/*/*", "en-US")
// ^ here we only use a single locale for the sake of the example,
// on a real app you can register as many languages as you want to support.
if err != nil {
panic(err)
}
app.Get("/", func(ctx iris.Context) {
ctx.HTML("<html><body>\n")
ctx.WriteString(tableStyle)
ctx.WriteString(`<table class="table-bordered table-odd">
<thead>
<tr>
<th>Key</th>
<th>Translation</th>
<th>Arguments</th>
</tr>
</thead><tbody>
`)
defer ctx.WriteString("</tbody></table></body></html>")
tr(ctx, "Classic")
tr(ctx, "YouLate", 1)
tr(ctx, "YouLate", 2)
tr(ctx, "FreeDay", 1)
tr(ctx, "FreeDay", 5)
tr(ctx, "FreeDay", 3, 15)
tr(ctx, "HeIsHome", "Peter")
tr(ctx, "HouseCount", female, 2, "Maria")
tr(ctx, "HouseCount", male, 1, "Peter")
tr(ctx, "nav.home")
tr(ctx, "nav.user")
tr(ctx, "nav.more.what")
tr(ctx, "nav.more.even.more")
tr(ctx, "nav.more.even.aplural", 1)
tr(ctx, "nav.more.even.aplural", 15)
tr(ctx, "VarTemplate", iris.Map{
"Name": "Peter",
"GenderCount": male,
})
tr(ctx, "VarTemplatePlural", 1, female)
tr(ctx, "VarTemplatePlural", 2, female, 1)
tr(ctx, "VarTemplatePlural", 2, female, 5)
tr(ctx, "VarTemplatePlural", 1, male)
tr(ctx, "VarTemplatePlural", 2, male, 1)
tr(ctx, "VarTemplatePlural", 2, male, 2)
tr(ctx, "VarTemplatePlural", iris.Map{
"PluralCount": 5,
"Names": []string{"Makis", "Peter"},
"InlineJoin": func(arr []string) string {
return strings.Join(arr, ", ")
},
})
tr(ctx, "TemplatePlural", iris.Map{
"PluralCount": 1,
"Name": "Peter",
})
tr(ctx, "TemplatePlural", iris.Map{
"PluralCount": 5,
"Names": []string{"Makis", "Peter"},
"InlineJoin": func(arr []string) string {
return strings.Join(arr, ", ")
},
})
tr(ctx, "VarTemplatePlural", 2, male, 4)
tr(ctx, "TemplateVarTemplatePlural", iris.Map{
"PluralCount": 3,
"DogsCount": 5,
})
tr(ctx, "message.HostResult")
tr(ctx, "LocalVarsHouseCount.Text", 3, 4)
})
app.Listen(":8080")
}
func tr(ctx iris.Context, key string, args ...interface{}) {
translation := ctx.Tr(key, args...)
ctx.Writef("<tr><td>%s</td><td>%s</td><td>%v</td></tr>\n", key, translation, args)
}

View File

@@ -0,0 +1,131 @@
package main_test
import (
"strings"
"testing"
"github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/httptest"
)
const (
female = iota + 1
male
)
func TestI18nPlurals(t *testing.T) {
handler := func(ctx iris.Context) {
tr(ctx, "Classic")
tr(ctx, "YouLate", 1)
tr(ctx, "YouLate", 2)
tr(ctx, "FreeDay", 1)
tr(ctx, "FreeDay", 5)
tr(ctx, "FreeDay", 3, 15)
tr(ctx, "HeIsHome", "Peter")
tr(ctx, "HouseCount", female, 2, "Maria")
tr(ctx, "HouseCount", male, 1, "Peter")
tr(ctx, "nav.home")
tr(ctx, "nav.user")
tr(ctx, "nav.more.what")
tr(ctx, "nav.more.even.more")
tr(ctx, "nav.more.even.aplural", 1)
tr(ctx, "nav.more.even.aplural", 15)
tr(ctx, "VarTemplate", iris.Map{
"Name": "Peter",
"GenderCount": male,
})
tr(ctx, "VarTemplatePlural", 1, female)
tr(ctx, "VarTemplatePlural", 2, female, 1)
tr(ctx, "VarTemplatePlural", 2, female, 5)
tr(ctx, "VarTemplatePlural", 1, male)
tr(ctx, "VarTemplatePlural", 2, male, 1)
tr(ctx, "VarTemplatePlural", 2, male, 2)
tr(ctx, "VarTemplatePlural", iris.Map{
"PluralCount": 5,
"Names": []string{"Makis", "Peter"},
"InlineJoin": func(arr []string) string {
return strings.Join(arr, ", ")
},
})
tr(ctx, "TemplatePlural", iris.Map{
"PluralCount": 1,
"Name": "Peter",
})
tr(ctx, "TemplatePlural", iris.Map{
"PluralCount": 5,
"Names": []string{"Makis", "Peter"},
"InlineJoin": func(arr []string) string {
return strings.Join(arr, ", ")
},
})
tr(ctx, "VarTemplatePlural", 2, male, 4)
tr(ctx, "TemplateVarTemplatePlural", iris.Map{
"PluralCount": 3,
"DogsCount": 5,
})
tr(ctx, "message.HostResult")
tr(ctx, "LocalVarsHouseCount.Text", 3, 4)
}
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)
defer r.Body.Close()
httptest.Do(w, r, handler, func(app *iris.Application) {
err := app.I18n.Load("./locales/*/*", "en-US", "el-GR")
if err != nil {
panic(err)
}
})
expected := `Classic=classic
YouLate=You are 1 minute late.
YouLate=You are 2 minutes late.
FreeDay=You have a day off
FreeDay=You have 5 free days
FreeDay=You have three days and 15 minutes off.
HeIsHome=Peter is home
HouseCount=She (Maria) has 2 houses
HouseCount=He (Peter) has 1 house
nav.home=Home
nav.user=Account
nav.more.what=this
nav.more.even.more=yes
nav.more.even.aplural=You are 1 minute late.
nav.more.even.aplural=You are 15 minutes late.
VarTemplate=(He) Peter is home
VarTemplatePlural=She is awesome
VarTemplatePlural=other (She) has 1 house
VarTemplatePlural=other (She) has 5 houses
VarTemplatePlural=He is awesome
VarTemplatePlural=other (He) has 1 house
VarTemplatePlural=other (He) has 2 houses
VarTemplatePlural=Makis, Peter are awesome
TemplatePlural=Peter is unique
TemplatePlural=Makis, Peter are awesome
VarTemplatePlural=other (He) has 4 houses
TemplateVarTemplatePlural=These 3 are wonderful, feeding 5 dogsssss in total!
message.HostResult=Store Encrypted Message Online
LocalVarsHouseCount.Text=She has 4 houses
`
if got := w.Body.String(); expected != got {
t.Fatalf("expected:\n'%s'\n\nbut got:\n'%s'", expected, got)
}
}
func tr(ctx iris.Context, key string, args ...interface{}) {
translation := ctx.Tr(key, args...)
ctx.Writef("%s=%s\n", key, translation)
}

View File

@@ -0,0 +1 @@
HiDogs: Hi %d {{plural (tr "Dog") .count }}

View File

@@ -0,0 +1,2 @@
Dog: "dog"
HiDogs: Hi %d {{plural (tr "Dog") .count }}

View File

@@ -5,13 +5,20 @@ import (
"text/template"
"github.com/kataras/iris/v12"
// go get -u github.com/gertd/go-pluralize
"github.com/gertd/go-pluralize"
// go get -u golang.org/x/text/message
"golang.org/x/text/feature/plural"
"golang.org/x/text/language"
"golang.org/x/text/message"
)
/*
Iris I18n supports text/template inside the translation values.
Follow this example to learn how to use that feature.
This is just an example on how to use template functions.
See the "plurals" example for a more comprehensive pluralization support instead.
*/
func main() {
@@ -22,7 +29,20 @@ func main() {
func newApp() *iris.Application {
app := iris.New()
pluralize := pluralize.NewClient()
// set the printers after load, so they can be done by loop of available languages.
printers := make(map[string]*message.Printer)
message.Set(language.Greek, "Hello %d dog",
plural.Selectf(1, "%d",
"one", "Γεια σου σκυλί",
"other", "Γεια σας %[1]d σκυλιά",
))
/* by variable, single word:
message.Set(language.Greek, "Hi %d dog(s)",
catalog.Var("dogs", plural.Selectf(1, "%d", "one", "σκυλί", "other", "σκυλιά")),
catalog.String("Γεια %[1]d ${dogs}"))
*/
// Set custom functions per locale!
app.I18n.Loader.Funcs = func(current iris.Locale) template.FuncMap {
@@ -30,12 +50,7 @@ func newApp() *iris.Application {
"plural": func(word string, count int) string {
// Your own implementation or use a 3rd-party package
// like we do here.
//
// Note that this is only for english,
// but you can use the "current" locale
// and make a map with dictionaries to
// pluralize words based on the given language.
return pluralize.Pluralize(word, count, true)
return printers[current.Language()].Sprintf(word, count)
},
"uppercase": func(word string) string {
return strings.ToUpper(word)
@@ -51,6 +66,12 @@ func newApp() *iris.Application {
panic(err)
}
for _, tag := range app.I18n.Tags() {
printers[tag.String()] = message.NewPrinter(tag)
}
message.NewPrinter(language.Greek).Printf("Hello %d dog", 2)
app.Get("/", func(ctx iris.Context) {
text := ctx.Tr("HiDogs", iris.Map{
"count": 2,

View File

@@ -3,4 +3,3 @@ module app
go 1.15
require github.com/kataras/iris/v12 master

View File

@@ -22,6 +22,7 @@ func main() {
// the .Name inside the ./templates/hi.html.
ctx.ViewData("Name", "iris")
// render the template with the file name relative to the './templates'.
// file extension is OPTIONAL.
ctx.View("hi.html")
})

View File

@@ -12,14 +12,14 @@ func main() {
app.RegisterView(tmpl)
app.Get("/", func(ctx iris.Context) {
ctx.View("index.ace", iris.Map{
ctx.View("index", iris.Map{
"Title": "Title of The Page",
})
})
app.Get("/layout", func(ctx iris.Context) {
ctx.ViewLayout("layouts/main.ace") // layout for that response.
ctx.View("index.ace", iris.Map{
ctx.ViewLayout("layouts/main") // layout for that response.
ctx.View("index", iris.Map{ // file extension is optional.
"Title": "Title of the main Page",
})
})