diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..afc51382 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,7 @@ +{ + "name": "OpenCode Development", + "image": "mcr.microsoft.com/devcontainers/javascript-node:1-22-bookworm", + "features": { + "ghcr.io/devcontainers-extra/features/bun:1": {} + } +} \ No newline at end of file diff --git a/packages/web/src/i18n/locales.test.ts b/packages/web/src/i18n/locales.test.ts new file mode 100644 index 00000000..2cde1010 --- /dev/null +++ b/packages/web/src/i18n/locales.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, test } from "bun:test" +import { matchLocale } from "./locales" + +describe("matchLocale", () => { + test("returns null for blank or malformed input", () => { + expect(matchLocale("")).toBeNull() + expect(matchLocale("%E0%A4%A")).toBeNull() + }) + + test("matches exact locale aliases", () => { + expect(matchLocale("en")).toBe("root") + expect(matchLocale("br")).toBe("pt-br") + expect(matchLocale("pt-br")).toBe("pt-br") + }) + + test("matches simplified Chinese locales", () => { + expect(matchLocale("zh")).toBe("zh-cn") + expect(matchLocale("zh-CN")).toBe("zh-cn") + }) + + test("matches traditional Chinese locales", () => { + expect(matchLocale("zh-Hant")).toBe("zh-tw") + expect(matchLocale("zh-TW")).toBe("zh-tw") + expect(matchLocale("zh-HK")).toBe("zh-tw") + }) + + test("matches Portuguese regional locales", () => { + expect(matchLocale("pt-PT")).toBe("pt-br") + }) + + test("matches Norwegian locales", () => { + expect(matchLocale("no-NO")).toBe("nb") + expect(matchLocale("nb-NO")).toBe("nb") + expect(matchLocale("nn-NO")).toBe("nb") + }) + + test("matches supported regional locales", () => { + expect(matchLocale("fr-CA")).toBe("fr") + }) + + test("returns null for unsupported locales", () => { + expect(matchLocale("xx-YY")).toBeNull() + }) +}) \ No newline at end of file diff --git a/packages/web/src/i18n/locales.ts b/packages/web/src/i18n/locales.ts index d1b3e74d..0fade756 100644 --- a/packages/web/src/i18n/locales.ts +++ b/packages/web/src/i18n/locales.ts @@ -97,21 +97,24 @@ export function exactLocale(input: string) { export function matchLocale(input: string) { const value = parse(input) - if (!value) return null + let result: Locale | null = null - if (value.startsWith("zh")) { - if (value.includes("hant") || value.includes("-tw") || value.includes("-hk") || value.includes("-mo")) { - return "zh-tw" + if (value) { + if (value.startsWith("zh")) { + result = + value.includes("hant") || value.includes("-tw") || value.includes("-hk") || value.includes("-mo") + ? "zh-tw" + : "zh-cn" + } else if (value in localeAlias) { + result = localeAlias[value as keyof typeof localeAlias] + } else if (value.startsWith("pt")) { + result = "pt-br" + } else if (value.startsWith("no") || value.startsWith("nb") || value.startsWith("nn")) { + result = "nb" + } else { + result = starts.find((item) => value.startsWith(item[0]))?.[1] ?? null } - return "zh-cn" - } - - if (value in localeAlias) { - return localeAlias[value as keyof typeof localeAlias] } - if (value.startsWith("pt")) return "pt-br" - if (value.startsWith("no") || value.startsWith("nb") || value.startsWith("nn")) return "nb" - - return starts.find((item) => value.startsWith(item[0]))?.[1] ?? null + return result }