pikbdesigns/laravel-full-translation

Laravel Full Translation — a Laravel 13 package for URL-based multilingual routing (/en/, /es/) with automatic locale detection, translated route slugs, a built-in string scanner, and Artisan commands to export and audit your app's translation completeness. No manual route duplication required.

Maintainers

Package info

github.com/pikbdesigns/laravel-full-translation

Homepage

Documentation

pkg:composer/pikbdesigns/laravel-full-translation

Transparency log

Statistics

Installs: 1

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.1.1 2026-07-20 22:58 UTC

This package is auto-updated.

Last update: 2026-07-20 23:06:08 UTC


README

URL-based multilingual support for Laravel without rewriting every route.

Add locale prefixes (/en/about, /es/about) to your Laravel app with minimal setup. The package handles locale detection (URL, session, cookie, browser Accept-Language), route registration, URL generation, and translation file management.

Table of Contents

Requirements

  • PHP 8.2+
  • Laravel 11+

Installation

composer require pikbdesigns/laravel-full-translation

The service provider and facade are auto-discovered. Publish the config:

php artisan vendor:publish --tag=translations-config

Optionally publish the views (for the language switcher component):

php artisan vendor:publish --tag=translations-views

Configuration

The published config lives at config/full-translation.php. All keys have sensible defaults.

Locale Settings

Key Default Description
default_locale 'en' Fallback locale when none is detected
supported_locales ['en', 'es', 'fr'] Locales your app supports. Can be a flat array or associative array with rich metadata
locale_mapping [] Maps URL slugs to internal locale codes (e.g., 'pt-br' => 'pt_BR')
hide_default_locale false When true, the default locale has no URL prefix (/about vs /en/about)
locales_order [] Custom order for locales in the language switcher (e.g., ['es', 'fr', 'en'])

Rich Locale Metadata

supported_locales can be a simple array of codes or an associative array with metadata:

// Simple (default)
'supported_locales' => ['en', 'es', 'fr'],

// Rich metadata
'supported_locales' => [
    'en' => ['name' => 'English', 'script' => 'Latn', 'native' => 'English', 'regional' => 'en_GB'],
    'es' => ['name' => 'Spanish', 'script' => 'Latn', 'native' => 'español', 'regional' => 'es_ES'],
    'fr' => ['name' => 'French', 'script' => 'Latn', 'native' => 'Français', 'regional' => 'fr_FR'],
],

The native name is used in the language switcher when available. The regional key maps to PHP locale codes for LC_TIME and LC_MONETARY.

Detection & Persistence

Key Default Description
use_session true Store detected locale in the session
use_cookie true Store detected locale in a cookie
cookie_name 'locale' Cookie name for locale persistence
cookie_lifetime 525600 Cookie lifetime in minutes (default: 1 year)
use_accept_language true Use browser Accept-Language header for detection

URL Behavior

Key Default Description
route_prefix '{locale}' Placeholder used in route prefixes
urls_ignored [] URL patterns to skip locale processing (e.g., ['/nova', '/nova/*'])
http_methods_ignored ['POST', 'PUT', 'PATCH', 'DELETE'] HTTP methods that skip locale processing

Scanner Settings

Key Default Description
scan_helpers [...] Function/directive names the scanner extracts strings from
scan_paths ['app', 'resources/views', 'routes'] Directories to scan for translatable strings
excluded_directories [] Directories to exclude from scanning (relative to scan_paths)
file_patterns ['*.php', '*.blade.php'] File patterns to scan (supports * and ? wildcards)
allow_newlines false Whether strings containing newlines are included in scanning

Export Settings

Key Default Description
sort_keys true Sort translation keys alphabetically in exported files
translated_sort_order 'alpha' Sort order when sort_keys is false: 'alpha', 'top', or 'bottom'
add_manual_strings true Automatically add strings from manual-strings.json to translation files on export
exclude_translation_keys true Exclude Laravel PHP translation keys from JSON export if they have translations

Middleware

Register these in your app/Http/Kernel.php (or bootstrap/app.php in Laravel 11+):

Middleware Purpose
SetLocale Detects locale from URL, session, cookie, or browser and sets app()->setLocale()
LocaleSessionRedirect Redirects GET requests to the session-stored locale's URL
LocaleCookieRedirect Redirects GET requests to the cookie-stored locale's URL
HideDefaultLocaleInUrl 302 redirects /en/about to /about when en is the default

Global middleware (bootstrap/app.php)

->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'locale.set' => \Pikbdesigns\FullTranslation\Http\Middleware\SetLocale::class,
        'locale.session' => \Pikbdesigns\FullTranslation\Http\Middleware\LocaleSessionRedirect::class,
        'locale.cookie' => \Pikbdesigns\FullTranslation\Http\Middleware\LocaleCookieRedirect::class,
        'locale.hide' => \Pikbdesigns\FullTranslation\Http\Middleware\HideDefaultLocaleInUrl::class,
    ]);
})

Recommended order

Apply SetLocale early (typically in the web middleware group), then the redirect middlewares. Use HideDefaultLocaleInUrl when hide_default_locale is true.

Ignoring URLs and methods

The SetLocale middleware respects urls_ignored and http_methods_ignored:

// Skip locale processing for Nova and API routes
'urls_ignored' => ['/nova', '/nova/*', '/api/*'],

// Skip locale processing for form submissions
'http_methods_ignored' => ['POST', 'PUT', 'PATCH', 'DELETE'],

Route Registration

Route::localized() macro

The package registers a Route::localized() macro that creates route groups for every supported locale:

Route::localized(function () {
    Route::get('/about', [PageController::class, 'about'])->name('about');
    Route::get('/contact', [PageController::class, 'contact'])->name('contact');
});

This generates named routes like localized.en.about, localized.es.about, etc. The SetLocale middleware is automatically applied to each group.

hide_default_locale behavior

When hide_default_locale is false (default):

  • All routes are registered with locale prefixes: /en/about, /es/about, /fr/about
  • Unlocalized URLs (e.g., /about) redirect to the localized version (e.g., /en/about)
  • The redirect respects session, cookie, and Accept-Language header for locale selection
  • Root / redirects to /{locale}/

When hide_default_locale is true:

  • The default locale is served without a prefix: /about
  • Other locales still use prefixes: /es/about, /fr/about
  • Visiting /en/about redirects to /about (via HideDefaultLocaleInUrl middleware)

Manual approach

If you prefer explicit control:

Route::prefix('{locale}')
    ->middleware(['locale.set'])
    ->group(function () {
        Route::get('/about', [PageController::class, 'about'])->name('about');
    });

Helper Functions

All helpers are globally available (loaded via autoload files):

// Generate a localized URL for the current or given locale
localizeUrl('es');          // https://example.com/es/about
localizeUrl(null, '/faq');  // https://example.com/en/faq

// Get the current application locale
getCurrentLocale(); // 'en'

// Get all supported locales (codes only)
getSupportedLocales(); // ['en', 'es', 'fr']

// Check if a locale is the default
isDefaultLocale('en'); // true
isDefaultLocale();     // checks current locale

// Strip locale prefix from a URL
getNonLocalizedUrl('/es/about'); // '/about'

// Render a locale-specific view with fallback
localizedView('pages.home');        // tries pages.home.en, falls back to pages.home
localizedView('pages.home', [], 'es'); // tries pages.home.es

Facade

use Pikbdesigns\FullTranslation\Facades\Localization;

// Basic
Localization::getLocale();                          // 'en'
Localization::setLocale('es');
Localization::getDefaultLocale();                   // 'en'
Localization::isDefaultLocale('en');                // true

// Locales
Localization::getSupportedLocales();                // ['en', 'es', 'fr']
Localization::getSupportedLocalesWithMetadata();    // ['en' => ['name' => 'English', ...], ...]
Localization::getAvailableLocales();                // [['name' => 'English', 'code' => 'en', 'native' => 'English'], ...]
Localization::getLocalesOrder();                    // []

// URLs
Localization::getLocalizedUrl('es');                // https://example.com/es/about
Localization::getLocalizedUrl('es', '/faq', false); // '/es/faq'
Localization::getNonLocalizedUrl('/es/about');       // '/about'

// Route translations
Localization::getRouteTranslations('es');            // require lang/es/routes.php
Localization::getTranslatedRoute('about', 'es');    // 'acerca-de'
Localization::mapLocale('pt-br');                   // 'pt_BR' (if mapped)

// Ignoring
Localization::getUrlsIgnored();                     // ['/nova', '/nova/*']
Localization::getHttpMethodsIgnored();              // ['POST', 'PUT', 'PATCH', 'DELETE']
Localization::isUrlIgnored('/nova/dashboard');      // true
Localization::isHttpMethodIgnored('POST');          // true

Language Switcher

A Blade component is included at resources/views/language-switcher.blade.php:

@include('full-translation::language-switcher')

It renders links for each supported locale (except the current one, shown as active text). When using rich locale metadata, it displays native language names (e.g., "Français" instead of "FR").

You can publish and customize it:

php artisan vendor:publish --tag=translations-views

Custom ordering

Use locales_order to control the display order:

'locales_order' => ['es', 'fr', 'en'],

Translation Export & Inspect Commands

Export translations

Scans your codebase for translatable strings and writes them to JSON files:

# Export for all supported locales
php artisan export:translations

# Export for specific locale(s)
php artisan export:translations en
php artisan export:translations en,fr,es

Features:

  • Reads scan_helpers and scan_paths from config
  • Respects file_patterns and excluded_directories for fine-grained scanning control
  • Creates/updates {locale}.json files in lang/
  • Default locale values are set to the key itself; other locales receive the default locale's value as a placeholder (so there's always something to display)
  • Respects sort_keys for alphabetical ordering
  • When sort_keys is false, uses translated_sort_order ('alpha', 'top', or 'bottom')
  • Merges strings from manual-strings.json when add_manual_strings is true
  • Skips strings containing newlines when allow_newlines is false

Inspect translations

Shows a summary table of all keys and their completion status. A key is marked as pending (?) if its value in a non-default locale is identical to the default locale's value, meaning it hasn't been translated yet. Missing keys are also treated as pending.

php artisan inspect:translations

Output:

Translation Keys Summary
--------------------------------------------------------------------------------
Key               EN              ES              FR              Status
--------------------------------------------------------------------------------
Welcome           Welcome         Bienvenido      Bienvenue       ?
About             About                                       ?

Translation File Structure

The package uses Laravel's JSON translation files:

lang/
+-- en.json          # Default locale
+-- es.json          # Spanish
+-- fr.json          # French
+-- {locale}/
    +-- routes.php   # Route string translations

JSON files

Standard Laravel JSON translations. Keys are the original strings, values are translations:

{
    "Welcome": "Bienvenido",
    "About": "Acerca de"
}

Route translations

The {locale}/routes.php file maps route strings for translated URLs:

// lang/es/routes.php
return [
    'about' => 'acerca-de',
    'contact' => 'contacto',
];

Used by RouteStringTranslator and accessible via Localization::getTranslatedRoute().

Manual strings

For dynamic phrases that can't be scanned (e.g., constructed at runtime), add them to lang/manual-strings.json:

[
    "Hello :name",
    "You have :count items"
]

These are automatically merged into translation files when add_manual_strings is true.

Testing

composer test

License

MIT