docs/TRANSLATION.md
Thank you for helping translate 1Panel! A complete locale touches the frontend, the Core service, the Agent service, application-store metadata, and the project README files. This guide lists the repository changes and validation required before opening a pull request.
Reference pull requests
- WIP: Dev v2 spanish (#10352) is a full-locale example for Spanish (
es-ES).- docs: add Persian README (#13132) is a localized-README example.
1Panel has several independent translation surfaces:
| Surface | Technology | Repository format |
|---|---|---|
| Frontend | Vue 3, vue-i18n, Element Plus | TypeScript (.ts) |
| Core service | Go i18n | YAML (.yaml) |
| Agent service | Go i18n | YAML (.yaml) |
| Application store | API models and frontend types | Go and TypeScript |
| Project introduction | GitHub README files | Markdown (.md) |
Adding only a frontend module does not provide complete locale support. Follow all applicable steps below and state explicitly in the pull request if a surface is intentionally out of scope.
Choose the canonical runtime locale first. Use a BCP 47 language tag and preserve its casing wherever the runtime locale is used. File names and third-party locale packs do not always use the same casing.
| Language | Runtime locale | Frontend module | Element Plus pack | Backend YAML | App-store key | README |
|---|---|---|---|---|---|---|
| Simplified Chinese | zh | zh.ts | zh-cn | zh.yaml | zh | README.zh-Hans.md |
| Traditional Chinese | zh-Hant | zh-Hant.ts | zh-tw | zh-Hant.yaml | zh-hant | README.zh-Hant.md |
| English | en | en.ts | en | en.yaml | en | README.md |
| Brazilian Portuguese | pt-BR | pt-br.ts | pt-br | pt-BR.yaml | pt-br | README.pt-br.md |
| Spanish (Spain) | es-ES | es-es.ts | es | es-ES.yaml | es-es | README.es-es.md |
| Lao | lo | lo.ts | lo | lo.yaml | lo | README.lo.md |
Use lowercase for simple runtime codes (ja, ko, lo) and standard BCP 47
casing for variants (pt-BR, es-ES, zh-Hant). Do not infer a file name from
the runtime locale: add an explicit mapping when the casing or code differs.
Before editing, search for hard-coded locale collections so a newly introduced selector, whitelist, or compatibility map is not missed:
rg "zh-Hant|pt-BR|es-ES" frontend core agent
Copy the English module and translate its values:
frontend/src/lang/modules/en.ts -> frontend/src/lang/modules/<module-name>.ts
Keep the following unchanged unless the English source changed structurally:
{0}, ${name}, and {{ .detail }};getFuLocaleMessage merge at the end of the file.Pass the canonical runtime locale to getFuLocaleMessage, even when the module
file name uses different casing:
import { getFuLocaleMessage } from '@/lang/fu';
const message = {
commons: {
// Translated values.
},
};
export default {
...getFuLocaleMessage('<runtime-locale>'),
...message,
};
The English module evolves continuously. Copy it from the same branch as your change instead of relying on a fixed key count or line count. Machine translation can be used for a first pass, but every string must be reviewed in its UI context.
Add the canonical runtime locale and its module file to LOCALE_LOADERS in
frontend/src/lang/index.ts:
const LOCALE_LOADERS: Record<string, LocaleLoader> = {
// Existing entries.
'<runtime-locale>': () => import('./modules/<module-name>'),
};
Then add the locale to fuLocales in frontend/src/lang/fu.ts. These messages
belong to the shared FU table and steps components and are not supplied by the
main module:
const fuLocales: Record<string, FuLocaleMessage> = {
// Existing entries.
'<runtime-locale>': {
fu: {
table: {
more: '...',
custom_table_rows: '...',
},
steps: {
cancel: '...',
prev: '...',
next: '...',
finish: '...',
},
},
},
};
Element Plus provides its own translations for date pickers, pagination,
popovers, and other components. Import the matching pack in
frontend/src/App.vue:
import localePack from 'element-plus/es/locale/lang/<element-plus-code>';
Map the canonical runtime locale in the i18nLocale computed property:
const i18nLocale = computed(() => {
// Existing mappings.
if (globalStore.language === '<runtime-locale>') return localePack;
return zhCn;
});
Confirm the pack exists in
frontend/node_modules/element-plus/es/locale/lang/. If Element Plus does not
provide the language, document the chosen fallback in the pull request rather
than importing a nonexistent module.
frontend/src/views/login/components/login-form.vue currently renders two
language menus for different login layouts. Add the new locale to both menu
blocks, then add its native display name to languageLabelMap:
const languageLabelMap: Record<string, string> = {
// Existing entries.
'<runtime-locale>': '<Native language name>',
};
Verify both login layouts manually. Updating only languageLabelMap changes the
selected label but does not add a selectable menu item.
Add the locale to languageOptions in
frontend/src/views/setting/panel/index.vue:
const languageOptions = ref([
// Existing entries.
{ value: '<runtime-locale>', label: '<Native language name>' },
]);
Add the locale to supportedLocales in frontend/src/views/share/index.vue.
This list controls browser-language recognition and the Accept-Language
header used by public share requests.
const supportedLocales = [
// Existing entries.
'<runtime-locale>',
];
Review locale comparisons that select language-specific API fields. For
example, operation records currently provide Chinese and English detail fields
in frontend/src/views/log/operation/index.vue. Chinese runtime locales should
use detailZH; every other locale should retain a visible detailEN fallback.
Prefer one shared fallback branch instead of adding every non-Chinese locale to
another hard-coded list.
The backend validates the language sent by the login request. Add the canonical
runtime locale to the Language field's oneof validation tag in
core/app/dto/auth.go:
Language string `json:"language" validate:"oneof=zh en ... <runtime-locale>"`
Without this change, the new language can appear on the login page but the login request will be rejected as an invalid parameter.
Run gofmt on the Go file after editing it.
Copy the current English catalog and translate only its values:
core/i18n/lang/en.yaml -> core/i18n/lang/<runtime-locale>.yaml
Preserve every YAML key and placeholder:
ErrInvalidParams: "Translated text: {{ .detail }}"
ErrRecordExist: "Translated text"
Register the catalog in core/i18n/i18n.go:
var langFiles = map[string]string{
// Existing entries.
"<runtime-locale>": "lang/<runtime-locale>.yaml",
}
Core and Agent are independent Go modules and have independent catalogs. Copy and translate the Agent catalog separately:
agent/i18n/lang/en.yaml -> agent/i18n/lang/<runtime-locale>.yaml
Register it in agent/i18n/i18n.go:
var langFiles = map[string]string{
// Existing entries.
"<runtime-locale>": "lang/<runtime-locale>.yaml",
}
Do not copy the Core YAML file into Agent: their key sets are different.
Application-store names and descriptions use their own locale fields. Add the new field to both repository-side schemas.
In agent/app/dto/app.go, extend Locale:
type Locale struct {
// Existing fields.
NewLanguage string `json:"<app-store-key>"`
}
In frontend/src/api/interface/app.ts, extend Locale with the same JSON key:
interface Locale {
// Existing fields.
'<app-store-key>': string;
}
Add the app-store key to the description example in
core/cmd/server/app/app_config.yml as well. This embedded template is written
when the server command scaffolds a new local application configuration.
Review frontend/src/utils/app-store.ts as well. Simple codes that lowercase to
the same value normally need no special handling; regional or script variants
may need an explicit runtime-locale-to-app-store-key mapping.
These schema changes make the repository capable of reading the locale. The translated application names and descriptions are maintained in the external application-store data source and must be coordinated separately. Until that data exists, the application store falls back to English.
Copy the root README to the documentation directory:
README.md -> docs/README.<readme-code>.md
Translate prose while preserving Markdown structure, HTML, images, links,
commands, badges, and code samples. Check every relative link from its new
location under docs/; a link that works from the repository root may need a
different relative path in the localized file.
Then keep the language navigation synchronized:
README.md.docs/README.*.md language badge row.Use the native language name for the badge label. The README locale suffix may
follow an existing documentation convention (zh-Hans, pt-br, es-es) and
does not have to match the runtime locale casing.
Format every changed Go source file:
gofmt -w core/app/dto/auth.go core/i18n/i18n.go
gofmt -w agent/app/dto/app.go agent/i18n/i18n.go
The login-language enum and application-store locale model are represented in
generated Swagger files. Install the repository-compatible swag command, then
run the generator test from the Core module:
cd core
go test ./cmd/server/docs -run TestGenerateSwaggerDoc
This updates generated files under core/cmd/server/docs/. Do not edit the
generated Swagger output by hand. If the generator is unavailable, call that
out explicitly in the pull request instead of committing stale manual changes.
Run checks from the repository root unless a command changes directory:
# Frontend type checking.
cd frontend
npm run type-check
# Lint only the frontend files changed by the locale contribution.
./node_modules/.bin/eslint \
src/lang/modules/<module-name>.ts \
src/lang/index.ts \
src/lang/fu.ts \
src/App.vue \
src/views/login/components/login-form.vue \
src/views/setting/panel/index.vue \
src/views/share/index.vue \
src/views/log/operation/index.vue \
src/api/interface/app.ts \
src/utils/app-store.ts
# Compile and test the directly affected Go packages.
cd ../core
go test ./i18n ./app/dto
cd ../agent
go test ./i18n ./app/dto
# Check whitespace errors in all changed files.
cd ..
git diff --check
Also compare each translated catalog with its English source:
Accept-Language value.For right-to-left languages, verify the login page, navigation, forms, tables, dialogs, and mixed-direction values such as IP addresses. RTL layout support may require a separate frontend change beyond translating strings.
Some runtime assets are not maintained in this repository:
language/lang.tar.gz are
published through the 1Panel resource service.Repository locale support does not automatically update either resource. If the new language needs them, coordinate publication with the relevant maintainer and record the status in the pull request. Do not add generated or downloaded archives to this repository unless a maintainer requests it.
frontend/src/lang/modules/<module-name>.ts is complete and reviewed.frontend/src/lang/index.ts contains the locale loader.frontend/src/lang/fu.ts contains the FU messages.frontend/src/App.vue maps an existing Element Plus locale pack or documents a fallback.languageLabelMap are updated in frontend/src/views/login/components/login-form.vue.languageOptions is updated in frontend/src/views/setting/panel/index.vue.supportedLocales is updated in frontend/src/views/share/index.vue.core/app/dto/auth.go.core/i18n/lang/<runtime-locale>.yaml exists and is registered in core/i18n/i18n.go.agent/i18n/lang/<runtime-locale>.yaml exists and is registered in agent/i18n/i18n.go.agent/app/dto/app.go, frontend/src/api/interface/app.ts, and core/cmd/server/app/app_config.yml.frontend/src/utils/app-store.ts when needed.docs/README.<readme-code>.md exists and every README language badge is synchronized.| Language | Runtime locale |
|---|---|
| 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 |
| Persian | fa |
| Lao | lo |
feat(i18n): add <language name> (<runtime-locale>) locale support
Example: feat(i18n): add German (de-DE) locale support