Convert JSON to CSV

Flattening JSON translations into key,value CSV rows makes them reviewable in a spreadsheet without any tooling.

Flattening a JSON tree into reviewable rows

JSON translation bundles are built for machines. They nest several levels deep, they are easy to diff and impossible to skim, and nobody outside engineering will ever open one. Converting to CSV turns that tree into a flat list of one string per row, which is the only shape non-developers reliably work with.

Nesting is encoded as dot notation in the key column, so no information is lost and the process reverses cleanly through the CSV to JSON converter.

Input — en.json

{
    "welcome": "Welcome back, :name!",
    "cart": {
        "empty": "Your cart is empty",
        "items": ":count item|:count items"
    }
}

Output — en.csv

key,value
welcome,"Welcome back, :name!"
cart.empty,"Your cart is empty"
cart.items,":count item|:count items"

Values are quoted per RFC 4180 wherever they contain commas, quotes or newlines, so the file opens correctly in Excel, Google Sheets, LibreOffice and any CAT tool that reads delimited text.

What happens to deep nesting and arrays

Front-end translation bundles nest much more aggressively than Laravel lang files, often four or five levels deep as teams mirror their component hierarchy. Flattening handles any depth, but the resulting keys get long enough to be worth thinking about.

Deep nesting flattens predictably

checkout.payment.errors.card_declined
checkout.payment.errors.insufficient_funds
checkout.shipping.options.express.label

Those keys are still perfectly usable, but they scroll off the visible width of a spreadsheet column and a translator will spend their time reading identifiers instead of copy. If the export is going to a human rather than a tool, widen the key column and add a separate context column rather than expecting anyone to parse the hierarchy in their head.

JSON arrays are the other structural case. An array has numeric indices, so it flattens with integers in the key path — onboarding.steps.0, onboarding.steps.1, and so on. That round-trips correctly, but it carries a warning for translators: the order is meaningful and the indices must not be renumbered, sorted or have rows inserted between them. A sorted spreadsheet will reorder your onboarding steps.

If a value in your JSON is a number or a boolean rather than a string, it flattens to its text representation. Converting back produces the string "true", not the boolean true. Translation files should hold only strings; anything else belongs in configuration.

The review workflow this enables

The reason to flatten is almost always that someone who is not a developer needs to read or write the strings. A few concrete situations where the spreadsheet is genuinely the better tool:

  • Native-speaker QA. A colleague scans four hundred rows in a grid in twenty minutes. Reviewing the same content as nested JSON in a pull request takes an hour and they will miss things.
  • Locale comparison. Export every locale, paste each value column side by side, and gaps in coverage become visually obvious without writing a script.
  • Agency handoff. Translation vendors quote and deliver in spreadsheets. Sending JSON invites them to open it in a text editor, which invites broken syntax.
  • Copy and tone rewrites. Marketing will edit a spreadsheet. They will not clone the repository.
  • Terminology audits. Sorting by value groups near-duplicate strings together, which is how you find the five slightly different ways your product says "Sign in".

Add columns before sending. A source column with the original language, a context column explaining where the string appears, and a notes column for the translator's questions turn a bare key-value dump into something a professional can work from. Context matters most: keys like close, post and order are verbs or nouns depending on the screen, and without that information half the guesses come back wrong.

Keeping the round trip lossless

The conversion is reversible, but only if the spreadsheet does not damage the file in between. Almost every corrupted translation import is corrupted by the spreadsheet application rather than by the converter.

Risk What it does Prevention
Excel opens as system codepage Accents become é Import via Data → From Text/CSV, choose UTF-8
Leading =, +, -, @ Value parsed as a formula Format the sheet as Text before editing
Autocorrect Straight quotes become curly Disable autocorrect; paste as plain text
Sorting the sheet Keys and values misalign Lock the key column; never sort a live sheet
European Excel Re-saves with semicolons Agree the delimiter before sending

Keep the original export. When the edited file returns, diffing the key columns immediately shows rows that were added, removed or reordered — the three failures that break an automated re-import and the three that are hardest to notice by eye.

Finally, remember that the CSV is a snapshot. The moment you ship a feature that adds keys, the spreadsheet is stale, and reconciling a returned file against a codebase that has moved on is the recurring tax of this workflow. It is entirely manageable for batch translation with a defined start and end, and it becomes tiresome the moment translation needs to be continuous.

Choosing what to export

A front-end bundle usually contains more than the strings you want translated, and exporting all of it wastes both the translator's time and your budget. Trim before converting.

  • Drop machine-facing values. Analytics event names, feature flags, route fragments and CSS class names sometimes end up in translation bundles. They are not copy and must never be translated.
  • Separate by namespace. If your bundle is split by feature, export each namespace as its own sheet. Smaller files mean fewer merge conflicts and let you send only the areas that changed.
  • Exclude already-translated framework strings. Validation and auth messages are maintained professionally in most locales by open-source projects; paying to redo them is money spent to get worse copy.
  • Flag the plural rows. A value containing pipes means nothing to a translator who has not been told the convention, and the target language may need more forms than the source supplied.
  • Filter to what actually changed. For an incremental round, diff against the last export and send only new or modified keys rather than the whole bundle again.

One practical habit makes every later round cheaper: keep each export in version control next to the bundle it came from. It costs nothing, and it means the diff that tells you what a translator changed is always one command away rather than a reconstruction from memory.

Frequently asked questions

Does the converter keep :placeholders and plural pipes intact?

Yes. Values are treated as opaque strings, so Laravel placeholders like :name or :count and pipe-separated plural forms come through unchanged.

How is nesting handled?

Nested keys are flattened to dot notation in CSV (cart.empty) and re-expanded into nested structures when converting back.

Is my data uploaded or stored?

The conversion runs on our server but nothing is persisted: files are converted in memory and the response is returned immediately. For PHP input, only string, number and array literals are parsed — code is never executed.

Is there a size limit?

Yes, 200 KB per conversion, which covers even very large lang files. If you need bulk conversion across many locales, import your files into LangSyncer and export any format.

Converting files by hand gets old fast

Import your lang files into LangSyncer once, edit and translate them in a dashboard, and export any format — or skip files entirely with live translations.

We use cookies to improve your experience and analyze site traffic. Cookie Policy

Cookie Preferences

Essential

Required for the site to work

Analytics

Help us improve the site

Marketing

Personalized ads and content