From 10b5101757b61f3ec6f9e9d207c1e7375d33df93 Mon Sep 17 00:00:00 2001 From: infosave2007 Date: Sat, 8 Aug 2026 09:51:38 +0300 Subject: [PATCH] fix(i18n): complexity tier names are translated everywhere they're shown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- web/js/i18n.js | 5 +++++ web/js/i18n/de.js | 3 +++ web/js/i18n/en.js | 3 +++ web/js/i18n/es.js | 3 +++ web/js/i18n/fr.js | 3 +++ web/js/i18n/ru.js | 3 +++ web/js/i18n/tr.js | 3 +++ web/js/i18n/zh.js | 3 +++ web/js/views/benchmark.js | 4 ++-- web/js/views/playground.js | 4 ++-- web/js/views/routing.js | 12 ++++++------ 11 files changed, 36 insertions(+), 10 deletions(-) diff --git a/web/js/i18n.js b/web/js/i18n.js index d277899..4c8bea4 100644 --- a/web/js/i18n.js +++ b/web/js/i18n.js @@ -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; diff --git a/web/js/i18n/de.js b/web/js/i18n/de.js index cc8c5fd..f3b1ecb 100644 --- a/web/js/i18n/de.js +++ b/web/js/i18n/de.js @@ -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", }; diff --git a/web/js/i18n/en.js b/web/js/i18n/en.js index e31d584..80c81cf 100644 --- a/web/js/i18n/en.js +++ b/web/js/i18n/en.js @@ -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", }; diff --git a/web/js/i18n/es.js b/web/js/i18n/es.js index a8dbeb9..72e0632 100644 --- a/web/js/i18n/es.js +++ b/web/js/i18n/es.js @@ -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", }; diff --git a/web/js/i18n/fr.js b/web/js/i18n/fr.js index 6d32b89..887d2f7 100644 --- a/web/js/i18n/fr.js +++ b/web/js/i18n/fr.js @@ -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", }; diff --git a/web/js/i18n/ru.js b/web/js/i18n/ru.js index 9fe231b..47eb081 100644 --- a/web/js/i18n/ru.js +++ b/web/js/i18n/ru.js @@ -623,4 +623,7 @@ export default { "routing.tasks.addModel": "модель", "routing.bands.enable": "Мои границы сложности", "routing.bands.hint": "Тяните ручки: где кончается low и где начинается high — ручки толкают друг друга, цифры обновляются на лету. Непрерывный score роутера пересчитывается ВАШИМИ границами; галочка снята — действуют пороги самого роутера.", + "tier.low": "лёгкий", + "tier.medium": "средний", + "tier.high": "сложный", }; diff --git a/web/js/i18n/tr.js b/web/js/i18n/tr.js index 6492030..5d2bb43 100644 --- a/web/js/i18n/tr.js +++ b/web/js/i18n/tr.js @@ -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", }; diff --git a/web/js/i18n/zh.js b/web/js/i18n/zh.js index a86e412..801fafb 100644 --- a/web/js/i18n/zh.js +++ b/web/js/i18n/zh.js @@ -631,4 +631,7 @@ export default { "routing.tasks.addModel": "模型", "routing.bands.enable": "自定义复杂度边界", "routing.bands.hint": "拖动手柄设定 low 到哪里结束、high 从哪里开始 — 手柄会相互推动,数字实时更新。路由器的连续得分将按你的边界重新划分;取消勾选则使用路由器自身的边界。", + "tier.low": "简单", + "tier.medium": "中等", + "tier.high": "困难", }; diff --git a/web/js/views/benchmark.js b/web/js/views/benchmark.js index edeae8e..140d319 100644 --- a/web/js/views/benchmark.js +++ b/web/js/views/benchmark.js @@ -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, diff --git a/web/js/views/playground.js b/web/js/views/playground.js index 9f4fc56..bb20bc8 100644 --- a/web/js/views/playground.js +++ b/web/js/views/playground.js @@ -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" }, diff --git a/web/js/views/routing.js b/web/js/views/routing.js index 37f97b2..bccf7b5 100644 --- a/web/js/views/routing.js +++ b/web/js/views/routing.js @@ -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; });