SuperAPI AI Directory

i18n-review

Coding & development

View repository

Updated 10-16 08:09

About this skill

i18n-review: $ARGUMENTS

MODE

Read-only audit. Report issues with file, line, current code, and suggested fix. Never modify code unless user explicitly asks.

SEVERITY

  • 🔴 CRITICAL -- Broken/wrong translations (split sentences, missing plurals, placeholder mismatches)
  • 🟡 WARNING -- Quality/consistency degradation (over/under-wrapping, non-semantic keys)
  • 🔵 INFO -- Improvement suggestions (expansion risks, naming conventions, key organization)

DECISION RULE

If even ONE target language needs a different string, it MUST be wrapped.

ANTI-PATTERNS

  • NEVER auto-remove keys -- report only
  • NEVER mark dynamic-key-covered keys as unused without noting the dynamic pattern
  • NEVER flag false positives: "OK"/"Email"/"Yes"/"No" as button/form labels ARE translatable

Phase 1: DETECT (framework + i18n setup)

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 + target locales All configured
Plural format ICU / suffix / pipe / YAML / stringsdict

Phase 2-8: PARALLEL REVIEW

Use Agent tool (subagent_type: "research") to run review phases in parallel. Launch up to 4 subagents simultaneously:

Subagent A: Over-wrapping + Under-wrapping scan Subagent B: Split sentences + Hardcoded values scan Subagent C: Locale file quality + Pluralization review Subagent D: Unused keys analysis

Then run text expansion review on the main thread (needs results from other phases).


Subagent A: OVER-WRAPPING + UNDER-WRAPPING

But verify first -- these LOOK constant but DO translate: "OK" (JP: 了解, CN: 确定), "Email" (CN: 电子邮件, JP: メール), "N/A" (JP: 該当なし), "Yes"/"No" (DE: Ja/Nein), "Home"/"Search"/"Login", single-word labels, status words. Litmus test: "Does this EXACT string work in ALL target languages?" If yes -> over-wrapped.

Search patterns per file type:

  • JSX/TSX/Vue: tag content >[^<{]*[A-Za-z]{2,}[^<{]*<, translatable attrs (placeholder/title/alt/aria-label), message assignments
  • Python: message/label assignments, Django templates without {% trans %}
  • Ruby: ERB tag content, flash/render notice assignments
  • Swift/Kotlin: Text/Label/title/message/hint assignments

Commonly missed: "OK"/"Cancel", "Yes"/"No", form labels ("Email"/"Name"/"Date"), nav items, filter options ("N/A"/"None"/"All"), status text, "(optional)"/"(required)", "e.g."/"etc.", counter labels (+ plurals), ordinals ("1st"/"2nd"), aria-labels on icon buttons.

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


Subagent B: SPLIT SENTENCES + HARDCODED VALUES

Pattern Problem Fix
t('label') + ':' CJK uses fullwidth : Colon inside key
t('label') + ' - ' + val Separator varies by locale Single key
t('greeting') + name Word order differs (JP: name first) t('greeting', {name})
t('count') + ' ' + t('items') Breaks word order Single key + plurals
t('or') / t('and') standalone Connectors inside full sentence Merge into parent key
t('error') + ': ' + msg Separator varies t('errorMessage', {message})
t('step') + n + '/' + total Format differs per locale t('stepProgress', {step, total})
t('label').toLowerCase() Case rules differ by language (Turkish İ/i, German ß→SS) Separate key with correct casing
{n} {t('label')} (JSX) Bypasses plural handling ("1 languages") t('label_count', {count: n}) with ICU plurals

Search patterns:

rg -n 't\([^)]+\)\s*\+\s*["\x27`:}]'     # t() + string
rg -n '["\x27]\s*\+\s*t\('                 # string + t()
rg -n '`[^`]*\$\{t\('                      # template literal with t()
rg -n 't\([^)]+\).*t\([^)]+\)'             # multiple t() concatenated
Hardcoded Should be Format
File extensions (".json") {formats} string
Limits ("10MB") {limit} string
Counts ("14 days") {count} + plurals
Emails/URLs {email} / {url} string
Version numbers {version} string
Lists ("English, French") {languages} Intl.ListFormat
Prices ("$9.99") {price} Intl.NumberFormat
Dates ("Jan 1, 2025") {date} Intl.DateTimeFormat
Nav paths ("Settings > Account") Translate + locale separator parameterize

Test: if it could change without a code deploy, it MUST be a {parameter}.


Subagent C: LOCALE FILE QUALITY + PLURALIZATION

Key parity (per locale): count vs source, missing keys, extra keys, placeholder name mismatches, plural category coverage.

Key naming quality: mixed conventions (camelCase/snake_case/dot.notation), non-semantic keys ("text1","label2","msg_3"), incorrect scope sharing.

Language =0 1 2 5 21 Required forms
EN/DE/ES other one other other other one, other
FR/PT-BR one one other other other one, other
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.


Subagent D: UNUSED KEYS

This is the highest-value check. LLM semantic understanding is the differentiator over regex/AST tools.

Step 1: Extract all leaf keys from source locale (JSON dot paths / YAML nesting / PO msgids).

Step 2: Search codebase for each key using MULTIPLE patterns:

rg -l "'key.path'" --glob '!**/node_modules/**' --glob '!**/*.test.*' --glob '!**/dist/**' --glob '!**/messages/**' --glob '!**/locales/**'
rg -l '"key.path"' # same exclusions

Step 3: Handle dynamic keys (CRITICAL -- prevents false positives):

Pattern Implication
t(`error.${type}`) ALL error.* keys potentially used
const k = 'nav.home'; t(k) Trace variable to t() call
{ label: 'tab.info' } -> t(item.label) Trace config object
useTranslations('Foo') -> t('bar') Key is Foo.bar not bar
t(cond ? 'a' : 'b') Both a and b are used

Search for dynamic patterns:

rg -n 't\(`[^`]*\$\{' --glob '**/*.{ts,tsx,js,jsx,vue,svelte}'     # template literals
rg -n 't\(\s*[a-zA-Z_]\w*\s*[,)]' --glob '**/*.{ts,tsx,js,jsx}'   # variable refs
rg -n ":\s*['\"][a-z]+\.[a-z]" --glob '**/*.{ts,tsx,js,jsx}'       # config objects

Step 4: Classify:

  • UNUSED (HIGH) -- No direct ref, no dynamic pattern, no variable trace -> safe to remove
  • LIKELY UNUSED (MEDIUM) -- No direct ref, but namespace has dynamic patterns -> review manually
  • POTENTIALLY UNUSED (LOW) -- Referenced through variables/config that might be dead code -> investigate

MUST show evidence: "no references found" vs "only in dead code" vs "covered by dynamic pattern t(\errors.${type}`)`"


Main Thread: TEXT EXPANSION + PUNCTUATION

Language 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

Flag strings in: fixed-width containers (buttons, badges, table cells), CSS text-overflow: ellipsis, short labels in tight layouts (nav, tabs, form labels).

OUTPUT FORMAT

i18n Review: {target}
========================================
Framework: {detected} | Locale: {format} | Source: {locale} | Targets: [{list}]
Files reviewed: {n} | Wrapped strings: {n}

=== 🔴 CRITICAL ({n}) ===

SPLIT SENTENCES:
  🔴 {file}:{line} -- {pattern} -- Fix: {suggestion}

PLURAL ISSUES:
  🔴 {file}:{line} -- {pattern} -- Fix: {suggestion}
  🔴 {key} -- missing forms for {lang}: needs {forms}

PLACEHOLDER MISMATCHES:
  🔴 {key} -- en has {name}, {locale} missing

=== 🟡 WARNING ({n}) ===

OVER-WRAPPING:
  🟡 {file}:{line} -- t('{key}') wraps "{value}" -- {reason}

UNDER-WRAPPING:
  🟡 {file}:{line} -- "{string}" not wrapped -- {reason}

HARDCODED VALUES:
  🟡 {file}:{line} -- "{value}" in key "{key}" -- use {parameter}

LOCALE QUALITY:
  🟡 Non-translatable keys: {list}
  🟡 Key parity: {locale} missing {n} keys
  🟡 Naming: {examples}

UNUSED KEYS ({n} total):
  HIGH ({n} -- safe to remove):
    {file}: "{key}" -- "{value}" -- {evidence}
  MEDIUM ({n} -- review):
    {file}: "{key}" -- "{value}" -- {evidence}
  Savings: {n} keys -> {n} fewer strings across {n} locales

=== 🔵 INFO ({n}) ===

PUNCTUATION:
  🔵 {file}:{line} -- {pattern} -- {locale concern}

TEXT EXPANSION:
  🔵 {file}:{line} -- "{string}" ({n} chars) in {container} -- {risk}

=== SUMMARY ===
  🔴 Critical: {n}  |  🟡 Warning: {n}  |  🔵 Info: {n}

=== QUICK WINS (easiest high-impact fixes) ===
  1. {fix} -- {impact}
  2. {fix} -- {impact}
  3. {fix} -- {impact}

=== TOP PRIORITIES ===
  1. {most impactful fix}
  2. {second}
  3. {third}

PRINCIPLES

Read-only default. Prioritize by impact (split sentences > missing plurals > over-wrapping). Context matters (consider target languages). No false positives (verify before flagging). Actionable output (file + line + code + fix). The litmus test: if ONE target language differs, it MUST be wrapped.