Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Lang Sync Inertia Laravel Package

erag/laravel-lang-sync-inertia

Syncs Laravel localization files with Inertia.js so frontend and backend share one translation source. Expose PHP lang strings as Inertia props, keep Vue/React text aligned with backend locales, and avoid duplicating translation management across your stack.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish

composer require erag/laravel-lang-sync-inertia
php artisan vendor:publish --tag="lang-sync-inertia-config"

Read config/lang-sync-inertia.php immediately after publishing — this is the single most important file. It determines which locale directories are scanned, which loader (json vs php), the generated output path, and the shared Inertia prop name. Everything else in the package is driven from here.

2. Confirm your language file layout

The package assumes Laravel's standard lang/{locale} structure. Decide early whether you are translating with flat JSON (lang/en.json) or nested PHP arrays (lang/en/messages.php). The two produce different frontend shapes, and mixing them in one project is the most common source of "why is this key undefined" confusion. Pick one and enforce it.

3. Generate the frontend bundle

php artisan lang:sync

This scans your lang directory and emits the translation payload the frontend consumes. Run it once now and inspect the output file — seeing the generated shape (flat dot-notation keys vs. nested objects) tells you exactly how to call translations from Vue/React/Svelte.

4. Wire it into Inertia

The package hooks into the shared props layer. If it ships a middleware, register it; if it exposes a shareable class, add it to app/Http/Middleware/HandleInertiaRequests.php:

public function share(Request $request): array
{
    return [
        ...parent::share($request),
        'translations' => fn () => lang_sync()->forLocale(app()->getLocale()),
        'locale'       => app()->getLocale(),
    ];
}

Keep it in a closure. Sharing translations eagerly on every request — including JSON API calls and file downloads — is a measurable waste once your translation set grows.

5. First real use case

A locale switcher is the canonical first integration:

// routes/web.php
Route::post('/locale/{locale}', function (string $locale) {
    abort_unless(in_array($locale, config('app.supported_locales')), 404);
    session(['locale' => $locale]);
    return back();
})->name('locale.switch');

Pair it with a middleware that reads session('locale') before app()->setLocale(), then render a dropdown in your layout that submits to that route. You now have end-to-end localization with a single generated artifact.


Implementation Patterns

Lazy shared props with per-page scoping

The default HandleInertiaRequests share sends the full translation dictionary to every page. That is fine for a 200-key app and painful for a 5,000-key one. Two mitigations:

  • Namespaced groups. Keep translations in lang/{locale}/admin.php, lang/{locale}/shop.php, etc., and share only the groups a controller needs via a ->only(['shop', 'common'])-style filter.
  • Per-controller props. Skip the global share for heavy sections and pass translations explicitly from the controller into the page props, so they version with the page rather than the whole app.

Start global, then move to scoped sharing the first time your Inertia payload exceeds ~50 KB of JSON.

Dev-time auto-sync, build-time determinism

The package's auto-sync/watch mode is a developer convenience — it regenerates the bundle when you edit a lang file so HMR picks up changes without restarting Vite. Treat it as dev only. In CI and production, generate the bundle as an explicit build step before vite build:

"scripts": {
  "build": "php artisan lang:sync --force && vite build"
}

This makes the translation artifact a build input rather than a runtime side effect, which is what you want for cacheable, reproducible deploys.

Locale resolution order

The cleanest pattern is a single middleware that resolves locale in a fixed priority order and nothing else touches setLocale:

  1. Explicit route parameter (for localized URLs like /de/pricing)
  2. Session value (user picked it in the switcher)
  3. Accept-Language header negotiation
  4. config('app.locale') fallback

Consolidating this prevents the classic bug where one controller sets the locale and a queued job or an Inertia partial reload sees a different one.

Keep the switcher a POST, not a link

Persisting locale via GET /locale/de invites crawlers and prefetchers to flip users' languages. A POST + back() keeps it a user action and preserves the current page, which is what users expect.

Frontend helper wrapping

Wrap the injected translations in a tiny composable/helper rather than reaching into usePage().props.translations at each call site:

// resources/js/lang.js
import { usePage } from '@inertiajs/vue3'

export function t(key, replacements = {}) {
  const dict = usePage().props.translations ?? {}
  let value = dict[key] ?? key
  for (const [k, v] of Object.entries(replacements)) {
    value = value.replaceAll(`:${k}`, v)
  }
  return value
}

The indirection pays off the first time you need to add fallback-to-key, pluralization, or a test-mode override.

Server-side validation messages

A frequently missed integration: Laravel's validation errors arrive in errors already translated server-side, so form errors are localized for free — but only if your locale middleware runs before validation. Verify it does. Client-side validation messages, by contrast, must come from the shared dictionary. Don't duplicate the strings; keep client-side copy minimal.


Gotchas and Tips

Payload bloat is the #1 killer. The default full-dictionary share is convenient and quietly expensive. Measure JSON.stringify(usePage().props).length on a real page before you ship, and keep an eye on it as the app grows. Inertia partial reloads only help if you're already scoping props.

Generated artifacts and .gitignore. Decide explicitly whether the synced bundle is committed or built. Committed artifacts drift silently; generated-only artifacts break local onboarding if the file isn't produced by a post-install script. The safest middle ground: gitignore it, and add php artisan lang:sync to both composer install scripts (post-autoload-dump) and the CI build.

SSR breaks here first. If you enable Inertia SSR, translations must be present in the SSR request payload. Since they arrive via shared props this usually works — but any code that reads from window, localStorage, or a browser-only locale cookie will throw during SSR. Pass the locale through props, never read it client-side.

Cache invalidation after deploy. If you cache the generated bundle (config cache, OPcache, or a CDN), a stale bundle means users see last week's copy after you fix a translation. Bust it on deploy — tie the bundle's filename or a version prop to a build hash, or simply clear the relevant cache in your deploy script.

Locale and session middleware ordering. Session-backed locale switching only works if StartSession runs before whatever sets the locale. Registering the locale middleware at the top of web produces the confusing "switcher works on refresh but not after redirect" behavior. Put it after the session group.

PHP/JSON shape mismatch. Nested PHP array files produce nested JS objects; flat JSON files produce flat objects with dot keys. If t('auth.failed') returns the raw key for one file and the translation for another, that mismatch is why. Normalize on the frontend helper, or standardize on one file format.

Key naming drift is silent. There is no compile-time link between a translation key used in a template and one defined in lang/. Add a CI check that greps for missing keys, or at minimum log misses in non-production by overriding the helper's fallback branch. Untranslated strings reaching production are the most common regression in localized apps.

Missing-key fallback semantics. Confirm whether the package falls back to config('app.fallback_locale'), to the key itself, or to an empty string. Each produces very different visible behavior, and the "returns the key" case can look like a sync failure when it's actually a typo in the template.

Pluralization is not uniform. Laravel's trans_choice and the |-separated plural forms don't map cleanly onto Intl.PluralRules, and languages with more than two plural categories (Polish, Arabic, Russian) will be wrong if you naively split on |. For anything beyond English/French/Spanish, use the package's generated forms or call back to the server; don't hand-roll count > 1 ? ... : ....

Docker/WSL watch mode. Filesystem event watchers inside containers frequently miss changes on bind-mounted volumes (inotify limits, especially on Windows/macOS hosts). If auto-sync "randomly stops working," switch the watcher to polling or just re-run lang:sync manually — don't burn an afternoon on it.

Escape untranslated interpolations. If you support :attribute-style placeholders or HTML in translations, never render them with v-html/dangerouslySetInnerHTML unless the source file is trusted and reviewed. Prefer splitting the string into parts and composing in the template.

Maturity check before betting on it. The package is young and low-adoption (66 stars, no known dependents). Pin the version in composer.json, read the source of the sync command before relying on its output format, and keep the generated bundle's shape isolated behind your own t() helper. That way, if you later swap the package for a hand-rolled lang:export artisan command, only one file changes.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib