From i18n-tools
Use when preparing a web app's UI for translation/localization and you have its source code — it produces a code-aware localization plan (a string inventory traced to its usage in the code, a terminology/glossary draft, and the structural blockers that must be fixed before translation), NOT the finished translations. Trigger whenever the user wants to translate, localize, internationalize, or "i18n"/"l10n" an app's interface; add or ship a new language or locale; add RTL (Arabic/Hebrew/Farsi) support; extract or audit user-facing strings; or prepare UI text for translators or a translation vendor. Also trigger when a translation looks broken in a specific language — labels overflow, placeholders go missing, text is mis-cased or won't render — since that is usually a code/CSS/font problem the locale file cannot fix. Fire even when the user just says "translate the app" or "add Spanish" without mentioning the code. The defining move is reading the code (components, t() call sites, routes, CSS, fonts) as the primary context, not the strings in isolation. (Less of a fit for translating prose/docs, fixing one catalog string, or merely installing an i18n library.)
How this skill is triggered — by the user, by Claude, or both
Slash command
/i18n-tools:ui-localizationThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Prepare a web app's UI for translation by using its **source code** as the primary context. The
Prepare a web app's UI for translation by using its source code as the primary context. The deliverable is a localization plan: a code-aware string inventory, a terminology/glossary draft, and the list of structural problems that must be fixed before translation. It is not the finished translations — producing this plan is what makes the eventual translation safe, consistent, and free of nasty surprises.
Read the code, not just the strings. Two reasons:
{{var}} replacer with no plural support.
None of these show up in a string catalog. You only catch them by reading the code — and they are the
difference between a localization that ships and one that embarrasses.A generic translator stops at #1 and misses #2 entirely. The structural sweep (Phase 4) is the heart of this skill; treat the rest as the scaffolding that makes it actionable.
A request to "add a Spanish locale" to a 30-string settings page does not need a 20-document audit. A full multi-locale program for a customer-facing product does. Match the output to the ask:
references/artifact-templates.md,
split into per-area files.When unsure, start lean and offer to go deeper. Don't bury a small job in ceremony.
Track these as tasks and work them in order. Phases 1–3 are mechanical discovery; 4 is the differentiator; 5–6 turn findings into decisions and a deliverable.
scripts/i18n-sweep.sh, then open and verify each lead (the crown jewel).digraph ui_localization {
"Confirm scope & locales" [shape=box];
"Map the codebase" [shape=box];
"Inventory strings w/ usage" [shape=box];
"Protect technical elements" [shape=box];
"Sweep for structural blockers" [shape=box];
"Draft glossary + questions" [shape=box];
"Assemble plan; hand off to translation" [shape=doublecircle];
"Confirm scope & locales" -> "Map the codebase";
"Map the codebase" -> "Inventory strings w/ usage";
"Inventory strings w/ usage" -> "Protect technical elements";
"Protect technical elements" -> "Sweep for structural blockers";
"Sweep for structural blockers" -> "Draft glossary + questions";
"Draft glossary + questions" -> "Assemble plan; hand off to translation";
}
First confirm the goal with the user: which target locales, what kind of product, the desired tone, and whether they'll do the translation later or want this plan to feed external translators. They often can't judge target-language fluency — so frame everything you'll later ask them around product meaning and release decisions, never grammar.
Then map the codebase by reading it (don't infer from folder names):
react-i18next, next-intl, vue-i18n, lingui, formatjs, or none)./locales, /i18n, /messages) and strings
hardcoded inline in components. Apps with no i18n setup keep everything inline — then part of your job is
pinpointing the extraction sites.t(), i18n.t(), <Trans>, $t, useTranslation, etc.Accept-Language? none yet?).Produce a Codebase Localization Map and a one-paragraph Scope Summary (see
references/artifact-templates.md). Confirm with the user: target locales, tone/formality, and which
product names stay untranslated, before going deep.
For each user-facing string, record where it's defined, where it's used, the component, the UI element type (button / menu / label / placeholder / tooltip / toast / dialog / validation / empty state), the action or event it sits next to, nearby strings, any placeholders, and how often it's reused.
The point is disambiguation: when a string's meaning is unclear, the code resolves it. And watch for the
reuse trap — one source string used in two places with different meanings (e.g. common.open as both
"open a file" and a status of "open"). One translation rarely fits both; recommend splitting the key. Flag
these in a Reuse Risk list.
Detect everything that must pass through translation untouched: interpolation ({name}, {{count}}),
ICU message syntax, printf (%s, %1$s), HTML/JSX tags, markdown, URLs, emails, keyboard shortcuts,
escape sequences, and embedded \n that drives layout. Use the code to learn what each placeholder means
— t("invite.sent", { email: user.email }) tells you {email} is an email address, and
t("upload.done", { count: files.length }) tells you {count} needs plural rules. Capture a
Placeholder Meaning Map so a translator (or you, later) treats each variable correctly.
This is where the skill earns its keep. Sweep the code for the traps that break or embarrass translations
even when every string is translated perfectly — styling/logic derived from display text, font/script
coverage gaps, browser-vs-app locale drift, naive interpolation, missing pluralization, RTL hazards,
text-transform case bugs, sentence-fragment markup, hardcoded strings, number/date/money formatting, and
broken locale loading/registration (a whole locale silently failing to load, or a date library never
registered for it — often the highest-severity issue in an otherwise-mature app).
Start with the bundled sweep: run scripts/i18n-sweep.sh <app-source-dir> (point it at the app root so
it sees index.html too). It greps the whole catalog of traps at once and prints candidate sites grouped by
category — so you never re-derive the search or forget a category. The output is leads, not findings: a
grep hit isn't a blocker until you open the file and confirm the code actually does the risky thing (and
hand-built formatting like a literal $ in JSX won't always match — read around each hit). Note too that
some blockers are absences the grep can't show: no dir plumbing, a font with no glyphs for the target
script, a missing locale source. Use the sweep to anchor coverage, then reason about what isn't there.
Read references/structural-blockers.md alongside it — it's the catalog the sweep is built from: why
each category breaks localization, the code signals, and how to flag it. These findings are usually the most
valuable part of your deliverable, because they're invisible to anyone working from the string catalog alone.
Be precise, and stingy with numbers. Counts are the easiest thing to get wrong and the easiest for a
reviewer to disprove — and one bad number taints the whole report. So: (a) default to qualitative scope
("widespread", "a handful", "one") and only cite a number when it genuinely strengthens a finding; (b) when
you do, run the exact command at report-time and paste its output, naming precisely what it counts —
e.g. rg -l '$i18n.t(' src | wc -l → "307 files use t()" — never a remembered or estimated figure;
(c) never cite the sweep's category totals as facts — those are lead counts (candidate lines for
triage), not blocker counts, and they conflate scopes; (d) don't attach a number to a noun you didn't
count — "584 components" when you actually counted files is the classic miss. Trace a behavior through its
whole code path before claiming how it fails. On a mature codebase, a short list of verified,
correctly-scoped findings beats a padded one — credit what's already handled, so the reader trusts what you flag.
Extract candidate terms from both the strings and the code — route names, component names, entity/model names, feature folders, repeated UI labels. Group them (core product concepts, navigation, actions, account/billing, statuses, errors, non-translatable terms) and mark each translate-or-keep. Then write the terminology questions for the user — always about product meaning, never language fluency. Good questions: "Is Workspace a generic concept or a branded feature name?" "Does Remove mean remove-from-list while Delete means permanent?" Note where the same concept is named two ways in the copy (e.g. "odds" vs "multiplier") so translators don't diverge.
Pull it together into a readiness report (structure in references/artifact-templates.md): scope, codebase
map, string inventory, placeholder/technical map, glossary draft, prioritized structural blockers, open
product questions, and a translation plan — recommended i18n approach (e.g. adopt an ICU-capable library
if interpolation is naive), what to fix before vs. during extraction, and per-locale risks (length
expansion, plural complexity, formality, script/RTL). For multi-locale work, resolve the code-derived
product decisions once and note they apply to every locale; per-locale, capture only what differs.
Single out the critical strings. Some text carries consequences far beyond layout: destructive actions (delete / remove / reset / cancel), payments and billing, legal and compliance copy, security and privacy warnings, account changes, and permission prompts. A mistranslation here isn't a cosmetic bug — it can make a user delete the wrong thing or misunderstand what they're agreeing to, so it's the most expensive error in the whole job. Pull these into a short critical-strings register in the deliverable, flag them for careful, unambiguous wording, and recommend a back-translation check for them in the translation step — that lets the user sign off on meaning even in a language they don't read. The cost is reading the language they don't read. This matters because it's exactly the judgement a string catalog erases: "Delete workspace" and "Close" look like ordinary short labels until you notice one is irreversible.
End by stating the boundary plainly: this is the plan; executing the translation is the next step, and this document is what makes that step safe. Don't silently start translating — the user scoped this to the analysis.
t() → the inventory is key-based; cross-check that every key used in code
exists in the source catalog and flag unused/orphaned keys.docs/i18n-strings.md
in this repo is a worked example of exactly this case.)scripts/i18n-sweep.sh <app-source-dir> — runs the whole structural-blocker grep sweep at once and prints
candidate sites grouped by category. Run it at the start of Phase 4; treat output as leads to verify.references/structural-blockers.md — the catalog the sweep is built from: how to detect each trap, why it
breaks, how to flag it. Read this for Phase 4 (and keep it open during Phase 2–3 — some traps surface
while you inventory).references/artifact-templates.md — formats for every deliverable (codebase map, inventory columns,
reuse-risk and placeholder maps, glossary, terminology questions, critical-strings register, the readiness
report, recommended file layout). Pull the ones the job needs; ignore the rest.Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub denperov/denperov-plugins --plugin i18n-tools