diff --git a/sui/core/locale.go b/sui/core/locale.go index 9c9b7779..7ded38a3 100644 --- a/sui/core/locale.go +++ b/sui/core/locale.go @@ -2,9 +2,119 @@ package core import ( "fmt" + "path/filepath" "regexp" + "strings" + "time" + + "github.com/yaoapp/gou/application" + "github.com/yaoapp/gou/process" + "github.com/yaoapp/kun/log" + "gopkg.in/yaml.v3" ) +// Locales the locales +var Locales = map[string]map[string]*Locale{} + +type localeData struct { + name string + path string + locale *Locale + cmd uint8 +} + +var chLocale = make(chan *localeData, 1) + +const ( + saveLocale uint8 = iota + removeLocale +) + +func init() { + go localeWriter() +} + +func localeWriter() { + for { + select { + case data := <-chLocale: + switch data.cmd { + case saveLocale: + if _, ok := Locales[data.name]; !ok { + Locales[data.name] = map[string]*Locale{} + } + Locales[data.name][data.path] = data.locale + + case removeLocale: + if _, ok := Locales[data.name]; ok { + delete(Locales[data.name], data.path) + } + } + } + } +} + +// Locale get the locale +func (parser *TemplateParser) Locale() *Locale { + var locales map[string]*Locale = nil + name, ok := parser.option.Locale.(string) + if !ok { + return nil + } + + root := parser.option.Root + route := parser.option.Route + disableCache := parser.option.Preview || parser.option.Debug || parser.option.Editor || parser.option.DisableCache + locales, ok = Locales[name] + if !ok { + locales = map[string]*Locale{} + } + + locale, ok := locales[route] + if ok && !disableCache { + return locale + } + + path := filepath.Join("public", parser.option.Root, ".locales", name, strings.TrimPrefix(route, root)+".yml") + if exists, err := application.App.Exists(path); !exists { + if err != nil { + log.Error("[parser] %s Locale %s", route, err.Error()) + } + return nil + } + + // Load the locale + locale = &Locale{Name: name} + + raw, err := application.App.Read(path) + if err != nil { + log.Error("[parser] %s Locale %s", route, err.Error()) + return nil + } + + err = yaml.Unmarshal(raw, locale) + if err != nil { + log.Error("[parser] %s Locale %s", route, err.Error()) + return nil + } + + if locale.Timezone == "" { + locale.Timezone = GetSystemTimezone() + } + + if locale.Direction == "" { + locale.Direction = "ltr" + } + + if parser.data != nil { + parser.data["$timezone"] = locale.Timezone + parser.data["$direction"] = locale.Direction + } + + chLocale <- &localeData{name, route, locale, saveLocale} + return locale +} + // MergeTranslations merge the translations func (locale *Locale) MergeTranslations(translations []Translation, prefix ...string) { if locale.Keys == nil { @@ -85,3 +195,53 @@ func (locale *Locale) ParseKeys() { } return } + +// Fmt format the value +func (locale *Locale) Fmt(name string, value string) string { + if locale.Formatter == "" { + return value + } + + pname := fmt.Sprintf("%s.%s", locale.Formatter, name) + p, err := process.Of(pname, value, map[string]string{ + "name": locale.Name, + "timezone": locale.Timezone, + "direction": locale.Direction, + }) + if err != nil { + log.Error("[locale] %s %s", pname, err.Error()) + return value + } + + res, err := p.Exec() + if err != nil { + log.Error("[locale] %s %s", pname, err.Error()) + return value + } + + if v, ok := res.(string); ok { + return v + } + + log.Error("[locale] %s %s", pname, "The formatter must return a string") + return value +} + +// GetSystemTimezone get the system timezone +func GetSystemTimezone() string { + now := time.Now() + + _, offset := now.Zone() + + hours := offset / 3600 + minutes := (offset % 3600) / 60 + + sign := "+" + if hours < 0 || minutes < 0 { + sign = "-" + hours = -hours + minutes = -minutes + } + + return fmt.Sprintf("%s%02d:%02d", sign, hours, minutes) +} diff --git a/sui/core/parser.go b/sui/core/parser.go index cf0afb58..860f6504 100644 --- a/sui/core/parser.go +++ b/sui/core/parser.go @@ -2,15 +2,12 @@ package core import ( "fmt" - "path/filepath" "strings" "github.com/PuerkitoBio/goquery" jsoniter "github.com/json-iterator/go" - "github.com/yaoapp/gou/application" "github.com/yaoapp/kun/log" "golang.org/x/net/html" - "gopkg.in/yaml.v3" ) // Load the jit components @@ -80,94 +77,6 @@ var keepAttrs = map[string]bool{ "s:click": true, } -// Locales the locales -var Locales = map[string]map[string]*Locale{} - -type localeData struct { - name string - path string - locale *Locale - cmd uint8 -} - -var chLocale = make(chan *localeData, 1) - -const ( - saveLocale uint8 = iota - removeLocale -) - -func init() { - go localeWriter() -} - -func localeWriter() { - for { - select { - case data := <-chLocale: - switch data.cmd { - case saveLocale: - if _, ok := Locales[data.name]; !ok { - Locales[data.name] = map[string]*Locale{} - } - Locales[data.name][data.path] = data.locale - - case removeLocale: - if _, ok := Locales[data.name]; ok { - delete(Locales[data.name], data.path) - } - } - } - } -} - -// Locale get the locale -func (parser *TemplateParser) Locale() *Locale { - var locales map[string]*Locale = nil - name, ok := parser.option.Locale.(string) - if !ok { - return nil - } - - root := parser.option.Root - route := parser.option.Route - disableCache := parser.option.Preview || parser.option.Debug || parser.option.Editor || parser.option.DisableCache - locales, ok = Locales[name] - if !ok { - locales = map[string]*Locale{} - } - - locale, ok := locales[route] - if ok && !disableCache { - return locale - } - - path := filepath.Join("public", parser.option.Root, ".locales", name, strings.TrimPrefix(route, root)+".yml") - if exists, err := application.App.Exists(path); !exists { - if err != nil { - log.Error("[parser] %s Locale %s", route, err.Error()) - } - return nil - } - - // Load the locale - locale = &Locale{} - raw, err := application.App.Read(path) - if err != nil { - log.Error("[parser] %s Locale %s", route, err.Error()) - return nil - } - - err = yaml.Unmarshal(raw, locale) - if err != nil { - log.Error("[parser] %s Locale %s", route, err.Error()) - return nil - } - - chLocale <- &localeData{name, route, locale, saveLocale} - return locale -} - // NewTemplateParser create a new template parser func NewTemplateParser(data Data, option *ParserOption) *TemplateParser { if option == nil { @@ -228,6 +137,9 @@ func (parser *TemplateParser) Render(html string) (string, error) { body.AppendHtml(bodyInjectionScript(data, parser.debug())) } + // Fmt + parser.Fmt(doc) + // For editor if parser.option != nil && parser.option.Editor { return doc.Find("body").Html() @@ -245,6 +157,17 @@ func (parser *TemplateParser) Render(html string) (string, error) { return doc.Html() } +// Fmt formats the HTML template +func (parser *TemplateParser) Fmt(doc *goquery.Document) { + if parser.locale != nil { + sels := doc.Find(`[s\:trans-fmt]`) + sels.Each(func(i int, sel *goquery.Selection) { + name := sel.AttrOr("s:trans-fmt", "") + sel.SetText(parser.locale.Fmt(name, sel.Text())) + }) + } +} + // Parse parses and renders the HTML template func (parser *TemplateParser) parseNode(node *html.Node) { diff --git a/sui/core/request.go b/sui/core/request.go index b79cc7ad..c2c2362c 100644 --- a/sui/core/request.go +++ b/sui/core/request.go @@ -76,6 +76,8 @@ func (r *Request) NewData() Data { data["$url"] = r.URL data["$theme"] = r.Theme data["$locale"] = r.Locale + data["$timezone"] = GetSystemTimezone() + data["$direction"] = "ltr" return data } diff --git a/sui/core/types.go b/sui/core/types.go index fd995c8a..aa329033 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -97,40 +97,12 @@ type Translation struct { // Locale is the struct for the locale type Locale struct { - Keys map[string]string `json:"keys,omitempty"` - Messages map[string]string `json:"messages,omitempty"` - Date LocaleDate `json:"date,omitempty"` - Currency LocaleCurrency `json:"currency,omitempty"` - Number LocaleNumber `json:"number,omitempty"` -} - -// LocaleDate the struct for the locale date format -type LocaleDate struct { - Short string `json:"short,omitempty"` - Long string `json:"long,omitempty"` - Full string `json:"full,omitempty"` - Month string `json:"month,omitempty"` - Week string `json:"week,omitempty"` - Year string `json:"year,omitempty"` - Day string `json:"day,omitempty"` - Human string `json:"human,omitempty"` -} - -// LocaleCurrency the struct for the locale currency -type LocaleCurrency struct { - Format string `json:"format,omitempty"` - Unit string `json:"unit,omitempty"` - Separator string `json:"separator,omitempty"` - Delimiter string `json:"delimiter,omitempty"` - Precision int `json:"precision,omitempty"` -} - -// LocaleNumber the struct for the locale number -type LocaleNumber struct { - Format string `json:"format,omitempty"` - Separator string `json:"separator,omitempty"` - Delimiter string `json:"delimiter,omitempty"` - Precision int `json:"precision,omitempty"` + Name string `json:"name,omitempty"` + Formatter string `json:"formatter,omitempty"` + Keys map[string]string `json:"keys,omitempty"` + Messages map[string]string `json:"messages,omitempty"` + Direction string `json:"direction,omitempty"` + Timezone string `json:"timezone,omitempty"` } // PageTreeNode is the struct for the page tree node diff --git a/sui/storages/local/build.go b/sui/storages/local/build.go index fea32004..c4e9f481 100644 --- a/sui/storages/local/build.go +++ b/sui/storages/local/build.go @@ -263,12 +263,14 @@ func (tmpl *Template) getLocale(name string, route string, pageOnly ...bool) cor } locale := core.Locale{ - Keys: map[string]string{}, - Messages: map[string]string{}, - Date: global.Date, - Currency: global.Currency, - Number: global.Number, + Name: name, + Keys: map[string]string{}, + Messages: map[string]string{}, + Direction: global.Direction, + Timezone: global.Timezone, + Formatter: global.Formatter, } + raw, err := tmpl.local.fs.ReadFile(file) if err != nil { log.Error(`[SUI] Read the locale file error: %s`, err.Error())