Thank you for your interest in helping translate 1Panel into a new language! This guide walks through every file you need to create or modify to add full language support across the frontend and backend.
Reference PR: WIP: Dev v2 spanish (#10352) β a merged, real-world example of adding a brand-new locale (Spanish
es-ES).
1Panel's i18n system spans two layers:
| Layer | Technology | Translation format |
|---|---|---|
| Frontend (Vue 3) | vue-i18n + Element Plus |
TypeScript (.ts) |
| Backend (Go) | go-i18n / nicksnyder/go-i18n |
YAML (.yaml) |
Adding a new language requires changes in both layers, as well as registering the locale in a few selector components.
Copy the reference English file and translate all values.
frontend/src/lang/modules/en.ts β frontend/src/lang/modules/<locale>.ts
Replace every English string with its translation. Keep all keys, nested objects, and the getFuLocaleMessage call at the bottom unchanged:
// frontend/src/lang/modules/<locale>.ts
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
// ... translated strings
},
// ...
};
export default {
...getFuLocaleMessage('<locale>'),
...message,
};Tip: The file is ~4 400 lines. Using a translation tool for a first pass is fine, but please review the output for accuracy and context.
Open frontend/src/lang/index.ts and add your locale to LOCALE_LOADERS:
// frontend/src/lang/index.ts
const LOCALE_LOADERS: Record<string, LocaleLoader> = {
// existing entries β¦
'<locale>': () => import('./modules/<locale>'),
};frontend/src/lang/fu.ts contains translations for custom FU table/steps components. Add a new entry for your locale:
// frontend/src/lang/fu.ts
const fuLocales: Record<string, FuLocaleMessage> = {
// existing entries β¦
'<locale>': {
fu: {
table: {
more: '...',
custom_table_rows: '...',
},
steps: {
cancel: '...',
prev: '...',
next: '...',
finish: '...',
},
},
},
};Element Plus ships its own locale strings (used in date pickers, pagination, etc.). Import the matching locale pack and wire it into frontend/src/App.vue:
// frontend/src/App.vue β import section
import <varName> from 'element-plus/es/locale/lang/<ep-locale-code>';Then add a branch in the i18nLocale computed property:
const i18nLocale = computed(() => {
// existing branches β¦
if (globalStore.language === '<locale>') return <varName>;
return zhCn; // fallback unchanged
});You can find all available Element Plus locale codes in node_modules/element-plus/es/locale/lang/.
Open frontend/src/views/login/components/login-form.vue and add your locale label to languageLabelMap:
const languageLabelMap: Record<string, string> = {
// existing entries β¦
'<locale>': '<Native language name>',
};Open frontend/src/views/setting/panel/index.vue and add an option to languageOptions:
const languageOptions = ref([
// existing entries β¦
{ value: '<locale>', label: '<Native language name>' },
]);The backend has two independent Go modules, each with its own YAML translation file. Copy the English reference and translate:
core/i18n/lang/en.yaml β core/i18n/lang/<locale>.yaml (~264 lines)
agent/i18n/lang/en.yaml β agent/i18n/lang/<locale>.yaml (~592 lines)
YAML format example:
ErrInvalidParams: "Invalid request parameters: {{ .detail }}"
ErrRecordExist: "Record already exists"
# β¦Add your locale key and file path in both i18n registries:
core/i18n/i18n.go
var langFiles = map[string]string{
// existing entries β¦
"<locale>": "lang/<locale>.yaml",
}agent/i18n/i18n.go
var langFiles = map[string]string{
// existing entries β¦
"<locale>": "lang/<locale>.yaml",
}Before opening a PR, verify the following:
-
frontend/src/lang/modules/<locale>.tscreated and all strings translated - Locale registered in
frontend/src/lang/index.ts(LOCALE_LOADERS) - Locale entry added to
frontend/src/lang/fu.ts - Element Plus locale imported and mapped in
frontend/src/App.vue - Locale label added to
languageLabelMapinfrontend/src/views/login/components/login-form.vue - Locale option added to
languageOptionsinfrontend/src/views/setting/panel/index.vue -
core/i18n/lang/<locale>.yamlcreated -
agent/i18n/lang/<locale>.yamlcreated - Locale registered in
core/i18n/i18n.go(langFiles) - Locale registered in
agent/i18n/i18n.go(langFiles) - Manually verified: new locale appears in the login language dropdown
- Manually verified: new locale appears in Settings β Panel β Language
- Manually verified: UI renders correctly after switching to the new locale
Use BCP 47 locale codes. Examples used in this project:
| Language | Locale code |
|---|---|
| Simplified Chinese | zh |
| Traditional Chinese | zh-Hant |
| English | en |
| Japanese | ja |
| Korean | ko |
| Russian | ru |
| Malay | ms |
| Turkish | tr |
| Brazilian Portuguese | pt-BR |
| Spanish (Spain) | es-ES |
Use lowercase for simple codes (ja, ko) and the standard BCP 47 casing for regional variants (pt-BR, es-ES, zh-Hant).
Frontend TypeScript file names use all-lowercase with hyphens preserved exactly as they appear in the existing frontend/src/lang/modules/ directory (e.g. pt-br.ts, es-es.ts, zh-hant.ts).
Backend YAML file names mirror the locale code exactly, preserving the original casing (e.g. pt-BR.yaml, es-ES.yaml, zh-Hant.yaml).
feat(i18n): add <language name> (<locale>) locale support
Example: feat(i18n): add German (de-DE) locale support