Skip to content

feat(lang): 构建时可把整个部署固定为一门语言 - #302

Open
wenjianzhang wants to merge 4 commits into
masterfrom
feat/fixed-locale
Open

feat(lang): 构建时可把整个部署固定为一门语言#302
wenjianzhang wants to merge 4 commits into
masterfrom
feat/fixed-locale

Conversation

@wenjianzhang

Copy link
Copy Markdown
Member

i18n 上线后留下的缺口:没有任何办法把一套部署固定成一门语言src/lang/locales.ts 的优先级只有「访客选过的 → 浏览器语言 → zh-CN」,一个纯中文的内网升级上来,英文浏览器的同事就落在英文界面,运维想让所有人继续看中文只能改代码。

用法

VUE_APP_LOCALE=zh-CN pnpm build:prod     # 或者在 .env.production 里写
  • (仓库里三个 .env 都是空的)—— 现在的行为,访客自己决定
  • zh-CN / en-US —— 全站固定成这门语言,导航栏和登录页的切换器整个不渲染,访客存过的选择也不再生效

三个取舍

固定,不是「改默认」。 它压过 localStorage 里的旧选择。需要这个功能的部署通常已经跑了一阵,有人中途用过切换器——把那批人留在原地,「这套部署是中文的」就对所有人成立,除了最先发现切换器的那几位。代价是「保留切换器、只改默认」这个诉求不支持,真有人提再加第二个变量。

拼错让构建失败,不是静默回退。

Error: VUE_APP_LOCALE is set to "zh_CN", which is not a language this build ships.
Use one of: en-US, zh-CN. Leave it empty to follow the visitor's browser.

否则界面看起来一切正常、只是仍跟着浏览器走,而唯一会发现的人是以为自己已经锁上的运维。判据是 src/lang/ 下有没有对应目录——配置文件 import 不了持有 LOCALES 的模块,所以另配一条单测把目录清单和 LOCALES 绑在一起:以后加语言,两边必须同时改。

切换器整个不渲染,而不是只藏菜单。 两个宿主都往它身上传了 class 和 id,留一个还挂着 right-menu-item 的元素,导航栏那一格就还占着位。

一处我自己引入的缺陷

setLocale 原本无条件 rememberLocale(locale)。固定模式下它会把部署配置写进每个访客的 localStorage——哪天去掉变量重新构建,所有人被钉在从没选过的语言上,切换器回来了但已经替他们答过了。改成固定时不写,访客原本存的值也原样留着。

验证

七条反证,全部真红(每条都先确认退化补丁确实落地):

退化 结果
initialLocale 不先看固定值 3 条红
fixedLocale 不过 isLocale 1 条红
LOCALES 加一门没有目录的语言 一致性断言红
构建不拒绝非法值 2 条红
切换器不隐藏 1 条红
setLocale 无条件写 storage 2 条红
e2e 不清空 VUE_APP_LOCALE 28 失败 1 通过,12.2 分钟全在等超时

真实构建VUE_APP_LOCALE=zh_CN pnpm build:prod 中止并列出可用语言;en-US 构建后 preview 打开——界面英文、#lang-select 数量 0、localStorage 里访客存的 zh-CN 没被覆写;去掉变量重新构建,切换器回来、界面按存的选择变回中文。

单测 350 全绿(新增 18 条),e2e 255 全绿,lint 0 error,首屏 486.65 kB 未超预算。

playwright.config.ts 顺手把这个变量清空——不清的话,shell 里残留一个就够让 i18n 那套跑 12 分钟然后红一片,而屏幕上没有任何线索。

An intranet that runs in one language had no way to say so. Since i18n landed
every visitor's browser decides, so a deployment that upgrades finds its
English-reading staff looking at an English interface, and the only way back
was to edit the source.

The pinned language outranks both the stored choice and the browser. Over the
stored choice on purpose: the deployments that need this have been running
long enough that some of their users already went through the switcher, and
leaving those visitors where they were would make "this deployment is Chinese"
true of everyone except the people who found the switcher first.

A value that is not a language we ship fails the build rather than falling
back. A typo -- zh_CN, zh, cn -- is otherwise a deployment that quietly keeps
following the browser: nothing is missing from the interface, no request fails,
and the one person who would ever notice is the operator who thought they had
pinned it. vite.config.mjs decides by looking for a pack directory under
src/lang, because a config file cannot import the module that holds LOCALES; a
unit test holds the two to each other, so a directory that exists is also a
language the app will accept.

setLocale stops recording the language when the build is pinned. Storage is
where a visitor's own choice lives, and writing a build flag into it would
outlive the flag: unset VUE_APP_LOCALE later and everyone who ever loaded the
site stays in a language they never picked, with the switcher back on screen
but already answered.
A dropdown whose entries the deployment will not honour offers a choice and
then ignores it: the click loads a language pack and repaints, and the next
reload puts everything back, because initialLocale answers to the pinned value
rather than to what was stored.

The whole component drops out rather than the menu inside it. Both hosts pass
a class and an id in, so an element still carrying right-menu-item would keep
its share of the navbar's flex row.
Pinned to one language the switcher is gone, and two dozen tests in
i18n.spec.ts wait out their timeouts against an interface the suite is not
describing: 12 minutes to fail, with nothing on screen to say why. A shell
that still has the variable exported from a build is enough to do it.

Cleared rather than inherited, for the same reason `locale` is pinned to zh-CN
a few lines above.
What the variable does, that a misspelling fails the build rather than being
ignored, that adding a language means both a directory and a LOCALES line now,
and not to export it while running e2e.
Copilot AI lite review requested due to automatic review settings September 9, 2026 03:13

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants