cyberwolfstudio/lingua
Lingua is a Laravel package for managing translations in a database. It scans your project for translation keys, provides an editable UI for adding and updating strings, and caches translations for fast runtime performance. Simplify multilingual app localization without hand-editing language files.
composer require cyberwolfstudio/lingua. The service provider is auto-discovered on Laravel 5.5+; no manual registration needed.php artisan vendor:publish --tag=lingua-config
Open config/lingua.php. It defines the source (lang/ / resources/lang/), the target (a JS/JSON file under resources/js or public/), and the output format (global object vs ESM export). Almost every customization you'll ever need lives here.php artisan lingua:generate
Inspect the emitted file before wiring it into your frontend — this tells you exactly what shape (window.Lingua = {...}, export default {...}, per-locale files, etc.) your JS code needs to consume.import { createI18n } from 'vue-i18n';
import messages from './lang/generated.js'; // the Lingua output
const i18n = createI18n({
locale: document.documentElement.lang || 'en',
fallbackLocale: 'en',
messages,
});
For React + i18next it's the same idea — feed the generated object into i18next.init({ resources }).Look first at config/lingua.php and the single generated artifact; if those two are right, the rest of the package is just a file-copy step.
Wire generation into the build, not into your fingers. The package is a code generator; treat it like ziggy:generate or ide-helper. The pattern that survives in real projects:
// package.json
{
"scripts": {
"predev": "php artisan lingua:generate",
"dev": "vite",
"prebuild": "php artisan lingua:generate",
"build": "vite build"
}
}
Or, if you're already on Vite, use spatie/laravel-vite-plugin's refresh paths plus a small custom plugin that re-runs artisan lingua:generate when files under lang/ change — otherwise HMR will not pick up translation edits and you'll be reloading manually all day.
Per-locale lazy loading. If the config supports splitting output by locale (most versions of this pattern do — check for a per_locale / files option), generate lang/en.js, lang/de.js, and dynamic-import the file matching the current locale:
const load = (locale) => import(`./lang/${locale}.js`);
const { default: messages } = await load(currentLocale);
This keeps the initial bundle small and matches Laravel's own app()->getLocale() semantics — pass the current locale down from Blade:
<script>window.__LOCALE__ = "{{ app()->getLocale() }}";</script>
@vite('resources/js/app.js')
Blade-first vs SPA. For a server-rendered app with pockets of JS, generate the global-object format and skip a JS build entirely — a <script src="/js/lang.js"> in app.blade.php is often all you need. For a full SPA, generate ESM and import it.
CI drift check. Since the generated file is derived state, the durable pattern is:
php artisan lingua:generate
git diff --exit-code resources/js/lang/
Run this in CI to fail when a developer edits lang/*.php but forgets to regenerate. Pair it with a .gitignore rule if and only if you also add the generation step to every environment's build (Docker build stages especially — a cached image without translations is a nasty bug).
Testing. In tests/TestCase.php or a trait, run the command in setUp before booting your JS-side fixtures, or point lingua.source at a fixture directory so tests are hermetic and fast.
lang/de.json but not in the JS bundle because someone never re-ran the command. Make generation part of the build, not a documented manual step.lang/*.php arrays are naturally nested. Vue-i18n and i18next both accept nested objects — but keys containing literal dots (e.g., 'user.name' as a flat key) will be split into a path. Prefer dot-notation in JS templates or flatten consistently; test one file with real nesting before trusting it across the whole app.lang/vendor/* and lang/{locale}/passwords.php/validation.php are frequently missed if your source glob doesn't include them — validation messages are usually the first thing a QA tester notices is untranslated.{}, or @ can break a generated .js file that uses raw string interpolation. If the package offers a JSON output mode, prefer it over a hand-rolled JS wrapper.public/ will be served but not hashed or cache-busted; files written to resources/js/ must sit inside Vite's root. Pick deliberately, and update vite.config.js build.rollupOptions.input if you emit a separate entry.fallbackLocale. Laravel's fallback logic is server-side; vue-i18n/i18next fallback is separate. Set it explicitly or users on a partially translated locale see raw keys.config/lingua.php, run php artisan config:clear before regenerating — cached config will silently ignore your edits in dev.laravel-vue-i18n and laravel-lang/publisher cover the same ground with larger communities. Keeping the generated artifact as a plain JS module means swapping generators is a one-line change in your build script.How can I help you explore Laravel packages today?