Internationalization file checker
Developer tools
Loading
Loading tool
The tool is loaded only when you open it.
All processing for this tool happens in your browser. Your input is not sent to a server.
About this tool
Compare a reference locale with a target locale before releasing translated messages. The checker retains nested object paths, empty containers and JSON types, then reports missing keys, extra keys and mismatches at JSON Pointer locations. Choose plain text, named braces or the explicitly limited ICU-flat grammar; no template is rendered or executed. Translation wording and primitive values are deliberately not compared. The result is a structural review aid, not a translation-quality score or proof that your localization framework will accept every message.
Common uses
- Find a missing menu label, an obsolete target key or a string accidentally changed into an object after merging translation files.
- Check that translated messages retain the named or positional arguments expected by application code, without treating normal wording differences as errors.
- Review flat cardinal or ordinal plural messages using each language’s own categories, then share a located JSON issue report with the localization reviewer.
How to use it
- 1.Paste or open the reference and target UTF-8 JSON files. Both roots must be objects. Start with plain text, or explicitly select braces or ICU-flat; select the actual language tag for each side when using ICU-flat.
- 2.Keep the default array rejection unless index-by-index comparison matches your file format. Run the check, then inspect errors and warnings by side, kind and page. A missing subtree is reported at its container and each descendant path.
- 3.Review the complete issue list, then copy or download the JSON report. Display filters and pagination do not narrow the export. Editing input or comparison settings invalidates the old result; reduce the scope and rerun if any report limit is reached.
Reproducible localization checks
Plain mode ignores wording and values
reference:
{"greeting":"Hello {name}","count":1,"enabled":true,"empty":{}}
target:
{"greeting":"こんにちは {broken","count":99,"enabled":false,"empty":{}}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[]}Different translations, numbers and booleans keep the same types. Plain mode deliberately ignores the unmatched brace, so the compact summary has no issues.
Missing paths, extra paths and type changes
reference:
{"menu":{"home":"Home","gone":"Old"},"count":1,"empty":{}}
target:
{"menu":{"home":"首页","extra":"New"},"count":"1","empty":[]}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"indices"}{"valid":true,"issues":[{"code":"missing-key","path":"/menu/gone","side":"before","severity":"error"},{"code":"type-mismatch","path":"/count","side":"after","severity":"error"},{"code":"type-mismatch","path":"/empty","side":"after","severity":"error"},{"code":"extra-key","path":"/menu/extra","side":"after","severity":"error"}]}The target lacks /menu/gone and adds /menu/extra. A number becomes a string and an empty object becomes an empty array; explicit index mode permits the array to be inspected.
A missing subtree includes its containers
reference:
{"group":{"title":"Title"},"empty":{}}
target:
{}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"missing-key","path":"/group","side":"before","severity":"error"},{"code":"missing-key","path":"/group/title","side":"before","severity":"error"},{"code":"missing-key","path":"/empty","side":"before","severity":"error"}]}The reference group, its title and the separate empty object each have a JSON path. All three are missing from the target; empty containers are not flattened away.
Escaped JSON Pointer and ordinary prototype names
reference:
{"a/b":{"~name":"x"},"a.b":"x","__proto__":"text","constructor":{}}
target:
{"a/b":{},"a.b":"y","__proto__":"译文","constructor":{}}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"missing-key","path":"/a~1b/~0name","side":"before","severity":"error"}]}The missing key is ~name inside a/b, reported as /a~1b/~0name. The literal a.b, __proto__ and constructor keys remain independent ordinary data.
Duplicate names after JSON escape decoding
reference:
{"title":"One","ti\u0074le":"Two"}
target:
{}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":false,"issues":[{"code":"duplicate-key","path":"/title","side":"before","severity":"error"}]}title and ti\u0074le decode to the same name. The reference is rejected instead of silently keeping the last value; comparison and report export are unavailable.
The default array policy rejects both inputs
reference:
{"steps":["A","B"]}
target:
{"steps":["一"]}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":false,"issues":[{"code":"arrays-rejected","path":"/steps","side":"before","severity":"error"},{"code":"arrays-rejected","path":"/steps","side":"after","severity":"error"}]}Both documents contain an array at /steps. Default rejection diagnoses each side before key comparison; it does not pretend array elements are object keys.
Explicit zero-based array comparison
reference:
{"steps":["A","B"]}
target:
{"steps":["一"]}
options:
{"mode":"plain","beforeLocale":"en","afterLocale":"ja","arrays":"indices"}{"valid":true,"issues":[{"code":"missing-key","path":"/steps/1","side":"before","severity":"error"}]}With index mode enabled, the first elements are both strings even though their wording differs. The reference’s second element is missing at /steps/1.
Literal doubled braces and argument sets
reference:
{"label":"{{literal}} {name} {0} {name}"}
target:
{"label":"{0} {name} {{文字}}"}
options:
{"mode":"braces","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[]}Doubled braces are literal text. Both messages use name and 0; changing their order or repeating name does not change the set and produces no mismatch.
A renamed argument is a mismatch
reference:
{"label":"Hello {name}, item {0}"}
target:
{"label":"{user} さん、項目 {0}"}
options:
{"mode":"braces","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"placeholder-mismatch","path":"/label","side":"after","severity":"error"}]}The target replaces name with user while keeping positional argument 0. The located mismatch is on the target string; changing the translated wording itself is irrelevant.
Malformed message syntax still yields diagnostics
reference:
{"label":"Hello {name"}
target:
{"label":"こんにちは {name}"}
options:
{"mode":"braces","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"message-syntax","path":"/label","side":"before","severity":"error"}]}The JSON is valid, but the reference string lacks a closing brace. valid remains true because the comparison input is usable; the message-syntax error still needs correction.
English and Japanese use their own categories
reference:
{"cart":"{count, plural, one {# item} other {# items}}"}
target:
{"cart":"{count, plural, other {# 個}}"}
options:
{"mode":"icu-flat","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[]}English includes one and other; Japanese uses only other. All required arguments and locale-specific categories fit, so equal category sets across languages are not required.
Coverage and unreachable categories are warnings
reference:
{"cart":"{count, plural, other {# items}}"}
target:
{"cart":"{count, plural, one {# 個} other {# 個}}"}
options:
{"mode":"icu-flat","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"category-coverage","path":"/cart","side":"before","severity":"warning"},{"code":"category-unreachable","path":"/cart","side":"after","severity":"warning"}]}English has no explicit one branch and therefore receives a coverage warning. Japanese supplies one even though its cardinal rules use other; that branch receives an unreachable-category warning.
Branch arguments and explicit integer selectors
reference:
{"cart":"{count, plural, =0 {None} one {# {unit}} other {# {unit}}}"}
target:
{"cart":"{count, plural, other {# 個}}"}
options:
{"mode":"icu-flat","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"plural-placeholder-mismatch","path":"/cart","side":"after","severity":"error"},{"code":"branch-placeholder-mismatch","path":"/cart","side":"after","severity":"error"},{"code":"exact-selector-missing","path":"/cart","side":"after","severity":"warning"}]}The Japanese other branch loses unit from the reference other branch, producing an error. The reference =0 selector is also missing; that difference is a separate warning, not a mandatory locale category. The union of arguments across all plural branches is also compared, so one lost argument can produce both plural-wide and branch-level errors.
Unsupported ICU constructs and missing other
reference:
{"nested":"{n, plural, one {{m, plural, other {x}}} other {x}}","format":"{price, number}","required":"{n, plural, one {x}}"}
target:
{"nested":"{n, plural, other {#}}","format":"{price}","required":"{n, plural, other {#}}"}
options:
{"mode":"icu-flat","beforeLocale":"en","afterLocale":"ja","arrays":"reject"}{"valid":true,"issues":[{"code":"unsupported-nested","path":"/nested","side":"before","severity":"error"},{"code":"unsupported-formatter","path":"/format","side":"before","severity":"error"},{"code":"missing-other","path":"/required","side":"before","severity":"error"}]}The reference has a nested plural, a number formatter and a plural without other. Each is diagnosed at its own string path; the checker never renders, executes or silently accepts these messages.
Common localization review mistakes
- Choosing braces for an i18next-style {{name}} message: in this tool’s braces mode that spelling is literal text, not an argument.
- Comparing language-specific plural category names as if every language needed the English set.
- Using dotted paths for keys that actually contain dots, slashes or tildes instead of reading the escaped JSON Pointer.
- Enabling index comparison for arrays whose elements have been reordered; the checker does not match elements by identity.
- Treating a report with valid: true as error-free, or a filtered screen as the entire exported report.
- Sharing an issue report without reviewing private key paths and argument names; translation values are omitted, but names can still identify a project.
Limits and notes
- Only strict JSON with object roots is accepted: no JSONC comments, trailing commas, YAML, JavaScript modules or automatic repair. Duplicate decoded keys and unpaired Unicode surrogates are rejected, including at nested paths. Object-key order is ignored; names are case-sensitive and are not Unicode-normalized. JSON Pointer escapes ~ as ~0 and / as ~1, so dotted keys are not confused with nesting; __proto__ and constructor remain ordinary data keys. Arrays are rejected by default or explicitly compared by zero-based index. Empty objects, empty arrays and null retain distinct types.
- Plain mode ignores all message grammar. Braces mode recognizes {name} or positional {0}, with ASCII identifiers [A-Za-z_][A-Za-z0-9_]* or digits only; {{ and }} are literal brace escapes. Malformed single braces are errors. Argument sets are compared, so reordering or repeating the same argument is allowed. Values, translation wording and repeated-argument counts are not diffed.
- ICU-flat supports simple arguments, one-level plural and selectordinal with named categories or nonnegative integer selectors such as =0. Branches may contain simple arguments and # text. Nested selectors, select, formatters such as number/date, offset, repeated plural definitions for one argument, and ICU apostrophe escaping are unsupported and diagnosed. Each plural requires other. Supported syntax is inspected without formatting or evaluating any message; this is not full ICU MessageFormat compatibility. Inside a plural branch, # counts as a use of its controlling argument, equivalent to {count} for a plural controlled by count; outside a plural it is literal text. In ICU mode, numeric argument identifiers are canonical integers 0–32767, with no leading zeros; this includes plural-controller names.
- Plural categories come from the current runtime’s Intl.PluralRules for each supplied locale and cardinal/ordinal type. Missing optional categories and categories unreachable for that locale are warnings, not a demand for identical categories across languages. Exact-selector differences are also warnings. A target branch compares arguments with the same reference selector, or with reference other when no selector matches. Fallback and coverage checks are static approximations; they do not establish linguistic correctness or runtime rendering behavior. The union of arguments across all plural branches is also compared, so one lost argument can produce both plural-wide and branch-level errors. Exact =N branches may spell their fixed number, so only their controlling argument is excluded from branch and union placeholder comparisons; other names remain checked.
- Each input is limited to 262,144 UTF-8 bytes, depth 32 and 10,000 JSON nodes; each message has a 1,000-token argument/branch budget. At most 1,000 issues and a 1 MiB (1,048,576-byte) JSON report are retained. A truncated or oversized report cannot be exported as complete. Invalid JSON prevents comparison/export; supported JSON with message errors can still export diagnostics. Processing is local, with no uploads, network lookup, persistence or expression execution. Reports omit translation values but retain key paths and argument names, which may themselves be private. Each JSON Pointer is capped at 4,096 UTF-8 bytes and all paths together at 1 MiB; a diagnostic detail over 512 UTF-16 code units marks the report truncated and blocks export.
Frequently asked questions
Why do changed translations and numbers produce no issue?
The checker compares paths, JSON types and the selected message grammar. Two strings with different wording are expected in different locales. Numbers and booleans are only type-checked; their values are not compared or converted. Use JSON Diff when you need an actual value diff.
Does Japanese need the same plural branches as English?
No. English and Japanese can have different supported cardinal categories. Each side is checked against its own Intl.PluralRules categories, and other is required. Omitting an optional category produces a coverage warning because other can handle it; it is not automatically an invalid message.
Which placeholder mode should I choose?
Use plain for structural checks when your message dialect is unknown. Choose braces only for the documented {name}/{0} grammar with doubled literal braces. Choose ICU-flat only for the displayed subset. Framework-specific interpolation, full ICU nesting and custom formatters need that framework’s own validator.
What does a successful check or valid report actually mean?
A valid report means the JSON and options were usable, not that the report has no errors. Inspect its issues and severity. A clean result only covers this tool’s structural and grammar rules; it does not prove translation accuracy, completeness against application usage, or behavior in your production locale-data version.
- RFC 8259: JSON syntax and interoperability
- RFC 6901: JSON Pointer locations
- ECMA-402: Intl.PluralRules
- Unicode ICU: MessageFormat and plural messages
- ICU MessagePattern