mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-01-09 00:24:55 +08:00
fix: improve logic and fix bug in i18n module (#11019)
* feat: Implement language caching and improve language handling in i18n module * refactor: Optimize i18n initialization with sync.Once for thread safety * fix: Replace logging with fmt.Println for language file loading errors in i18n module * fix: Correct format string in error logging for language file loading in i18n module * fix: Update language files and improve error handling in i18n module * fix: Update Malay language file extension from .yml to .yaml and add new translations in i18n module * fix: Improve error messages for language file loading in i18n module * fix: Ensure cached database language is set correctly during i18n initialization * fix: Enhance language detection in i18n module by using Accept-Language header
This commit is contained in:
parent
99c2eb04c9
commit
ef6d8bb17b
11 changed files with 112 additions and 65 deletions
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,12 @@ package i18n
|
|||
|
||||
import (
|
||||
"embed"
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/app/repo"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/core/global"
|
||||
|
||||
|
|
@ -13,6 +17,21 @@ 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",
|
||||
"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 {
|
||||
|
|
@ -114,41 +133,49 @@ func UseI18n() gin.HandlerFunc {
|
|||
return func(context *gin.Context) {
|
||||
lang := context.GetHeader("Accept-Language")
|
||||
if lang == "" {
|
||||
lang = GetLanguageFromDB()
|
||||
lang = GetLanguage()
|
||||
}
|
||||
global.I18n = i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
initOnce.Do(func() {
|
||||
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("[i18n] load language file %s failed: %v\n", file, err)
|
||||
isSuccess = false
|
||||
}
|
||||
}
|
||||
|
||||
if !isSuccess {
|
||||
panic("[i18n] failed to init language files, See log above for details")
|
||||
}
|
||||
|
||||
dbLang := getLanguageFromDBInternal()
|
||||
if dbLang == "" {
|
||||
dbLang = defaultLang
|
||||
}
|
||||
SetCachedDBLanguage(dbLang)
|
||||
|
||||
global.I18n = i18n.NewLocalizer(bundle, dbLang)
|
||||
})
|
||||
}
|
||||
|
||||
func UseI18nForCmd(lang string) {
|
||||
if lang == "" {
|
||||
lang = "en"
|
||||
}
|
||||
|
||||
if bundle == nil {
|
||||
Init()
|
||||
}
|
||||
if lang == "" {
|
||||
lang = defaultLang
|
||||
}
|
||||
global.I18nForCmd = i18n.NewLocalizer(bundle, lang)
|
||||
}
|
||||
|
||||
func GetMsgByKeyForCmd(key string) string {
|
||||
if global.I18nForCmd == nil {
|
||||
UseI18nForCmd("")
|
||||
|
|
@ -158,6 +185,7 @@ func GetMsgByKeyForCmd(key string) string {
|
|||
})
|
||||
return content
|
||||
}
|
||||
|
||||
func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string {
|
||||
if global.I18nForCmd == nil {
|
||||
UseI18nForCmd("")
|
||||
|
|
@ -181,13 +209,30 @@ 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
|
||||
var initOnce sync.Once
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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: "このバックアップアカウントはスケジュールタスクで使用されており、削除できません"
|
||||
|
|
|
|||
|
|
@ -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: "이 백업 계정은 예약된 작업에 사용 중이며 삭제할 수 없습니다"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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: "Эта учетная запись резервного копирования используется в запланированных задачах и не может быть удалена"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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: "該備份帳號已在排程任務中使用,無法刪除"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue