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

I18N Laravel Package

conedevelopment/i18n

Multilingual routing for Laravel: define a route once, then serve translated URIs and route names per locale. Includes middleware to detect and set the application locale from the URL, plus helpers for generating localized links and language switchers.

Deep Wiki
Context7

Getting Started

Install the package and publish its config:

composer require conedevelopment/i18n
php artisan vendor:publish --tag=i18n

This drops config/i18n.php where you control which locales, translation groups, and namespaces get exported to the front-end.

Expose the translations to your layout once. The package provides a Blade directive (typically @i18n) that injects a script definition into the page:

{{-- resources/views/layouts/app.blade.php --}}
<head>
    ...
    @i18n
</head>

In JavaScript you now have a global __() helper that mirrors Laravel's translator:

__('messages.welcome')                       // "Welcome"
__('messages.greeting', { name: 'Taylor' })  // "Hello, Taylor"

First use case: replace hard-coded English strings in your Blade/JS templates with translation keys. Start with one group (resources/lang/en/messages.php), verify __('messages.some_key') resolves in the browser console, then expand.

Where to look first:

  • config/i18n.php — output path, included/excluded groups, fallback locale.
  • The generated public asset (often public/vendor/i18n/translations.js or similar) — confirms what was actually exported.
  • The package's route/controller if you prefer loading translations on demand rather than inline.

Implementation Patterns

Inline in the Blade layout vs. separate asset

Two common approaches:

  1. Inline @i18n — simplest, no extra request, translations available before your JS bundle runs. Good for small-to-medium key sets.
  2. Generated JS file — reference public/vendor/i18n/*.js from your layout, benefit from HTTP caching and cache busting. Better when you have hundreds of keys or multiple bundles.

For inline, place @i18n before your compiled JS so __() exists when your app initializes:

@i18n
<script src="{{ mix('js/app.js') }}"></script>

Using with Vue/React

Since the helper is global, wrap it once rather than importing per-component:

// resources/js/i18n.js
export const t = (key, replacements) => window.__(key, replacements);

Then import t wherever needed. This keeps components testable (you can stub t) and avoids scattering globals.

<template>
  <h1>{{ t('dashboard.title') }}</h1>
</template>

Placeholder replacements

The JS helper supports Laravel-style :attribute replacements:

__('messages.items_in_cart', { count: cart.length })
// lang: 'messages.items_in_cart' => 'You have :count items in your cart.'

For pluralization, prefer exporting explicit keys per count from the back-end or use a small pluralization helper — don't assume the JS helper handles | plural syntax identically to PHP.

Scoping which groups are exported

Keep the payload lean by including only front-end-facing groups:

// config/i18n.php
'groups' => ['messages', 'validation', 'auth'],
// or
'exclude' => ['admin', 'emails', 'pagination'],

This is the single biggest lever on page weight. Validation is usually worth including; emails and admin are usually not.

Locale switching

When a user changes language, the translated payload must change too. Typical pattern:

// routes/web.php
Route::get('/locale/{locale}', function ($locale) {
    session(['locale' => $locale]);
    app()->setLocale($locale);
    return back();
});

Then ensure @i18n renders under the current app()->getLocale(). If you cache the inline output, key the cache by locale.

Build-time generation

If you generate a static JS file, run the generator as part of your deploy pipeline, after config caching:

php artisan config:cache
php artisan i18n:generate   # or the package command name in your version
npm run production

Treat the generated file as a build artifact: add it to .gitignore, generate per environment.

Caching translations for large apps

For a big app with dozens of groups, wrap generation in a scheduled command or a deploy hook, and serve the file with long cache headers plus a hash in the filename. Avoid regenerating on every request.

Gotchas and Tips

Stale translations in production

The most common bug: you edit a lang file, deploy, and the front-end still shows old strings. Causes:

  • config:cache was run before the lang files changed (config caching doesn’t cover lang files, but the generator may have run earlier).
  • The generated JS file was committed and the deploy skipped regeneration.
  • Browser/CDN cache. Add a version query or content hash to the asset URL.

Debug by inspecting the generated asset directly for the new key.

JSON translations vs. nested arrays

Laravel has both resources/lang/en.json (flat string keys) and resources/lang/en/*.php (nested arrays). The package exports these differently — flat JSON keys are looked up with the full string, array keys with dot notation. If __('Welcome') fails in JS but __('messages.welcome') works, check which file the key lives in. Be consistent across your app.

Missing-key behavior

When a key is absent, Laravel returns the key itself. In JS this can silently leak messages.foo into your UI. Add a dev-only assertion:

if (import.meta.env.DEV) {
  const orig = window.__;
  window.__ = (key, repl) => {
    const out = orig(key, repl);
    if (out === key) console.warn('[i18n] missing key:', key);
    return out;
  };
}

Escaping and HTML

The JS helper typically returns plain strings, not HTML. Don’t inject translation output with v-html unless you control the content — it’s a straightforward XSS vector if translations come from a user-editable source. For :attribute replacements containing user data, escape at the call site.

Placeholder syntax mismatches

__('msg.hi', { name }) works, but __('msg.hi', ['name' => ...]) (PHP array) will not. Keep JS call sites using object literals.

Config cache order

After changing config/i18n.php, always run:

php artisan config:clear
php artisan config:cache

in that order during deploy. Forgetting this means your changed groups/exclude list is ignored.

Extension points

  • Custom output path — set it in config; useful when your asset pipeline serves from a non-standard directory.
  • Custom generator/command — if you need to transform keys (e.g., flatten, add prefixes), extend the package’s generator rather than post-processing the file with a script.
  • Custom route/controller — if inline output is too heavy, expose translations via an authenticated endpoint and fetch on app boot; this is a clean place to hook up per-tenant or per-role translation scoping.

Testing

Assert that every key used in JS exists in the lang files. A cheap approach: grep your resources/js for __('...') patterns and diff against array_keys from the lang files in a Pest/PHPUnit test. Catching missing keys in CI is far cheaper than catching them in production.

Version drift

Last release is 2026-04-07, so pin the package version in composer.json and re-verify the generator command name and config keys after upgrading — these have changed across major versions in similar packages. Run the test suite and check the generated asset diff on upgrade.

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.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons