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

Laravel Tongue Laravel Package

pmochine/laravel-tongue

Laravel Tongue is a lightweight localization package for Laravel that simplifies multilingual routes and language switching. Define translated URLs, detect and persist the visitor's locale, and serve localized content with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Laravel-Tongue (this package) gives you translated URLs, locale detection, and locale-aware redirects without pulling in a full i18n framework. The 6.1.0 release is an additive, non-breaking release — nothing to migrate, but two features are worth adopting early because they change how you should structure routes.

  1. Install and publish the config:
    composer require pmochine/laravel-tongue
    php artisan vendor:publish --tag=tongue
    
  2. Open config/tongue.php first — this is the source of truth for your locals, default locale, and detection strategy. Almost every "why is my locale wrong?" question is answered here.
  3. Register the detects-tongue middleware in the web group (or bootstrap/app.php on Laravel 11+). It sets the active locale before the route is dispatched.
  4. Pick your first use case: a marketing site with /en/about and /fr/a-propos. Define the route once with dialect()->localizedRoutes() so each locale gets its own URI, then hit it with a browser in both locales and confirm the detected locale is correct.

If you plan to deploy behind Octane or run php artisan route:cache (you almost certainly do), start with localizedRoutes() from day one rather than adding it later — retrofitting it means rewiring route definitions and every route()/url() call site that used the locale-aware approach.

Implementation Patterns

Localized route registration (the new default). Use dialect()->localizedRoutes() to register the translated route once per locale, each with its own localized path. This produces concrete routes for every locale, which is what makes route:cache and Octane safe: route registration no longer depends on the current request's locale. Treat this as the standard way to define translated routes in new code.

Canonicalizing URLs with speaks-tongue. Register speaks-tongue alongside detects-tongue. When a request arrives at a route belonging to a different locale than the active one, the middleware redirects to the localized path (fr.example.com/welcome → fr.example.com/bienvenue). Because it uses 307 for form requests, method and payload survive the round trip — useful for POST-heavy apps where a translated slug shouldn't cause a silent loss of form data.

Middleware ordering is part of your route design. Both detects-tongue and speaks-tongue now run before SubstituteBindings. That is deliberate: route model binding resolves translated slugs in the request's locale, and the redirect happens before a binding can 404. If you have custom middleware that touches locale (auth scoping, tenant resolution), slot it around this pair rather than before it.

Redirects with explicit statuses. dialect()->redirect() now takes a status as its second argument. Use 302 for GET-only navigation, 307 when the target is a form endpoint or the caller may be a non-browser client, and 308 if you want the method-and-body preservation to be cached permanently. Don't rely on the default when the distinction matters.

Day-to-day workflow. Keep translated slugs in your language files, define each route through localizedRoutes(), and let the generated names be locale-specific. Build links with the normal route() helper — since routes are materialized per locale, php artisan route:list is a reliable way to see exactly what URLs exist. Add hreflang/canonical tags per locale to avoid duplicate-content penalties, because you genuinely are serving multiple URIs for the same content.

Integration tip. When writing tests, assert both the redirect status and the target path. Feature tests that use assertRedirect() will not catch a 307 that should have been a 302, and HTTP clients that follow redirects may silently downgrade POST to GET.

Gotchas and Tips

  • Middleware priority is the failure mode you'll hit first. If detects-tongue ends up after SubstituteBindings (easy to do when registering it ad-hoc or in a custom kernel), translated-slug binding resolves against the wrong locale and you get 404s that look like missing routes. In Laravel 11+, append these aliases to the priority list in bootstrap/app.php explicitly. In older apps, add them to $middlewarePriority.
  • 307 vs 302 in tests and clients. A 307 requires the client to replay the same method and body. Some HTTP clients and browser dev tools still convert POST to GET after a 301/302. If a form "works manually but fails in tests," check whether the test client followed the redirect and downgraded the request.
  • Don't register routes conditionally on the current locale. Anything that reads dialect()->getLocale() at route-definition time will break route:cache and leak state under Octane. The whole point of localizedRoutes() is that definitions are locale-independent at boot.
  • Clear caches when changing locales or slugs. After editing config/tongue.php or a translated path, run php artisan route:clear and php artisan config:clear — stale cached routes still serve the old locale paths and will look like a middleware bug.
  • Detection order can shadow itself. If you configure both subdomain and path detection, decide which wins and test the ambiguous case (fr.example.com/en/welcome). Set the precedence deliberately in config rather than relying on which middleware happens to run first.
  • Debugging locale: log dialect()->getLocale() at the start of a request-scoped middleware and compare against app()->getLocale(). Divergence tells you whether detection failed or something later reset it.
  • Extension points: the middleware are thin — subclass or replace them if you need custom detection (e.g. Accept-Language fallback or a user preference column). The config file is the supported seam for locale lists and defaults; avoid editing package internals, since 6.1.0's ordering guarantees are enforced from within the middleware themselves.
  • Route list hygiene. With N locales and M routes you now have N×M entries. Use route:list --path= filters when auditing, and keep locale codes in generated route names so route('fr.about')-style calls stay readable.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource