diff --git a/ui/src/i18n/lib/translate.ts b/ui/src/i18n/lib/translate.ts index 3b1cfa0978a..0a03226ff42 100644 --- a/ui/src/i18n/lib/translate.ts +++ b/ui/src/i18n/lib/translate.ts @@ -18,20 +18,30 @@ class I18nManager { this.loadLocale(); } - private loadLocale() { + private resolveInitialLocale(): Locale { const saved = localStorage.getItem("openclaw.i18n.locale"); if (isSupportedLocale(saved)) { - this.locale = saved; - } else { - const navLang = navigator.language; - if (navLang.startsWith("zh")) { - this.locale = navLang === "zh-TW" || navLang === "zh-HK" ? "zh-TW" : "zh-CN"; - } else if (navLang.startsWith("pt")) { - this.locale = "pt-BR"; - } else { - this.locale = "en"; - } + return saved; } + const navLang = navigator.language; + if (navLang.startsWith("zh")) { + return navLang === "zh-TW" || navLang === "zh-HK" ? "zh-TW" : "zh-CN"; + } + if (navLang.startsWith("pt")) { + return "pt-BR"; + } + return "en"; + } + + private loadLocale() { + const initialLocale = this.resolveInitialLocale(); + if (initialLocale === "en") { + this.locale = "en"; + return; + } + // Use the normal locale setter so startup locale loading follows the same + // translation-loading + notify path as manual locale changes. + void this.setLocale(initialLocale); } public getLocale(): Locale { @@ -39,12 +49,13 @@ class I18nManager { } public async setLocale(locale: Locale) { - if (this.locale === locale) { + const needsTranslationLoad = !this.translations[locale]; + if (this.locale === locale && !needsTranslationLoad) { return; } // Lazy load translations if needed - if (!this.translations[locale]) { + if (needsTranslationLoad) { try { let module: Record; if (locale === "zh-CN") { diff --git a/ui/src/i18n/test/translate.test.ts b/ui/src/i18n/test/translate.test.ts index 8d6f32ef2d6..b06aa8d2d23 100644 --- a/ui/src/i18n/test/translate.test.ts +++ b/ui/src/i18n/test/translate.test.ts @@ -2,10 +2,10 @@ import { describe, it, expect, beforeEach } from "vitest"; import { i18n, t } from "../lib/translate.ts"; describe("i18n", () => { - beforeEach(() => { + beforeEach(async () => { localStorage.clear(); // Reset to English - void i18n.setLocale("en"); + await i18n.setLocale("en"); }); it("should return the key if translation is missing", () => { @@ -28,4 +28,16 @@ describe("i18n", () => { // but let's assume it falls back to English for now. expect(t("common.health")).toBeDefined(); }); + + it("loads translations even when setting the same locale again", async () => { + const internal = i18n as unknown as { + locale: string; + translations: Record; + }; + internal.locale = "zh-CN"; + delete internal.translations["zh-CN"]; + + await i18n.setLocale("zh-CN"); + expect(t("common.health")).toBe("健康状况"); + }); });