mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-06 17:38:35 +00:00
fix(webui): sync localized preboot copy
This commit is contained in:
parent
1e7518a207
commit
6b5820ea89
@ -90,6 +90,17 @@
|
|||||||
} catch {}
|
} catch {}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<title>nanobot</title>
|
||||||
|
</head>
|
||||||
|
<body class="bg-background text-foreground antialiased">
|
||||||
|
<div id="root">
|
||||||
|
<div class="boot-splash">
|
||||||
|
<div class="boot-splash-inner">
|
||||||
|
<span class="boot-dot" aria-hidden="true"></span>
|
||||||
|
<span data-boot-copy>Loading nanobot…</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
var localeKey = "nanobot.locale";
|
var localeKey = "nanobot.locale";
|
||||||
@ -122,6 +133,10 @@
|
|||||||
boot: "Cargando nanobot…",
|
boot: "Cargando nanobot…",
|
||||||
description: "Interfaz web de nanobot: conversa con tu espacio de trabajo de nanobot."
|
description: "Interfaz web de nanobot: conversa con tu espacio de trabajo de nanobot."
|
||||||
},
|
},
|
||||||
|
"pt-BR": {
|
||||||
|
boot: "Carregando nanobot…",
|
||||||
|
description: "Interface web do nanobot — converse com o seu workspace do nanobot."
|
||||||
|
},
|
||||||
vi: {
|
vi: {
|
||||||
boot: "Đang tải nanobot…",
|
boot: "Đang tải nanobot…",
|
||||||
description: "Giao diện web nanobot — trò chuyện với workspace nanobot của bạn."
|
description: "Giao diện web nanobot — trò chuyện với workspace nanobot của bạn."
|
||||||
@ -149,6 +164,9 @@
|
|||||||
) {
|
) {
|
||||||
return "zh-TW";
|
return "zh-TW";
|
||||||
}
|
}
|
||||||
|
if (lower === "pt" || lower.indexOf("pt-") === 0) {
|
||||||
|
return "pt-BR";
|
||||||
|
}
|
||||||
var base = lower.split("-")[0];
|
var base = lower.split("-")[0];
|
||||||
return copy[base] ? base : "en";
|
return copy[base] ? base : "en";
|
||||||
}
|
}
|
||||||
@ -170,17 +188,6 @@
|
|||||||
} catch {}
|
} catch {}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<title>nanobot</title>
|
|
||||||
</head>
|
|
||||||
<body class="bg-background text-foreground antialiased">
|
|
||||||
<div id="root">
|
|
||||||
<div class="boot-splash">
|
|
||||||
<div class="boot-splash-inner">
|
|
||||||
<span class="boot-dot" aria-hidden="true"></span>
|
|
||||||
<span data-boot-copy>Loading nanobot…</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -165,7 +165,7 @@
|
|||||||
"webuiDefaultAccess": "Acesso padrão",
|
"webuiDefaultAccess": "Acesso padrão",
|
||||||
"cliAppsCatalog": "Catálogo",
|
"cliAppsCatalog": "Catálogo",
|
||||||
"cliAppsFilter": "Filtro",
|
"cliAppsFilter": "Filtro",
|
||||||
"engine": "Engine",
|
"engine": "Motor",
|
||||||
"logs": "Logs",
|
"logs": "Logs",
|
||||||
"diagnostics": "Diagnóstico",
|
"diagnostics": "Diagnóstico",
|
||||||
"contextWindow": "Janela de contexto",
|
"contextWindow": "Janela de contexto",
|
||||||
@ -588,7 +588,7 @@
|
|||||||
"editTitle": "Editar automação",
|
"editTitle": "Editar automação",
|
||||||
"save": "Salvar",
|
"save": "Salvar",
|
||||||
"deleteTitle": "Excluir automação",
|
"deleteTitle": "Excluir automação",
|
||||||
"deleteDescription": "Isso remove {{name}} do store de cron. As mensagens anteriores da conversa permanecem na sessão.",
|
"deleteDescription": "Isso remove {{name}} do armazenamento do cron. As mensagens anteriores da conversa permanecem na sessão.",
|
||||||
"cancel": "Cancelar",
|
"cancel": "Cancelar",
|
||||||
"status": {
|
"status": {
|
||||||
"system": "Sistema",
|
"system": "Sistema",
|
||||||
@ -1152,4 +1152,4 @@
|
|||||||
"absolutePathRequired": "Informe um caminho absoluto de pasta nesta máquina."
|
"absolutePathRequired": "Informe um caminho absoluto de pasta nesta máquina."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { resolve } from "node:path";
|
||||||
|
import { runInNewContext } from "node:vm";
|
||||||
|
|
||||||
import { act, render, screen, waitFor } from "@testing-library/react";
|
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||||
import userEvent from "@testing-library/user-event";
|
import userEvent from "@testing-library/user-event";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
@ -5,7 +9,11 @@ import { describe, expect, it, vi } from "vitest";
|
|||||||
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
||||||
import { ThreadComposer } from "@/components/thread/ThreadComposer";
|
import { ThreadComposer } from "@/components/thread/ThreadComposer";
|
||||||
import { resources } from "@/i18n";
|
import { resources } from "@/i18n";
|
||||||
import { LOCALE_STORAGE_KEY, resolveInitialLocale } from "@/i18n/config";
|
import {
|
||||||
|
LOCALE_STORAGE_KEY,
|
||||||
|
resolveInitialLocale,
|
||||||
|
supportedLocales,
|
||||||
|
} from "@/i18n/config";
|
||||||
|
|
||||||
const QUICK_ACTION_KEYS = ["plan", "analyze", "brainstorm", "code", "summarize", "more"];
|
const QUICK_ACTION_KEYS = ["plan", "analyze", "brainstorm", "code", "summarize", "more"];
|
||||||
const IMAGE_QUICK_ACTION_KEYS = ["icon", "sticker", "poster", "product", "portrait", "edit"];
|
const IMAGE_QUICK_ACTION_KEYS = ["icon", "sticker", "poster", "product", "portrait", "edit"];
|
||||||
@ -128,6 +136,46 @@ const LOCALIZED_WORKSPACE_COPY_KEYS = [
|
|||||||
"workspace.dialog.usePath",
|
"workspace.dialog.usePath",
|
||||||
"workspace.dialog.absolutePathRequired",
|
"workspace.dialog.absolutePathRequired",
|
||||||
];
|
];
|
||||||
|
const INDEX_HTML = readFileSync(resolve(process.cwd(), "index.html"), "utf8");
|
||||||
|
const PREBOOT_SCRIPT = INDEX_HTML.match(
|
||||||
|
/<script>\s*(\(function \(\) \{\s*var localeKey = "nanobot\.locale";[\s\S]*?\}\)\(\);)\s*<\/script>/,
|
||||||
|
)?.[1];
|
||||||
|
const BOOT_COPY_MARKUP = '<span data-boot-copy>Loading nanobot…</span>';
|
||||||
|
|
||||||
|
function runPrebootLocale(storedLocale: string) {
|
||||||
|
if (!PREBOOT_SCRIPT) throw new Error("Could not find the preboot locale script in index.html");
|
||||||
|
|
||||||
|
const documentElement = { lang: "" };
|
||||||
|
const meta = {
|
||||||
|
content: "",
|
||||||
|
setAttribute(name: string, value: string) {
|
||||||
|
if (name === "content") this.content = value;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const boot = { textContent: "" };
|
||||||
|
|
||||||
|
runInNewContext(PREBOOT_SCRIPT, {
|
||||||
|
localStorage: {
|
||||||
|
getItem: (key: string) => key === LOCALE_STORAGE_KEY ? storedLocale : null,
|
||||||
|
},
|
||||||
|
navigator: { languages: [], language: "en" },
|
||||||
|
document: {
|
||||||
|
documentElement,
|
||||||
|
querySelector: (selector: string) => {
|
||||||
|
if (selector === '[data-i18n-meta="description"]') return meta;
|
||||||
|
if (selector === "[data-boot-copy]") return boot;
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
lang: documentElement.lang,
|
||||||
|
boot: boot.textContent,
|
||||||
|
description: meta.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||||
return !!value && typeof value === "object" && !Array.isArray(value);
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
@ -156,6 +204,39 @@ function interpolationKeys(value: unknown): string[] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("webui i18n", () => {
|
describe("webui i18n", () => {
|
||||||
|
it("runs preboot localization after the splash copy exists", () => {
|
||||||
|
expect(INDEX_HTML.indexOf(BOOT_COPY_MARKUP)).toBeGreaterThan(-1);
|
||||||
|
expect(INDEX_HTML.indexOf(PREBOOT_SCRIPT ?? "")).toBeGreaterThan(
|
||||||
|
INDEX_HTML.indexOf(BOOT_COPY_MARKUP),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("keeps preboot copy aligned with every registered locale", () => {
|
||||||
|
for (const { code } of supportedLocales) {
|
||||||
|
const result = runPrebootLocale(code);
|
||||||
|
const expected = resources[code].common.app;
|
||||||
|
|
||||||
|
expect({ code, ...result }).toEqual({
|
||||||
|
code,
|
||||||
|
lang: code,
|
||||||
|
boot: expected.loading.boot,
|
||||||
|
description: expected.meta.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("normalizes Portuguese locales before the app bundle loads", () => {
|
||||||
|
const expected = resources["pt-BR"].common.app;
|
||||||
|
|
||||||
|
for (const locale of ["pt", "pt-PT"]) {
|
||||||
|
expect(runPrebootLocale(locale)).toEqual({
|
||||||
|
lang: "pt-BR",
|
||||||
|
boot: expected.loading.boot,
|
||||||
|
description: expected.meta.description,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("defaults to English until the user chooses another language", () => {
|
it("defaults to English until the user chooses another language", () => {
|
||||||
localStorage.removeItem(LOCALE_STORAGE_KEY);
|
localStorage.removeItem(LOCALE_STORAGE_KEY);
|
||||||
expect(resolveInitialLocale()).toBe("en");
|
expect(resolveInitialLocale()).toBe("en");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user