enesthedev/translated-routes

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.

Fund package maintenance!
Enes

Installs: 21

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 1

pkg:composer/enesthedev/translated-routes

3.2.0 2025-11-02 20:15 UTC

This package is auto-updated.

Last update: 2025-12-08 22:29:21 UTC


README

Latest Version on Packagist Total Downloads

Simple, elegant route translation for Laravel 11+. Just add ->translate() to your routes.

Why This Package?

  • Zero configuration - Works with Laravel's locale system
  • Native feel - Uses ->translate() macro on routes
  • Cache-optimized - Built-in caching with configurable TTL
  • Inertia-ready - Shares locale data automatically
  • No magic - Simple translation lookup from lang/{locale}/routes.php

Installation

composer require enesthedev/translated-routes
php artisan vendor:publish --tag="translated-routes-config"
php artisan translated-routes:install

Quick Start

1. Create Translation Files

You have two options for organizing your translation files:

Option A: Separate Files Per Locale (Recommended)

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}',
];

Option B: Single File with All Locales

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.php exists, it uses that. Otherwise, it looks for lang/{locale}/routes.php.

2. Use ->translate() on Your Routes

use 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();
});

3. Set Your App Locale

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');

4. Results

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!

Inertia.js Support

Backend

Add the middleware to share locale data:

Route::middleware(['web', 'share-inertia-locale'])->group(function () {
    Route::get('about', [PageController::class, 'about'])->translate();
});

Frontend (React/TypeScript)

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'

API

Route Macro

// 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();
});

Facade

use Enes\TranslatedRoutes\Facades\TranslatedRoutes;

// Get locale data for Inertia
TranslatedRoutes::getLocaleData();

// Get supported locales
TranslatedRoutes::getSupportedLocales();

// Clear cache
TranslatedRoutes::clearCache();
TranslatedRoutes::clearCache('en');

Helper Function

// Remove locale from URL
non_localized_url('/tr/about');  // Returns: '/about'

Configuration

// 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),
];

How It Works

  1. You define your routes with keys (e.g., 'about', 'contact')
  2. You add ->translate() to the route or group
  3. The package looks up the translation in lang/{App::getLocale()}/routes.php
  4. Route parameters ({slug}, {id}) are automatically preserved
  5. The translated route is set and cached

Locale Management

This 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'));

Artisan Commands

# 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

Advanced Usage

Wildcard Translations

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.

Translation Validation

Check your translations for consistency:

php artisan translated-routes:validate

Export for Frontend

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'

Named Routes with Parameters

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)

API Routes

Route::prefix('api')->group(function () {
    Route::get('user', [UserController::class, 'show'])->translate();
});

Fallback

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)

Performance

  • First request: ~2ms (loads and caches translations)
  • Cached requests: ~0.01ms (static memory cache)
  • Cache TTL: Configurable (default 24 hours)

Real-World Example

// 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();
});

What This Package Does

✅ 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

What This Package Does NOT Do

❌ Locale detection/switching (use other packages)
❌ Content translation (use Laravel's trans())
❌ Session management (use Laravel's session)
❌ URL redirects (handle in your middleware)

Requirements

  • PHP 8.4+
  • Laravel 11.0+

Testing

composer test

Changelog

See CHANGELOG for more information.

License

MIT License. See License File for more information.