[开发]多语言改造方案与使用说明

👑Lv.11 元老 🌞 骨干会员
2026-10-24 00:20:29

本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:内部文档归档。

多语言改造方案与使用说明

本篇由 3 篇原始文档合并去重而成:

- 《多语言改造计划》(i18n 总体方案稿):改动体量摸底、改造范围边界、语言包与翻译函数机制、分层接入、缓存隔离、分阶段实施计划;

- 《多语言改造进度总结_20260817》(2026-08-17 快照):阶段 1~3 完成、阶段 4 部分完成的落地进度、关键设计约定与验证方式沉淀;

- 《多语言使用说明》(适用版本 zh/en 双语):面向管理员/用户的使用文档(设置入口、切换方式、判定顺序、语言包格式)。

三篇中重复的语言判定顺序、缓存分键规则、语言包格式与 key 规范已去重,各保留一份;插件目录(plugins/)按约定未改造,其注入内容保持中文。

一、背景与目标

  • 基线:FlintHub 1.0(SplitDB 纯 SQLite 分片引擎);PHP 8.0.2(SQLite 3.33.0);全站硬编码简体中文;数据规模 19.2 万帖 / 5 万用户。
  • 改动体量摸底(全站 PHP/JS/CSS 中文字符扫描):全站硬编码中文约 3.6 万,其中约 40% 是代码注释,真正需要翻译的用户可见文案约 1.5~1.8 万字,折合约 900~1200 条独立文案;分布集中于前台 Views(54 个,7037 字符)、前台 Controllers(13 个)、assets/js(6 个,editor/thread/auth),后台 Controllers(17 个)与 Helpers(20 个)次之。
  • 目标:为 FlintHub 增加多语言能力(默认简体中文 + 英文,架构支持扩展更多语言)。
  • 范围边界:
  • 翻译:界面 UI 文案(按钮/标题/提示/表单标签/错误消息)、邮件模板(注册验证/找回密码/通知)、编辑器/前端 JS 提示、时间格式(timeAgo 等);
  • 不翻译:帖子/回复正文等用户内容、代码注释(Models/SplitDB/Core)、CSS 注释/类名/HTML 结构;
  • 默认不做(列为后续可选):版块名/分类名/标签名等数据库内容(如需可加字段,见 2.7)、审计日志 detail 与队列任务描述。
  • 结论:改动量"中等偏大、但结构清晰"——数据引擎(SplitDB/Model 内核)零翻译,核心是"文案抽取 + 渲染层注入",需小心缓存/语言切换联动。

二、方案设计 / 实施细则

2.1 语言包与翻译函数(核心机制)

  • 语言包目录 lang/(站点根目录,与 config.php 平级;纯数据文件非类,不走 Autoloader):
lang/
├── zh.php          # 简体中文(默认)
├── en.php          # 英文
└── (未来扩展) de.php / ja.php / ...

  • 语言包为纯数据 PHP 数组文件,I18n::load() 直接懒加载(opcache 缓存数组,不重复 include);
  • 格式:扁平 key => 文案数组,key 语义化点分命名(如 forum.latest、thread.reply_btn、error.bucket_full),值支持 {placeholder} 占位符;
  • 新增 app/Helpers/I18n.php:
  • I18n::get($key, $params = []) — 取当前语言文案,缺 key 回退中文,再缺回退 key 本身(永不白屏);
  • I18n::current() / I18n::set($lang) — 当前语言(Session/Cookie)读写;
  • I18n::available() — 可用语言列表(由扫描 lang/ 目录得出);
  • I18n::load($lang) — 懒加载语言包;
  • 语言码白名单清洗:非 [a-z][a-z0-9_-]* 一律回退 zh,防路径穿越;
  • jsSubset() — 供 JS 注入的语言包子集;
  • 视图层快捷函数:TemplateCompiler 新增 t($key, $params = []) 方法(与现有 $this->e()/icon()/url() 并列);PHP 直渲视图(大部分前台视图是 PHP 直渲)直接用 $this->t(),零编译改动、风险最低;模板语法注册 {$t:key} 回调(编译为 <?php echo $this->t('key'); ?>),两套写法都可用;
  • 控制器/Helper 层:直接 \app\Helpers\I18n::get() 或全局函数 t()(命名空间无冲突,简单注册);
  • JS 端文案载体:layouts/main.php 注入 window.FlintHubI18n(jsSubset 子集)+ 全局 window.__t(key, params) + Alpine.store('i18n', {t, dict});JS 内提示改用 window.__t('js.');x-text="i18n.t('key')" 可响应式渲染,切语言后自动更新;子集仅注入 js. + lang.*(约 57 条),不把上千条文案全量输出到页面。

2.2 语言选择与持久化

判定顺序(从高到低):URL ?lang= > Cookie flinthub_lang > Session lang > 后台设置 site_lang > 浏览器 Accept-Language > 中文兜底。

通道优先级实现
URL 参数 ?lang=en最高(临时切换)写入 Session/Cookie 后 PRG 回跳原页(去参,URL 恢复干净,防刷新丢参)
Cookie flinthub_lang高(跨会话)登录/游客均生效,有效期 30 天
Session lang中同会话内保持
后台设置 site_lang低(默认兜底)Settings 表新键,默认 zh
浏览器 Accept-Language最低仅首访无任何记录时探测:en 开头 → en,否则 zh

  • 语言切换入口:前台页头放「中 / EN」切换下拉(仅在有 ≥2 种语言时显示),用 Alpine x-data 实现(非无状态裸 JS),点击后跳 ?lang=en → 服务端写入 Cookie/Session 后整页 PRG 刷新回原页;admin 分支导航共用同一入口,后台页也可切语言;
  • ★ 切换方式取舍:整页刷新,而非 htmx 局部刷新——游客语言切换命中前置静态缓存链路(pages/ 按语言分键),htmx 局部请求绕不开该链路且易出现"半中文半英文";登录态交互区(投票等)维持现有 htmx 用法不变;
  • ★ htmx 请求语言标识:凡 htmx 局部请求(/api/*、投票/上传/抓图等)在 htmx 层统一附加语言(hx-headers 或请求参数 lang=),服务端语言判定优先读该标识,防止局部刷新片段回退默认语言;
  • 加载时机:app/init.php 早期(Session 启动后、视图渲染前)初始化 I18n。

2.3 各层接入方式(按改动由小到大)

层改动方式示例
视图(文案量最大)硬编码中文 → <?php echo $this->t('forum.latest'); ?><h2>置顶主题</h2> → $this->t('forum.pinned')
控制器redirect 提示/错误消息 → t() 或 I18n::getredirect('/thread/x?error=' . urlencode(t('error.no_perm')))
JSwindow.__t() / Alpine.store('i18n')确认删除? → __t('confirm_delete')
邮件emails/ 视图与 Mailer 主题行 → t()主题更新通知 → t('mail.thread_updated')
timeAgo / 分页TemplateCompiler::timeAgo() / pagination() 内的中文映射 → 语言包天前/小时前/刚刚;上一页/下一页/共 X 页

2.4 缓存隔离(技术关键点,最容易被忽略的坑)

缓存风险处理
前置静态缓存 pages/(index.php + PageCache)⚠️ 游客缓存与语言绑定:若中文页面被缓存,英文游客会命中中文页缓存键追加语言后缀:中文不加后缀(home_1,存量缓存零影响),非默认语言加 _en(home_1_en);PageCache::serve/render 的 key 由调用方拼语言
Settings 三层缓存无语言字段,无风险不动
统计/快照缓存纯数字,无风险不动
浏览器 HTTP 缓存静态资源无文案不动

  • 失效逻辑必须全语言失效:发帖/回帖 invalidate 时扫目录按前缀删,或建 pages/{lang}/ 子目录整体删,避免出现「切语言看到旧缓存」;
  • 缓存文件数 × 语言数,总量可控(每语言只缓存当前页数)。

2.5 分阶段实施计划(P41-i18n-1 ~ 5,每阶段可独立验证)

阶段内容验证
P41-i18n-1 基础设施lang/zh.php / lang/en.php(约 300 key 起步);app/Helpers/I18n.php;TemplateCompiler 的 t() + {$t:key};app/init.php 语言初始化;SettingsController + settings_basic「默认语言」下拉;PageCache 缓存按语言分键默认中文零变化回归;?lang=en 切英文;缓存分键不串
P41-i18n-2 前台界面前台 Views 全部可见文案抽取(forum/thread/post/blog/home/search/tags/profile/message/auth);前台 Controllers redirect 提示/错误消息抽取;页头语言切换入口英文下全链路走查(发帖/回帖/搜索/登录/注册/个人中心)
P41-i18n-3 前端 JS + 邮件editor.js / thread.js / auth.js 文案 → window.__t();emails/ 视图 + Mailer 主题行编辑器工具栏英文、邮件英文
P41-i18n-4 后台 + 收尾后台 Views/Controllers 文案抽取;timeAgo/分页/日期本地化;en.php 补全到 100%(diff key 集合,缺 key 报警);update/ 镜像 + CHANGELOG + 使用说明—
P41-i18n-5 注释精简(独立并行)逐文件人工压缩冗长注释、删除确无用注释;保持 UTF-8 without BOM + LF;每文件 php -l + fc /b 镜像比对全量 php -l 0 错误;镜像一致;抽样视觉核对无异常

注释精简原则(与 i18n 独立、可并行):精简要义不整段删;确无用才删(有历史决策价值的保留一句话);逐文件处理,严禁 sed/正则全局替换(防破坏字符串/编码);不改逻辑、不碰字符串文案与 CSS 规则;建议先注释精简再抽文案,或同一文件一并处理。

2.6 使用说明(面向管理员与用户)

管理员:设置站点默认语言

  • 入口:后台 → 系统设置 → 基本信息(/admin/settings/basic)→ 站点语言(site_lang);
  • 可选值:zh(中文,默认)/ en(English);
  • 该设置在语言判定顺序中排第 4 位,仅在用户没有通过 URL / Cookie / Session 指定语言时生效;保存后立即生效,已设置过语言的用户不受影响。

用户:切换界面语言

  • 页面下拉切换(前台 + 后台共用):页头右侧语言切换下拉框(仅当语言包数量 ≥ 2 时显示)→ 选择后页面以 ?lang=en 重新加载(PRG:写 Cookie 后去参回跳,URL 恢复干净);选择会写入 Cookie flinthub_lang(30 天)+ Session lang,下次访问自动记住;
  • URL 参数临时切换(无 Cookie 副作用):任意页面 URL 后追加 ?lang=en(或 ?lang=zh)即可单次预览该语言,如 https://你的域名/?lang=en;非法语言码会被白名单清洗回退 zh。

语言包文件与 key 命名规范

  • 语言包位于站点根目录 lang/ 下,文件名即语言码(zh.php / en.php),为纯 PHP 数组,key 命名点分语义化:
前缀用途示例
forum. / thread. / home.前台页面forum.latest、thread.reply
admin.后台管理admin.users_title、admin.db_archive
auth. / profile. / message.账户与消息auth.login_btn
js.前端 JS(注入 window.__t)js.pwd_weak
mail.邮件主题/正文mail.register_subject
time. / pagination.时间相对化 / 分页time.days_ago、pagination.prev
lang.语言名(下拉框显示)lang.zh、lang.en

  • 占位符:值中可用 {name},调用时传参替换(如 I18n::get('admin.batch_deleted', ['count' => 5]));
  • 回退链(永不白屏):目标语言缺 key → 中文 → key 本身;
  • 新增语言的步骤:① 复制 lang/en.php 为 lang/{新语言码}.php(如 lang/ja.php);② 翻译所有 value,key 与 zh/en 完全一致;③ 页面下拉框自动出现新语言(I18n::available() 扫描 lang/ 目录);④ 校验对齐:双向 array_diff_key 必须 0/0。

代码中使用文案的方式

位置写法
视图(模板)<?php echo $this->t('key', ['p' => $v]); ?> 或模板内 {$t:key}
控制器 / Helper\app\Helpers\I18n::get('key', ['p' => $v]) 或全局函数 t('key', [...])
前端 JSwindow.__t('key', {p: v})(key 须为 js. / lang. 前缀,页面已注入子集)

常见问题

  • 切英文后某些按钮还是中文?——plugins/ 插件注入内容按约定未改造;其余位置若发现遗漏,检查对应文案是否仍是硬编码中文,应改为 t('key') 并补语言包;
  • 改语言包不生效?——确认文件保存为 UTF-8 without BOM;若开了页面缓存,可在后台「维护 → 清理缓存」后重试;
  • 加了新 key 只在一种语言生效?——必须 zh/en(及所有语言包)同步补 key,否则非默认语言会走回退链显示中文或 key 本身。

2.7 数据层多语言(可选扩展,默认不做)

如需「版块名/分类名」随语言切换(内容级 i18n):categories 表加 name_en 列(幂等 ALTER,同 P41 ensureArchivedColumn 模式);模型读取时按 I18n::current() 选择列。这属于内容翻译(需人工翻译所有版块名),默认列为后续可选项,本计划主体不做。

三、落地情况 / 进度

进度快照(2026-08-17),计划已确认、五阶段串行实施,插件暂不改造。最新状态以 会话交接文档_20260817_P34版.md 为准。

阶段任务状态
1-基础设施lang 语言包 / I18n / 编译回调 / init 初始化 / 缓存分键✅ 完成
1-验证默认中文零变化 + ?lang=en 切换✅ 完成(21 项冒烟通过)
2-前台前台 Views / Controllers 文案抽取、语言切换入口✅ 完成
2-验证英文下发帖/回帖/登录/注册/搜索链路✅ 完成(5 项验证通过)
3-JS邮件editor.js / auth.js 改 __t() + Alpine.store('i18n')、Mailer 主题行✅ 完成
3-验证编辑器工具栏 / 邮件英文正常✅ 完成(9 项验证通过)
4-后台后台 Views/Controllers 文案抽取🔶 部分完成
4-收尾timeAgo/分页本地化、en 对齐、update 镜像+CHANGELOG⬜ 未开始
5-注释精简逐文件压缩注释(不保留 Pxx 编号)+ php -l + fc /b⬜ 未开始

阶段 1(基础设施,全部完成):

文件内容
lang/zh.php、lang/en.php语言包 899 个 key,zh/en 完全对齐(0 缺失);key 语义化点分命名,值支持 {placeholder}
app/Helpers/I18n.php懒加载 / 判定顺序(URL>Cookie>Session>后台设置>浏览器>中文兜底)/ 回退链(en→zh→key)/ 语言码白名单清洗防路径穿越 / jsSubset() 供 JS 注入
app/Core/TemplateCompiler.php新增 t() 方法 + {$t:key} 模板回调
app/init.php?lang= PRG 切换(去参回跳)+ 语言探测预热
app/Helpers/PageCache.php语言分键:中文不加后缀(存量缓存零影响),非默认语言加 _en;invalidate 全语言失效

阶段 2(前台,全部完成):28 个前台视图全部抽取为 $this->t('key')(forum/home/thread/post/auth/blog/search/tags/profile/message/errors/layouts/_components);13 个前台控制器的 redirect 提示 / 错误消息 / 邮件正文全部抽取(Api/Attachment/Auth/Blog/Forum/Home/Message/Post/Profile/Search/Tags/Theme/Thread);layouts/main.php 页头语言切换下拉(Alpine 交互,≥2 语言才显示,保留当前 URL 的 ?lang= 切换);邮件正文(AuthController 注册/找回/重发)改用语言包拼接。

阶段 3(JS / 邮件,全部完成):layouts/main.php 注入 window.FlintHubI18n(jsSubset 子集)+ 全局 window.__t(key, params) + Alpine.store('i18n', {t, dict});assets/js/editor.js 工具栏按钮 / prompt / alert / 字数统计 / 上传提示等全部改 window.__t('js.');assets/js/auth.js 密码强度(弱/中/强)改 __t('js.pwd_');app/Helpers/Mailer.php 默认发件人名称 mail.site_name;emoji 名称数组保留中文(属内容数据,非界面文案),thread.js 无可见文案。验证 9 项通过(__t() key 全部存在于语言包、jsSubset 含 js.+lang. 57 条、mail.* 17 条 zh/en 对齐、布局含 store/__t/FlintHubI18n 注入)。

阶段 4(后台,🔶 部分完成):

  • 已完成 26/27 个后台视图:_sidebar、users、threads、categories、blog_categories、trashed、tags、levels、blog_manage、user_edit、groups、settings_display、thread_edit、verify、database、themes、index、permissions、mail_settings、plugins、plugin-edit、maintenance、queue_settings、search_settings、settings_basic、database/overview;
  • ⚠️ app/Views/admin/database/settings.php — 只抽取了开头部分(标题/成功消息/actionLabels/全局警告块/桶数调整区),后半部分(自动扩容/单桶容量/归档/扩容记录/一致性检查,约 801 字符残留)未抽取;语言包 key 已全部补好(admin.db_* 前缀),直接套用即可;
  • ⚠️ 17 个后台控制器完全未开始:app/Controllers/Admin/*(Base/Blog/Category/Dashboard/Database/Group/Level/Log/Maintenance/Plugin/Settings/Tag/Theme/Thread/Trashed/User/Verify)。

阶段 4 收尾(未开始):#15 timeAgo/分页本地化(TemplateCompiler 的 timeAgo/pagination 方法用 time. / pagination. key);#16 en.php 与 zh.php 完全对齐(当前已 899 对齐,随新增 key 需复检);#17 update/ 镜像 + CHANGELOG + 使用说明。

阶段 5 注释精简(未开始):逐文件压缩冗长注释、删除确无用注释,不保留 Pxx 编号(用户明确要求);每文件 php -l + 对照 fc /b(或字节数)确认仅注释差异。

关键设计约定(新会话必读):

  1. 默认中文零变化:中文不加缓存键、t() 缺 key 回退 key 本身,全站默认行为与改造前一致;
  2. 语言判定顺序:URL ?lang= > Cookie flinthub_lang > Session lang > 后台 site_lang > 浏览器 Accept-Language > 中文兜底;
  3. 缓存分键:home_1(中文)/ home_1_en(英文);invalidate 删全目录;
  4. JS 文案:布局注入 window.__t(key, params)(读 window.FlintHubI18n 子集),不依赖 Alpine 初始化时序,可直接在原生 JS 使用;
  5. 后台页也可切语言:admin 分支导航已含语言切换入口(共用 main.php 注入);
  6. 插件不改造:plugins/ 目录全部不动,插件钩子注入的 HTML 保持中文;
  7. 注释阶段规则:注释精简时 Pxx 编号一并删除,仅改注释不改逻辑;JS/HTML 注释里的中文属注释范畴,不参与文案抽取。

验证方式沉淀:语法 php -l <file>(每批修改后必跑);JS 语法 node --check assets/js/.js;冒烟 cli/_tmp_verify_i18n.php 临时脚本模式(语言包加载/回退/缓存分键/子集/残留扫描),用后即删;残留扫描(PHP 脚本剔除注释后统计视图/控制器可见中文,定位遗漏);语言包对齐 array_diff_key zh/en 双向对比,必须 0/0。

四、影响面与回滚

  • 影响面:默认中文行为零变化——site_lang=zh 且用户无任何语言记录时,行为与现状完全一致(中文直出、缓存键恢复原样 home_1,避免存量缓存失效风暴);未涉及 SplitDB 引擎、Models 内核、config.php、install.php 的改动(除非做 §2.7 数据层多语言);插件目录不改造;语言判定纯读取,不引入迁移;
  • 回滚:缺 key 回退链(目标语言 → 中文 → key 本身)保证绝不白屏/报错;t() 与 e() 分离,输出处继续按现有习惯转义;邮件/审计日志等"管理员向"内容可保持中文;
  • 风险与注意事项:
  1. 静态缓存按语言分键是最大风险点:漏改则 en 游客看到中文缓存页,需在阶段 1 就完成并全语言失效;
  2. 文案量大且分散(约 900~1200 条),人工替换容易漏——建议脚本辅助(扫描中文字符串,人工逐个确认),避免盲改破坏 HTML/JS 字符串;
  3. JS 中文在单引号/模板字符串中的抽取需逐处确认引号与转义(editor.js 大量内联中文);
  4. en 语言包质量:机器翻译的英文提示可能生硬,核心高频文案建议人工校稿;
  5. 时间本地化:timeAgo 与 date 格式保持服务器时区(Asia/Shanghai),仅文案本地化;
  6. SEO:URL 结构不变(不引入 /en/ 路径前缀),hreflang 可后续加,本计划不涉及;
  7. 注释精简风险:严禁 sed/正则批量替换;删除"看似没用"注释前确认无历史决策价值;改注释后必须 php -l + fc /b 镜像比对,防止误删语法关键行;CSS 只删注释不动规则;
  8. 工作量估算:基础设施 1~2 个会话;前台+JS 3~4 个会话;后台+收尾 2~3 个会话;注释精简约 2~3 个会话(按全量人工校稿计)。

五、遗留事项

  • 阶段 4 未完成项:app/Views/admin/database/settings.php 后半段抽取(约 801 字符,语言包 key 已备好可直接套用)+ 17 个后台控制器文案抽取;
  • 阶段 4 收尾:#15 timeAgo/分页本地化、#16 en.php 复检对齐、#17 update/ 镜像 + CHANGELOG + 使用说明;
  • 阶段 5 注释精简未开始(逐文件人工,Pxx 编号一并删除,仅改注释不改逻辑);
  • 数据层多语言(categories 加 name_en)为后续可选项,未并入本计划;
  • SEO hreflang 标签后续可加,本计划不涉及;
  • 插件目录(plugins/)注入内容按约定保持中文,后续如需多语言属独立子项。

本篇由《多语言改造计划》《多语言改造进度总结_20260817》《多语言使用说明》合并去重整理而成,原文路径见文首 merged_from 列表。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| 浏览 1 次 | 回复 0 次

全部回复 (0)

暂无回复

请 登录