mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
Merge branch 'develop'
This commit is contained in:
18
.example/i18n/gi18n/gi18n-dir.go
Normal file
18
.example/i18n/gi18n/gi18n-dir.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := gi18n.New()
|
||||
t.SetLanguage("ja")
|
||||
err := t.SetPath("./i18n-dir")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}}{#world}}!`))
|
||||
}
|
||||
18
.example/i18n/gi18n/gi18n-file.go
Normal file
18
.example/i18n/gi18n/gi18n-file.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := gi18n.New()
|
||||
t.SetLanguage("ja")
|
||||
err := t.SetPath("./i18n-file")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}}{#world}}!`))
|
||||
}
|
||||
15
.example/i18n/gi18n/gi18n.go
Normal file
15
.example/i18n/gi18n/gi18n.go
Normal file
@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
)
|
||||
|
||||
func main() {
|
||||
t := gi18n.New()
|
||||
t.SetLanguage("ja")
|
||||
fmt.Println(t.Translate(`hello`))
|
||||
fmt.Println(t.Translate(`{#hello}}{#world}}!`))
|
||||
|
||||
}
|
||||
2
.example/i18n/gi18n/i18n-dir/en/hello.toml
Normal file
2
.example/i18n/gi18n/i18n-dir/en/hello.toml
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello = "Hello"
|
||||
2
.example/i18n/gi18n/i18n-dir/en/world.toml
Normal file
2
.example/i18n/gi18n/i18n-dir/en/world.toml
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
world = "World"
|
||||
2
.example/i18n/gi18n/i18n-dir/ja/hello.yaml
Normal file
2
.example/i18n/gi18n/i18n-dir/ja/hello.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
1
.example/i18n/gi18n/i18n-dir/ja/world.yaml
Normal file
1
.example/i18n/gi18n/i18n-dir/ja/world.yaml
Normal file
@ -0,0 +1 @@
|
||||
world: "世界"
|
||||
1
.example/i18n/gi18n/i18n-dir/ru/hello.ini
Normal file
1
.example/i18n/gi18n/i18n-dir/ru/hello.ini
Normal file
@ -0,0 +1 @@
|
||||
hello = Привет
|
||||
1
.example/i18n/gi18n/i18n-dir/ru/world.ini
Normal file
1
.example/i18n/gi18n/i18n-dir/ru/world.ini
Normal file
@ -0,0 +1 @@
|
||||
world=мир
|
||||
3
.example/i18n/gi18n/i18n-dir/zh-CN/hello.json
Normal file
3
.example/i18n/gi18n/i18n-dir/zh-CN/hello.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "你好"
|
||||
}
|
||||
3
.example/i18n/gi18n/i18n-dir/zh-CN/world.json
Normal file
3
.example/i18n/gi18n/i18n-dir/zh-CN/world.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"world": "世界"
|
||||
}
|
||||
4
.example/i18n/gi18n/i18n-dir/zh-TW/hello.xml
Normal file
4
.example/i18n/gi18n/i18n-dir/zh-TW/hello.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<hello>你好</hello>
|
||||
</i18n>
|
||||
4
.example/i18n/gi18n/i18n-dir/zh-TW/world.xml
Normal file
4
.example/i18n/gi18n/i18n-dir/zh-TW/world.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<world>世界</world>
|
||||
</i18n>
|
||||
3
.example/i18n/gi18n/i18n-file/en.toml
Normal file
3
.example/i18n/gi18n/i18n-file/en.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
.example/i18n/gi18n/i18n-file/ja.yaml
Normal file
3
.example/i18n/gi18n/i18n-file/ja.yaml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
world: "世界"
|
||||
2
.example/i18n/gi18n/i18n-file/ru.ini
Normal file
2
.example/i18n/gi18n/i18n-file/ru.ini
Normal file
@ -0,0 +1,2 @@
|
||||
hello = Привет
|
||||
world = мир
|
||||
4
.example/i18n/gi18n/i18n-file/zh-CN.json
Normal file
4
.example/i18n/gi18n/i18n-file/zh-CN.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"hello": "你好",
|
||||
"world": "世界"
|
||||
}
|
||||
5
.example/i18n/gi18n/i18n-file/zh-TW.xml
Normal file
5
.example/i18n/gi18n/i18n-file/zh-TW.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<i18n>
|
||||
<hello>你好</hello>
|
||||
<world>世界</world>
|
||||
</i18n>
|
||||
3
.example/i18n/gi18n/i18n/en.toml
Normal file
3
.example/i18n/gi18n/i18n/en.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
.example/i18n/gi18n/i18n/ja.toml
Normal file
3
.example/i18n/gi18n/i18n/ja.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "こんにちは"
|
||||
world = "世界"
|
||||
3
.example/i18n/gi18n/i18n/ru.toml
Normal file
3
.example/i18n/gi18n/i18n/ru.toml
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Привет"
|
||||
world = "мир"
|
||||
2
.example/i18n/gi18n/i18n/zh-CN.toml
Normal file
2
.example/i18n/gi18n/i18n/zh-CN.toml
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
2
.example/i18n/gi18n/i18n/zh-TW.toml
Normal file
2
.example/i18n/gi18n/i18n/zh-TW.toml
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
20
.example/i18n/gi18n/resource/gi18n-resource.go
Normal file
20
.example/i18n/gi18n/resource/gi18n-resource.go
Normal file
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
_ "github.com/gogf/gf/os/gres/testdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
m := g.I18n()
|
||||
m.SetLanguage("ja")
|
||||
err := m.SetPath("/i18n-dir")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(m.Translate(`hello`))
|
||||
fmt.Println(m.Translate(`{{hello}}{{world}}!`))
|
||||
}
|
||||
@ -8,7 +8,8 @@ import (
|
||||
|
||||
func main() {
|
||||
gres.Dump()
|
||||
g.Dump(gres.Scan("/root/image/", "*", true))
|
||||
g.Dump(gres.Scan("/template", "*"))
|
||||
g.Dump(gres.Scan("/template/layout2", "*.html", true))
|
||||
g.Dump(gres.Scan("/root/image/logo", "*"))
|
||||
//g.Dump(gres.Scan("/root/image/", "*", true))
|
||||
//g.Dump(gres.Scan("/template", "*"))
|
||||
//g.Dump(gres.Scan("/template/layout2", "*.html", true))
|
||||
}
|
||||
|
||||
18
.example/os/gview/resource/main3.go
Normal file
18
.example/os/gview/resource/main3.go
Normal file
@ -0,0 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/os/gres"
|
||||
_ "github.com/gogf/gf/os/gres/testdata"
|
||||
)
|
||||
|
||||
func main() {
|
||||
gres.Dump()
|
||||
|
||||
v := g.View()
|
||||
s, err := v.Parse("index.html")
|
||||
fmt.Println(err)
|
||||
fmt.Println(s)
|
||||
}
|
||||
@ -33,7 +33,7 @@ golang version >= 1.10
|
||||
|
||||
# Architecture
|
||||
<div align=center>
|
||||
<img src="https://gfcdn.johng.cn/images/arch.png"/>
|
||||
<img src="https://goframe.org/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
# Quick Start
|
||||
|
||||
@ -37,7 +37,7 @@ golang版本 >= 1.10
|
||||
|
||||
# 架构
|
||||
<div align=center>
|
||||
<img src="https://gfcdn.johng.cn/images/arch.png"/>
|
||||
<img src="https://goframe.org/images/arch.png"/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
@ -27,17 +27,17 @@ type apiString interface {
|
||||
}
|
||||
|
||||
const (
|
||||
OrmTagForStruct = "orm"
|
||||
OrmTagForUnique = "unique"
|
||||
OrmTagForPrimary = "primary"
|
||||
ORM_TAG_FOR_STRUCT = "orm"
|
||||
ORM_TAG_FOR_UNIQUE = "unique"
|
||||
ORM_TAG_FOR_PRIMARY = "primary"
|
||||
)
|
||||
|
||||
// 获得struct对象对应的where查询条件
|
||||
func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interface{}) {
|
||||
array := ([]string)(nil)
|
||||
for tag, field := range structs.TagMapField(pointer, []string{OrmTagForStruct}, true) {
|
||||
for tag, field := range structs.TagMapField(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
array = strings.Split(tag, ",")
|
||||
if len(array) > 1 && gstr.InArray([]string{OrmTagForUnique, OrmTagForPrimary}, array[1]) {
|
||||
if len(array) > 1 && gstr.InArray([]string{ORM_TAG_FOR_UNIQUE, ORM_TAG_FOR_PRIMARY}, array[1]) {
|
||||
return array[0], []interface{}{field.Value()}
|
||||
}
|
||||
if len(where) > 0 {
|
||||
@ -52,7 +52,7 @@ func GetWhereConditionOfStruct(pointer interface{}) (where string, args []interf
|
||||
// 获得orm标签与属性的映射关系
|
||||
func GetOrmMappingOfStruct(pointer interface{}) map[string]string {
|
||||
mapping := make(map[string]string)
|
||||
for tag, attr := range structs.TagMapName(pointer, []string{OrmTagForStruct}, true) {
|
||||
for tag, attr := range structs.TagMapName(pointer, []string{ORM_TAG_FOR_STRUCT}, true) {
|
||||
mapping[strings.Split(tag, ",")[0]] = attr
|
||||
}
|
||||
return mapping
|
||||
@ -172,7 +172,7 @@ func getInsertOperationByOption(option int) string {
|
||||
// 将对象转换为map,如果对象带有继承对象,那么执行递归转换。
|
||||
// 该方法用于将变量传递给数据库执行之前。
|
||||
func structToMap(obj interface{}) map[string]interface{} {
|
||||
data := gconv.Map(obj, OrmTagForStruct)
|
||||
data := gconv.Map(obj, ORM_TAG_FOR_STRUCT)
|
||||
for key, value := range data {
|
||||
rv := reflect.ValueOf(value)
|
||||
kind := rv.Kind()
|
||||
|
||||
@ -10,6 +10,7 @@ package gdebug
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
@ -187,6 +188,12 @@ func CallerFilePath() string {
|
||||
return path
|
||||
}
|
||||
|
||||
// CallerDirectory returns the directory of the caller.
|
||||
func CallerDirectory() string {
|
||||
_, path, _ := Caller()
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// CallerFileLine returns the file path along with the line number of the caller.
|
||||
func CallerFileLine() string {
|
||||
_, path, line := Caller()
|
||||
|
||||
@ -161,7 +161,7 @@ func doLoadContent(dataType string, data []byte, safe ...bool) (*Json, error) {
|
||||
dataType = checkDataType(data)
|
||||
}
|
||||
switch dataType {
|
||||
case "json", ".json":
|
||||
case "json", ".json", ".js":
|
||||
|
||||
case "xml", ".xml":
|
||||
if data, err = gxml.ToJson(data); err != nil {
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"github.com/gogf/gf/database/gkvdb"
|
||||
"github.com/gogf/gf/database/gredis"
|
||||
"github.com/gogf/gf/frame/gins"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/net/gtcp"
|
||||
"github.com/gogf/gf/net/gudp"
|
||||
@ -56,6 +57,12 @@ func Resource(name ...string) *gres.Resource {
|
||||
return gins.Resource(name...)
|
||||
}
|
||||
|
||||
// I18n returns an instance of gi18n.Manager.
|
||||
// The parameter <name> is the name for the instance.
|
||||
func I18n(name ...string) *gi18n.Manager {
|
||||
return gins.I18n(name...)
|
||||
}
|
||||
|
||||
// Res is alias of Resource.
|
||||
// See Resource.
|
||||
func Res(name ...string) *gres.Resource {
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/database/gdb"
|
||||
"github.com/gogf/gf/database/gredis"
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
"github.com/gogf/gf/os/gcfg"
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
@ -85,6 +86,12 @@ func Resource(name ...string) *gres.Resource {
|
||||
return gres.Instance(name...)
|
||||
}
|
||||
|
||||
// I18n returns an instance of gi18n.Manager.
|
||||
// The parameter <name> is the name for the instance.
|
||||
func I18n(name ...string) *gi18n.Manager {
|
||||
return gi18n.Instance(name...)
|
||||
}
|
||||
|
||||
// Database returns an instance of database ORM object
|
||||
// with specified configuration group name.
|
||||
func Database(name ...string) gdb.DB {
|
||||
|
||||
38
i18n/gi18n/gi18n.go
Normal file
38
i18n/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 (
|
||||
defaultManager = Instance()
|
||||
)
|
||||
|
||||
// SetPath sets the directory path storing i18n files.
|
||||
func SetPath(path string) error {
|
||||
return defaultManager.SetPath(path)
|
||||
}
|
||||
|
||||
// SetLanguage sets the language for translator.
|
||||
func SetLanguage(language string) {
|
||||
defaultManager.SetLanguage(language)
|
||||
}
|
||||
|
||||
// SetDelimiters sets the delimiters for translator.
|
||||
func SetDelimiters(left, right string) {
|
||||
defaultManager.SetDelimiters(left, right)
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
func T(content string, language ...string) string {
|
||||
return defaultManager.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 defaultManager.Translate(content, language...)
|
||||
}
|
||||
31
i18n/gi18n/gi18n_instance.go
Normal file
31
i18n/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) *Manager {
|
||||
key := DEFAULT_NAME
|
||||
if len(name) > 0 && name[0] != "" {
|
||||
key = name[0]
|
||||
}
|
||||
return instances.GetOrSetFuncLock(key, func() interface{} {
|
||||
return New()
|
||||
}).(*Manager)
|
||||
}
|
||||
208
i18n/gi18n/gi18n_manager.go
Normal file
208
i18n/gi18n/gi18n_manager.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"
|
||||
)
|
||||
|
||||
// Manager, it is concurrent safe, supporting hot reload.
|
||||
type Manager 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) *Manager {
|
||||
var opts Options
|
||||
if len(options) > 0 {
|
||||
opts = options[0]
|
||||
} else {
|
||||
opts = DefaultOptions()
|
||||
}
|
||||
if len(opts.Delimiters) == 0 {
|
||||
opts.Delimiters = defaultDelimiters
|
||||
}
|
||||
return &Manager{
|
||||
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 (m *Manager) SetPath(path string) error {
|
||||
if gres.Contains(path) {
|
||||
m.options.Path = path
|
||||
} else {
|
||||
realPath, _ := gfile.Search(path)
|
||||
if realPath == "" {
|
||||
return errors.New(fmt.Sprintf(`%s does not exist`, path))
|
||||
}
|
||||
m.options.Path = realPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLanguage sets the language for translator.
|
||||
func (m *Manager) SetLanguage(language string) {
|
||||
m.options.Language = language
|
||||
}
|
||||
|
||||
// SetDelimiters sets the delimiters for translator.
|
||||
func (m *Manager) SetDelimiters(left, right string) {
|
||||
m.pattern = fmt.Sprintf(`%s(\w+)%s`, gregex.Quote(left), gregex.Quote(right))
|
||||
}
|
||||
|
||||
// T is alias of Translate.
|
||||
func (m *Manager) T(content string, language ...string) string {
|
||||
return m.Translate(content, language...)
|
||||
}
|
||||
|
||||
// Translate translates <content> with configured language.
|
||||
// The parameter <language> specifies custom translation language ignoring configured language.
|
||||
func (m *Manager) Translate(content string, language ...string) string {
|
||||
m.init()
|
||||
m.mu.RLock()
|
||||
defer m.mu.RUnlock()
|
||||
var data map[string]string
|
||||
if len(language) > 0 {
|
||||
data = m.data[language[0]]
|
||||
} else {
|
||||
data = m.data[m.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(m.pattern, content, func(match []string) string {
|
||||
if v, ok := data[match[1]]; ok {
|
||||
return v
|
||||
}
|
||||
return match[0]
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *Manager) init() {
|
||||
m.mu.RLock()
|
||||
if m.data != nil {
|
||||
m.mu.RUnlock()
|
||||
return
|
||||
}
|
||||
m.mu.RUnlock()
|
||||
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if gres.Contains(m.options.Path) {
|
||||
files := gres.ScanDirFile(m.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var name string
|
||||
var lang string
|
||||
var array []string
|
||||
m.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
name = file.Name()
|
||||
path = name[len(m.options.Path)+1:]
|
||||
array = strings.Split(path, "/")
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if m.data[lang] == nil {
|
||||
m.data[lang] = make(map[string]string)
|
||||
}
|
||||
j, _ := gjson.LoadContent(file.Content())
|
||||
if j != nil {
|
||||
for k, v := range j.ToMap() {
|
||||
m.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
files, _ := gfile.ScanDirFile(m.options.Path, "*.*", true)
|
||||
if len(files) > 0 {
|
||||
var path string
|
||||
var lang string
|
||||
var array []string
|
||||
m.data = make(map[string]map[string]string)
|
||||
for _, file := range files {
|
||||
path = file[len(m.options.Path)+1:]
|
||||
array = strings.Split(path, "/")
|
||||
if len(array) > 1 {
|
||||
lang = array[0]
|
||||
} else {
|
||||
lang = gfile.Name(array[0])
|
||||
}
|
||||
if m.data[lang] == nil {
|
||||
m.data[lang] = make(map[string]string)
|
||||
}
|
||||
j, _ := gjson.LoadContent(gfile.GetBytes(file))
|
||||
if j != nil {
|
||||
for k, v := range j.ToMap() {
|
||||
m.data[lang][k] = gconv.String(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
m.mu.Lock()
|
||||
m.data = nil
|
||||
m.mu.Unlock()
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
143
i18n/gi18n/gi18n_unit_test.go
Normal file
143
i18n/gi18n/gi18n_unit_test.go
Normal file
@ -0,0 +1,143 @@
|
||||
// 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/os/gtime"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
|
||||
_ "github.com/gogf/gf/os/gres/testdata"
|
||||
)
|
||||
|
||||
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}"), "こんにちは世界")
|
||||
|
||||
t.SetLanguage("zh-CN")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "你好世界")
|
||||
t.SetDelimiters("{$", "}")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
gtest.Assert(t.T("{$hello}{$world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
t := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-file",
|
||||
})
|
||||
t.SetLanguage("none")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
t.SetLanguage("ja")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
t.SetLanguage("zh-CN")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
t := gi18n.New(gi18n.Options{
|
||||
Path: gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-dir",
|
||||
})
|
||||
t.SetLanguage("none")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
t.SetLanguage("ja")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
t.SetLanguage("zh-CN")
|
||||
gtest.Assert(t.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_DefaultManager(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
err := gi18n.SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
gi18n.SetLanguage("none")
|
||||
gtest.Assert(gi18n.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
gi18n.SetLanguage("ja")
|
||||
gtest.Assert(gi18n.T("{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
gi18n.SetLanguage("zh-CN")
|
||||
gtest.Assert(gi18n.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
err := gi18n.SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n-dir")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
gi18n.SetLanguage("none")
|
||||
gtest.Assert(gi18n.Translate("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
gi18n.SetLanguage("ja")
|
||||
gtest.Assert(gi18n.Translate("{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
gi18n.SetLanguage("zh-CN")
|
||||
gtest.Assert(gi18n.Translate("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Instance(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := gi18n.Instance()
|
||||
err := m.SetPath("/i18n-dir")
|
||||
gtest.Assert(err, nil)
|
||||
m.SetLanguage("zh-CN")
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
m := gi18n.Instance()
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
gtest.Assert(g.I18n().T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
|
||||
gtest.Case(t, func() {
|
||||
m := gi18n.Instance(gconv.String(gtime.Nanosecond()))
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_Resource(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
m := g.I18n("resource")
|
||||
err := m.SetPath("/i18n-dir")
|
||||
gtest.Assert(err, nil)
|
||||
|
||||
m.SetLanguage("none")
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "{#hello}{#world}")
|
||||
|
||||
m.SetLanguage("ja")
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "こんにちは世界")
|
||||
|
||||
m.SetLanguage("zh-CN")
|
||||
gtest.Assert(m.T("{#hello}{#world}"), "你好世界")
|
||||
})
|
||||
}
|
||||
2
i18n/gi18n/testdata/i18n-dir/en/hello.toml
vendored
Normal file
2
i18n/gi18n/testdata/i18n-dir/en/hello.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello = "Hello"
|
||||
2
i18n/gi18n/testdata/i18n-dir/en/world.toml
vendored
Normal file
2
i18n/gi18n/testdata/i18n-dir/en/world.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
world = "World"
|
||||
2
i18n/gi18n/testdata/i18n-dir/ja/hello.yaml
vendored
Normal file
2
i18n/gi18n/testdata/i18n-dir/ja/hello.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
1
i18n/gi18n/testdata/i18n-dir/ja/world.yaml
vendored
Normal file
1
i18n/gi18n/testdata/i18n-dir/ja/world.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
world: "世界"
|
||||
1
i18n/gi18n/testdata/i18n-dir/ru/hello.ini
vendored
Normal file
1
i18n/gi18n/testdata/i18n-dir/ru/hello.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
hello = Привет
|
||||
1
i18n/gi18n/testdata/i18n-dir/ru/world.ini
vendored
Normal file
1
i18n/gi18n/testdata/i18n-dir/ru/world.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
world=мир
|
||||
3
i18n/gi18n/testdata/i18n-dir/zh-CN/hello.json
vendored
Normal file
3
i18n/gi18n/testdata/i18n-dir/zh-CN/hello.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "你好"
|
||||
}
|
||||
3
i18n/gi18n/testdata/i18n-dir/zh-CN/world.json
vendored
Normal file
3
i18n/gi18n/testdata/i18n-dir/zh-CN/world.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"world": "世界"
|
||||
}
|
||||
4
i18n/gi18n/testdata/i18n-dir/zh-TW/hello.xml
vendored
Normal file
4
i18n/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
i18n/gi18n/testdata/i18n-dir/zh-TW/world.xml
vendored
Normal file
4
i18n/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
i18n/gi18n/testdata/i18n-file/en.toml
vendored
Normal file
3
i18n/gi18n/testdata/i18n-file/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
i18n/gi18n/testdata/i18n-file/ja.yaml
vendored
Normal file
3
i18n/gi18n/testdata/i18n-file/ja.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
world: "世界"
|
||||
2
i18n/gi18n/testdata/i18n-file/ru.ini
vendored
Normal file
2
i18n/gi18n/testdata/i18n-file/ru.ini
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = Привет
|
||||
world = мир
|
||||
4
i18n/gi18n/testdata/i18n-file/zh-CN.json
vendored
Normal file
4
i18n/gi18n/testdata/i18n-file/zh-CN.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"hello": "你好",
|
||||
"world": "世界"
|
||||
}
|
||||
5
i18n/gi18n/testdata/i18n-file/zh-TW.xml
vendored
Normal file
5
i18n/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
i18n/gi18n/testdata/i18n/en.toml
vendored
Normal file
3
i18n/gi18n/testdata/i18n/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
i18n/gi18n/testdata/i18n/ja.toml
vendored
Normal file
3
i18n/gi18n/testdata/i18n/ja.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "こんにちは"
|
||||
world = "世界"
|
||||
3
i18n/gi18n/testdata/i18n/ru.toml
vendored
Normal file
3
i18n/gi18n/testdata/i18n/ru.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Привет"
|
||||
world = "мир"
|
||||
2
i18n/gi18n/testdata/i18n/zh-CN.toml
vendored
Normal file
2
i18n/gi18n/testdata/i18n/zh-CN.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
2
i18n/gi18n/testdata/i18n/zh-TW.toml
vendored
Normal file
2
i18n/gi18n/testdata/i18n/zh-TW.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
@ -35,7 +35,7 @@ func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
//
|
||||
// Note that it returns only files, exclusive of directories.
|
||||
func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
isRecursive := true
|
||||
isRecursive := false
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
// Package gres provides resource management and packing/unpacking feature between files and bytes.
|
||||
package gres
|
||||
|
||||
const (
|
||||
// Separator for directories.
|
||||
Separator = "/"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default resource object.
|
||||
defaultResource = Instance()
|
||||
@ -54,14 +59,22 @@ func IsEmpty() bool {
|
||||
return defaultResource.tree.IsEmpty()
|
||||
}
|
||||
|
||||
// Scan returns the files under the given path, the parameter <path> should be a folder type.
|
||||
// ScanDir returns the files under the given path, the parameter <path> should be a folder type.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func Scan(path string, pattern string, recursive ...bool) []*File {
|
||||
return defaultResource.Scan(path, pattern, recursive...)
|
||||
func ScanDir(path string, pattern string, recursive ...bool) []*File {
|
||||
return defaultResource.ScanDir(path, pattern, recursive...)
|
||||
}
|
||||
|
||||
// ScanDirFile returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// Note that it returns only files, exclusive of directories.
|
||||
func ScanDirFile(path string, pattern string, recursive ...bool) []*File {
|
||||
return defaultResource.ScanDirFile(path, pattern, recursive...)
|
||||
}
|
||||
|
||||
// Dump prints the files of the default resource object.
|
||||
|
||||
@ -18,7 +18,7 @@ func (f *File) Close() error {
|
||||
|
||||
// Readdir implements Readdir interface of http.File.
|
||||
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
|
||||
files := f.resource.Scan(f.Name(), "*", false)
|
||||
files := f.resource.ScanDir(f.Name(), "*", false)
|
||||
if len(files) > 0 {
|
||||
if count < 0 || count > len(files) {
|
||||
count = len(files)
|
||||
|
||||
@ -122,13 +122,40 @@ func (r *Resource) IsEmpty() bool {
|
||||
return r.tree.IsEmpty()
|
||||
}
|
||||
|
||||
// Scan returns the files under the given path, the parameter <path> should be a folder type.
|
||||
// ScanDir returns the files under the given path, the parameter <path> should be a folder type.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func (r *Resource) Scan(path string, pattern string, recursive ...bool) []*File {
|
||||
func (r *Resource) ScanDir(path string, pattern string, recursive ...bool) []*File {
|
||||
isRecursive := false
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
return r.doScanDir(path, pattern, isRecursive, false)
|
||||
}
|
||||
|
||||
// ScanDirFile returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// Note that it returns only files, exclusive of directories.
|
||||
func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) []*File {
|
||||
isRecursive := false
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
return r.doScanDir(path, pattern, isRecursive, true)
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory
|
||||
// and returns the absolute path list of files that are not sorted.
|
||||
//
|
||||
// The pattern parameter <pattern> supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File {
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
@ -150,6 +177,9 @@ func (r *Resource) Scan(path string, pattern string, recursive ...bool) []*File
|
||||
}
|
||||
first = false
|
||||
}
|
||||
if onlyFile && value.(*File).FileInfo().IsDir() {
|
||||
return true
|
||||
}
|
||||
name = key.(string)
|
||||
if len(name) <= length {
|
||||
return true
|
||||
@ -157,7 +187,11 @@ func (r *Resource) Scan(path string, pattern string, recursive ...bool) []*File
|
||||
if path != name[:length] {
|
||||
return false
|
||||
}
|
||||
if len(recursive) == 0 || !recursive[0] {
|
||||
// To avoid of, eg: /i18n and /i18n-dir
|
||||
if !first && name[length] != '/' {
|
||||
return true
|
||||
}
|
||||
if !recursive {
|
||||
if strings.IndexByte(name[length+1:], '/') != -1 {
|
||||
return true
|
||||
}
|
||||
|
||||
2
os/gres/testdata/files/i18n-dir/en/hello.toml
vendored
Normal file
2
os/gres/testdata/files/i18n-dir/en/hello.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello = "Hello"
|
||||
2
os/gres/testdata/files/i18n-dir/en/world.toml
vendored
Normal file
2
os/gres/testdata/files/i18n-dir/en/world.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
world = "World"
|
||||
2
os/gres/testdata/files/i18n-dir/ja/hello.yaml
vendored
Normal file
2
os/gres/testdata/files/i18n-dir/ja/hello.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
1
os/gres/testdata/files/i18n-dir/ja/world.yaml
vendored
Normal file
1
os/gres/testdata/files/i18n-dir/ja/world.yaml
vendored
Normal file
@ -0,0 +1 @@
|
||||
world: "世界"
|
||||
1
os/gres/testdata/files/i18n-dir/ru/hello.ini
vendored
Normal file
1
os/gres/testdata/files/i18n-dir/ru/hello.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
hello = Привет
|
||||
1
os/gres/testdata/files/i18n-dir/ru/world.ini
vendored
Normal file
1
os/gres/testdata/files/i18n-dir/ru/world.ini
vendored
Normal file
@ -0,0 +1 @@
|
||||
world=мир
|
||||
3
os/gres/testdata/files/i18n-dir/zh-CN/hello.json
vendored
Normal file
3
os/gres/testdata/files/i18n-dir/zh-CN/hello.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"hello": "你好"
|
||||
}
|
||||
3
os/gres/testdata/files/i18n-dir/zh-CN/world.json
vendored
Normal file
3
os/gres/testdata/files/i18n-dir/zh-CN/world.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"world": "世界"
|
||||
}
|
||||
4
os/gres/testdata/files/i18n-dir/zh-TW/hello.xml
vendored
Normal file
4
os/gres/testdata/files/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
os/gres/testdata/files/i18n-dir/zh-TW/world.xml
vendored
Normal file
4
os/gres/testdata/files/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
os/gres/testdata/files/i18n-file/en.toml
vendored
Normal file
3
os/gres/testdata/files/i18n-file/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
os/gres/testdata/files/i18n-file/ja.yaml
vendored
Normal file
3
os/gres/testdata/files/i18n-file/ja.yaml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello: "こんにちは"
|
||||
world: "世界"
|
||||
2
os/gres/testdata/files/i18n-file/ru.ini
vendored
Normal file
2
os/gres/testdata/files/i18n-file/ru.ini
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = Привет
|
||||
world = мир
|
||||
4
os/gres/testdata/files/i18n-file/zh-CN.json
vendored
Normal file
4
os/gres/testdata/files/i18n-file/zh-CN.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"hello": "你好",
|
||||
"world": "世界"
|
||||
}
|
||||
5
os/gres/testdata/files/i18n-file/zh-TW.xml
vendored
Normal file
5
os/gres/testdata/files/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
os/gres/testdata/files/i18n/en.toml
vendored
Normal file
3
os/gres/testdata/files/i18n/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
os/gres/testdata/files/i18n/ja.toml
vendored
Normal file
3
os/gres/testdata/files/i18n/ja.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "こんにちは"
|
||||
world = "世界"
|
||||
3
os/gres/testdata/files/i18n/ru.toml
vendored
Normal file
3
os/gres/testdata/files/i18n/ru.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Привет"
|
||||
world = "мир"
|
||||
2
os/gres/testdata/files/i18n/zh-CN.toml
vendored
Normal file
2
os/gres/testdata/files/i18n/zh-CN.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
2
os/gres/testdata/files/i18n/zh-TW.toml
vendored
Normal file
2
os/gres/testdata/files/i18n/zh-TW.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
1
os/gres/testdata/files/template/index.html
vendored
Normal file
1
os/gres/testdata/files/template/index.html
vendored
Normal file
@ -0,0 +1 @@
|
||||
It's the index template file.
|
||||
2
os/gres/testdata/testdata.go
vendored
2
os/gres/testdata/testdata.go
vendored
File diff suppressed because one or more lines are too long
@ -24,11 +24,12 @@ import (
|
||||
|
||||
// View object for template engine.
|
||||
type View struct {
|
||||
mu sync.RWMutex
|
||||
paths *garray.StringArray // Searching path array.
|
||||
data map[string]interface{} // Global template variables.
|
||||
funcMap map[string]interface{} // Global template function map.
|
||||
delimiters []string // Customized template delimiters.
|
||||
mu sync.RWMutex
|
||||
paths *garray.StringArray // Searching path array.
|
||||
data map[string]interface{} // Global template variables.
|
||||
funcMap map[string]interface{} // Global template function map.
|
||||
i18nEnabled bool // Is i18n enabled in this template.
|
||||
delimiters []string // Customized template delimiters.
|
||||
}
|
||||
|
||||
// Params is type for template params.
|
||||
@ -59,10 +60,11 @@ func ParseContent(content string, params Params) (string, error) {
|
||||
// The parameter <path> specifies the template directory path to load template files.
|
||||
func New(path ...string) *View {
|
||||
view := &View{
|
||||
paths: garray.NewStringArray(true),
|
||||
data: make(map[string]interface{}),
|
||||
funcMap: make(map[string]interface{}),
|
||||
delimiters: make([]string, 2),
|
||||
paths: garray.NewStringArray(true),
|
||||
data: make(map[string]interface{}),
|
||||
funcMap: make(map[string]interface{}),
|
||||
i18nEnabled: true,
|
||||
delimiters: make([]string, 2),
|
||||
}
|
||||
if len(path) > 0 && len(path[0]) > 0 {
|
||||
view.SetPath(path[0])
|
||||
|
||||
@ -51,3 +51,10 @@ func (view *View) BindFuncMap(funcMap FuncMap) {
|
||||
}
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
// SetI18nEnabled enables/disables i18n feature in this template.
|
||||
func (view *View) SetI18nEnabled(enabled bool) {
|
||||
view.mu.Lock()
|
||||
view.i18nEnabled = enabled
|
||||
view.mu.Unlock()
|
||||
}
|
||||
|
||||
@ -13,6 +13,8 @@ import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
|
||||
"github.com/gogf/gf/os/gfcache"
|
||||
|
||||
"github.com/gogf/gf/os/gres"
|
||||
@ -47,12 +49,9 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
r := templates.GetOrSetFuncLock(path, func() interface{} {
|
||||
tpl = template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
// Firstly checking the resource manager.
|
||||
if files := gres.Scan(path, pattern, true); len(files) > 0 {
|
||||
if files := gres.ScanDirFile(path, pattern, true); len(files) > 0 {
|
||||
var err error
|
||||
for _, v := range files {
|
||||
if v.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
_, err = tpl.New(v.FileInfo().Name()).Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
@ -82,19 +81,17 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
|
||||
// searchFile returns the found absolute path for <file>, and its template folder path.
|
||||
func (view *View) searchFile(file string) (path string, folder string, err error) {
|
||||
separator := gfile.Separator
|
||||
// Firstly checking the resource manager.
|
||||
separator = "/"
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
f := (*gres.File)(nil)
|
||||
for _, v := range array {
|
||||
v = strings.TrimRight(v, separator)
|
||||
if f = gres.Get(v + separator + file); f != nil {
|
||||
v = strings.TrimRight(v, "/")
|
||||
if f = gres.Get(v + "/" + file); f != nil {
|
||||
path = f.Name()
|
||||
folder = gfile.Dir(path)
|
||||
break
|
||||
}
|
||||
if f = gres.Get(v + separator + "template" + separator + file); f != nil {
|
||||
if f = gres.Get(v + "/template/" + file); f != nil {
|
||||
path = f.Name()
|
||||
folder = gfile.Dir(path)
|
||||
break
|
||||
@ -105,19 +102,29 @@ func (view *View) searchFile(file string) (path string, folder string, err error
|
||||
if path == "" {
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
v = strings.TrimRight(v, separator)
|
||||
v = strings.TrimRight(v, gfile.Separator)
|
||||
if path, _ = gspath.Search(v, file); path != "" {
|
||||
folder = v
|
||||
break
|
||||
}
|
||||
if path, _ = gspath.Search(v+separator+"template", file); path != "" {
|
||||
folder = v + separator + "template"
|
||||
if path, _ = gspath.Search(v+gfile.Separator+"template", file); path != "" {
|
||||
folder = v + gfile.Separator + "template"
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Checking the configuration file in default paths.
|
||||
if path == "" && !gres.IsEmpty() {
|
||||
for _, v := range []string{"/template", "/template/"} {
|
||||
if file := gres.Get(v + file); file != nil {
|
||||
path = file.Name()
|
||||
folder = gfile.Dir(path)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Error checking.
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if view.paths.Len() > 0 {
|
||||
@ -131,7 +138,7 @@ func (view *View) searchFile(file string) (path string, folder string, err error
|
||||
}
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, v))
|
||||
index++
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, strings.TrimRight(v, "/")+separator+"template"))
|
||||
buffer.WriteString(fmt.Sprintf("\n%d. %s", index, strings.TrimRight(v, "/")+gfile.Separator+"template"))
|
||||
index++
|
||||
}
|
||||
})
|
||||
@ -202,7 +209,11 @@ func (view *View) Parse(file string, params ...Params) (parsed string, err error
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gstr.Replace(buffer.String(), "<no value>", ""), nil
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
if view.i18nEnabled {
|
||||
result = gi18n.T(result)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content>
|
||||
@ -254,5 +265,9 @@ func (view *View) ParseContent(content string, params ...Params) (string, error)
|
||||
if err := tpl.Execute(buffer, vars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return gstr.Replace(buffer.String(), "<no value>", ""), nil
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
if view.i18nEnabled {
|
||||
result = gi18n.T(result)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
// Copyright 2017 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 gview_test
|
||||
|
||||
import (
|
||||
@ -16,7 +22,7 @@ func init() {
|
||||
os.Setenv("GF_GVIEW_ERRORPRINT", "false")
|
||||
}
|
||||
|
||||
func TestView_Basic(t *testing.T) {
|
||||
func Test_Basic(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
str := `hello {{.name}},version:{{.version}};hello {{GetName}},version:{{GetVersion}};{{.other}}`
|
||||
pwd := gfile.Pwd()
|
||||
@ -45,7 +51,7 @@ func TestView_Basic(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestView_Func(t *testing.T) {
|
||||
func Test_Func(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
str := `{{eq 1 1}};{{eq 1 2}};{{eq "A" "B"}}`
|
||||
result, err := gview.ParseContent(str, nil)
|
||||
@ -162,7 +168,7 @@ func TestView_Func(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestView_FuncInclude(t *testing.T) {
|
||||
func Test_FuncInclude(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
header := `<h1>HEADER</h1>`
|
||||
main := `<h1>hello gf</h1>`
|
||||
@ -200,7 +206,7 @@ func TestView_FuncInclude(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestView_SetPath(t *testing.T) {
|
||||
func Test_SetPath(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
view := gview.Instance("addpath")
|
||||
err := view.AddPath("tmp")
|
||||
@ -230,7 +236,7 @@ func TestView_SetPath(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestView_ParseContent(t *testing.T) {
|
||||
func Test_ParseContent(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
str := `{{.name}}`
|
||||
view := gview.New()
|
||||
49
os/gview/gview_unit_i18n_test.go
Normal file
49
os/gview/gview_unit_i18n_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
// 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 gview_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/debug/gdebug"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func Test_I18n(t *testing.T) {
|
||||
gtest.Case(t, func() {
|
||||
content := `{{.name}} says "{#hello}{#world}!"`
|
||||
expect1 := `john says "你好世界!"`
|
||||
expect2 := `john says "こんにちは世界!"`
|
||||
expect3 := `john says "{#hello}{#world}!"`
|
||||
|
||||
g.I18n().SetPath(gdebug.CallerDirectory() + gfile.Separator + "testdata" + gfile.Separator + "i18n")
|
||||
|
||||
g.I18n().SetLanguage("zh-CN")
|
||||
result1, err := g.View().ParseContent(content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(result1, expect1)
|
||||
|
||||
g.I18n().SetLanguage("ja")
|
||||
result2, err := g.View().ParseContent(content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(result2, expect2)
|
||||
|
||||
g.I18n().SetLanguage("none")
|
||||
result3, err := g.View().ParseContent(content, g.Map{
|
||||
"name": "john",
|
||||
})
|
||||
gtest.Assert(err, nil)
|
||||
gtest.Assert(result3, expect3)
|
||||
})
|
||||
}
|
||||
3
os/gview/testdata/i18n/en.toml
vendored
Normal file
3
os/gview/testdata/i18n/en.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Hello"
|
||||
world = "World"
|
||||
3
os/gview/testdata/i18n/ja.toml
vendored
Normal file
3
os/gview/testdata/i18n/ja.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "こんにちは"
|
||||
world = "世界"
|
||||
3
os/gview/testdata/i18n/ru.toml
vendored
Normal file
3
os/gview/testdata/i18n/ru.toml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
hello = "Привет"
|
||||
world = "мир"
|
||||
2
os/gview/testdata/i18n/zh-CN.toml
vendored
Normal file
2
os/gview/testdata/i18n/zh-CN.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
2
os/gview/testdata/i18n/zh-TW.toml
vendored
Normal file
2
os/gview/testdata/i18n/zh-TW.toml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
hello = "你好"
|
||||
world = "世界"
|
||||
@ -100,14 +100,14 @@ func ReplaceFunc(pattern string, src []byte, replaceFunc func(b []byte) []byte)
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceFunc replace all matched <pattern> in bytes <src>
|
||||
// ReplaceFuncMatch replace all matched <pattern> in bytes <src>
|
||||
// with custom replacement function <replaceFunc>.
|
||||
// The parameter <match> type for <replaceFunc> is [][]byte,
|
||||
// which is the result contains all sub-patterns of <pattern> using Match function.
|
||||
func ReplaceFuncMatch(pattern string, src []byte, replaceFunc func(match [][]byte) []byte) ([]byte, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return r.ReplaceAllFunc(src, func(bytes []byte) []byte {
|
||||
match, _ := Match(pattern, src)
|
||||
match, _ := Match(pattern, bytes)
|
||||
return replaceFunc(match)
|
||||
}), nil
|
||||
} else {
|
||||
@ -131,7 +131,7 @@ func ReplaceStringFunc(pattern string, src string, replaceFunc func(s string) st
|
||||
func ReplaceStringFuncMatch(pattern string, src string, replaceFunc func(match []string) string) (string, error) {
|
||||
if r, err := getRegexp(pattern); err == nil {
|
||||
return string(r.ReplaceAllFunc([]byte(src), func(bytes []byte) []byte {
|
||||
match, _ := MatchString(pattern, src)
|
||||
match, _ := MatchString(pattern, string(bytes))
|
||||
return []byte(replaceFunc(match))
|
||||
})), nil
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user