codezero / laravel-localized-routes
在Laravel应用中设置、管理和使用本地化路由的便捷方式。
Requires
- php: ^8.1
- codezero/browser-locale: ^3.0
- codezero/composer-preload-files: ^1.0
- codezero/laravel-uri-translator: ^2.0
- codezero/php-url-builder: ^1.0
- illuminate/support: ^10.0|^11.0
Requires (Dev)
- mockery/mockery: ^1.3.3
- orchestra/testbench: ^8.0|^9.0
- phpunit/phpunit: ^10.5
- dev-master
- 4.0.1
- 4.0.0
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.0
- 2.x-dev
- 2.10.2
- 2.10.1
- 2.10.0
- 2.9.1
- 2.9.0
- 2.8.0
- 2.7.3
- 2.7.2
- 2.7.1
- 2.7.0
- 2.6.4
- 2.6.3
- 2.6.2
- 2.6.1
- 2.6.0
- 2.5.0
- 2.4.2
- 2.4.1
- 2.4.0
- 2.3.2
- 2.3.1
- 2.3.0
- 2.2.7
- 2.2.6
- 2.2.5
- 2.2.4
- 2.2.3
- 2.2.2
- 2.2.1
- 2.2.0
- 2.1.0
- 2.0.0
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.0
- 1.1.0
- 1.0.1
- 1.0.0
- dev-support-livewire
This package is auto-updated.
Last update: 2024-09-18 00:03:41 UTC
README
在Laravel应用中设置和使用本地化路由的便捷方式。
📖 目录
- 要求
- 升级
- 安装
- 配置
- 添加中间件以更新应用程序本地化
- 注册路由
- 本地化404页面
- 缓存路由
- 生成路由URL
- 生成签名路由URL
- 重定向到路由
- 自动重定向到本地化URL
- 辅助工具
- 测试
- 鸣谢
- 安全性
- 变更日志
- 许可证
✅ 要求
- PHP >= 8.1
- Laravel >= 10
- Composer ^2.3 (用于codezero/composer-preload-files)
⬆ 升级
升级到新主要版本?请查看我们的升级指南以获取说明。
📦 安装
使用Composer安装此包
composer require codezero/laravel-localized-routes
Laravel将自动注册ServiceProvider。
⚙ 配置
☑ 发布配置文件
php artisan vendor:publish --provider="CodeZero\LocalizedRoutes\LocalizedRoutesServiceProvider" --tag="config"
现在您可以在config文件夹中找到一个localized-routes.php
文件。
☑ 配置支持的本地化
简单本地化
将您希望支持的任何本地化添加到已发布的config/localized-routes.php
文件中
'supported_locales' => ['en', 'nl'];
这些本地化将被用作别名,附加到您的本地化路由的URL前面。
自定义别名
您还可以为本地化使用自定义别名
'supported_locales' => [ 'en' => 'english-slug', 'nl' => 'dutch-slug', ];
自定义域名
或者您可以为本地化使用自定义域名
'supported_locales' => [ 'en' => 'english-domain.test', 'nl' => 'dutch-domain.test', ];
☑ 使用回退本地化
当使用route()
辅助函数为不支持的语言生成URL时,Laravel会抛出Symfony\Component\Routing\Exception\RouteNotFoundException
异常。但是,您可以配置回退本地化,尝试解析回退URL。如果这也失败,则抛出异常。
'fallback_locale' => 'en',
☑ 省略主本地化的别名
如果您想省略主本地化的别名,请指定您的默认本地化
'omitted_locale' => 'en',
如果使用域名而不是别名,此选项不起作用。
☑ 作用域选项
要仅为一组本地化路由设置选项,可以将它指定为本地化路由宏的第二个参数。这将覆盖配置文件设置。目前,只能覆盖2个选项。
Route::localized(function () { Route::get('about', [AboutController::class, 'index']); }, [ 'supported_locales' => ['en', 'nl', 'fr'], 'omitted_locale' => 'en', ]);
🧩 添加中间件以更新应用程序本地化
默认情况下,应用程序的本地化始终是您在config/app.php中配置的。要自动更新应用程序的本地化,您需要在web中间件组中注册中间件。确保在StartSession
之后和SubstituteBindings
之前添加它。
如果您使用本地化路由键(翻译后的别名),则中间件顺序非常重要!在设置区域设置时,会话必须处于活动状态,并且替换路由绑定时必须设置区域设置。
Laravel 11 及更高版本
将中间件添加到 bootstrap/app.php
中的 web
中间件组。
// bootstrap/app.php ->withMiddleware(function (Middleware $middleware) { $middleware->web(remove: [ \Illuminate\Routing\Middleware\SubstituteBindings::class, ]); $middleware->web(append: [ \CodeZero\LocalizedRoutes\Middleware\SetLocale::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ]); })
Laravel 10
将中间件添加到 app/Http/Kernel.php
中的 web
中间件组。
// app/Http/Kernel.php protected $middlewareGroups = [ 'web' => [ //... \Illuminate\Session\Middleware\StartSession::class, // <= after this //... \CodeZero\LocalizedRoutes\Middleware\SetLocale::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, // <= before this ], ];
检测器
中间件将按顺序运行以下检测器,直到其中一个返回支持的区域设置
更新配置文件中的 detectors
数组,以选择要运行的检测器及其顺序。
您可以通过实现
CodeZero\LocalizedRoutes\Middleware\Detectors\Detector
接口来创建自己的检测器,并在配置文件中添加对其的引用。检测器由 Laravel 的 IOC 容器解析,因此您可以在构造函数中添加任何依赖项。
存储
如果检测到支持的区域设置,它将自动存储在
更新配置中的 stores
数组以选择要使用的存储。
您可以通过实现
CodeZero\LocalizedRoutes\Middleware\Stores\Store
接口来创建自己的存储,并在配置文件中添加对其的引用。存储由 Laravel 的 IOC 容器解析,因此您可以在构造函数中添加任何依赖项。
尽管不需要进一步配置,但您可以在配置文件中更改高级设置。
🚘 注册路由
在 Route::localized()
闭包内定义您的路由,以自动为每个区域设置注册它们。
这将在路由的 URI 和名称前添加区域设置。如果您配置了自定义域名,则将使用那些域名而不是 URI 别名。您也可以在闭包中使用路由组。
Route::localized(function () { Route::get('about', [AboutController::class, 'index'])->name('about'); });
对于支持的区域设置 ['en', 'nl']
,上述代码将注册
如果省略的区域设置设置为 en
,则结果将是
在大多数实际场景中,您会注册一个区域设置路由或非区域设置路由,但不会同时注册两者。如果您这样做,您将始终需要指定区域设置来使用
route()
辅助函数生成 URL,因为现有的路由名称始终具有优先级。特别是当省略主区域设置从 URL 中时,这可能会出现问题,因为您不能在这种情况下同时拥有区域化的/about
路由和非区域化的/about
路由。相同的概念也适用于/
(根)路由!请注意,即使省略了别名,路由名称仍然具有区域设置前缀。
☑ 使用路由模型绑定翻译参数
当解析来自请求的路由参数时,您可能依赖于 Laravel 的路由模型绑定。您在控制器中为模型添加类型提示,它将根据 ID 或特定的属性(如 {model:slug}
)查找 {model}
。如果它找到与 URL 中参数值匹配的项,则将其注入到控制器中。
// Example: use the post slug as the route parameter Route::get('posts/{post:slug}', [PostsController::class, 'index']); // PostsController.php public function index(Post $post) { return $post; }
但是,为了解析区域化参数,您需要向您的模型添加 resolveRouteBinding()
方法。在此方法中,您需要编写逻辑以找到匹配项,使用 URL 中的参数值。
例如,您可能在数据库中有一个包含翻译后别名的 JSON 列
public function resolveRouteBinding($value, $field = null) { // Default field to query if no parameter field is specified $field = $field ?: $this->getRouteKeyName(); // If the parameter field is 'slug', // lets query a JSON field with translations if ($field === 'slug') { $field .= '->' . App::getLocale(); } // Perform the query to find the parameter value in the database return $this->where($field, $value)->firstOrFail(); }
如果您正在寻找在模型上实现翻译属性的好解决方案,请务必查看 spatie/laravel-translatable。
☑ 翻译硬编码的 URI 别名
此包包括 codezero/laravel-uri-translator。它注册了一个 Lang::uri()
宏,该宏允许您翻译单个硬编码的 URI 别名。此宏不会翻译路由参数。
需要翻译URI的路由必须有一个名称,才能使用route()
辅助函数或Route::localizedUrl()
宏来生成其本地化版本。因为这些路由的slug根据语言环境而不同,所以路由名称是唯一将它们联系在一起的东西。
首先,为每个语言环境在你的应用lang
文件夹中创建一个routes.php
翻译文件,例如
lang/nl/routes.php lang/fr/routes.php
然后,向每个文件添加适当的翻译
// lang/nl/routes.php return [ 'about' => 'over', 'us' => 'ons', ];
最后,在注册路由时使用宏
Route::localized(function () { Route::get(Lang::uri('about/us'), [AboutController::class, 'index'])->name('about'); });
URI宏接受两个额外参数
- 一个语言环境,如果你需要翻译成除了当前应用语言环境以外的语言环境。
- 一个命名空间,如果你的翻译文件位于一个包中。
Lang::uri('hello/world', 'fr', 'my-package');
你也可以使用trans()->uri('hello/world')
代替Lang::uri('hello/world')
。
示例
使用这些示例翻译
// lang/nl/routes.php return [ 'hello' => 'hallo', 'world' => 'wereld', 'override/hello/world' => 'something/very/different', 'hello/world/{parameter}' => 'uri/with/{parameter}', ];
这些是可能的翻译结果
// Translate every slug individually // Translates to: 'hallo/wereld' Lang::uri('hello/world'); // Keep original slug when missing translation // Translates to: 'hallo/big/wereld' Lang::uri('hello/big/world'); // Translate slugs, but not parameter placeholders // Translates to: 'hallo/{world}' Lang::uri('hello/{world}'); // Translate full URIs if an exact translation exists // Translates to: 'something/very/different' Lang::uri('override/hello/world'); // Translate full URIs if an exact translation exists (with placeholder) // Translates to: 'uri/with/{parameter}' Lang::uri('hello/world/{parameter}');
🔦 本地化404页面
标准的404
响应没有实际的Route
,并且不通过中间件。这意味着我们的中间件将无法更新语言环境,请求无法本地化。
为了修复这个问题,你可以在你的routes/web.php
文件的末尾注册这个回退路由
Route::fallback(\CodeZero\LocalizedRoutes\Controllers\FallbackController::class);
因为回退路由是一个实际的Route
,中间件将运行并更新语言环境。
回退路由是Laravel提供的一个“捕获所有”路由。如果你输入一个不存在的URL,将触发这个路由而不是典型的404异常。
FallbackController
将尝试返回一个位于resources/views/errors/404.blade.php
的404错误视图。如果这个视图不存在,将抛出正常的Symfony\Component\HttpKernel\Exception\NotFoundHttpException
异常。你可以通过更改配置文件中的404_view
条目来配置使用哪个视图。
以下情况下,回退路由不会应用
- 你的现有路由抛出
404
异常(如abort(404)
) - 你的现有路由抛出
ModelNotFoundException
(如使用路由模型绑定) - 你的现有路由抛出任何其他异常
🗄 缓存路由
在生产环境中,你可以安全地按常规缓存你的路由。
php artisan route:cache
⚓ 生成路由URL
☑ 为活动语言环境生成URL
你可以像平常一样使用route()
辅助函数来获取命名路由的URL。
$url = route('about');
如果你注册了一个未本地化的about
路由,那么about
是一个现有的路由名称,其URL将返回。否则,这将尝试为活动语言环境生成about
URL,例如en.about
。
☑ 为特定语言环境生成URL
在某些情况下,你可能需要为特定语言环境生成一个URL。为此,Laravel的route()
辅助函数中添加了一个额外的语言环境参数。
$url = route('about', [], true, 'nl'); // this will load 'nl.about'
☑ 生成带有本地化参数的URL
有几种方法可以生成带有本地化参数的路由URL。
手动传递本地化参数
假设我们有一个具有getSlug()
方法的Post
模型
public function getSlug($locale = null) { $locale = $locale ?: App::getLocale(); $slugs = [ 'en' => 'en-slug', 'nl' => 'nl-slug', ]; return $slugs[$locale] ?? ''; }
当然,在实际项目中,slugs不会是硬编码的。如果你在寻找一个实现模型上翻译属性的好方案,务必查看spatie/laravel-translatable。
现在你可以将本地化slug传递给route()
函数
route('posts.show', [$post->getSlug()]); route('posts.show', [$post->getSlug('nl')], true, 'nl');
使用自定义的本地化路由键
你可以通过向你的模型添加getRouteKey()
方法来让Laravel自动解析本地化参数
public function getRouteKey() { $locale = App::getLocale(); $slugs = [ 'en' => 'en-slug', 'nl' => 'nl-slug', ]; return $slugs[$locale] ?? ''; }
现在你只需要传递模型
route('posts.show', [$post]); route('posts.show', [$post], true, 'nl');
☑ 回退URL
可以在配置文件中提供回退语言环境。如果route()
辅助函数的语言环境参数不是受支持的语言环境,则将使用回退语言环境。
// When the fallback locale is set to 'en' // and the supported locales are 'en' and 'nl' $url = route('about', [], true, 'nl'); // this will load 'nl.about' $url = route('about', [], true, 'wk'); // this will load 'en.about'
如果既不能解析常规路由也不能解析本地化路由,将抛出Symfony\Component\Routing\Exception\RouteNotFoundException
异常。
☑ 生成当前URL的本地化版本
要为任何地区生成当前路由的URL,您可以使用Route::localizedUrl()
宏。
手动传递参数
就像使用route()
辅助函数一样,您可以将参数作为第二个参数传递。
假设我们有一个具有getSlug()
方法的Post
模型
public function getSlug($locale = null) { $locale = $locale ?: App::getLocale(); $slugs = [ 'en' => 'en-slug', 'nl' => 'nl-slug', ]; return $slugs[$locale] ?? ''; }
现在您可以将本地化别名传递给宏
$current = Route::localizedUrl(null, [$post->getSlug()]); $en = Route::localizedUrl('en', [$post->getSlug('en')]); $nl = Route::localizedUrl('nl', [$post->getSlug('nl')]);
使用自定义路由键
如果您添加了模型的getRouteKey()
方法,您根本不需要传递参数。
public function getRouteKey() { $locale = App::getLocale(); $slugs = [ 'en' => 'en-slug', 'nl' => 'nl-slug', ]; return $slugs[$locale] ?? ''; }
宏现在将自动确定当前路由具有哪些参数并获取这些值。
$current = Route::localizedUrl(); $en = Route::localizedUrl('en'); $nl = Route::localizedUrl('nl');
多个路由键
如果您有一个具有多个键的路由,例如/en/posts/{id}/{slug}
,那么您可以在模型中实现ProvidesRouteParameters
接口。然后,从getRouteParameters()
方法中返回所需的参数值。
use CodeZero\LocalizedRoutes\ProvidesRouteParameters; use Illuminate\Database\Eloquent\Model; class Post extends Model implements ProvidesRouteParameters { public function getRouteParameters($locale = null) { return [ $this->id, $this->getSlug($locale) // Add this method yourself of course :) ]; } }
现在,参数仍然会自动解析
$current = Route::localizedUrl(); $en = Route::localizedUrl('en'); $nl = Route::localizedUrl('nl');
保留或删除查询字符串
默认情况下,查询字符串将包含在生成的URL中。如果您不想这样,可以向宏传递一个额外的参数
$keepQuery = false; $current = Route::localizedUrl(null, [], true, $keepQuery);
☑ 示例地区切换器
以下Blade代码片段将在每个替代地区添加当前页面的链接。
它只会在当前路由是本地化或回退路由时运行。
@if (Route::isLocalized() || Route::isFallback()) <ul> @foreach(LocaleConfig::getLocales() as $locale) @if ( ! App::isLocale($locale)) <li> <a href="{{ Route::localizedUrl($locale) }}"> {{ strtoupper($locale) }} </a> </li> @endif @endforeach </ul> @endif
🖋 生成签名路由URL
生成本地化签名路由和临时签名路由URL与生成正常路由URL一样简单。传递路由名称和必要的参数,您将获得当前地区的URL。
$signedUrl = URL::signedRoute('reset.password', ['user' => $id]); $signedUrl = URL::temporarySignedRoute('reset.password', now()->addMinutes(30), ['user' => $id]);
您还可以为特定地区生成签名路由URL
$signedUrl = URL::signedRoute('reset.password', ['user' => $id], null, true, 'nl'); $signedUrl = URL::temporarySignedRoute('reset.password', now()->addMinutes(30), ['user' => $id], true, 'nl');
有关签名路由的更多信息,请参阅Laravel文档。
🚌 重定向到路由
您可以使用redirect()
辅助函数或Redirect
外观重定向到路由,就像在正常的Laravel应用程序中一样。
如果您注册了一个未本地化的about
路由,那么about
是一个现有的路由名称,其URL将被重定向。否则,这将尝试重定向到活动地区的about
路由,例如en.about
。
return redirect()->route('about');
您还可以重定向到特定地区的URL
// Redirects to 'nl.about' return redirect()->route('about', [], 302, [], 'nl');
还包括本地化的signedRoute
和temporarySignedRoute
重定向
// Redirects to the active locale return redirect()->signedRoute('signed.route', ['user' => $id]); return redirect()->temporarySignedRoute('signed.route', now()->addMinutes(30), ['user' => $id]); // Redirects to 'nl.signed.route' return redirect()->signedRoute('signed.route', ['user' => $id], null, 302, [], 'nl'); return redirect()->temporarySignedRoute('signed.route', now()->addMinutes(30), ['user' => $id], 302, [], 'nl');
🪧 自动重定向到本地化URL
要将任何非本地化URL重定向到其本地化版本,可以将配置选项redirect_to_localized_urls
设置为true
,并在您的routes/web.php
文件末尾注册以下回退路由,使用FallbackController
。
Route::fallback(\CodeZero\LocalizedRoutes\Controllers\FallbackController::class);
回退路由是Laravel提供的一个“捕获所有”路由。如果你输入一个不存在的URL,将触发这个路由而不是典型的404异常。
FallbackController
将尝试重定向到URL的本地化版本,或者如果它不存在,则返回一个本地化404响应。
例如
如果省略的地区设置为en
如果路由不存在,将返回404
响应。
🪜 辅助函数
Route::hasLocalized()
// Check if a named route exists in the active locale: $exists = Route::hasLocalized('about'); // Check if a named route exists in a specific locale: $exists = Route::hasLocalized('about', 'nl');
Route::isLocalized()
// Check if the current route is localized: $isLocalized = Route::isLocalized(); // Check if the current route is localized and has a specific name: $isLocalized = Route::isLocalized('about'); // Check if the current route has a specific locale and has a specific name: $isLocalized = Route::isLocalized('about', 'nl'); // Check if the current route is localized and its name matches a pattern: $isLocalized = Route::isLocalized(['admin.*', 'dashboard.*']); // Check if the current route has one of the specified locales and has a specific name: $isLocalized = Route::isLocalized('about', ['en', 'nl']);
Route::isFallback()
// Check if the current route is a fallback route: $isFallback = Route::isFallback();
🚧 测试
composer test
☕ 致谢
🔒 安全性
如果您发现任何与安全相关的问题,请通过电子邮件与我联系,而不是使用问题跟踪器。
📑 更新日志
有关此包所有显著更改的完整列表,请参阅发行页面。
📜 许可证
MIT许可证(MIT)。有关更多信息,请参阅许可证文件。