简介
本文围绕前台多语言路由的核心机制,聚焦 LangPrefixParser 类,系统说明其如何从 URL 路径中提取语言代码、如何标准化处理语言前缀、如何在入口层与 Request/Router 协作完成重定向与路由分发,并给出与前端语言切换的集成方式、配置方法与最佳实践。
项目结构
- 入口 index.php 在请求早期调用 LangPrefixParser::parse 剥离语言前缀,并将结果写入 Request(语言前缀与已剥离的路由串)。
- 随后进入 Init 启动流程,读取当前语言包并加载站点配置。
- 路由阶段由 Front Router 与 FrontResolver 消费 Request 中的路由字符串进行匹配与调度。
- 前端通过 theme/default/js/route.js 生成带语言前缀或 lang 参数的链接;LLM 文档等场景也按相同规则构造语言化 URL。
graph TB
A["浏览器请求"] --> B["index.php<br/>入口预处理"]
B --> C["LangPrefixParser::parse()<br/>提取语言前缀/剥离路由"]
C --> D["Request::setRouteLangSign()<br/>Request::setRouteString()"]
D --> E["Init::boot()<br/>初始化语言与配置"]
E --> F["Front Router / FrontResolver<br/>路由匹配与分发"]
F --> G["控制器/服务/视图"]
图表来源
- index.php:28-34
- LangPrefixParser.php:39-56
- Request.php:1330
- Init.php:214-221
- Router.php:29
- FrontResolver.php:41
章节来源
- index.php:28-34
- LangPrefixParser.php:21-56
- Request.php:1330
- Init.php:214-221
- Router.php:29
- FrontResolver.php:41
核心组件
- LangPrefixParser:纯函数式工具,负责从原始 route 串中识别并剥离语言前缀,返回语言前缀与剩余路由串。
- Request:承载当前请求的路径与语言前缀元信息,供后续 Init 与路由阶段消费。
- Front Router / FrontResolver:基于 Request 提供的路由串进行匹配与分发。
- 前端 route.js:根据重写模式与语言配置生成带语言前缀或查询参数的链接。
- LlmsService:示例展示如何按重写/非重写模式生成语言化 URL。
章节来源
- LangPrefixParser.php:31-56
- Request.php:1330
- Router.php:29
- FrontResolver.php:41
- route.js:125-136
- LlmsService.php:53-68
架构总览
下图展示了从请求到响应过程中,语言前缀的提取、存储与使用的全链路。
sequenceDiagram
participant U as "用户"
participant I as "index.php"
participant P as "LangPrefixParser"
participant RQ as "Request"
participant IN as "Init"
participant RT as "Front Router"
participant FR as "FrontResolver"
participant C as "控制器/服务"
U->>I : 访问 /en/article/detail/1
I->>P : parse(route="en/article/detail/1")
P-->>I : {langSign : "en", routeString : "article/detail/1"}
I->>RQ : setRouteLangSign("en"), setRouteString("article/detail/1")
I->>IN : boot(Request.current())
IN-->>RT : 已设置语言与路由串
RT->>FR : 匹配路由
FR-->>C : 分发到对应控制器/服务
C-->>U : 渲染页面
图表来源
- index.php:28-34
- LangPrefixParser.php:39-56
- Request.php:1330
- Router.php:29
- FrontResolver.php:41
详细组件分析
LangPrefixParser:语言检测算法与前缀提取
- 输入:原始 route 串(通常来自 $_GET['route']),经 (string) 强制转换。
- 处理:
- 去除首尾斜杠后按 / 分割为片段数组。
- 若首个片段非空且匹配两位小写-两位小写的语言代码格式(大小写不敏感),则将其作为语言前缀剥离,剩余片段重新拼接为路由串。
- 否则语言前缀为空串,路由串保持原样。
- 输出:包含语言前缀与已剥离路由串的数组。
flowchart TD
Start(["开始"]) --> Trim["去除首尾斜杠并分割为片段"]
Trim --> CheckFirst{"第一个片段非空且匹配语言代码格式?"}
CheckFirst -- "是" --> Extract["提取为语言前缀<br/>剩余片段拼接为路由串"]
CheckFirst -- "否" --> Keep["语言前缀为空串<br/>路由串保持不变"]
Extract --> Return(["返回 {langSign, routeString}"])
Keep --> Return
图表来源
- LangPrefixParser.php:39-56
章节来源
- LangPrefixParser.php:39-56
入口集成:index.php 与 Request
- 入口在路由分发之前执行语言前缀剥离,并将结果写入 Request:
- setRouteLangSign:写入语言前缀(如 en)。
- setRouteString:写入已剥离语言前缀后的路由串。
- 同时清理 $_GET['route'] 与 $_REQUEST['route'],避免业务层直接读取。
章节来源
- index.php:28-34
- Request.php:1330
路由阶段:Front Router 与 FrontResolver
- Front Router 仅读取 Request 上的路由字符串(已由 LangPrefixParser 剥离语言前缀),并将其交给 FrontResolver 进行匹配。
- DelegatingRouter 对 route/lang 的处理亦依赖前台边界写入的 routeString/routeLangSign。
章节来源
- Router.php:29
- FrontResolver.php:41
- DelegatingRouter.php:73
默认语言与语言包选择
- Init 在启动时确定当前语言包:若 locale 已激活则取当前语言包,否则回退到站点配置的 site.language,未配置时默认为 zh_cn。
- 该值将用于后续模板渲染、翻译资源加载等。
章节来源
- Init.php:214-221
前端语言切换与 URL 生成
- 当启用 URL 重写时,route.js 会按配置 sign 将语言代码作为路径前缀插入(如 /en/...)。
- 未启用重写时,route.js 会在查询参数中添加 lang=语言包,以维持语言状态。
- LlmsService 在生成 llms.txt 的语言切换链接时,同样遵循重写与非重写两种模式,确保一致性。
sequenceDiagram
participant FE as "前端页面"
participant JS as "route.js"
participant S as "服务器"
FE->>JS : 点击语言切换
alt 启用重写
JS->>S : GET /{sign}/{path}
else 未启用重写
JS->>S : GET /{path}?lang={pack}
end
S-->>FE : 返回目标语言页面
图表来源
- route.js:125-136
- LlmsService.php:53-68
章节来源
- route.js:125-136
- LlmsService.php:53-68
语言前缀标准化与无效代码处理策略
- 标准化:
- 语言代码匹配采用正则校验,要求两位小写字母-两位小写字母(大小写不敏感),因此输入会被规范化为该格式。
- 剥离后路由串通过 implode('/', $parts) 重建,消除多余斜杠带来的歧义。
- 无效语言代码:
- 若不匹配语言代码格式,则视为无语言前缀,langSign 为空串,路由串保持不变。
- 这保证了非法前缀不会污染路由匹配,也不会导致异常跳转。
章节来源
- LangPrefixParser.php:44-50
重定向逻辑与优先级
- 本仓库中语言前缀的解析发生在入口层,不涉及自动重定向;语言切换主要通过前端生成不同 URL 实现。
- 若业务需要重定向(例如统一规范语言前缀),可在控制器或服务中抛出 RedirectException,由入口统一处理并重定向到目标 URL。
- 优先级建议:
- URL 重写模式优先:路径前缀更语义化,利于 SEO。
- 查询参数 lang 作为兼容模式:便于旧链接与 API 调用。
- Cookie/Session:如需持久化用户偏好,可在会话层维护语言包,并在生成链接时注入到 URL。
章节来源
- index.php:46-51
依赖关系分析
- LangPrefixParser 是纯函数式工具,不依赖 Config/Locale/容器,可在 Init 之前安全运行。
- 入口 index.php 依赖 LangPrefixParser 与 Request,将解析结果写入 Request。
- Front Router/FrontResolver 依赖 Request 中的路由串进行匹配。
- 前端 route.js 与后端行为保持一致:根据重写开关决定语言前缀的放置位置。
graph LR
P["LangPrefixParser"] --> RQ["Request"]
I["index.php"] --> P
I --> RQ
RQ --> RT["Front Router"]
RT --> FR["FrontResolver"]
FE["前端 route.js"] --> |生成URL| I
图表来源
- LangPrefixParser.php:21-30
- index.php:28-34
- Router.php:29
- FrontResolver.php:41
- route.js:125-136
章节来源
- LangPrefixParser.php:21-30
- index.php:28-34
- Router.php:29
- FrontResolver.php:41
- route.js:125-136
性能考量
- LangPrefixParser 为轻量级字符串处理,时间复杂度近似 O(n),空间开销极小,适合在入口高频调用。
- 避免在循环或大对象上重复解析,建议在入口只解析一次并缓存于 Request。
- 前端生成链接时应复用已有语言配置,减少不必要的字符串拼接与判断。
故障排查指南
- 现象:语言前缀未被识别
- 检查 URL 是否满足语言代码格式(两位小写-两位小写)。
- 确认入口是否正确调用 parse 并写入 Request。
- 现象:路由匹配失败
- 确认 Request 中的 routeString 已被正确剥离语言前缀。
- 检查 Front Resolver 是否能匹配到预期路由。
- 现象:前端语言切换无效
- 检查重写模式与 sign 配置是否与后端一致。
- 确认 route.js 生成的 URL 符合预期(路径前缀或查询参数)。
章节来源
- LangPrefixParser.php:44-50
- index.php:28-34
- route.js:125-136
结论
LangPrefixParser 以最小依赖实现了稳定可靠的多语言前缀解析,配合入口层对 Request 的设置以及前后端一致的 URL 生成策略,构成了完整的多语言路由支持基础。通过合理配置重写模式与语言包,可实现清晰的国际化路由设计与良好的用户体验。
附录
- 多语言网站路由设计最佳实践
- 优先使用 URL 重写模式,将语言代码置于路径前缀,提升可读性与 SEO。
- 保留查询参数 lang 作为兼容模式,便于旧链接与 API 调用。
- 在会话层维护用户语言偏好,并在生成链接时注入到 URL,保证跨页一致性。
- 对非法语言前缀采取“忽略”策略,避免破坏路由匹配。
- 常见问题解决方案
- 语言前缀不生效:核对正则匹配与入口解析流程。
- 路由冲突:确保语言前缀剥离后路由串唯一且可被解析。
- 前端链接不一致:统一使用 route.js 生成链接,避免手动拼接。