mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
fix: worker use only i18n keys instead of full i18n
This commit is contained in:
@@ -16,6 +16,7 @@
|
|||||||
fileResult?: FileResult | null;
|
fileResult?: FileResult | null;
|
||||||
textSource?: string;
|
textSource?: string;
|
||||||
textResult?: string | null;
|
textResult?: string | null;
|
||||||
|
textVars?: Record<string, string | number>;
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
onupload: (file: File) => void;
|
onupload: (file: File) => void;
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
fileResult = null,
|
fileResult = null,
|
||||||
textSource = "",
|
textSource = "",
|
||||||
textResult = null,
|
textResult = null,
|
||||||
|
textVars = undefined,
|
||||||
running = false,
|
running = false,
|
||||||
error = "",
|
error = "",
|
||||||
onupload,
|
onupload,
|
||||||
@@ -106,6 +108,7 @@
|
|||||||
{result}
|
{result}
|
||||||
{fileResult}
|
{fileResult}
|
||||||
{textResult}
|
{textResult}
|
||||||
|
{textVars}
|
||||||
{toolId}
|
{toolId}
|
||||||
{running}
|
{running}
|
||||||
oncopy={oncopytext}
|
oncopy={oncopytext}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
result: PixelImage | null;
|
result: PixelImage | null;
|
||||||
fileResult?: FileResult | null;
|
fileResult?: FileResult | null;
|
||||||
textResult?: string | null;
|
textResult?: string | null;
|
||||||
|
textVars?: Record<string, string | number>;
|
||||||
toolId?: string;
|
toolId?: string;
|
||||||
running?: boolean;
|
running?: boolean;
|
||||||
oncopy?: () => void;
|
oncopy?: () => void;
|
||||||
@@ -22,6 +23,7 @@
|
|||||||
result,
|
result,
|
||||||
fileResult = null,
|
fileResult = null,
|
||||||
textResult = null,
|
textResult = null,
|
||||||
|
textVars = undefined,
|
||||||
toolId = "",
|
toolId = "",
|
||||||
running = false,
|
running = false,
|
||||||
oncopy,
|
oncopy,
|
||||||
@@ -64,6 +66,7 @@
|
|||||||
<SchemaTextResult
|
<SchemaTextResult
|
||||||
value={textResult}
|
value={textResult}
|
||||||
kind="verdict"
|
kind="verdict"
|
||||||
|
vars={textVars}
|
||||||
{toolId}
|
{toolId}
|
||||||
oncopy={oncopy ?? (() => {})}
|
oncopy={oncopy ?? (() => {})}
|
||||||
ondownload={ondownloadtxt ?? (() => {})}
|
ondownload={ondownloadtxt ?? (() => {})}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Copy, Download, FileText } from "@lucide/svelte";
|
import { verdictText, verdictTone } from "$lib/i18n/schema-tool-strings";
|
||||||
import { verdictTone, verdictText } from "$lib/i18n/schema-tool-strings";
|
|
||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
|
import { Copy, Download, FileText } from "@lucide/svelte";
|
||||||
import IconButton from "./ui/IconButton.svelte";
|
import IconButton from "./ui/IconButton.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
value: string;
|
value: string;
|
||||||
kind: "text" | "verdict";
|
kind: "text" | "verdict";
|
||||||
toolId?: string;
|
toolId?: string;
|
||||||
|
vars?: Record<string, string | number>;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
oncopy: () => void;
|
oncopy: () => void;
|
||||||
ondownload: () => void;
|
ondownload: () => void;
|
||||||
@@ -16,6 +17,7 @@
|
|||||||
value,
|
value,
|
||||||
kind,
|
kind,
|
||||||
toolId = "",
|
toolId = "",
|
||||||
|
vars = undefined,
|
||||||
fileName = "result.txt",
|
fileName = "result.txt",
|
||||||
oncopy,
|
oncopy,
|
||||||
ondownload,
|
ondownload,
|
||||||
@@ -23,7 +25,7 @@
|
|||||||
|
|
||||||
const tone = $derived(verdictTone(value));
|
const tone = $derived(verdictTone(value));
|
||||||
const displayValue = $derived(
|
const displayValue = $derived(
|
||||||
kind === "verdict" && toolId ? verdictText(toolId, value) : value,
|
kind === "verdict" && toolId ? verdictText(toolId, value, vars) : value,
|
||||||
);
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -38,6 +38,9 @@
|
|||||||
let fileResult = $state<FileResult | null>(null);
|
let fileResult = $state<FileResult | null>(null);
|
||||||
let textSource = $state("");
|
let textSource = $state("");
|
||||||
let textResult = $state<string | null>(null);
|
let textResult = $state<string | null>(null);
|
||||||
|
let verdictVars = $state<Record<string, string | number> | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
let running = $state(false);
|
let running = $state(false);
|
||||||
let error = $state("");
|
let error = $state("");
|
||||||
let started = $state(false);
|
let started = $state(false);
|
||||||
@@ -59,6 +62,7 @@
|
|||||||
result = null;
|
result = null;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
textResult = null;
|
textResult = null;
|
||||||
|
verdictVars = undefined;
|
||||||
error = "";
|
error = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +94,16 @@
|
|||||||
result = null;
|
result = null;
|
||||||
textResult = null;
|
textResult = null;
|
||||||
} else {
|
} 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;
|
result = null;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
}
|
}
|
||||||
@@ -125,7 +138,9 @@
|
|||||||
async function copyText() {
|
async function copyText() {
|
||||||
if (!textResult) return;
|
if (!textResult) return;
|
||||||
const copyValue =
|
const copyValue =
|
||||||
resultKind === "verdict" ? verdictText(tool.id, textResult) : textResult;
|
resultKind === "verdict"
|
||||||
|
? verdictText(tool.id, textResult, verdictVars)
|
||||||
|
: textResult;
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(copyValue);
|
await navigator.clipboard.writeText(copyValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -196,6 +211,7 @@
|
|||||||
{fileResult}
|
{fileResult}
|
||||||
{textSource}
|
{textSource}
|
||||||
{textResult}
|
{textResult}
|
||||||
|
textVars={verdictVars}
|
||||||
{inputMode}
|
{inputMode}
|
||||||
{resultKind}
|
{resultKind}
|
||||||
{running}
|
{running}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ function ensureWorker(): Worker | null {
|
|||||||
data: Uint8ClampedArray;
|
data: Uint8ClampedArray;
|
||||||
}[];
|
}[];
|
||||||
text?: string;
|
text?: string;
|
||||||
|
textVars?: Record<string, string | number>;
|
||||||
error?: string;
|
error?: string;
|
||||||
errorKey?: string;
|
errorKey?: string;
|
||||||
errorVars?: Record<string, string | number>;
|
errorVars?: Record<string, string | number>;
|
||||||
@@ -120,7 +121,11 @@ function ensureWorker(): Worker | null {
|
|||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
} else if (payload.ok && typeof payload.text === "string") {
|
} 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) {
|
} else if (payload.errorKey) {
|
||||||
entry.reject(new ToolError(payload.errorKey, payload.errorVars));
|
entry.reject(new ToolError(payload.errorKey, payload.errorVars));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type WorkerResponse =
|
|||||||
height?: number;
|
height?: number;
|
||||||
data?: Uint8ClampedArray;
|
data?: Uint8ClampedArray;
|
||||||
text?: string;
|
text?: string;
|
||||||
|
textVars?: Record<string, string | number>;
|
||||||
files?: WorkerImageFile[];
|
files?: WorkerImageFile[];
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
@@ -67,6 +68,15 @@ async function handle(request: WorkerRequest): Promise<void> {
|
|||||||
} satisfies WorkerResponse);
|
} satisfies WorkerResponse);
|
||||||
return;
|
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) {
|
if ("files" in output) {
|
||||||
const files = output.files.map((file) => ({
|
const files = output.files.map((file) => ({
|
||||||
name: file.name,
|
name: file.name,
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { ToolEntry } from "$lib/registry";
|
import type { ToolEntry } from "$lib/registry";
|
||||||
import type { Field } from "$lib/registry-schema";
|
import type { Field } from "$lib/registry-schema";
|
||||||
import { ru } from "./ru";
|
|
||||||
import { getMergedDict } from "./locale.svelte";
|
import { getMergedDict } from "./locale.svelte";
|
||||||
import type { SearchDoc } from "./matching";
|
import { normalizeForSearch, scoreDoc, type SearchDoc } from "./matching";
|
||||||
import { t } from "./t";
|
import { ru } from "./ru";
|
||||||
|
import { interpolate, t } from "./t";
|
||||||
|
|
||||||
function labelOf(id: string): string {
|
function labelOf(id: string): string {
|
||||||
return id
|
return id
|
||||||
@@ -43,8 +43,15 @@ export function optionLabel(
|
|||||||
return getMergedDict().tools[toolId]?.options?.[fieldId]?.[value] ?? fallback;
|
return getMergedDict().tools[toolId]?.options?.[fieldId]?.[value] ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verdictText(toolId: string, key: string): string {
|
export function verdictText(
|
||||||
return getMergedDict().tools[toolId]?.results?.[key] ?? key;
|
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" {
|
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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
renderPredicateMask,
|
renderPredicateMask,
|
||||||
} from "../core/masks";
|
} from "../core/masks";
|
||||||
import { base64ToBytes, looksLikePng, stripDataUri } from "../core/textio";
|
import { base64ToBytes, looksLikePng, stripDataUri } from "../core/textio";
|
||||||
import { t } from "../i18n/t";
|
|
||||||
import { field, toolSchema } from "../registry-schema";
|
import { field, toolSchema } from "../registry-schema";
|
||||||
import { imgTool, textGen, type ToolEntry } from "./types";
|
import { imgTool, textGen, type ToolEntry } from "./types";
|
||||||
|
|
||||||
@@ -277,7 +276,7 @@ const pngFileSize: ToolEntry<NoParams> = {
|
|||||||
const blob = await encode(img, "image/png");
|
const blob = await encode(img, "image/png");
|
||||||
const kb = blob.size / 1024;
|
const kb = blob.size / 1024;
|
||||||
const kbText = kb >= 100 ? Math.round(kb).toString() : kb.toFixed(1);
|
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 } };
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,31 @@
|
|||||||
import type { ToolEntry } from "./types";
|
|
||||||
import { geometryEntries } from "./geometry";
|
|
||||||
import { alphaEntries } from "./alpha";
|
import { alphaEntries } from "./alpha";
|
||||||
import { convertEntries } from "./convert";
|
|
||||||
import { analyzeEntries } from "./analyze";
|
import { analyzeEntries } from "./analyze";
|
||||||
import { filtersEntries } from "./filters";
|
|
||||||
import { colorEntries } from "./color";
|
import { colorEntries } from "./color";
|
||||||
|
import { convertEntries } from "./convert";
|
||||||
|
import { filtersEntries } from "./filters";
|
||||||
import { generateEntries } from "./generate";
|
import { generateEntries } from "./generate";
|
||||||
|
import { geometryEntries } from "./geometry";
|
||||||
import { textEntries } from "./text";
|
import { textEntries } from "./text";
|
||||||
|
import type { ToolEntry } from "./types";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
genTool,
|
genTool,
|
||||||
imgTool,
|
imgTool,
|
||||||
textGen,
|
INPUT_MODES,
|
||||||
requireSource,
|
requireSource,
|
||||||
requireText,
|
requireText,
|
||||||
INPUT_MODES,
|
|
||||||
RESULT_KINDS,
|
RESULT_KINDS,
|
||||||
|
textGen,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
export type {
|
export type {
|
||||||
|
FileResult,
|
||||||
InputMode,
|
InputMode,
|
||||||
ResultKind,
|
ResultKind,
|
||||||
ToolContext,
|
ToolContext,
|
||||||
ToolEntry,
|
ToolEntry,
|
||||||
ToolResult,
|
|
||||||
ToolImageFile,
|
ToolImageFile,
|
||||||
FileResult,
|
ToolResult,
|
||||||
|
VerdictResult,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
|
|
||||||
export const TOOLS: ToolEntry[] = [
|
export const TOOLS: ToolEntry[] = [
|
||||||
|
|||||||
@@ -49,7 +49,18 @@ export interface FileResult {
|
|||||||
files: ToolImageFile[];
|
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`, схема —
|
* Инструмент нового registry: полностью типизирован на `Params`, схема —
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
import CatalogHeader from "$lib/components/CatalogHeader.svelte";
|
import CatalogHeader from "$lib/components/CatalogHeader.svelte";
|
||||||
import CatalogToolbar from "$lib/components/CatalogToolbar.svelte";
|
import CatalogToolbar from "$lib/components/CatalogToolbar.svelte";
|
||||||
import ToolCard from "$lib/components/ToolCard.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 { t } from "$lib/i18n/t";
|
||||||
import { TOOL_ICONS } from "$lib/tool-icons";
|
import { TOOL_ICONS } from "$lib/tool-icons";
|
||||||
|
|
||||||
@@ -14,12 +19,8 @@
|
|||||||
PREVIEW_GROUPS.map((g) => ({
|
PREVIEW_GROUPS.map((g) => ({
|
||||||
id: g.id,
|
id: g.id,
|
||||||
label: t(`categories.${g.id}`),
|
label: t(`categories.${g.id}`),
|
||||||
tools: g.tools.filter(
|
tools: searchTools(g.tools, query).filter(
|
||||||
(t) =>
|
() => category === "all" || category === g.id,
|
||||||
(category === "all" || category === g.id) &&
|
|
||||||
(query.trim() === "" ||
|
|
||||||
t.title.toLowerCase().includes(query.trim().toLowerCase()) ||
|
|
||||||
t.description.toLowerCase().includes(query.trim().toLowerCase())),
|
|
||||||
),
|
),
|
||||||
})).filter((g) => g.tools.length > 0),
|
})).filter((g) => g.tools.length > 0),
|
||||||
);
|
);
|
||||||
@@ -35,9 +36,9 @@
|
|||||||
<CatalogGroup label={group.label} count={group.tools.length}>
|
<CatalogGroup label={group.label} count={group.tools.length}>
|
||||||
{#each group.tools as tool, i (tool.id)}
|
{#each group.tools as tool, i (tool.id)}
|
||||||
<ToolCard
|
<ToolCard
|
||||||
title={tool.title}
|
title={toolTitle(tool)}
|
||||||
id={tool.id}
|
id={tool.id}
|
||||||
description={tool.description}
|
description={toolDescription(tool)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[tool.id]}
|
icon={TOOL_ICONS[tool.id]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
import CatalogHeader from "$lib/components/CatalogHeader.svelte";
|
import CatalogHeader from "$lib/components/CatalogHeader.svelte";
|
||||||
import CatalogToolbar from "$lib/components/CatalogToolbar.svelte";
|
import CatalogToolbar from "$lib/components/CatalogToolbar.svelte";
|
||||||
import ToolCard from "$lib/components/ToolCard.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 { t } from "$lib/i18n/t";
|
||||||
import { TOOL_ICONS } from "$lib/tool-icons";
|
import { TOOL_ICONS } from "$lib/tool-icons";
|
||||||
|
|
||||||
@@ -14,12 +19,8 @@
|
|||||||
PREVIEW_GROUPS.map((g) => ({
|
PREVIEW_GROUPS.map((g) => ({
|
||||||
id: g.id,
|
id: g.id,
|
||||||
label: t(`categories.${g.id}`),
|
label: t(`categories.${g.id}`),
|
||||||
tools: g.tools.filter(
|
tools: searchTools(g.tools, query).filter(
|
||||||
(t) =>
|
() => category === "all" || category === g.id,
|
||||||
(category === "all" || category === g.id) &&
|
|
||||||
(query.trim() === "" ||
|
|
||||||
t.title.toLowerCase().includes(query.trim().toLowerCase()) ||
|
|
||||||
t.description.toLowerCase().includes(query.trim().toLowerCase())),
|
|
||||||
),
|
),
|
||||||
})).filter((g) => g.tools.length > 0),
|
})).filter((g) => g.tools.length > 0),
|
||||||
);
|
);
|
||||||
@@ -33,9 +34,9 @@
|
|||||||
<CatalogGroup label={group.label} count={group.tools.length}>
|
<CatalogGroup label={group.label} count={group.tools.length}>
|
||||||
{#each group.tools as tool, i (tool.id)}
|
{#each group.tools as tool, i (tool.id)}
|
||||||
<ToolCard
|
<ToolCard
|
||||||
title={tool.title}
|
title={toolTitle(tool)}
|
||||||
id={tool.id}
|
id={tool.id}
|
||||||
description={tool.description}
|
description={toolDescription(tool)}
|
||||||
index={i + 1}
|
index={i + 1}
|
||||||
icon={TOOL_ICONS[tool.id]}
|
icon={TOOL_ICONS[tool.id]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user