Neatbo.

Structured PO catalog converter

Convert UTF-8 gettext PO and structured JSON while preserving context, plural indices, headers, comments, previous values and obsolete entries.

Browser-local processingInputPO / Structured JSONOutputStructured JSON / PO / CSVUp to 1 MiB per file · File limit: 1
  1. 1Add input
  2. 2Adjust settings
  3. 3Get your result

Tool input and files are processed in this browser without being uploaded.

Your input

Inputs are kept temporarily in this tab when switching tools. Refreshing or closing clears them; large results may need to be regenerated.

⌘ / Ctrl + Enter to run

or drag and drop it here

Files stay on this device. Your originals stay unchanged.

.po · .json

Up to 1 MiB per file · File limit: 1

    0 characters · 0 bytes
    Options

    Complete the required options first. You can keep the defaults for the rest.

    Preparing the tool…

    Before you start

    Move a translation catalog through an explicit record model without flattening its meaning. Review active and obsolete entries, edit indexed translations, and regenerate a readable PO file.

    How to use this tool

    1. Choose PO → JSON or structured JSON → PO and open one local UTF-8 catalog.
    2. Review context, plural forms, comments and obsolete state in JSON; retain all six fields when editing an entry.
    3. Download catalog.json and catalog.po, then validate them in the receiving gettext workflow.

    Supported inputs and limits

    One UTF-8 PO/JSON file or pasted source up to 1 MiB. At most 10,000 entries, 100,000 physical PO lines and 100 contiguous translation indices per entry. Generated PO is bounded to 4 MiB. Selected file takes precedence; BOM is omitted and generated PO uses LF with canonical quoted/multiline strings. JSON parsing also has a 200,000-node and depth-128 budget.

    JSON must use schema neatbo-po-v1, encoding UTF-8, entries and trailingComments. Each entry has context, msgid, plural, translations, comments and obsolete. Context null differs from an empty context. Singular translations use key 0; plural indices begin at 0 and are contiguous. Duplicate active/obsolete identities and unknown schema fields reject.

    Preserves the header entry and its translation text, translator/extracted/reference/flag/previous comment lines, obsolete records and trailing comments. Does not flatten to a msgid dictionary, preserve original wrapping/spacing, compile MO, translate or evaluate plural formulas. A declared nplurals is checked for active plural form counts only.

    Strict UTF-8 input and declared UTF-8 charset only; other charsets reject. PO uses unindented declarations and C string escapes for quotes/backslashes, common controls and UTF-8 byte octal/hex escapes. Unknown escapes, NUL, malformed quotes, missing msgstr and comments inside an unfinished entry reject. JSON/PO downloads retain complete records; the table/CSV summarize entries, not every translation.

    Worked example

    Example input

    # catalog comment
    msgid ""
    msgstr ""
    "Content-Type: text/plain; charset=UTF-8\n"
    "Plural-Forms: nplurals=2; plural=(n != 1);\n"
    
    #. extracted comment
    #: app.ts:1 other.ts:2
    #, fuzzy, c-format
    #| msgctxt "old-menu"
    #| msgid "old file"
    msgctxt "menu"
    msgid "%d file"
    msgid_plural "%d files"
    msgstr[0] "%d 个文件"
    msgstr[1] "%d 个文件"
    
    # obsolete comment
    #~| msgid "older"
    #~ msgctxt "old"
    #~ msgid "gone"
    #~ msgstr "旧"
    
    # trailing catalog comment
    
    Example options
    {"params": {"direction": "po-to-json"}}

    Example output

    {
      "schema": "neatbo-po-v1",
      "encoding": "UTF-8",
      "entries": [
        {
          "context": null,
          "plural": null,
          "translations": {
            "0": "Content-Type: text/plain; charset=UTF-8\nPlural-Forms: nplurals=2; plural=(n != 1);\n"
          },
          "comments": [
            "# catalog comment"
          ],
          "obsolete": false,
          "msgid": ""
        },
        {
          "context": "menu",
          "plural": "%d files",
          "translations": {
            "0": "%d 个文件",
            "1": "%d 个文件"
          },
          "comments": [
            "#. extracted comment",
            "#: app.ts:1 other.ts:2",
            "#, fuzzy, c-format",
            "#| msgctxt \"old-menu\"",
            "#| msgid \"old file\""
          ],
          "obsolete": false,
          "msgid": "%d file"
        },
        {
          "context": "old",
          "plural": null,
          "translations": {
            "0": "旧"
          },
          "comments": [
            "# obsolete comment",
            "#~| msgid \"older\""
          ],
          "obsolete": true,
          "msgid": "gone"
        }
      ],
      "trailingComments": [
        "# trailing catalog comment"
      ]
    }

    When something does not work

    Use the complete exported schema and repair missing or duplicate fields/indices. Convert non-UTF-8 catalogs explicitly before opening them. Fix malformed quoting or unsupported escapes; separate comments from unfinished entries. Reduce catalog size if a budget is exceeded.

    Frequently asked questions

    Can I use a flat translation JSON object?

    No. It cannot represent context, indexed plural forms and obsolete state faithfully; use the exported record schema.

    Does it evaluate Plural-Forms?

    No. The expression stays in the header. Only an explicitly declared nplurals count is checked against active plural translations.

    Will the output look byte-for-byte like the source?

    No. Field values and comment lines are preserved, but PO quoting, multiline layout, blank spacing and line endings are canonicalized.

    Documentation & further reading

    Related tools