mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 08:28:31 +00:00
feat v1.11.0: full English translation + en/ru UI localization
- All card UI strings moved to src/i18n.ts (en/ru); language follows the HA profile automatically, new 'language: en|ru' card option forces it; GUI editor localized and got the language dropdown; generated device names localized via BuildCtx.loc. - English-only codebase: comments, docstrings, test names, backend error messages and logs. Russian remains only in the ru dictionary, ru.json, iconFor regexes matching Russian device names (+their fixtures) and README.ru.md. - Docs English-first: README (EN) + README.ru.md, ARCHITECTURE/DEVELOPMENT/ ROADMAP/CHANGELOG fully translated; translations/en.json had Russian - fixed. - Removed obsolete RELEASE_NOTES_v1.9.3.md and scripts_publish.sh.
This commit is contained in:
+21
-20
@@ -6,7 +6,7 @@ import {
|
||||
} from '../test-build/logic.js';
|
||||
import { iconFor } from '../test-build/rules.js';
|
||||
|
||||
test('lqiColor: границы и середина', () => {
|
||||
test('lqiColor: boundaries and midpoint', () => {
|
||||
assert.equal(lqiColor(40), 'hsl(0, 85%, 55%)');
|
||||
assert.equal(lqiColor(180), 'hsl(120, 85%, 55%)');
|
||||
assert.equal(lqiColor(110), 'hsl(60, 85%, 55%)');
|
||||
@@ -20,24 +20,24 @@ test('snapToGrid', () => {
|
||||
assert.equal(snapToGrid(16, 10), 20);
|
||||
});
|
||||
|
||||
test('segKey: направление не влияет', () => {
|
||||
test('segKey: direction does not matter', () => {
|
||||
assert.equal(segKey([0, 0], [10, 5]), segKey([10, 5], [0, 0]));
|
||||
assert.notEqual(segKey([0, 0], [10, 5]), segKey([0, 0], [10, 6]));
|
||||
});
|
||||
|
||||
test('samePoint с допуском', () => {
|
||||
test('samePoint with tolerance', () => {
|
||||
assert.ok(samePoint([1, 1], [1.0005, 0.9995]));
|
||||
assert.ok(!samePoint([1, 1], [1.5, 1]));
|
||||
});
|
||||
|
||||
test('pointInPolygon: квадрат', () => {
|
||||
test('pointInPolygon: square', () => {
|
||||
const sq = [[0, 0], [10, 0], [10, 10], [0, 10]];
|
||||
assert.ok(pointInPolygon([5, 5], sq));
|
||||
assert.ok(!pointInPolygon([15, 5], sq));
|
||||
assert.ok(!pointInPolygon([-1, -1], sq));
|
||||
});
|
||||
|
||||
test('pointInPolygon: L-образный полигон', () => {
|
||||
test('pointInPolygon: L-shaped polygon', () => {
|
||||
const L = [[0, 0], [6, 0], [6, 2], [2, 2], [2, 6], [0, 6]];
|
||||
assert.ok(pointInPolygon([1, 5], L));
|
||||
assert.ok(pointInPolygon([5, 1], L));
|
||||
@@ -60,60 +60,61 @@ test('averageLqi', () => {
|
||||
assert.equal(averageLqi([1, 2, 2]), 2);
|
||||
});
|
||||
|
||||
test('iconFor: ключевые правила', () => {
|
||||
test('iconFor: key rules', () => {
|
||||
// Russian device names below are intentional: iconFor rules match Russian names (see src/rules.ts).
|
||||
assert.equal(iconFor('Датчик протечки кухня', 'HOBEIAN'), 'mdi:water-alert');
|
||||
assert.equal(iconFor('Замок Терраса', 'TTLock'), 'mdi:lock');
|
||||
assert.equal(iconFor('Настенная лампа 1', 'Yandex Bulb'), 'mdi:lightbulb');
|
||||
assert.equal(iconFor('Ворота', 'Tuya Garage'), 'mdi:garage-variant');
|
||||
assert.equal(iconFor('Термоголовка', 'Aqara'), 'mdi:radiator');
|
||||
assert.equal(iconFor('Неизвестное', 'XYZ'), 'mdi:chip');
|
||||
assert.equal(iconFor('Unknown gadget', 'XYZ'), 'mdi:chip');
|
||||
});
|
||||
|
||||
test('fitView: портретный план в широкой сцене — по бокам поля, весь план внутри', () => {
|
||||
// vb 100x200 (аспект 0.5), сцена аспект 2 → view шире плана, высота = 200
|
||||
test('fitView: portrait plan in a wide scene — margins on the sides, whole plan inside', () => {
|
||||
// vb 100x200 (aspect 0.5), scene aspect 2 → view wider than the plan, height = 200
|
||||
const v = fitView([0, 0, 100, 200], 2);
|
||||
assert.equal(v.h, 200);
|
||||
assert.equal(v.w, 400); // 200*2
|
||||
assert.equal(v.x, -150); // (100-400)/2 центрирование
|
||||
assert.equal(v.x, -150); // (100-400)/2 centering
|
||||
assert.equal(v.y, 0);
|
||||
// весь план внутри view
|
||||
// the whole plan is inside the view
|
||||
assert.ok(v.x <= 0 && v.x + v.w >= 100 && v.y <= 0 && v.y + v.h >= 200);
|
||||
});
|
||||
|
||||
test('fitView: аспект сцены совпадает с планом — view == vb', () => {
|
||||
const v = fitView([10, 20, 300, 150], 2); // планA = 2 == аспект
|
||||
test('fitView: scene aspect matches the plan — view == vb', () => {
|
||||
const v = fitView([10, 20, 300, 150], 2); // plan aspect = 2 == scene aspect
|
||||
assert.equal(v.x, 10); assert.equal(v.y, 20); assert.equal(v.w, 300); assert.equal(v.h, 150);
|
||||
});
|
||||
|
||||
test('declump: близкие точки расходятся не ближе minDist и остаются в границах', () => {
|
||||
test('declump: close points spread apart no closer than minDist and stay within bounds', () => {
|
||||
const b = { x: 0, y: 0, w: 100, h: 100 };
|
||||
const pts = [ { x: 50, y: 50 }, { x: 51, y: 50 }, { x: 50, y: 51 } ];
|
||||
declump(pts, b, 20, 5);
|
||||
// все пары не ближе ~minDist (с допуском на кламп к границам)
|
||||
// all pairs no closer than ~minDist (with tolerance for clamping to the bounds)
|
||||
for (let i = 0; i < pts.length; i++)
|
||||
for (let j = i + 1; j < pts.length; j++) {
|
||||
const d = Math.hypot(pts[i].x - pts[j].x, pts[i].y - pts[j].y);
|
||||
assert.ok(d > 12, `пара ${i},${j} слишком близко: ${d}`);
|
||||
assert.ok(d > 12, `pair ${i},${j} too close: ${d}`);
|
||||
}
|
||||
// в границах [5..95]
|
||||
// within the bounds [5..95]
|
||||
for (const q of pts) {
|
||||
assert.ok(q.x >= 5 && q.x <= 95 && q.y >= 5 && q.y <= 95);
|
||||
}
|
||||
});
|
||||
|
||||
test('declump: одна точка не двигается', () => {
|
||||
test('declump: a single point does not move', () => {
|
||||
const pts = [{ x: 30, y: 40 }];
|
||||
declump(pts, { x: 0, y: 0, w: 100, h: 100 }, 20, 5);
|
||||
assert.deepEqual(pts, [{ x: 30, y: 40 }]);
|
||||
});
|
||||
|
||||
test('averageLqi: пусто → null, иначе округлённое среднее', () => {
|
||||
test('averageLqi: empty → null, otherwise the rounded average', () => {
|
||||
assert.equal(averageLqi([]), null);
|
||||
assert.equal(averageLqi([100, 200]), 150);
|
||||
assert.equal(averageLqi([1, 2, 2]), 2);
|
||||
});
|
||||
|
||||
test('safeUrl: допускает http(s) и относительные, режет опасные схемы', () => {
|
||||
test('safeUrl: allows http(s) and relative paths, cuts dangerous schemes', () => {
|
||||
assert.equal(safeUrl('https://example.com/a?b=1'), 'https://example.com/a?b=1');
|
||||
assert.equal(safeUrl('http://x.ru'), 'http://x.ru');
|
||||
assert.equal(safeUrl('//cdn.x.ru/f.pdf'), '//cdn.x.ru/f.pdf');
|
||||
|
||||
Reference in New Issue
Block a user