From d09c95469494ec5f243f1981f6bc08b6e9fa6eb7 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Sun, 27 Sep 2026 16:35:27 +0500 Subject: [PATCH] feat: move routes to slugs --- web/e2e/helpers/page.ts | 8 ++++---- web/src/lib/components/ToolCard.svelte | 2 +- web/src/lib/i18n/en.ts | 5 ++--- web/src/lib/i18n/ru.ts | 5 ++--- web/src/lib/i18n/smoke-i18n.test.ts | 3 ++- web/src/routes/tools/[id]/+page.ts | 11 ----------- web/src/routes/tools/{[id] => [slug]}/+page.svelte | 6 +++--- web/src/routes/tools/[slug]/+page.ts | 11 +++++++++++ 8 files changed, 25 insertions(+), 26 deletions(-) delete mode 100644 web/src/routes/tools/[id]/+page.ts rename web/src/routes/tools/{[id] => [slug]}/+page.svelte (87%) create mode 100644 web/src/routes/tools/[slug]/+page.ts diff --git a/web/e2e/helpers/page.ts b/web/e2e/helpers/page.ts index ecdd6ad..dd78666 100644 --- a/web/e2e/helpers/page.ts +++ b/web/e2e/helpers/page.ts @@ -31,14 +31,14 @@ export async function useEnglish(page: Page): Promise { }); } -export async function openTool(page: Page, id: string): Promise { +export async function openTool(page: Page, slug: string): Promise { await useEnglish(page); - await page.goto(`/tools/${id}`); + await page.goto(`/tools/${slug}`); await expect(page.getByRole("heading", { level: 1 })).toBeVisible(); } -export function toolLink(page: Page, id: string): Locator { - return page.locator(`a[href="/tools/${id}"]`); +export function toolLink(page: Page, slug: string): Locator { + return page.locator(`a[href="/tools/${slug}"]`); } export function resultImage(page: Page): Locator { diff --git a/web/src/lib/components/ToolCard.svelte b/web/src/lib/components/ToolCard.svelte index a83b532..e3276f3 100644 --- a/web/src/lib/components/ToolCard.svelte +++ b/web/src/lib/components/ToolCard.svelte @@ -14,7 +14,7 @@ let { title, slug, description, index, icon }: Props = $props(); - + {#if icon}{/if} {title} diff --git a/web/src/lib/i18n/en.ts b/web/src/lib/i18n/en.ts index e61362a..c0dd30d 100644 --- a/web/src/lib/i18n/en.ts +++ b/web/src/lib/i18n/en.ts @@ -166,7 +166,7 @@ export const en: Dict = { textRequired: "Enter text first", workerFailed: "Worker execution failed", workerUnavailable: "Worker is unavailable", - notFound: "Tool not found", + notFound: "Page not found", noWatermark: "Pick a watermark image first", badTransform: "Degenerate transformation matrix", skewAngle: "Skew angles cannot be 90° or -90°", @@ -189,7 +189,6 @@ export const en: Dict = { "Each pixel must be 8 hex characters RRGGBBAA, separated by spaces", pixelCountMismatch: "Pixel count ({count}) is not divisible by width {width} without a remainder", - toolNotFound: 'Tool "{id}" not found', badJson: "The file is not valid JSON", badPipelineShape: "The file structure does not look like a chain of steps", pipelineVersion: "Unsupported chain version: {version}", @@ -201,7 +200,7 @@ export const en: Dict = { cropSize: "Crop width and height must be positive", sizePositive: "Dimensions must be positive and finite", tooManyParts: "Too many parts ({count}). Maximum is 1000.", - toolUnknown: 'No tool is registered under "{id}".', + pageUnknown: 'No page is registered under "{slug}".', }, fields: { alphaThreshold: "Alpha threshold", diff --git a/web/src/lib/i18n/ru.ts b/web/src/lib/i18n/ru.ts index 9d4b184..bead8bf 100644 --- a/web/src/lib/i18n/ru.ts +++ b/web/src/lib/i18n/ru.ts @@ -167,7 +167,7 @@ export const ru: Dict = { textRequired: "Сначала введите текст", workerFailed: "Ошибка исполнения в воркере", workerUnavailable: "Воркер недоступен", - notFound: "Инструмент не найден", + notFound: "Страница не найдена", noWatermark: "Сначала выберите картинку-знак", badTransform: "Вырожденная матрица трансформации", skewAngle: "Углы наклона не могут быть 90° или -90°", @@ -190,7 +190,6 @@ export const ru: Dict = { "Каждый пиксель — 8 hex-символов RRGGBBAA, значения через пробел", pixelCountMismatch: "Число пикселей ({count}) не делится на ширину {width} без остатка", - toolNotFound: 'Инструмент "{id}" не найден', badJson: "Файл не является корректным JSON", badPipelineShape: "Структура файла не похожа на цепочку шагов", pipelineVersion: "Неподдерживаемая версия цепочки: {version}", @@ -202,7 +201,7 @@ export const ru: Dict = { cropSize: "Ширина и высота области обрезки должны быть положительными", sizePositive: "Размеры должны быть положительными и конечными", tooManyParts: "Слишком много частей ({count}). Ограничение — 1000.", - toolUnknown: "Инструмента с id «{id}» нет в реестре.", + pageUnknown: "Страницы «{slug}» нет в каталоге.", }, fields: { alphaThreshold: "Порог альфы", diff --git a/web/src/lib/i18n/smoke-i18n.test.ts b/web/src/lib/i18n/smoke-i18n.test.ts index f1e9acf..8ec603b 100644 --- a/web/src/lib/i18n/smoke-i18n.test.ts +++ b/web/src/lib/i18n/smoke-i18n.test.ts @@ -64,11 +64,12 @@ describe("Смоук §6 i18n (новый UI)", () => { expect(t("errors.badHex", { value: "#zz" })).toBe( 'Некорректный HEX-цвет: "#zz"', ); - expect(t("errors.toolNotFound", { id: "x" })).toContain("не найден"); + expect(t("errors.pageUnknown", { slug: "x" })).toContain("каталоге"); setLocale("en"); expect(t("errors.badHex", { value: "#zz" })).toBe( 'Invalid HEX color: "#zz"', ); + expect(t("errors.pageUnknown", { slug: "x" })).toContain("No page"); }); it("6. Ё не мешает нормализации поискового запроса", () => { diff --git a/web/src/routes/tools/[id]/+page.ts b/web/src/routes/tools/[id]/+page.ts deleted file mode 100644 index bfea726..0000000 --- a/web/src/routes/tools/[id]/+page.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { PAGES } from "$lib/registry"; - -export const prerender = true; - -export function entries() { - return PAGES.map((page) => ({ id: page.slug })); -} - -export function load({ params }: { params: { id: string } }) { - return { id: params.id }; -} diff --git a/web/src/routes/tools/[id]/+page.svelte b/web/src/routes/tools/[slug]/+page.svelte similarity index 87% rename from web/src/routes/tools/[id]/+page.svelte rename to web/src/routes/tools/[slug]/+page.svelte index 9f31b00..9523637 100644 --- a/web/src/routes/tools/[id]/+page.svelte +++ b/web/src/routes/tools/[slug]/+page.svelte @@ -6,11 +6,11 @@ import { SlidersHorizontal as ToolIcon } from "@lucide/svelte"; interface Props { - data: { id: string }; + data: { slug: string }; } let { data }: Props = $props(); - const page = $derived(getPageBySlug(data.id)); + const page = $derived(getPageBySlug(data.slug)); const tool = $derived(page ? getTool(page.steps[0].id) : undefined); @@ -23,7 +23,7 @@
diff --git a/web/src/routes/tools/[slug]/+page.ts b/web/src/routes/tools/[slug]/+page.ts new file mode 100644 index 0000000..0946cf7 --- /dev/null +++ b/web/src/routes/tools/[slug]/+page.ts @@ -0,0 +1,11 @@ +import { PAGES } from "$lib/registry"; + +export const prerender = true; + +export function entries() { + return PAGES.map((page) => ({ slug: page.slug })); +} + +export function load({ params }: { params: { slug: string } }) { + return { slug: params.slug }; +}