Refactor localization system and migrate to Fyne's built-in support
Replaced the `i18n` and `toml` dependencies with Fyne's built-in language system for localization management. Updated the `Localizer` implementation to handle translations using JSON files and embed functionality. Simplified language selection and persisted settings via Fyne's preferences API.
This commit is contained in:
@@ -1,21 +1,28 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"github.com/BurntSushi/toml"
|
||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||
"golang.org/x/text/cases"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/lang"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/language/display"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
)
|
||||
|
||||
//go:embed translations
|
||||
var translations embed.FS
|
||||
|
||||
var supportedLanguages = map[string]Lang{
|
||||
"ru": {Code: "ru", Title: "Русский"},
|
||||
"kk": {Code: "kk", Title: "Қазақ Тілі"},
|
||||
"en": {Code: "en", Title: "English"},
|
||||
}
|
||||
|
||||
type LocalizerContract interface {
|
||||
IsStartWithLanguageSelection() bool
|
||||
GetMessage(key string, data ...any) string
|
||||
GetLanguages() []Lang
|
||||
GetMessage(localizeConfig *i18n.LocalizeConfig) string
|
||||
SetCurrentLanguage(lang Lang) error
|
||||
SetCurrentLanguageByCode(code string) error
|
||||
GetCurrentLanguage() *CurrentLanguage
|
||||
GetCurrentLanguage() Lang
|
||||
SetCurrentLanguage(selectLang Lang, isSaveSetting bool) error
|
||||
AddChangeCallback(messageID string, callback func(text string))
|
||||
}
|
||||
|
||||
@@ -24,135 +31,164 @@ type Lang struct {
|
||||
Title string
|
||||
}
|
||||
|
||||
type CurrentLanguage struct {
|
||||
Lang Lang
|
||||
localizer *i18n.Localizer
|
||||
localizerDefault *i18n.Localizer
|
||||
}
|
||||
|
||||
type changeCallback struct {
|
||||
messageID string
|
||||
callback func(text string)
|
||||
}
|
||||
|
||||
type Localizer struct {
|
||||
bundle *i18n.Bundle
|
||||
languages []Lang
|
||||
currentLanguage *CurrentLanguage
|
||||
changeCallbacks map[int]*changeCallback
|
||||
setting SettingLanguageContract
|
||||
currentLang Lang
|
||||
changeCallbacks map[int]*changeCallback
|
||||
isStartWithLanguageSelection bool
|
||||
}
|
||||
|
||||
func NewLocalizer(directory string, languageDefault language.Tag) (*Localizer, error) {
|
||||
bundle := i18n.NewBundle(languageDefault)
|
||||
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||
func newLocalizer(app fyne.App) (*Localizer, error) {
|
||||
setting := newSettingLanguage(app)
|
||||
currentLanguage, isLanguageNotSupported := setting.GetLang()
|
||||
|
||||
languages, err := initLanguages(directory, bundle)
|
||||
localizer := &Localizer{
|
||||
setting: setting,
|
||||
changeCallbacks: map[int]*changeCallback{},
|
||||
isStartWithLanguageSelection: isLanguageNotSupported,
|
||||
}
|
||||
|
||||
err := localizer.SetCurrentLanguage(currentLanguage, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localizerDefault := i18n.NewLocalizer(bundle, languageDefault.String())
|
||||
|
||||
return &Localizer{
|
||||
bundle: bundle,
|
||||
languages: languages,
|
||||
currentLanguage: &CurrentLanguage{
|
||||
Lang: Lang{
|
||||
Code: languageDefault.String(),
|
||||
Title: cases.Title(languageDefault).String(display.Self.Name(languageDefault)),
|
||||
},
|
||||
localizer: localizerDefault,
|
||||
localizerDefault: localizerDefault,
|
||||
},
|
||||
changeCallbacks: map[int]*changeCallback{},
|
||||
}, nil
|
||||
return localizer, nil
|
||||
}
|
||||
|
||||
func initLanguages(directory string, bundle *i18n.Bundle) ([]Lang, error) {
|
||||
var languages []Lang
|
||||
func (l *Localizer) IsStartWithLanguageSelection() bool {
|
||||
return l.isStartWithLanguageSelection
|
||||
}
|
||||
|
||||
files, err := filepath.Glob(directory + "/active.*.toml")
|
||||
func (l *Localizer) GetMessage(key string, data ...any) string {
|
||||
return lang.L(key, data...)
|
||||
}
|
||||
|
||||
func (l *Localizer) GetLanguages() []Lang {
|
||||
return getLanguages()
|
||||
}
|
||||
|
||||
func (l *Localizer) GetCurrentLanguage() Lang {
|
||||
return l.currentLang
|
||||
}
|
||||
|
||||
func (l *Localizer) SetCurrentLanguage(selectLang Lang, isSaveSetting bool) error {
|
||||
l.currentLang = selectLang
|
||||
|
||||
translationsData, err := l.getTranslations(selectLang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, file := range files {
|
||||
lang, err := bundle.LoadMessageFile(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
title := cases.Title(lang.Tag).String(display.Self.Name(lang.Tag))
|
||||
languages = append(languages, Lang{Code: lang.Tag.String(), Title: title})
|
||||
return err
|
||||
}
|
||||
|
||||
sort.Sort(languagesSort(languages))
|
||||
|
||||
return languages, nil
|
||||
}
|
||||
|
||||
func (l Localizer) GetLanguages() []Lang {
|
||||
return l.languages
|
||||
}
|
||||
|
||||
func (l Localizer) GetMessage(localizeConfig *i18n.LocalizeConfig) string {
|
||||
message, err := l.GetCurrentLanguage().localizer.Localize(localizeConfig)
|
||||
name := lang.SystemLocale().LanguageString()
|
||||
err = lang.AddTranslations(fyne.NewStaticResource(name+".json", translationsData))
|
||||
if err != nil {
|
||||
message, err = l.GetCurrentLanguage().localizerDefault.Localize(localizeConfig)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if isSaveSetting {
|
||||
l.setting.SetLang(selectLang)
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
func (l Localizer) SetCurrentLanguage(lang Lang) error {
|
||||
l.currentLanguage.Lang = lang
|
||||
l.currentLanguage.localizer = i18n.NewLocalizer(l.bundle, lang.Code)
|
||||
l.eventSetCurrentLanguage()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Localizer) SetCurrentLanguageByCode(code string) error {
|
||||
lang, err := language.Parse(code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
title := cases.Title(lang).String(display.Self.Name(lang))
|
||||
return l.SetCurrentLanguage(Lang{Code: lang.String(), Title: title})
|
||||
}
|
||||
|
||||
func (l Localizer) GetCurrentLanguage() *CurrentLanguage {
|
||||
return l.currentLanguage
|
||||
}
|
||||
|
||||
func (l Localizer) AddChangeCallback(messageID string, callback func(text string)) {
|
||||
func (l *Localizer) AddChangeCallback(messageID string, callback func(text string)) {
|
||||
l.changeCallbacks[len(l.changeCallbacks)] = &changeCallback{messageID: messageID, callback: callback}
|
||||
}
|
||||
|
||||
func (l Localizer) eventSetCurrentLanguage() {
|
||||
func (l *Localizer) eventSetCurrentLanguage() {
|
||||
for _, changeCallback := range l.changeCallbacks {
|
||||
text := l.GetMessage(&i18n.LocalizeConfig{MessageID: changeCallback.messageID})
|
||||
text := l.GetMessage(changeCallback.messageID)
|
||||
changeCallback.callback(text)
|
||||
}
|
||||
}
|
||||
|
||||
type languagesSort []Lang
|
||||
|
||||
func (l languagesSort) Len() int { return len(l) }
|
||||
func (l languagesSort) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
|
||||
func (l languagesSort) Less(i, j int) bool {
|
||||
return languagePriority(l[i]) < languagePriority(l[j])
|
||||
}
|
||||
func languagePriority(l Lang) int {
|
||||
priority := 0
|
||||
|
||||
switch l.Code {
|
||||
case "ru":
|
||||
priority = -3
|
||||
case "kk":
|
||||
priority = -2
|
||||
case "en":
|
||||
priority = -1
|
||||
func (l *Localizer) getTranslations(language Lang) ([]byte, error) {
|
||||
baseJson, err := translations.ReadFile("translations/base." + language.Code + ".json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appJson, err := translations.ReadFile("translations/app." + language.Code + ".json")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return priority
|
||||
return l.mergeTranslations(baseJson, appJson)
|
||||
}
|
||||
|
||||
func (l *Localizer) mergeTranslations(baseJson []byte, appJson []byte) ([]byte, error) {
|
||||
base := map[string]interface{}{}
|
||||
custom := map[string]interface{}{}
|
||||
err := json.Unmarshal(baseJson, &base)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(appJson, &custom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for k, v := range custom {
|
||||
base[k] = v
|
||||
}
|
||||
return json.Marshal(base)
|
||||
}
|
||||
|
||||
func getLanguages() []Lang {
|
||||
items := []Lang{}
|
||||
for _, item := range supportedLanguages {
|
||||
items = append(items, item)
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
type SettingLanguageContract interface {
|
||||
GetLang() (currentLang Lang, isLanguageNotSupported bool)
|
||||
SetLang(language Lang)
|
||||
}
|
||||
|
||||
type SettingLanguage struct {
|
||||
app fyne.App
|
||||
}
|
||||
|
||||
func newSettingLanguage(app fyne.App) *SettingLanguage {
|
||||
return &SettingLanguage{
|
||||
app: app,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SettingLanguage) GetLang() (currentLang Lang, isLanguageNotSupported bool) {
|
||||
languageCode := s.app.Preferences().String("language")
|
||||
currentLang = supportedLanguages["ru"]
|
||||
|
||||
if languageCode == "" {
|
||||
languageTag, err := language.Parse(lang.SystemLocale().LanguageString())
|
||||
if err != nil {
|
||||
return currentLang, true
|
||||
}
|
||||
base, _ := languageTag.Base()
|
||||
languageCode = base.String()
|
||||
}
|
||||
|
||||
if findLang, ok := findSupportedLanguage(languageCode); ok {
|
||||
return findLang, false
|
||||
}
|
||||
|
||||
return currentLang, true
|
||||
}
|
||||
|
||||
func (s *SettingLanguage) SetLang(language Lang) {
|
||||
s.app.Preferences().SetString("language", language.Code)
|
||||
}
|
||||
|
||||
func findSupportedLanguage(code string) (Lang, bool) {
|
||||
lang, ok := supportedLanguages[code]
|
||||
return lang, ok
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user