From c2f3838485a78b12246a480cda564feb5a19255e Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sun, 13 Sep 2026 12:44:51 +0500 Subject: [PATCH] fix: worker use only i18n keys instead of full i18n --- web/src/lib/components/SchemaPreview.svelte | 3 ++ .../lib/components/SchemaResultTile.svelte | 3 ++ .../lib/components/SchemaTextResult.svelte | 8 +++-- web/src/lib/components/SchemaToolView.svelte | 20 ++++++++++-- web/src/lib/executor/executor.ts | 7 ++++- web/src/lib/executor/executor.worker.ts | 10 ++++++ web/src/lib/i18n/schema-tool-strings.ts | 31 ++++++++++++++++--- web/src/lib/registry/analyze.ts | 3 +- web/src/lib/registry/index.ts | 17 +++++----- web/src/lib/registry/types.ts | 13 +++++++- web/src/routes/+page.svelte | 17 +++++----- web/src/routes/list-tools/+page.svelte | 17 +++++----- 12 files changed, 111 insertions(+), 38 deletions(-) diff --git a/web/src/lib/components/SchemaPreview.svelte b/web/src/lib/components/SchemaPreview.svelte index 6029738..76fe51e 100644 --- a/web/src/lib/components/SchemaPreview.svelte +++ b/web/src/lib/components/SchemaPreview.svelte @@ -16,6 +16,7 @@ fileResult?: FileResult | null; textSource?: string; textResult?: string | null; + textVars?: Record; running?: boolean; error?: string; onupload: (file: File) => void; @@ -34,6 +35,7 @@ fileResult = null, textSource = "", textResult = null, + textVars = undefined, running = false, error = "", onupload, @@ -106,6 +108,7 @@ {result} {fileResult} {textResult} + {textVars} {toolId} {running} oncopy={oncopytext} diff --git a/web/src/lib/components/SchemaResultTile.svelte b/web/src/lib/components/SchemaResultTile.svelte index f9b46e7..8190ba4 100644 --- a/web/src/lib/components/SchemaResultTile.svelte +++ b/web/src/lib/components/SchemaResultTile.svelte @@ -12,6 +12,7 @@ result: PixelImage | null; fileResult?: FileResult | null; textResult?: string | null; + textVars?: Record; toolId?: string; running?: boolean; oncopy?: () => void; @@ -22,6 +23,7 @@ result, fileResult = null, textResult = null, + textVars = undefined, toolId = "", running = false, oncopy, @@ -64,6 +66,7 @@ {})} ondownload={ondownloadtxt ?? (() => {})} diff --git a/web/src/lib/components/SchemaTextResult.svelte b/web/src/lib/components/SchemaTextResult.svelte index 11c5144..99ee11c 100644 --- a/web/src/lib/components/SchemaTextResult.svelte +++ b/web/src/lib/components/SchemaTextResult.svelte @@ -1,13 +1,14 @@ diff --git a/web/src/lib/components/SchemaToolView.svelte b/web/src/lib/components/SchemaToolView.svelte index db977a5..8b75bed 100644 --- a/web/src/lib/components/SchemaToolView.svelte +++ b/web/src/lib/components/SchemaToolView.svelte @@ -38,6 +38,9 @@ let fileResult = $state(null); let textSource = $state(""); let textResult = $state(null); + let verdictVars = $state | undefined>( + undefined, + ); let running = $state(false); let error = $state(""); let started = $state(false); @@ -59,6 +62,7 @@ result = null; fileResult = null; textResult = null; + verdictVars = undefined; error = ""; } @@ -90,7 +94,16 @@ result = null; textResult = null; } else { - textResult = out as string; + if (typeof out === "string") { + textResult = out; + verdictVars = undefined; + } else if (out && "key" in out) { + textResult = out.key; + verdictVars = out.vars; + } else { + textResult = null; + verdictVars = undefined; + } result = null; fileResult = null; } @@ -125,7 +138,9 @@ async function copyText() { if (!textResult) return; const copyValue = - resultKind === "verdict" ? verdictText(tool.id, textResult) : textResult; + resultKind === "verdict" + ? verdictText(tool.id, textResult, verdictVars) + : textResult; try { await navigator.clipboard.writeText(copyValue); } catch (e) { @@ -196,6 +211,7 @@ {fileResult} {textSource} {textResult} + textVars={verdictVars} {inputMode} {resultKind} {running} diff --git a/web/src/lib/executor/executor.ts b/web/src/lib/executor/executor.ts index 52807e5..53bccf4 100644 --- a/web/src/lib/executor/executor.ts +++ b/web/src/lib/executor/executor.ts @@ -95,6 +95,7 @@ function ensureWorker(): Worker | null { data: Uint8ClampedArray; }[]; text?: string; + textVars?: Record; error?: string; errorKey?: string; errorVars?: Record; @@ -120,7 +121,11 @@ function ensureWorker(): Worker | null { })), }); } else if (payload.ok && typeof payload.text === "string") { - entry.resolve(payload.text); + entry.resolve( + payload.textVars + ? { key: payload.text, vars: payload.textVars } + : payload.text, + ); } else if (payload.errorKey) { entry.reject(new ToolError(payload.errorKey, payload.errorVars)); } else { diff --git a/web/src/lib/executor/executor.worker.ts b/web/src/lib/executor/executor.worker.ts index cfe1b2c..8cac5df 100644 --- a/web/src/lib/executor/executor.worker.ts +++ b/web/src/lib/executor/executor.worker.ts @@ -20,6 +20,7 @@ type WorkerResponse = height?: number; data?: Uint8ClampedArray; text?: string; + textVars?: Record; files?: WorkerImageFile[]; } | { @@ -67,6 +68,15 @@ async function handle(request: WorkerRequest): Promise { } satisfies WorkerResponse); return; } + if ("key" in output) { + (self as unknown as Worker).postMessage({ + id: request.id, + ok: true, + text: output.key, + textVars: output.vars, + } satisfies WorkerResponse); + return; + } if ("files" in output) { const files = output.files.map((file) => ({ name: file.name, diff --git a/web/src/lib/i18n/schema-tool-strings.ts b/web/src/lib/i18n/schema-tool-strings.ts index 9257241..8cf783f 100644 --- a/web/src/lib/i18n/schema-tool-strings.ts +++ b/web/src/lib/i18n/schema-tool-strings.ts @@ -1,9 +1,9 @@ import type { ToolEntry } from "$lib/registry"; import type { Field } from "$lib/registry-schema"; -import { ru } from "./ru"; import { getMergedDict } from "./locale.svelte"; -import type { SearchDoc } from "./matching"; -import { t } from "./t"; +import { normalizeForSearch, scoreDoc, type SearchDoc } from "./matching"; +import { ru } from "./ru"; +import { interpolate, t } from "./t"; function labelOf(id: string): string { return id @@ -43,8 +43,15 @@ export function optionLabel( return getMergedDict().tools[toolId]?.options?.[fieldId]?.[value] ?? fallback; } -export function verdictText(toolId: string, key: string): string { - return getMergedDict().tools[toolId]?.results?.[key] ?? key; +export function verdictText( + toolId: string, + key: string, + vars?: Record, +): string { + return interpolate( + getMergedDict().tools[toolId]?.results?.[key] ?? key, + vars, + ); } export function verdictTone(key: string): "success" | "danger" | "info" { @@ -73,3 +80,17 @@ export function toolSearchDoc(tool: ToolEntry): SearchDoc { ]), }; } + +/** + * Кросс-языковой поиск по каталогу: скор через `scoreDoc` на + * `toolSearchDoc` (заголовки/описания всех локалей). Порядок каталога + * сохраняется, элементы без совпадения отбрасываются. + */ +export function searchTools( + tools: readonly ToolEntry[], + query: string, +): ToolEntry[] { + const q = normalizeForSearch(query.trim()); + if (q.length === 0) return [...tools]; + return tools.filter((tool) => scoreDoc(toolSearchDoc(tool), q) !== null); +} diff --git a/web/src/lib/registry/analyze.ts b/web/src/lib/registry/analyze.ts index 90da3c8..2344cec 100644 --- a/web/src/lib/registry/analyze.ts +++ b/web/src/lib/registry/analyze.ts @@ -8,7 +8,6 @@ import { renderPredicateMask, } from "../core/masks"; import { base64ToBytes, looksLikePng, stripDataUri } from "../core/textio"; -import { t } from "../i18n/t"; import { field, toolSchema } from "../registry-schema"; import { imgTool, textGen, type ToolEntry } from "./types"; @@ -277,7 +276,7 @@ const pngFileSize: ToolEntry = { const blob = await encode(img, "image/png"); const kb = blob.size / 1024; const kbText = kb >= 100 ? Math.round(kb).toString() : kb.toFixed(1); - return t("tools.png-file-size.results.line", { kb: kbText }); + return { key: "line", vars: { kb: kbText } }; }), }; diff --git a/web/src/lib/registry/index.ts b/web/src/lib/registry/index.ts index 9d5de1b..ff89d7a 100644 --- a/web/src/lib/registry/index.ts +++ b/web/src/lib/registry/index.ts @@ -1,30 +1,31 @@ -import type { ToolEntry } from "./types"; -import { geometryEntries } from "./geometry"; import { alphaEntries } from "./alpha"; -import { convertEntries } from "./convert"; import { analyzeEntries } from "./analyze"; -import { filtersEntries } from "./filters"; import { colorEntries } from "./color"; +import { convertEntries } from "./convert"; +import { filtersEntries } from "./filters"; import { generateEntries } from "./generate"; +import { geometryEntries } from "./geometry"; import { textEntries } from "./text"; +import type { ToolEntry } from "./types"; export { genTool, imgTool, - textGen, + INPUT_MODES, requireSource, requireText, - INPUT_MODES, RESULT_KINDS, + textGen, } from "./types"; export type { + FileResult, InputMode, ResultKind, ToolContext, ToolEntry, - ToolResult, ToolImageFile, - FileResult, + ToolResult, + VerdictResult, } from "./types"; export const TOOLS: ToolEntry[] = [ diff --git a/web/src/lib/registry/types.ts b/web/src/lib/registry/types.ts index 5c69cdb..2400bb4 100644 --- a/web/src/lib/registry/types.ts +++ b/web/src/lib/registry/types.ts @@ -49,7 +49,18 @@ export interface FileResult { files: ToolImageFile[]; } -export type ToolResult = PixelImage | string | FileResult; +/** + * Вердикт с подстановками: `key` — ключ словаря (`tools[id].results[key]`), + * `vars` — значения для интерполяции `{name}`. Локализация выполняется на + * стороне UI; из run() (и тем более из worker) локализованные строки не + * возвращаются. + */ +export interface VerdictResult { + key: string; + vars?: Record; +} + +export type ToolResult = PixelImage | string | FileResult | VerdictResult; /** * Инструмент нового registry: полностью типизирован на `Params`, схема — diff --git a/web/src/routes/+page.svelte b/web/src/routes/+page.svelte index b92f065..012f405 100644 --- a/web/src/routes/+page.svelte +++ b/web/src/routes/+page.svelte @@ -4,6 +4,11 @@ import CatalogHeader from "$lib/components/CatalogHeader.svelte"; import CatalogToolbar from "$lib/components/CatalogToolbar.svelte"; import ToolCard from "$lib/components/ToolCard.svelte"; + import { + searchTools, + toolDescription, + toolTitle, + } from "$lib/i18n/schema-tool-strings"; import { t } from "$lib/i18n/t"; import { TOOL_ICONS } from "$lib/tool-icons"; @@ -14,12 +19,8 @@ PREVIEW_GROUPS.map((g) => ({ id: g.id, label: t(`categories.${g.id}`), - tools: g.tools.filter( - (t) => - (category === "all" || category === g.id) && - (query.trim() === "" || - t.title.toLowerCase().includes(query.trim().toLowerCase()) || - t.description.toLowerCase().includes(query.trim().toLowerCase())), + tools: searchTools(g.tools, query).filter( + () => category === "all" || category === g.id, ), })).filter((g) => g.tools.length > 0), ); @@ -35,9 +36,9 @@ {#each group.tools as tool, i (tool.id)} diff --git a/web/src/routes/list-tools/+page.svelte b/web/src/routes/list-tools/+page.svelte index f9ef94b..d36d844 100644 --- a/web/src/routes/list-tools/+page.svelte +++ b/web/src/routes/list-tools/+page.svelte @@ -4,6 +4,11 @@ import CatalogHeader from "$lib/components/CatalogHeader.svelte"; import CatalogToolbar from "$lib/components/CatalogToolbar.svelte"; import ToolCard from "$lib/components/ToolCard.svelte"; + import { + searchTools, + toolDescription, + toolTitle, + } from "$lib/i18n/schema-tool-strings"; import { t } from "$lib/i18n/t"; import { TOOL_ICONS } from "$lib/tool-icons"; @@ -14,12 +19,8 @@ PREVIEW_GROUPS.map((g) => ({ id: g.id, label: t(`categories.${g.id}`), - tools: g.tools.filter( - (t) => - (category === "all" || category === g.id) && - (query.trim() === "" || - t.title.toLowerCase().includes(query.trim().toLowerCase()) || - t.description.toLowerCase().includes(query.trim().toLowerCase())), + tools: searchTools(g.tools, query).filter( + () => category === "all" || category === g.id, ), })).filter((g) => g.tools.length > 0), ); @@ -33,9 +34,9 @@ {#each group.tools as tool, i (tool.id)}