PHPackages                             goodcat/laravel-l10n - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Localization &amp; i18n](/categories/localization)
4. /
5. goodcat/laravel-l10n

ActiveLibrary[Localization &amp; i18n](/categories/localization)

goodcat/laravel-l10n
====================

An opinionated Laravel package for app localization

v0.5.1(3w ago)1489[2 issues](https://github.com/goodcat-dev/laravel-l10n/issues)MITPHPPHP ^8.2CI passing

Since Aug 30Pushed 2w agoCompare

[ Source](https://github.com/goodcat-dev/laravel-l10n)[ Packagist](https://packagist.org/packages/goodcat/laravel-l10n)[ RSS](/packages/goodcat-laravel-l10n/feed)WikiDiscussions main Synced 1w ago

READMEChangelog (2)Dependencies (46)Versions (19)Used By (0)

Laravel L10n
============

[](#laravel-l10n)

[![Latest Version on Packagist](https://camo.githubusercontent.com/7c3cf72f9a15000df3ccf506971ffd316c3e8f6979c9718f7fd68234292293b0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f676f6f646361742f6c61726176656c2d6c31306e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/goodcat/laravel-l10n)[![GitHub Tests Action Status](https://camo.githubusercontent.com/16e8d64eb2499e6efab490c31babfe4df99e502d3c98cde4896c79a02b3c2353/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f676f6f646361742d6465762f6c61726176656c2d6c31306e2f746573742e796d6c3f6272616e63683d6d61696e266c6162656c3d74657374267374796c653d666c61742d737175617265)](https://github.com/goodcat-dev/laravel-l10n/actions?query=workflow%3Atest+branch%3Amain)

An opinionated Laravel package for app localization.

Table of Contents
-----------------

[](#table-of-contents)

- [Quickstart](#quickstart)
- [Localized Routes](#localized-routes)
- [URL Generation](#url-generation)
- [Route Caching](#route-caching)
- [Locale Preference](#locale-preference)
- [Helpers](#helpers)
- [Components](#components)
- [Localized Views](#localized-views)
- [JavaScript URL Generation](#javascript-url-generation)

Quickstart
----------

[](#quickstart)

Get started with `laravel-l10n` in three steps.

1. Download the package via Composer. ```
    composer require goodcat/laravel-l10n
    ```
2. Add the locale middlewares to your `bootstrap/app.php` file. ```
    return Application::configure(basePath: dirname(__DIR__))
        ->withMiddleware(function (Middleware $middleware): void {
            $middleware->web([
                \Goodcat\L10n\Middleware\SetLocale::class,
                \Goodcat\L10n\Middleware\SetPreferredLocale::class,
            ]);
        });
    ```
3. Define localized routes using the `lang()` method. ```
    Route::get('/example', Controller::class)
        ->lang(['fr', 'de', 'it', 'es']);
    ```

That's it. You're all set to start using `laravel-l10n`.

Localized Routes
----------------

[](#localized-routes)

### Defining Localized Routes

[](#defining-localized-routes)

Use the `lang()` method to define which locales a route should support:

```
Route::get('/example', Controller::class)
    ->lang(['es', 'fr', 'it']);
```

This will generate:

- `/example` (fallback locale)
- `/es/ejemplo` (Spanish, translated via language file)
- `/fr/example` (French, no translation defined)
- `/it/example` (Italian, no translation defined)

The fallback locale here is Laravel's own `fallback_locale` (`APP_FALLBACK_LOCALE` in `.env`), it must match the language your routes and content are actually written in.

Listing the fallback locale in `lang()` is harmless: the canonical route already serves it, so no extra route is registered.

#### Route groups

[](#route-groups)

To avoid repetitive language definitions on every single route, you can use `Route::lang()->group()`:

```
Route::lang(['es', 'it'])->group(function () {
    Route::get('/example', fn () => 'Hello, World!');
    Route::get('/another', fn () => 'Another route');
});
```

All routes inside the group will inherit the locale definitions. A route can also extend the group's locales with its own `lang()` call — the locales are merged:

```
Route::lang(['es', 'it'])->group(function () {
    Route::get('/example', fn () => 'Hello, World!'); // es, it
    Route::get('/another', fn () => 'Another route')
        ->lang(['fr']); // es, it, fr
});
```

### Route Strategy

[](#route-strategy)

The `route_strategy` option in `config/l10n.php` controls how locale prefixes are applied:

- `prefix_except_default` (default) keeps the fallback locale unprefixed (e.g. `/example`, `/es/ejemplo`).
- `prefix` prefixes every locale and does not register an unprefixed route (e.g. `/en/example`, `/es/ejemplo`).
- `no_prefix` uses translated URIs without locale prefixes (e.g. `/example`, `/ejemplo`).

With `prefix`, the canonical route itself is prefixed with the fallback locale instead of staying unprefixed. This changes the URL, not the rule from [Defining Localized Routes](#defining-localized-routes): the fallback locale listed in `lang()` is still skipped the same way, so you never end up with a duplicate route for it.

Note

`config/l10n.php` is created by publishing the package config: `php artisan vendor:publish --tag=l10n-config`.

### Translating Route URIs

[](#translating-route-uris)

Manage route translations in dedicated language files. The expected file structure is as follows:

```
/lang
├── /es
│   └── routes.php
├── /fr
│   └── routes.php
├── /it
│   └── routes.php
```

Inside your `routes.php` file, map the original route URI to a translated slug:

```
// lang/es/routes.php
return [
    'example' => 'ejemplo',
];
```

If no translation is provided for a given locale, the original URI is used as-is.

Warning

With the `no_prefix` strategy, a translation that shares its domain and URI with the canonical route or an earlier translation is skipped and falls back to the canonical route.

Note

The key should be the route URI **without** the leading slash. For example, for `Route::get('/example')`, the key should be `example`.

### Domain Translations

[](#domain-translations)

If your application uses domain-based routing, you can translate domains in the same `routes.php` language files. The key is the original domain string:

```
// lang/es/routes.php
return [
    'example'     => 'ejemplo',
    'example.com' => 'es.example.com',
];
```

URL Generation
--------------

[](#url-generation)

The package automatically replaces Laravel's default URL generator with `LocalizedUrlGenerator`, ensuring that the `route()` helper generates the correct URLs for the current locale without any extra configuration.

Note

If you need to use a custom URL generator, you can override it in your `AppServiceProvider` by aliasing your own implementation to the `url` service.

### Using the `route()` and `action()` Helpers

[](#using-the-route-and-action-helpers)

Once the generator is registered, the `route()` helper will intelligently create URLs based on the current application locale.

- **For the current locale**: The helper automatically generates the correct URL based on the active language.
- **For a specific locale**: You can explicitly request a URL for a different language by passing the `lang` parameter to the `route()` helper.

```
// Assuming the current locale is 'en'
route('example'); // Returns "/example"

// To generate a URL for a different locale
route('example', ['lang' => 'fr']); // Returns "/fr/example"

// If a translation exists for 'es' in lang/es/routes.php, the translated slug is used
route('example', ['lang' => 'es']); // Returns "/es/ejemplo"
```

The `action()` helper works the same way:

```
action(Controller::class, ['lang' => 'es']); // Returns "/es/ejemplo"
```

Warning

`lang` is a reserved parameter name. The URL generator consumes it to select the locale, so it never reaches the route: a route defining its own `{lang}` parameter (e.g. `/translate/{lang}/text`) cannot be generated via `route()` or `action()`.

Route Caching
-------------

[](#route-caching)

Localized routes are fully compatible with Laravel's route caching, with no custom cache setup required. When routes are cached, the package skips route generation at runtime (the localized variants are already included in the cache).

```
php artisan route:cache
```

Locale Preference
-----------------

[](#locale-preference)

This package provides a mechanism for automatically detecting a user's preferred language.

The `SetPreferredLocale` middleware is responsible for populating the preferred locale. It does this by checking a series of configurable **preferred locale resolvers**.

By default, the package checks the following sources in order:

1. **SessionLocale**: Checks if a locale was set in the session.
2. **UserLocale**: Checks if the authenticated user has a preferred locale (the user model must implement Laravel's `Illuminate\Contracts\Translation\HasLocalePreference` interface).
3. **BrowserLocale**: Falls back to the browser's `Accept-Language` header.

### Customizing Resolvers

[](#customizing-resolvers)

You can customize the resolvers by setting the static property on the `L10n` class. Do this in the `boot()` method of a service provider, such as `AppServiceProvider`:

```
use Goodcat\L10n\L10n;
use Goodcat\L10n\Resolvers\BrowserLocale;

L10n::$preferredLocaleResolvers = [
    new BrowserLocale,
];
```

### Creating a Custom Resolver

[](#creating-a-custom-resolver)

Implement the `LocaleResolver` interface to create your own resolver:

```
use Goodcat\L10n\Resolvers\LocaleResolver;
use Illuminate\Http\Request;

class CookieLocale implements LocaleResolver
{
    public function resolve(Request $request): ?string
    {
        return $request->cookie('locale');
    }
}
```

Then add it to the resolver chain:

```
L10n::$preferredLocaleResolvers = [
    new CookieLocale,
    new SessionLocale,
    new UserLocale,
    new BrowserLocale,
];
```

Helpers
-------

[](#helpers)

This package adds several helper methods to your Laravel application.

### Application Helpers

[](#application-helpers)

```
// Get the user's preferred locale
app()->getPreferredLocale(); // Returns ?string

// Set the user's preferred locale (dispatches PreferredLocaleUpdated event)
app()->setPreferredLocale('es');

// Check if a locale is the fallback locale
app()->isFallbackLocale('en'); // Returns bool
```

### Route Helpers

[](#route-helpers)

```
// Get the locale served by a route. A route without l10n
// metadata counts as the fallback locale.
$request->route()->locale(); // Returns string
```

### Route Matching

[](#route-matching)

Use `L10n::is()` to check if the current route matches a given pattern, regardless of the locale:

```
L10n::is('dashboard');  // Matches /dashboard, /es/dashboard, /it/bacheca, etc.
L10n::is('admin.*');    // Wildcard patterns are supported, just like Route::is()
```

This is the localized equivalent of `Route::is()`. It resolves the canonical route behind any localized variant and delegates to `Route::named()`.

Components
----------

[](#components)

The package provides Blade components for common localization needs.

### Alternate Hreflang Links

[](#alternate-hreflang-links)

The package provides a Blade component that generates `` tags following [Google's guidelines for localized versions](https://developers.google.com/search/docs/specialty/international/localized-versions).

Add the component to the `` of your layout:

```

```

For a route with `es` and `it` translations, this will render:

```

```

The component includes all localized variants, the fallback locale, and an `x-default` entry pointing to the canonical route. It renders nothing for routes without translations.

### Locale Switcher

[](#locale-switcher)

The package provides a Blade component that renders a `` element for switching between available locales. When the user selects a different locale, the page reloads to the corresponding localized URL.

```

```

For a route with `es` and `it` translations, this will render:

```

    en
    es
    it

```

The current locale is automatically selected. The component renders nothing for routes without translations.

You can pass any HTML attribute to the component:

```

```

### Customizing the Templates

[](#customizing-the-templates)

To customize the HTML output of the Blade components, publish the views:

```
php artisan vendor:publish --tag=l10n-views
```

This copies the templates to `resources/views/vendor/l10n/components/`. Each template documents the available variables in a docblock at the top of the file.

Localized Views
---------------

[](#localized-views)

The application's view loader is configured to automatically search for a localized version of a view before falling back to the generic one.

When you render a view, the system follows a specific search order based on the current application locale.

- **Locale-specific path**: The application first tries to find the view within a folder that matches the current locale. For example, if the locale is set to `it`, it will look for the `example` view in `resources/views/it/example.blade.php`.
- **Generic path**: If the view is not found in the locale-specific folder, it will then fall back to the generic `resources/views/example.blade.php`.

This makes it straightforward to organize your views with a clean, language-based folder structure, like the one below.

```
/resources/views
├── example.blade.php
├── /it
│   └── example.blade.php
└── /es
    └── example.blade.php

```

The `example.blade.php` file in the root views folder can serve as your default template, while the localized versions (`it/example.blade.php`, `es/example.blade.php`) contain language-specific content or layouts.

JavaScript URL Generation
-------------------------

[](#javascript-url-generation)

This package provides helper functions for generating localized URLs in your JavaScript/TypeScript frontend. Two stubs are available: one for [Wayfinder](https://github.com/laravel/wayfinder) and one for [Ziggy](https://github.com/tightenco/ziggy). Given the following route definition:

```
Route::get('/foo/{id}', Controller::class)
    ->lang(['it', 'es'])
    ->name('foo');
```

### Wayfinder

[](#wayfinder)

If you're using Wayfinder, publish the TypeScript helper:

```
php artisan vendor:publish --tag=l10n-wayfinder
```

Note

The `route()` helper only works with named routes, actions are not supported.

This creates a `resources/js/l10n.ts` file exporting a `route(routes, args?)` helper that selects the appropriate localized route based on the current locale.

Import the `route` helper and pass Wayfinder's generated route functions:

```
import { route } from '@/l10n';
import foo from '@/routes/foo';

const esUrl = route(foo, { id: 1, lang: 'es' }).url;
```

The locale is resolved in the following order:

1. The `lang` parameter, if provided.
2. The `lang` attribute of the `` element, normalized to Laravel's locale format (`pt-BR` matches a `pt_BR` route).
3. The canonical route, when no localized route matches.

During server-side rendering, pass `lang` explicitly to select a localized route; without it, the helper falls back to the canonical route.

In the generated files the canonical route is exported under the `__canonical` key instead of the fallback locale name, while translations keep their locale keys (`it`, `es`). You only need the marker when calling the canonical route directly, without the `route()` helper: `foo.__canonical({ id: 1 })`.

### Ziggy

[](#ziggy)

If you're using Ziggy, publish the JavaScript helper:

```
php artisan vendor:publish --tag=l10n-ziggy
```

This creates a `resources/js/l10n.js` file with a `route` function.

Use `route()` as a drop-in replacement for Ziggy's `route()` function:

```
import { route } from '@/l10n';

route('foo', { id: 1, lang: 'it' });
```

The function automatically looks for a localized route by appending the locale to the route name (e.g., `foo.es`). If a localized route exists, it uses that; otherwise, it falls back to the original route name.

The locale is resolved from the `lang` parameter first, then from the HTML `lang` attribute. For regional locales, the helper tries both the original value and Laravel's underscore format, so `pt-BR` can match either `foo.pt-BR` or `foo.pt_BR`. During server-side rendering, pass `lang` explicitly to select a localized route; without it, the helper falls back to the canonical route.

All Ziggy arguments are forwarded, including an explicit configuration object as the fourth argument. Calling `route()` without a route name returns Ziggy's `Router` instance as usual.

###  Health Score

45

—

FairBetter than 91% of packages

Maintenance93

Actively maintained with recent releases

Popularity20

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity48

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 91.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~23 days

Recently: every ~15 days

Total

15

Last Release

26d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6ca068e1dd24801f83c4c4802af654bdb2db4025499aa94d0575898a4a5d5c8f?d=identicon)[OutlawPlz](/maintainers/OutlawPlz)

---

Top Contributors

[![OutlawPlz](https://avatars.githubusercontent.com/u/14197383?v=4)](https://github.com/OutlawPlz "OutlawPlz (158 commits)")[![matteo-palazzo](https://avatars.githubusercontent.com/u/83695398?v=4)](https://github.com/matteo-palazzo "matteo-palazzo (14 commits)")

---

Tags

laravellocalizationl10ngoodcat

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/goodcat-laravel-l10n/health.svg)

```
[![Health](https://phpackages.com/badges/goodcat-laravel-l10n/health.svg)](https://phpackages.com/packages/goodcat-laravel-l10n)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3345.4M354](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

79227.1M229](/packages/laravel-mcp)[laravel/cashier

Laravel Cashier provides an expressive, fluent interface to Stripe's subscription billing services.

2.6k31.8M162](/packages/laravel-cashier)[api-platform/laravel

API Platform support for Laravel

58190.1k21](/packages/api-platform-laravel)[intervention/image-laravel

Laravel Integration of Intervention Image

1589.8M226](/packages/intervention-image-laravel)[fleetbase/core-api

Core Framework and Resources for Fleetbase API

1239.7k25](/packages/fleetbase-core-api)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
