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:
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
key1: "αυτό είναι μια τιμή από το πρώτο αρχείο: locale_multi_first"
|
||||
@@ -0,0 +1 @@
|
||||
key1: "this is a value from the first file: locale_multi_first"
|
||||
@@ -1,2 +0,0 @@
|
||||
Dog: "σκυλί"
|
||||
HiDogs: Γειά {{plural (tr "Dog") .count }}
|
||||
@@ -1,2 +0,0 @@
|
||||
Dog: "dog"
|
||||
HiDogs: Hi {{plural (tr "Dog") .count }}
|
||||
@@ -1 +0,0 @@
|
||||
key1 = αυτό είναι μια τιμή από το πρώτο αρχείο: locale_multi_first
|
||||
@@ -1 +0,0 @@
|
||||
key1 = this is a value from the first file: locale_multi_first
|
||||
5
_examples/i18n/plurals/locales/en-US/1648.ini
Normal file
5
_examples/i18n/plurals/locales/en-US/1648.ini
Normal file
@@ -0,0 +1,5 @@
|
||||
[message]
|
||||
Encrypted = Encrypted
|
||||
Message = Message
|
||||
EncryptedMessage = {{tr "message.Encrypted"}} {{tr "message.Message"}}
|
||||
HostResult = Store {{tr "message.EncryptedMessage"}} Online
|
||||
89
_examples/i18n/plurals/locales/en-US/welcome.yml
Normal file
89
_examples/i18n/plurals/locales/en-US/welcome.yml
Normal 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)
|
||||
175
_examples/i18n/plurals/main.go
Normal file
175
_examples/i18n/plurals/main.go
Normal 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)
|
||||
}
|
||||
131
_examples/i18n/plurals/main_test.go
Normal file
131
_examples/i18n/plurals/main_test.go
Normal 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)
|
||||
}
|
||||
1
_examples/i18n/template/locales/el-GR/welcome.yml
Normal file
1
_examples/i18n/template/locales/el-GR/welcome.yml
Normal file
@@ -0,0 +1 @@
|
||||
HiDogs: Hi %d {{plural (tr "Dog") .count }}
|
||||
2
_examples/i18n/template/locales/en-US/welcome.yml
Normal file
2
_examples/i18n/template/locales/en-US/welcome.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
Dog: "dog"
|
||||
HiDogs: Hi %d {{plural (tr "Dog") .count }}
|
||||
@@ -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,
|
||||
@@ -3,4 +3,3 @@ module app
|
||||
go 1.15
|
||||
|
||||
require github.com/kataras/iris/v12 master
|
||||
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
|
||||
@@ -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",
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user