add LoadKV method on Iris.Application.I18N instance

This commit is contained in:
Gerasimos (Makis) Maropoulos 2023-03-18 15:43:18 +02:00
parent 41326d4ddd
commit 0954986a66
No known key found for this signature in database
GPG Key ID: B9839E9CD30B7B6B
9 changed files with 108 additions and 11 deletions

View File

@ -23,6 +23,8 @@ Developers are not forced to upgrade if they don't really need it. Upgrade whene
Change applies to `master` branch. Change applies to `master` branch.
- Add `LoadKV` method on `Iris.Application.I18N` instance. It should be used when no locale files are available. It loads locales via pure Go Map (or database decoded values).
- Remove [ace](https://github.com/eknkc/amber) template parser support, as it was discontinued by its author more than five years ago. - Remove [ace](https://github.com/eknkc/amber) template parser support, as it was discontinued by its author more than five years ago.
# Sa, 11 March 2023 | v12.2.0 # Sa, 11 March 2023 | v12.2.0

View File

@ -4,7 +4,7 @@ User = Λογαριασμός
[debug] [debug]
Title = Μενού προγραμματιστή Title = Μενού προγραμματιστή
AccessLog = Πρόσβαση στο αρχείο καταγραφής AccessLog = Πρόσβαση στο αρχείο καταγραφής
AccessLogClear = Καθαρισμός {{tr "debug.AccessLog"}} AccessLogClear = Καθαρισμός {{ tr "debug.AccessLog" }}
[user.connections] [user.connections]
Title = {{tr "nav.User"}} Συνδέσεις Title = {{ tr "nav.User" }} Συνδέσεις

View File

@ -1,4 +1,4 @@
[forms] [forms]
member = μέλος member = μέλος
register = Γίνε {{uppercase (tr "forms.member") }} register = Γίνε {{ uppercase (tr "forms.member") }}
registered = εγγεγραμμένοι registered = εγγεγραμμένοι

View File

@ -6,7 +6,7 @@ User = Account
[debug] [debug]
Title = Developer Menu Title = Developer Menu
AccessLog = Access Log AccessLog = Access Log
AccessLogClear = Clear {{tr "debug.AccessLog"}} AccessLogClear = Clear {{ tr "debug.AccessLog" }}
[user.connections] [user.connections]
Title = {{tr "nav.User"}} Connections Title = {{ tr "nav.User" }} Connections

View File

@ -1,4 +1,4 @@
[forms] [forms]
member = member member = member
register = Become a {{uppercase (tr "forms.member") }} register = Become a {{ uppercase (tr "forms.member") }}
registered = registered registered = registered

View File

@ -13,6 +13,16 @@ var embeddedFS embed.FS
func main() { func main() {
app := newApp() app := newApp()
// http://localhost:8080
// http://localhost:8080?lang=el
// http://localhost:8080?lang=el
// http://localhost:8080?lang=el-GR
// http://localhost:8080?lang=en
// http://localhost:8080?lang=en-US
//
// http://localhost:8080/title
// http://localhost:8080/title?lang=el-GR
// ...
app.Listen(":8080") app.Listen(":8080")
} }
@ -30,11 +40,14 @@ func newApp() *iris.Application {
// Instead of: // Instead of:
// err := app.I18n.Load("./locales/*/*.ini", "en-US", "el-GR") // err := app.I18n.Load("./locales/*/*.ini", "en-US", "el-GR")
// Apply the below in order to build with embedded locales inside your executable binary. // apply the below in order to build with embedded locales inside your executable binary.
err := app.I18n.LoadFS(embeddedFS, "./embedded/locales/*/*.ini", "en-US", "el-GR") err := app.I18n.LoadFS(embeddedFS, "./embedded/locales/*/*.ini", "en-US", "el-GR")
if err != nil { if err != nil {
panic(err) panic(err)
} } // OR to load all languages by filename:
// app.I18n.LoadFS(embeddedFS, "./embedded/locales/*/*.ini")
// Then set the default language using:
// app.I18n.SetDefault("en-US")
app.Get("/", func(ctx iris.Context) { app.Get("/", func(ctx iris.Context) {
text := ctx.Tr("forms.register") // en-US: prints "Become a MEMBER". text := ctx.Tr("forms.register") // en-US: prints "Become a MEMBER".

View File

@ -141,8 +141,8 @@ func (i *I18n) LoadAssets(assetNames func() []string, asset func(string) ([]byte
return i.Reset(Assets(assetNames, asset, i.Loader), languages...) return i.Reset(Assets(assetNames, asset, i.Loader), languages...)
} }
// LoadFS is a method shortcut to load files using `embed.FS` or `fs.FS` or // LoadFS is a method shortcut to load files using
// `http.FileSystem` or `string` (local directory). // an `embed.FS` or `fs.FS` or `http.FileSystem` value.
// The "pattern" is a classic glob pattern. // The "pattern" is a classic glob pattern.
// //
// See `New` and `FS` package-level functions for more. // See `New` and `FS` package-level functions for more.
@ -156,6 +156,13 @@ func (i *I18n) LoadFS(fileSystem fs.FS, pattern string, languages ...string) err
return i.Reset(loader, languages...) return i.Reset(loader, languages...)
} }
// LoadKV is a method shortcut to load locales from a map of specified languages.
// See `KV` package-level function for more.
func (i *I18n) LoadKV(langMap LangMap, languages ...string) error {
loader := KV(langMap, i.Loader)
return i.Reset(loader, languages...)
}
// Reset sets the locales loader and languages. // Reset sets the locales loader and languages.
// It is not meant to be used by users unless // It is not meant to be used by users unless
// a custom `Loader` must be used instead of the default one. // a custom `Loader` must be used instead of the default one.
@ -300,6 +307,16 @@ func parsePath(m *Matcher, path string) int {
return -1 return -1
} }
func parseLanguageName(m *Matcher, name string) int {
if t, err := language.Parse(name); err == nil {
if _, index, conf := m.MatchOrAdd(t); conf > language.Low {
return index
}
}
return -1
}
func reverseStrings(s []string) []string { func reverseStrings(s []string) []string {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i] s[i], s[j] = s[j], s[i]

View File

@ -5,6 +5,7 @@ import (
"text/template" "text/template"
"github.com/kataras/iris/v12/context" "github.com/kataras/iris/v12/context"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
"golang.org/x/text/message/catalog" "golang.org/x/text/message/catalog"

View File

@ -73,6 +73,70 @@ func FS(fileSystem fs.FS, pattern string, options LoaderConfig) (Loader, error)
return load(assetNames, assetFunc, options), nil return load(assetNames, assetFunc, options), nil
} }
// LangMap key as language (e.g. "el-GR") and value as a map of key-value pairs (e.g. "hello": "Γειά").
type LangMap = map[string]map[string]interface{}
// KV is a loader which accepts a map of language(key) and the available key-value pairs.
// Example Code:
//
// m := i18n.LangMap{
// "en-US": map[string]interface{}{
// "hello": "Hello",
// },
// "el-GR": map[string]interface{}{
// "hello": "Γειά",
// },
// }
//
// app := iris.New()
// [...]
// app.I18N.LoadKV(m)
// app.I18N.SetDefault("en-US")
func KV(langMap LangMap, opts ...LoaderConfig) Loader {
return func(m *Matcher) (Localizer, error) {
options := DefaultLoaderConfig
if len(opts) > 0 {
options = opts[0]
}
languageIndexes := make([]int, 0, len(langMap))
keyValuesMulti := make([]map[string]interface{}, 0, len(langMap))
for languageName, pairs := range langMap {
langIndex := parseLanguageName(m, languageName) // matches and adds the language tag to m.Languages.
languageIndexes = append(languageIndexes, langIndex)
keyValuesMulti = append(keyValuesMulti, pairs)
}
cat, err := internal.NewCatalog(m.Languages, options)
if err != nil {
return nil, err
}
for _, langIndex := range languageIndexes {
if langIndex == -1 {
// If loader has more languages than defined for use in New function,
// e.g. when New(KV(m), "en-US") contains el-GR and en-US but only "en-US" passed.
continue
}
kv := keyValuesMulti[langIndex]
err := cat.Store(langIndex, kv)
if err != nil {
return nil, err
}
}
if n := len(cat.Locales); n == 0 {
return nil, fmt.Errorf("locales not found in map")
} else if options.Strict && n < len(m.Languages) {
return nil, fmt.Errorf("locales expected to be %d but %d parsed", len(m.Languages), n)
}
return cat, nil
}
}
// DefaultLoaderConfig represents the default loader configuration. // DefaultLoaderConfig represents the default loader configuration.
var DefaultLoaderConfig = LoaderConfig{ var DefaultLoaderConfig = LoaderConfig{
Left: "{{", Left: "{{",
@ -88,7 +152,7 @@ var DefaultLoaderConfig = LoaderConfig{
// and any Loader options. // and any Loader options.
// It returns a valid `Loader` which loads and maps the locale files. // It returns a valid `Loader` which loads and maps the locale files.
// //
// See `Glob`, `Assets` and `LoaderConfig` too. // See `FS`, `Glob`, `Assets` and `LoaderConfig` too.
func load(assetNames []string, asset func(string) ([]byte, error), options LoaderConfig) Loader { func load(assetNames []string, asset func(string) ([]byte, error), options LoaderConfig) Loader {
return func(m *Matcher) (Localizer, error) { return func(m *Matcher) (Localizer, error) {
languageFiles, err := m.ParseLanguageFiles(assetNames) languageFiles, err := m.ParseLanguageFiles(assetNames)