Free tool

Convert .xcstrings to JSON

A String Catalog is JSON, but not JSON any i18n library can read. This flattens it into the shape i18next, next-intl and react-intl expect.

Runs in your browser. No account, no upload, nothing leaves your machine.

.xcstringsJSON

Runs in your browser. The file is never uploaded.

Why a String Catalog is not usable JSON

Opening a .xcstrings in a text editor shows JSON, which makes it look like a two-minute job. It is not: every string sits under strings.KEY.localizations.LANG.stringUnit.value, plurals sit one level deeper again under variations.plural, and the source language is frequently absent because Xcode uses the key as the English text. This tool walks all three cases and gives you a flat or nested object with the actual strings in it, the shape a web frontend or a React Native bundle can load.

What survives

Converting is easy. Not breaking the file is the hard part.

Every converter that treats a language file as plain text will eventually ship you a crash. These are the three places it happens.

Placeholders stay placeholders
%@, %lld, %1$s, {name} and {{count}} come out exactly as they went in. A placeholder that got escaped, renumbered or translated is a crash on the device, not a typo.
Plural forms stay separate
Xcode variations, the Android plurals element and gettext msgstr[n] all mean the same thing and all spell it differently. They map onto each other here instead of collapsing into one string. Polish has four forms; all four survive.
Keys keep their shape
Nesting, dotted paths, arrays and translator comments carry across wherever the target format has somewhere to put them. Where it does not, the section below says so.

What does not come across

Written down here, because the alternative is finding out in a broken build.

Device variations and substitutions do not survive: JSON has no place to put them. Plurals are kept as key.plural.one and key.plural.other rather than collapsed, so you can map them onto whatever your library uses. extractionState and the per-string translation state are dropped, because they describe Xcode's workflow, not the text.

How it works

  1. Step 1

    Drop the file in

    Drag your .xcstrings onto the left box, pick it with the file button, or paste the text. Nothing is uploaded. The conversion runs in this tab.

  2. Step 2

    Read the result

    The JSON appears as you type. The counter under the box says how many keys came through, so a file that lost half its content is visible immediately.

  3. Step 3

    Copy or download

    Take the text to the clipboard, or download it with the right extension. Swap the arrow to go the other way.

One language file in. 64 languages out.

Converting a format is the part you can do by hand. Translating 3,627 keys into 64 languages without breaking a single placeholder is the part you cannot. On the next release you only pay for the strings that changed.

€19 per month incl. VAT, cancellable monthly.

Questions

Nested or flat keys?
Both. There is a checkbox under the output. Nested is the default because i18next, next-intl and vue-i18n all expect nested objects. Flat keeps settings.title as a literal key, which is what react-intl and most flat-file setups want.
My catalog produced fewer keys than I expected.
Check the counter under the output box. It says how many keys came through. If a string has no localization for the source language and the key is not the text itself, there is nothing to read. Strings marked as stale in Xcode still convert; the tool does not filter on extractionState.
Can I convert JSON back into a String Catalog?
Yes, press the swap arrow. Keys are written sorted alphabetically, which is how Xcode writes them, so the result stays diff-friendly in git.
Does it work with .stringsdict too?
Not directly. .stringsdict was the plural format before String Catalogs; if you still have one, Xcode itself will migrate it into a .xcstrings, and this tool takes it from there.