fix(i18n): complexity tier names are translated everywhere they're shown

low/medium/high leaked as raw English into the bands legend, the tier badges
on the Routing page, the pair complexity selects, the benchmark's tier column
and the Playground decision panel. New trTier() helper renders the human name
(ru: лёгкий/средний/сложный, de: leicht/mittel/schwer, fr/es/zh/tr likewise);
custom tier names pass through untouched, badge colors stay keyed by the raw
tier. Verified in the browser in Russian and German.
This commit is contained in:
infosave2007
2026-08-08 09:51:38 +03:00
parent 788c87c565
commit 10b5101757
11 changed files with 36 additions and 10 deletions
+5
View File
@@ -48,6 +48,11 @@ export function onLangChange(fn) {
return () => listeners.delete(fn);
}
// Human name for a complexity tier; custom tier names pass through as-is.
export function trTier(tier) {
return ["low", "medium", "high"].includes(tier) ? t("tier." + tier) : tier;
}
export function t(key, vars) {
const d = DICTS[lang] || en;
let s = d[key] ?? en[key] ?? key;
+3
View File
@@ -631,4 +631,7 @@ export default {
"routing.tasks.addModel": "Modell",
"routing.bands.enable": "Eigene Komplexitätsgrenzen",
"routing.bands.hint": "Ziehen Sie die Griffe: wo low endet und high beginnt — die Griffe schieben einander, die Zahlen aktualisieren sich live. Der kontinuierliche Router-Score wird mit IHREN Grenzen neu eingeteilt; ohne Häkchen gelten die Grenzen des Routers.",
"tier.low": "leicht",
"tier.medium": "mittel",
"tier.high": "schwer",
};
+3
View File
@@ -624,4 +624,7 @@ export default {
"routing.tasks.addModel": "model",
"routing.bands.enable": "My complexity boundaries",
"routing.bands.hint": "Drag the handles to set where low ends and where high begins — the handles push each other, the numbers update live. The router's continuous score is re-mapped with YOUR boundaries; unchecked = the router's own bands.",
"tier.low": "low",
"tier.medium": "medium",
"tier.high": "high",
};
+3
View File
@@ -631,4 +631,7 @@ export default {
"routing.tasks.addModel": "modelo",
"routing.bands.enable": "Mis límites de complejidad",
"routing.bands.hint": "Arrastra los tiradores: dónde termina low y dónde empieza high — se empujan entre sí y las cifras se actualizan en vivo. La puntuación continua del router se re-mapea con TUS límites; sin marcar = las bandas del propio router.",
"tier.low": "fácil",
"tier.medium": "medio",
"tier.high": "difícil",
};
+3
View File
@@ -631,4 +631,7 @@ export default {
"routing.tasks.addModel": "modèle",
"routing.bands.enable": "Mes limites de complexité",
"routing.bands.hint": "Faites glisser les poignées : où finit low et où commence high — elles se poussent l'une l'autre, les chiffres se mettent à jour en direct. Le score continu du routeur est re-découpé avec VOS limites ; décoché = les bandes du routeur.",
"tier.low": "facile",
"tier.medium": "moyen",
"tier.high": "difficile",
};
+3
View File
@@ -623,4 +623,7 @@ export default {
"routing.tasks.addModel": "модель",
"routing.bands.enable": "Мои границы сложности",
"routing.bands.hint": "Тяните ручки: где кончается low и где начинается high — ручки толкают друг друга, цифры обновляются на лету. Непрерывный score роутера пересчитывается ВАШИМИ границами; галочка снята — действуют пороги самого роутера.",
"tier.low": "лёгкий",
"tier.medium": "средний",
"tier.high": "сложный",
};
+3
View File
@@ -631,4 +631,7 @@ export default {
"routing.tasks.addModel": "model",
"routing.bands.enable": "Kendi karmaşıklık sınırlarım",
"routing.bands.hint": "Tutamaçları sürükleyin: low nerede biter, high nerede başlar — tutamaçlar birbirini iter, sayılar anlık güncellenir. Yönlendiricinin sürekli puanı SİZİN sınırlarınızla yeniden bölümlenir; işaret kaldırılırsa yönlendiricinin kendi bantları geçerli.",
"tier.low": "kolay",
"tier.medium": "orta",
"tier.high": "zor",
};
+3
View File
@@ -631,4 +631,7 @@ export default {
"routing.tasks.addModel": "模型",
"routing.bands.enable": "自定义复杂度边界",
"routing.bands.hint": "拖动手柄设定 low 到哪里结束、high 从哪里开始 — 手柄会相互推动,数字实时更新。路由器的连续得分将按你的边界重新划分;取消勾选则使用路由器自身的边界。",
"tier.low": "简单",
"tier.medium": "中等",
"tier.high": "困难",
};
+2 -2
View File
@@ -8,7 +8,7 @@
// (keyword checks with | synonyms, think-tag stripping, anti-loop detection,
// pass/partial/fail), now embedded, localized and CDN-free.
import { h, mount, toast, ms, money, moneyEl } from "../ui.js";
import { t } from "../i18n.js";
import { t, trTier } from "../i18n.js";
import { api, testStream } from "../api.js";
// ── built-in suite ──────────────────────────────────────────────────────────
@@ -402,7 +402,7 @@ export async function renderBenchmark() {
: h("span", { class: r.taskOk ? "" : "err" },
(r.taskOk ? "✓ " : "✗ ") + r.task,
r.taskOk ? "" : h("div", { class: "muted small" }, t("bm.expectedLbl") + " " + (r.expected || []).join("/")))),
h("td", {}, r.tier ? h("span", { class: "badge tier-" + r.tier }, r.tier) : "—"),
h("td", {}, r.tier ? h("span", { class: "badge tier-" + r.tier }, trTier(r.tier)) : "—"),
h("td", {},
h("span", { class: "mono small" }, r.chosen || "—"),
r.cost > 0 ? h("div", { class: "muted small" }, moneyEl(r.cost)) : null,
+2 -2
View File
@@ -5,7 +5,7 @@
// Media results are decoded in the browser (PPM → canvas, MJPEG AVI → canvas
// player synced to the wav) — the same no-ffmpeg stance as the engine itself.
import { h, mount, toast, money, moneyEl, ms } from "../ui.js";
import { t } from "../i18n.js";
import { t, trTier } from "../i18n.js";
import { api, testStream, mediaFileUrl, mediaFileBytes } from "../api.js";
import { appState } from "../app.js";
import { ppmToCanvas, fileToPpmB64, aviJpegFrames, mjpegPlayer } from "../media-utils.js";
@@ -206,7 +206,7 @@ export async function renderPlayground() {
decision,
h("div", { class: "card-head" }, h("h3", {}, t("pg.decision"))),
kv(t("pg.task"), c.task_label || "—"),
kv(t("pg.tier"), c.complexity?.tier ? h("span", { class: "badge tier-" + c.complexity.tier }, c.complexity.tier) : "—"),
kv(t("pg.tier"), c.complexity?.tier ? h("span", { class: "badge tier-" + c.complexity.tier }, trTier(c.complexity.tier)) : "—"),
h(
"div",
{ class: "kv", style: "display:block" },
+6 -6
View File
@@ -1,6 +1,6 @@
// Routing — tier editor (ordered model chips), default model, and policy.
import { h, mount, toast } from "../ui.js";
import { t } from "../i18n.js";
import { t, trTier } from "../i18n.js";
import { api } from "../api.js";
import { appState } from "../app.js";
@@ -153,9 +153,9 @@ export async function renderRouting() {
// подписи не наезжают, когда ручки рядом
hMed.querySelector(".bands-val").classList.toggle("raised", me - lo < 0.09);
mount(legend,
h("span", { class: "badge tier-low" }, `low ≤ ${lo.toFixed(2)}`),
h("span", { class: "badge tier-medium" }, `medium ≤ ${me.toFixed(2)}`),
h("span", { class: "badge tier-high" }, `high > ${me.toFixed(2)}`));
h("span", { class: "badge tier-low" }, `${trTier("low")} ≤ ${lo.toFixed(2)}`),
h("span", { class: "badge tier-medium" }, `${trTier("medium")} ≤ ${me.toFixed(2)}`),
h("span", { class: "badge tier-high" }, `${trTier("high")} > ${me.toFixed(2)}`));
track.classList.toggle("disabled", !st.bands);
}
@@ -211,7 +211,7 @@ export async function renderRouting() {
return h(
"div",
{ class: "tierrow" },
h("div", { class: "tname" }, h("span", { class: "badge tier-" + tier }, tier)),
h("div", { class: "tname" }, h("span", { class: "badge tier-" + tier }, trTier(tier))),
chipEditor(st.tiers, tier)
);
}
@@ -254,7 +254,7 @@ export async function renderRouting() {
"select",
{ class: "pair-tier", title: t("routing.tasks.tierHint") },
h("option", { value: "any" }, t("routing.tasks.tierAny")),
...Object.keys(st.tiers).map((tr) => h("option", { value: tr }, tr))
...Object.keys(st.tiers).map((tr) => h("option", { value: tr }, trTier(tr)))
);
tierSel.value = p.tier;
tierSel.addEventListener("change", () => { p.tier = tierSel.value; });