mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
refactor: add error messages i18n
This commit is contained in:
+30
-1
@@ -108,7 +108,36 @@ export const en: Dict = {
|
||||
noImageRun: 'This tool does not process images',
|
||||
workerFailed: 'Worker execution failed',
|
||||
workerUnavailable: 'Worker is unavailable',
|
||||
notFound: 'Tool not found'
|
||||
notFound: 'Tool not found',
|
||||
badTransform: 'Degenerate transformation matrix',
|
||||
skewAngle: 'Skew angles cannot be 90° or -90°',
|
||||
badHex: 'Invalid HEX color: "{value}"',
|
||||
radiusInt: 'Radius must be a non-negative integer',
|
||||
kernelSize: 'Kernel does not match the image dimensions',
|
||||
sizeInt: 'Width and height must be integers ≥ 1',
|
||||
cropBounds: 'Crop area does not intersect the image',
|
||||
noCanvasCtx: 'Canvas 2D context is unavailable in this environment',
|
||||
badBase64: 'Expected a base64 string or a data-uri of an image',
|
||||
qualityRange: 'quality must be within 0..1',
|
||||
svgSize: 'Could not determine SVG dimensions',
|
||||
svgLoad: 'Failed to load SVG — check the markup',
|
||||
encodeUnsupported: 'The browser does not support encoding to {mime}',
|
||||
unsupportedFile:
|
||||
'Unsupported file format ({type}). Supported formats are PNG, JPEG, WebP, GIF and BMP.',
|
||||
widthInt: 'Image width must be an integer ≥ 1',
|
||||
noHexPixels: 'No hex pixel values found',
|
||||
badPixelToken: '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}',
|
||||
noSteps: 'The file has no list of steps',
|
||||
paramNumber: 'Parameter "{id}" must be a number',
|
||||
paramString: 'Parameter "{id}" must be a string',
|
||||
resizeSize: 'Width and/or height must be positive',
|
||||
cropSize: 'Crop width and height must be positive',
|
||||
sizePositive: 'Dimensions must be positive and finite'
|
||||
},
|
||||
tools: {
|
||||
'png-is-grayscale': {
|
||||
|
||||
+30
-1
@@ -108,7 +108,36 @@ export const ru: Dict = {
|
||||
noImageRun: 'Этот инструмент не обрабатывает изображения',
|
||||
workerFailed: 'Ошибка исполнения в воркере',
|
||||
workerUnavailable: 'Воркер недоступен',
|
||||
notFound: 'Инструмент не найден'
|
||||
notFound: 'Инструмент не найден',
|
||||
badTransform: 'Вырожденная матрица трансформации',
|
||||
skewAngle: 'Углы наклона не могут быть 90° или -90°',
|
||||
badHex: 'Некорректный HEX-цвет: "{value}"',
|
||||
radiusInt: 'Радиус должен быть целым неотрицательным числом',
|
||||
kernelSize: 'Ядро не совпадает с изображением по размеру',
|
||||
sizeInt: 'Ширина и высота должны быть целыми числами ≥ 1',
|
||||
cropBounds: 'Область обрезки не пересекает изображение',
|
||||
noCanvasCtx: 'Canvas 2D context недоступен в этом окружении',
|
||||
badBase64: 'Ожидается base64-строка или data-uri изображения',
|
||||
qualityRange: 'quality должно быть в диапазоне 0..1',
|
||||
svgSize: 'Не удалось определить размер SVG',
|
||||
svgLoad: 'Не удалось загрузить SVG — проверьте разметку',
|
||||
encodeUnsupported: 'Браузер не поддерживает кодирование в {mime}',
|
||||
unsupportedFile:
|
||||
'Неподдерживаемый формат файла ({type}). Поддерживаются PNG, JPEG, WebP, GIF и BMP.',
|
||||
widthInt: 'Ширина изображения должна быть целым числом ≥ 1',
|
||||
noHexPixels: 'Не найдено hex-значений пикселей',
|
||||
badPixelToken: 'Каждый пиксель — 8 hex-символов RRGGBBAA, значения через пробел',
|
||||
pixelCountMismatch: 'Число пикселей ({count}) не делится на ширину {width} без остатка',
|
||||
toolNotFound: 'Инструмент "{id}" не найден',
|
||||
badJson: 'Файл не является корректным JSON',
|
||||
badPipelineShape: 'Структура файла не похожа на цепочку шагов',
|
||||
pipelineVersion: 'Неподдерживаемая версия цепочки: {version}',
|
||||
noSteps: 'В файле нет списка шагов',
|
||||
paramNumber: 'Параметр "{id}" должен быть числом',
|
||||
paramString: 'Параметр "{id}" должен быть строкой',
|
||||
resizeSize: 'Ширина и/или высота должны быть положительными',
|
||||
cropSize: 'Ширина и высота области обрезки должны быть положительными',
|
||||
sizePositive: 'Размеры должны быть положительными и конечными'
|
||||
},
|
||||
tools: {
|
||||
'jpg-to-png': {
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { setLocale } from './locale.svelte';
|
||||
import { t } from './t';
|
||||
import { LOCALE_TAGS } from './dict';
|
||||
import { normalizeForSearch, scoreDoc } from './matching';
|
||||
import { toolSearchDoc } from './tool-strings';
|
||||
import { TOOLS } from '../registry';
|
||||
|
||||
afterEach(() => {
|
||||
setLocale('ru');
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
describe('Смоук §6 i18n', () => {
|
||||
it('1. html lang следует за локалью', () => {
|
||||
const doc = { documentElement: { lang: '' } };
|
||||
vi.stubGlobal('document', doc);
|
||||
setLocale('en');
|
||||
expect(doc.documentElement.lang).toBe('en');
|
||||
setLocale('ru');
|
||||
expect(doc.documentElement.lang).toBe('ru');
|
||||
});
|
||||
|
||||
it('3. Ключевые секции переведены без смеси языков', () => {
|
||||
const samples: Array<[string, string, string]> = [
|
||||
['header.workspace', 'Рабочая область', 'Workspace'],
|
||||
['catalog.heading', 'Каталог инструментов', 'Tool catalog'],
|
||||
['home.heroTitle', 'Что делаем с изображением?', 'What do you want to do'],
|
||||
['chain.inputLegend', 'Вход', 'Input'],
|
||||
['resultCard.nextTool', 'Следующий инструмент', 'Next tool'],
|
||||
['download.busy', 'Готовим файл', 'Preparing file']
|
||||
];
|
||||
for (const [key, ruPart, enPart] of samples) {
|
||||
setLocale('ru');
|
||||
expect(t(key), key + ' @ru').toContain(ruPart);
|
||||
setLocale('en');
|
||||
expect(t(key), key + ' @en').toContain(enPart);
|
||||
}
|
||||
});
|
||||
|
||||
it('5. Ошибки с vars локализуются на оба языка', () => {
|
||||
setLocale('ru');
|
||||
expect(t('errors.badHex', { value: '#zz' })).toBe('Некорректный HEX-цвет: "#zz"');
|
||||
expect(t('errors.toolNotFound', { id: 'x' })).toContain('не найден');
|
||||
setLocale('en');
|
||||
expect(t('errors.badHex', { value: '#zz' })).toBe('Invalid HEX color: "#zz"');
|
||||
});
|
||||
|
||||
it('6. Поиск кросс-языковой в обе стороны', () => {
|
||||
const ids = (q: string) =>
|
||||
TOOLS.filter((tool) => {
|
||||
const s = scoreDoc(toolSearchDoc(tool), normalizeForSearch(q));
|
||||
return s !== null && s > 0;
|
||||
}).map((tool) => tool.id);
|
||||
|
||||
setLocale('ru');
|
||||
let hits = ids('rotate');
|
||||
expect(hits).toContain('rotate-png');
|
||||
hits = ids('повер');
|
||||
expect(hits).toContain('rotate-png');
|
||||
|
||||
setLocale('en');
|
||||
hits = ids('пово');
|
||||
expect(hits).toContain('rotate-free-png');
|
||||
hits = ids('rotate');
|
||||
expect(hits).toContain('rotate-png');
|
||||
});
|
||||
|
||||
it('6b. Ё не мешает совпадению', () => {
|
||||
const blackWhite = TOOLS.find((tool) => tool.id === 'black-and-white-png')!;
|
||||
setLocale('ru');
|
||||
const hit = scoreDoc(toolSearchDoc(blackWhite), normalizeForSearch('ЧЁРНО'));
|
||||
expect(hit).not.toBeNull();
|
||||
const withoutYo = scoreDoc(toolSearchDoc(blackWhite), normalizeForSearch('черно'));
|
||||
expect(withoutYo).not.toBeNull();
|
||||
});
|
||||
|
||||
it('7. Теги локалей для форматирования чисел корректны', () => {
|
||||
expect(LOCALE_TAGS.ru).toBe('ru-RU');
|
||||
expect(LOCALE_TAGS.en).toBe('en-US');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user