Free tool

Convert .po to JSON

WordPress, Drupal and Django speak gettext. Your frontend speaks JSON. This is the bridge, and it reads multi-line entries properly.

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

.poJSON

Runs in your browser. The file is never uploaded.

Why a line-based reader fails on PO

A PO entry is not one line. The normal form for any long string is msgid "" followed by however many continuation lines it takes, each a bare quoted fragment that has to be concatenated onto the field opened above it. Add msgctxt for disambiguation, msgid_plural with indexed msgstr[0] and msgstr[1] targets, and four kinds of comment that mean four different things, and a regex over lines stops being enough. This reader is a state machine over the file, so a 400-character paragraph split across nine lines arrives as one string.

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.

The Plural-Forms rule in the header is not carried across. When writing a PO the tool emits a header with charset and language but no plural rule. A rule invented for the wrong language is a worse failure than an absent one, because msgfmt will accept it and pick the wrong form at runtime. Source references (#: src/app.js:12) and flags (#, fuzzy) are read but not written; only #. developer comments become translator notes, which is what #. means.

How it works

  1. Step 1

    Drop the file in

    Drag your .po 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

How are plurals represented in the JSON?
As key.plural.one and key.plural.other. Going back to PO, those are merged into a single entry with msgid_plural and indexed msgstr[n] targets, not two separate msgid entries, which would make msgfmt refuse the file as a duplicate.
What happens to msgctxt?
The context is joined to the message id with U+0004, the same separator gettext itself uses in the compiled catalog. It round-trips: convert to JSON and back and the msgctxt line returns.
Can I use this for a WordPress theme?
Yes. Export the .po from Loco Translate or Poedit, convert it here, and you have the strings in a shape a JavaScript build can import. Going the other way gives you a .po Poedit will open.
Does it read .pot template files?
Yes. A .pot is a .po with empty msgstr values. You get the keys with empty strings, which is exactly what you want as the starting point for a new language.