add-locale
Add a new UI language/locale to the claude-code-gui webview i18n system, or extend/verify an existing one. Walks a contributor (or agent) through creating the per-namespace translation JSON files, registering the locale, and verifying parity — consistently with how the shipped locales are structured
Install
mkdir -p .claude/skills/add-locale && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/16689" && unzip -o skill.zip -d .claude/skills/add-locale && rm skill.zipInstalls to .claude/skills/add-locale
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
Add a new UI language/locale to the claude-code-gui webview i18n system, or extend/verify an existing one. Walks a contributor (or agent) through creating the per-namespace translation JSON files, registering the locale, and verifying parity — consistently with how the shipped locales are structured. Use when someone wants to translate the UI into a new language, add a locale, or contribute translations. Trigger on: 언어 추가, 로케일 추가, 새 언어, 번역 추가, 번역 기여, add language, add locale, new language, translate the UI, contribute a translation, i18n locale.About this skill
Add a UI locale to the webview
The webview UI is internationalized with react-i18next. Every language is a
folder of JSON catalogs under webview/src/i18n/locales/<locale>/, one file per
namespace (page/area). English (en) is the source of truth and the
fallback for any missing key.
Adding a language = (1) create that folder of translated JSON, (2) register the
language in two small code spots. config.ts auto-loads locale files via a Vite
glob, so you never edit config.ts.
0. Pick the identifiers
- locale — a BCP-47 code and the folder name, e.g.
it(Italian),pt-BR(Brazilian Portuguese),zh-TW(Traditional Chinese). - setting value — the value stored in settings, a lowercase English name,
e.g.
italian. For script/region variants use a-suffix, e.g.chinese-traditional. - endonym — the language's own name for the dropdown label, e.g.
Italiano,繁體中文. (Labels are endonym-only — no romanized prefix.)
1. Create the translation files
List the namespaces (these are the English source files):
ls webview/src/i18n/locales/en/
# chat chatTools commandPalette common notifications permissions
# projectSelector sessionPanel settings switchAccount
Create webview/src/i18n/locales/<locale>/<namespace>.json for every file in
en/. Translate the string values; keep the key structure identical to
the English file.
Rules — same as the shipped locales:
- Mirror keys exactly. Same nesting, same key names as
en/. Only values change. - Preserve interpolation placeholders verbatim:
{{value}},{{count}},{{n}},{{seconds}},{{error}},{{label}},{{appName}}, … Keep whatever the English value uses, in place. - Plurals: if an English key uses i18next plural suffixes (
foo_one/foo_other), produce the plural set your language needs. Languages with no plural distinction (ko/ja/zh) keep the same suffix keys with identical text. Languages with more categories (e.g. Russian:_one/_few/_many) may add those suffix keys — that is correct even though it means more keys than English for those entries. - Do NOT translate: the brand "Claude Code", ALL-CAPS env vars
(
CLAUDE_CONFIG_DIR), file paths, URLs, acronyms (CLI, MCP, IDE, OS, URL), code identifiers, model names/IDs, shell commands, keyboard tokens (Cmd+K). - Natural, concise UI phrasing. Match the tone of the existing locales.
- Every file must be valid JSON.
2. Register the locale (two spots)
a. webview/src/i18n/languageMap.ts
- Add the locale to
SUPPORTED_LOCALES. - Add
'<setting value>': '<locale>'toLANGUAGE_TO_LOCALE.
b. webview/src/pages/SettingsPage/General/index.tsx
- Add
{ value: '<setting value>', label: '<endonym>' }toLANGUAGE_OPTIONS.
config.ts picks up the new files automatically (glob) — do not edit it.
3. Verify
- Key parity — every locale namespace must have the same leaf keys as
en(plural suffix variants aside). Run:
node -e '
const fs=require("fs"),p=require("path");
const base="webview/src/i18n/locales", L=process.argv[1];
const NS=fs.readdirSync(base+"/en").map(f=>f.replace(".json",""));
const leaves=(o,pre="")=>Object.entries(o).flatMap(([k,v])=>v&&typeof v==="object"&&!Array.isArray(v)?leaves(v,pre+k+"."):[pre+k]);
const norm=k=>k.replace(/_(zero|one|two|few|many|other)$/,"");
let miss=0;
for(const ns of NS){
const en=new Set(leaves(JSON.parse(fs.readFileSync(`${base}/en/${ns}.json`))).map(norm));
let tr; try{tr=new Set(leaves(JSON.parse(fs.readFileSync(`${base}/${L}/${ns}.json`))).map(norm));}catch(e){console.log(`MISSING FILE ${L}/${ns}.json`);miss++;continue;}
for(const k of en) if(!tr.has(k)){console.log(`${L}/${ns}: missing ${k}`);miss++;}
}
console.log(miss?`❌ ${miss} problems`:`✅ ${L} matches en`);
' <locale>
- Type check —
bash ./scripts/build.sh wv-lint(must be clean). - Tests —
bash ./scripts/build.sh wv-test. - Eyeball it — run the standalone dev server, open Settings → General → Interface Language, pick the new language, and click through the app.
Notes
- The Interface Language (this) is separate from the Claude response language (a free-text field). Adding a UI locale only affects the interface.
- If you only ship some namespaces, untranslated keys fall back to English — the app still works. But aim for full parity per §3.