Localize: App Internationalization Workflow
Target versions (May 2026): react-i18next 17.x, vue-i18n 11.x, next-intl 4.x, i18next 26.x
Systematic approach to internationalizing applications. Covers two scenarios: adding multilingual support from scratch and auditing existing i18n for gaps. Built from real production pain - the hardest part of i18n is not translation but finding every string that needs it, and making sure translations read naturally in context rather than as mechanical word-by-word output.
When to use
- Adding multilingual support to an existing single-language app
- Auditing a codebase for untranslated hardcoded strings
- Checking an already-internationalized app for completeness or quality gaps
- Setting up locale catalogs, providers, and translation infrastructure
- Generating machine translations for new or changed source strings
- Validating catalog completeness across locales
- Adding new languages to an already-internationalized app
- Reviewing translation quality (voice consistency, domain accuracy, placeholder integrity)
When NOT to use
- Translating standalone documents, README files, or prose - just ask the LLM directly
- Reviewing code quality or style issues - use code-review or anti-slop
- Building AI/LLM features that produce multilingual output at runtime - use ai-ml
- Setting up backend API endpoints for locale handling - use backend-api
- Writing tests for i18n behavior - use testing (though this skill includes validation)
AI Self-Check
AI tools consistently produce the same i18n mistakes. Before returning any generated i18n code, catalogs, or translations, verify against this list:
- Native orthography in catalogs: translated strings use proper Unicode characters for the target language (umlauts, accents, cedillas, CJK characters, etc.). ASCII-only rules from global or project config (CLAUDE.md, AGENTS.md, .cursorrules, etc.) apply to source code and prose, NOT to locale catalogs. Writing "hinzugefuegt" (ASCII ae/oe/ue substitution) instead of proper umlauts is a bug. Locale files are the one place where native script is mandatory.
- Every string category covered: checked all categories in the String Categories table below, not just visible text. Toast notifications, validation messages, aria-labels, placeholders, title attributes, alt text, loading states, conditional fragments, and error messages are the most commonly missed
- Source catalog is the type authority: types for message keys derive from the source locale (usually English), not from a union of all locales
- Placeholders preserved: every
{0},{name},{{var}},%s,%din source strings appears identically in translated strings - same count, same order, same syntax - Brand names protected: product names, service names, and proper nouns are preserved exactly in all locales
- No partial extraction: if auditing a file, every user-facing string in that file is extracted - not just the obvious ones. Check JSX text content, attribute values, template literals, and string arguments to UI functions
- Fallback chain exists: missing keys fall back to the source locale, then to the key itself - never to an empty string or a crash
- Validation script created: a script or test exists that compares all locale catalogs against the source for missing keys, extra keys, and empty values
- Keys use dot notation: keys follow a consistent
namespace.context.labelpattern. Keys describe what the text is for, not what it says - Voice consistency: translations within each language use the same register (formal or informal) throughout. German "du" vs "Sie", French "tu" vs "vous", Spanish "tu" vs "usted" - pick one per language and stick with it across the entire catalog
- Context-aware translation: translations read naturally in the app's domain, not as mechanical word-by-word output. UI labels, error messages, and toast notifications should sound like a native speaker wrote them for that specific app
- No library API hallucination: if using a library (i18next, next-intl, vue-i18n), verify import paths, hook names, and configuration options against current docs
- RTL/bidirectional text handled: layout direction set in HTML lang/dir attributes, no LTR-only CSS assumptions
- Catalog files parseable: JSON/YAML validates without syntax errors, no trailing commas or unquoted keys
- Locale detection complete: browser navigator.language, Accept-Language header, or user preference stored and respected
- Current source checked: dated versions, CLI flags, API names, and support windows are verified against primary docs before repeating them
- Hidden state identified: local config, credentials, caches, contexts, branches, cluster targets, or previous runs are made explicit before acting
- Verification is real: final checks exercise the actual runtime, parser, service, or integration point instead of only linting prose or happy paths
- Routing overlap checked: overlapping skills, trigger terms, and "When NOT to use" boundaries are checked before returning guidance
- Spec claims verified: claims about tool behavior, output contracts, or repo conventions are checked against current docs, scripts, or skill files
- Locale data checked: ICU message syntax, plural categories, and CLDR assumptions match the target locales
- Fallback behavior tested: missing keys, pseudo-locales, RTL, and long strings are exercised
Performance
- Load locale bundles per route or language instead of shipping every catalog to every user.
- Cache compiled ICU messages where the framework supports it.
- Detect hardcoded strings with static scans before manual review.
Best Practices
- Keep source strings stable and meaningful; do not use English copy as an implicit key if text changes often.
- Use translators' notes for placeholders, gender, tone, and domain-specific terms.
- Never concatenate translated fragments where grammar can change by language.
Workflow
Entry points (always read the AI Self-Check above first, regardless of entry point):
- Adding i18n from scratch? Start at Step 1.
- Already have i18n, checking completeness? Start at Step 1 (assess), then Step 3 (audit).
- Just need translations for new keys? Jump to Step 4.
- Just validating catalogs? Jump to Step 5.
Step 1: Assess current state
Before touching code, understand what exists:
- Check for existing i18n setup - look for i18n libraries in
package.json(or equivalent), locale/translation directories, i18n config files, and translation function usage (t(),$t(),useTranslations,FormattedMessage, etc.) - Identify the framework - React, Next.js, Vue, Nuxt, Svelte, SvelteKit, Angular, or vanilla JS/TS. This determines the provider pattern and available libraries.
- Locate catalog files - find where locale/translation files live. Common locations:
src/locales/,src/i18n/messages/,public/locales/, or inline<i18n>blocks. Note the format (JSON, YAML, TS objects) and identify the source locale. - Count the scope - estimate how many files contain user-facing strings. Adapt
the file extension to your framework. The JSX-text regex alone undercounts by a
lot (misses attributes, toasts, errors) - combine with attribute and toast patterns:
# React/JSX: text content + attribute values (placeholder, title, alt, aria-label) grep -rlE '>[A-Z][a-z]|(placeholder|title|alt|aria-label)="[A-Z]' \ --include='*.tsx' --include='*.jsx' src/ | wc -l # Toast/validation strings hide in logic (not JSX). Check separately. grep -rlE 'toast\.(success|error|info|warn)|setError\(' \ --include='*.ts' --include='*.tsx' src/ | wc -l # Vue: *.vue | Svelte: *.svelte | Angular: *