fix: worker use only i18n keys instead of full i18n

This commit is contained in:
2026-09-13 12:44:51 +05:00
parent 49d0a9444e
commit c2f3838485
12 changed files with 111 additions and 38 deletions
@@ -16,6 +16,7 @@
fileResult?: FileResult | null;
textSource?: string;
textResult?: string | null;
textVars?: Record<string, string | number>;
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}
@@ -12,6 +12,7 @@
result: PixelImage | null;
fileResult?: FileResult | null;
textResult?: string | null;
textVars?: Record<string, string | number>;
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 @@
<SchemaTextResult
value={textResult}
kind="verdict"
vars={textVars}
{toolId}
oncopy={oncopy ?? (() => {})}
ondownload={ondownloadtxt ?? (() => {})}
@@ -1,13 +1,14 @@
<script lang="ts">
import { Copy, Download, FileText } from "@lucide/svelte";
import { verdictTone, verdictText } from "$lib/i18n/schema-tool-strings";
import { verdictText, verdictTone } from "$lib/i18n/schema-tool-strings";
import { t } from "$lib/i18n/t";
import { Copy, Download, FileText } from "@lucide/svelte";
import IconButton from "./ui/IconButton.svelte";
interface Props {
value: string;
kind: "text" | "verdict";
toolId?: string;
vars?: Record<string, string | number>;
fileName?: string;
oncopy: () => void;
ondownload: () => void;
@@ -16,6 +17,7 @@
value,
kind,
toolId = "",
vars = undefined,
fileName = "result.txt",
oncopy,
ondownload,
@@ -23,7 +25,7 @@
const tone = $derived(verdictTone(value));
const displayValue = $derived(
kind === "verdict" && toolId ? verdictText(toolId, value) : value,
kind === "verdict" && toolId ? verdictText(toolId, value, vars) : value,
);
</script>
+18 -2
View File
@@ -38,6 +38,9 @@
let fileResult = $state<FileResult | null>(null);
let textSource = $state("");
let textResult = $state<string | null>(null);
let verdictVars = $state<Record<string, string | number> | 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}
+6 -1
View File
@@ -95,6 +95,7 @@ function ensureWorker(): Worker | null {
data: Uint8ClampedArray;
}[];
text?: string;
textVars?: Record<string, string | number>;
error?: string;
errorKey?: string;
errorVars?: Record<string, string | number>;
@@ -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 {
+10
View File
@@ -20,6 +20,7 @@ type WorkerResponse =
height?: number;
data?: Uint8ClampedArray;
text?: string;
textVars?: Record<string, string | number>;
files?: WorkerImageFile[];
}
| {
@@ -67,6 +68,15 @@ async function handle(request: WorkerRequest): Promise<void> {
} 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,
+26 -5
View File
@@ -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, string | number>,
): 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);
}
+1 -2
View File
@@ -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<NoParams> = {
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 } };
}),
};
+9 -8
View File
@@ -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[] = [
+12 -1
View File
@@ -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<string, string | number>;
}
export type ToolResult = PixelImage | string | FileResult | VerdictResult;
/**
* Инструмент нового registry: полностью типизирован на `Params`, схема —
+9 -8
View File
@@ -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 @@
<CatalogGroup label={group.label} count={group.tools.length}>
{#each group.tools as tool, i (tool.id)}
<ToolCard
title={tool.title}
title={toolTitle(tool)}
id={tool.id}
description={tool.description}
description={toolDescription(tool)}
index={i + 1}
icon={TOOL_ICONS[tool.id]}
/>
+9 -8
View File
@@ -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 @@
<CatalogGroup label={group.label} count={group.tools.length}>
{#each group.tools as tool, i (tool.id)}
<ToolCard
title={tool.title}
title={toolTitle(tool)}
id={tool.id}
description={tool.description}
description={toolDescription(tool)}
index={i + 1}
icon={TOOL_ICONS[tool.id]}
/>