mirror of
https://github.com/Matysh/houseplan-card
synced 2026-07-31 16:38:31 +00:00
feat v1.13.0: universality — floors-import wizard, editable icon rules, tap actions (phase 9)
- floors wizard: spaces from the HA floor registry, step-by-step with mandatory plan upload per floor, skip support, auto-markup after the last one - icon rules as data (settings.icon_rules) + in-card editor with live test, invalid-regex guard, bilingual defaults, device_class fallback chain - tap_action (card) + per-device override; security model: card-wide toggle only for light/switch/fan/humidifier, explicit override for covers, never lock/alarm; 600ms long-press always opens the info card - i18n dictionaries → src/i18n/*.json (+parity tests); light-theme badge fixes - frontend tests 15→28
This commit is contained in:
+9
-294
@@ -1,306 +1,21 @@
|
||||
/**
|
||||
* Card UI localization. Language is resolved from the card config
|
||||
* (`language: en|ru`) or, by default, from the HA user profile
|
||||
* (hass.locale.language); anything that is not Russian falls back to English.
|
||||
* Card UI localization. Dictionaries live in src/i18n/<lang>.json so that new
|
||||
* languages can be contributed without touching TypeScript. The language is
|
||||
* resolved from the card config (`language: en|ru`) or, by default, from the
|
||||
* HA user profile (hass.locale.language); anything that is not a known
|
||||
* language falls back to English.
|
||||
*/
|
||||
import en from './i18n/en.json';
|
||||
import ru from './i18n/ru.json';
|
||||
|
||||
export type Lang = 'en' | 'ru';
|
||||
|
||||
const en = {
|
||||
'card.title': 'House plan',
|
||||
'count.devices': '{n} dev.',
|
||||
|
||||
'empty.no_spaces': 'No spaces yet.',
|
||||
'empty.add_first': 'Add the first space and upload a floor plan.',
|
||||
'empty.install': 'Install the House Plan integration and add it in "Devices & services".',
|
||||
|
||||
'btn.add_space': 'Add space',
|
||||
'btn.cancel': 'Cancel',
|
||||
'btn.save': 'Save',
|
||||
'btn.close': 'Close',
|
||||
'btn.delete': 'Delete',
|
||||
'btn.remove': 'Remove',
|
||||
'btn.edit': 'Edit',
|
||||
'btn.open_in_ha': 'Open in HA',
|
||||
'btn.reset': 'Reset',
|
||||
'btn.attach': 'Attach…',
|
||||
'btn.upload': 'Upload…',
|
||||
'btn.replace': 'Replace…',
|
||||
'btn.no_area': 'No area',
|
||||
|
||||
'title.zoom_in': 'Zoom in',
|
||||
'title.zoom_out': 'Zoom out',
|
||||
'title.zoom_reset': 'Reset zoom',
|
||||
'title.add_device': 'Add a device to the plan',
|
||||
'title.show_all': 'Show all area devices (no curation)',
|
||||
'title.reset_layout': 'Reset icon positions to auto layout',
|
||||
'title.markup': 'Room markup: grid, lines, outlines',
|
||||
'title.configure_space': 'Configure space',
|
||||
'title.add_space': 'Add space',
|
||||
'title.markup_add': 'Add a room: connect grid dots with lines until the outline closes',
|
||||
'title.markup_erase': 'Erase a line: click the line',
|
||||
'title.markup_delroom': 'Delete a room: click inside the room',
|
||||
'title.no_area_room': 'Decorative room without an HA area (e.g. a hallway)',
|
||||
'title.choose_area': 'Select a Home Assistant area',
|
||||
'title.need_plan': 'Upload a floor-plan image',
|
||||
|
||||
'markup.add': 'Add',
|
||||
'markup.erase': 'Erase',
|
||||
'markup.delete': 'Delete',
|
||||
'markup.hint_points': 'points: {n} · Esc/Ctrl+Z — undo a dot · close the outline by clicking the first one',
|
||||
'markup.hint_start': 'click a grid dot to start the outline',
|
||||
|
||||
'tip.room': 'room — open the area',
|
||||
'tip.lqi': 'average zigbee signal:',
|
||||
|
||||
'info.device_header': 'Device on the plan',
|
||||
'info.model': 'Model',
|
||||
'info.state': 'State',
|
||||
'info.link': 'Link',
|
||||
'info.manuals': 'Manuals',
|
||||
'info.none': 'No additional information',
|
||||
|
||||
'marker.new_device': 'New device',
|
||||
'marker.name_label': 'Name (shown on the plan)',
|
||||
'marker.name_ph': 'Name',
|
||||
'marker.binding_label': 'Bind to an HA device',
|
||||
'marker.virtual_option': 'Virtual device (no binding)',
|
||||
'marker.search_ph': 'Search device / group…',
|
||||
'marker.nothing_found': 'nothing found',
|
||||
'marker.room_label': 'Room',
|
||||
'marker.room_override': ' (override placement)',
|
||||
'marker.room_choose': '— select a room —',
|
||||
'marker.room_auto': '— by device area (auto) —',
|
||||
'marker.icon_label': 'Icon',
|
||||
'marker.icon_ph': 'mdi:… (empty = auto)',
|
||||
'marker.model_label': 'Model',
|
||||
'marker.model_ph': 'e.g. Aqara T&H',
|
||||
'marker.link_label': 'Link',
|
||||
'marker.desc_label': 'Description',
|
||||
'marker.desc_ph': 'Notes, specs…',
|
||||
'marker.manuals_label': 'Manuals (PDF etc.)',
|
||||
'marker.sub_device': 'device',
|
||||
'marker.sub_z2m_group': ' · Z2M group',
|
||||
'marker.sub_group': 'group',
|
||||
'marker.sub_helper': 'helper',
|
||||
|
||||
'space.new': 'New space',
|
||||
'space.header': 'Space',
|
||||
'space.title_label': 'Title',
|
||||
'space.title_ph': 'e.g. Garage',
|
||||
'space.plan_label': 'Floor plan (background)',
|
||||
'space.no_plan': 'no plan image',
|
||||
'space.plan_alt': 'plan',
|
||||
|
||||
'room.new': 'New room',
|
||||
'room.name_label': 'Display name',
|
||||
'room.name_ph': 'e.g. Terrace',
|
||||
'room.area_label': 'Home Assistant area (unassigned)',
|
||||
'room.no_area_option': '— no area —',
|
||||
'room.default_name': 'Room',
|
||||
|
||||
'device.unnamed': 'unnamed',
|
||||
'device.light_group': 'light group',
|
||||
'device.fallback': 'device',
|
||||
'device.virtual': 'virtual device',
|
||||
|
||||
'confirm.reset_layout': 'Reset all icon positions to the auto layout?',
|
||||
'confirm.delete_room': 'Delete room "{name}"?',
|
||||
'confirm.remove_marker': 'Remove "{name}" from the plan?',
|
||||
'confirm.delete_space': 'Delete space "{title}" with all its rooms and markup?',
|
||||
|
||||
'toast.pos_save_failed': 'Failed to save position: {err}',
|
||||
'toast.no_entity': 'The device has no suitable entity',
|
||||
'toast.markup_needs_server': 'Markup is available after the config is moved to the server',
|
||||
'toast.conflict': 'Config was changed in another window — data refreshed, repeat your last action',
|
||||
'toast.cfg_save_failed': 'Failed to save config: {err}',
|
||||
'toast.room_saved': 'Room saved ({n}). Devices added: {added}. Outline the next one or exit markup.',
|
||||
'toast.room_saved_no_area': 'Room saved ({n}, no area). Outline the next one or exit markup.',
|
||||
'toast.marker_needs_server': 'Device editing is available after the config is moved to the server',
|
||||
'toast.virtual_name_required': 'Enter a name for the virtual device',
|
||||
'toast.marker_saved': 'Device saved',
|
||||
'toast.marker_removed': 'Device removed from the plan',
|
||||
'toast.integration_missing': 'The House Plan integration is not installed — management unavailable',
|
||||
'toast.plan_formats': 'Supported formats: SVG, PNG, JPG, WebP',
|
||||
'toast.plan_required': 'Upload a floor plan — it is required',
|
||||
'toast.space_added_onboard': 'Space added. Outline the rooms: click grid dots and close the contour.',
|
||||
'toast.space_added': 'Space added',
|
||||
'toast.space_saved': 'Space saved',
|
||||
'toast.space_deleted': 'Space deleted',
|
||||
'toast.delete_failed': 'Delete failed: {err}',
|
||||
'toast.error': 'Error: {err}',
|
||||
'toast.file_failed': 'File "{name}" was not uploaded: {err}',
|
||||
'toast.files_attached': 'Files attached: {n}',
|
||||
|
||||
'err.unknown': 'unknown error',
|
||||
'err.code': 'code {code}',
|
||||
'err.too_large': 'file larger than {mb} MB',
|
||||
'err.bad_ext': 'unsupported type (PDF/image expected)',
|
||||
'err.unauthorized': 'administrator rights required',
|
||||
|
||||
'editor.title': 'Title',
|
||||
'editor.default_floor': 'Default space',
|
||||
'editor.icon_size': 'Icon size, % of plan width',
|
||||
'editor.show_temperature': 'Show temperature',
|
||||
'editor.live_states': 'Live states (on/off, open…)',
|
||||
'editor.show_signal': 'Show zigbee signal (LQI)',
|
||||
'editor.language': 'Interface language',
|
||||
'editor.lang_auto': 'Auto (HA profile)',
|
||||
'editor.lang_en': 'English',
|
||||
'editor.lang_ru': 'Русский',
|
||||
};
|
||||
|
||||
type Key = keyof typeof en;
|
||||
|
||||
const ru: Record<Key, string> = {
|
||||
'card.title': 'План дома',
|
||||
'count.devices': '{n} устр.',
|
||||
|
||||
'empty.no_spaces': 'Пространств пока нет.',
|
||||
'empty.add_first': 'Добавьте первое пространство и загрузите план этажа.',
|
||||
'empty.install': 'Установите интеграцию House Plan и добавьте запись в «Устройства и службы».',
|
||||
|
||||
'btn.add_space': 'Добавить пространство',
|
||||
'btn.cancel': 'Отмена',
|
||||
'btn.save': 'Сохранить',
|
||||
'btn.close': 'Закрыть',
|
||||
'btn.delete': 'Удалить',
|
||||
'btn.remove': 'Убрать',
|
||||
'btn.edit': 'Редактировать',
|
||||
'btn.open_in_ha': 'Открыть в HA',
|
||||
'btn.reset': 'Сброс',
|
||||
'btn.attach': 'Прикрепить…',
|
||||
'btn.upload': 'Загрузить…',
|
||||
'btn.replace': 'Заменить…',
|
||||
'btn.no_area': 'Без зоны',
|
||||
|
||||
'title.zoom_in': 'Приблизить',
|
||||
'title.zoom_out': 'Отдалить',
|
||||
'title.zoom_reset': 'Сбросить масштаб',
|
||||
'title.add_device': 'Добавить устройство на план',
|
||||
'title.show_all': 'Показывать все устройства зоны (без курирования)',
|
||||
'title.reset_layout': 'Сбросить позиции значков к авто-раскладке',
|
||||
'title.markup': 'Разметка комнат: сетка, линии, контуры',
|
||||
'title.configure_space': 'Настроить пространство',
|
||||
'title.add_space': 'Добавить пространство',
|
||||
'title.markup_add': 'Добавить комнату: соединяйте точки сетки линиями до замкнутого контура',
|
||||
'title.markup_erase': 'Стереть линию: клик по линии',
|
||||
'title.markup_delroom': 'Удалить комнату: клик внутри комнаты',
|
||||
'title.no_area_room': 'Декоративная комната без привязки к зоне (например, холл)',
|
||||
'title.choose_area': 'Выберите зону Home Assistant',
|
||||
'title.need_plan': 'Загрузите подложку (план этажа)',
|
||||
|
||||
'markup.add': 'Добавить',
|
||||
'markup.erase': 'Стереть',
|
||||
'markup.delete': 'Удалить',
|
||||
'markup.hint_points': 'точек: {n} · Esc/Ctrl+Z — убрать точку · замкните контур кликом по первой',
|
||||
'markup.hint_start': 'кликните точку сетки, чтобы начать контур',
|
||||
|
||||
'tip.room': 'комната — открыть зону',
|
||||
'tip.lqi': 'средний сигнал zigbee:',
|
||||
|
||||
'info.device_header': 'Устройство на плане',
|
||||
'info.model': 'Модель',
|
||||
'info.state': 'Состояние',
|
||||
'info.link': 'Ссылка',
|
||||
'info.manuals': 'Инструкции',
|
||||
'info.none': 'Нет дополнительной информации',
|
||||
|
||||
'marker.new_device': 'Новое устройство',
|
||||
'marker.name_label': 'Имя (отображается на плане)',
|
||||
'marker.name_ph': 'Название',
|
||||
'marker.binding_label': 'Привязка к устройству HA',
|
||||
'marker.virtual_option': 'Виртуальное устройство (без привязки)',
|
||||
'marker.search_ph': 'Поиск устройства / группы…',
|
||||
'marker.nothing_found': 'ничего не найдено',
|
||||
'marker.room_label': 'Комната',
|
||||
'marker.room_override': ' (переопределить размещение)',
|
||||
'marker.room_choose': '— выберите комнату —',
|
||||
'marker.room_auto': '— по зоне устройства (авто) —',
|
||||
'marker.icon_label': 'Иконка',
|
||||
'marker.icon_ph': 'mdi:… (пусто = авто)',
|
||||
'marker.model_label': 'Модель',
|
||||
'marker.model_ph': 'напр. Aqara T&H',
|
||||
'marker.link_label': 'Ссылка',
|
||||
'marker.desc_label': 'Описание',
|
||||
'marker.desc_ph': 'Заметки, характеристики…',
|
||||
'marker.manuals_label': 'Инструкции (PDF и т.п.)',
|
||||
'marker.sub_device': 'устройство',
|
||||
'marker.sub_z2m_group': ' · Z2M-группа',
|
||||
'marker.sub_group': 'группа',
|
||||
'marker.sub_helper': 'хелпер',
|
||||
|
||||
'space.new': 'Новое пространство',
|
||||
'space.header': 'Пространство',
|
||||
'space.title_label': 'Название',
|
||||
'space.title_ph': 'Например: Гараж',
|
||||
'space.plan_label': 'Подложка (план)',
|
||||
'space.no_plan': 'нет подложки',
|
||||
'space.plan_alt': 'план',
|
||||
|
||||
'room.new': 'Новая комната',
|
||||
'room.name_label': 'Отображаемое имя',
|
||||
'room.name_ph': 'Например: Терраса',
|
||||
'room.area_label': 'Зона Home Assistant (свободные)',
|
||||
'room.no_area_option': '— без зоны —',
|
||||
'room.default_name': 'Комната',
|
||||
|
||||
'device.unnamed': 'без имени',
|
||||
'device.light_group': 'группа света',
|
||||
'device.fallback': 'устройство',
|
||||
'device.virtual': 'виртуальное устройство',
|
||||
|
||||
'confirm.reset_layout': 'Сбросить позиции всех иконок к авто-раскладке?',
|
||||
'confirm.delete_room': 'Удалить комнату «{name}»?',
|
||||
'confirm.remove_marker': 'Убрать «{name}» с плана?',
|
||||
'confirm.delete_space': 'Удалить пространство «{title}» со всеми комнатами и разметкой?',
|
||||
|
||||
'toast.pos_save_failed': 'Не удалось сохранить позицию: {err}',
|
||||
'toast.no_entity': 'У устройства нет подходящей сущности',
|
||||
'toast.markup_needs_server': 'Разметка доступна после переноса конфига на сервер',
|
||||
'toast.conflict': 'Конфиг изменён в другом окне — данные обновлены, повторите последнее действие',
|
||||
'toast.cfg_save_failed': 'Не удалось сохранить конфиг: {err}',
|
||||
'toast.room_saved': 'Комната сохранена ({n}). Устройств добавлено: {added}. Обведите следующую или выйдите из разметки.',
|
||||
'toast.room_saved_no_area': 'Комната сохранена ({n}, без зоны). Обведите следующую или выйдите из разметки.',
|
||||
'toast.marker_needs_server': 'Редактирование устройств доступно после переноса конфига на сервер',
|
||||
'toast.virtual_name_required': 'Укажите имя виртуального устройства',
|
||||
'toast.marker_saved': 'Устройство сохранено',
|
||||
'toast.marker_removed': 'Устройство убрано с плана',
|
||||
'toast.integration_missing': 'Интеграция House Plan не установлена — управление недоступно',
|
||||
'toast.plan_formats': 'Поддерживаются SVG, PNG, JPG, WebP',
|
||||
'toast.plan_required': 'Загрузите подложку — план этажа обязателен',
|
||||
'toast.space_added_onboard': 'Пространство добавлено. Обведите комнаты: кликайте по точкам сетки и замкните контур.',
|
||||
'toast.space_added': 'Пространство добавлено',
|
||||
'toast.space_saved': 'Пространство сохранено',
|
||||
'toast.space_deleted': 'Пространство удалено',
|
||||
'toast.delete_failed': 'Ошибка удаления: {err}',
|
||||
'toast.error': 'Ошибка: {err}',
|
||||
'toast.file_failed': 'Файл «{name}» не загружен: {err}',
|
||||
'toast.files_attached': 'Прикреплено файлов: {n}',
|
||||
|
||||
'err.unknown': 'неизвестная ошибка',
|
||||
'err.code': 'код {code}',
|
||||
'err.too_large': 'файл больше {mb} МБ',
|
||||
'err.bad_ext': 'недопустимый тип (нужен PDF/изображение)',
|
||||
'err.unauthorized': 'нужны права администратора',
|
||||
|
||||
'editor.title': 'Заголовок',
|
||||
'editor.default_floor': 'Пространство по умолчанию',
|
||||
'editor.icon_size': 'Размер иконок, % ширины плана',
|
||||
'editor.show_temperature': 'Показывать температуру',
|
||||
'editor.live_states': 'Живые состояния (вкл/выкл, открыто…)',
|
||||
'editor.show_signal': 'Показывать сигнал zigbee (LQI)',
|
||||
'editor.language': 'Язык интерфейса',
|
||||
'editor.lang_auto': 'Авто (профиль HA)',
|
||||
'editor.lang_en': 'English',
|
||||
'editor.lang_ru': 'Русский',
|
||||
};
|
||||
|
||||
const DICTS: Record<Lang, Record<Key, string>> = { en, ru };
|
||||
const DICTS: Record<Lang, Record<string, string>> = { en, ru };
|
||||
|
||||
/** Resolve the UI language: explicit config option wins, then the HA profile. */
|
||||
export function langOf(hass: any, configLang?: string | null): Lang {
|
||||
if (configLang === 'ru' || configLang === 'en') return configLang;
|
||||
if (configLang && configLang in DICTS) return configLang as Lang;
|
||||
const l = (hass?.locale?.language || hass?.language || 'en').toLowerCase();
|
||||
return l.startsWith('ru') ? 'ru' : 'en';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user