Simple route translation for Laravel 11+. Just add ->translate() to your routes. Wildcard support, validation, export to JSON/JS/TS. Works with any locale management system.
enesthedev/translated-routes is a Laravel package for simple route translation for laravel 11+. just add ->translate() to your routes. wildcard support, validation, export to json/js/ts. works with any locale management system..
It currently has 1 GitHub stars and 21 downloads on Packagist (latest version 3.2.0).
Install it with composer require enesthedev/translated-routes.
Discover more Laravel packages by enesthedev
or browse all Laravel packages to compare alternatives.
Last updated
Simple, elegant route translation for Laravel 11+. Just add ->translate() to your routes.
->translate() macro on routeslang/{locale}/routes.phpcomposer require enesthedev/translated-routes
php artisan vendor:publish --tag="translated-routes-config"
php artisan translated-routes:install
You have two options for organizing your translation files:
lang/en/routes.php
return [
'about' => 'about-us',
'contact' => 'contact',
'blog' => 'blog/{slug}',
'products' => 'products/{category}/{id}',
];
lang/tr/routes.php
return [
'about' => 'hakkimizda',
'contact' => 'iletisim',
'blog' => 'blog/{slug}',
'products' => 'urunler/{category}/{id}',
];
lang/routes.php
return [
'en' => [
'about' => 'about-us',
'contact' => 'contact',
'blog' => 'blog/{slug}',
'products' => 'products/{category}/{id}',
],
'tr' => [
'about' => 'hakkimizda',
'contact' => 'iletisim',
'blog' => 'blog/{slug}',
'products' => 'urunler/{category}/{id}',
],
];
Note: The package automatically detects which structure you're using. If
lang/routes.phpexists, it uses that. Otherwise, it looks forlang/{locale}/routes.php.
->translate() on Your Routesuse Illuminate\Support\Facades\Route;
// Single route translation
Route::get('about', [PageController::class, 'about'])
->name('about')
->translate();
Route::get('contact', [PageController::class, 'contact'])
->name('contact')
->translate();
Route::get('blog/{slug}', [BlogController::class, 'show'])
->name('blog.show')
->translate();
// Group translation - Option 1: translateGroup helper
Route::translateGroup(['middleware' => 'auth'], function () {
Route::get('settings/profile', [ProfileController::class, 'edit'])->name('profile.edit');
Route::get('settings/password', [PasswordController::class, 'edit'])->name('password.edit');
});
// Group translation - Option 2: Individual translate calls
Route::middleware('auth')->group(function () {
Route::get('settings/profile', [ProfileController::class, 'edit'])
->name('profile.edit')
->translate();
Route::get('settings/password', [PasswordController::class, 'edit'])
->name('password.edit')
->translate();
});
The package uses Laravel's App::getLocale(), so use any locale management package or middleware you prefer:
// In a middleware or wherever you set locale
App::setLocale('tr');
When App::getLocale() is 'en':
/about-us
/contact
/blog/hello-world
/products/electronics/123
When App::getLocale() is 'tr':
/hakkimizda
/iletisim
/blog/hello-world
/urunler/electronics/123
Parameters are automatically preserved!
Add the middleware to share locale data:
Route::middleware(['web', 'share-inertia-locale'])->group(function () {
Route::get('about', [PageController::class, 'about'])->translate();
});
types/index.d.ts
export interface LocaleData {
code: string;
name: string;
native: string;
active: boolean;
}
export interface PageProps {
locale: {
current: string;
default: string;
supported: Record<string, LocaleData>;
};
}
LanguageSwitcher.tsx
import { usePage, router } from '@inertiajs/react';
import { PageProps } from '@/types';
export default function LanguageSwitcher() {
const { locale } = usePage<PageProps>().props;
const switchLocale = (code: string) => {
// Use your preferred locale switching method
router.post('/locale/switch', { locale: code });
};
return (
<div className="flex gap-2">
{Object.values(locale.supported).map((lang) => (
<button
key={lang.code}
onClick={() => switchLocale(lang.code)}
className={lang.active ? 'font-bold' : ''}
>
{lang.native}
</button>
))}
</div>
);
}
Current Locale Usage
const { locale } = usePage<PageProps>().props;
console.log(locale.current); // 'en' or 'tr'
console.log(locale.supported.tr.native); // 'Türkçe'
// Translate a single route
Route::get('about', $action)->translate();
// Translate all routes in a group - Option 1 (Recommended)
Route::translateGroup(['middleware' => 'auth'], function () {
Route::get('settings/profile', [ProfileController::class, 'edit']);
Route::get('settings/password', [PasswordController::class, 'edit']);
});
// Translate all routes in a group - Option 2
Route::middleware('auth')->group(function () {
Route::get('settings/profile', [ProfileController::class, 'edit'])->translate();
Route::get('settings/password', [PasswordController::class, 'edit'])->translate();
});
use Enes\TranslatedRoutes\Facades\TranslatedRoutes;
// Get locale data for Inertia
TranslatedRoutes::getLocaleData();
// Get supported locales
TranslatedRoutes::getSupportedLocales();
// Clear cache
TranslatedRoutes::clearCache();
TranslatedRoutes::clearCache('en');
// Remove locale from URL
non_localized_url('/tr/about'); // Returns: '/about'
// config/translated-routes.php
return [
// Define your supported locales
'supported_locales' => [
'en' => ['name' => 'English', 'native' => 'English'],
'tr' => ['name' => 'Turkish', 'native' => 'Türkçe'],
],
// Cache settings
'cache_enabled' => env('TRANSLATED_ROUTES_CACHE', true),
'cache_ttl' => env('TRANSLATED_ROUTES_CACHE_TTL', 86400),
];
'about', 'contact')->translate() to the route or grouplang/{App::getLocale()}/routes.php{slug}, {id}) are automatically preservedThis package does not manage locale switching. Use any method you prefer:
Option 1: mcamara/laravel-localization
Route::group([
'prefix' => LaravelLocalization::setLocale(),
'middleware' => ['localeSessionRedirect']
], function () {
Route::get('about', $action)->translate();
});
Option 2: Custom Middleware
class SetLocale
{
public function handle($request, $next)
{
if ($locale = $request->segment(1)) {
if (in_array($locale, ['en', 'tr'])) {
App::setLocale($locale);
}
}
return $next($request);
}
}
Option 3: Session-based
Route::post('/locale/switch', function (Request $request) {
session(['locale' => $request->locale]);
App::setLocale($request->locale);
return back();
});
// In a middleware
App::setLocale(session('locale', 'en'));
# Create translation files
php artisan translated-routes:install
# Clear cache
php artisan translated-routes:clear # All locales
php artisan translated-routes:clear en # Specific locale
# Validate translations
php artisan translated-routes:validate # Check for missing/inconsistent translations
# List translated routes
php artisan translated-routes:list # All locales
php artisan translated-routes:list --locale=en # Specific locale
# Export translations
php artisan translated-routes:export --format=json # Export to JSON
php artisan translated-routes:export --format=js # Export to JavaScript
php artisan translated-routes:export --format=ts # Export to TypeScript
# Profile performance
php artisan translated-routes:profile # Benchmark translation performance
Support for dynamic route patterns:
// lang/en/routes.php
return [
'blog/*' => 'blog/*',
'user/*/profile' => 'user/*/profile',
];
// lang/tr/routes.php
return [
'blog/*' => 'blog/*',
'user/*/profile' => 'kullanici/*/profil',
];
This allows matching multiple routes with a single pattern, reducing repetition.
Check your translations for consistency:
php artisan translated-routes:validate
Export translations for SPA/PWA applications:
php artisan translated-routes:export --format=ts
Then use in your TypeScript application:
import { getRoute, type RouteKey, type Locale } from '@/translations/routes';
const route = getRoute('about', 'tr'); // 'hakkimizda'
Route::get('blog', [BlogController::class, 'show'])
->name('blog.show')
->translate();
// Generate URL
route('blog.show', ['slug' => 'hello-world']);
// Result: /blog/hello-world (en) or /blog/hello-world (tr)
Route::prefix('api')->group(function () {
Route::get('user', [UserController::class, 'show'])->translate();
});
If a translation key doesn't exist, the original URI is used:
// lang/en/routes.php - 'missing-key' not defined
Route::get('missing-key', $action)->translate();
// Result: /missing-key (uses original URI)
// routes/web.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\{HomeController, BlogController, ProductController};
Route::middleware(['web', 'share-inertia-locale'])->group(function () {
// Home page
Route::get('/', [HomeController::class, 'index'])->name('home');
// Translated routes
Route::group([], function () {
Route::get('about', [HomeController::class, 'about'])->name('about');
Route::get('contact', [HomeController::class, 'contact'])->name('contact');
Route::get('services', [HomeController::class, 'services'])->name('services');
Route::get('blog', [BlogController::class, 'index'])->name('blog.index');
Route::get('blog/{slug}', [BlogController::class, 'show'])->name('blog.show');
Route::get('products', [ProductController::class, 'index'])->name('products.index');
Route::get('products/{category}/{id}', [ProductController::class, 'show'])->name('products.show');
})->translate();
// Forms
Route::post('contact', [HomeController::class, 'contactSubmit'])->name('contact.submit')->translate();
});
✅ Translates route URIs based on App::getLocale()
✅ Preserves route parameters automatically
✅ Caches translations for performance
✅ Shares locale data with Inertia.js
✅ Provides non_localized_url() helper
❌ Locale detection/switching (use other packages)
❌ Content translation (use Laravel's trans())
❌ Session management (use Laravel's session)
❌ URL redirects (handle in your middleware)
composer test
See CHANGELOG for more information.
MIT License. See License File for more information.