Yii2多语言URL路由问题:如何为Action URL添加多语言支持?
嘿,我完全懂你的痛点——手动给每个Action配置多语言路由太繁琐了,尤其是未来还要加更多语言,那工作量简直不敢想。好在Yii2原生就有一套高效的方案,不用逐个绑定控制器,就能实现你要的那种多语言URL效果,下面一步步给你讲清楚:
核心思路:自定义URL规则+翻译系统联动
我们要做的是让路由里的固定片段(比如category/categoria)通过翻译系统自动映射,动态的slug(比如chair/silla)从数据库取对应语言的值,同时用自定义URL规则类统一处理路由的生成和解析,避免重复劳动。
1. 先确保你的i18n组件配置到位
首先得保证翻译系统能正常工作,在config/web.php里的components下配置好i18n:
'components' => [ 'i18n' => [ 'translations' => [ '*' => [ 'class' => 'yii\i18n\PhpMessageSource', 'basePath' => '@app/messages', 'sourceLanguage' => 'en', // 源语言设为英文 'fileMap' => [ 'app' => 'app.php', // 核心翻译文件 ], ], ], ], // 其他组件配置... ],
然后在messages目录下创建对应语言的翻译文件,比如西班牙语的messages/es/app.php:
return [ 'category' => 'categoria', // 未来加其他需要翻译的路由片段,比如'product' => 'producto',直接加这里 ];
2. 编写自定义URL规则类
这个类的作用是自动把URL里的翻译片段转成源语言的路由(解析请求),以及把源语言路由转成当前语言的翻译片段(生成URL)。在app/components下创建I18nUrlRule.php:
namespace app\components; use yii\web\UrlRule; use Yii; class I18nUrlRule extends UrlRule { public $translations = []; // 存储需要翻译的路由片段和对应的翻译分类 // 解析请求:把URL里的翻译片段转成源语言路由 public function parseRequest($manager, $request) { $pathInfo = $request->getPathInfo(); $parts = explode('/', $pathInfo); // 遍历替换每个路由片段为源语言键 foreach ($parts as $key => $part) { foreach ($this->translations as $sourceKey => $category) { // 用源语言反向查找翻译对应的键 $reverseTrans = Yii::t($category, $sourceKey, [], 'en'); if ($part === $reverseTrans) { $parts[$key] = $sourceKey; break; } } } $request->setPathInfo(implode('/', $parts)); return parent::parseRequest($manager, $request); } // 生成URL:把源语言路由转成当前语言的翻译片段 public function createUrl($manager, $route, $params) { $url = parent::createUrl($manager, $route, $params); // 替换路由里的源语言键为当前语言的翻译 foreach ($this->translations as $sourceKey => $category) { $translated = Yii::t($category, $sourceKey); $url = str_replace($sourceKey, $translated, $url); } return $url; } }
3. 配置URL管理器使用自定义规则
在config/web.php的urlManager里添加这个自定义规则,这样就能批量处理多语言路由了:
'urlManager' => [ 'enablePrettyUrl' => true, 'showScriptName' => false, 'rules' => [ [ 'class' => 'app\components\I18nUrlRule', 'pattern' => '<category>/<slug>', 'route' => 'category/slug', 'translations' => [ 'category' => 'app', // 告诉规则:category这个片段用app翻译文件里的内容 ], ], // 未来加其他类似路由,比如product/slug,直接复制这个规则改参数就行 ], ],
4. 处理动态slug的多语言存储
你例子里的chair/silla是动态分类别名,这部分不能用翻译文件,得存在数据库里。比如给你的category表加slug_en、slug_es字段(未来加新语言就加对应字段),然后在分类模型里加个方法获取当前语言的slug:
// Category模型里的方法 public function getCurrentSlug() { $currentLang = Yii::$app->language; $slugField = "slug_{$currentLang}"; return $this->$slugField ?? $this->slug_en; // fallback到英文 }
5. 生成和解析URL的正确姿势
- 生成URL:在视图或控制器里用Yii的URL助手,结合模型的方法:
// 比如获取某个分类的URL $categoryUrl = Url::to([ 'category/slug', 'category' => 'category', // 这里用源语言的键,自定义规则会自动转成当前语言的翻译 'slug' => $category->getCurrentSlug() ]); // 西班牙语环境下会生成 /categoria/silla
- 解析URL:在
CategoryController的actionSlug里,你拿到的$category参数已经是源语言的category(因为自定义规则已经帮你转好了),然后根据当前语言查询对应的slug:
public function actionSlug($category, $slug) { $currentLang = Yii::$app->language; $categoryModel = Category::find() ->where(["slug_{$currentLang}" => $slug]) ->one(); if (!$categoryModel) { throw new \yii\web\NotFoundHttpException('分类不存在'); } // 后续业务逻辑... }
未来扩展的便捷性
当你要加新语言(比如法语)时,只需要做3件事:
- 添加
messages/fr/app.php翻译文件,补充路由片段的翻译(比如category=>catégorie)。 - 给
category表加slug_fr字段,存入对应语言的别名。 - 不需要修改任何路由规则,自定义规则会自动处理新语言的路由生成和解析。
这样就完全不用手动给每个Action绑定控制器了,一套规则搞定所有同类路由,非常省心。
内容的提问来源于stack exchange,提问作者Chico3001

