mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
adding package gi18n
This commit is contained in:
38
util/gi18n/gi18n.go
Normal file
38
util/gi18n/gi18n.go
Normal file
@ -0,0 +1,38 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gi18n implements internationalization and localization.
|
||||
package gi18n
|
||||
|
||||
var (
|
||||
defaultTranslator = Instance()
|
||||
)
|
||||
|
||||
// SetPath sets the directory path storing i18n files.
|
||||
func SetPath(path string) error {
|
||||
return defaultTranslator.SetPath(path)
|
||||
}
|
||||
|
||||
// SetLanguage sets the language for translator.
|
||||
func SetLanguage(language string) {
|
||||
defaultTranslator.SetLanguage(language)
|
||||
}
|
||||
|
||||
// SetDelimiters sets the delimiters for translator.
|
||||
func SetDelimiters(left, right string) {
|
||||
defaultTranslator.SetDelimiters(left, right)
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
func T(content string, language ...string) string {
|
||||
return defaultTranslator.T(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func Translate(content string, language ...string) string {
|
||||
return defaultTranslator.Translate(content, language...)
|
||||
}
|
||||
31
util/gi18n/gi18n_instance.go
Normal file
31
util/gi18n/gi18n_instance.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gi18n
|
||||
|
||||
import "github.com/gogf/gf/container/gmap"
|
||||
|
||||
const (
|
||||
// Default group name for instance usage.
|
||||
DEFAULT_NAME = "default"
|
||||
)
|
||||
|
||||
var (
|
||||
// Instances map.
|
||||
instances = gmap.NewStrAnyMap(true)
|
||||
)
|
||||
|
||||
// Instance returns an instance of Resource.
|
||||
// The parameter <name> is the name for the instance.
|
||||
func Instance(name ...string) *Translator {
|
||||
key := DEFAULT_NAME
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
key = name[0]
|
||||
}
|
||||
return instances.GetOrSetFuncLock(key, func() interface{} {
|
||||
return New()
|
||||
}).(*Translator)
|
||||
}
|
||||
208
util/gi18n/gi18n_translator.go
Normal file
208
util/gi18n/gi18n_translator.go
Normal file
@ -0,0 +1,208 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gi18n
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
|
||||
"github.com/gogf/gf/text/gregex"
|
||||
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/encoding/gjson"
|
||||
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gres"
|
||||
)
|
||||
|
||||
// Translator, it is concurrent safe, supporting hot reload.
|
||||
type Translator struct {
|
||||
mu sync.RWMutex
|
||||
data map[string]map[string]string // Translating map.
|
||||
pattern string // Pattern for regex parsing.
|
||||
options Options // configuration options.
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Path string // I18n files storage path.
|
||||
Language string // Local language.
|
||||
Delimiters []string // Delimiters for variable parsing.
|
||||
}
|
||||
|
||||
var (
|
||||
defaultDelimiters = []string{"{#", "}"}
|
||||
)
|
||||
|
||||
func New(options ...Options) *Translator {
|
||||
var opts Options
|
||||
if len(options) > 0 {
|
||||
opts = options[0]
|
||||
} else {
|
||||
opts = DefaultOptions()
|
||||
}
|
||||
if len(opts.Delimiters) == 0 {
|
||||
opts.Delimiters = defaultDelimiters
|
||||
}
|
||||
return &Translator{
|
||||
options: opts,
|
||||
pattern: fmt.Sprintf(
|
||||
`%s(\w+)%s`,
|
||||
gregex.Quote(opts.Delimiters[0]),
|
||||
gregex.Quote(opts.Delimiters[1]),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func DefaultOptions() Options {
|
||||
path := "i18n"
|
||||
realPath, _ := gfile.Search(path)
|
||||
if realPath != "" {
|
||||
path = realPath
|
||||
} else {
|
||||
path = "/" + path
|
||||
}
|
||||
return Options{
|
||||
Path: path,
|
||||
Delimiters: []string{"{#", "}"},
|
||||
}
|
||||
}
|
||||
|
||||
// SetPath sets the directory path storing i18n files.
|
||||
func (t *Translator) SetPath(path string) error {
|
||||
if gres.Contains(path) {
|
||||
t.options.Path = path
|
||||
} else {
|
||||
realPath, _ := gfile.Search(path)
|
||||
if realPath == "" {
|
||||
return errors.New(fmt.Sprintf(`%s does not exist`, path))
|
||||
}
|
||||
t.options.Path = realPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLanguage sets the language for translator.
|
||||
func (t *Translator) SetLanguage(language string) {
|
||||
t.options.Language = language
|
||||
}
|
||||
|
||||
// SetDelimiters sets the delimiters for translator.
|
||||
func (t *Translator) SetDelimiters(left, right string) {
|
||||
t.pattern = fmt.Sprintf(`%s(\w+)%s`, gregex.Quote(left), gregex.Quote(right))
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
func (t *Translator) T(content string, language ...string) string {
|
||||
return t.Translate(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (t *Translator) Translate(content string, language ...string) string {
|
||||
t.init()
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
var data map[string]string
|
||||
if len(language) > 0 {
|
||||
data = t.data[language[0]]
|
||||
} else {
|
||||
data = t.data[t.options.Language]
|
||||
}
|
||||
if data == nil {
|
||||
return content
|
||||
}
|
||||
// Parse content as name.
|
||||
if v, ok := data[content]; ok {
|
||||
return v
|
||||
}
|
||||
// Parse content as variables container.
|
||||
result, _ := gregex.ReplaceStringFuncMatch(t.pattern, content, func(match []string) string {
|
||||
if v, ok := data[match[1]]; ok {
|
||||
return v
|
||||
}
|
||||
return match[0]
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func (t *Translator) init() {
|
||||
t.mu.RLock()
|
||||
if t.data != nil {
|
||||
t.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
t.mu.RUnlock()
|
||||
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
if gres.Contains(t.options.Path) {
|
||||
files := gres.ScanDirFile(t.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var name string
|
||||
var lang string
|
||||
var array []string
|
||||
t.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
name = file.Name()
|
||||
path = name[len(t.options.Path)+1:]
|
||||
array = strings.Split(path, "/")
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if t.data[lang] == nil {
|
||||
t.data[lang] = make(map[string]string)
|
||||
}
|
||||
j, _ := gjson.LoadContent(file.Content())
|
||||
if j != nil {
|
||||
for k, v := range j.ToMap() {
|
||||
t.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
files, _ := gfile.ScanDirFile(t.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var lang string
|
||||
var array []string
|
||||
t.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
path = file[len(t.options.Path)+1:]
|
||||
array = strings.Split(path, "/")
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if t.data[lang] == nil {
|
||||
t.data[lang] = make(map[string]string)
|
||||
}
|
||||
j, _ := gjson.LoadContent(gfile.GetBytes(file))
|
||||
if j != nil {
|
||||
for k, v := range j.ToMap() {
|
||||
t.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
t.mu.Lock()
|
||||
t.data = nil
|
||||
t.mu.Unlock()
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
31
util/gi18n/gi18n_unit_test.go
Normal file
31
util/gi18n/gi18n_unit_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2019 gf Author(https://github.com/gogf/gf). All Rights Reserved.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the MIT License.
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gi18n_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/util/gi18n"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
t := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n",
|
||||
})
|
||||
t.SetLanguage("none")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
t.SetLanguage("ja")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "こんにちは世界")
|
||||
})
|
||||
}
|
||||
2
util/gi18n/testdata/i18n-dir/en/hello.toml
vendored
Normal file
2
util/gi18n/testdata/i18n-dir/en/hello.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello = "Hello"
|
||||
2
util/gi18n/testdata/i18n-dir/en/world.toml
vendored
Normal file
2
util/gi18n/testdata/i18n-dir/en/world.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
world = "World"
|
||||
2
util/gi18n/testdata/i18n-dir/ja/hello.yaml
vendored
Normal file
2
util/gi18n/testdata/i18n-dir/ja/hello.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
1
util/gi18n/testdata/i18n-dir/ja/world.yaml
vendored
Normal file
1
util/gi18n/testdata/i18n-dir/ja/world.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
world: "世界"
|
||||
1
util/gi18n/testdata/i18n-dir/ru/hello.ini
vendored
Normal file
1
util/gi18n/testdata/i18n-dir/ru/hello.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
hello = Привет
|
||||
1
util/gi18n/testdata/i18n-dir/ru/world.ini
vendored
Normal file
1
util/gi18n/testdata/i18n-dir/ru/world.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
world=мир
|
||||
3
util/gi18n/testdata/i18n-dir/zh-CN/hello.json
vendored
Normal file
3
util/gi18n/testdata/i18n-dir/zh-CN/hello.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "你好"
|
||||
}
|
||||
3
util/gi18n/testdata/i18n-dir/zh-CN/world.json
vendored
Normal file
3
util/gi18n/testdata/i18n-dir/zh-CN/world.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"world": "世界"
|
||||
}
|
||||
4
util/gi18n/testdata/i18n-dir/zh-TW/hello.xml
vendored
Normal file
4
util/gi18n/testdata/i18n-dir/zh-TW/hello.xml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<hello>你好</hello>
|
||||
</i18n>
|
||||
4
util/gi18n/testdata/i18n-dir/zh-TW/world.xml
vendored
Normal file
4
util/gi18n/testdata/i18n-dir/zh-TW/world.xml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<world>世界</world>
|
||||
</i18n>
|
||||
3
util/gi18n/testdata/i18n-file/en.toml
vendored
Normal file
3
util/gi18n/testdata/i18n-file/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
util/gi18n/testdata/i18n-file/ja.yaml
vendored
Normal file
3
util/gi18n/testdata/i18n-file/ja.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
world: "世界"
|
||||
2
util/gi18n/testdata/i18n-file/ru.ini
vendored
Normal file
2
util/gi18n/testdata/i18n-file/ru.ini
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = Привет
|
||||
world = мир
|
||||
4
util/gi18n/testdata/i18n-file/zh-CN.json
vendored
Normal file
4
util/gi18n/testdata/i18n-file/zh-CN.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"hello": "你好",
|
||||
"world": "世界"
|
||||
}
|
||||
5
util/gi18n/testdata/i18n-file/zh-TW.xml
vendored
Normal file
5
util/gi18n/testdata/i18n-file/zh-TW.xml
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<hello>你好</hello>
|
||||
<world>世界</world>
|
||||
</i18n>
|
||||
3
util/gi18n/testdata/i18n/en.toml
vendored
Normal file
3
util/gi18n/testdata/i18n/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
util/gi18n/testdata/i18n/ja.toml
vendored
Normal file
3
util/gi18n/testdata/i18n/ja.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "こんにちは"
|
||||
world = "世界"
|
||||
3
util/gi18n/testdata/i18n/ru.toml
vendored
Normal file
3
util/gi18n/testdata/i18n/ru.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Привет"
|
||||
world = "мир"
|
||||
2
util/gi18n/testdata/i18n/zh-CN.toml
vendored
Normal file
2
util/gi18n/testdata/i18n/zh-CN.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
2
util/gi18n/testdata/i18n/zh-TW.toml
vendored
Normal file
2
util/gi18n/testdata/i18n/zh-TW.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
Reference in New Issue
Block a user