fix(wizard): honor process locale when overrides are blank (#111076)

* fix(wizard): ignore blank locale env overrides

* test(wizard): cover locale fallback chain

* docs(wizard): document locale env precedence

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
LZY3538
2026-07-18 23:28:03 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent ba62b944fa
commit 7055ed578d
5 changed files with 36 additions and 7 deletions
+2 -1
View File
@@ -179,7 +179,7 @@ openclaw onboard --reset --reset-scope full
## Locale
Interactive onboarding uses the CLI wizard locale for fixed setup copy. Resolve order:
Interactive onboarding uses the CLI wizard locale for fixed setup copy. It uses the first nonblank value in this order:
1. `OPENCLAW_LOCALE`
2. `LC_ALL`
@@ -191,6 +191,7 @@ Supported wizard locales are `en`, `zh-CN`, and `zh-TW`. Locale values may use u
```bash
OPENCLAW_LOCALE=zh-CN openclaw onboard
OPENCLAW_LOCALE=en openclaw onboard # Explicit English override
```
## Non-interactive setup
+1 -1
View File
@@ -351,7 +351,7 @@ For hot setup-only paths, prefer the narrow setup helper seams over the broader
Use the broader `plugin-sdk/setup` seam when you want the full shared setup toolbox, including config-patch helpers such as `moveSingleAccountChannelSectionToDefaultAccount(...)`.
Use `createSetupTranslator(...)` for fixed setup wizard copy. It follows the CLI wizard locale (`OPENCLAW_LOCALE`, then system locale variables) and falls back to English. Keep plugin-specific setup text in plugin-owned code and use shared catalog keys only for common setup labels, status text, and official bundled plugin setup copy.
Use `createSetupTranslator(...)` for fixed setup wizard copy. It uses the first nonblank value from `OPENCLAW_LOCALE`, `LC_ALL`, `LC_MESSAGES`, and `LANG`, in that order, then falls back to English. Set `OPENCLAW_LOCALE=en` for an explicit English override. Keep plugin-specific setup text in plugin-owned code and use shared catalog keys only for common setup labels, status text, and official bundled plugin setup copy.
The setup patch adapters stay hot-path safe on import. Their bundled single-account promotion contract-surface lookup is lazy, so importing `plugin-sdk/setup-runtime` does not eagerly load bundled contract-surface discovery before the adapter is actually used.
+4 -3
View File
@@ -38,12 +38,13 @@ the browser through the Control UI. Docs: [Dashboard](/web/dashboard).
## Locale
The wizard localizes fixed onboarding copy. Resolve order: `OPENCLAW_LOCALE`,
`LC_ALL`, `LC_MESSAGES`, `LANG`, then English. Supported locales: `en`,
`zh-CN`, `zh-TW`.
The wizard localizes fixed onboarding copy. It uses the first nonblank value from
`OPENCLAW_LOCALE`, `LC_ALL`, `LC_MESSAGES`, and `LANG`, in that order, then
falls back to English. Supported locales: `en`, `zh-CN`, `zh-TW`.
```bash
OPENCLAW_LOCALE=zh-CN openclaw onboard
OPENCLAW_LOCALE=en openclaw onboard # Explicit English override
```
Product names, commands, config keys, URLs, provider IDs, model IDs, and
+25 -1
View File
@@ -36,12 +36,36 @@ describe("wizard i18n", () => {
});
it("uses OPENCLAW_LOCALE before process locale variables", () => {
vi.stubEnv("OPENCLAW_LOCALE", "zh-TW");
vi.stubEnv("OPENCLAW_LOCALE", "en");
vi.stubEnv("LC_ALL", "zh-CN");
vi.stubEnv("LANG", "zh-TW");
expect(t("wizard.gateway.port")).toBe("Gateway port");
});
it("ignores blank locale overrides when a process locale is available", () => {
vi.stubEnv("OPENCLAW_LOCALE", " ");
vi.stubEnv("LC_ALL", "");
vi.stubEnv("LC_MESSAGES", "zh-CN");
vi.stubEnv("LANG", "en-US");
expect(t("wizard.gateway.port")).toBe("Gateway 端口");
});
it("continues through a blank LC_MESSAGES value to LANG", () => {
vi.stubEnv("OPENCLAW_LOCALE", "");
vi.stubEnv("LC_ALL", " ");
vi.stubEnv("LC_MESSAGES", "\t");
vi.stubEnv("LANG", "zh-TW");
expect(t("wizard.gateway.port")).toBe("Gateway 連接埠");
});
it("uses English when every locale variable is blank", () => {
vi.stubEnv("OPENCLAW_LOCALE", " ");
vi.stubEnv("LC_ALL", "");
vi.stubEnv("LC_MESSAGES", "\t");
vi.stubEnv("LANG", " ");
expect(t("wizard.gateway.port")).toBe("Gateway port");
});
it("falls back to English and interpolates params", () => {
expect(t("wizard.gateway.port", undefined, { locale: "zh-CN" })).toBe("Gateway 端口");
expect(t("wizard.gateway.missing", undefined, { locale: "zh-CN" })).toBe(
+4 -1
View File
@@ -49,7 +49,10 @@ function resolveWizardLocale(value: string | undefined): WizardLocale {
}
function resolveWizardLocaleFromEnv(env: NodeJS.ProcessEnv = process.env): WizardLocale {
return resolveWizardLocale(env.OPENCLAW_LOCALE ?? env.LC_ALL ?? env.LC_MESSAGES ?? env.LANG);
const locale = [env.OPENCLAW_LOCALE, env.LC_ALL, env.LC_MESSAGES, env.LANG].find((value) =>
value?.trim(),
);
return resolveWizardLocale(locale);
}
function readKey(map: WizardTranslationMap, key: string): string | undefined {