Merge branch 'develop'

This commit is contained in:
John
2019-09-01 00:30:19 +08:00
97 changed files with 885 additions and 55 deletions

View 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}}!`))
}

View 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}}!`))
}

View 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}}!`))
}

View File

@ -0,0 +1,2 @@
hello = "Hello"

View File

@ -0,0 +1,2 @@
world = "World"

View File

@ -0,0 +1,2 @@
hello: "こんにちは"

View File

@ -0,0 +1 @@
world: "世界"

View File

@ -0,0 +1 @@
hello = Привет

View File

@ -0,0 +1 @@
world=мир

View File

@ -0,0 +1,3 @@
{
"hello": "你好"
}

View File

@ -0,0 +1,3 @@
{
"world": "世界"
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<hello>你好</hello>
</i18n>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<world>世界</world>
</i18n>

View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

View File

@ -0,0 +1,3 @@
hello: "こんにちは"
world: "世界"

View File

@ -0,0 +1,2 @@
hello = Привет
world = мир

View File

@ -0,0 +1,4 @@
{
"hello": "你好",
"world": "世界"
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<hello>你好</hello>
<world>世界</world>
</i18n>

View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

View File

@ -0,0 +1,3 @@
hello = "こんにちは"
world = "世界"

View File

@ -0,0 +1,3 @@
hello = "Привет"
world = "мир"

View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View 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}}!`))
}

View File

@ -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))
}

View 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)
}

View File

@ -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

View File

@ -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>

View File

@ -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()

View File

@ -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()

View File

@ -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 {

View File

@ -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 {

View File

@ -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
View 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...)
}

View 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
View 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()
})
}
}
}

View 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}"), "你好世界")
})
}

View File

@ -0,0 +1,2 @@
hello = "Hello"

View File

@ -0,0 +1,2 @@
world = "World"

View File

@ -0,0 +1,2 @@
hello: "こんにちは"

View File

@ -0,0 +1 @@
world: "世界"

View File

@ -0,0 +1 @@
hello = Привет

View File

@ -0,0 +1 @@
world=мир

View File

@ -0,0 +1,3 @@
{
"hello": "你好"
}

View File

@ -0,0 +1,3 @@
{
"world": "世界"
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<hello>你好</hello>
</i18n>

View 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
View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

3
i18n/gi18n/testdata/i18n-file/ja.yaml vendored Normal file
View File

@ -0,0 +1,3 @@
hello: "こんにちは"
world: "世界"

2
i18n/gi18n/testdata/i18n-file/ru.ini vendored Normal file
View File

@ -0,0 +1,2 @@
hello = Привет
world = мир

View File

@ -0,0 +1,4 @@
{
"hello": "你好",
"world": "世界"
}

View 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
View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

3
i18n/gi18n/testdata/i18n/ja.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "こんにちは"
world = "世界"

3
i18n/gi18n/testdata/i18n/ru.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "Привет"
world = "мир"

2
i18n/gi18n/testdata/i18n/zh-CN.toml vendored Normal file
View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

2
i18n/gi18n/testdata/i18n/zh-TW.toml vendored Normal file
View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View File

@ -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]
}

View File

@ -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.

View File

@ -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)

View File

@ -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
}

View File

@ -0,0 +1,2 @@
hello = "Hello"

View File

@ -0,0 +1,2 @@
world = "World"

View File

@ -0,0 +1,2 @@
hello: "こんにちは"

View File

@ -0,0 +1 @@
world: "世界"

View File

@ -0,0 +1 @@
hello = Привет

View File

@ -0,0 +1 @@
world=мир

View File

@ -0,0 +1,3 @@
{
"hello": "你好"
}

View File

@ -0,0 +1,3 @@
{
"world": "世界"
}

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<hello>你好</hello>
</i18n>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<i18n>
<world>世界</world>
</i18n>

View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

View File

@ -0,0 +1,3 @@
hello: "こんにちは"
world: "世界"

View File

@ -0,0 +1,2 @@
hello = Привет
world = мир

View File

@ -0,0 +1,4 @@
{
"hello": "你好",
"world": "世界"
}

View 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
View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

3
os/gres/testdata/files/i18n/ja.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "こんにちは"
world = "世界"

3
os/gres/testdata/files/i18n/ru.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "Привет"
world = "мир"

View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View File

@ -0,0 +1 @@
It's the index template file.

File diff suppressed because one or more lines are too long

View File

@ -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])

View File

@ -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()
}

View File

@ -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
}

View File

@ -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()

View 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
View File

@ -0,0 +1,3 @@
hello = "Hello"
world = "World"

3
os/gview/testdata/i18n/ja.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "こんにちは"
world = "世界"

3
os/gview/testdata/i18n/ru.toml vendored Normal file
View File

@ -0,0 +1,3 @@
hello = "Привет"
world = "мир"

2
os/gview/testdata/i18n/zh-CN.toml vendored Normal file
View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

2
os/gview/testdata/i18n/zh-TW.toml vendored Normal file
View File

@ -0,0 +1,2 @@
hello = "你好"
world = "世界"

View File

@ -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 {