fix: update i18n - schema components

This commit is contained in:
2026-09-13 06:21:28 +05:00
parent a4c85c0814
commit 6c88a23e48
14 changed files with 86 additions and 64 deletions
+3 -2
View File
@@ -1,8 +1,9 @@
# План: инструмент «Разрезать PNG на части» (split-into-parts-png)
> Статус: **выполнено и в архиве** (2026-09). Инструмент и механизм «результат =
> набор файлов» (1 → many) реализованы; первый потребитель — `split-into-parts-png`.
> Общий процесс таких инструментов — задача 18 в `docs/backlog.md`.
> набор файлов» (1 → many) реализованы; первый потребитель —
> `split-into-parts-png`. Общий процесс таких инструментов — задача 18 в
> `docs/backlog.md`.
## Цель
+18 -31
View File
@@ -156,12 +156,11 @@
- `verdictTone(key)``success`|`danger`|`info` по имени ключа
(`*Yes`/`*No`/`*Portrait`/`*Landscape`) — не по строке.
- **0.9** Расширить тип `Dict` (только новая ветка `lib/i18n`):
- секция `preview` **не заводится**: оставшиеся после чистки mono-лейблов (см.
решение B) строки нового UI живут в собственных категориях — расширяем
существующие (`catalog`, `header`, `dropZone`, `sourceCard`, `resultCard`,
`paramsCard`, `textResult`, `ui`) и заводим две маленькие новые: `actions`
(Generate / Open image / Download result…) и `textSource` (placeholder, Try
sample…);
- секция `preview` **не заводится**: строки нового UI живут в собственных
категориях — расширяем существующие (`catalog`, `header`, `dropZone`,
`sourceCard`, `resultCard`, `paramsCard`, `textResult`, `ui`) и заводим две
маленькие новые: `actions` (Generate / Open image / Download result…) и
`textSource` (placeholder, Try sample…);
- новая глобальная секция `fields: Record<string, string>` — переводы подписей
полей по ключу из схемы (решение C);
- новая глобальная секция `groups: Record<string, string>` — переводы
@@ -197,36 +196,29 @@
`t("header.footerNote")`.
- **3.3** `tools/[id]/+page.svelte`: «Tool not found», fallback title → `t()`.
### Фаза 4: Компоненты — основной блок (по одному коммиту на компонент)
### Фаза 4: Компоненты — основной блок (коммиты кучками по смыслу)
Каждый коммит: удаление декоративных mono-лейблов (решение B) + замена
оставшихся строк на `t`/хелперы + lint/check. Список mono-лейблов, которые
**удаляем**: eyebrow'ы (`PNG PROCESSING / SINGLE TOOL`, `KIT SHOWCASE`,
`EASY-PNG-TOOLS / CATALOG`), статусы (`LIVE PREVIEW`, `rendering ok`), панельные
подписи (`TOOL SETTINGS`), дублирующие капшены (`SOURCE / RESULT`,
`GENERATOR / RESULT`). Оставляем и локализуем только информативные: категории
каталога, счётчики инструментов, подписи данных (`FORMAT`, `parts`, `px`, «ZIP
(PNG)»), alt-тексты, а также содержательные заголовки («Configure output»).
Все текстовые подписи в компонентах — информативные, сохраняем и переводим на
`t`/хелперы. Каждый коммит: замена строк + lint/check.
- `SchemaToolView.svelte`: убрать eyebrow/`LIVE PREVIEW`/`TOOL SETTINGS`,
перевести «Configure output» и «This tool has no schema yet.».
- `SchemaSourceTile.svelte`: убрать figcaption `SOURCE`, перевести «choose an
- `SchemaToolView.svelte`: title/description →
`toolTitle()`/`toolDescription()`, перевести `TOOL SETTINGS`, «Configure
output» и «This tool has no schema yet.».
- `SchemaSourceTile.svelte`: перевести figcaption `SOURCE`, «choose an
image»/«no source…» и alt.
- `SchemaResultTile.svelte`: убрать figcaption `RESULT`, перевести «no result
yet»/«parts» и alt.
- `SchemaPreview.svelte`: убрать панельные
`SOURCE / RESULT`/`GENERATOR / RESULT`, перевести meta-капшены (`FORMAT` и
т.п.).
- `SchemaResultTile.svelte`: перевести figcaption `RESULT`, «no result yet» и
alt; суффикс «parts» — в `PreviewTile`.
- `SchemaPreview.svelte`: перевести панельный лейбл `PREVIEW PANEL` и
meta-капшены `SOURCE / RESULT / FORMAT`; значения «parts»/«text» → `t()`.
- `SchemaActions.svelte`: Generating…, Generate, Open image, Download result.
- `SchemaFields.svelte`: Reset, «updates automatically», `labelOf(field.id)`
`fieldLabel(field)`.
- `SchemaTextSource.svelte`: placeholder, Try sample, Render text.
- `SchemaTextResult.svelte`: убрать лейбл `RESULT` у вердикта, перевести Copy /
- `SchemaTextResult.svelte`: перевести лейбл `RESULT` у вердикта, Copy /
Download .txt; tone detection → `verdictTone()`.
- `CatalogToolbar.svelte`: фильтры → `t("categories.all")` +
`t("categories.X")`, поисковый placeholder.
- `CatalogHeader.svelte`: убрать eyebrow, перевести heading/lead и счётчик
«TOOLS AVAILABLE».
- `CatalogHeader.svelte`: перевести heading/lead и счётчик «TOOLS AVAILABLE».
- `CatalogGroup.svelte`: перевести суффикс счётчика «TOOLS».
- `Dropzone.svelte`: текст зоны → `t("dropZone.pickDefault")`.
- Schema field controls (по одному или группой): Width/Height, From/To, Backing
@@ -320,11 +312,6 @@
(`title: "groups.canvas"`), перевод — в глобальной секции `groups` словаря
(`Dict.groups: Record<string, string>`). В UI — `groupLabel(title)` =
`t(title)` с фолбэком на сырой ключ.
- **B. Mono-лейблы**: декоративные лейблы без полезной информации **удаляем**
(eyebrow'ы, статусы, панельные капшены, дублирующие подписи секций) — они
занимают место. Оставляем только там, где реально нужно, и локализуем:
счётчики инструментов, категории каталога, подписи данных (FORMAT, parts, px,
«ZIP (PNG)»), alt-тексты, содержательные headings.
- **C. Лейблы полей**: в схеме задаём i18n-ключ
(`field.number({ label: "fields.thickness", … })`), в UI выводится перевод —
`fieldLabel(field)` = `t(label)`. Ключи — глобальная секция `fields` словаря,
+6 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { Copy } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import IconButton from "./ui/IconButton.svelte";
interface Props {
@@ -16,7 +17,11 @@
<pre class="code-pre"><code>{code}</code></pre>
{#if copyable}
<div class="code-copy">
<IconButton icon={Copy} label="Copy" onclick={() => oncopy?.()} />
<IconButton
icon={Copy}
label={t("textResult.copy")}
onclick={() => oncopy?.()}
/>
</div>
{/if}
</div>
+4 -2
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { Upload } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import DownloadButton from "./ui/DownloadButton.svelte";
interface Props {
@@ -16,7 +17,8 @@
<div class="actions">
{#if inputMode === "image"}
<label class="btn upload">
<Upload size={14} /> Open image
<Upload size={14} />
{t("actions.openImage")}
<input
type="file"
accept="image/*"
@@ -29,7 +31,7 @@
{/if}
{#if canDownload}
<DownloadButton
label="Download result"
label={t("actions.downloadResult")}
onclick={ondownload}
disabled={running}
/>
+9 -7
View File
@@ -4,6 +4,7 @@
import SchemaResultTile from "$lib/components/SchemaResultTile.svelte";
import SchemaSourceTile from "$lib/components/SchemaSourceTile.svelte";
import type { PixelImage } from "$lib/core/types";
import { t } from "$lib/i18n/t";
import type { FileResult, InputMode, ResultKind } from "$lib/registry";
interface Props {
@@ -46,7 +47,7 @@
case "none":
return "—";
case "text":
return "text";
return t("textInput.heading");
case "image":
return source ? `${source.width} × ${source.height} px` : "—";
}
@@ -58,10 +59,10 @@
: "—"
: resultKind === "files"
? fileResult
? `${fileResult.files.length} parts`
? `${fileResult.files.length} ${t("resultCard.parts")}`
: "—"
: textResult
? "text"
? t("textInput.heading")
: "—",
);
const formatValue = $derived(resultKind === "files" ? "ZIP (PNG)" : "PNG");
@@ -75,7 +76,7 @@
</script>
<div class="panel-head">
<span class="label">PREVIEW PANEL</span>
<span class="label">{t("resultCard.previewPanel")}</span>
<SchemaActions
{inputMode}
canDownload={hasResult}
@@ -112,9 +113,9 @@
<div class="meta">
<MetaList
items={[
{ caption: "SOURCE", value: sourceValue },
{ caption: "RESULT", value: resultValue },
{ caption: "FORMAT", value: formatValue },
{ caption: t("sourceCard.source"), value: sourceValue },
{ caption: t("resultCard.result"), value: resultValue },
{ caption: t("resultCard.format"), value: formatValue },
]}
/>
</div>
@@ -132,6 +133,7 @@
.label {
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
color: var(--color-main);
}
.error {
@@ -1,6 +1,7 @@
<script lang="ts">
import { toDataUrl } from "$lib/core/io";
import type { PixelImage } from "$lib/core/types";
import { t } from "$lib/i18n/t";
import type { FileResult, ResultKind } from "$lib/registry";
import { Check } from "@lucide/svelte";
import SchemaTextResult from "./SchemaTextResult.svelte";
@@ -40,7 +41,12 @@
);
</script>
<PreviewTile label="RESULT" viewMode={resultKind} loading={running} {parts}>
<PreviewTile
label={t("resultCard.result")}
viewMode={resultKind}
loading={running}
{parts}
>
<div class="canvas" class:checker={resultKind === "image"}>
{#if resultKind === "text" && textResult}
<div class="text-result-wrap">
@@ -70,11 +76,11 @@
{/each}
</div>
{:else if resultKind === "image" && resultUrl}
<img src={resultUrl} alt="result" />
<img src={resultUrl} alt={t("resultCard.alt")} />
{:else if running}
<Check size={22} />
{:else}
<span class="empty">no result yet</span>
<span class="empty">{t("resultCard.noResult")}</span>
{/if}
</div>
</PreviewTile>
@@ -1,6 +1,7 @@
<script lang="ts">
import { toDataUrl } from "$lib/core/io";
import type { PixelImage } from "$lib/core/types";
import { t } from "$lib/i18n/t";
import type { InputMode } from "$lib/registry";
import PreviewTile from "./layout/PreviewTile.svelte";
import SchemaTextSource from "./SchemaTextSource.svelte";
@@ -25,7 +26,7 @@
const sourceUrl = $derived(source ? toDataUrl(source) : null);
</script>
<PreviewTile label="SOURCE" viewMode={mode}>
<PreviewTile label={t("sourceCard.source")} viewMode={mode}>
{#if mode === "text"}
<div class="text-source-wrap">
<SchemaTextSource
@@ -36,11 +37,11 @@
/>
</div>
{:else if mode === "image" && sourceUrl}
<img src={sourceUrl} alt="source" />
<img src={sourceUrl} alt={t("sourceCard.alt")} />
{:else if mode === "image"}
<span class="empty">choose an image</span>
<span class="empty">{t("sourceCard.chooseImage")}</span>
{:else}
<span class="empty">no source — configure the parameters</span>
<span class="empty">{t("sourceCard.noSource")}</span>
{/if}
</PreviewTile>
@@ -1,5 +1,6 @@
<script lang="ts">
import { Copy, Download, FileText } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import IconButton from "./ui/IconButton.svelte";
interface Props {
@@ -28,10 +29,10 @@
{#if kind === "verdict"}
<div class="verdict">
<span class="verdict-label">RESULT</span>
<span class="verdict-label">{t("resultCard.result")}</span>
<div class="badge badge-tone--{tone}" role="status">
<span class="verdict-text">{value}</span>
<IconButton icon={Copy} label="Copy" onclick={oncopy} />
<IconButton icon={Copy} label={t("textResult.copy")} onclick={oncopy} />
</div>
</div>
{:else}
@@ -39,10 +40,10 @@
<div class="result-head">
<span class="result-label"><FileText size={14} /> {fileName}</span>
<div class="result-actions">
<IconButton icon={Copy} label="Copy" onclick={oncopy} />
<IconButton icon={Copy} label={t("textResult.copy")} onclick={oncopy} />
<IconButton
icon={Download}
label="Download .txt"
label={t("textResult.downloadTxt")}
onclick={ondownload}
/>
</div>
@@ -98,6 +99,7 @@
color: var(--color-text-muted);
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
}
.badge {
display: inline-flex;
@@ -1,5 +1,6 @@
<script lang="ts">
import { ArrowUp, Upload } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
interface Props {
value: string;
@@ -11,7 +12,7 @@
}
let {
value,
placeholder = "Paste base64 / hex / bytes…",
placeholder = t("textSource.placeholder"),
disabled = false,
oninput,
onrender,
@@ -35,11 +36,13 @@
<div class="actions">
{#if onsample}
<button type="button" class="secondary" onclick={onsample}>
<Upload size={14} /> Try sample
<Upload size={14} />
{t("textSource.trySample")}
</button>
{/if}
<button type="button" class="render" onclick={onrender} {disabled}>
<ArrowUp size={14} /> Render text
<ArrowUp size={14} />
{t("textSource.render")}
</button>
</div>
</div>
+7 -5
View File
@@ -6,6 +6,7 @@
import { decodeFile, encode } from "$lib/core/io";
import type { PixelImage } from "$lib/core/types";
import { execute } from "$lib/executor";
import { toolDescription, toolTitle } from "$lib/i18n/schema-tool-strings";
import { t } from "$lib/i18n/t";
import type { FileResult, ToolEntry } from "$lib/registry";
import {
@@ -161,16 +162,16 @@
<div class="schema-tool">
<header class="header">
<div class="title-block">
<h1>{tool.title}</h1>
<p class="lede">{tool.description}</p>
<h1>{toolTitle(tool)}</h1>
<p class="lede">{toolDescription(tool)}</p>
</div>
</header>
<div class="workspace">
<section class="panel settings">
<div class="panel-head">
<span class="label">TOOL SETTINGS</span>
<strong>Configure output</strong>
<span class="label">{t("paramsCard.toolSettings")}</span>
<strong>{t("paramsCard.configureOutput")}</strong>
</div>
<SchemaFields {schema} {values} onchange={setValue} onreset={reset} />
</section>
@@ -199,7 +200,7 @@
</div>
</div>
{:else}
<p class="no-schema">This tool has no schema yet.</p>
<p class="no-schema">{t("paramsCard.noSchema")}</p>
{/if}
<style>
@@ -223,6 +224,7 @@
.label {
font: var(--font-size-s) var(--font-mono);
letter-spacing: var(--space-text-l);
text-transform: uppercase;
color: var(--color-text-muted);
}
.label {
+2 -1
View File
@@ -1,5 +1,6 @@
<script lang="ts">
import { Ellipsis, GripVertical, X } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import type { Snippet } from "svelte";
interface Props {
@@ -28,7 +29,7 @@
<button
type="button"
class="step-remove"
aria-label="Remove step"
aria-label={t("chain.removeStepAria")}
onclick={() => onremove?.()}
>
<X size={16} />
@@ -1,6 +1,7 @@
<script lang="ts">
import type { InputMode, ResultKind } from "$lib/registry";
import { RefreshCw } from "@lucide/svelte";
import { t } from "$lib/i18n/t";
import type { Snippet } from "svelte";
interface Props {
@@ -26,7 +27,7 @@
<span>
{label}
{#if parts !== undefined}
<span class="count">{parts} parts</span>
<span class="count">{parts} {t("resultCard.parts")}</span>
{/if}
{#if loading}
<RefreshCw class="rotating" size="12" />
@@ -45,6 +46,7 @@
gap: var(--space-m);
margin-bottom: var(--space-m);
font: var(--font-size-s) var(--font-mono);
text-transform: uppercase;
color: var(--color-text-muted);
& :global(.rotating) {
animation: rotate var(--duration-l) linear infinite;
+4
View File
@@ -57,6 +57,7 @@ export const en: Dict = {
removeStepAria: "Remove step",
},
sourceCard: {
source: "Source",
replaceImage: "Replace image",
chooseImage: "choose an image",
noSource: "no source — configure the parameters",
@@ -71,11 +72,14 @@ export const en: Dict = {
nextTool: "⛓ Next tool",
breakChain: "✂ Break the chain",
noResult: "no result yet",
result: "Result",
previewPanel: "Preview",
parts: "parts",
format: "Format",
alt: "Result image",
},
paramsCard: {
toolSettings: "Tool settings",
noParams: "This tool has no parameters — the result is ready as is.",
configureOutput: "Configure output",
noSchema: "This tool has no schema yet.",
+4
View File
@@ -58,6 +58,7 @@ export const ru: Dict = {
removeStepAria: "Убрать шаг",
},
sourceCard: {
source: "Источник",
replaceImage: "Заменить изображение",
chooseImage: "выберите изображение",
noSource: "нет источника — настройте параметры",
@@ -72,11 +73,14 @@ export const ru: Dict = {
nextTool: "⛓ Следующий инструмент",
breakChain: "✂ Оборвать цепочку",
noResult: "результата пока нет",
result: "Результат",
previewPanel: "Предпросмотр",
parts: "частей",
format: "Формат",
alt: "Изображение-результат",
},
paramsCard: {
toolSettings: "Настройки инструмента",
noParams: "У этого инструмента нет параметров — результат уже готов.",
configureOutput: "Настроить вывод",
noSchema: "У этого инструмента пока нет схемы.",