JSON i18n translator that runs in your browser
Drop a JSON file in below, pick a target language, download the translated file. The JSON file stays on your device.
How it works
Drop a JSON file on the page, click to choose one, or paste the contents straight in. The parser reads the file in your browser and walks every string leaf (top-level and nested), then tells you how many entries still need translation. Pick a target language and translation starts immediately. The translated file is reassembled from the same parsed structure so nested objects, key order, and non-string values (numbers, booleans, arrays) all survive the round trip.
Quality Warning
Please don’t rely on the quality of these translations to ship a finalized product. This is just a starting point. It’s machine translation and there are no guarantees about its quality.
What we do not do
The file itself never leaves your device. We do not see what you are translating, we do not store any of the strings, and there is nothing about your file in our logs. The privacy wedge in DropFormat is structural: the JSON parser, the Google Translate caller, and the file writer all run inside your browser tab.
What does leave your device: the individual strings go to translate.googleapis.com the same way they would if you pasted them into translate.google.com yourself. We are not in the middle. If your file contains internal product strings you do not want a third party to see, do not translate it here; use an offline tool.
What it supports
JSON is the de facto i18n format for JavaScript and TypeScript apps. The most common shapes all work: i18next nested-key bundles, react-intl flat-dotted extracts, vue-i18n nested catalogs, and ad-hoc “key to string” maps any team has assembled. The parser walks every leaf string and treats it as a translatable entry; numbers, booleans, arrays, and null values are preserved verbatim and never sent for translation.
Nested objects are recursed. A bundle like {"common": {"save": "Save"}} produces one entry at path common.save; on download the nested structure is rebuilt under the original key, with insertion order preserved.
Flat dotted keys are taken as-is. A react-intl extract like {"app.title": "Demo"} stays as the single key app.title; the dot is not auto-split into a nested app object. That round-trip discipline matters: many extractors treat dotted keys as opaque identifiers, and silently splitting them would break the lookup at runtime.
ICU MessageFormat plural values like {count, plural, =0 {No items} other {# items}} are first-class. The parser detects pure ICU plural patterns and fans each case out into its own row in the review pane. Translate each case independently; on download the cases are re-assembled into one ICU pattern under the original key. The plural variable name and the case labels (=0, one, other, etc.) are preserved verbatim.
i18next suffix-style plurals ("item_one": "1 item", "item_other": "{{count}} items") are translated as independent flat siblings. Each suffix variant gets its own row; there is no implicit cross-key grouping. That keeps the output predictable for the wide variety of suffix conventions in the wild (v3 _plural, v21 _one/_other, full CLDR _zero through _other).
Placeholders like {name}, {{name}}, %s, and {0} pass through the translator unchanged. The protection layer that masks placeholders before translation and restores them after is the same one used for the PO, XLIFF, and ARB translators.
Not yet: react-intl {defaultMessage, description} object wrappers (the wrapper is preserved but its defaultMessage is not extracted as a translatable entry in v1), array-as-plural-set patterns from older i18next versions, and JSON Pointer / $ref indirection.
Flutter ARB files and Apple String Catalog (.xcstrings) files are also JSON-shaped but have their own translators that handle their richer metadata. The classifier here redirects ARB-shaped input to /translate/arb/ and xcstrings-shaped input to /translate/xcstrings/ rather than translating them as generic JSON, so you always end up with a file that round-trips through the right tool.
What we tolerate, and what we reject
Some malformed files we repair on the way in. Others we refuse to load so we do not make things worse. This list is the parser’s actual behaviour, generated from the format module itself, not aspirational copy.
Repaired automatically:
- UTF-8 byte order mark (BOM) at the start of the file
- Windows (CRLF) or mixed line endings
- Trailing JSON commas before } or ]
Refused with an explanation:
- Empty file. Add at least one key/value pair and re-upload.
- Does not parse as JSON. Fix the JSON syntax so the file parses, then re-upload.
- Top-level value is not a JSON object (e.g. an array). Wrap the entries in a top-level object keyed by string ids.
- No translatable string values found. Confirm at least one leaf value is a string.
- Looks like ARB, .xcstrings, or another i18n JSON variant. Use the /translate/ hub to pick the format-specific translator.
Common questions
Why is the translation in some cases worse than the Google Translate website? The free endpoint we use is the same one the Google Translate website uses, but the API does not always know about the context the website’s interface provides. For names, branded terms, or jargon, expect to override in the review pane.
What if I am translating something Google’s endpoint will not accept? The free endpoint occasionally returns the source unchanged for content it would rather not translate. Those entries show “kept original” in the review pane so you can hand-translate just those.
Does it cache anything? No. Each run starts fresh.
Will the output be valid JSON? Yes. The compiler serializes through JSON.stringify with 2-space indentation, so the file is always parseable. It will load cleanly in i18next, react-intl, vue-i18n, and any JSON-aware tool.
What happens to non-string values? Numbers, booleans, arrays, and null values pass through untouched. Only string leaves are sent for translation. That keeps mixed bundles (config + i18n, or bundles with version stamps and feature flags) safe to drop in.
What about my nested key order? Top-level and nested insertion order are both preserved. The output keys appear in the same order as the input. Reviewers who eyeball diffs by file order see only translation changes, not key reshuffles.
Why this exists
Most i18n libraries expect you to either pay for a managed translation service or run a CLI against your local files. This tool sits in between: free, browser-based, no upload, designed for the “I just need a fast first pass on this JSON bundle” workflow. Drop the .json, pick the language, get back a translated file in the same shape.
What is JSON i18n?
JSON i18n is the umbrella term for any JSON file used to hold a UI’s translatable strings. Unlike Flutter ARB or Apple’s .xcstrings catalog, generic JSON i18n has no standard metadata schema: each library picks its own conventions. i18next (the most common library for React, Next.js, Remix, and many Node servers) uses nested-key bundles by default, with suffix-style plurals (_one, _other). react-intl (FormatJS) uses flat-dotted keys and ICU MessageFormat for plurals and selects. vue-i18n uses nested-key bundles and ICU MessageFormat. Many internal apps roll their own flat key/value JSON without picking a library. This translator handles all of those shapes by being permissive about structure and explicit about what it will and will not transform on round-trip.