mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -0,0 +1,385 @@
|
||||
# Issue #348 — полная немецкая локализация House Plan
|
||||
|
||||
- **Issue:** https://github.com/Matysh/houseplan-card/issues/348
|
||||
- **Зависимость:** #62 — единый реестр языков и автоматические i18n-gates
|
||||
- **Статус документа:** готово к ревью ТЗ
|
||||
- **Приоритет / тип:** P1 / feature
|
||||
- **Трек:** полный: задача меняет публичный языковой контракт, несколько
|
||||
frontend/backend-поверхностей, первый render и performance budget
|
||||
- **Пользовательское изменение:** да
|
||||
|
||||
## 1. Сценарий и пользовательская ценность
|
||||
|
||||
Немецкоязычный пользователь выбирает Deutsch в профиле Home Assistant либо
|
||||
явно в настройках карточки. После этого View, kiosk, встроенные редакторы,
|
||||
диалоги, подсказки, ошибки и config/options flow интеграции House Plan работают
|
||||
на немецком без английских или русских фрагментов.
|
||||
|
||||
Задача закрывает языковой барьер для крупной Home Assistant-аудитории и впервые
|
||||
проверяет инфраструктуру #62 на реальном третьем языке. После неё следующий
|
||||
перевод должен добавляться тем же проверяемым путём, без нового ручного списка
|
||||
локалей.
|
||||
|
||||
## 2. Что человек увидит до и после
|
||||
|
||||
**До:** `de`, `de-DE`, `de-AT` и `de-CH` не зарегистрированы и синхронно
|
||||
откатываются на English. В selector языка нет Deutsch.
|
||||
|
||||
**После:**
|
||||
|
||||
- в selector языка после `Русский` появляется `Deutsch`;
|
||||
- Auto выбирает Deutsch для любого HA locale с primary subtag `de`;
|
||||
- явное `language: de` принудительно включает немецкий независимо от профиля;
|
||||
- все собственные строки House Plan отображаются на немецком;
|
||||
- при первой загрузке немецкого словаря пользователь не видит промежуточный
|
||||
English UI: на холодном mount допустим только короткий нейтральный busy-state,
|
||||
а уже отрисованный кадр при смене языка остаётся на месте до атомарной смены;
|
||||
- English и Русский продолжают работать и загружаться синхронно, как до задачи.
|
||||
|
||||
## 3. Границы перевода
|
||||
|
||||
### 3.1. Переводится
|
||||
|
||||
- весь key set `src/i18n/en.json`: View, kiosk, Plan/Devices/Background editors,
|
||||
onboarding, диалоги, tooltips/help, aria-тексты, подтверждения, toasts, ошибки,
|
||||
backup/Optimize/vacuum и GUI editors обеих карточек;
|
||||
- `custom_components/houseplan/translations/de.json`: config flow, options flow
|
||||
и repair issue;
|
||||
- native label языка — `Deutsch`.
|
||||
|
||||
### 3.2. Не переводится карточкой
|
||||
|
||||
- названия пространств, комнат, устройств, сущностей, HA areas и интеграций;
|
||||
- состояния, единицы, даты и значения, которые уже форматирует Home Assistant;
|
||||
- entity IDs, model names, filenames, URLs, stable config/API tokens;
|
||||
- бренд `House Plan`, `Home Assistant`, `HA`, `HACS`, `Zigbee`, `LQI`, `Glow` и
|
||||
общеупотребимые технические имена форматов;
|
||||
- полный немецкий комплект README/User Guide. Каноническая пользовательская
|
||||
документация по-прежнему ведётся на English и Русском по правилу #62.
|
||||
|
||||
## 4. Языковой и терминологический контракт
|
||||
|
||||
English dictionary — смысловой канон; Русский используется только для снятия
|
||||
неоднозначности. Перевод не должен буквально переносить ошибки или неудачную
|
||||
грамматику исходной строки: смысл, действие и уровень опасности сохраняются.
|
||||
|
||||
Базовый glossary:
|
||||
|
||||
| Канонический термин | Немецкий UI |
|
||||
| --- | --- |
|
||||
| View | Ansicht |
|
||||
| Plan editor | Planeditor |
|
||||
| Device editor | Geräteeditor |
|
||||
| Background editor | Hintergrundeditor |
|
||||
| Space | Bereich |
|
||||
| Room | Raum |
|
||||
| Wall / physical wall | Wand / physische Wand |
|
||||
| Zero-thickness wall | Wand ohne Dicke |
|
||||
| Partition | Trennwand |
|
||||
| Column | Säule |
|
||||
| Opening / passage | Öffnung / offener Durchgang |
|
||||
| Door / window / gate | Tür / Fenster / Tor |
|
||||
| Device / entity | Gerät / Entität |
|
||||
| Fill | Füllung |
|
||||
| Glow | Glow |
|
||||
| Save / Cancel / Delete | Speichern / Abbrechen / Löschen |
|
||||
|
||||
Правила качества:
|
||||
|
||||
1. Используется вежливый нейтральный UI без `du`/`Sie`, где это возможно;
|
||||
команды формулируются коротким imperative/infinitive style.
|
||||
2. Существительные и заголовки следуют немецкому написанию с заглавной буквы;
|
||||
предложения — sentence case, не English Title Case.
|
||||
3. Используются настоящие `ä`, `ö`, `ü`, `ß`, а не `ae/oe/ue/ss`, кроме
|
||||
стабильных идентификаторов.
|
||||
4. Placeholders `{name}`, `{n}`, `{reason}` и остальные не переводятся, не
|
||||
добавляются и не удаляются; знаки вокруг них согласуются по-немецки.
|
||||
5. Строки с числом сохраняют нейтральную грамматику: текущий `subst()` не имеет
|
||||
ICU/plural rules.
|
||||
6. Кнопки и короткие labels не заканчиваются точкой; ошибки и полные подсказки
|
||||
могут заканчиваться точкой согласованно с English.
|
||||
7. В одном workflow нельзя смешивать синонимы из glossary без смысловой причины.
|
||||
|
||||
## 5. Locale resolution и persisted config
|
||||
|
||||
Реестр #62 получает canonical entry:
|
||||
|
||||
```ts
|
||||
{ code: 'de', nativeLabel: 'Deutsch', ... }
|
||||
```
|
||||
|
||||
Существующая exact → primary → English политика не меняется:
|
||||
|
||||
| Explicit config | HA locale | Результат |
|
||||
| --- | --- | --- |
|
||||
| `de`, `DE` | любой | `de` |
|
||||
| пусто/нет | `de`, `de-DE`, `de_AT`, `de-CH` | `de` |
|
||||
| неизвестное legacy-значение | `de-*` | `de` |
|
||||
| пусто/нет | неизвестная локаль | `en` |
|
||||
|
||||
`language` остаётся строковым полем без изменения schema и миграции. Уже
|
||||
сохранённое неизвестное значение selector по-прежнему показывает losslessly,
|
||||
пока пользователь явно не выберет Auto или зарегистрированный язык.
|
||||
|
||||
## 6. Runtime загрузки немецкого словаря
|
||||
|
||||
### 6.1. Почему словарь ленивый
|
||||
|
||||
На базе `origin/dev` перед задачей initial View graph занимает **255 910 B gzip
|
||||
из бюджета 256 000 B**. Frontend English/Russian dictionaries имеют около
|
||||
66/70 KiB raw каждый. Третий static import гарантированно нарушит принятый в
|
||||
#337 budget. Повышение бюджета либо перенос случайного View-кода в editor chunk
|
||||
не являются допустимым решением этой задачи.
|
||||
|
||||
English и Русский сохраняются eager/synchronous ради compatibility-контракта
|
||||
#62. Только `de` поставляется отдельным content-hashed dynamic locale chunk.
|
||||
|
||||
### 6.2. Registry и cache
|
||||
|
||||
Typed registry остаётся единственным источником code/native label/stable order,
|
||||
но поддерживает две формы записи:
|
||||
|
||||
- eager dictionary (`en`, `ru`);
|
||||
- lazy loader (`de`).
|
||||
|
||||
Общий locale runtime:
|
||||
|
||||
- дедуплицирует одновременные запросы `de` от нескольких экземпляров карточек;
|
||||
- хранит успешно загруженный dictionary в page-lifetime cache;
|
||||
- предоставляет синхронным `t()`/`hasTranslation()` только уже готовый словарь;
|
||||
- уведомляет подписанные Lit hosts об успешном завершении;
|
||||
- не делает fetch/import для `en` и `ru`;
|
||||
- очищает rejected promise и выполняет один bounded retry; после повторного
|
||||
отказа разблокирует UI с English fallback, пишет один `console.warn` и не
|
||||
создаёт бесконечного цикла запросов/рендеров.
|
||||
|
||||
Dynamic module экспортирует собственный build fingerprint. Loader принимает
|
||||
словарь только при совпадении с fingerprint initial chunk; stale/mixed bundle не
|
||||
может тихо отрисовать перевод от другой версии.
|
||||
|
||||
### 6.3. Render gate
|
||||
|
||||
Один shared host controller используется четырьмя самостоятельными корневыми
|
||||
поверхностями:
|
||||
|
||||
1. `houseplan-card`;
|
||||
2. `houseplan-space-card`;
|
||||
3. GUI editor `houseplan-card`;
|
||||
4. GUI editor `houseplan-space-card`.
|
||||
|
||||
До готовности выбранного lazy dictionary:
|
||||
|
||||
- холодный mount рисует только нейтральный `aria-busy="true"` progress surface
|
||||
без English/Russian текста и без интерактивных элементов;
|
||||
- если host уже имеет полный кадр и язык меняют на `de`, старый кадр остаётся
|
||||
неизменным до готовности и затем заменяется немецким атомарно;
|
||||
- редактор/диалог не может принять click по невидимой старой локали;
|
||||
- late resolve после смены обратно на `en`/`ru` или disconnect не возвращает
|
||||
host на `de` и не вызывает исключения;
|
||||
- ошибка после bounded retry переходит в рабочий English fallback, а не оставляет
|
||||
вечный spinner.
|
||||
|
||||
Вложенные локализованные компоненты не создают независимых запросов: они
|
||||
появляются только после gate корневого host либо подписываются на тот же cache.
|
||||
|
||||
## 7. Bundle manifest, доставка и budget
|
||||
|
||||
Locale chunk является полноценным HACS asset:
|
||||
|
||||
- попадает в `houseplan-assets.json`, bundle sync, backend allowlist, release ZIP
|
||||
и tree/hash verification из #337;
|
||||
- входит в `lazyFiles`, но **не** в `lazyEditorFiles` и
|
||||
`lazyOnboardingFiles`; manifest получает явную `lazyLocaleFiles` группу;
|
||||
- не запрашивается при English/Russian View;
|
||||
- при немецком View загружается ровно один раз на страницу до появления первого
|
||||
локализованного кадра;
|
||||
- initial View graph после loader/registry изменений остаётся ≤ 256 000 B gzip.
|
||||
|
||||
Разрешено удалить только доказанно неиспользуемые legacy i18n keys (включая
|
||||
оставшиеся после #62 ручные labels `editor.lang_en`/`editor.lang_ru`), если это
|
||||
нужно для сохранения budget. Удаление подтверждается source-usage test и проходит
|
||||
одинаково во всех словарях; смысл живого UI не меняется. Повышать budget нельзя.
|
||||
|
||||
## 8. Layout, accessibility и touch
|
||||
|
||||
Немецкие строки обычно длиннее English. Перевод считается рабочим только если:
|
||||
|
||||
- заголовки диалогов переносятся и не обрезаются;
|
||||
- footer actions не создают горизонтальный scroll и остаются доступны;
|
||||
- toolbars/context trays используют штатный wrap/scroll, не уменьшая рабочую
|
||||
область иначе, чем English/Russian;
|
||||
- View и kiosk при 390 px остаются полностью работоспособны по
|
||||
`docs/TOUCH-SUPPORT.md`;
|
||||
- tooltip/help не создаёт лишний dialog scroll;
|
||||
- `aria-label`, `role=status/alert` и busy-state имеют корректный язык либо
|
||||
нейтральны до загрузки;
|
||||
- `lang="de"` или эквивалентный локальный marker доступен корневой поверхности,
|
||||
чтобы browser/assistive technology применяли немецкие правила произношения.
|
||||
|
||||
Редакторы остаются desktop-first; их данные обязаны быть безопасны на touch, но
|
||||
новая точность touch-editing в задачу не входит.
|
||||
|
||||
## 9. Translation completeness и автоматические gates
|
||||
|
||||
Проверки #62 расширяются на eager и lazy entries и обязаны загружать production
|
||||
словари через registry API, а не обходить lazy path прямым чтением только ради
|
||||
зелёного теста.
|
||||
|
||||
Gate проверяет:
|
||||
|
||||
- registry ↔ frontend files ↔ backend files one-to-one для `en`, `ru`, `de`;
|
||||
- exact key set относительно English, непустые значения, placeholders и полный
|
||||
help body/aria contract;
|
||||
- отсутствие кириллицы в `de` кроме явно allowlisted user/example literals;
|
||||
- отсутствие необоснованного English fallback: значения `de` не равны English,
|
||||
кроме allowlist брендов, единиц, стабильных терминов и односимвольных glyph;
|
||||
- glossary для ключевых публичных controls;
|
||||
- locale matrix `de`/`de-*`/explicit/fallback и selector order
|
||||
`Auto`, `English`, `Русский`, `Deutsch`;
|
||||
- lazy state machine: dedupe, success, race, retry/failure fallback, disconnect;
|
||||
- production bundle не содержит German dictionary в initial static graph.
|
||||
|
||||
Backend translation test проверяет German config/options/repair strings через
|
||||
HA translation API либо, если harness не предоставляет стабильный language
|
||||
API, точную file/key/placeholder parity плюс чтение `de.json`.
|
||||
|
||||
## 10. Acceptance criteria
|
||||
|
||||
- **AC1. Полный German key set.** `de` зарегистрирован как `Deutsch`; frontend и
|
||||
backend dictionaries имеют полный набор ключей, непустые значения и точные
|
||||
placeholders/help pairs. **Доказательство:** registry-driven unit + backend
|
||||
translation test.
|
||||
- **AC2. Locale resolution.** Explicit `de` и Auto для `de`, `de-DE`, `de_AT`,
|
||||
`de-AT`, `de-CH` выбирают German; неизвестная locale использует English.
|
||||
**Доказательство:** unit matrix.
|
||||
- **AC3. Нет смешанного UI.** Репрезентативные View, device dialog, Plan editor,
|
||||
Devices editor, Background editor, onboarding и оба GUI editors содержат
|
||||
немецкие House Plan strings и не содержат English/Russian UI fragments.
|
||||
**Доказательство:** targeted Playwright smoke с semantic assertions.
|
||||
- **AC4. Без языкового flash.** На искусственно задержанном `de` import холодный
|
||||
mount показывает нейтральный busy-state; warm language switch сохраняет старый
|
||||
complete frame и атомарно публикует German; controls inert до commit.
|
||||
**Доказательство:** unit state machine + Playwright delayed-load smoke.
|
||||
- **AC5. Без deadlock при ошибке.** Два отказа lazy load дают один warning и
|
||||
рабочий English fallback; смена языка/disconnect во время pending не публикует
|
||||
устаревший кадр. **Доказательство:** unit с controllable loader.
|
||||
- **AC6. Performance.** German dictionary отсутствует в initial static graph;
|
||||
English/Russian не запрашивают locale chunk; German загружает его один раз;
|
||||
initial View ≤ 256 000 B gzip. **Доказательство:** bundle-manifest/budget unit,
|
||||
production build manifest и Playwright network trace.
|
||||
- **AC7. Доставка.** Locale chunk присутствует в manifest, HACS tree, backend
|
||||
allowlist и ZIP-verification path; classified как `lazyLocaleFiles`, а не
|
||||
editor/onboarding. **Доказательство:** bundle tree/backend tests.
|
||||
- **AC8. Layout/accessibility.** Немецкие View/kiosk и репрезентативные dialogs
|
||||
не имеют clipping/горизонтального scroll при desktop и 390 px; busy-state
|
||||
нейтрален и `aria-busy`; German surface сообщает locale assistive technology.
|
||||
**Доказательство:** smoke metrics + reviewed golden desktop/mobile.
|
||||
- **AC9. English/Russian compatibility.** Existing eager first render, locale
|
||||
selector values, persisted config и видимые EN/RU strings не меняются, кроме
|
||||
появления option Deutsch и удаления доказанно мёртвых registry-label keys.
|
||||
**Доказательство:** unit regression + bundle network smoke + diff review.
|
||||
- **AC10. Документация и release artifacts.** User guides перечисляют `de`,
|
||||
contribution docs описывают eager/lazy third-locale flow, а оба changelog
|
||||
сообщают о German UI и ссылке #348. **Доказательство:** docs diff/check-docs.
|
||||
|
||||
## 11. План тестов и обязательные gates
|
||||
|
||||
### Unit / backend
|
||||
|
||||
- registry/file/key/placeholder/help/glossary parity для трёх языков;
|
||||
- locale resolution и selector order;
|
||||
- pure locale loader/controller state machine с deferred promises;
|
||||
- bundle manifest role classification и initial budget;
|
||||
- locale asset allowlist/tree validation;
|
||||
- German backend translation structure/lookup.
|
||||
|
||||
### Browser / visual
|
||||
|
||||
- новый `demo/smoke_german_locale.mjs`: semantic coverage AC3, locale switch,
|
||||
delayed loader, failure fallback и network count;
|
||||
- German desktop golden: основной View + открытый device/settings dialog;
|
||||
- German 390 px golden: View/kiosk либо самый длинный гарантированный View
|
||||
dialog; отсутствие overflow измеряется smoke, а не только глазами;
|
||||
- существующие EN/RU docs/golden не перезаписываются без визуальной причины.
|
||||
|
||||
### Локально перед code review
|
||||
|
||||
```text
|
||||
npm run typecheck
|
||||
npm test
|
||||
npm run bundle:sync
|
||||
npm run bundle:budget
|
||||
npm run inventory
|
||||
node scripts/smoke-select.mjs --base origin/dev --head HEAD
|
||||
node demo/smoke_german_locale.mjs
|
||||
node scripts/check-docs.mjs
|
||||
git diff --check
|
||||
```
|
||||
|
||||
Backend HA harness и golden capture запускаются по селектору/CI на Linux; на
|
||||
Windows локальный Chromium smoke обязателен, если зависимости доступны.
|
||||
|
||||
## 12. Затронутые файлы и модули
|
||||
|
||||
Ожидаемый scope (точные имена helper-файлов может уточнить реализация):
|
||||
|
||||
- `src/i18n/de.json`, lazy module/controller под `src/i18n/`;
|
||||
- `src/i18n/registry.ts`, `src/i18n.ts`;
|
||||
- `src/houseplan-card.ts`, `src/space-card.ts`, `src/editor.ts`,
|
||||
`src/space-editor.ts` — shared readiness gate;
|
||||
- `custom_components/houseplan/translations/de.json`;
|
||||
- `scripts/bundle-manifest.mjs`, bundle/tree/backend route tests при необходимости;
|
||||
- `test/i18n.test.mjs`, locale loader и bundle tests;
|
||||
- targeted smoke/golden matrix;
|
||||
- `CONTRIBUTING.md`, `docs/USER-GUIDE.md`, `docs/USER-GUIDE.ru.md`,
|
||||
`docs/TESTING.md`, оба changelog и этот документ;
|
||||
- generated bundle tree и принятые Linux golden baselines.
|
||||
|
||||
Новых config keys и backend API нет.
|
||||
|
||||
## 13. Риски и меры
|
||||
|
||||
| Риск | Мера |
|
||||
| --- | --- |
|
||||
| Машинный/буквальный перевод меняет смысл опасного действия | glossary, ручной аудит confirmations/errors/security strings, semantic smoke |
|
||||
| German entry формально есть, часть UI падает в English | exact key parity + no-English allowlist + representative browser coverage |
|
||||
| Длинные строки ломают диалоги и kiosk | desktop/mobile metrics и golden |
|
||||
| Lazy locale даёт English flash | root render gate и delayed-load smoke |
|
||||
| Lazy locale зависает после 404/mixed deploy | fingerprint, bounded retry, fail-open English |
|
||||
| Несколько карточек скачивают chunk многократно | page cache и dedupe unit/network trace |
|
||||
| Locale chunk ошибочно считается editor-only | явная manifest role и AC7 |
|
||||
| Loader превышает initial budget даже без словаря | измерение build; только доказанная очистка мёртвых i18n keys, budget не повышается |
|
||||
| EN/RU стали async или изменили первый кадр | eager entries остаются; network/render regression |
|
||||
|
||||
## 14. Откат
|
||||
|
||||
Откат удаляет `de` entry/files и lazy locale runtime, возвращает registry/tests
|
||||
к eager `en`/`ru`. Persisted `language: de` безопасно становится неизвестным
|
||||
legacy value: selector показывает его временной option, runtime применяет HA
|
||||
locale → English fallback по контракту #62. Data migration и rollback backend
|
||||
config не нужны.
|
||||
|
||||
## 15. Release-артефакты
|
||||
|
||||
- `docs/CHANGELOG.md` и `docs/CHANGELOG.ru.md`: значимый bullet со ссылкой #348;
|
||||
- User Guide EN/RU: `language = auto|en|ru|de`, Auto locale matrix и короткое
|
||||
описание German lazy first load/fallback;
|
||||
- CONTRIBUTING: третий язык может быть lazy, registry entry определяет loader;
|
||||
- `docs/TESTING.md`: German semantic/layout/network checks;
|
||||
- generated multi-file bundle и release ZIP validation;
|
||||
- Linux-reviewed German desktop/mobile golden baselines;
|
||||
- security изменений нет; performance artifact — manifest с initial/lazy locale
|
||||
gzip и network trace.
|
||||
|
||||
## 16. План реализации
|
||||
|
||||
1. Расширить registry pure contract eager/lazy entries и написать loader tests.
|
||||
2. Добавить shared Lit readiness gate и race/failure tests четырёх root hosts.
|
||||
3. Добавить `de.json`, перевести/вычитать frontend и backend strings, включить
|
||||
glossary/no-fallback gates.
|
||||
4. Развести locale/editor/onboarding roles в bundle manifest и проверить
|
||||
package/backend/ZIP delivery.
|
||||
5. Добавить semantic/layout/network smoke и German golden scenarios.
|
||||
6. Обновить docs/changelogs, собрать и синхронизировать bundle.
|
||||
7. Прогнать gates, передать точный SHA на независимое code review.
|
||||
|
||||
@@ -82,6 +82,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным
|
||||
| [#299](https://github.com/Matysh/houseplan-card/issues/299) Записи толщины не пересекают границу роли стены | [299-mixed-role-wall-records.md](299-mixed-role-wall-records.md) |
|
||||
| [#314](https://github.com/Matysh/houseplan-card/issues/314) Атомарная запись v8 drafts и независимой геометрии | [314-v8-draft-write-regression.md](314-v8-draft-write-regression.md) |
|
||||
| [#306](https://github.com/Matysh/houseplan-card/issues/306) Нулевые стены вместо виртуальных границ | [306-zero-thickness-walls.md](306-zero-thickness-walls.md) |
|
||||
| [#348](https://github.com/Matysh/houseplan-card/issues/348) Полная немецкая локализация | [348-german-localization.md](348-german-localization.md) |
|
||||
|
||||
## P2
|
||||
|
||||
|
||||
Reference in New Issue
Block a user