Convert Laravel PHP array to JSON

Laravel supports JSON translation files out of the box, and most JavaScript i18n libraries (vue-i18n, i18next) consume JSON directly, so converting PHP lang arrays to JSON is the most common migration step.

What actually changes when you convert

A Laravel PHP language file is executable code: it returns an array, and the framework resolves keys against it at runtime. A JSON language file is inert data parsed by json_decode(). The conversion looks like a formatting change, but it moves your translations from something PHP evaluates into something PHP reads, and that has consequences worth understanding before you commit the output to your repository.

The values themselves are the easy part. Every string survives byte for byte, including Laravel placeholders such as :name and :count, pipe-separated plural forms, HTML fragments, and any Unicode you have in there. What changes is the structure around those values and, crucially, how you will look them up in your Blade templates afterwards.

Input — lang/en/messages.php

<?php

return [
    'welcome' => 'Welcome back, :name!',
    'cart' => [
        'empty' => 'Your cart is empty',
        'items' => ':count item|:count items',
    ],
];

Output — messages.json

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

Note that the nesting is preserved rather than flattened. cart.empty does not become a literal key called "cart.empty" — it stays a nested object. That is the correct behaviour for most JavaScript i18n libraries, and it is what makes the conversion reversible without loss.

Laravel has two translation systems — pick the right one

This is the single most common source of confusion, and getting it wrong produces an app where every translated string silently renders as its own key. Laravel supports two entirely separate mechanisms, and they use JSON very differently.

The first is short keys. Files live at lang/{locale}/{file}.php and you address them by a dotted path that starts with the filename: __('messages.cart.empty'). This is the system your PHP array currently belongs to.

The second is translation strings as keys. A single file lives at lang/{locale}.json — note that it sits directly in lang/, not in a locale subdirectory — and the keys are the English source sentences themselves: __('Your cart is empty'). Laravel looks the sentence up and returns the translated version, falling back to the key itself when there is no match.

Short keys Strings as keys
File location lang/es/messages.php lang/es.json
Lookup __('messages.welcome') __('Welcome back, :name!')
Missing translation Renders the raw key Renders the English source
Nesting Supported and idiomatic Not used — keys are flat sentences

If you drop this converter's nested output into lang/es.json, Laravel will not find anything, because it expects sentence keys at the top level. Nested JSON belongs in a front-end bundle or in a tool that consumes it — not in Laravel's strings-as-keys file.

So the practical rule is: convert to JSON when the consumer is JavaScript, a translation platform, or an API. Keep PHP arrays when the consumer is Laravel itself and you are already using short keys. Converting your short-key files to JSON purely to "modernise" them means rewriting every __() call in your codebase, which is rarely worth it.

Feeding the output to a JavaScript i18n library

The most common legitimate reason to run this conversion is a Laravel back end sharing copy with a Vue, React, or Inertia front end. Both vue-i18n and i18next consume nested JSON directly, so the output drops straight into a locale bundle.

vue-i18n

import { createI18n } from 'vue-i18n'
import en from './locales/en.json'
import es from './locales/es.json'

const i18n = createI18n({
  legacy: false,
  locale: 'en',
  fallbackLocale: 'en',
  messages: { en, es },
})

// In a component: {{ t('cart.empty') }}

One incompatibility to plan for: Laravel's :name placeholder syntax is not what these libraries expect. vue-i18n uses {name} and i18next uses {{name}}. The converter deliberately leaves values untouched, so you will need a small transform pass if you want the same JSON to serve both runtimes.

Rewriting placeholders for vue-i18n

const toVueI18n = (obj) =>
  Object.fromEntries(
    Object.entries(obj).map(([key, value]) => [
      key,
      typeof value === 'object'
        ? toVueI18n(value)
        : value.replace(/:(\w+)/g, '{$1}'),
    ])
  )

Plural forms need attention too. Laravel's pipe syntax happens to match vue-i18n's own pipe convention for simple cases, but the two disagree the moment you use explicit ranges like {0} none|[1,19] some|[20,*] many. Test your plural strings against real numbers rather than assuming they carry over.

Where this conversion earns its keep

  • Sharing copy with a SPA. One source of truth in lang/, exported to JSON at build time so the front end never drifts from the back end.
  • Uploading to a translation platform. Almost every TMS accepts JSON; far fewer parse PHP arrays, and those that do often mangle nested structures.
  • Diffing translations in review. JSON diffs cleanly in a pull request. PHP arrays produce noisier diffs because formatting, trailing commas and array syntax all vary.
  • Feeding a static site or mobile app. React Native, Flutter and most static generators read JSON natively and have no PHP runtime at all.
  • Writing validation scripts. Checking that every locale has the same key set is a few lines against JSON; against PHP it means booting the framework or parsing code.

A build-step example: add a Composer or npm script that regenerates the front-end bundle whenever the canonical PHP files change, so the two never diverge. Because the conversion is deterministic, the generated JSON can safely be gitignored and rebuilt in CI.

Mistakes worth avoiding

  1. 1 Do not convert files containing PHP logic. If a lang file calls a function, concatenates variables, or returns anything other than literal strings and arrays, the parser will reject it. Language files should be pure data; if yours are not, fix that first.
  2. 2 Watch for numeric-looking keys. A PHP array with keys like 1, 2, 3 is a list, and JSON will faithfully represent it as an object with string keys. That is usually what you want for translations, but it will surprise code expecting an array.
  3. 3 Check your encoding. The output is UTF-8 without escaping, so accented characters and non-Latin scripts appear literally rather than as \u00e9 sequences. That is more readable and equally valid, but some older parsers insist on ASCII-escaped JSON.
  4. 4 Do not lose your comments. PHP arrays can carry // comments explaining tricky strings to translators. JSON has no comment syntax, so that context is dropped. Move it into a translator-notes field in your TMS before you convert.

Finally, treat the conversion as a migration rather than a sync. Running it once and committing the result is fine. Running it repeatedly in both directions, by hand, across a dozen locales is exactly the kind of work that quietly rots — which is the problem a managed translation layer exists to solve.

One more structural consideration: Laravel resolves short keys per file, so lang/en/messages.php and lang/en/validation.php are separate namespaces that never collide. JSON has no equivalent boundary. If you merge several PHP files into one JSON bundle, prefix each file's tree with its original filename, otherwise a title key from one file will quietly overwrite a title key from another and you will lose a string with no error anywhere.

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 arrays in Laravel PHP array map one-to-one to nested structures in JSON; nothing is flattened.

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