From e7d483620cbd8c9e4699c69cb303f424eeec935b Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:28:41 +0800 Subject: [PATCH 1/9] feat: Implement language caching and improve language handling in i18n module --- core/app/service/setting.go | 2 + core/i18n/i18n.go | 84 ++++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 28 deletions(-) diff --git a/core/app/service/setting.go b/core/app/service/setting.go index 0741bd82d7bd..b1e4c819f7ef 100644 --- a/core/app/service/setting.go +++ b/core/app/service/setting.go @@ -26,6 +26,7 @@ import ( "github.com/1Panel-dev/1Panel/core/buserr" "github.com/1Panel-dev/1Panel/core/constant" "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" "github.com/1Panel-dev/1Panel/core/utils/common" "github.com/1Panel-dev/1Panel/core/utils/controller" "github.com/1Panel-dev/1Panel/core/utils/encrypt" @@ -148,6 +149,7 @@ func (u *SettingService) Update(key, value string) error { case "UserName", "Password": _ = global.SESSION.Clean() case "Language": + i18n.SetCachedDBLanguage(value) if err := xpack.Sync(constant.SyncLanguage); err != nil { global.LOG.Errorf("sync language to node failed, err: %v", err) } diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 802a60850d17..c5db242e966e 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -2,8 +2,10 @@ package i18n import ( "embed" - "github.com/1Panel-dev/1Panel/core/app/repo" "strings" + "sync/atomic" + + "github.com/1Panel-dev/1Panel/core/app/repo" "github.com/1Panel-dev/1Panel/core/global" @@ -13,6 +15,23 @@ import ( "gopkg.in/yaml.v3" ) +const defaultLang = "en" + +var langFiles = map[string]string{ + "zh": "lang/zh.yaml", + "en": "lang/en.yaml", + "zh-Hant": "lang/zh-Hant.yaml", + "fa": "lang/fa.yaml", + "pt": "lang/pt.yaml", + "pt-BR": "lang/pt-BR.yaml", + "ja": "lang/ja.yaml", + "ru": "lang/ru.yaml", + "ms": "lang/ms.yaml", + "ko": "lang/ko.yaml", + "tr": "lang/tr.yaml", + "es-ES": "lang/es-ES.yaml", +} + func GetMsgWithMap(key string, maps map[string]interface{}) string { var content string if maps == nil { @@ -112,43 +131,35 @@ var bundle *i18n.Bundle func UseI18n() gin.HandlerFunc { return func(context *gin.Context) { - lang := context.GetHeader("Accept-Language") - if lang == "" { - lang = GetLanguageFromDB() - } - global.I18n = i18n.NewLocalizer(bundle, lang) + global.I18n = i18n.NewLocalizer(bundle, GetLanguage()) } } func Init() { bundle = i18n.NewBundle(language.Chinese) bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) - _, _ = bundle.LoadMessageFileFS(fs, "lang/zh.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/en.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/zh-Hant.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/fa.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/pt.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/pt-BR.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/ja.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/ru.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/ms.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/ko.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/tr.yaml") - _, _ = bundle.LoadMessageFileFS(fs, "lang/es-ES.yaml") - lang := GetLanguageFromDB() - global.I18n = i18n.NewLocalizer(bundle, lang) + + for _, file := range langFiles { + if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { + global.LOG.Warnf("failed to load language file %s: %v", file, err) + } + } + + dbLang := getLanguageFromDBInternal() + SetCachedDBLanguage(dbLang) + if dbLang == "" { + dbLang = defaultLang + } + global.I18n = i18n.NewLocalizer(bundle, dbLang) } func UseI18nForCmd(lang string) { if lang == "" { - lang = "en" - } - - if bundle == nil { - Init() + lang = defaultLang } global.I18nForCmd = i18n.NewLocalizer(bundle, lang) } + func GetMsgByKeyForCmd(key string) string { if global.I18nForCmd == nil { UseI18nForCmd("") @@ -158,6 +169,7 @@ func GetMsgByKeyForCmd(key string) string { }) return content } + func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string { if global.I18nForCmd == nil { UseI18nForCmd("") @@ -181,13 +193,29 @@ func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string { } } -func GetLanguageFromDB() string { +func getLanguageFromDBInternal() string { if global.DB == nil { - return "en" + return defaultLang } lang, _ := repo.NewISettingRepo().GetValueByKey("Language") if lang == "" { - return "en" + return defaultLang } return lang } + +var cachedDBLang atomic.Value + +func GetLanguage() string { + if v := cachedDBLang.Load(); v != nil { + return v.(string) + } + return defaultLang +} + +func SetCachedDBLanguage(lang string) { + if lang == "" { + lang = defaultLang + } + cachedDBLang.Store(lang) +} From 83874f4c2738731b2c02dccdbb5c90a85fe8bdce Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:35:28 +0800 Subject: [PATCH 2/9] refactor: Optimize i18n initialization with sync.Once for thread safety --- core/i18n/i18n.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index c5db242e966e..e57824ed6f96 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -3,6 +3,7 @@ package i18n import ( "embed" "strings" + "sync" "sync/atomic" "github.com/1Panel-dev/1Panel/core/app/repo" @@ -136,24 +137,29 @@ func UseI18n() gin.HandlerFunc { } func Init() { - bundle = i18n.NewBundle(language.Chinese) - bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) - - for _, file := range langFiles { - if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { - global.LOG.Warnf("failed to load language file %s: %v", file, err) + initOnce.Do(func() { + bundle = i18n.NewBundle(language.Chinese) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) + + for _, file := range langFiles { + if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { + global.LOG.Warnf("failed to load language file %s: %v", file, err) + } } - } - dbLang := getLanguageFromDBInternal() - SetCachedDBLanguage(dbLang) - if dbLang == "" { - dbLang = defaultLang - } - global.I18n = i18n.NewLocalizer(bundle, dbLang) + dbLang := getLanguageFromDBInternal() + SetCachedDBLanguage(dbLang) + if dbLang == "" { + dbLang = defaultLang + } + global.I18n = i18n.NewLocalizer(bundle, dbLang) + }) } func UseI18nForCmd(lang string) { + if bundle == nil { + Init() + } if lang == "" { lang = defaultLang } @@ -205,6 +211,7 @@ func getLanguageFromDBInternal() string { } var cachedDBLang atomic.Value +var initOnce sync.Once func GetLanguage() string { if v := cachedDBLang.Load(); v != nil { From 18114c545ab43626b7599472bd37ad56efe45ef3 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:40:23 +0800 Subject: [PATCH 3/9] fix: Replace logging with fmt.Println for language file loading errors in i18n module --- core/i18n/i18n.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index e57824ed6f96..8e5d335d9fbe 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -2,6 +2,7 @@ package i18n import ( "embed" + "fmt" "strings" "sync" "sync/atomic" @@ -143,7 +144,7 @@ func Init() { for _, file := range langFiles { if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { - global.LOG.Warnf("failed to load language file %s: %v", file, err) + fmt.Println("failed to load language file %s: %v", file, err) } } From b057a4b1bdc8048e5fb02efcc08bee3146627450 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:42:06 +0800 Subject: [PATCH 4/9] fix: Correct format string in error logging for language file loading in i18n module --- core/i18n/i18n.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 8e5d335d9fbe..0438761f21a7 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -144,7 +144,7 @@ func Init() { for _, file := range langFiles { if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { - fmt.Println("failed to load language file %s: %v", file, err) + fmt.Printf("failed to load language file %s: %v\n", file, err) } } From bf437900428466f9b70e3f57cccb1d0d5232b381 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:55:07 +0800 Subject: [PATCH 5/9] fix: Update language files and improve error handling in i18n module --- core/i18n/i18n.go | 10 +++++++--- core/i18n/lang/en.yaml | 10 +++++----- core/i18n/lang/es-ES.yaml | 10 +++++----- core/i18n/lang/ja.yaml | 10 +++++----- core/i18n/lang/ko.yaml | 10 +++++----- core/i18n/lang/pt-BR.yaml | 10 +++++----- core/i18n/lang/ru.yaml | 10 +++++----- core/i18n/lang/tr.yaml | 10 +++++----- core/i18n/lang/zh-Hant.yaml | 10 +++++----- 9 files changed, 47 insertions(+), 43 deletions(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 0438761f21a7..911e80e94af4 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -23,12 +23,10 @@ var langFiles = map[string]string{ "zh": "lang/zh.yaml", "en": "lang/en.yaml", "zh-Hant": "lang/zh-Hant.yaml", - "fa": "lang/fa.yaml", - "pt": "lang/pt.yaml", "pt-BR": "lang/pt-BR.yaml", "ja": "lang/ja.yaml", "ru": "lang/ru.yaml", - "ms": "lang/ms.yaml", + "ms": "lang/ms.yml", "ko": "lang/ko.yaml", "tr": "lang/tr.yaml", "es-ES": "lang/es-ES.yaml", @@ -142,12 +140,18 @@ func Init() { bundle = i18n.NewBundle(language.Chinese) bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) + isSuccess := true for _, file := range langFiles { if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { fmt.Printf("failed to load language file %s: %v\n", file, err) + isSuccess = false } } + if !isSuccess { + panic("failed to load language files, See log above for details") + } + dbLang := getLanguageFromDBInternal() SetCachedDBLanguage(dbLang) if dbLang == "" { diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml index ebf786366d88..6579df3fad67 100644 --- a/core/i18n/lang/en.yaml +++ b/core/i18n/lang/en.yaml @@ -37,11 +37,11 @@ MasterNode: "Master Node" # app CustomAppStoreFileValid: "Application store package requires .tar.gz format" ErrFileNotFound: "{{ .name }} file does not exist" -AppBackup: 'Application backup', -AppBackupPush: 'Transfer application backup file {{.file}} to node {{ .name }}', -ErrSourceTargetSame: 'Source node and target node cannot be the same!', -AppInstall: 'Install application {{ .name }} on node {{ .targetNode }}', -AppInstallCheck: 'Check application installation environment', +AppBackup: 'Application backup' +AppBackupPush: 'Transfer application backup file {{.file}} to node {{ .name }}' +ErrSourceTargetSame: 'Source node and target node cannot be the same!' +AppInstall: 'Install application {{ .name }} on node {{ .targetNode }}' +AppInstallCheck: 'Check application installation environment' # backup ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted" diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml index efce69f02bd0..f4dff0f456ca 100644 --- a/core/i18n/lang/es-ES.yaml +++ b/core/i18n/lang/es-ES.yaml @@ -37,11 +37,11 @@ MasterNode: "Nodo Maestro" # app CustomAppStoreFileValid: "El paquete de la tienda de aplicaciones debe tener formato .tar.gz" ErrFileNotFound: "El archivo {{ .name }} no existe" -AppBackup: 'Copia de seguridad de aplicación', -AppBackupPush: 'Transferir archivo de copia de seguridad de aplicación {{.file}} al nodo {{ .name }}', -ErrSourceTargetSame: '¡El nodo de origen y el nodo de destino no pueden ser el mismo!', -AppInstall: 'Instalar aplicación {{ .name }} en nodo {{ .targetNode }}', -AppInstallCheck: 'Verificar entorno de instalación de aplicación', +AppBackup: 'Copia de seguridad de aplicación' +AppBackupPush: 'Transferir archivo de copia de seguridad de aplicación {{.file}} al nodo {{ .name }}' +ErrSourceTargetSame: '¡El nodo de origen y el nodo de destino no pueden ser el mismo!' +AppInstall: 'Instalar aplicación {{ .name }} en nodo {{ .targetNode }}' +AppInstallCheck: 'Verificar entorno de instalación de aplicación' # backup ErrBackupInUsed: "Esta cuenta de respaldo se utiliza en tareas programadas y no se puede eliminar" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml index a120817ea3bf..fe2d19913b10 100644 --- a/core/i18n/lang/ja.yaml +++ b/core/i18n/lang/ja.yaml @@ -37,11 +37,11 @@ MasterNode: "マスターノード" # app CustomAppStoreFileValid: "アプリストアパッケージは .tar.gz 形式である必要があります" ErrFileNotFound: "{{ .name }} ファイルが存在しません" -AppBackup: 'アプリケーションバックアップ', -AppBackupPush: 'アプリケーションバックアップファイル {{.file}} をノード {{ .name }} に転送', -ErrSourceTargetSame: 'ソースノードとターゲットノードは同じにできません!', -AppInstall: 'ノード {{ .targetNode }} にアプリケーション {{ .name }} をインストール', -AppInstallCheck: 'アプリケーションインストール環境を確認', +AppBackup: 'アプリケーションバックアップ' +AppBackupPush: 'アプリケーションバックアップファイル {{.file}} をノード {{ .name }} に転送' +ErrSourceTargetSame: 'ソースノードとターゲットノードは同じにできません!' +AppInstall: 'ノード {{ .targetNode }} にアプリケーション {{ .name }} をインストール' +AppInstallCheck: 'アプリケーションインストール環境を確認' # backup ErrBackupInUsed: "このバックアップアカウントはスケジュールタスクで使用されており、削除できません" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml index 22149304abb5..2508f1a47949 100644 --- a/core/i18n/lang/ko.yaml +++ b/core/i18n/lang/ko.yaml @@ -37,11 +37,11 @@ MasterNode: "마스터 노드" # app CustomAppStoreFileValid: "앱 스토어 패키지는 .tar.gz 형식이어야 합니다" ErrFileNotFound: "{{ .name }} 파일이 존재하지 않습니다" -AppBackup: '애플리케이션 백업', -AppBackupPush: '애플리케이션 백업 파일 {{.file}}을(를) 노드 {{ .name }}(으)로 전송', -ErrSourceTargetSame: '소스 노드와 대상 노드는 동일할 수 없습니다!', -AppInstall: '노드 {{ .targetNode }}에 애플리케이션 {{ .name }} 설치', -AppInstallCheck: '애플리케이션 설치 환경 확인', +AppBackup: '애플리케이션 백업' +AppBackupPush: '애플리케이션 백업 파일 {{.file}}을(를) 노드 {{ .name }}(으)로 전송' +ErrSourceTargetSame: '소스 노드와 대상 노드는 동일할 수 없습니다!' +AppInstall: '노드 {{ .targetNode }}에 애플리케이션 {{ .name }} 설치' +AppInstallCheck: '애플리케이션 설치 환경 확인' # backup ErrBackupInUsed: "이 백업 계정은 예약된 작업에 사용 중이며 삭제할 수 없습니다" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml index c40f3daee155..2b30d717f957 100644 --- a/core/i18n/lang/pt-BR.yaml +++ b/core/i18n/lang/pt-BR.yaml @@ -37,11 +37,11 @@ MasterNode: "Nó Mestre" # app CustomAppStoreFileValid: "O pacote da loja de aplicativos deve estar no formato .tar.gz" ErrFileNotFound: "Arquivo {{ .name }} não encontrado" -AppBackup: 'Backup de aplicação', -AppBackupPush: 'Transferir arquivo de backup de aplicação {{.file}} para o nó {{ .name }}', -ErrSourceTargetSame: 'O nó de origem e o nó de destino não podem ser os mesmos!', -AppInstall: 'Instalar aplicação {{ .name }} no nó {{ .targetNode }}', -AppInstallCheck: 'Verificar ambiente de instalação da aplicação', +AppBackup: 'Backup de aplicação' +AppBackupPush: 'Transferir arquivo de backup de aplicação {{.file}} para o nó {{ .name }}' +ErrSourceTargetSame: 'O nó de origem e o nó de destino não podem ser os mesmos!' +AppInstall: 'Instalar aplicação {{ .name }} no nó {{ .targetNode }}' +AppInstallCheck: 'Verificar ambiente de instalação da aplicação' # backup ErrBackupInUsed: "Esta conta de backup está em uso em tarefas agendadas e não pode ser excluída" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml index 084aec387c55..d2e80b8ed5cf 100644 --- a/core/i18n/lang/ru.yaml +++ b/core/i18n/lang/ru.yaml @@ -37,11 +37,11 @@ MasterNode: "Главный узел" # app CustomAppStoreFileValid: "Пакет магазина приложений должен быть в формате .tar.gz" ErrFileNotFound: "Файл {{ .name }} не найден" -AppBackup: 'Резервная копия приложения', -AppBackupPush: 'Передать файл резервной копии приложения {{.file}} на узел {{ .name }}', -ErrSourceTargetSame: 'Исходный узел и целевой узел не могут быть одинаковыми!', -AppInstall: 'Установить приложение {{ .name }} на узел {{ .targetNode }}', -AppInstallCheck: 'Проверить среду установки приложения', +AppBackup: 'Резервная копия приложения' +AppBackupPush: 'Передать файл резервной копии приложения {{.file}} на узел {{ .name }}' +ErrSourceTargetSame: 'Исходный узел и целевой узел не могут быть одинаковыми!' +AppInstall: 'Установить приложение {{ .name }} на узел {{ .targetNode }}' +AppInstallCheck: 'Проверить среду установки приложения' # backup ErrBackupInUsed: "Эта учетная запись резервного копирования используется в запланированных задачах и не может быть удалена" diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml index 7b220cc7af21..0390bb2e53a3 100644 --- a/core/i18n/lang/tr.yaml +++ b/core/i18n/lang/tr.yaml @@ -37,11 +37,11 @@ MasterNode: "Ana Düğüm" # app CustomAppStoreFileValid: "Uygulama mağazası paketi .tar.gz formatında olmalıdır" ErrFileNotFound: "{{ .name }} dosyası mevcut değil" -AppBackup: 'Uygulama yedekleme', -AppBackupPush: 'Uygulama yedek dosyası {{.file}} düğüm {{ .name }} a aktar', -ErrSourceTargetSame: 'Kaynak düğüm ve hedef düğüm aynı olamaz!', -AppInstall: 'Düğüm {{ .targetNode }} üzerine {{ .name }} uygulamasını yükle', -AppInstallCheck: 'Uygulama kurulum ortamını kontrol et', +AppBackup: 'Uygulama yedekleme' +AppBackupPush: 'Uygulama yedek dosyası {{.file}} düğüm {{ .name }} a aktar' +ErrSourceTargetSame: 'Kaynak düğüm ve hedef düğüm aynı olamaz!' +AppInstall: 'Düğüm {{ .targetNode }} üzerine {{ .name }} uygulamasını yükle' +AppInstallCheck: 'Uygulama kurulum ortamını kontrol et' # backup ErrBackupInUsed: "Bu yedekleme hesabı zamanlanmış görevlerde kullanılıyor ve silinemez" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml index 18834f5454d5..818b6e4eda7f 100644 --- a/core/i18n/lang/zh-Hant.yaml +++ b/core/i18n/lang/zh-Hant.yaml @@ -37,11 +37,11 @@ MasterNode: "主節點" #app CustomAppStoreFileValid: "應用商店包需要 .tar.gz 格式" ErrFileNotFound: "{{ .name }} 檔案不存在" -AppBackup: '應用備份', -AppBackupPush: '傳輸應用備份文件 {{.file}} 到節點 {{ .name }}', -ErrSourceTargetSame: '源節點和目標節點不能相同!', -AppInstall: '在 {{ .targetNode }} 節點安裝應用 {{ .name }}', -AppInstallCheck: '檢查應用安裝環境', +AppBackup: '應用備份' +AppBackupPush: '傳輸應用備份文件 {{.file}} 到節點 {{ .name }}' +ErrSourceTargetSame: '源節點和目標節點不能相同!' +AppInstall: '在 {{ .targetNode }} 節點安裝應用 {{ .name }}' +AppInstallCheck: '檢查應用安裝環境' #backup ErrBackupInUsed: "該備份帳號已在排程任務中使用,無法刪除" From cc350fcb61d2ea3c62530d849c4634aeab907da6 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 12:59:34 +0800 Subject: [PATCH 6/9] fix: Update Malay language file extension from .yml to .yaml and add new translations in i18n module --- core/i18n/i18n.go | 2 +- core/i18n/lang/{ms.yml => ms.yaml} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename core/i18n/lang/{ms.yml => ms.yaml} (100%) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 911e80e94af4..903dbd11884a 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -26,7 +26,7 @@ var langFiles = map[string]string{ "pt-BR": "lang/pt-BR.yaml", "ja": "lang/ja.yaml", "ru": "lang/ru.yaml", - "ms": "lang/ms.yml", + "ms": "lang/ms.yaml", "ko": "lang/ko.yaml", "tr": "lang/tr.yaml", "es-ES": "lang/es-ES.yaml", diff --git a/core/i18n/lang/ms.yml b/core/i18n/lang/ms.yaml similarity index 100% rename from core/i18n/lang/ms.yml rename to core/i18n/lang/ms.yaml From 29f94438c6e0480f13bd285ad423a841876ef500 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:00:42 +0800 Subject: [PATCH 7/9] fix: Improve error messages for language file loading in i18n module --- core/i18n/i18n.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 903dbd11884a..a9df839c2c2b 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -143,13 +143,13 @@ func Init() { isSuccess := true for _, file := range langFiles { if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { - fmt.Printf("failed to load language file %s: %v\n", file, err) + fmt.Printf("[i18n] load language file %s failed: %v\n", file, err) isSuccess = false } } if !isSuccess { - panic("failed to load language files, See log above for details") + panic("[i18n] failed to init language files, See log above for details") } dbLang := getLanguageFromDBInternal() From 906d6bbea39899b438f96bc5206c5cabe74436c0 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:15:33 +0800 Subject: [PATCH 8/9] fix: Ensure cached database language is set correctly during i18n initialization --- core/i18n/i18n.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index a9df839c2c2b..5c66b0b258d3 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -153,10 +153,11 @@ func Init() { } dbLang := getLanguageFromDBInternal() - SetCachedDBLanguage(dbLang) if dbLang == "" { dbLang = defaultLang } + SetCachedDBLanguage(dbLang) + global.I18n = i18n.NewLocalizer(bundle, dbLang) }) } From 81c095d3c74c74b8326c17684c9cab4527d2e953 Mon Sep 17 00:00:00 2001 From: HynoR <20227709+HynoR@users.noreply.github.com> Date: Fri, 21 Nov 2025 13:37:10 +0800 Subject: [PATCH 9/9] fix: Enhance language detection in i18n module by using Accept-Language header --- core/i18n/i18n.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go index 5c66b0b258d3..c8a84ed1b610 100644 --- a/core/i18n/i18n.go +++ b/core/i18n/i18n.go @@ -131,7 +131,11 @@ var bundle *i18n.Bundle func UseI18n() gin.HandlerFunc { return func(context *gin.Context) { - global.I18n = i18n.NewLocalizer(bundle, GetLanguage()) + lang := context.GetHeader("Accept-Language") + if lang == "" { + lang = GetLanguage() + } + global.I18n = i18n.NewLocalizer(bundle, lang) } }