mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
fix: update i18n - schema components
This commit is contained in:
@@ -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
@@ -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` словаря,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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: "У этого инструмента пока нет схемы.",
|
||||
|
||||
Reference in New Issue
Block a user