From dcbaea2c087093890659f78a061dcc5a4bfe1e9d Mon Sep 17 00:00:00 2001 From: Matysh Date: Fri, 28 Aug 2026 10:18:09 +0300 Subject: [PATCH] docs: specify German localization Issue: #348 User-Visible: no --- docs/specs/348-german-localization.md | 385 ++++++++++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 386 insertions(+) create mode 100644 docs/specs/348-german-localization.md diff --git a/docs/specs/348-german-localization.md b/docs/specs/348-german-localization.md new file mode 100644 index 00000000..4ad973e2 --- /dev/null +++ b/docs/specs/348-german-localization.md @@ -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. + diff --git a/docs/specs/README.md b/docs/specs/README.md index ce6836a5..eb828ae4 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -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