feat: use i18n in components

This commit is contained in:
2026-08-25 08:32:43 +05:00
parent 74a18d91a7
commit 396036802c
11 changed files with 48 additions and 26 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<!doctype html> <!doctype html>
<html lang="ru"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
+13 -7
View File
@@ -4,10 +4,12 @@
import SelectField from './ui/SelectField.svelte'; import SelectField from './ui/SelectField.svelte';
import SliderField from './ui/SliderField.svelte'; import SliderField from './ui/SliderField.svelte';
import TextField from './ui/TextField.svelte'; import TextField from './ui/TextField.svelte';
import type { ParamDef } from '$lib/registry'; import type { ParamDef, ToolEntry } from '$lib/registry';
import { optionLabel, paramLabel } from '$lib/i18n/tool-strings';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
interface Props { interface Props {
tool: ToolEntry;
params: ParamDef[]; params: ParamDef[];
values: Record<string, any>; values: Record<string, any>;
pipetteTargetId?: string | null; pipetteTargetId?: string | null;
@@ -17,6 +19,7 @@
} }
let { let {
tool,
params, params,
values = $bindable(), values = $bindable(),
pipetteTargetId = null, pipetteTargetId = null,
@@ -33,11 +36,11 @@
{#each params as param (param.id)} {#each params as param (param.id)}
<div class="field"> <div class="field">
{#if param.type === 'checkbox'} {#if param.type === 'checkbox'}
<CheckboxField id={param.id} label={param.label} bind:checked={values[param.id]} /> <CheckboxField id={param.id} label={paramLabel(tool, param)} bind:checked={values[param.id]} />
{:else if param.type === 'number'} {:else if param.type === 'number'}
<TextField <TextField
id={param.id} id={param.id}
label={param.label} label={paramLabel(tool, param)}
type="number" type="number"
min={param.min} min={param.min}
max={param.max} max={param.max}
@@ -47,7 +50,7 @@
{:else if param.type === 'slider'} {:else if param.type === 'slider'}
<SliderField <SliderField
id={param.id} id={param.id}
label={param.label} label={paramLabel(tool, param)}
min={param.min} min={param.min}
max={param.max} max={param.max}
step={param.step} step={param.step}
@@ -57,14 +60,17 @@
{:else if param.type === 'select'} {:else if param.type === 'select'}
<SelectField <SelectField
id={param.id} id={param.id}
label={param.label} label={paramLabel(tool, param)}
options={param.options} options={param.options.map((o) => ({
value: o.value,
label: optionLabel(tool, param, o.value)
}))}
bind:value={values[param.id]} bind:value={values[param.id]}
/> />
{:else if param.type === 'color'} {:else if param.type === 'color'}
<ColorField <ColorField
id={param.id} id={param.id}
label={param.label} label={paramLabel(tool, param)}
bind:value={values[param.id]} bind:value={values[param.id]}
pipetteActive={pipetteTargetId === param.id} pipetteActive={pipetteTargetId === param.id}
onPipetteToggle={() => onPipetteToggle?.(param.id)} onPipetteToggle={() => onPipetteToggle?.(param.id)}
+5 -3
View File
@@ -12,6 +12,7 @@
import { createAutoRunner } from '$lib/tools/auto-run'; import { createAutoRunner } from '$lib/tools/auto-run';
import { executeStep } from '$lib/tools/executor'; import { executeStep } from '$lib/tools/executor';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolDescription, toolTitle } from '$lib/i18n/tool-strings';
import ParamsCard from './tool/ParamsCard.svelte'; import ParamsCard from './tool/ParamsCard.svelte';
import ResultCard from './tool/ResultCard.svelte'; import ResultCard from './tool/ResultCard.svelte';
import SourceCard from './tool/SourceCard.svelte'; import SourceCard from './tool/SourceCard.svelte';
@@ -178,7 +179,7 @@
} catch (e) { } catch (e) {
const rawMsg = e instanceof Error ? e.message : String(e); const rawMsg = e instanceof Error ? e.message : String(e);
throw new Error( throw new Error(
t('toolPage.stepError', { n: i + 1, title: stepTool.title, msg: t(rawMsg) }) t('toolPage.stepError', { n: i + 1, title: toolTitle(stepTool), msg: t(rawMsg) })
); );
} }
if (!runner.isCurrent(token)) return; if (!runner.isCurrent(token)) return;
@@ -255,8 +256,8 @@
<svelte:window onpaste={handlePaste} /> <svelte:window onpaste={handlePaste} />
<section> <section>
<h1>{tool.title}</h1> <h1>{toolTitle(tool)}</h1>
<p class="description text-muted">{tool.description}</p> <p class="description text-muted">{toolDescription(tool)}</p>
{#if errorText} {#if errorText}
<div class="error-banner" role="alert">{errorText}</div> <div class="error-banner" role="alert">{errorText}</div>
@@ -307,6 +308,7 @@
<span class="edge-legend" aria-hidden="true">{t('toolPage.legendParams')}</span> <span class="edge-legend" aria-hidden="true">{t('toolPage.legendParams')}</span>
</div> </div>
<ParamsCard <ParamsCard
tool={tool}
params={tool.params} params={tool.params}
bind:values bind:values
{pipetteTargetId} {pipetteTargetId}
@@ -2,6 +2,7 @@
import { outputOf, sanitizeParams, type ToolEntry } from '$lib/registry'; import { outputOf, sanitizeParams, type ToolEntry } from '$lib/registry';
import type { PixelImage } from '$lib/core/types'; import type { PixelImage } from '$lib/core/types';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolTitle } from '$lib/i18n/tool-strings';
import DownloadButton from '../DownloadButton.svelte'; import DownloadButton from '../DownloadButton.svelte';
import Button from '../ui/Button.svelte'; import Button from '../ui/Button.svelte';
import EmptyState from '../ui/EmptyState.svelte'; import EmptyState from '../ui/EmptyState.svelte';
@@ -48,7 +49,7 @@
{#if StepIcon} {#if StepIcon}
<span class="step-icon" aria-hidden="true"><StepIcon size={14} strokeWidth={2} /></span> <span class="step-icon" aria-hidden="true"><StepIcon size={14} strokeWidth={2} /></span>
{/if} {/if}
{t('chain.stepLabel', { n: index + 1, title: tool.title })} {t('chain.stepLabel', { n: index + 1, title: toolTitle(tool) })}
<button <button
type="button" type="button"
class="remove" class="remove"
@@ -99,7 +100,7 @@
<div class="params-sep"> <div class="params-sep">
<span class="edge-legend" aria-hidden="true">{t('chain.paramsLegend')}</span> <span class="edge-legend" aria-hidden="true">{t('chain.paramsLegend')}</span>
</div> </div>
<ParamsCard params={tool.params} bind:values /> <ParamsCard tool={tool} params={tool.params} bind:values />
{/if} {/if}
</div> </div>
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { isChainable, TOOLS } from '$lib/registry'; import { isChainable, TOOLS } from '$lib/registry';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolDescription, toolTitle } from '$lib/i18n/tool-strings';
import ToolCard from './ToolCard.svelte'; import ToolCard from './ToolCard.svelte';
interface Props { interface Props {
@@ -56,8 +57,8 @@
if (s !== null && s > 0) { if (s !== null && s > 0) {
found.push({ found.push({
id: tool.id, id: tool.id,
title: tool.title, title: toolTitle(tool),
description: tool.description, description: toolDescription(tool),
popularity: tool.popularity ?? 50, popularity: tool.popularity ?? 50,
score: s score: s
}); });
@@ -1,9 +1,10 @@
<script lang="ts"> <script lang="ts">
import ParamForm from '../ParamForm.svelte'; import ParamForm from '../ParamForm.svelte';
import type { ParamDef } from '$lib/registry'; import type { ParamDef, ToolEntry } from '$lib/registry';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
interface Props { interface Props {
tool: ToolEntry;
params: ParamDef[]; params: ParamDef[];
values: Record<string, any>; values: Record<string, any>;
pipetteTargetId?: string | null; pipetteTargetId?: string | null;
@@ -13,6 +14,7 @@
} }
let { let {
tool,
params, params,
values = $bindable(), values = $bindable(),
pipetteTargetId = null, pipetteTargetId = null,
@@ -25,6 +27,7 @@
<div class="root"> <div class="root">
{#if params.length > 0 || hasMask} {#if params.length > 0 || hasMask}
<ParamForm <ParamForm
{tool}
{params} {params}
bind:values bind:values
{pipetteTargetId} {pipetteTargetId}
@@ -64,7 +64,7 @@
{:else if tool.resultType === 'text'} {:else if tool.resultType === 'text'}
<div class="media"> <div class="media">
{#if textResult !== null} {#if textResult !== null}
<TextResult text={textResult} filename={tool.id} /> <TextResult text={textResult} filename={tool.id} toolId={tool.id} />
{/if} {/if}
</div> </div>
{:else} {:else}
+10 -4
View File
@@ -1,29 +1,35 @@
<script lang="ts"> <script lang="ts">
import { downloadBlob } from '$lib/core/io'; import { downloadBlob } from '$lib/core/io';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { getMergedDict } from '$lib/i18n/locale.svelte';
interface Props { interface Props {
text: string; text: string;
filename: string; filename: string;
toolId?: string;
} }
let { text, filename }: Props = $props(); let { text, filename, toolId }: Props = $props();
const shown = $derived(
toolId ? (getMergedDict().tools[toolId]?.results?.[text] ?? text) : text
);
let copied = $state(false); let copied = $state(false);
async function copy() { async function copy() {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(shown);
copied = true; copied = true;
setTimeout(() => (copied = false), 1500); setTimeout(() => (copied = false), 1500);
} }
function download() { function download() {
downloadBlob(new Blob([text], { type: 'text/plain' }), `${filename}.txt`); downloadBlob(new Blob([shown], { type: 'text/plain' }), `${filename}.txt`);
} }
</script> </script>
<div class="panel text-result"> <div class="panel text-result">
<textarea class="output" rows="10" readonly value={text} aria-label={t('textResult.outputAria')}></textarea> <textarea class="output" rows="10" readonly value={shown} aria-label={t('textResult.outputAria')}></textarea>
<div class="actions"> <div class="actions">
<button type="button" class="secondary" onclick={copy}> <button type="button" class="secondary" onclick={copy}>
{copied ? t('textResult.copied') : t('textResult.copy')} {copied ? t('textResult.copied') : t('textResult.copy')}
+2 -1
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { getTool } from '$lib/registry'; import { getTool } from '$lib/registry';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolTitle } from '$lib/i18n/tool-strings';
import ToolPage from '$lib/components/ToolPage.svelte'; import ToolPage from '$lib/components/ToolPage.svelte';
import ToolSearch from '$lib/components/search/ToolSearch.svelte'; import ToolSearch from '$lib/components/search/ToolSearch.svelte';
import Button from '$lib/components/ui/Button.svelte'; import Button from '$lib/components/ui/Button.svelte';
@@ -56,7 +57,7 @@
{@const restoreId = lastToolId} {@const restoreId = lastToolId}
<div class="restore-row"> <div class="restore-row">
<Button variant="secondary" fullWidth onclick={() => openTool(restoreId, true)}> <Button variant="secondary" fullWidth onclick={() => openTool(restoreId, true)}>
{t('home.restoreLast', { title: getTool(restoreId)?.title ?? '' })} {t('home.restoreLast', { title: getTool(restoreId) ? toolTitle(getTool(restoreId)!) : '' })}
</Button> </Button>
</div> </div>
{/if} {/if}
+3 -2
View File
@@ -1,6 +1,7 @@
<script lang="ts"> <script lang="ts">
import { CATEGORIES } from '$lib/categories'; import { CATEGORIES } from '$lib/categories';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolDescription, toolTitle } from '$lib/i18n/tool-strings';
import ToolCard from '$lib/components/search/ToolCard.svelte'; import ToolCard from '$lib/components/search/ToolCard.svelte';
import { TOOLS } from '$lib/registry'; import { TOOLS } from '$lib/registry';
</script> </script>
@@ -23,8 +24,8 @@
<div class="panel"> <div class="panel">
<ToolCard <ToolCard
toolId={tool.id} toolId={tool.id}
title={tool.title} title={toolTitle(tool)}
description={tool.description} description={toolDescription(tool)}
href="/tools/{tool.id}" href="/tools/{tool.id}"
/> />
</div> </div>
+3 -2
View File
@@ -2,6 +2,7 @@
import ToolPage from '$lib/components/ToolPage.svelte'; import ToolPage from '$lib/components/ToolPage.svelte';
import { getTool } from '$lib/registry'; import { getTool } from '$lib/registry';
import { t } from '$lib/i18n/t'; import { t } from '$lib/i18n/t';
import { toolDescription, toolTitle } from '$lib/i18n/tool-strings';
let { data } = $props(); let { data } = $props();
@@ -9,9 +10,9 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{tool?.title ?? t('toolPage.fallbackTitle')} — easy-png-tools</title> <title>{tool ? toolTitle(tool) : t('toolPage.fallbackTitle')} — easy-png-tools</title>
{#if tool} {#if tool}
<meta name="description" content={tool.description} /> <meta name="description" content={toolDescription(tool)} />
{/if} {/if}
</svelte:head> </svelte:head>