mirror of
https://github.com/rustfs/console.git
synced 2026-09-19 01:47:44 +08:00
fix: complete i18n configuration and translations
Normalize locale detection, sync document language direction, complete locale keys, and add i18n regression coverage. Refs rustfs/rustfs#2735
This commit is contained in:
@@ -0,0 +1,27 @@
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
|
||||
import { DEFAULT_LOCALE, getLocaleFile, isRtlLocale, normalizeLocale } from "../../lib/i18n-config.js"
|
||||
|
||||
test("normalizeLocale accepts supported, browser, and legacy locale values", () => {
|
||||
assert.equal(DEFAULT_LOCALE, "en")
|
||||
assert.equal(normalizeLocale("en-US"), "en")
|
||||
assert.equal(normalizeLocale("zh_CN"), "zh")
|
||||
assert.equal(normalizeLocale("zhCN"), "zh")
|
||||
assert.equal(normalizeLocale("pt-BR"), "pt")
|
||||
assert.equal(normalizeLocale("pt_BR"), "pt")
|
||||
assert.equal(normalizeLocale("ar-MA"), "ar")
|
||||
assert.equal(normalizeLocale("unknown"), "en")
|
||||
})
|
||||
|
||||
test("getLocaleFile resolves normalized locales to bundled locale files", () => {
|
||||
assert.equal(getLocaleFile("en-US"), "en-US")
|
||||
assert.equal(getLocaleFile("zh_CN"), "zh-CN")
|
||||
assert.equal(getLocaleFile("pt-BR"), "pt-BR")
|
||||
})
|
||||
|
||||
test("isRtlLocale detects right-to-left languages after normalization", () => {
|
||||
assert.equal(isRtlLocale("ar-MA"), true)
|
||||
assert.equal(isRtlLocale("ar"), true)
|
||||
assert.equal(isRtlLocale("en-US"), false)
|
||||
})
|
||||
@@ -0,0 +1,88 @@
|
||||
import test from "node:test"
|
||||
import assert from "node:assert/strict"
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const localeDir = path.resolve("i18n/locales")
|
||||
const sourceRoots = ["app", "components", "contexts", "hooks", "lib", "config"]
|
||||
const sourceExtensions = new Set([".ts", ".tsx", ".js", ".jsx"])
|
||||
|
||||
function readLocale(file) {
|
||||
return JSON.parse(fs.readFileSync(path.join(localeDir, file), "utf8"))
|
||||
}
|
||||
|
||||
function walkSourceFiles(dir, files = []) {
|
||||
if (!fs.existsSync(dir)) return files
|
||||
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const filePath = path.join(dir, entry.name)
|
||||
if (entry.isDirectory()) {
|
||||
if (entry.name !== "node_modules") walkSourceFiles(filePath, files)
|
||||
continue
|
||||
}
|
||||
|
||||
if (sourceExtensions.has(path.extname(entry.name))) files.push(filePath)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
function extractLiteralTranslationKeys(source) {
|
||||
const keys = new Set()
|
||||
const translationCall = /\bt\(\s*(["'`])((?:\\.|(?!\1)[\s\S])*?)\1/g
|
||||
let match
|
||||
|
||||
while ((match = translationCall.exec(source))) {
|
||||
if (!match[2].includes("${")) {
|
||||
keys.add(match[2].replace(/\\'/g, "'").replace(/\\"/g, '"'))
|
||||
}
|
||||
}
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
function extractPlaceholders(value) {
|
||||
return [...value.matchAll(/\{[^{}]+\}/g)].map(([placeholder]) => placeholder).sort()
|
||||
}
|
||||
|
||||
test("all locale files provide the same keys as en-US", () => {
|
||||
const referenceKeys = Object.keys(readLocale("en-US.json")).sort()
|
||||
const localeFiles = fs
|
||||
.readdirSync(localeDir)
|
||||
.filter((file) => file.endsWith(".json"))
|
||||
.sort()
|
||||
|
||||
for (const file of localeFiles) {
|
||||
assert.deepEqual(Object.keys(readLocale(file)).sort(), referenceKeys, file)
|
||||
}
|
||||
})
|
||||
|
||||
test("en-US contains every literal translation key used by the app", () => {
|
||||
const enUS = readLocale("en-US.json")
|
||||
const usedKeys = new Set()
|
||||
|
||||
for (const file of sourceRoots.flatMap((root) => walkSourceFiles(root))) {
|
||||
for (const key of extractLiteralTranslationKeys(fs.readFileSync(file, "utf8"))) {
|
||||
usedKeys.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
const missing = [...usedKeys].filter((key) => !(key in enUS)).sort()
|
||||
assert.deepEqual(missing, [])
|
||||
})
|
||||
|
||||
test("translated locale values preserve interpolation placeholders", () => {
|
||||
const enUS = readLocale("en-US.json")
|
||||
const localeFiles = fs
|
||||
.readdirSync(localeDir)
|
||||
.filter((file) => file.endsWith(".json") && file !== "en-US.json")
|
||||
.sort()
|
||||
|
||||
for (const file of localeFiles) {
|
||||
const locale = readLocale(file)
|
||||
|
||||
for (const [key, value] of Object.entries(enUS)) {
|
||||
assert.deepEqual(extractPlaceholders(locale[key]), extractPlaceholders(value), `${file}: ${key}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user