test(vscode): extract locale normalization and template interpolation tests

This commit is contained in:
Igor Šćekić
2026-02-20 13:05:27 +01:00
parent 464c3e1541
commit 36c223b55c
3 changed files with 171 additions and 60 deletions
@@ -0,0 +1,94 @@
import { describe, it, expect } from "bun:test"
import { normalizeLocale, resolveTemplate } from "../../webview-ui/src/context/language-utils"
describe("normalizeLocale", () => {
it("returns 'en' for English", () => {
expect(normalizeLocale("en")).toBe("en")
expect(normalizeLocale("en-US")).toBe("en")
expect(normalizeLocale("en-GB")).toBe("en")
})
it("returns 'zh' for Simplified Chinese", () => {
expect(normalizeLocale("zh")).toBe("zh")
expect(normalizeLocale("zh-CN")).toBe("zh")
expect(normalizeLocale("zh-Hans")).toBe("zh")
})
it("returns 'zht' for Traditional Chinese", () => {
expect(normalizeLocale("zh-Hant")).toBe("zht")
expect(normalizeLocale("zh-TW")).toBe("zh")
expect(normalizeLocale("zh-hant-TW")).toBe("zht")
})
it("returns 'de' for German", () => {
expect(normalizeLocale("de")).toBe("de")
expect(normalizeLocale("de-AT")).toBe("de")
})
it("returns 'ko' for Korean", () => {
expect(normalizeLocale("ko")).toBe("ko")
expect(normalizeLocale("ko-KR")).toBe("ko")
})
it("returns 'no' for Norwegian Bokmål", () => {
expect(normalizeLocale("nb")).toBe("no")
expect(normalizeLocale("nb-NO")).toBe("no")
})
it("returns 'no' for Norwegian Nynorsk", () => {
expect(normalizeLocale("nn")).toBe("no")
})
it("returns 'br' for Portuguese", () => {
expect(normalizeLocale("pt")).toBe("br")
expect(normalizeLocale("pt-BR")).toBe("br")
expect(normalizeLocale("pt-PT")).toBe("br")
})
it("falls back to 'en' for unknown locale", () => {
expect(normalizeLocale("xx")).toBe("en")
expect(normalizeLocale("xyz-ZZ")).toBe("en")
})
it("is case-insensitive", () => {
expect(normalizeLocale("EN")).toBe("en")
expect(normalizeLocale("DE")).toBe("de")
expect(normalizeLocale("ZH-HANT")).toBe("zht")
})
})
describe("resolveTemplate", () => {
it("returns text unchanged when no params", () => {
expect(resolveTemplate("hello world")).toBe("hello world")
})
it("returns text unchanged when params is undefined", () => {
expect(resolveTemplate("no {{var}} here", undefined)).toBe("no {{var}} here")
})
it("interpolates a single variable", () => {
expect(resolveTemplate("Hello {{name}}!", { name: "World" })).toBe("Hello World!")
})
it("interpolates multiple variables", () => {
const result = resolveTemplate("{{a}} + {{b}} = {{c}}", { a: "1", b: "2", c: "3" })
expect(result).toBe("1 + 2 = 3")
})
it("replaces missing variable with empty string", () => {
expect(resolveTemplate("{{missing}}", {})).toBe("")
})
it("handles numeric variable values", () => {
expect(resolveTemplate("count: {{n}}", { n: 42 })).toBe("count: 42")
})
it("handles whitespace around key in braces", () => {
expect(resolveTemplate("{{ name }}", { name: "test" })).toBe("test")
})
it("leaves unrelated text intact", () => {
const result = resolveTemplate("prefix {{x}} suffix", { x: "VALUE" })
expect(result).toBe("prefix VALUE suffix")
})
})
@@ -0,0 +1,70 @@
export type Locale =
| "en"
| "zh"
| "zht"
| "ko"
| "de"
| "es"
| "fr"
| "da"
| "ja"
| "pl"
| "ru"
| "ar"
| "no"
| "br"
| "th"
| "bs"
export const LOCALES: readonly Locale[] = [
"en",
"zh",
"zht",
"ko",
"de",
"es",
"fr",
"da",
"ja",
"pl",
"ru",
"ar",
"no",
"br",
"th",
"bs",
]
/**
* Normalize a BCP 47 language tag to one of the supported Locale values.
* Falls back to "en" for unrecognized locales.
*/
export function normalizeLocale(lang: string): Locale {
const lower = lang.toLowerCase()
if (lower.startsWith("zh")) {
return lower.includes("hant") ? "zht" : "zh"
}
for (const loc of LOCALES) {
if (lower.startsWith(loc)) {
return loc
}
}
if (lower.startsWith("nb") || lower.startsWith("nn")) {
return "no"
}
if (lower.startsWith("pt")) {
return "br"
}
return "en"
}
/**
* Perform {{key}} template interpolation against a params record.
*/
export function resolveTemplate(text: string, params?: Record<string, string | number | boolean | undefined>): string {
if (!params) return text
return text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_, rawKey) => {
const value = params[String(rawKey)]
return value === undefined ? "" : String(value)
})
}
@@ -58,43 +58,12 @@ import { dict as kiloBr } from "@kilocode/kilo-i18n/br"
import { dict as kiloTh } from "@kilocode/kilo-i18n/th"
import { dict as kiloBs } from "@kilocode/kilo-i18n/bs"
import { useVSCode } from "./vscode"
import { normalizeLocale as _normalizeLocale, resolveTemplate as _resolveTemplate } from "./language-utils"
export type Locale =
| "en"
| "zh"
| "zht"
| "ko"
| "de"
| "es"
| "fr"
| "da"
| "ja"
| "pl"
| "ru"
| "ar"
| "no"
| "br"
| "th"
| "bs"
export const LOCALES: readonly Locale[] = [
"en",
"zh",
"zht",
"ko",
"de",
"es",
"fr",
"da",
"ja",
"pl",
"ru",
"ar",
"no",
"br",
"th",
"bs",
]
export type { Locale } from "./language-utils"
export { LOCALES } from "./language-utils"
import type { Locale } from "./language-utils"
import { LOCALES } from "./language-utils"
export const LOCALE_LABELS: Record<Locale, string> = {
en: "English",
@@ -137,33 +106,11 @@ const dicts: Record<Locale, Record<string, string>> = {
}
function normalizeLocale(lang: string): Locale {
const lower = lang.toLowerCase()
if (lower.startsWith("zh")) {
return lower.includes("hant") ? "zht" : "zh"
}
for (const loc of LOCALES) {
if (lower.startsWith(loc)) {
return loc
}
}
// Special cases
if (lower.startsWith("nb") || lower.startsWith("nn")) {
return "no"
}
if (lower.startsWith("pt")) {
return "br"
}
return "en"
return _normalizeLocale(lang)
}
function resolveTemplate(text: string, params?: UiI18nParams) {
if (!params) {
return text
}
return text.replace(/\{\{\s*([^}]+?)\s*\}\}/g, (_, rawKey) => {
const value = params[String(rawKey)]
return value === undefined ? "" : String(value)
})
return _resolveTemplate(text, params as Record<string, string | number | boolean | undefined>)
}
interface LanguageProviderProps {