SuperAPI AI Directory

i18n-wrap

General & other

View repository

Updated 10-15 22:15

About this skill

i18n-wrap: $ARGUMENTS

DECISION RULE

If even ONE target language needs a different string, it MUST be wrapped. When uncertain: wrap it. Over-wrapping = minor annoyance. Under-wrapping = broken UX.

ANTI-PATTERNS (never do these)

  • NEVER split sentences across keys (t('greeting') + name -- word order varies)
  • NEVER wrap technical constants (API, PDF, USD, regex, CSS classes, env vars, console.log)
  • NEVER guess framework -- ask user if ambiguous
  • NEVER delete existing translations or overwrite them
  • NEVER use defaultValue in t() calls
  • NEVER transform t() output (.toLowerCase(), .toUpperCase(), .trim(), template casing) -- case rules differ by language (e.g. Turkish İ/i, German ß→SS); create a separate key instead
  • NEVER interpolate count outside t() ({n} {t('label')}) -- bypasses plural handling ("1 languages"); use single key with ICU plurals: t('label_count', {count: n})

Phase 1: DETECT (framework + i18n setup)

MUST detect before any changes:

Detect How
Framework package.json, imports, config files
Translation fn t(), $t(), _(), $localize, NSLocalizedString, getString
Locale files Location + format (JSON/YAML/PO/XLIFF/ARB/.strings/.xml)
Key convention dot.notation / nested / flat / prefixed
Source locale en, en-US, etc.
Target locales All configured locales
Plural format ICU MessageFormat / suffix keys / pipe / YAML / stringsdict

If ambiguous, ask user. Never guess.

Phase 2: AUDIT (clean existing issues first)

Use Agent tool (subagent_type: "research") to scan locale files in parallel -- one subagent per locale file checking for non-translatable keys.

Unwrap: console/debug messages, technical IDs, CSS classes, HTML IDs, config values, env vars in t() calls.

Report audit results before proceeding.

Phase 3: SCAN (find unwrapped strings)

Use Agent tool (subagent_type: "research") for parallel scanning -- delegate one subagent per file type pattern (JSX/TSX, Python, Ruby, Swift/Kotlin).

Each subagent should search its file type for:

  1. Text content between tags: >[^<{]*[A-Za-z]{2,}[^<{]*<
  2. Translatable attributes: (placeholder|title|alt|aria-label|aria-description)="[^"]*[A-Za-z]{2,}[^"]*"
  3. Message/label assignments: (message|label|text|title|description|placeholder|error|warning|success)\s*[:=]\s*["'][A-Za-z]

Glob exclusions (always): !**/node_modules/** !**/*.test.* !**/*.spec.* !**/__tests__/** !**/dist/** !**/build/** !**/.next/** !**/coverage/** !**/*.config.* !**/*.d.ts

Phase 4: CATEGORIZE (semantic key namespaces)

Prefix Content
common. Shared buttons, labels, generic messages
nav. Menu items, breadcrumbs, links
auth. Login, register, password forms
{feature}.form. / validation. Form labels, validation errors
errors. / {feature}.errors. User-facing error messages
{feature}.success. / notifications. Confirmations, toasts
{pageName}. Page headings, descriptions, content
meta. SEO titles, descriptions
Inline with parent aria-labels near their component keys

Same text + different context = different keys (e.g., profile.buttons.save vs settings.buttons.save).

Phase 5: WRAP (apply i18n functions)

For each string: import hook/function if needed, initialize translator, replace hardcoded string, handle interpolation (${name} -> t('key', {name})), convert ternary plurals to ICU/framework format, replace manual date/number formatting with Intl formatters. Never break JSX structure.

Phase 6: KEYS (update locale files)

  1. Add new keys to source locale, maintaining structure/formatting/ordering
  2. For target locales: add missing keys as "[NEEDS_TRANSLATION] English text"
  3. NEVER remove existing keys or overwrite existing translations
  4. Validate JSON/YAML is parseable after changes

Phase 7: VALIDATE

Use Agent tool (subagent_type: "research") to run validation checks in parallel:

  • Subagent 1: Build + lint (pnpm build && pnpm lint or framework equivalent)
  • Subagent 2: Key parity check across all locales (count, missing, extra, placeholder match, plural categories)
  • Subagent 3: Run test suite (update assertions for changed text, never delete tests)

QUALITY CHECKLISTS

FRAMEWORK CHECKLISTS

Framework Import/Init Plurals Interpolation Rich text/markup Key notes
next-intl useTranslations(ns) client, await getTranslations(ns) server ICU: {count, plural, one{#} other{#}} t('key', {name}) t.rich('key', {bold: c=><b>{c}</b>}) No defaultValue; NextIntlClientProvider needs locale prop; deep merge fallback in request.ts
react-i18next useTranslation(ns) hook, withTranslation HOC Suffix: key_one, key_other {{name}} in JSON <Trans i18nKey="key"> Configure fallback lang + language detector
vue-i18n $t() template, useI18n() script setup Pipe: no items | {n} item | {n} items $t('key', {name}) <i18n-t keypath="key"> Configure fallbackLocale; lazy load for large apps
Angular i18n attr, $localize in TS ICU in templates ICU expressions i18n attribute Extract with ng extract-i18n; XLIFF format; per-locale builds
Svelte $_('key') / $format('key') ICU MessageFormat $_('key', {values: {name}}) N/A init() with fallback; waitLocale() to prevent FOUC
Python _("str") / gettext("str") ngettext("sg","pl",n) _("Hi %(name)s") % {"name": n} {% trans %} / {% blocktrans %} PO->MO compile; pgettext for context; no f-strings in gettext
Ruby t('key') / I18n.t('key') count: + zero/one/other YAML t('key', name: val) ERB: <%= t('key') %> YAML in config/locales; lazy lookup .key in views
Flutter AppLocalizations.of(context)!.key ICU in ARB ICU MessageFormat N/A ARB in lib/l10n; flutter gen-l10n; configure delegates+supportedLocales
iOS NSLocalizedString("key", comment:) / String(localized:) .stringsdict String.localizedStringWithFormat N/A .strings in {locale}.lproj; XLIFF export; set CFBundleDevelopmentRegion
Android getString(R.string.key) / @string/key XML <plurals> element getString(R.string.key, arg) %1$s N/A strings.xml in res/values-{locale}; resConfigs in build.gradle

REFERENCE TABLES

Language =0 1 2 5 21 Forms
EN/DE/ES other one other other other one, other
FR/PT-BR one one other other other one, other (0+1=one)
JA/KO/ZH other other other other other other only
AR zero one two few other zero,one,two,few,many,other
RU/PL other one other many one one,few,many,other

Test with: 0, 1, 2, 5, 21.

Language Avg expansion Notes
DE/RU +30% (max +50%) Flag tight layouts
FR +20% (max +40%)
ES/PT +25% (max +40%)
AR +25% (max +50%) + RTL
JA -30% chars Wider glyphs
ZH -50% chars Wider glyphs
KO -20% chars Wider glyphs

OUTPUT FORMAT

i18n Wrap: {target}
========================================
Framework: {detected} | Locale: {format} | Source: {locale} | Targets: [{list}]

--- AUDIT ---
  Cleaned: {n} non-translatable keys | Unwrapped: {n} t() calls reverted

--- SCAN ---
  Files: {n} scanned | Strings: {n} found | Already wrapped: {n} | Newly wrapped: {n} | Skipped: {n}

--- KEYS ---
  {namespace}.*: {n} keys
  ...

--- LOCALE FILES ---
  {file}: +{n} keys
  Missing translations: {n} across {n} locales

--- VALIDATION ---
  Build: PASS/FAIL | Lint: PASS/FAIL | Tests: PASS/FAIL ({n} updated) | Key parity: PASS/FAIL | Plurals: PASS/FAIL

--- WARNINGS ---
  🔴 {critical issues}
  🟡 {quality concerns}
  🔵 {expansion risks, suggestions}

--- NEXT STEPS ---
  1. Send {n} keys for translation to {locales}
  2. Review flagged context-dependent strings

PRINCIPLES

Detect don't assume. Audit first. Semantic keys. Full sentences never split. Named params {name} not {0}. Framework-native plurals. Key parity across locales. Non-destructive. Validate everything.