Merge pull request #692 from trheyi/main

optimize SUI page writeLocaleSource and writeLocaleFiles logic
This commit is contained in:
Max 2024-07-17 09:26:52 +08:00 committed by GitHub
commit a2a60d87cc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 412 additions and 124 deletions

View file

@ -36,6 +36,18 @@ func (ctx *BuildContext) GetJitComponents() []string {
return jitComponents
}
// GetComponents get the components
func (ctx *BuildContext) GetComponents() []string {
if ctx.components == nil {
return []string{}
}
components := []string{}
for name := range ctx.components {
components = append(components, name)
}
return components
}
// GetTranslations get the translations
func (ctx *BuildContext) GetTranslations() []Translation {
if ctx.translations == nil {

87
sui/core/locale.go Normal file
View file

@ -0,0 +1,87 @@
package core
import (
"fmt"
"regexp"
)
// MergeTranslations merge the translations
func (locale *Locale) MergeTranslations(translations []Translation, prefix ...string) {
if locale.Keys == nil {
locale.Keys = map[string]string{}
}
if locale.Messages == nil {
locale.Messages = map[string]string{}
}
var reg *regexp.Regexp = nil
if len(prefix) > 0 && prefix[0] != "" {
reg = regexp.MustCompile(fmt.Sprintf(`^%s_([0-9]+)$`, prefix[0]))
}
for _, t := range translations {
// Keep only the keys that start with the keyPrefix
if reg != nil && !reg.MatchString(t.Key) {
continue
}
message := t.Message
if _, has := locale.Messages[message]; has {
message = locale.Messages[message]
}
locale.Keys[t.Key] = message
msg, has := locale.Messages[t.Message]
if has && msg != t.Message {
continue
}
locale.Messages[t.Message] = t.Message
}
}
// Merge merge the locale
func (locale *Locale) Merge(locale2 Locale) {
if locale2.Keys != nil {
if locale.Keys == nil {
locale.Keys = map[string]string{}
}
for key, value := range locale2.Keys {
if _, has := locale.Keys[key]; has {
continue
}
locale.Keys[key] = value
}
}
if locale2.Messages != nil {
if locale.Messages == nil {
locale.Messages = map[string]string{}
}
for key, value := range locale2.Messages {
if _, has := locale.Messages[key]; has {
continue
}
locale.Messages[key] = value
}
}
}
// ParseKeys match
func (locale *Locale) ParseKeys() {
if locale.Keys == nil {
locale.Keys = map[string]string{}
}
if locale.Messages == nil {
locale.Messages = map[string]string{}
}
for key, msgKey := range locale.Keys {
if message, has := locale.Messages[msgKey]; has {
locale.Keys[key] = message
}
}
return
}

209
sui/core/locale_test.go Normal file
View file

@ -0,0 +1,209 @@
package core
import (
"testing"
)
func TestLocaleMergeTranslations(t *testing.T) {
tests := []struct {
name string
locale Locale
translations []Translation
prefix string
expectedKeys map[string]string
expectedMsgs map[string]string
}{
{
name: "Empty translations",
locale: Locale{
Keys: map[string]string{},
Messages: map[string]string{},
},
translations: []Translation{},
prefix: "",
expectedKeys: map[string]string{},
expectedMsgs: map[string]string{},
},
{
name: "Nil Keys and Messages",
locale: Locale{
Keys: nil,
Messages: nil,
},
translations: []Translation{
{Key: "greeting", Message: "Hello"},
},
prefix: "",
expectedKeys: map[string]string{
"greeting": "Hello",
},
expectedMsgs: map[string]string{
"Hello": "Hello",
},
},
{
name: "With prefix",
locale: Locale{
Keys: map[string]string{},
Messages: map[string]string{},
},
translations: []Translation{
{Key: "prefix_1", Message: "Hello"},
{Key: "other_1", Message: "World"},
},
prefix: "prefix",
expectedKeys: map[string]string{
"prefix_1": "Hello",
},
expectedMsgs: map[string]string{
"Hello": "Hello",
},
},
{
name: "Update existing keys and values",
locale: Locale{
Keys: map[string]string{
"greeting": "Hi",
},
Messages: map[string]string{
"Hi": "Hi",
},
},
translations: []Translation{
{Key: "greeting", Message: "Hello"},
},
prefix: "",
expectedKeys: map[string]string{
"greeting": "Hello",
},
expectedMsgs: map[string]string{
"Hi": "Hi",
"Hello": "Hello",
},
},
{
name: "Duplicate messages",
locale: Locale{
Keys: map[string]string{},
Messages: map[string]string{},
},
translations: []Translation{
{Key: "welcome", Message: "Hello"},
{Key: "farewell", Message: "Hello"},
},
prefix: "",
expectedKeys: map[string]string{
"welcome": "Hello",
"farewell": "Hello",
},
expectedMsgs: map[string]string{
"Hello": "Hello",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.locale.MergeTranslations(tt.translations, tt.prefix)
if !testCompareMaps(tt.locale.Keys, tt.expectedKeys) {
t.Errorf("expected keys %v, got %v", tt.expectedKeys, tt.locale.Keys)
}
if !testCompareMaps(tt.locale.Messages, tt.expectedMsgs) {
t.Errorf("expected messages %v, got %v", tt.expectedMsgs, tt.locale.Messages)
}
})
}
}
func TestLocaleMerge(t *testing.T) {
tests := []struct {
name string
locale Locale
locale2 Locale
expectedKeys map[string]string
expectedMsgs map[string]string
}{
{
name: "Nil Keys and Messages in locale2",
locale: Locale{
Keys: map[string]string{"greeting": "Hello"},
Messages: map[string]string{"Hello": "Hello"},
},
locale2: Locale{
Keys: nil,
Messages: nil,
},
expectedKeys: map[string]string{"greeting": "Hello"},
expectedMsgs: map[string]string{"Hello": "Hello"},
},
{
name: "Nil Keys and Messages in locale",
locale: Locale{
Keys: nil,
Messages: nil,
},
locale2: Locale{
Keys: map[string]string{"farewell": "Goodbye"},
Messages: map[string]string{"Goodbye": "Goodbye"},
},
expectedKeys: map[string]string{"farewell": "Goodbye"},
expectedMsgs: map[string]string{"Goodbye": "Goodbye"},
},
{
name: "Merge non-existing keys and messages",
locale: Locale{
Keys: map[string]string{"greeting": "Hello"},
Messages: map[string]string{"Hello": "Hello"},
},
locale2: Locale{
Keys: map[string]string{"farewell": "Goodbye"},
Messages: map[string]string{"Goodbye": "Goodbye"},
},
expectedKeys: map[string]string{
"greeting": "Hello",
"farewell": "Goodbye",
},
expectedMsgs: map[string]string{
"Hello": "Hello",
"Goodbye": "Goodbye",
},
},
{
name: "Merge with existing keys and messages",
locale: Locale{
Keys: map[string]string{"greeting": "Hello"},
Messages: map[string]string{"Hello": "Hello"},
},
locale2: Locale{
Keys: map[string]string{"greeting": "Hi"},
Messages: map[string]string{"Hello": "Hi"},
},
expectedKeys: map[string]string{"greeting": "Hello"},
expectedMsgs: map[string]string{"Hello": "Hello"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.locale.Merge(tt.locale2)
if !testCompareMaps(tt.locale.Keys, tt.expectedKeys) {
t.Errorf("expected keys %v, got %v", tt.expectedKeys, tt.locale.Keys)
}
if !testCompareMaps(tt.locale.Messages, tt.expectedMsgs) {
t.Errorf("expected messages %v, got %v", tt.expectedMsgs, tt.locale.Messages)
}
})
}
}
func testCompareMaps(a, b map[string]string) bool {
if len(a) != len(b) {
return false
}
for k, v := range a {
if b[k] != v {
return false
}
}
return true
}

View file

@ -80,8 +80,14 @@ func ComponentName(name string, hash ...bool) string {
// TranslationKey convert the name to translation key
func TranslationKey(name string, sequence int) string {
prefix := TranslationKeyPrefix(name)
return fmt.Sprintf("%s_%d", prefix, sequence)
}
// TranslationKeyPrefix convert the name to translation key prefix
func TranslationKeyPrefix(name string) string {
name = strings.ReplaceAll(name, "/", "_")
name = strings.ReplaceAll(name, "[", "_")
name = strings.ReplaceAll(name, "]", "_")
return fmt.Sprintf("trans_%s_%d", name, sequence)
return fmt.Sprintf("trans_%s", name)
}

View file

@ -217,6 +217,89 @@ func (tmpl *Template) SyncAssets(option *core.BuildOption) error {
return copyDirectory(sourceRoot, targetRoot)
}
func (tmpl *Template) getLocaleGlobal(name string) core.Locale {
global := core.Locale{
Keys: map[string]string{},
Messages: map[string]string{},
}
file := filepath.Join(tmpl.Root, "__locales", name, "__global.yml")
exist, err := tmpl.local.fs.Exists(file)
if err != nil {
log.Error(`[SUI] Check the global locale file error: %s`, err.Error())
return global
}
if !exist {
return global
}
raw, err := tmpl.local.fs.ReadFile(file)
if err != nil {
log.Error(`[SUI] Read the global locale file error: %s`, err.Error())
return global
}
err = yaml.Unmarshal(raw, &global)
if err != nil {
log.Error(`[SUI] Parse the global locale file error: %s`, err.Error())
return global
}
return global
}
func (tmpl *Template) getLocale(name string, route string, pageOnly ...bool) core.Locale {
file := filepath.Join(tmpl.Root, "__locales", name, fmt.Sprintf("%s.yml", route))
global := tmpl.getLocaleGlobal(name)
// Check the locale file
exist, err := tmpl.local.fs.Exists(file)
if err != nil {
return global
}
if !exist {
return global
}
locale := core.Locale{
Keys: map[string]string{},
Messages: map[string]string{},
Date: global.Date,
Currency: global.Currency,
Number: global.Number,
}
raw, err := tmpl.local.fs.ReadFile(file)
if err != nil {
log.Error(`[SUI] Read the locale file error: %s`, err.Error())
return global
}
err = yaml.Unmarshal(raw, &locale)
if err != nil {
log.Error(`[SUI] Parse the locale file error: %s`, err.Error())
return global
}
if len(pageOnly) == 0 || !pageOnly[0] {
// Merge the global
for key, message := range global.Keys {
if _, ok := locale.Keys[key]; !ok {
locale.Keys[key] = message
}
}
for key, message := range global.Messages {
if _, ok := locale.Messages[key]; !ok {
locale.Messages[key] = message
}
}
}
return locale
}
// Build is the struct for the public
func (page *Page) Build(globalCtx *core.GlobalBuildContext, option *core.BuildOption) ([]string, error) {
@ -399,89 +482,6 @@ func (page *Page) localeFiles(data map[string]interface{}) map[string]string {
return roots
}
func (page *Page) localeGlobal(name string) core.Locale {
global := core.Locale{
Keys: map[string]string{},
Messages: map[string]string{},
}
file := filepath.Join(page.tmpl.Root, "__locales", name, "__global.yml")
exist, err := page.tmpl.local.fs.Exists(file)
if err != nil {
log.Error(`[SUI] Check the global locale file error: %s`, err.Error())
return global
}
if !exist {
return global
}
raw, err := page.tmpl.local.fs.ReadFile(file)
if err != nil {
log.Error(`[SUI] Read the global locale file error: %s`, err.Error())
return global
}
err = yaml.Unmarshal(raw, &global)
if err != nil {
log.Error(`[SUI] Parse the global locale file error: %s`, err.Error())
return global
}
return global
}
func (page *Page) locale(name string, pageOnly ...bool) core.Locale {
file := filepath.Join(page.tmpl.Root, "__locales", name, fmt.Sprintf("%s.yml", page.Route))
global := page.localeGlobal(name)
// Check the locale file
exist, err := page.tmpl.local.fs.Exists(file)
if err != nil {
return global
}
if !exist {
return global
}
locale := core.Locale{
Keys: map[string]string{},
Messages: map[string]string{},
Date: global.Date,
Currency: global.Currency,
Number: global.Number,
}
raw, err := page.tmpl.local.fs.ReadFile(file)
if err != nil {
log.Error(`[SUI] Read the locale file error: %s`, err.Error())
return global
}
err = yaml.Unmarshal(raw, &locale)
if err != nil {
log.Error(`[SUI] Parse the locale file error: %s`, err.Error())
return global
}
if len(pageOnly) == 0 || !pageOnly[0] {
// Merge the global
for key, message := range global.Keys {
if _, ok := locale.Keys[key]; !ok {
locale.Keys[key] = message
}
}
for key, message := range global.Messages {
if _, ok := locale.Messages[key]; !ok {
locale.Messages[key] = message
}
}
}
return locale
}
func (page *Page) writeLocaleSource(ctx *core.BuildContext, option *core.BuildOption) error {
locales := page.tmpl.Locales()
@ -495,25 +495,14 @@ func (page *Page) writeLocaleSource(ctx *core.BuildContext, option *core.BuildOp
}
}
prefix := core.TranslationKeyPrefix(page.Route)
for _, lc := range locales {
if lc.Default {
continue
}
locale := page.locale(lc.Value, true)
for _, t := range translations {
message := t.Message
// Match the key
if _, has := locale.Messages[message]; has {
message = locale.Messages[message]
}
locale.Keys[t.Key] = message
msg, has := locale.Messages[t.Message]
if has && msg != t.Message {
continue
}
locale.Messages[t.Message] = t.Message
}
locale := page.tmpl.getLocale(lc.Value, page.Route, true)
locale.MergeTranslations(translations, prefix)
// Call the hook
var keys any = locale.Keys
@ -571,37 +560,22 @@ func (page *Page) writeLocaleFiles(ctx *core.BuildContext, data map[string]inter
if len(translations) == 0 {
return nil
}
prefix := core.TranslationKeyPrefix(page.Route)
files := page.localeFiles(data)
components := ctx.GetComponents()
for name, file := range files {
locale := page.tmpl.getLocale(name, page.Route)
locale.MergeTranslations(translations, prefix)
// Init Data
keys := map[string]string{}
messages := map[string]string{}
for _, t := range translations {
keys[t.Key] = t.Message
messages[t.Message] = t.Message
// Merge the components locale
for _, route := range components {
compLocale := page.tmpl.getLocale(name, route, true)
compLocale.ParseKeys()
locale.Merge(compLocale)
}
locale := page.locale(name)
for key := range keys {
if _, has := locale.Keys[key]; has {
keys[key] = locale.Keys[key]
}
if msgValue, has := locale.Messages[keys[key]]; has {
keys[key] = msgValue
}
}
for message := range messages {
if _, has := locale.Messages[message]; has {
messages[message] = locale.Messages[message]
}
}
locale.Keys = keys
locale.Messages = messages
// Remove messages
locale.Messages = map[string]string{}
raw, err := yaml.Marshal(locale)
if err != nil {
log.Error(`[SUI] Marshal the locale file error: %s`, err.Error())