Internationalization and localization review (i18n / l10n)
You are a localization auditor. Your task is to find everything that would keep the
interface from working correctly in languages and regions other than the
development language: hardcoded strings, untranslated keys, layout that breaks on
long/RTL languages, naive pluralization and concatenation, date/number/currency
formatting that ignores the locale, time-zone and unicode issues. The discipline is
evidence over assertion: every finding is tied to file:line and the SPECIFIC
locale/language that breaks a specific behavior. Where a visual effect matters
(clipping, mirroring) — confirm it with a live run in the browser on the
corresponding locale, not just by reading the code.
The skill is project-agnostic. First determine: which i18n mechanism is used
(react-i18next / i18next / react-intl(FormatJS) / vue-i18n / Angular i18n /
gettext / ICU MessageFormat / Rails I18n / .NET resx / Django gettext / bare
JSON dictionaries / no system at all), where the translation resources live, and
which locales are declared. That determines what to check and how. If the scope is
large and the Agent tool is available — split it across subagents by zone/language
(see "Running it").
INPUT / SCOPE (how to determine the perimeter)
Scope: $ARGUMENTS. Determine the input mode and build the SCOPE. The scope is
broader than the literal input: include the shared formatting components/utilities
(a single formatDate/formatMoney/<Trans> wrapper) that the code under review
uses — a defect in a shared formatting utility is multiplied across the whole app.
A. CODE: feature / screen / directory / branch / diff / whole frontend. Scope =
the directory contents (or git diff --stat against the base branch) + the
translation resource files affected by these screens + the shared formatting
utilities and i18n wrappers that the code calls. Determine the target locales (from
the i18n config / README / list of resource files) and the URLs of the screens for
the live run.
B. DOCUMENT: requirements / spec / localization spec (.md/.txt/.docx). Read it
in full. Extract: the list of target languages/regions, format requirements
(currency, date, units), whether RTL support is stated, time-zone rules. Match
against the code (grep): a requirement "we support ar-SA with RTL" while the code
has no dir/logical CSS properties is a finding.
C. ISSUE in a tracker (Jira/YouTrack/GitHub/Linear: ID or link). Get the issue
text via an available integration (an MCP tool, if connected; otherwise ask the
user). Find the related commits (git log --all --grep=<ID> --oneline,
git show --stat), and build the list of affected files and resources.
If the scope is ambiguous — check with the task author; do not check the whole
frontend at random. Record the final SCOPE (files, translation resources, target
locales) at the start of the report. If the target locales are unknown — use a
representative set: a long language (de/fi), RTL (ar/he), CJK (ja/zh), a language
with complex pluralization (ru/pl/ar), and confirm the list with the user.
KEY PRINCIPLE: "works in English" ≠ "localizable"
Check adversarially:
- Do not trust that a string is extracted into a dictionary — verify it is
extracted WHOLE and meaningfully, not assembled by concatenating pieces (word
order differs in another language).
- Do not consider a translation complete just because a locale file exists —
compare the set of keys across locales (a missing key = a silent fallback to the
default language or the raw key being shown).
- Do not judge the layout by English — German/Finnish text is 30–40% longer, RTL
mirrors the layout. Check on a real long/RTL translation (or on a pseudo-locale).
- Do not consider
new Date().toLocaleString() without an explicit locale/time
zone correct — it depends on the server/browser environment and silently gives
different results.
- State the status explicitly: "hardcoded" / "extracted, but assembled by
concatenation" / "extracted, key missing in some locales" / "localized
correctly".
METHODOLOGY: THREE INDEPENDENT PASSES (within the SCOPE)
PASS 1 — Tooling
- If the project has an i18n linter/extractor — run it:
i18next-parser/
i18next scanner (key reconciliation and finding missing keys), formatjs extract, eslint-plugin-i18next / eslint-plugin-formatjs /
eslint-plugin-react rule against literal strings, gettext tools (msgcmp,
msgfmt --check). Goal — automatically detect hardcoding and key desync.
- Compare the key sets across all locale resource files (a key diff): find keys
present in one locale and missing in others, and vice versa (dead keys).
grep across the SCOPE for suspicious hardcoding patterns (see checklist block
1).
PASS 2 — Manual line-by-line code review
Go through the SCOPE files line by line against the checklist below. For each
finding — file:line, category, the specific breaking locale, scenario.
PASS 3 — LIVE run across locales (where there is a visual effect)
Actually bring up the app (the standard dev command from package.json/README or the
specified environment) and, via the Claude Browser MCP, check the key screens:
- Switch the locale to a long language (de/fi) — check clipping/overflow/text
collision in buttons, tabs, badges, menus; if pseudo-localization exists — turn
it on.
- Switch to RTL (ar/he) — check layout mirroring (
dir="rtl"): alignment,
direction of arrow icons, element order, padding/margin.
- CJK (ja/zh) — line wrapping, absence of spaces, line height.
- Formats — verify that dates/numbers/currencies on screen are rendered per the
chosen locale, not hardcoded; change the system locale/time zone if it matters.
- Record the result with a screenshot/description. Whatever could not be run live
(the needed locale not in the build, headless) — into the "what was not checked"
section.
CHECKLIST BY CATEGORY (apply the ones relevant to the SCOPE)
- Hardcoded user-facing strings. Any text visible to the user must go through
an i18n function/resource, not be a literal in the code. Look for: string
literals in JSX/templates,
alt/title/placeholder/aria-label with text,
text in throw new Error(...) shown to the user, text in validators, enum
values rendered as-is, strings in toasts/alerts. Exceptions (technical logs, API
keys) — flag them but do not count them as findings.
- Translation completeness and integrity. All keys are present in all target
locales; no raw keys have "leaked" into the UI (
user.profile.title instead of
text); a fallback to the default language is deliberate and does not mask a
missing key; no empty values; no duplicate keys with diverging meaning.
- Text length and UI overflow. Fixed width/height on text containers,
text-overflow: ellipsis where clipping loses meaning, absence of wrapping
(white-space: nowrap) on translatable elements; buttons/badges/tabs sized for
short English. Check on a long language (de/fi ~+35%).
- RTL (right-to-left).
dir="rtl" is set by locale; logical CSS properties
are used (margin-inline-start instead of margin-left, padding-inline,
inset-inline) instead of physical ones; directional icons ("back/forward"
arrows, progress) are mirrored; the layout is not broken on flex/grid; the order
of paired elements is correct.
- Pluralization. Not concatenation
count + " item(s)" and not a naive ternary
count === 1 ? 'item' : 'items' — the target language's plural rules are used
(ICU plural, i18next _plural/count, gettext ngettext). Account for the fact
that Russian/Polish/Arabic have more than two forms (one/few/many/other), while
Japanese has one. Check the zero form where it is special.
- Date and time formatting. Not a hardcoded format (
DD.MM.YYYY,
MM/DD/YYYY) — Intl.DateTimeFormat/a locale-aware library with the locale
passed in is used; the 12/24-hour format, the first day of the week, and
month/day names come from the locale.
- Number, currency, unit formatting. Thousands/decimal separators by locale
(1,234.56 vs 1 234,56 vs 1.234,56),
Intl.NumberFormat; currency is formatted
with the correct symbol/position/code (Intl.NumberFormat(..., {style: 'currency', currency})) rather than by gluing on a $; units of measure and
systems (metric/imperial) by region.
- Time zones. Time is stored in UTC and displayed in the user's time zone; no
new Date(string) without a zone giving an offset; daylight saving time is
accounted for; "today/yesterday" is computed in the user's time zone, not the
server's.
- Sorting and collation. Lists are sorted with locale awareness
(
Intl.Collator/localeCompare(locale)), not by character codes (otherwise
diacritics, ё/е, case, CJK sort incorrectly).
- Unicode, encodings, normalization. UTF-8 everywhere; correct handling of
emoji and composite graphemes (length counted by code points/graphemes, not by
UTF-16 code units — string truncation does not tear a surrogate pair/emoji);
normalization (NFC/NFD) on comparison/search/storage; input in non-native
layouts (diacritics, IME for CJK) does not break validation.
- Variable interpolation in translations. Variables are inserted as named
placeholders inside the translatable string (
t('greeting', {name}) →
"Hello, {name}!"), so the translator can change word order; NOT by gluing
fragments (t('greeting') + name + t('suffix')). Placeholders are present in
all locales and not lost in translation; number/date formatting inside the
message is also locale-aware (ICU {n, number}/{d, date}).
- Locale-dependent assets and signs. Images/icons with baked-in text
(localized versions or overlaid text needed); a flag ≠ a language (do not use a
country flag as a language switcher); symbols, gestures, colors with cultural
connotations; sample data (names, phones, addresses, formats) in the UI by
region.
- Locale selection and persistence. The locale is detected correctly
(
Accept-Language/user setting/URL), persists across sessions, switches without
reloading data in the wrong language; SSR/meta tags (<html lang>, hreflang)
are consistent with the chosen locale; the server and client do not diverge in
locale (hydration).
EDGE CASES THAT ARE OFTEN MISSED
- Strings inside error/validation messages and in toasts — they are often forgotten
when extracting to the dictionary.
- Concatenating "{count} {unit}" or "Showing X of Y" from pieces — breaks word
order and pluralization at the same time.
- Pseudo-translation is absent, so overflow is only found after the release in
German.
toLocaleDateString() without an explicit locale argument — "works" on the
developer's machine, gives a different format in prod.
- A key was added only to the default locale, the others silently show English
(or the raw key) — it looks like "translated".
- RTL: the logo / "back" icon is not mirrored; a dropdown opens off the edge of the
screen;
text-align: left is hardcoded.
- Truncating a string with
.substring(0, n) tears an emoji/surrogate pair → a
broken character.
- Pluralization was checked in English (2 forms) and fails in Russian
(5 files → "5 файла"/"5 файлов").
- Currency formatted as
"$" + amount — wrong for the euro / locales where the
symbol comes after the amount, and for currencies with no fractional part (JPY).
- Sorting a dropdown of countries/names by ASCII — diacritics drift to the end.
- A "yesterday/today" date computed in UTC → for a user in UTC-8 it shows the wrong
day.
- A hardcoded
lang="en" in <html> while the locale is switched — breaks screen
readers and hyphenation.
- Numeric input: the user enters
1.234,56 (European format), the parser expects
1234.56 → an incorrect value.
- A field length/character limit set for Latin — for CJK a single "character"
carries more meaning, and the limit becomes too strict.
SEVERITY SCALE
For each finding, state which locale/language breaks and exactly what.
- Critical — on the target locale the functionality is inoperable or data is
corrupted (incorrect number/currency parsing → a wrong amount; raw keys instead
of text across the whole screen; RTL completely breaks navigation).
- High — a noticeable UX breakage on the target locale: clipped/overlapping
text on key elements, a wrong date/currency format on important data, incorrect
pluralization in the main scenario, large translation gaps.
- Medium — a local problem: some strings are hardcoded, suboptimal sorting,
lack of support for one of the secondary locales, minor overflow.
- Low — best practice with no direct breakage scenario on the current target
locales (physical CSS properties with no RTL plans, absence of a pseudo-locale).
Verdict: ready for localization into the target languages / ready with caveats /
not ready (list the blockers and in which languages).
REPORT FORMAT
- Executive summary (no jargon): will the product work in the target
languages/regions, what will break and in which language, what to fix first, is
there a risk of data corruption (money/dates).
- SCOPE — the files/resources/locales checked and what was left out of scope.
- One-line verdict up front.
- Coverage matrix — which locales were checked live, which only from the code,
what the linter checked.
- List of findings: ID, file:line, category, the specific breaking
locale/language, scenario, severity, recommendation.
- What was done well — strong i18n patterns worth replicating.
- Action plan: blockers vs. deferred.
- What was not checked — limitations (not all locales in the build, no real
translations to check length, RTL was not tested live, etc.).
RULES FOR WRITING UP FINDINGS
Before you start, check whether a report for this scope exists in docs/qa/i18n/ —
if so, continue the ID numbering and update statuses rather than recreating it.
For each finding:
- A stable ID:
I18N-<scope-slug>-001.
- file:line (and/or URL + element).
- Category (the checklist block number / type: hardcoding, pluralization, RTL,
format…).
- The specific breaking locale/language and scenario: "on de-DE the button text
"Speichern und fortfahren" is clipped in the 120px container" — not in the
abstract.
- Severity with justification (what breaks / whether data is corrupted).
- A concrete recommendation ("extract the string into resource
common.save",
"replace the concatenation with an ICU plural", "use Intl.NumberFormat with the
locale and currency passed in").
Save the report to docs/qa/i18n/<scope-slug>.md (follow the existing repository
structure; docs/qa/... is the default).
RUNNING IT (practical instructions)
- Determine the SCOPE YOURSELF in the main thread (see "Input") and the target
locales — do not delegate; a subagent does not see the conversation context.
Determine the project's i18n mechanism, where the resources are, and how to bring
up the app.
- Check whether a previous report exists in
docs/qa/i18n/.
- Run PASS 1 (linter/extractor, key diff across locales, grep for hardcoding).
- Carry out PASS 2 (line-by-line review against the checklist) and PASS 3 (live run
on a long/RTL/CJK language in the browser — mandatory for the visual categories
3, 4). If the scope is large and the Agent tool is available — split zones/
languages across subagents; give each one the concrete paths/resources, the
checklist, the severity scale, and the finding format (the subagent does not see
this file). Write confirmed findings into an interim file as you go.
- Merge the three passes into the report, filter out false positives, save to
docs/qa/i18n/<scope-slug>.md.
- Explicitly list what was not checked.
This is testing, not implementation: fixes are made by the developer based on the
report, not by you within this skill.
1---2name: i18n-l10n-review3description: Internationalization (i18n) and localization (l10n) review — scope from a feature/screen/directory/branch, a requirements document, or a tracker issue; checking hardcoded strings, translation completeness, UI overflow on long languages, RTL support, pluralization, date/number/currency/time formats by locale, time zones, unicode, and variable interpolation, with a live run across locales in the browser. Every finding is tied to file:line, the specific breaking locale/language, and a scenario, with an explicit verdict on multilingual readiness. Use when asked to check localization, do an i18n/l10n review, assess support for other languages, find hardcoded user-facing strings, verify whether the layout will break on long translations or on RTL (Arabic/Hebrew), or whether dates/numbers/currencies are formatted correctly by locale — even without the word "review", e.g. "is there any hardcoded text here", "will this work in German", "did we really translate everything", "why is the date shown in American format", "4---56# Internationalization and localization review (i18n / l10n)78You are a localization auditor. Your task is to find everything that would keep the9interface from working correctly in languages and regions other than the10development language: hardcoded strings, untranslated keys, layout that breaks on11long/RTL languages, naive pluralization and concatenation, date/number/currency12formatting that ignores the locale, time-zone and unicode issues. The discipline is13evidence over assertion: every finding is tied to file:line and the SPECIFIC14locale/language that breaks a specific behavior. Where a visual effect matters15(clipping, mirroring) — confirm it with a live run in the browser on the16corresponding locale, not just by reading the code.1718The skill is project-agnostic. First determine: which i18n mechanism is used19(react-i18next / i18next / react-intl(FormatJS) / vue-i18n / Angular i18n /20gettext / ICU MessageFormat / Rails I18n / .NET resx / Django `gettext` / bare21JSON dictionaries / no system at all), where the translation resources live, and22which locales are declared. That determines what to check and how. If the scope is23large and the Agent tool is available — split it across subagents by zone/language24(see "Running it").2526## INPUT / SCOPE (how to determine the perimeter)2728Scope: `$ARGUMENTS`. Determine the input mode and build the SCOPE. The scope is29broader than the literal input: include the shared formatting components/utilities30(a single `formatDate`/`formatMoney`/`<Trans>` wrapper) that the code under review31uses — a defect in a shared formatting utility is multiplied across the whole app.3233**A. CODE: feature / screen / directory / branch / diff / whole frontend.** Scope =34the directory contents (or `git diff --stat` against the base branch) + the35translation resource files affected by these screens + the shared formatting36utilities and i18n wrappers that the code calls. Determine the target locales (from37the i18n config / README / list of resource files) and the URLs of the screens for38the live run.3940**B. DOCUMENT: requirements / spec / localization spec (.md/.txt/.docx).** Read it41in full. Extract: the list of target languages/regions, format requirements42(currency, date, units), whether RTL support is stated, time-zone rules. Match43against the code (`grep`): a requirement "we support ar-SA with RTL" while the code44has no `dir`/logical CSS properties is a finding.4546**C. ISSUE in a tracker (Jira/YouTrack/GitHub/Linear: ID or link).** Get the issue47text via an available integration (an MCP tool, if connected; otherwise ask the48user). Find the related commits (`git log --all --grep=<ID> --oneline`,49`git show --stat`), and build the list of affected files and resources.5051If the scope is ambiguous — check with the task author; do not check the whole52frontend at random. Record the final SCOPE (files, translation resources, target53locales) at the start of the report. If the target locales are unknown — use a54representative set: a long language (de/fi), RTL (ar/he), CJK (ja/zh), a language55with complex pluralization (ru/pl/ar), and confirm the list with the user.5657## KEY PRINCIPLE: "works in English" ≠ "localizable"58Check adversarially:591. Do not trust that a string is extracted into a dictionary — verify it is60 extracted WHOLE and meaningfully, not assembled by concatenating pieces (word61 order differs in another language).622. Do not consider a translation complete just because a locale file exists —63 compare the set of keys across locales (a missing key = a silent fallback to the64 default language or the raw key being shown).653. Do not judge the layout by English — German/Finnish text is 30–40% longer, RTL66 mirrors the layout. Check on a real long/RTL translation (or on a pseudo-locale).674. Do not consider `new Date().toLocaleString()` without an explicit locale/time68 zone correct — it depends on the server/browser environment and silently gives69 different results.705. State the status explicitly: "hardcoded" / "extracted, but assembled by71 concatenation" / "extracted, key missing in some locales" / "localized72 correctly".7374## METHODOLOGY: THREE INDEPENDENT PASSES (within the SCOPE)7576### PASS 1 — Tooling77- If the project has an i18n linter/extractor — run it: `i18next-parser`/78 `i18next scanner` (key reconciliation and finding missing keys), `formatjs79 extract`, `eslint-plugin-i18next` / `eslint-plugin-formatjs` /80 `eslint-plugin-react` rule against literal strings, `gettext` tools (`msgcmp`,81 `msgfmt --check`). Goal — automatically detect hardcoding and key desync.82- Compare the key sets across all locale resource files (a key diff): find keys83 present in one locale and missing in others, and vice versa (dead keys).84- `grep` across the SCOPE for suspicious hardcoding patterns (see checklist block85 1).8687### PASS 2 — Manual line-by-line code review88Go through the SCOPE files line by line against the checklist below. For each89finding — file:line, category, the specific breaking locale, scenario.9091### PASS 3 — LIVE run across locales (where there is a visual effect)92Actually bring up the app (the standard dev command from package.json/README or the93specified environment) and, via the Claude Browser MCP, check the key screens:94- **Switch the locale** to a long language (de/fi) — check clipping/overflow/text95 collision in buttons, tabs, badges, menus; if pseudo-localization exists — turn96 it on.97- **Switch to RTL** (ar/he) — check layout mirroring (`dir="rtl"`): alignment,98 direction of arrow icons, element order, padding/margin.99- **CJK** (ja/zh) — line wrapping, absence of spaces, line height.100- **Formats** — verify that dates/numbers/currencies on screen are rendered per the101 chosen locale, not hardcoded; change the system locale/time zone if it matters.102- Record the result with a screenshot/description. Whatever could not be run live103 (the needed locale not in the build, headless) — into the "what was not checked"104 section.105106## CHECKLIST BY CATEGORY (apply the ones relevant to the SCOPE)1071081. **Hardcoded user-facing strings.** Any text visible to the user must go through109 an i18n function/resource, not be a literal in the code. Look for: string110 literals in JSX/templates, `alt`/`title`/`placeholder`/`aria-label` with text,111 text in `throw new Error(...)` shown to the user, text in validators, `enum`112 values rendered as-is, strings in toasts/alerts. Exceptions (technical logs, API113 keys) — flag them but do not count them as findings.1142. **Translation completeness and integrity.** All keys are present in all target115 locales; no raw keys have "leaked" into the UI (`user.profile.title` instead of116 text); a fallback to the default language is deliberate and does not mask a117 missing key; no empty values; no duplicate keys with diverging meaning.1183. **Text length and UI overflow.** Fixed width/height on text containers,119 `text-overflow: ellipsis` where clipping loses meaning, absence of wrapping120 (`white-space: nowrap`) on translatable elements; buttons/badges/tabs sized for121 short English. Check on a long language (de/fi ~+35%).1224. **RTL (right-to-left).** `dir="rtl"` is set by locale; logical CSS properties123 are used (`margin-inline-start` instead of `margin-left`, `padding-inline`,124 `inset-inline`) instead of physical ones; directional icons ("back/forward"125 arrows, progress) are mirrored; the layout is not broken on flex/grid; the order126 of paired elements is correct.1275. **Pluralization.** Not concatenation `count + " item(s)"` and not a naive ternary128 `count === 1 ? 'item' : 'items'` — the target language's plural rules are used129 (ICU `plural`, i18next `_plural`/count, gettext `ngettext`). Account for the fact130 that Russian/Polish/Arabic have more than two forms (one/few/many/other), while131 Japanese has one. Check the zero form where it is special.1326. **Date and time formatting.** Not a hardcoded format (`DD.MM.YYYY`,133 `MM/DD/YYYY`) — `Intl.DateTimeFormat`/a locale-aware library with the locale134 passed in is used; the 12/24-hour format, the first day of the week, and135 month/day names come from the locale.1367. **Number, currency, unit formatting.** Thousands/decimal separators by locale137 (1,234.56 vs 1 234,56 vs 1.234,56), `Intl.NumberFormat`; currency is formatted138 with the correct symbol/position/code (`Intl.NumberFormat(..., {style:139 'currency', currency})`) rather than by gluing on a `$`; units of measure and140 systems (metric/imperial) by region.1418. **Time zones.** Time is stored in UTC and displayed in the user's time zone; no142 `new Date(string)` without a zone giving an offset; daylight saving time is143 accounted for; "today/yesterday" is computed in the user's time zone, not the144 server's.1459. **Sorting and collation.** Lists are sorted with locale awareness146 (`Intl.Collator`/`localeCompare(locale)`), not by character codes (otherwise147 diacritics, ё/е, case, CJK sort incorrectly).14810. **Unicode, encodings, normalization.** UTF-8 everywhere; correct handling of149 emoji and composite graphemes (length counted by code points/graphemes, not by150 UTF-16 code units — string truncation does not tear a surrogate pair/emoji);151 normalization (NFC/NFD) on comparison/search/storage; input in non-native152 layouts (diacritics, IME for CJK) does not break validation.15311. **Variable interpolation in translations.** Variables are inserted as named154 placeholders inside the translatable string (`t('greeting', {name})` →155 "Hello, {name}!"), so the translator can change word order; NOT by gluing156 fragments (`t('greeting') + name + t('suffix')`). Placeholders are present in157 all locales and not lost in translation; number/date formatting inside the158 message is also locale-aware (ICU `{n, number}`/`{d, date}`).15912. **Locale-dependent assets and signs.** Images/icons with baked-in text160 (localized versions or overlaid text needed); a flag ≠ a language (do not use a161 country flag as a language switcher); symbols, gestures, colors with cultural162 connotations; sample data (names, phones, addresses, formats) in the UI by163 region.16413. **Locale selection and persistence.** The locale is detected correctly165 (`Accept-Language`/user setting/URL), persists across sessions, switches without166 reloading data in the wrong language; SSR/meta tags (`<html lang>`, hreflang)167 are consistent with the chosen locale; the server and client do not diverge in168 locale (hydration).169170## EDGE CASES THAT ARE OFTEN MISSED171- Strings inside error/validation messages and in toasts — they are often forgotten172 when extracting to the dictionary.173- Concatenating "{count} {unit}" or "Showing X of Y" from pieces — breaks word174 order and pluralization at the same time.175- Pseudo-translation is absent, so overflow is only found after the release in176 German.177- `toLocaleDateString()` without an explicit locale argument — "works" on the178 developer's machine, gives a different format in prod.179- A key was added only to the default locale, the others silently show English180 (or the raw key) — it looks like "translated".181- RTL: the logo / "back" icon is not mirrored; a dropdown opens off the edge of the182 screen; `text-align: left` is hardcoded.183- Truncating a string with `.substring(0, n)` tears an emoji/surrogate pair → a184 broken character.185- Pluralization was checked in English (2 forms) and fails in Russian186 (5 files → "5 файла"/"5 файлов").187- Currency formatted as `"$" + amount` — wrong for the euro / locales where the188 symbol comes after the amount, and for currencies with no fractional part (JPY).189- Sorting a dropdown of countries/names by ASCII — diacritics drift to the end.190- A "yesterday/today" date computed in UTC → for a user in UTC-8 it shows the wrong191 day.192- A hardcoded `lang="en"` in `<html>` while the locale is switched — breaks screen193 readers and hyphenation.194- Numeric input: the user enters `1.234,56` (European format), the parser expects195 `1234.56` → an incorrect value.196- A field length/character limit set for Latin — for CJK a single "character"197 carries more meaning, and the limit becomes too strict.198199## SEVERITY SCALE200For each finding, state which locale/language breaks and exactly what.201- **Critical** — on the target locale the functionality is inoperable or data is202 corrupted (incorrect number/currency parsing → a wrong amount; raw keys instead203 of text across the whole screen; RTL completely breaks navigation).204- **High** — a noticeable UX breakage on the target locale: clipped/overlapping205 text on key elements, a wrong date/currency format on important data, incorrect206 pluralization in the main scenario, large translation gaps.207- **Medium** — a local problem: some strings are hardcoded, suboptimal sorting,208 lack of support for one of the secondary locales, minor overflow.209- **Low** — best practice with no direct breakage scenario on the current target210 locales (physical CSS properties with no RTL plans, absence of a pseudo-locale).211212Verdict: ready for localization into the target languages / ready with caveats /213not ready (list the blockers and in which languages).214215## REPORT FORMAT2161. **Executive summary** (no jargon): will the product work in the target217 languages/regions, what will break and in which language, what to fix first, is218 there a risk of data corruption (money/dates).2192. **SCOPE** — the files/resources/locales checked and what was left out of scope.2203. **One-line verdict** up front.2214. **Coverage matrix** — which locales were checked live, which only from the code,222 what the linter checked.2235. **List of findings**: ID, file:line, category, the specific breaking224 locale/language, scenario, severity, recommendation.2256. **What was done well** — strong i18n patterns worth replicating.2267. **Action plan**: blockers vs. deferred.2278. **What was not checked** — limitations (not all locales in the build, no real228 translations to check length, RTL was not tested live, etc.).229230## RULES FOR WRITING UP FINDINGS231Before you start, check whether a report for this scope exists in `docs/qa/i18n/` —232if so, continue the ID numbering and update statuses rather than recreating it.233For each finding:234- A stable ID: `I18N-<scope-slug>-001`.235- file:line (and/or URL + element).236- Category (the checklist block number / type: hardcoding, pluralization, RTL,237 format…).238- The specific breaking locale/language and scenario: "on de-DE the button text239 "Speichern und fortfahren" is clipped in the 120px container" — not in the240 abstract.241- Severity with justification (what breaks / whether data is corrupted).242- A concrete recommendation ("extract the string into resource `common.save`",243 "replace the concatenation with an ICU plural", "use `Intl.NumberFormat` with the244 locale and currency passed in").245Save the report to `docs/qa/i18n/<scope-slug>.md` (follow the existing repository246structure; `docs/qa/...` is the default).247248## RUNNING IT (practical instructions)2491. Determine the SCOPE YOURSELF in the main thread (see "Input") and the target250 locales — do not delegate; a subagent does not see the conversation context.251 Determine the project's i18n mechanism, where the resources are, and how to bring252 up the app.2532. Check whether a previous report exists in `docs/qa/i18n/`.2543. Run PASS 1 (linter/extractor, key diff across locales, grep for hardcoding).2554. Carry out PASS 2 (line-by-line review against the checklist) and PASS 3 (live run256 on a long/RTL/CJK language in the browser — mandatory for the visual categories257 3, 4). If the scope is large and the Agent tool is available — split zones/258 languages across subagents; give each one the concrete paths/resources, the259 checklist, the severity scale, and the finding format (the subagent does not see260 this file). Write confirmed findings into an interim file as you go.2615. Merge the three passes into the report, filter out false positives, save to262 `docs/qa/i18n/<scope-slug>.md`.2636. Explicitly list what was not checked.264265This is testing, not implementation: fixes are made by the developer based on the266report, not by you within this skill.267