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

Js Localization Laravel Package

fixhub/js-localization

Bridges Laravel's localization system and your JavaScript front end, exposing PHP language files as JS-accessible translations so both sides share the same strings. Keeps messages in sync across locales and avoids duplicating text between server and client.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require fixhub/js-localization

The service provider is auto-discovered on Laravel 5.5+. On older versions add it manually to config/app.php:

'providers' => [
    // ...
    Fixhub\JsLocalization\JsLocalizationServiceProvider::class,
],

Publish the config first — this is the file you should read before anything else

php artisan vendor:publish --provider="Fixhub\JsLocalization\JsLocalizationServiceProvider"

Open config/js-localization.php. It tells you which lang paths are scanned, which key group is exported, and where the generated file lands. Every "it's not working" issue traces back to this file.

First use case: inline translations on a single page

<script>
    window.Lang = {!! Lang::js() !!};
</script>

<script>
    console.log(Lang.get('messages.welcome'));
</script>

Or generate a static bundle for the whole app

php artisan lang:js

This reads your lang/ (or resources/lang/) directory and writes a JS file — typically public/js/messages.js — which you then <script src> in your layout. Use the static approach if you have more than a handful of strings; use the inline approach for quick one-offs or per-page overrides.

Verify it worked before writing any JS against it

php artisan lang:js && head -c 500 public/js/messages.js

If that file is empty or missing, stop and fix the config; no amount of JS debugging will help.

Implementation Patterns

1. Static bundle — the default production pattern

The most common setup is a deploy-time artifact:

# In your deploy script, after composer install / config cache
php artisan lang:js
{{-- resources/views/layouts/app.blade.php --}}
<script src="{{ asset('js/messages.js') }}?v={{ filemtime(public_path('js/messages.js')) }}"></script>

Add php artisan lang:js to your deploy pipeline (Envoyer, Forge, GitHub Actions, Dockerfile RUN). Translations change rarely, so you don't want to regenerate on every request.

2. Inline injection — for dynamic locales or small apps

<script>
    window.Lang = {!! Lang::js() !!};
</script>

Good for SPAs bootstrapped by Blade, or when the locale is resolved per-request from the session/user record and you don't want to ship 20 locale bundles.

3. Per-locale bundles with runtime switching

Generate one file per locale and load on demand:

php artisan lang:js --locale=fr
<script>
    window.__locale = '{{ app()->getLocale() }}';
</script>
<script src="{{ asset('js/messages.' . app()->getLocale() . '.js') }}"></script>

For a locale switcher without a full reload, fetch the bundle and merge:

async function switchLocale(locale) {
    const res = await fetch(`/js/messages.${locale}.js`);
    const text = await res.text();
    // Bundle assigns window.Lang, so eval or re-inject via a <script> tag
    const s = document.createElement('script');
    s.textContent = text;
    document.head.appendChild(s);
}

Re-injecting a <script> tag is cleaner than eval and keeps CSP simpler than unsafe-eval.

4. Exposing translations to Vue / React / Alpine

Treat window.Lang.get() as the single boundary between Blade and your components:

// resources/js/i18n.js
export const t = (key, replacements = {}) => {
    if (!window.Lang) return key;
    return window.Lang.get(key, replacements);
};
<template><button>{{ t('messages.save') }}</button></template>

This keeps the package out of your component tree entirely — the bundle is loaded once by Blade, components just read the global.

5. Placeholder replacement

Laravel's :name placeholders work in JS if you pass replacements:

// lang/en/messages.php
return ['welcome' => 'Welcome, :name!'];
Lang.get('messages.welcome', { name: 'Ada' }); // "Welcome, Ada!"

Prefer this over string concatenation — it survives RTL, word order changes, and pluralization quirks in other languages.

6. Integration with a locale-aware middleware

If you resolve locale from Accept-Language or a user setting, do it in middleware that runs before the view renders, so Lang::js() and the --locale flag pick up the right value:

public function handle($request, Closure $next)
{
    app()->setLocale($request->user()?->locale ?? $request->getPreferredLanguage(['en', 'fr', 'de']));
    return $next($request);
}

Gotchas and Tips

This package is effectively unmaintained

Zero stars, zero dependents, and a low opportunity score. Before committing, verify:

  • Does it support your Laravel version? The Lang::js() API and config shape are inherited from mariuzzo/laravel-js-localization, which stopped seeing active development years ago.
  • If it breaks on Laravel 11/12, plan to fork it or replace the one method you actually use (reading lang/ and json_encode-ing it) with ~30 lines of your own code.

For most apps the whole package is: walk the lang directory, json_encode it, write a file. Don't take a dependency you can't maintain.

Escaping — the one thing that will bite you in production

{!! Lang::js() !!} is an unescaped output. If a translator puts </script> in a string, you get an XSS vector. Mitigations:

<script>
    window.Lang = {!! json_encode(Lang::js()) !!}; // still unescaped output
</script>

Better, use the JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT flags or the static file approach, which avoids the inline context entirely. Verify with:

grep -R '</script' lang/

Dotted keys vs. nested keys

Lang.get('messages.welcome') assumes the bundle is nested. If your config flattens keys to "messages.welcome": "...", the same call works — but passing an already-dotted key into a nested lookup with Lang.get('messages') returns the whole array. Pick one access convention and stick to it in i18n.js.

Missing keys return the key, not an error

Lang.get('messages.typo') returns "messages.typo" — visible on screen but silent in the console. In dev, add a check:

const originalGet = Lang.get;
Lang.get = (key, ...rest) => {
    const val = originalGet(key, ...rest);
    if (val === key) console.warn('[i18n] missing:', key);
    return val;
};

Config cache

php artisan config:cache bakes config/js-localization.php in. If you edit lang files and regenerate, also php artisan config:clear when debugging, or you'll chase a stale config.

Browser cache on the generated file

The generated JS file is a static asset — browsers will serve a stale copy after deploy. Append ?v=filemtime(...) or ?v={{ md5_file(...) }} to the <script src> URL. This is the single most common "translations didn't update" report.

Placeholder syntax collisions

Laravel uses :name. Some translation files use :Name (capitalized, substituted differently). Confirm both sides use the same casing — the package does a straight replacement and won't warn on a miss.

Plurals and genders are not handled

The package exports strings; it does not resolve | plural forms or {0}, {1} ICU syntax. If your lang/ files use trans_choice-style plurals:

'apples' => 'one apple|:count apples',

…you'll get the raw | string in JS. Either post-process the bundle in i18n.js, or export separate apples_one / apples_other keys. Don't assume Laravel's pluralization makes it across the bridge.

Large bundles

All lang files × all groups in one file gets heavy for large apps. Scope it in config (single group, or --locale) rather than shipping every translation to every user.

Extension point: wrap it

If you need per-page bundles, key filtering, or a translated-and-flattened format for a specific frontend library, wrap the package behind your own helper and a small Blade directive rather than calling Lang::js() scattered across views:

Blade::directive('translations', fn () => "<?php echo app('translator.js')->render(); ?>");

Then swapping the package out later is a one-file change.

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.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core