diff --git a/.HA_VERSION b/.HA_VERSION index 3d04c1f..6784e1e 100644 --- a/.HA_VERSION +++ b/.HA_VERSION @@ -1 +1 @@ -2026.6.2 \ No newline at end of file +2026.6.3 \ No newline at end of file diff --git a/.cache/brands/integrations/androidtv_remote/logo.png b/.cache/brands/integrations/androidtv_remote/logo.png new file mode 100644 index 0000000..ee35581 Binary files /dev/null and b/.cache/brands/integrations/androidtv_remote/logo.png differ diff --git a/.cache/brands/integrations/ha_washdata/logo.png b/.cache/brands/integrations/ha_washdata/logo.png new file mode 100644 index 0000000..4868f9b Binary files /dev/null and b/.cache/brands/integrations/ha_washdata/logo.png differ diff --git a/.cache/brands/integrations/mobile_app/dark_logo.png b/.cache/brands/integrations/mobile_app/dark_logo.png new file mode 100644 index 0000000..b7f2991 Binary files /dev/null and b/.cache/brands/integrations/mobile_app/dark_logo.png differ diff --git a/.cache/brands/integrations/ollama/dark_logo.png b/.cache/brands/integrations/ollama/dark_logo.png new file mode 100644 index 0000000..b82e7a0 Binary files /dev/null and b/.cache/brands/integrations/ollama/dark_logo.png differ diff --git a/.cache/brands/integrations/ollama/logo.png b/.cache/brands/integrations/ollama/logo.png new file mode 100644 index 0000000..cf6813e Binary files /dev/null and b/.cache/brands/integrations/ollama/logo.png differ diff --git a/.ha_run.lock b/.ha_run.lock index 00f9d2e..3887e9f 100644 --- a/.ha_run.lock +++ b/.ha_run.lock @@ -1 +1 @@ -{"pid": 71, "version": 1, "ha_version": "2026.6.2", "start_ts": 1781274194.4665778} \ No newline at end of file +{"pid": 71, "version": 1, "ha_version": "2026.6.3", "start_ts": 1781389664.1672328} \ No newline at end of file diff --git a/custom_components/ha_washdata/__init__.py b/custom_components/ha_washdata/__init__.py new file mode 100644 index 0000000..d1a82ba --- /dev/null +++ b/custom_components/ha_washdata/__init__.py @@ -0,0 +1,838 @@ +"""The WashData integration.""" + +from __future__ import annotations + +import json +import logging +from pathlib import Path +from typing import Any + +from homeassistant.components import persistent_notification +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import Platform +from homeassistant.core import HomeAssistant, ServiceCall +from homeassistant.exceptions import ServiceValidationError +from homeassistant.helpers import device_registry as dr + +from .const import ( + DOMAIN, + SERVICE_SUBMIT_FEEDBACK, + CONF_LINKED_DEVICE, + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_DEVICE_TYPE, + CONF_POWER_SENSOR, + CONF_NOTIFY_SERVICE, + CONF_NOTIFY_EVENTS, + NOTIFY_EVENT_LIVE, + CONF_NOTIFY_START_SERVICES, + CONF_NOTIFY_FINISH_SERVICES, + CONF_NOTIFY_LIVE_SERVICES, + CONF_NOTIFY_ACTIONS, + CONF_NOTIFY_PEOPLE, + CONF_NOTIFY_ONLY_WHEN_HOME, + CONF_NOTIFY_FIRE_EVENTS, + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + CONF_NOTIFY_TIMEOUT_SECONDS, + CONF_NOTIFY_CHANNEL, + CONF_NOTIFY_FINISH_CHANNEL, + CONF_NOTIFY_REMINDER_MESSAGE, + DEFAULT_NOTIFY_ONLY_WHEN_HOME, + DEFAULT_NOTIFY_FIRE_EVENTS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_TIMEOUT_SECONDS, + DEFAULT_NOTIFY_CHANNEL, + DEFAULT_NOTIFY_FINISH_CHANNEL, + DEFAULT_NOTIFY_REMINDER_MESSAGE, + CONF_PROGRESS_RESET_DELAY, + CONF_LEARNING_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_AUTO_LABEL_CONFIDENCE, + DEFAULT_PROGRESS_RESET_DELAY, + DEFAULT_LEARNING_CONFIDENCE, + DEFAULT_DURATION_TOLERANCE, + DEFAULT_AUTO_LABEL_CONFIDENCE, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SMOOTHING_WINDOW, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_INTERRUPTED_MIN_SECONDS, + CONF_ABRUPT_DROP_WATTS, + CONF_ABRUPT_DROP_RATIO, + CONF_ABRUPT_HIGH_LOAD_FACTOR, + DEFAULT_SMOOTHING_WINDOW, + DEFAULT_PROFILE_DURATION_TOLERANCE, + DEFAULT_INTERRUPTED_MIN_SECONDS, + DEFAULT_ABRUPT_DROP_WATTS, + DEFAULT_ABRUPT_DROP_RATIO, + DEFAULT_ABRUPT_HIGH_LOAD_FACTOR, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_MAX_PAST_CYCLES, + CONF_MAX_FULL_TRACES_PER_PROFILE, + CONF_MAX_FULL_TRACES_UNLABELED, + CONF_WATCHDOG_INTERVAL, + CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + CONF_COMPLETION_MIN_SECONDS, + CONF_NOTIFY_BEFORE_END_MINUTES, + DEFAULT_PROFILE_MATCH_INTERVAL, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO, + DEFAULT_MAX_PAST_CYCLES, + DEFAULT_MAX_FULL_TRACES_PER_PROFILE, + DEFAULT_MAX_FULL_TRACES_UNLABELED, + DEFAULT_WATCHDOG_INTERVAL, + DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + DEFAULT_COMPLETION_MIN_SECONDS, + DEFAULT_NOTIFY_BEFORE_END_MINUTES, + DEFAULT_DEVICE_TYPE, + DEFAULT_START_DURATION_THRESHOLD, + CONF_START_DURATION_THRESHOLD, +) +from .log_utils import DeviceLoggerAdapter + +_LOGGER = logging.getLogger(__name__) + +PLATFORMS: list[Platform] = [ + Platform.SENSOR, + Platform.BINARY_SENSOR, + Platform.SELECT, + Platform.BUTTON, +] + + +def _require_str(value: Any, name: str) -> str: + if not isinstance(value, str) or not value: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key=f"{name}_required", + ) + return value + + +async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Migrate config entry to the latest version while preserving settings.""" + _log = DeviceLoggerAdapter(_LOGGER, entry.title) + version = entry.version or 1 + minor_version = entry.minor_version or 1 + + if version > 3: + _log.error( + "Refusing to migrate unsupported future schema %s.%s", version, minor_version + ) + return False + + if version == 3 and minor_version >= 5: + return True + + data: dict[str, Any] = dict(entry.data) + options: dict[str, Any] = dict(entry.options) + + # Preserve core settings from data into options if missing + if CONF_MIN_POWER not in options and CONF_MIN_POWER in data: + options[CONF_MIN_POWER] = data[CONF_MIN_POWER] + if CONF_OFF_DELAY not in options and CONF_OFF_DELAY in data: + options[CONF_OFF_DELAY] = data[CONF_OFF_DELAY] + if CONF_DEVICE_TYPE not in options and CONF_DEVICE_TYPE in data: + options[CONF_DEVICE_TYPE] = data[CONF_DEVICE_TYPE] + if CONF_POWER_SENSOR not in options and CONF_POWER_SENSOR in data: + options[CONF_POWER_SENSOR] = data[CONF_POWER_SENSOR] + if CONF_NOTIFY_SERVICE not in options and CONF_NOTIFY_SERVICE in data: + options[CONF_NOTIFY_SERVICE] = data[CONF_NOTIFY_SERVICE] + + # Migrate legacy single CONF_NOTIFY_SERVICE into per-event service lists. + # Users who configured a notify service before 0.3.x would otherwise lose + # their notification settings entirely on upgrade. + legacy_svc = options.get(CONF_NOTIFY_SERVICE) or data.get(CONF_NOTIFY_SERVICE) + if legacy_svc and isinstance(legacy_svc, str): + # CONF_NOTIFY_EVENTS is a deprecated list of enabled event types. + # Only migrate live services when live events were explicitly opted in. + legacy_events = options.get(CONF_NOTIFY_EVENTS) or data.get(CONF_NOTIFY_EVENTS) or [] + if CONF_NOTIFY_START_SERVICES not in options: + options[CONF_NOTIFY_START_SERVICES] = [legacy_svc] + if CONF_NOTIFY_FINISH_SERVICES not in options: + options[CONF_NOTIFY_FINISH_SERVICES] = [legacy_svc] + if CONF_NOTIFY_LIVE_SERVICES not in options and NOTIFY_EVENT_LIVE in legacy_events: + options[CONF_NOTIFY_LIVE_SERVICES] = [legacy_svc] + + options.setdefault(CONF_PROGRESS_RESET_DELAY, DEFAULT_PROGRESS_RESET_DELAY) + options.setdefault(CONF_LEARNING_CONFIDENCE, DEFAULT_LEARNING_CONFIDENCE) + options.setdefault(CONF_DURATION_TOLERANCE, DEFAULT_DURATION_TOLERANCE) + options.setdefault(CONF_AUTO_LABEL_CONFIDENCE, DEFAULT_AUTO_LABEL_CONFIDENCE) + options.setdefault(CONF_NO_UPDATE_ACTIVE_TIMEOUT, DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT) + options.setdefault(CONF_SMOOTHING_WINDOW, DEFAULT_SMOOTHING_WINDOW) + options.setdefault( + CONF_PROFILE_DURATION_TOLERANCE, DEFAULT_PROFILE_DURATION_TOLERANCE + ) + options.setdefault(CONF_INTERRUPTED_MIN_SECONDS, DEFAULT_INTERRUPTED_MIN_SECONDS) + options.setdefault(CONF_ABRUPT_DROP_WATTS, DEFAULT_ABRUPT_DROP_WATTS) + options.setdefault(CONF_ABRUPT_DROP_RATIO, DEFAULT_ABRUPT_DROP_RATIO) + options.setdefault(CONF_ABRUPT_HIGH_LOAD_FACTOR, DEFAULT_ABRUPT_HIGH_LOAD_FACTOR) + + options.setdefault( + CONF_DEVICE_TYPE, data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE) + ) + options.setdefault(CONF_START_DURATION_THRESHOLD, DEFAULT_START_DURATION_THRESHOLD) + + options.setdefault(CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL) + options.setdefault( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO + ) + options.setdefault( + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO + ) + options.setdefault(CONF_MAX_PAST_CYCLES, DEFAULT_MAX_PAST_CYCLES) + options.setdefault( + CONF_MAX_FULL_TRACES_PER_PROFILE, DEFAULT_MAX_FULL_TRACES_PER_PROFILE + ) + options.setdefault( + CONF_MAX_FULL_TRACES_UNLABELED, DEFAULT_MAX_FULL_TRACES_UNLABELED + ) + options.setdefault(CONF_WATCHDOG_INTERVAL, DEFAULT_WATCHDOG_INTERVAL) + options.setdefault( + CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD + ) + options.setdefault(CONF_COMPLETION_MIN_SECONDS, DEFAULT_COMPLETION_MIN_SECONDS) + options.setdefault( + CONF_NOTIFY_BEFORE_END_MINUTES, DEFAULT_NOTIFY_BEFORE_END_MINUTES + ) + + # Normalize notification options (added in 0.3.2) + options.setdefault(CONF_NOTIFY_ACTIONS, []) + options.setdefault(CONF_NOTIFY_PEOPLE, []) + options.setdefault(CONF_NOTIFY_ONLY_WHEN_HOME, DEFAULT_NOTIFY_ONLY_WHEN_HOME) + options.setdefault(CONF_NOTIFY_FIRE_EVENTS, DEFAULT_NOTIFY_FIRE_EVENTS) + options.setdefault( + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS + ) + options.setdefault( + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT + ) + + # 3.5: notification delivery overhaul (lifecycle tag, timeout, per-type channels, + # distinct reminder message). + options.setdefault(CONF_NOTIFY_TIMEOUT_SECONDS, DEFAULT_NOTIFY_TIMEOUT_SECONDS) + options.setdefault(CONF_NOTIFY_CHANNEL, DEFAULT_NOTIFY_CHANNEL) + options.setdefault(CONF_NOTIFY_FINISH_CHANNEL, DEFAULT_NOTIFY_FINISH_CHANNEL) + options.setdefault(CONF_NOTIFY_REMINDER_MESSAGE, DEFAULT_NOTIFY_REMINDER_MESSAGE) + + keys_to_remove = [ + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_DEVICE_TYPE, + CONF_POWER_SENSOR, + CONF_NOTIFY_SERVICE, + ] + for k in keys_to_remove: + data.pop(k, None) + + # 3.4: drain-spike delayed-start model replaced by band-based DELAY_WAIT. + # Strip the obsolete drain knobs so they don't linger in options and + # confuse anyone inspecting entry.options. + for k in ( + "delay_drain_min_power", + "delay_drain_max_power", + "delay_drain_max_duration", + ): + options.pop(k, None) + + hass.config_entries.async_update_entry( + entry, + data=data, + options=options, + version=3, + minor_version=5, + ) + _log.info( + "Migrated WashData entry from version %s.%s to 3.5", version, minor_version + ) + return True + + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up WashData from a config entry.""" + _log = DeviceLoggerAdapter(_LOGGER, entry.title) + # Guard against duplicate setup during hot-reload + if entry.entry_id in hass.data.get(DOMAIN, {}): + _log.warning( + "Entry %s already set up, skipping duplicate setup", entry.entry_id + ) + return True + + hass.data.setdefault(DOMAIN, {}) + + # Migration: Remove old auto_maintenance switch entity (now in settings) + # pylint: disable=import-outside-toplevel + from homeassistant.helpers import entity_registry as er + + ent_reg = er.async_get(hass) + old_switch_id = f"{entry.entry_id}_auto_maintenance" + old_entity = ent_reg.async_get_entity_id("switch", DOMAIN, old_switch_id) + if old_entity: + _log.info( + "Removing deprecated auto_maintenance switch entity: %s", old_entity + ) + ent_reg.async_remove(old_entity) + + # pylint: disable=import-outside-toplevel + from .manager import WashDataManager + + manager = WashDataManager(hass, entry) + hass.data[DOMAIN][entry.entry_id] = manager + + await manager.async_setup() + + # Check for initial profile from onboarding + if "initial_profile" in entry.data: + init_prof = entry.data["initial_profile"] + name = init_prof.get("name") + duration = init_prof.get("avg_duration") + if name: + try: + # Create the profile immediately + await manager.profile_store.create_profile_standalone( + name, avg_duration=duration + ) + manager._logger.info("Created initial profile '%s' from onboarding", name) + + # Clean up config entry (remove initial_profile to avoid re-creation or cruft) + new_data = { + k: v for k, v in entry.data.items() if k != "initial_profile" + } + hass.config_entries.async_update_entry(entry, data=new_data) + + except Exception as e: # pylint: disable=broad-exception-caught + manager._logger.error("Failed to create initial profile: %s", e) + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + _apply_device_link(hass, entry) + + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + + # Register service if not already + if not hass.services.has_service(DOMAIN, "label_cycle"): + + async def handle_label_cycle(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + cycle_id = _require_str(call.data.get("cycle_id"), "cycle_id") + profile_name = call.data.get("profile_name", "").strip() + + # Find the config entry for this device + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + + # Assign existing profile or remove label + if profile_name: + await manager.profile_store.assign_profile_to_cycle( + cycle_id, profile_name + ) + else: + await manager.profile_store.assign_profile_to_cycle(cycle_id, None) + + manager.notify_update() + + hass.services.async_register(DOMAIN, "label_cycle", handle_label_cycle) + + # Register create_profile service + if not hass.services.has_service(DOMAIN, "create_profile"): + + async def handle_create_profile(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + profile_name = _require_str(call.data.get("profile_name"), "profile_name") + reference_cycle_id = call.data.get("reference_cycle_id") + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + await manager.profile_store.create_profile_standalone( + profile_name, reference_cycle_id + ) + manager.notify_update() + + hass.services.async_register(DOMAIN, "create_profile", handle_create_profile) + + # Register delete_profile service + if not hass.services.has_service(DOMAIN, "delete_profile"): + + async def handle_delete_profile(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + profile_name = _require_str(call.data.get("profile_name"), "profile_name") + unlabel_cycles = call.data.get("unlabel_cycles", True) + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + await manager.profile_store.delete_profile(profile_name, unlabel_cycles) + manager.notify_update() + + hass.services.async_register(DOMAIN, "delete_profile", handle_delete_profile) + + # Register auto_label_cycles service + if not hass.services.has_service(DOMAIN, "auto_label_cycles"): + + async def handle_auto_label_cycles(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + confidence_threshold = call.data.get("confidence_threshold", 0.75) + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + stats = await manager.profile_store.auto_label_cycles( + confidence_threshold + ) + manager.notify_update() + + manager._logger.info( + "Auto-label complete: %s labeled, %s skipped", + stats["labeled"], + stats["skipped"], + ) + + hass.services.async_register( + DOMAIN, "auto_label_cycles", handle_auto_label_cycles + ) + + # Register trim_cycle service + if not hass.services.has_service(DOMAIN, "trim_cycle"): + + async def handle_trim_cycle(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + cycle_id = _require_str(call.data.get("cycle_id"), "cycle_id") + trim_start_s = max(0.0, float(call.data.get("trim_start_s", 0))) + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="device_not_found", + ) + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_config_entry", + ) + if entry_id not in hass.data[DOMAIN]: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_loaded", + ) + + manager = hass.data[DOMAIN][entry_id] + store = manager.profile_store + + # Determine trim end - default to full cycle duration if not supplied + raw_end = call.data.get("trim_end_s") + if raw_end is not None: + trim_end_s = max(0.0, float(raw_end)) + else: + p_data = store.get_cycle_power_data(cycle_id) + if not p_data: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="cycle_not_found_or_no_power", + ) + trim_end_s = max(point[0] for point in p_data) + + if trim_end_s <= trim_start_s: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="trim_invalid_range", + ) + + ok = await store.trim_cycle_power_data(cycle_id, trim_start_s, trim_end_s) + if not ok: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="trim_failed_empty_window", + ) + manager.notify_update() + + hass.services.async_register(DOMAIN, "trim_cycle", handle_trim_cycle) + + # Register custom card via frontend.py - once per HA instance only. + if not hass.data.get("ha_washdata_card_registered") and not hass.data.get( + "ha_washdata_card_deferred" + ) and not hass.data.get("ha_washdata_card_registering"): + # pylint: disable=import-outside-toplevel + from .frontend import ( + CARD_REGISTERED, + CARD_DEFERRED, + WashDataCardRegistration, + ) + + card_reg = WashDataCardRegistration(hass) + hass.data["ha_washdata_card_registering"] = True + try: + register_result = await card_reg.async_register() + except Exception as err: # pylint: disable=broad-exception-caught + hass.data["ha_washdata_card_registering"] = False + _log.warning("Card registration failed, will retry on next setup: %s", err) + else: + hass.data["ha_washdata_card_registering"] = False + if register_result == CARD_REGISTERED: + hass.data["ha_washdata_card_deferred"] = False + hass.data["ha_washdata_card_registered"] = True + elif register_result == CARD_DEFERRED: + hass.data["ha_washdata_card_deferred"] = True + hass.data["ha_washdata_card_registered"] = False + else: + hass.data["ha_washdata_card_deferred"] = False + hass.data["ha_washdata_card_registered"] = False + _log.warning("Card registration failed and was not deferred") + + # Register feedback service + if not hass.services.has_service( + DOMAIN, SERVICE_SUBMIT_FEEDBACK.rsplit(".", maxsplit=1)[-1] + ): + + async def handle_submit_feedback(call: ServiceCall) -> None: + entry_id_raw = call.data.get("entry_id") + device_id_raw = call.data.get("device_id") + + entry_id: str | None = ( + entry_id_raw if isinstance(entry_id_raw, str) and entry_id_raw else None + ) + if entry_id is None: + # Prefer device_id for user-facing workflows. + device_id = _require_str(device_id_raw, "device_id") + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + + if not entry_id: + raise ValueError("entry_id or device_id is required") + + cycle_id = _require_str(call.data.get("cycle_id"), "cycle_id") + user_confirmed = call.data.get("user_confirmed", False) + corrected_profile = call.data.get("corrected_profile") + corrected_duration = call.data.get("corrected_duration") # in seconds + notes = call.data.get("notes", "") + dismiss = call.data.get("dismiss", False) + + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this entry") + + manager = hass.data[DOMAIN][entry_id] + success = await manager.learning_manager.async_submit_cycle_feedback( + cycle_id=cycle_id, + user_confirmed=user_confirmed, + corrected_profile=corrected_profile, + corrected_duration=corrected_duration, + notes=notes, + dismiss=dismiss, + ) + manager.notify_update() + + if success: + # Best-effort dismiss the feedback notification if it exists. + try: + notification_id = f"ha_washdata_feedback_{entry_id}_{cycle_id}" + persistent_notification.async_dismiss(hass, notification_id) + except Exception: # pylint: disable=broad-exception-caught + pass + + manager._logger.info("Cycle feedback submitted for %s", cycle_id) + else: + manager._logger.warning("Failed to submit feedback for cycle %s", cycle_id) + + hass.services.async_register( + DOMAIN, + SERVICE_SUBMIT_FEEDBACK.rsplit(".", maxsplit=1)[-1], + handle_submit_feedback, + ) + + # Export store to file (per entry/device) + if not hass.services.has_service(DOMAIN, "export_config"): + + async def handle_export_config(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + file_path = call.data.get("path") + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + entry = hass.config_entries.async_get_entry(entry_id) + if entry is None: + raise ValueError(f"Config entry not found: {entry_id}") + payload = manager.profile_store.export_data( + entry_data=dict(entry.data), + entry_options=dict(entry.options), + ) + + target = ( + Path(file_path) + if file_path + else Path(hass.config.path(f"ha_washdata_export_{entry_id}.json")) + ) + target = target.resolve() + + # Write export + target.write_text(json.dumps(payload, indent=2), encoding="utf-8") + manager._logger.info("Exported ha_washdata entry %s to %s", entry_id, target) + + hass.services.async_register(DOMAIN, "export_config", handle_export_config) + + # Import store from file into the target entry/device + if not hass.services.has_service(DOMAIN, "import_config"): + + async def handle_import_config(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + file_path = call.data.get("path") + + if not file_path: + raise ValueError("path is required for import") + + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + + entry_id = next(iter(device.config_entries), None) + if not entry_id: + raise ValueError("No config entry found for device") + if entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded for this device") + + manager = hass.data[DOMAIN][entry_id] + entry = hass.config_entries.async_get_entry(entry_id) + if entry is None: + raise ValueError(f"Config entry not found: {entry_id}") + + source = Path(file_path).resolve() + if not source.exists(): + raise ValueError(f"File not found: {source}") + + try: + payload = json.loads(source.read_text(encoding="utf-8")) + except Exception as err: # noqa: BLE001 + raise ValueError(f"Failed to read import file: {err}") from err + + config_updates = await manager.profile_store.async_import_data(payload) + + # Apply imported settings to config entry if present + entry_data = config_updates.get("entry_data", {}) + entry_options = config_updates.get("entry_options", {}) + + if entry_data or entry_options: + new_data: dict[str, Any] = dict(entry.data) + new_options: dict[str, Any] = dict(entry.options) + + # Only update min_power/off_delay from data (don't overwrite power_sensor/name) + for key in [CONF_MIN_POWER, CONF_OFF_DELAY]: + if key in entry_data: + new_data[key] = entry_data[key] + + # Update all options from import + new_options.update(entry_options) + + hass.config_entries.async_update_entry( + entry, + data=new_data, + options=new_options, + ) + manager._logger.info("Applied imported settings to config entry %s", entry_id) + + manager._logger.info("Imported ha_washdata entry %s from %s", entry_id, source) + + hass.services.async_register(DOMAIN, "import_config", handle_import_config) + + # Register recorder services + if not hass.services.has_service(DOMAIN, "record_start"): + async def handle_record_start(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + entry_id = next(iter(device.config_entries), None) + if not entry_id or entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded") + + manager = hass.data[DOMAIN][entry_id] + await manager.async_start_recording() + + hass.services.async_register(DOMAIN, "record_start", handle_record_start) + + if not hass.services.has_service(DOMAIN, "record_stop"): + async def handle_record_stop(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ValueError("Device not found") + entry_id = next(iter(device.config_entries), None) + if not entry_id or entry_id not in hass.data[DOMAIN]: + raise ValueError("Integration not loaded") + + manager = hass.data[DOMAIN][entry_id] + await manager.async_stop_recording() + + hass.services.async_register(DOMAIN, "record_stop", handle_record_stop) + + # Register pause/resume services + if not hass.services.has_service(DOMAIN, "pause_cycle"): + async def handle_pause_cycle(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="device_not_found", + ) + entry_id = next( + (eid for eid in device.config_entries if eid in hass.data.get(DOMAIN, {})), + None, + ) + if not entry_id: + if any(eid for eid in device.config_entries): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_loaded", + ) + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_config_entry", + ) + + manager = hass.data[DOMAIN][entry_id] + await manager.async_pause_cycle() + + hass.services.async_register(DOMAIN, "pause_cycle", handle_pause_cycle) + + if not hass.services.has_service(DOMAIN, "resume_cycle"): + async def handle_resume_cycle(call: ServiceCall) -> None: + device_id = _require_str(call.data.get("device_id"), "device_id") + registry = dr.async_get(hass) + device = registry.async_get(device_id) + if not device: + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="device_not_found", + ) + entry_id = next( + (eid for eid in device.config_entries if eid in hass.data.get(DOMAIN, {})), + None, + ) + if not entry_id: + if any(eid for eid in device.config_entries): + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="integration_not_loaded", + ) + raise ServiceValidationError( + translation_domain=DOMAIN, + translation_key="no_config_entry", + ) + + manager = hass.data[DOMAIN][entry_id] + await manager.async_resume_cycle() + + hass.services.async_register(DOMAIN, "resume_cycle", handle_resume_cycle) + + return True + + +def _apply_device_link(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Sync the WashData device's via_device link with the configured option. + + When CONF_LINKED_DEVICE points at an existing device (e.g. the smart plug or + appliance), the WashData device is shown as "Connected via " in the + HA device registry. Clearing the option removes the link. Stale targets that + no longer exist are treated as "no link" so the registry never references a + deleted device. + """ + registry = dr.async_get(hass) + washdata_device = registry.async_get_device(identifiers={(DOMAIN, entry.entry_id)}) + if washdata_device is None: + return + + linked_device_id = entry.options.get(CONF_LINKED_DEVICE) or None + if linked_device_id and registry.async_get(linked_device_id) is None: + linked_device_id = None + + if washdata_device.via_device_id != linked_device_id: + registry.async_update_device( + washdata_device.id, via_device_id=linked_device_id + ) + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload config entry - update settings without interrupting running cycles.""" + manager = hass.data[DOMAIN].get(entry.entry_id) + if manager: + # Update configuration without interrupting detector + await manager.async_reload_config(entry) + # Options changes (e.g. linked device) reload in place without + # recreating entities, so apply the device link explicitly here. + _apply_device_link(hass, entry) + else: + # Full reload if manager not found + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + manager = hass.data[DOMAIN].pop(entry.entry_id) + await manager.async_shutdown() + + return unload_ok diff --git a/custom_components/ha_washdata/__pycache__/__init__.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000..4cda6b6 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/__init__.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/analysis.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/analysis.cpython-314.pyc new file mode 100644 index 0000000..1d15ef5 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/analysis.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/binary_sensor.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/binary_sensor.cpython-314.pyc new file mode 100644 index 0000000..d40d462 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/binary_sensor.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/button.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/button.cpython-314.pyc new file mode 100644 index 0000000..cc8d3bd Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/button.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/config_flow.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/config_flow.cpython-314.pyc new file mode 100644 index 0000000..a1f939a Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/config_flow.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/const.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/const.cpython-314.pyc new file mode 100644 index 0000000..d4e07c0 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/const.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/cycle_detector.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/cycle_detector.cpython-314.pyc new file mode 100644 index 0000000..24bab79 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/cycle_detector.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/diag_buffer.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/diag_buffer.cpython-314.pyc new file mode 100644 index 0000000..138b12f Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/diag_buffer.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/diagnostics.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/diagnostics.cpython-314.pyc new file mode 100644 index 0000000..cc7ad19 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/diagnostics.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/features.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/features.cpython-314.pyc new file mode 100644 index 0000000..4ec1b02 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/features.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/frontend.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/frontend.cpython-314.pyc new file mode 100644 index 0000000..196acf5 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/frontend.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/learning.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/learning.cpython-314.pyc new file mode 100644 index 0000000..e28b745 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/learning.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/log_utils.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/log_utils.cpython-314.pyc new file mode 100644 index 0000000..632f23b Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/log_utils.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/manager.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/manager.cpython-314.pyc new file mode 100644 index 0000000..829b47e Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/manager.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/phase_catalog.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/phase_catalog.cpython-314.pyc new file mode 100644 index 0000000..be55479 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/phase_catalog.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/profile_store.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/profile_store.cpython-314.pyc new file mode 100644 index 0000000..4a1ac82 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/profile_store.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/recorder.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/recorder.cpython-314.pyc new file mode 100644 index 0000000..beccf23 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/recorder.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/select.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/select.cpython-314.pyc new file mode 100644 index 0000000..bf22aec Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/select.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/sensor.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/sensor.cpython-314.pyc new file mode 100644 index 0000000..433d169 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/sensor.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/signal_processing.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/signal_processing.cpython-314.pyc new file mode 100644 index 0000000..a8bc5a6 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/signal_processing.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/suggestion_engine.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/suggestion_engine.cpython-314.pyc new file mode 100644 index 0000000..76be942 Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/suggestion_engine.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/__pycache__/time_utils.cpython-314.pyc b/custom_components/ha_washdata/__pycache__/time_utils.cpython-314.pyc new file mode 100644 index 0000000..1a5d39a Binary files /dev/null and b/custom_components/ha_washdata/__pycache__/time_utils.cpython-314.pyc differ diff --git a/custom_components/ha_washdata/analysis.py b/custom_components/ha_washdata/analysis.py new file mode 100644 index 0000000..68948a0 --- /dev/null +++ b/custom_components/ha_washdata/analysis.py @@ -0,0 +1,540 @@ +"""Analysis module for heavy CPU tasks (offloaded to executor).""" +from __future__ import annotations + +import logging +from typing import Any, Optional + +import numpy as np + +_LOGGER = logging.getLogger(__name__) +ALIGNMENT_CONTEXT_BUFFER = 50 + +def find_best_alignment( + current_power: list[float] | np.ndarray, + sample_power: list[float] | np.ndarray, + dt: float = 1.0 # pylint: disable=unused-argument +) -> tuple[float, dict[str, float], int]: + """Find Best Alignment using Coarse-to-Fine Search (CPU Bound).""" + + curr = np.array(current_power) + ref = np.array(sample_power) + + n_curr = len(curr) + n_ref = len(ref) + + # 1. Coarse Alignment (Cross-Correlation) + # Downsample for speed if arrays are large + ds_factor = 1 + if n_curr > 200: + ds_factor = int(n_curr / 100) + + if ds_factor > 1: + c_coarse = curr[::ds_factor] + r_coarse = ref[::ds_factor] + else: + c_coarse = curr + r_coarse = ref + + # Standardize + if np.std(c_coarse) > 1e-6: + c_norm = (c_coarse - np.mean(c_coarse)) / np.std(c_coarse) + else: + c_norm = c_coarse + + if np.std(r_coarse) > 1e-6: + r_norm = (r_coarse - np.mean(r_coarse)) / np.std(r_coarse) + else: + r_norm = r_coarse + + # Cross correlation + correlation = np.correlate(c_norm, r_norm, mode="full") + lags = np.arange(-len(r_norm) + 1, len(c_norm)) + + best_idx = int(np.argmax(correlation)) + best_lag_coarse = lags[best_idx] + + best_offset = best_lag_coarse * ds_factor + + # 2. Fine Refinement + window = 10 * ds_factor + min_off = max(-len(ref) + 1, best_offset - window) + max_off = min(len(curr), best_offset + window) + + best_mae = float("inf") + final_offset = best_offset + + for off in range(int(min_off), int(max_off) + 1): + # intersection + c_start = max(0, off) + c_end = min(n_curr, n_ref + off) + + r_start = max(0, -off) + r_end = min(n_ref, n_curr - off) + + if (c_end - c_start) < 10: + continue + + c_seg = curr[c_start:c_end] + r_seg = ref[r_start:r_end] + + mae = np.mean(np.abs(c_seg - r_seg)) + if mae < best_mae: + best_mae = mae + final_offset = off + + # Calculate Final Score metrics + off = final_offset + c_start = max(0, off) + c_end = min(n_curr, n_ref + off) + r_start = max(0, -off) + r_end = min(n_ref, n_curr - off) + + if (c_end - c_start) < 5: + return 0.0, {"mae": float(best_mae)}, final_offset + + c_final = curr[c_start:c_end] + r_final = ref[r_start:r_end] + + mae = np.mean(np.abs(c_final - r_final)) + + # Correlation + if np.std(c_final) > 1e-6 and np.std(r_final) > 1e-6: + corr = np.corrcoef(c_final, r_final)[0, 1] + else: + corr = 0.0 + + mae_score = 100.0 / (100.0 + mae) + score = (0.6 * max(0, corr)) + (0.4 * mae_score) + + return float(score), {"mae": float(mae), "corr": float(corr)}, final_offset + +def compute_dtw_lite( + x: np.ndarray, y: np.ndarray, band_width_ratio: float = 0.1 +) -> float: + """ + Compute DTW distance with Sakoe-Chiba band constraint. + Optimized 1D DP implementation. O(N*W). + """ + n, m = len(x), len(y) + if n == 0 or m == 0: + return float("inf") + + # Band width + w = max(1, int(min(n, m) * band_width_ratio)) + + # Use two rows to save memory and improve cache locality + prev_row = np.full(m + 1, float("inf")) + curr_row = np.full(m + 1, float("inf")) + prev_row[0] = 0 + + for i in range(1, n + 1): + center = int(i * (m / n)) + start_j = max(1, center - w) + end_j = min(m, center + w + 1) + + curr_row.fill(float("inf")) + + # Pre-calculate costs for the current window to reduce Python overhead + # x is 0-indexed, so x[i-1] + val_x = x[i - 1] + + for j in range(start_j, end_j + 1): + cost = abs(float(val_x - y[j - 1])) + + # Standard DTW recursion + # curr_row[j] = cost + min(insertion, deletion, match) + # insertion: prev_row[j] + # deletion: curr_row[j-1] + # match: prev_row[j-1] + + # Use a slightly faster min implementation if possible + m1 = prev_row[j] + m2 = curr_row[j - 1] + m3 = prev_row[j - 1] + + if m1 < m2: + if m1 < m3: + best_prev = m1 + else: + best_prev = m3 + else: + if m2 < m3: + best_prev = m2 + else: + best_prev = m3 + + curr_row[j] = cost + best_prev + + # Swap rows + prev_row[:] = curr_row[:] + + return float(prev_row[m]) + +def compute_matches_worker( + current_power: list[float], + current_duration: float, + snapshots: list[dict[str, Any]], + config: dict[str, Any] +) -> list[dict[str, Any]]: + """Worker function to compute matches against snapshots.""" + candidates: list[dict[str, Any]] = [] + + min_duration_ratio = config.get("min_duration_ratio", 0.07) + max_duration_ratio = config.get("max_duration_ratio", 1.3) + dtw_bandwidth = config.get("dtw_bandwidth", 0.1) + + curr_arr = np.array(current_power) + + for item in snapshots: + name = item["name"] + profile_duration = item["avg_duration"] + sample_power = item["sample_power"] + + # Duration Check + if profile_duration > 0: + ratio = current_duration / profile_duration + if ratio < min_duration_ratio or ratio > max_duration_ratio: + continue + + # Core Similarity + score, metrics, offset = find_best_alignment( + current_power, sample_power, 1.0 + ) + + if score > 0.1: + candidates.append({ + "name": name, + "score": score, + "metrics": metrics, + "profile_duration": profile_duration, + "current": current_power, + "sample": sample_power, + "offset": offset + }) + + candidates.sort(key=lambda x: x["score"], reverse=True) + + # Stage 3: DTW Refinement on Top 3 + if dtw_bandwidth > 0.0 and len(candidates) > 0: + to_refine = candidates[:3] + + for cand in to_refine: + sample_arr = np.array(cand["sample"]) + + dtw_dist = compute_dtw_lite( + curr_arr, + sample_arr, + band_width_ratio=dtw_bandwidth, + ) + + n_points = len(curr_arr) + if n_points > 0: + norm_dist = dtw_dist / n_points + else: + norm_dist = 999.0 + + dtw_score = 1.0 / (1.0 + norm_dist / 50.0) + + cand["original_score"] = float(cand["score"]) + cand["score"] = float(0.5 * cand["score"] + 0.5 * dtw_score) + cand["dtw_dist"] = float(norm_dist) + + candidates.sort(key=lambda x: x["score"], reverse=True) + + return candidates + +def compute_dtw_path( + x: np.ndarray, y: np.ndarray, band_width_ratio: float = 0.1 +) -> list[tuple[int, int]]: + """ + Compute DTW path with Sakoe-Chiba constraint. + Returns list of (x_index, y_index) tuples mapping X to Y. + """ + n, m = len(x), len(y) + if n == 0 or m == 0: + return [] + + w = max(1, int(min(n, m) * band_width_ratio)) + cost_matrix = np.full((n + 1, m + 1), float("inf")) + cost_matrix[0, 0] = 0 + + # Cost Matrix + for i in range(1, n + 1): + center = i * (m / n) + start_j = max(1, int(center - w)) + end_j = min(m, int(center + w) + 1) + + for j in range(start_j, end_j + 1): + cost = abs(float(x[i - 1] - y[j - 1])) + cost_matrix[i, j] = cost + min( + cost_matrix[i - 1, j], cost_matrix[i, j - 1], cost_matrix[i - 1, j - 1] + ) + + # Backtracking + if np.isinf(cost_matrix[n, m]): + # Endpoint is unreachable (e.g. Sakoe-Chiba band excluded it); no valid path. + return [] + + path: list[tuple[int, int]] = [] + i, j = n, m + + while i > 0 or j > 0: + # Record current zero-based coordinate before stepping back. + path.append((max(i - 1, 0), max(j - 1, 0))) + + if i == 0: + j -= 1 + elif j == 0: + i -= 1 + else: + candidates_cost = [ + (cost_matrix[i - 1, j], 0), # deletion (i-1) + (cost_matrix[i, j - 1], 1), # insertion (j-1) + (cost_matrix[i - 1, j - 1], 2) # match (both) + ] + candidates_cost.sort(key=lambda item: item[0]) + best_move = candidates_cost[0][1] + if best_move == 0: + i -= 1 + elif best_move == 1: + j -= 1 + else: + i -= 1 + j -= 1 + + path.reverse() + + return path + +def compute_envelope_worker( + raw_cycles_data: list[tuple[list[float], list[float], Optional[float]]] | list[tuple[list[float], list[float]]], + dtw_bandwidth: float +) -> tuple[list[float], list[float], list[float], list[float], list[float], float] | None: + """ + Compute statistical envelope. + Args: + raw_cycles_data: list of (offsets, power_values, duration) tuples. + Duration may be None and is used to compute target_duration. + dtw_bandwidth: ratio. + Returns: + (time_grid, min_curve, max_curve, avg_curve, std_curve, target_duration) or None. + """ + if not raw_cycles_data: + return None + normalized_curves: list[tuple[np.ndarray, np.ndarray, float]] = [] + sampling_rates: list[float] = [] + + # 1. Pre-process input + for curve in raw_cycles_data: + # Unpack curve tuple: (offsets, values) or (offsets, values, duration) + # Backward compatible with 2-tuple (offsets, values) format + try: + offsets_list, values_list, *rest = curve + curve_duration = rest[0] if rest else None + except (ValueError, TypeError): + continue + + if not offsets_list or not values_list: + continue + + if len(offsets_list) != len(values_list): + min_len = min(len(offsets_list), len(values_list)) + if min_len < 3: + continue + offsets_list = offsets_list[:min_len] + values_list = values_list[:min_len] + + if len(offsets_list) < 3 or len(values_list) < 3: + continue + + try: + offsets = np.asarray(offsets_list, dtype=float) + values = np.asarray(values_list, dtype=float) + except (TypeError, ValueError): + continue + + # Drop paired entries where either coordinate is non-finite. + finite_mask = np.isfinite(offsets) & np.isfinite(values) + offsets = offsets[finite_mask] + values = values[finite_mask] + if len(offsets) < 3: + continue + + if not np.all(np.diff(offsets) > 0): + continue + + try: + dur = float(curve_duration) if curve_duration is not None else float(offsets[-1]) + except (TypeError, ValueError, OverflowError): + continue + + # Validate duration is positive and finite before appending. + if not (dur > 0 and np.isfinite(dur)): + continue + + normalized_curves.append((offsets, values, dur)) + + if len(offsets) > 1: + intervals = np.diff(offsets) + positive_intervals = intervals[intervals > 0] + if positive_intervals.size > 0: + sr = float(np.median(positive_intervals)) + if np.isfinite(sr): + sampling_rates.append(sr) + if not normalized_curves: + return None + + # 2. Reference Selection (Median Duration) + # Input is now (offsets, values, duration) + max_times = [float(dur) for _, _, dur in normalized_curves] + median_dur = float(np.median(max_times)) + ref_idx = int(np.argmin([abs(t - median_dur) for t in max_times])) + + target_duration = max_times[ref_idx] + avg_sample_rate = float(np.median(sampling_rates)) if sampling_rates else 2.0 + + # Ensure target_duration is valid for calculations + if not (target_duration > 0 and np.isfinite(target_duration)): + target_duration = 1.0 # Safe default + + align_dt = avg_sample_rate + num_points = max(50, int(target_duration / align_dt)) + time_grid = np.linspace(0.0, target_duration, num_points) + + ref_offsets, ref_values, _ = normalized_curves[ref_idx] + ref_array = np.interp(time_grid, ref_offsets, ref_values) + + # 3. Resample & DTW + resampled: list[np.ndarray] = [] + + for i, (offsets, values, dur) in enumerate(normalized_curves): + if i == ref_idx: + resampled.append(ref_array) + continue + this_dur = dur + this_num_points = max(10, int(this_dur / align_dt)) + this_grid = np.linspace(0.0, this_dur, this_num_points) + this_array = np.interp(this_grid, offsets, values) + + path = compute_dtw_path(this_array, ref_array, band_width_ratio=dtw_bandwidth) + + if not path: + resampled.append(np.interp(time_grid, offsets, values)) + continue + path_arr = np.array(path) + cand_indices = path_arr[:, 0] + ref_indices = path_arr[:, 1] + + # Interpolate map + # Map ref indices (time_grid indices) to cand indices (this_grid indices) + # We assume monotonicity and filter duplicates by taking mean + + # Simplified: Use numpy interp of indicies + # ref_indices are 0..N_ref + # cand_indices are 0..N_cand + # We need mapping: for ref_idx in 0..num_points, what is cand_idx? + + # Since ref_indices in path are not strictly increasing (duplicates), + # we can't use them as 'x' for interp directly if strictness required. + # But we can sort/unique them. + + # Sort by ref_index? Path is already sorted roughly. + # Handle duplicates: average candidate indices for same ref index. + unique_ref, inverse = np.unique(ref_indices, return_inverse=True) + # Computing mean candidate index for each unique ref index + # This is slow in python loop. + # Vectorized: + # np.bincount? + mean_cand_indices = np.zeros_like(unique_ref, dtype=float) + np.add.at(mean_cand_indices, inverse, cand_indices) + counts = np.bincount(inverse) + mean_cand_indices /= counts + + # Now we have unique_ref -> mean_cand_indices + # Interpolate to full time_grid (0..num_points-1) + mapped_cand_indices = np.interp( + np.arange(num_points), + unique_ref, + mean_cand_indices, + left=0, + right=len(this_array)-1 + ) + + # Now get values + mapped_times = mapped_cand_indices * (this_dur / (len(this_array)-1)) + warped_values = np.interp(mapped_times, this_grid, this_array) + resampled.append(warped_values) + + # 4. Compute Stats + stacked = np.vstack(resampled) + min_curve = np.min(stacked, axis=0) + max_curve = np.max(stacked, axis=0) + avg_curve = np.mean(stacked, axis=0) + std_curve = np.std(stacked, axis=0) + + return ( + time_grid.tolist(), + min_curve.tolist(), + max_curve.tolist(), + avg_curve.tolist(), + std_curve.tolist(), + float(target_duration) + ) + +def verify_profile_alignment_worker( + current_power: list[float], + envelope_avg_curve: list[float], + envelope_time_grid: list[float], + dtw_bandwidth: float +) -> tuple[float, float, float]: + """ + Verify alignment of current trace against profile envelope. + Returns: (mapped_envelope_time, mapped_envelope_power, overlap_score) + """ + if not current_power or not envelope_avg_curve: + return 0.0, 9999.0, 0.0 + + curr = np.array(current_power) + ref = np.array(envelope_avg_curve) + + # 1. Coarse Alignment + score, _, offset = find_best_alignment(curr, ref, 1.0) + + # 2. Extract aligned segments + # Determine the mapping window. + + # Symmetric context window: pad equally left and right of the coarse alignment. + half = ALIGNMENT_CONTEXT_BUFFER // 2 + start_ref = max(0, offset - half) + end_ref = min(len(ref), offset + len(curr) + half) + + if end_ref <= start_ref: + return 0.0, 9999.0, 0.0 + + ref_seg = ref[start_ref:end_ref] + curr_seg = curr + + if offset < 0: + curr_seg = curr[-offset:] + + path = compute_dtw_path(curr_seg, ref_seg, band_width_ratio=dtw_bandwidth) + + if not path: + # Fallback to linear mapping based on offset + mapped_idx = min(len(ref)-1, offset + len(curr) - 1) + mapped_idx = max(0, mapped_idx) + else: + # Map the final point of the current trace to the reference index + last_pair = path[-1] + ref_seg_idx = last_pair[1] + mapped_idx = start_ref + ref_seg_idx + + # Ensure sequences are non-empty before indexing + if not envelope_time_grid or len(ref) == 0: + return 0.0, 9999.0, 0.0 + mapped_idx = min(mapped_idx, len(envelope_time_grid) - 1, len(ref) - 1) + + mapped_time = float(envelope_time_grid[mapped_idx]) + mapped_power = float(ref[mapped_idx]) + + return mapped_time, mapped_power, float(score) diff --git a/custom_components/ha_washdata/binary_sensor.py b/custom_components/ha_washdata/binary_sensor.py new file mode 100644 index 0000000..d5af4e5 --- /dev/null +++ b/custom_components/ha_washdata/binary_sensor.py @@ -0,0 +1,100 @@ +"""Binary sensor for WashData.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.const import EntityCategory +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import ( + DOMAIN, + STATE_RUNNING, + SIGNAL_WASHER_UPDATE, + CONF_EXPOSE_DEBUG_ENTITIES, +) +from .manager import WashDataManager +from .sensor import cleanup_orphaned_diagnostic_entities + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the binary sensor.""" + manager: WashDataManager = hass.data[DOMAIN][entry.entry_id] + entities = [WasherRunningBinarySensor(manager, entry)] + + if entry.options.get(CONF_EXPOSE_DEBUG_ENTITIES): + entities.append(WasherAmbiguitySensor(manager, entry)) + + async_add_entities(entities) + cleanup_orphaned_diagnostic_entities(hass, manager, entry) + + +class WasherRunningBinarySensor(BinarySensorEntity): + """Binary sensor indicating if washer is running.""" + + _attr_has_entity_name = True + + _attr_translation_key = "running" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize.""" + self._manager = manager + self._entry = entry + self._attr_unique_id = f"{entry.entry_id}_running" + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.entry_id)}, + "name": entry.title, + "manufacturer": "WashData", + } + + @property + def is_on(self) -> bool | None: + """Return true if the binary sensor is on.""" + return self._manager.check_state() == STATE_RUNNING + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_WASHER_UPDATE.format(self._entry.entry_id), + self._update_callback, + ) + ) + + @callback + def _update_callback(self) -> None: + """Update the sensor.""" + self.async_write_ha_state() + + +class WasherAmbiguitySensor(WasherRunningBinarySensor): + """Binary sensor indicating if current profiling is ambiguous.""" + + _attr_translation_key = "match_ambiguity" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize.""" + super().__init__(manager, entry) + self._attr_unique_id = f"{entry.entry_id}_ambiguity" + self._attr_icon = "mdi:alert-circle-outline" + self._attr_entity_category = EntityCategory.DIAGNOSTIC + + @property + def is_on(self) -> bool: # type: ignore[override] + """Return true if match is ambiguous.""" + return self._manager.match_ambiguity + + @property + def extra_state_attributes(self) -> dict[str, Any]: # type: ignore[override] + """Return ambiguous candidate info.""" + details = self._manager.last_match_details + return {"margin": details.get("ambiguity_margin", 0.0) if details else 0.0} diff --git a/custom_components/ha_washdata/button.py b/custom_components/ha_washdata/button.py new file mode 100644 index 0000000..cce7fbf --- /dev/null +++ b/custom_components/ha_washdata/button.py @@ -0,0 +1,144 @@ +"""Button platform for WashData.""" + +from __future__ import annotations + +import logging +from homeassistant.components.button import ButtonEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback + +from .const import DOMAIN, STATE_RUNNING, STATE_STARTING, STATE_PAUSED, STATE_ENDING, SIGNAL_WASHER_UPDATE +from .manager import WashDataManager + +_LOGGER = logging.getLogger(__name__) + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the WashData button.""" + manager: WashDataManager = hass.data[DOMAIN][entry.entry_id] + + async_add_entities([ + WashDataTerminateButton(manager, entry), + WashDataPauseCycleButton(manager, entry), + WashDataResumeCycleButton(manager, entry), + ]) + + +class WashDataTerminateButton(ButtonEntity): + """Button to force terminate the current cycle.""" + + _attr_has_entity_name = True + _attr_translation_key = "force_end_cycle" + _attr_icon = "mdi:stop-circle-outline" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the button.""" + self._manager = manager + self._entry = entry + self._attr_unique_id = f"{entry.entry_id}_force_end" + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.entry_id)}, + "name": entry.title, + "manufacturer": "WashData", + } + + def press(self) -> None: + """Handle the button press.""" + raise NotImplementedError() + + async def async_press(self) -> None: + """Handle the button press.""" + await self._manager.async_terminate_cycle() + + +class WashDataPauseCycleButton(ButtonEntity): + """Button to pause the current cycle (user-triggered).""" + + _attr_has_entity_name = True + _attr_translation_key = "pause_cycle" + _attr_icon = "mdi:pause-circle-outline" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the button.""" + self._manager = manager + self._entry = entry + self._attr_unique_id = f"{entry.entry_id}_pause_cycle" + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.entry_id)}, + "name": entry.title, + "manufacturer": "WashData", + } + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_WASHER_UPDATE.format(self._entry.entry_id), + self._update_callback, + ) + ) + + @callback + def _update_callback(self) -> None: + self.async_write_ha_state() + + @property + def available(self) -> bool: + """Only available when a cycle is active and not already user-paused.""" + return ( + self._manager.check_state() in (STATE_RUNNING, STATE_STARTING, STATE_PAUSED, STATE_ENDING) + and not self._manager.is_user_paused + ) + + async def async_press(self) -> None: + """Handle the button press.""" + await self._manager.async_pause_cycle() + + +class WashDataResumeCycleButton(ButtonEntity): + """Button to resume a user-paused cycle.""" + + _attr_has_entity_name = True + _attr_translation_key = "resume_cycle" + _attr_icon = "mdi:play-circle-outline" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the button.""" + self._manager = manager + self._entry = entry + self._attr_unique_id = f"{entry.entry_id}_resume_cycle" + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.entry_id)}, + "name": entry.title, + "manufacturer": "WashData", + } + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_WASHER_UPDATE.format(self._entry.entry_id), + self._update_callback, + ) + ) + + @callback + def _update_callback(self) -> None: + self.async_write_ha_state() + + @property + def available(self) -> bool: + """Only available when the cycle is user-paused.""" + return self._manager.is_user_paused + + async def async_press(self) -> None: + """Handle the button press.""" + await self._manager.async_resume_cycle() \ No newline at end of file diff --git a/custom_components/ha_washdata/config_flow.py b/custom_components/ha_washdata/config_flow.py new file mode 100644 index 0000000..ad2352d --- /dev/null +++ b/custom_components/ha_washdata/config_flow.py @@ -0,0 +1,5688 @@ +"""Config flow for WashData integration.""" +# pylint: disable=too-many-lines +# pyright: reportUnknownMemberType=false, reportUnknownVariableType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false, reportMissingParameterType=false, reportUnknownLambdaType=false, reportOptionalMemberAccess=false, reportOperatorIssue=false, reportReturnType=false, reportIncompatibleMethodOverride=false + +from __future__ import annotations + +import json +import logging +import os +import time +import base64 +import html +from datetime import datetime, timedelta +from typing import Any + +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.const import CONF_NAME +from homeassistant.data_entry_flow import FlowResult, section +from homeassistant.helpers import selector, translation +from homeassistant.util import slugify +from homeassistant.util import dt as dt_util + +from .const import ( + DOMAIN, + CONF_POWER_SENSOR, + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + CONF_SAMPLING_INTERVAL, + CONF_NOTIFY_SERVICE, + CONF_NOTIFY_ACTIONS, + CONF_NOTIFY_PEOPLE, + CONF_NOTIFY_ONLY_WHEN_HOME, + CONF_NOTIFY_FIRE_EVENTS, + CONF_NOTIFY_EVENTS, + CONF_NOTIFY_START_SERVICES, + CONF_NOTIFY_FINISH_SERVICES, + CONF_NOTIFY_LIVE_SERVICES, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SMOOTHING_WINDOW, + CONF_START_DURATION_THRESHOLD, + CONF_DEVICE_TYPE, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_APPLY_SUGGESTIONS, + CONF_SHOW_ADVANCED, + CONF_PROGRESS_RESET_DELAY, + CONF_DURATION_TOLERANCE, + CONF_AUTO_LABEL_CONFIDENCE, + DEFAULT_AUTO_LABEL_CONFIDENCE, + CONF_LEARNING_CONFIDENCE, + DEFAULT_LEARNING_CONFIDENCE, + CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS, + DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS, + CONF_EXPOSE_DEBUG_ENTITIES, + CONF_SAVE_DEBUG_TRACES, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_AUTO_MAINTENANCE, + CONF_WATCHDOG_INTERVAL, + CONF_COMPLETION_MIN_SECONDS, + CONF_NOTIFY_BEFORE_END_MINUTES, + CONF_RUNNING_DEAD_ZONE, + CONF_END_REPEAT_COUNT, + CONF_START_ENERGY_THRESHOLD, + CONF_END_ENERGY_THRESHOLD, + CONF_EXTERNAL_END_TRIGGER_ENABLED, + CONF_EXTERNAL_END_TRIGGER, + CONF_EXTERNAL_END_TRIGGER_INVERTED, + CONF_ANTI_WRINKLE_ENABLED, + CONF_ANTI_WRINKLE_MAX_POWER, + CONF_ANTI_WRINKLE_MAX_DURATION, + CONF_ANTI_WRINKLE_EXIT_POWER, + CONF_DELAY_START_DETECT_ENABLED, + CONF_DELAY_CONFIRM_SECONDS, + CONF_DELAY_TIMEOUT_HOURS, + NOTIFY_EVENT_START, + NOTIFY_EVENT_FINISH, + NOTIFY_EVENT_LIVE, + DEFAULT_NAME, + DEFAULT_MIN_POWER, + DEFAULT_OFF_DELAY, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT, + DEFAULT_SMOOTHING_WINDOW, + DEFAULT_START_DURATION_THRESHOLD, + DEFAULT_START_ENERGY_THRESHOLD, + DEFAULT_END_ENERGY_THRESHOLD, + DEFAULT_DEVICE_TYPE, + DEFAULT_PROFILE_DURATION_TOLERANCE, + DEVICE_TYPES, + DEPRECATED_DEVICE_TYPES, + DEFAULT_PROGRESS_RESET_DELAY, + DEFAULT_DURATION_TOLERANCE, + DEFAULT_PROFILE_MATCH_INTERVAL, + DEFAULT_AUTO_MAINTENANCE, + DEFAULT_WATCHDOG_INTERVAL, + DEFAULT_COMPLETION_MIN_SECONDS, + DEFAULT_NOTIFY_BEFORE_END_MINUTES, + DEFAULT_RUNNING_DEAD_ZONE, + DEFAULT_END_REPEAT_COUNT, + DEFAULT_MIN_OFF_GAP_BY_DEVICE, + DEFAULT_MIN_OFF_GAP, + CONF_MIN_OFF_GAP, + DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE, + DEVICE_COMPLETION_THRESHOLDS, + CONF_PROFILE_MATCH_THRESHOLD, + CONF_PROFILE_UNMATCH_THRESHOLD, + DEFAULT_PROFILE_MATCH_THRESHOLD, + DEFAULT_PROFILE_UNMATCH_THRESHOLD, + DEFAULT_SAMPLING_INTERVAL, + CONF_NOTIFY_TITLE, + CONF_NOTIFY_ICON, + CONF_NOTIFY_START_MESSAGE, + CONF_NOTIFY_FINISH_MESSAGE, + CONF_NOTIFY_PRE_COMPLETE_MESSAGE, + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + CONF_NOTIFY_LIVE_CHRONOMETER, + CONF_NOTIFY_REMINDER_MESSAGE, + CONF_NOTIFY_TIMEOUT_SECONDS, + CONF_NOTIFY_CHANNEL, + CONF_NOTIFY_FINISH_CHANNEL, + CONF_ENERGY_PRICE_STATIC, + CONF_ENERGY_PRICE_ENTITY, + DEFAULT_NOTIFY_TITLE, + DEFAULT_NOTIFY_START_MESSAGE, + DEFAULT_NOTIFY_FINISH_MESSAGE, + DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE, + DEFAULT_NOTIFY_REMINDER_MESSAGE, + DEFAULT_NOTIFY_TIMEOUT_SECONDS, + DEFAULT_NOTIFY_ONLY_WHEN_HOME, + DEFAULT_NOTIFY_FIRE_EVENTS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_LIVE_CHRONOMETER, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_OFF_DELAY_BY_DEVICE, + DEFAULT_SAMPLING_INTERVAL_BY_DEVICE, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE, + DEFAULT_ANTI_WRINKLE_ENABLED, + DEFAULT_ANTI_WRINKLE_MAX_POWER, + DEFAULT_ANTI_WRINKLE_MAX_DURATION, + DEFAULT_ANTI_WRINKLE_EXIT_POWER, + DEFAULT_DELAY_START_DETECT_ENABLED, + DEFAULT_DELAY_CONFIRM_SECONDS, + DEFAULT_DELAY_TIMEOUT_HOURS, + CONF_PUMP_STUCK_DURATION, + DEFAULT_PUMP_STUCK_DURATION, + DEVICE_TYPE_PUMP, + CONF_DOOR_SENSOR_ENTITY, + CONF_PAUSE_CUTS_POWER, + CONF_SWITCH_ENTITY, + CONF_LINKED_DEVICE, + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, + DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES, +) +from .profile_store import profile_sort_key + + +_LOGGER = logging.getLogger(__name__) + + +def _format_duration_label(seconds: int) -> str: + """Render a duration in seconds as '1h 25m' or '42m'.""" + minutes = max(0, int(seconds) // 60) + if minutes < 60: + return f"{minutes}m" + return f"{minutes // 60}h {minutes % 60:02d}m" + + +def _device_type_options( + current: str | None = None, +) -> list[str]: + """Build the device-type dropdown option keys. + + Returns plain option keys so the frontend resolves the labels per user from + the ``selector.device_type`` translations (passing pre-built labels would + lock them to the server language). Deprecated types are hidden for new + entries; for an existing entry whose saved device_type is deprecated, that + type is kept in the list so the user can either keep it or switch without + losing it from the dropdown. The "(deprecated)" wording lives in the + translated labels for the deprecated keys. + """ + return [ + key + for key in DEVICE_TYPES + if key not in DEPRECATED_DEVICE_TYPES or key == current + ] + + +def _escape_markdown(text: Any) -> str: + """Make a user-supplied label safe to embed in markdown descriptions. + + Profile and phase names are free text, so collapse any whitespace runs + (including newlines, which would otherwise break list rendering) into single + spaces and escape the markdown metacharacters that would otherwise inject + emphasis, code spans, links, or table cells. + """ + collapsed = " ".join(str(text).split()) + # Backslash must be escaped first so the others are not double-escaped. + for char in ("\\", "`", "*", "_", "[", "]", "~", "|"): + collapsed = collapsed.replace(char, f"\\{char}") + return collapsed + + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): str, + vol.Required( + CONF_DEVICE_TYPE, default=DEFAULT_DEVICE_TYPE + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=_device_type_options(), + mode=selector.SelectSelectorMode.DROPDOWN, + translation_key="device_type", + ) + ), + vol.Required(CONF_POWER_SENSOR): selector.EntitySelector( + selector.EntitySelectorConfig(domain="sensor"), + ), + vol.Optional(CONF_MIN_POWER, default=DEFAULT_MIN_POWER): vol.Coerce(float), + } +) + + +class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # pylint: disable=abstract-method + """Handle a config flow for WashData.""" + + VERSION = 3 + + def __init__(self) -> None: + """Initialize the config flow.""" + self._user_input: dict[str, Any] = {} + + def _get_schema( + self, user_input: dict[str, Any] | None = None # pylint: disable=unused-argument + ) -> vol.Schema: + """Get the configuration schema.""" + return STEP_USER_DATA_SCHEMA + + async def async_step_user( + self, user_input: dict[str, Any] | None = None # pylint: disable=unused-argument + ) -> FlowResult: + """Handle the initial step.""" + errors: dict[str, str] = {} + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=self._get_schema(), errors=errors + ) + + # Validate input + try: + # Basic validation + if user_input[CONF_MIN_POWER] <= 0: + errors[CONF_MIN_POWER] = "invalid_power" + except Exception: # pylint: disable=broad-exception-caught + _LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + + if errors: + return self.async_show_form( + step_id="user", data_schema=self._get_schema(user_input), errors=errors + ) + + # Store user input and proceed to profile creation + self._user_input = user_input + return await self.async_step_first_profile() + + async def async_step_first_profile( + self, user_input: dict[str, Any] | None = None # pylint: disable=unused-argument + ) -> FlowResult: + """Step to optionally create the first profile.""" + + if user_input is not None: + # Check if user wants to create a profile (if name is provided) + profile_name = user_input.get("profile_name", "").strip() + + # Combine initial setup data with profile data if present + data = dict(self._user_input) + + if profile_name: + duration_mins = user_input.get("manual_duration") + duration_sec = (duration_mins * 60.0) if duration_mins else None + + # Pass as special key to be handled in async_setup_entry + data["initial_profile"] = { + "name": profile_name, + "avg_duration": duration_sec, + } + + return self.async_create_entry(title=data[CONF_NAME], data=data) + + # Schema for first profile + schema = vol.Schema( + { + vol.Optional("profile_name"): str, + vol.Optional("manual_duration", default=120): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=480, + unit_of_measurement="min", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + ) + + return self.async_show_form(step_id="first_profile", data_schema=schema) + + @staticmethod + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Create the options flow.""" + return OptionsFlowHandler(config_entry) + + +class OptionsFlowHandler(config_entries.OptionsFlow): + """Handle a options flow for WashData.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self._config_entry = config_entry + self._selected_cycle_id: str | None = None + self._selected_profile: str | None = None + self._suggested_values: dict[str, Any] | None = None + self._pending_suggestion_diffs_md: str = "" + self._pending_suggestion_count: int = 0 + self._basic_options: dict[str, Any] = {} + self._editor_action: str | None = None + self._editor_selected_ids: list[str] = [] + self._editor_split_gap: int = 900 + self._editor_split_mode: str = "auto" + self._editor_split_manual_segments: list[tuple[float, float]] = [] + self._selected_phase_name: str | None = None + self._selected_phase_device_type: str | None = None + self._selected_phase_id: str | None = None + self._phase_assign_profile: str | None = None + self._phase_assign_mode: str = "offset_mode" + self._phase_assign_cycle_id: str | None = None + self._phase_assign_draft: list[dict[str, Any]] = [] + self._phase_assign_edit_index: int | None = None + self._phase_assign_auto_detected: list[dict[str, Any]] = [] + self._trim_cycle_id: str | None = None + self._trim_cycle_start_dt: datetime | None = None + self._trim_start_s: float = 0.0 + self._trim_end_s: float = 0.0 + self._selector_translations: dict[str, str] | None = None + self._menu_stack: list[str] = [] + + def _push_menu(self, step_id: str) -> None: + """Track entry into a menu so Back can pop to the previous one.""" + if not self._menu_stack or self._menu_stack[-1] != step_id: + self._menu_stack.append(step_id) + + async def async_step_menu_back( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Pop the menu stack and re-show the previous menu (or init).""" + if self._menu_stack: + self._menu_stack.pop() + target = self._menu_stack[-1] if self._menu_stack else "init" + # Clear so the parent re-pushes itself cleanly + if self._menu_stack: + self._menu_stack.pop() + return await getattr(self, f"async_step_{target}")() + + @staticmethod + def _translated_select( + options: list[str], + translation_key: str, + mode: selector.SelectSelectorMode = selector.SelectSelectorMode.DROPDOWN, + multiple: bool = False, + custom_value: bool = False, + sort: bool = False, + ) -> Any: + """Build a select selector whose labels come from translations.""" + return selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=mode, + multiple=multiple, + custom_value=custom_value, + sort=sort, + translation_key=translation_key, + ) + ) + + async def _selector_text(self, text_key: str, default: str) -> str: + """Resolve selector-localized text by key.""" + lang = self.context.get("language") or self.hass.config.language + if self._selector_translations is None: + self._selector_translations = await translation.async_get_translations( + self.hass, lang, "selector", {DOMAIN} + ) + + return self._selector_translations.get( + f"component.{DOMAIN}.selector.common_text.options.{text_key}", + default, + ) + + @staticmethod + def _suggestion_keys_to_apply() -> list[str]: + """Return settings keys that can be populated from suggestions.""" + return [ + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SAMPLING_INTERVAL, + CONF_PROFILE_MATCH_INTERVAL, + CONF_AUTO_LABEL_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_MIN_OFF_GAP, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + CONF_END_ENERGY_THRESHOLD, + CONF_RUNNING_DEAD_ZONE, + ] + + @staticmethod + def _format_preview_value(value: Any) -> str: + """Format values for change preview output.""" + if value is None: + return "-" + if isinstance(value, bool): + return "true" if value else "false" + if isinstance(value, int): + return str(value) + if isinstance(value, float): + return str(int(value)) if value.is_integer() else f"{value:.2f}" + return str(value) + + def _count_applicable_suggestions(self, suggestions: dict[str, Any]) -> int: + """Count suggestions that are actually applicable in this options flow.""" + count = 0 + for key in self._suggestion_keys_to_apply(): + entry = suggestions.get(key) + if isinstance(entry, dict) and entry.get("value") is not None: + count += 1 + return count + + async def _options_text(self, text_key: str, default: str) -> str: + """Resolve shared localized text by key from selector namespace.""" + lang = self.context.get("language") or self.hass.config.language + if self._selector_translations is None: + self._selector_translations = await translation.async_get_translations( + self.hass, lang, "selector", {DOMAIN} + ) + + return self._selector_translations.get( + f"component.{DOMAIN}.selector.common_text.options.{text_key}", + default, + ) + + async def async_step_init( + self, user_input: dict[str, Any] | None = None # pylint: disable=unused-argument + ) -> FlowResult: + """Manage the options.""" + self._menu_stack = ["init"] + # Pass menu_options as a list of step ids (not a {step_id: label} dict) + # so the frontend resolves each label in the user's profile language. A + # dict would lock the labels to the server language for every user. The + # pending-feedback count is surfaced inside the learning_feedbacks step + # description rather than on the menu label. + return self.async_show_menu( + step_id="init", + menu_options=[ + "settings", + "notifications", + "manage_cycles", + "manage_profiles", + "manage_phase_catalog", + "record_cycle", + "learning_feedbacks", + "diagnostics", + ], + ) + + async def async_step_settings( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage configuration settings (Basic Step).""" + # Initialize or clear stored basic options + if not hasattr(self, "_basic_options"): + self._basic_options = {} + + if user_input is not None: + # Check if user wants to edit advanced settings + if user_input.get(CONF_SHOW_ADVANCED): + # Store basic input to merge later + self._basic_options = user_input + # Remove the navigation flag from data storage + self._basic_options.pop(CONF_SHOW_ADVANCED, None) + return await self.async_step_advanced_settings() + + # Save Basic Settings Only + # Merge with existing options to preserve settings not shown in this form + user_input.pop(CONF_SHOW_ADVANCED, None) + # Safe merge: Start with data (legacy), override with options, then user input + # This prevents losing settings if options was empty (legacy migration) + merged_options = { + **self.config_entry.data, + **self.config_entry.options, + **user_input, + } + # Drop pump-only keys when the device type is not pump + if merged_options.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE) != DEVICE_TYPE_PUMP: + merged_options.pop(CONF_PUMP_STUCK_DURATION, None) + return self.async_create_entry(title="", data=merged_options) + + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + suggestions = manager.suggestions if manager else {} + suggestions_count = ( + self._count_applicable_suggestions(suggestions) + if isinstance(suggestions, dict) + else 0 + ) + + current_sensor = self.config_entry.options.get( + CONF_POWER_SENSOR, self.config_entry.data.get(CONF_POWER_SENSOR, "") + ) + + def get_val(key, default): + return self.config_entry.options.get( + key, self.config_entry.data.get(key, default) + ) + + # Resolve Device Type for Defaults + current_device_type = self.config_entry.options.get( + CONF_DEVICE_TYPE, + self.config_entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE), + ) + + # Specialized defaults for Device Type + default_off_delay = DEFAULT_OFF_DELAY_BY_DEVICE.get( + current_device_type, DEFAULT_OFF_DELAY + ) + + # Base schema with essential options + schema = { + # --- Device Configuration (Top Priority) --- + vol.Required( + CONF_DEVICE_TYPE, + default=get_val(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE), + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=_device_type_options(current=current_device_type), + mode=selector.SelectSelectorMode.DROPDOWN, + translation_key="device_type", + ) + ), + vol.Optional( + CONF_POWER_SENSOR, + default=current_sensor, + ): selector.EntitySelector(selector.EntitySelectorConfig(domain="sensor")), + # --- Power Thresholds --- + vol.Optional( + CONF_MIN_POWER, + default=get_val(CONF_MIN_POWER, DEFAULT_MIN_POWER), + ): vol.Coerce(float), + + vol.Optional( + CONF_OFF_DELAY, + default=get_val(CONF_OFF_DELAY, default_off_delay), + ): vol.Coerce(int), + vol.Optional(CONF_SHOW_ADVANCED, default=False): selector.BooleanSelector(), + } + + if current_device_type in DEPRECATED_DEVICE_TYPES: + # Conditional flow text cannot be resolved per-user by the frontend + # (it only translates static step descriptions), so this falls back + # to the instance language via _options_text. Deprecated device + # types are removed in 0.4.6 regardless. + warning_template = await self._options_text( + "settings_deprecation_warning", + "⚠️ **Deprecated device type:** {device_type} is scheduled " + "for removal in a future release. WashData's matching pipeline " + "does not produce reliable results for this appliance class. " + "Your integration keeps working through the deprecation period; " + "to silence this warning, switch **Device Type** below to one " + "of the supported types (Washing Machine, Dryer, Washer-Dryer " + "Combo, Dishwasher, Air Fryer, Bread Maker, or Pump), or to " + "**Other (Advanced)** if your appliance does not match any of " + "the supported types. **Other (Advanced)** ships intentionally " + "generic defaults that are not tuned for any specific " + "appliance, so you will need to configure thresholds, " + "timeouts, and matching parameters yourself; all your existing " + "settings are preserved when you switch.", + ) + # Resolve the interpolated device label through the same selector + # translations the rest of the warning uses (_options_text populated + # self._selector_translations above), so the name is localized to + # match instead of falling back to the raw English DEVICE_TYPES value. + current_label = (self._selector_translations or {}).get( + f"component.{DOMAIN}.selector.device_type.options.{current_device_type}", + DEVICE_TYPES.get(current_device_type, current_device_type), + ) + try: + warning = warning_template.format(device_type=current_label) + except (KeyError, IndexError, ValueError): + warning = warning_template + # Trailing blank line lives in code, not the translation string: HA + # rejects translation values with leading/trailing whitespace. + deprecation_warning = f"{warning}\n\n" + else: + deprecation_warning = "" + + return self.async_show_form( + step_id="settings", + data_schema=vol.Schema(schema), + description_placeholders={ + "error": "", + "suggestions_count": str(suggestions_count), + "deprecation_warning": deprecation_warning, + "device": "{device}", + "duration": "{duration}", + "program": "{program}", + "minutes": "{minutes}", + }, + ) + + async def async_step_apply_suggestions_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Review and confirm suggested values before applying them.""" + if not self._suggested_values: + return self.async_abort(reason="no_suggestions") + + if user_input is not None: + if user_input.get("confirm_apply_suggestions"): + # Pass staged values directly as form input — saves immediately + # without bouncing back to Advanced Settings for a second submit. + return await self.async_step_advanced_settings( + user_input=self._suggested_values + ) + + # User declined, clear staged values and return to advanced form. + self._suggested_values = None + self._pending_suggestion_diffs_md = "" + self._pending_suggestion_count = 0 + return await self.async_step_advanced_settings(user_input=None) + + changes_md = self._pending_suggestion_diffs_md or "- No value changes detected." + return self.async_show_form( + step_id="apply_suggestions_confirm", + data_schema=vol.Schema( + { + vol.Required("confirm_apply_suggestions", default=False): selector.BooleanSelector(), + } + ), + description_placeholders={ + "pending_count": str(self._pending_suggestion_count), + "changes": changes_md, + }, + ) + + async def async_step_notifications( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage notification settings.""" + if user_input is not None: + if user_input.pop("go_back", False): + return await self.async_step_init() + # Normalize icon: if the user cleared the field it may be absent or + # empty. Set it explicitly so the merge below overwrites any + # previously-saved value rather than keeping the stale one. + user_input[CONF_NOTIFY_ICON] = user_input.get(CONF_NOTIFY_ICON) or "" + # Normalize channel text fields the same way: a cleared field must + # overwrite any previously-saved channel rather than leaving a stale one. + user_input[CONF_NOTIFY_CHANNEL] = user_input.get(CONF_NOTIFY_CHANNEL) or "" + user_input[CONF_NOTIFY_FINISH_CHANNEL] = ( + user_input.get(CONF_NOTIFY_FINISH_CHANNEL) or "" + ) + # Normalize energy price fields: selectors may omit the key entirely + # when the user clears them. Explicitly writing None ensures the + # merged options dict overrides any previously-stored value. + if user_input.get(CONF_ENERGY_PRICE_ENTITY) in (None, ""): + user_input[CONF_ENERGY_PRICE_ENTITY] = None + if user_input.get(CONF_ENERGY_PRICE_STATIC) in (None, ""): + user_input[CONF_ENERGY_PRICE_STATIC] = None + # Normalize per-event notification service lists: cleared multi-selects + # omit the key entirely, which would leave stale recipients in the merged + # options. Explicitly setting [] ensures the merge overwrites old values. + for _svc_key in ( + CONF_NOTIFY_START_SERVICES, + CONF_NOTIFY_FINISH_SERVICES, + CONF_NOTIFY_LIVE_SERVICES, + ): + if user_input.get(_svc_key) in (None, ""): + user_input[_svc_key] = [] + merged_options = { + **self.config_entry.data, + **self.config_entry.options, + **user_input, + } + # Remove deprecated single-service keys now that per-event lists are saved. + merged_options.pop(CONF_NOTIFY_SERVICE, None) + merged_options.pop(CONF_NOTIFY_EVENTS, None) + # Also remove deprecated keys from entry.data so the manager doesn't fall back to them. + if CONF_NOTIFY_SERVICE in self.config_entry.data or CONF_NOTIFY_EVENTS in self.config_entry.data: + new_data = { + k: v for k, v in self.config_entry.data.items() + if k not in (CONF_NOTIFY_SERVICE, CONF_NOTIFY_EVENTS) + } + self.hass.config_entries.async_update_entry(self.config_entry, data=new_data) + return self.async_create_entry(title="", data=merged_options) + + notify_services: list[str] = sorted( + f"notify.{s}" + for s in self.hass.services.async_services().get("notify", {}) + if s != "send_message" + ) + notify_entities: list[str] = [ + state.entity_id for state in self.hass.states.async_all("notify") + ] + notify_services = sorted(set(notify_services) | set(notify_entities)) + + def get_val(key: str, default: Any) -> Any: + return self.config_entry.options.get( + key, self.config_entry.data.get(key, default) + ) + + # Migrate old single notify_service + notify_events to per-event service lists. + _old_svc: str = get_val(CONF_NOTIFY_SERVICE, "") + _old_events: list[str] = list(get_val(CONF_NOTIFY_EVENTS, []) or []) + + def _migrate_or_load(new_key: str, event_type: str) -> list[str]: + existing = list(get_val(new_key, []) or []) + if existing: + return existing + if _old_svc and (not _old_events or event_type in _old_events): + return [_old_svc] + return [] + + start_services = _migrate_or_load(CONF_NOTIFY_START_SERVICES, NOTIFY_EVENT_START) + finish_services = _migrate_or_load(CONF_NOTIFY_FINISH_SERVICES, NOTIFY_EVENT_FINISH) + live_services = _migrate_or_load(CONF_NOTIFY_LIVE_SERVICES, NOTIFY_EVENT_LIVE) + + # Ensure any already-saved custom service values appear in the dropdown. + known = set(notify_services) + for svc in start_services + finish_services + live_services: + if svc and svc not in known: + notify_services.append(svc) + known.add(svc) + + schema = { + vol.Optional( + CONF_NOTIFY_ACTIONS, + default=get_val(CONF_NOTIFY_ACTIONS, []), + ): selector.ActionSelector(), + vol.Optional( + CONF_NOTIFY_PEOPLE, + default=list(get_val(CONF_NOTIFY_PEOPLE, [])), + ): selector.EntitySelector( + selector.EntitySelectorConfig(domain="person", multiple=True) + ), + vol.Optional( + CONF_NOTIFY_ONLY_WHEN_HOME, + default=get_val(CONF_NOTIFY_ONLY_WHEN_HOME, DEFAULT_NOTIFY_ONLY_WHEN_HOME), + ): selector.BooleanSelector(), + vol.Optional( + CONF_NOTIFY_FIRE_EVENTS, + default=get_val(CONF_NOTIFY_FIRE_EVENTS, DEFAULT_NOTIFY_FIRE_EVENTS), + ): selector.BooleanSelector(), + vol.Optional( + CONF_NOTIFY_START_SERVICES, + default=start_services, + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=notify_services, + multiple=True, + mode=selector.SelectSelectorMode.DROPDOWN, + custom_value=True, + ) + ), + vol.Optional( + CONF_NOTIFY_FINISH_SERVICES, + default=finish_services, + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=notify_services, + multiple=True, + mode=selector.SelectSelectorMode.DROPDOWN, + custom_value=True, + ) + ), + vol.Optional( + CONF_NOTIFY_LIVE_SERVICES, + default=live_services, + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=notify_services, + multiple=True, + mode=selector.SelectSelectorMode.DROPDOWN, + custom_value=True, + ) + ), + vol.Optional( + CONF_NOTIFY_BEFORE_END_MINUTES, + default=get_val( + CONF_NOTIFY_BEFORE_END_MINUTES, DEFAULT_NOTIFY_BEFORE_END_MINUTES + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=60, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + default=get_val( + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=30, + max=1800, + step=30, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + default=get_val( + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=200, + step=5, + unit_of_measurement="%", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_NOTIFY_LIVE_CHRONOMETER, + default=get_val( + CONF_NOTIFY_LIVE_CHRONOMETER, + DEFAULT_NOTIFY_LIVE_CHRONOMETER, + ), + ): selector.BooleanSelector(), + vol.Optional( + CONF_NOTIFY_TITLE, + default=get_val(CONF_NOTIFY_TITLE, DEFAULT_NOTIFY_TITLE), + ): selector.TextSelector(), + vol.Optional( + CONF_NOTIFY_ICON, + description={"suggested_value": get_val(CONF_NOTIFY_ICON, "")}, + ): selector.IconSelector(), + vol.Optional( + CONF_NOTIFY_START_MESSAGE, + default=get_val(CONF_NOTIFY_START_MESSAGE, DEFAULT_NOTIFY_START_MESSAGE), + ): selector.TextSelector(selector.TextSelectorConfig(multiline=True)), + vol.Optional( + CONF_NOTIFY_FINISH_MESSAGE, + default=get_val(CONF_NOTIFY_FINISH_MESSAGE, DEFAULT_NOTIFY_FINISH_MESSAGE), + ): selector.TextSelector(selector.TextSelectorConfig(multiline=True)), + vol.Optional( + CONF_NOTIFY_PRE_COMPLETE_MESSAGE, + default=get_val( + CONF_NOTIFY_PRE_COMPLETE_MESSAGE, + DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE, + ), + ): selector.TextSelector(selector.TextSelectorConfig(multiline=True)), + vol.Optional( + CONF_NOTIFY_REMINDER_MESSAGE, + default=get_val( + CONF_NOTIFY_REMINDER_MESSAGE, + DEFAULT_NOTIFY_REMINDER_MESSAGE, + ), + ): selector.TextSelector(selector.TextSelectorConfig(multiline=True)), + vol.Optional( + CONF_NOTIFY_TIMEOUT_SECONDS, + default=get_val( + CONF_NOTIFY_TIMEOUT_SECONDS, + DEFAULT_NOTIFY_TIMEOUT_SECONDS, + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=86400, + step=1, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_NOTIFY_CHANNEL, + description={"suggested_value": get_val(CONF_NOTIFY_CHANNEL, "")}, + ): selector.TextSelector(), + vol.Optional( + CONF_NOTIFY_FINISH_CHANNEL, + description={"suggested_value": get_val(CONF_NOTIFY_FINISH_CHANNEL, "")}, + ): selector.TextSelector(), + vol.Optional( + CONF_ENERGY_PRICE_ENTITY, + description={"suggested_value": get_val(CONF_ENERGY_PRICE_ENTITY, None)}, + ): selector.EntitySelector( + selector.EntitySelectorConfig( + domain=["sensor", "input_number", "number"], + multiple=False, + ) + ), + vol.Optional( + CONF_ENERGY_PRICE_STATIC, + description={"suggested_value": get_val(CONF_ENERGY_PRICE_STATIC, None)}, + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=10, + step=0.001, + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional("go_back", default=False): selector.BooleanSelector(), + } + + return self.async_show_form( + step_id="notifications", + data_schema=vol.Schema(schema), + description_placeholders={ + "device": "{device}", + "duration": "{duration}", + "program": "{program}", + "minutes": "{minutes}", + "energy_kwh": "{energy_kwh}", + "cost": "{cost}", + }, + ) + + async def async_step_advanced_settings( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage advanced configuration settings (Step 2).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + suggestions = manager.suggestions if manager else {} + + def get_existing_value(key: str, default: Any) -> Any: + if key in self._basic_options: + return self._basic_options[key] + return self.config_entry.options.get( + key, self.config_entry.data.get(key, default) + ) + + if user_input is not None: + # Flatten section-wrapped fields back to a flat dict so the rest of + # the handler can keep treating user_input as {CONF_X: value, ...}. + flat_input: dict[str, Any] = {} + for _k, _v in user_input.items(): + if isinstance(_v, dict): + flat_input.update(_v) + else: + flat_input[_k] = _v + user_input = flat_input + # If "Apply Suggestions" checkbox was checked, merge suggested values into the input + if user_input.get(CONF_APPLY_SUGGESTIONS): + keys_to_apply = self._suggestion_keys_to_apply() + + updated_input = {**user_input} + updated_input[CONF_APPLY_SUGGESTIONS] = False + + applied_count = 0 + diff_lines: list[str] = [] + for key in keys_to_apply: + entry = ( + suggestions.get(key) if isinstance(suggestions, dict) else None + ) + if isinstance(entry, dict) and "value" in entry: + val = entry.get("value") + if val is None: + continue + if key in ( + CONF_OFF_DELAY, + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_PROFILE_MATCH_INTERVAL, + CONF_MIN_OFF_GAP, + CONF_RUNNING_DEAD_ZONE, + ): + suggested_val: Any = int(float(val)) + else: + suggested_val = float(val) + + current_val = user_input.get( + key, + get_existing_value(key, suggested_val), + ) + if current_val != suggested_val: + diff_lines.append( + ( + f"- `{key}`: " + f"`{self._format_preview_value(current_val)}` -> " + f"`{self._format_preview_value(suggested_val)}`" + ) + ) + + updated_input[key] = suggested_val + applied_count += 1 + + if applied_count > 0: + self._suggested_values = updated_input + self._pending_suggestion_count = applied_count + self._pending_suggestion_diffs_md = ( + "\n".join(diff_lines) if diff_lines else "- No value changes detected." + ) + return await self.async_step_apply_suggestions_confirm() + + return self.async_abort(reason="no_suggestions") + + # Ensure clearing CONF_EXTERNAL_END_TRIGGER translates to None/Empty + # Handle missing key, empty list [], empty string "", or None + _trigger_val = user_input.get(CONF_EXTERNAL_END_TRIGGER) + if not _trigger_val: + user_input[CONF_EXTERNAL_END_TRIGGER] = None + + # Same treatment for door sensor entity + _door_val = user_input.get(CONF_DOOR_SENSOR_ENTITY) + if not _door_val: + user_input[CONF_DOOR_SENSOR_ENTITY] = None + + # Normalize a cleared linked device (empty selection) to None so the + # via_device link is removed rather than left dangling. + if CONF_LINKED_DEVICE in user_input and not user_input[CONF_LINKED_DEVICE]: + user_input[CONF_LINKED_DEVICE] = None + + # Same treatment for switch entity + # Only normalize to None when key is present but empty; if the key + # is missing entirely (e.g. the step didn't expose the field) fall + # back to the already-configured value so we don't accidentally clear it. + _switch_val = user_input.get(CONF_SWITCH_ENTITY) + if CONF_SWITCH_ENTITY in user_input and not _switch_val: + user_input[CONF_SWITCH_ENTITY] = None + elif CONF_SWITCH_ENTITY not in user_input: + _existing_switch = self.config_entry.options.get( + CONF_SWITCH_ENTITY, + self.config_entry.data.get(CONF_SWITCH_ENTITY), + ) + if _existing_switch: + user_input[CONF_SWITCH_ENTITY] = _existing_switch + + # Final Save + final_options = { + **self.config_entry.data, + **self.config_entry.options, + **self._basic_options, + **user_input, + } + final_options.pop(CONF_APPLY_SUGGESTIONS, None) + # Drop pump-only keys when the device type is not pump + if final_options.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE) != DEVICE_TYPE_PUMP: + final_options.pop(CONF_PUMP_STUCK_DURATION, None) + if self._suggested_values: + await manager.profile_store.clear_suggestions() + self._suggested_values = None + self._pending_suggestion_diffs_md = "" + self._pending_suggestion_count = 0 + manager.notify_update() + return self.async_create_entry(title="", data=final_options) + + # Helper to get current value + def get_val(key, default): + # Prioritize suggested values (if "Apply Suggestions" triggered a reload) + if self._suggested_values and key in self._suggested_values: + return self._suggested_values[key] + # Fallback to basic options (if coming from basic step) + if key in self._basic_options: + return self._basic_options[key] + # Fallback to config options + return self.config_entry.options.get( + key, self.config_entry.data.get(key, default) + ) + + # Format suggestions for description + def _fmt_suggested(key: str) -> str: + val = ( + (suggestions.get(key) or {}).get("value") + if isinstance(suggestions, dict) + else None + ) + if val is None: + return "-" + try: + return str(int(val)) if float(val).is_integer() else f"{float(val):.2f}" + except Exception: # pylint: disable=broad-exception-caught + return str(val) + + # Issue #257: high-power appliances (well pumps, EV chargers, ovens, + # heat pumps) legitimately need start/stop thresholds far above the + # friendly defaults below. Expand the selector ceiling so it always + # admits the currently-saved value and any pending suggestion - + # otherwise the form rejects a value the integration itself produced + # ("Value is too large for dictionary value"). + def _threshold_cap(base: float, key: str, current_default: float) -> float: + cap = base + candidates: list[Any] = [get_val(key, current_default)] + if isinstance(suggestions, dict): + candidates.append((suggestions.get(key) or {}).get("value")) + for cand in candidates: + try: + cval = float(cand) + except (TypeError, ValueError): + continue + if cval > cap: + # Round up to the next clean 100 W so the BOX control keeps + # a tidy bound with a little headroom above the value. + cap = float((int(cval) // 100 + 1) * 100) + return cap + + _min_power_val = float(get_val(CONF_MIN_POWER, DEFAULT_MIN_POWER)) + _start_threshold_cap = _threshold_cap( + 500.0, CONF_START_THRESHOLD_W, _min_power_val + 1.0 + ) + _stop_threshold_cap = _threshold_cap( + 100.0, CONF_STOP_THRESHOLD_W, max(0.0, _min_power_val - 0.5) + ) + + reason_lines: list[str] = [] + for key in [ + CONF_MIN_POWER, + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SAMPLING_INTERVAL, + CONF_PROFILE_MATCH_INTERVAL, + CONF_DURATION_TOLERANCE, + CONF_PROFILE_DURATION_TOLERANCE, + ]: + entry = suggestions.get(key) if isinstance(suggestions, dict) else None + if isinstance(entry, dict) and entry.get("reason"): + reason_lines.append(f"- {key}: {entry['reason']}") + suggested_reason = "\n".join(reason_lines) if reason_lines else "" + + # Resolve Device Type for Defaults + current_device_type = self.config_entry.options.get( + CONF_DEVICE_TYPE, + self.config_entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE), + ) + if CONF_DEVICE_TYPE in self._basic_options: + current_device_type = self._basic_options[CONF_DEVICE_TYPE] + + # Device-specific defaults + _default_min_off_gap = DEFAULT_MIN_OFF_GAP_BY_DEVICE.get( + current_device_type, DEFAULT_MIN_OFF_GAP + ) + default_start_energy = DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE.get( + current_device_type, DEFAULT_START_ENERGY_THRESHOLD + ) + default_completion_min = DEVICE_COMPLETION_THRESHOLDS.get( + current_device_type, DEFAULT_COMPLETION_MIN_SECONDS + ) + + default_sampling = DEFAULT_SAMPLING_INTERVAL_BY_DEVICE.get( + current_device_type, DEFAULT_SAMPLING_INTERVAL + ) + + # Specialized defaults for Device Type + default_no_update_timeout = DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE.get( + current_device_type, DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT + ) + + default_min_duration_ratio = DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE.get( + current_device_type, DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO + ) + + detection_schema = { + vol.Optional( + CONF_START_DURATION_THRESHOLD, + default=get_val( + CONF_START_DURATION_THRESHOLD, DEFAULT_START_DURATION_THRESHOLD + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, + max=60.0, + step=0.5, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_START_ENERGY_THRESHOLD, + default=get_val(CONF_START_ENERGY_THRESHOLD, default_start_energy), + ): vol.Coerce(float), + vol.Optional( + CONF_COMPLETION_MIN_SECONDS, + default=get_val(CONF_COMPLETION_MIN_SECONDS, default_completion_min), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=3600, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_START_THRESHOLD_W, + default=get_val( + CONF_START_THRESHOLD_W, + float(get_val(CONF_MIN_POWER, DEFAULT_MIN_POWER)) + 1.0, + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, + # Issue #238: dryers with anti-damp/anti-crease tumbling + # can sit at 200-300 W during a delayed-start window. + # Issue #257: ceiling expands for high-power devices so a + # suggested/saved value above the default is never rejected. + max=_start_threshold_cap, + step=0.5, + unit_of_measurement="W", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_STOP_THRESHOLD_W, + default=get_val( + CONF_STOP_THRESHOLD_W, + max(0.0, float(get_val(CONF_MIN_POWER, DEFAULT_MIN_POWER)) - 0.5), + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, + # Issue #257: ceiling expands for high-power devices so a + # suggested/saved value above the default is never rejected. + max=_stop_threshold_cap, + step=0.5, + unit_of_measurement="W", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_END_ENERGY_THRESHOLD, + default=get_val( + CONF_END_ENERGY_THRESHOLD, DEFAULT_END_ENERGY_THRESHOLD + ), + ): vol.Coerce(float), + vol.Optional( + CONF_RUNNING_DEAD_ZONE, + default=get_val(CONF_RUNNING_DEAD_ZONE, DEFAULT_RUNNING_DEAD_ZONE), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=600, + step=10, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_END_REPEAT_COUNT, + default=get_val(CONF_END_REPEAT_COUNT, DEFAULT_END_REPEAT_COUNT), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, max=10, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_MIN_OFF_GAP, + default=get_val(CONF_MIN_OFF_GAP, _default_min_off_gap), + ): vol.Coerce(int), + vol.Optional( + CONF_SAMPLING_INTERVAL, + default=get_val(CONF_SAMPLING_INTERVAL, default_sampling), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1.0, + max=60.0, + step=0.5, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + + matching_schema = { + vol.Optional( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + default=get_val( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + default_min_duration_ratio, + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.1, max=1.0, step=0.01, mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_PROFILE_MATCH_INTERVAL, + default=get_val( + CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL + ), + ): vol.Coerce(int), + vol.Optional( + CONF_PROFILE_MATCH_THRESHOLD, + default=get_val( + CONF_PROFILE_MATCH_THRESHOLD, DEFAULT_PROFILE_MATCH_THRESHOLD + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.1, max=1.0, step=0.05, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_PROFILE_UNMATCH_THRESHOLD, + default=get_val( + CONF_PROFILE_UNMATCH_THRESHOLD, DEFAULT_PROFILE_UNMATCH_THRESHOLD + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.1, max=1.0, step=0.05, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_AUTO_LABEL_CONFIDENCE, + default=get_val(CONF_AUTO_LABEL_CONFIDENCE, DEFAULT_AUTO_LABEL_CONFIDENCE), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.5, max=1.0, step=0.05, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_LEARNING_CONFIDENCE, + default=get_val(CONF_LEARNING_CONFIDENCE, DEFAULT_LEARNING_CONFIDENCE), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, max=1.0, step=0.05, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS, + default=get_val( + CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS, + DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS, + ), + ): selector.BooleanSelector(), + vol.Optional( + CONF_DURATION_TOLERANCE, + default=get_val(CONF_DURATION_TOLERANCE, DEFAULT_DURATION_TOLERANCE), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, max=0.5, step=0.01, mode=selector.NumberSelectorMode.BOX + ) + ), + vol.Optional( + CONF_SMOOTHING_WINDOW, + default=get_val(CONF_SMOOTHING_WINDOW, DEFAULT_SMOOTHING_WINDOW), + ): vol.Coerce(int), + vol.Optional( + CONF_PROFILE_DURATION_TOLERANCE, + default=get_val( + CONF_PROFILE_DURATION_TOLERANCE, DEFAULT_PROFILE_DURATION_TOLERANCE + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, max=0.5, step=0.01, mode=selector.NumberSelectorMode.BOX + ) + ), + } + + timing_schema = { + vol.Optional( + CONF_WATCHDOG_INTERVAL, + default=get_val(CONF_WATCHDOG_INTERVAL, DEFAULT_WATCHDOG_INTERVAL), + ): vol.Coerce(int), + vol.Optional( + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + default=get_val(CONF_NO_UPDATE_ACTIVE_TIMEOUT, default_no_update_timeout), + ): vol.Coerce(int), + vol.Optional( + CONF_PROGRESS_RESET_DELAY, + default=get_val( + CONF_PROGRESS_RESET_DELAY, DEFAULT_PROGRESS_RESET_DELAY + ), + ): vol.Coerce(int), + vol.Optional( + CONF_AUTO_MAINTENANCE, + default=get_val(CONF_AUTO_MAINTENANCE, DEFAULT_AUTO_MAINTENANCE), + ): selector.BooleanSelector(), + vol.Optional( + CONF_EXPOSE_DEBUG_ENTITIES, + default=get_val(CONF_EXPOSE_DEBUG_ENTITIES, False), + ): selector.BooleanSelector(), + vol.Optional( + CONF_SAVE_DEBUG_TRACES, default=get_val(CONF_SAVE_DEBUG_TRACES, False) + ): selector.BooleanSelector(), + } + + anti_wrinkle_schema = { + vol.Optional( + CONF_ANTI_WRINKLE_ENABLED, + default=get_val( + CONF_ANTI_WRINKLE_ENABLED, DEFAULT_ANTI_WRINKLE_ENABLED + ), + ): selector.BooleanSelector(), + vol.Optional( + CONF_ANTI_WRINKLE_MAX_POWER, + default=get_val( + CONF_ANTI_WRINKLE_MAX_POWER, DEFAULT_ANTI_WRINKLE_MAX_POWER + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=10.0, + max=2000.0, + step=10.0, + unit_of_measurement="W", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_ANTI_WRINKLE_MAX_DURATION, + default=get_val( + CONF_ANTI_WRINKLE_MAX_DURATION, DEFAULT_ANTI_WRINKLE_MAX_DURATION + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=5.0, + max=600.0, + step=1.0, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_ANTI_WRINKLE_EXIT_POWER, + default=get_val( + CONF_ANTI_WRINKLE_EXIT_POWER, DEFAULT_ANTI_WRINKLE_EXIT_POWER + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.0, + max=10.0, + step=0.1, + unit_of_measurement="W", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + + delay_start_schema = { + vol.Optional( + CONF_DELAY_START_DETECT_ENABLED, + default=get_val( + CONF_DELAY_START_DETECT_ENABLED, DEFAULT_DELAY_START_DETECT_ENABLED + ), + ): selector.BooleanSelector(), + vol.Optional( + CONF_DELAY_CONFIRM_SECONDS, + default=get_val( + CONF_DELAY_CONFIRM_SECONDS, DEFAULT_DELAY_CONFIRM_SECONDS + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=10.0, + max=600.0, + step=5.0, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ), + vol.Optional( + CONF_DELAY_TIMEOUT_HOURS, + default=get_val( + CONF_DELAY_TIMEOUT_HOURS, DEFAULT_DELAY_TIMEOUT_HOURS + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.5, + max=24.0, + step=0.5, + unit_of_measurement="h", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + + external_triggers_schema = { + vol.Optional( + CONF_EXTERNAL_END_TRIGGER_ENABLED, + default=get_val(CONF_EXTERNAL_END_TRIGGER_ENABLED, False), + ): selector.BooleanSelector(), + vol.Optional( + CONF_EXTERNAL_END_TRIGGER, + description={"suggested_value": get_val(CONF_EXTERNAL_END_TRIGGER, None)}, + ): selector.EntitySelector( + selector.EntitySelectorConfig( + domain="binary_sensor", + multiple=False, + ) + ), + vol.Optional( + CONF_EXTERNAL_END_TRIGGER_INVERTED, + default=get_val(CONF_EXTERNAL_END_TRIGGER_INVERTED, False), + ): selector.BooleanSelector(), + vol.Optional( + CONF_DOOR_SENSOR_ENTITY, + description={"suggested_value": get_val(CONF_DOOR_SENSOR_ENTITY, None)}, + ): selector.EntitySelector( + selector.EntitySelectorConfig( + domain="binary_sensor", + multiple=False, + ) + ), + vol.Optional( + CONF_PAUSE_CUTS_POWER, + default=get_val(CONF_PAUSE_CUTS_POWER, False), + ): selector.BooleanSelector(), + vol.Optional( + CONF_SWITCH_ENTITY, + description={"suggested_value": get_val(CONF_SWITCH_ENTITY, None)}, + ): selector.EntitySelector( + selector.EntitySelectorConfig( + domain="switch", + multiple=False, + ) + ), + vol.Optional( + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, + default=get_val( + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES + ), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=480, + step=5, + unit_of_measurement="min", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + + suggestions_schema = { + vol.Optional(CONF_APPLY_SUGGESTIONS, default=False): selector.BooleanSelector(), + } + + device_link_schema = { + vol.Optional( + CONF_LINKED_DEVICE, + description={"suggested_value": get_val(CONF_LINKED_DEVICE, None)}, + ): selector.DeviceSelector(selector.DeviceSelectorConfig()), + } + + schema: dict[Any, Any] = { + vol.Required("suggestions_section"): section( + vol.Schema(suggestions_schema), {"collapsed": False} + ), + vol.Required("detection_section"): section( + vol.Schema(detection_schema), {"collapsed": False} + ), + vol.Required("matching_section"): section( + vol.Schema(matching_schema), {"collapsed": True} + ), + vol.Required("timing_section"): section( + vol.Schema(timing_schema), {"collapsed": True} + ), + vol.Required("anti_wrinkle_section"): section( + vol.Schema(anti_wrinkle_schema), {"collapsed": True} + ), + vol.Required("delay_start_section"): section( + vol.Schema(delay_start_schema), {"collapsed": True} + ), + vol.Required("external_triggers_section"): section( + vol.Schema(external_triggers_schema), {"collapsed": True} + ), + vol.Required("device_link_section"): section( + vol.Schema(device_link_schema), {"collapsed": True} + ), + } + + # --- Pump Monitor (Pump / Sump Pump only) --- + if current_device_type == DEVICE_TYPE_PUMP: + pump_schema = { + vol.Optional( + CONF_PUMP_STUCK_DURATION, + default=get_val(CONF_PUMP_STUCK_DURATION, DEFAULT_PUMP_STUCK_DURATION), + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=60, + max=86400, + step=60, + unit_of_measurement="s", + mode=selector.NumberSelectorMode.BOX, + ) + ) + } + schema[vol.Required("pump_section")] = section( + vol.Schema(pump_schema), {"collapsed": True} + ) + + data_schema = vol.Schema(schema) + + return self.async_show_form( + step_id="advanced_settings", + data_schema=data_schema, + description_placeholders={ + "error": "", + "suggestions_count": str( + self._count_applicable_suggestions(suggestions) + if isinstance(suggestions, dict) + else 0 + ), + "suggested": suggested_reason or "No suggestions available yet.", + "suggested_min_power": _fmt_suggested(CONF_MIN_POWER), + "suggested_off_delay": _fmt_suggested(CONF_OFF_DELAY), + "suggested_watchdog_interval": _fmt_suggested(CONF_WATCHDOG_INTERVAL), + "suggested_no_update_active_timeout": _fmt_suggested( + CONF_NO_UPDATE_ACTIVE_TIMEOUT + ), + "suggested_sampling_interval": _fmt_suggested( + CONF_SAMPLING_INTERVAL + ), + "suggested_profile_match_interval": _fmt_suggested( + CONF_PROFILE_MATCH_INTERVAL + ), + "suggested_auto_label_confidence": _fmt_suggested( + CONF_AUTO_LABEL_CONFIDENCE + ), + "suggested_duration_tolerance": _fmt_suggested(CONF_DURATION_TOLERANCE), + "suggested_profile_duration_tolerance": _fmt_suggested( + CONF_PROFILE_DURATION_TOLERANCE + ), + "suggested_profile_match_min_duration_ratio": _fmt_suggested( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO + ), + "suggested_profile_match_max_duration_ratio": _fmt_suggested( + CONF_PROFILE_MATCH_MAX_DURATION_RATIO + ), + "suggested_start_threshold_w": _fmt_suggested(CONF_START_THRESHOLD_W), + "suggested_stop_threshold_w": _fmt_suggested(CONF_STOP_THRESHOLD_W), + "suggested_end_energy_threshold": _fmt_suggested(CONF_END_ENERGY_THRESHOLD), + "suggested_running_dead_zone": _fmt_suggested(CONF_RUNNING_DEAD_ZONE), + + "suggested_reason": suggested_reason, + # Placeholders for keys in data_description + "device": "{device}", + "duration": "{duration}", + "program": "{program}", + "minutes": "{minutes}", + }, + ) + + + + + # --- Interactive Editor Steps --- + + async def async_step_interactive_editor( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step 1: Select Action (Merge or Split) as a button menu.""" + self._editor_action = None + self._editor_selected_ids = [] + self._editor_split_gap = 900 + self._editor_split_mode = "auto" + self._editor_split_manual_segments = [] + self._push_menu("interactive_editor") + return self.async_show_menu( + step_id="interactive_editor", + menu_options=[ + "editor_split", + "editor_merge", + "editor_delete", + "menu_back", + ], + ) + + async def async_step_editor_split( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: split action.""" + self._editor_action = "split" + return await self.async_step_editor_select() + + async def async_step_editor_merge( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: merge action.""" + self._editor_action = "merge" + return await self.async_step_editor_select() + + async def async_step_editor_delete( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: delete action.""" + self._editor_action = "delete" + return await self.async_step_editor_select() + + async def async_step_editor_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step 2: Select Cycles.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + errors = {} + + if user_input is not None: + selected = user_input.get("selected_cycles", []) + self._editor_selected_ids = selected + + # Validation + if self._editor_action == "split": + if len(selected) != 1: + errors["base"] = "select_exactly_one" + else: + return await self.async_step_editor_split_params() + + elif self._editor_action == "merge": + if len(selected) < 2: + errors["base"] = "select_at_least_two" + else: + return await self.async_step_editor_configure() + + elif self._editor_action == "delete": + if len(selected) < 1: + errors["base"] = "select_at_least_one" + else: + return await self.async_step_editor_configure() + + # Build options (Recent 50 cycles) + cycles = store.get_past_cycles()[-50:] + cycles.sort(key=lambda x: x["start_time"], reverse=True) + + unlabeled_text = await self._selector_text("unlabeled", "(Unlabeled)") + options = [] + for c in cycles: + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + prof = c.get("profile_name") or unlabeled_text + label = f"{start} - {duration_min}m - {prof}" + options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + info_text_key = { + "split": "editor_select_info_split", + "merge": "editor_select_info_merge", + "delete": "editor_select_info_delete", + }.get(self._editor_action or "", "editor_select_info") + + return self.async_show_form( + step_id="editor_select", + data_schema=vol.Schema( + { + vol.Required("selected_cycles"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.LIST, + multiple=True + ) + ) + } + ), + errors=errors, + description_placeholders={ + "info_text": await self._options_text(info_text_key, "") + } + ) + + async def async_step_editor_split_params( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step 2.5: Choose split method (auto-detect gaps or manual timestamps).""" + if user_input is not None: + mode = user_input.get("split_mode", "auto") + self._editor_split_mode = mode + self._editor_split_manual_segments = [] + if mode == "manual": + return await self.async_step_editor_split_manual_params() + return await self.async_step_editor_split_auto_params() + + return self.async_show_form( + step_id="editor_split_params", + data_schema=vol.Schema({ + vol.Required("split_mode", default=self._editor_split_mode): self._translated_select( + options=["auto", "manual"], + translation_key="split_mode", + ) + }), + ) + + async def async_step_editor_split_auto_params( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure auto-detect gap threshold.""" + if user_input is not None: + self._editor_split_gap = int(user_input["min_gap_seconds"]) + return await self.async_step_editor_configure() + + return self.async_show_form( + step_id="editor_split_auto_params", + data_schema=vol.Schema({ + vol.Required("min_gap_seconds", default=self._editor_split_gap): selector.NumberSelector( + selector.NumberSelectorConfig( + min=60, max=3600, mode=selector.NumberSelectorMode.BOX, unit_of_measurement="s" + ) + ) + }), + ) + + async def async_step_editor_split_manual_params( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Configure manual split timestamps (wall-clock HH:MM[:SS], one per line).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + errors: dict[str, str] = {} + cid = self._editor_selected_ids[0] if self._editor_selected_ids else None + cycle = next((c for c in store.get_past_cycles() if c["id"] == cid), None) if cid else None + if not cycle: + return self.async_abort(reason="cycle_not_found") + + cycle_start_dt = dt_util.parse_datetime(cycle["start_time"]) + cycle_end_dt = dt_util.parse_datetime(cycle["end_time"]) + if not cycle_start_dt or not cycle_end_dt: + return self.async_abort(reason="cycle_not_found") + + if user_input is not None: + raw = user_input.get("split_timestamps", "") or "" + offsets: list[float] = [] + for token in raw.replace(",", "\n").splitlines(): + token = token.strip() + if not token: + continue + off = self._wallclock_to_offset(token, cycle_start_dt, cycle_end_dt) + if off is None: + errors["base"] = "invalid_split_timestamp" + break + offsets.append(off) + + if not errors: + segments = store.build_split_segments_from_offsets(cycle, offsets) + if not segments: + errors["base"] = "no_split_segments_found" + else: + self._editor_split_manual_segments = segments + return await self.async_step_editor_configure() + + local_start = dt_util.as_local(cycle_start_dt) + local_end = dt_util.as_local(cycle_end_dt) + preview_md = "" + svg = store.generate_interactive_split_svg( + cycle["id"], + [(0.0, (cycle_end_dt - cycle_start_dt).total_seconds())], + title_prefix=await self._options_text("split_preview_title", "Split Preview"), + unlabeled_text=await self._selector_text("unlabeled", "(Unlabeled)"), + ) + if svg: + b64 = base64.b64encode(svg.encode("utf-8")).decode("utf-8") + preview_md = f"![Preview](data:image/svg+xml;base64,{b64})\n\n" + + return self.async_show_form( + step_id="editor_split_manual_params", + data_schema=vol.Schema({ + vol.Required("split_timestamps", default=""): selector.TextSelector( + selector.TextSelectorConfig(multiline=True) + ) + }), + errors=errors, + description_placeholders={ + "preview_md": preview_md, + "cycle_start_wallclock": local_start.strftime("%H:%M:%S"), + "cycle_end_wallclock": local_end.strftime("%H:%M:%S"), + "cycle_date": local_start.strftime("%Y-%m-%d"), + }, + ) + + async def async_step_editor_configure( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """ + Render the interactive editor preview and apply split or merge actions for selected cycles. + + If called with `user_input`, executes the chosen editor action ("split" or "merge") when the user confirms: + - For "split": analyzes the selected cycle into segments, optionally assigns profiles from per-segment inputs, applies the split, and triggers background envelope rebuild. + - For "merge": optionally creates a new profile, applies the merge with the chosen profile (or unlabeled), and triggers background envelope rebuild. + + When called without `user_input`, prepares a preview form: + - For "split": runs split analysis, generates an SVG preview, and presents per-segment profile selectors plus a confirmation checkbox (`confirm_commit`). + - For "merge": generates a merge preview (SVG or fallback message), presents a profile selector (`merged_profile` or `create_new`), an optional `new_profile_name`, and a confirmation checkbox (`confirm_commit`). + + Parameters: + user_input (dict[str, Any] | None): Submitted form values when committing an action. Expected keys vary by action: + - Split preview commit: + - "confirm_commit" (bool): required to execute the split. + - "segment_{i}_profile" (str): optional per-segment profile name or "none". + - Merge preview commit: + - "confirm_commit" (bool): required to execute the merge. + - "merged_profile" (str): one of profile names, "none", or "create_new". + - "new_profile_name" (str): when "create_new" is selected, name for the new profile. + + Returns: + FlowResult: A flow result that either shows the preview form, creates an entry after applying changes, or aborts with an error if required data (e.g., cycles or segments) is missing. + """ + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if user_input is not None: + # Execute + if self._editor_action == "split": + # User selected action "apply" + if user_input.get("confirm_commit"): + # We need the segments config. + # For simplicity, we auto-apply the analysis results for now, + # as complex per-segment assignment in one form is hard in HA config flow. + # We'll rely on auto-labeling or user can label later. + + # Re-run analysis (auto) or reuse manual segments + cycle = next((c for c in store.get_past_cycles() if c["id"] in self._editor_selected_ids), None) + if cycle: + if self._editor_split_mode == "manual": + segments = list(self._editor_split_manual_segments) + else: + segments = await self.hass.async_add_executor_job( + store.analyze_split_sync, cycle, self._editor_split_gap, 2.0 + ) + if segments: + # Apply with profiles from user input + final_segments = [] + for i, seg in enumerate(segments): + start_t, end_t = seg + prof = user_input.get(f"segment_{i}_profile") + if prof == "none": + prof = None + final_segments.append({"start": start_t, "end": end_t, "profile": prof}) + + await store.apply_split_interactive(cycle["id"], final_segments) + # Maintenance: Reprocess envelopes in background to avoid blocking UI + self.hass.async_create_task(store.async_rebuild_all_envelopes()) + + return self.async_create_entry(title="", data=dict(self.config_entry.options)) + + elif self._editor_action == "merge": + if user_input.get("confirm_commit"): + target_profile = user_input.get("merged_profile") + + if target_profile == "create_new": + new_name = user_input.get("new_profile_name", "").strip() + if new_name: + # Create profile + try: + await store.create_profile_standalone(new_name) + target_profile = new_name + except ValueError: + # Profile exists or other error, fallback to unlabel/existing behavior + pass + + if target_profile in ("none", "create_new"): + target_profile = None + + await store.apply_merge_interactive(self._editor_selected_ids, target_profile) + # Maintenance: Reprocess envelopes in background + self.hass.async_create_task(store.async_rebuild_all_envelopes()) + return self.async_create_entry(title="", data=dict(self.config_entry.options)) + + elif self._editor_action == "delete": + if user_input.get("confirm_commit"): + deleted_any = False + for cycle_id in self._editor_selected_ids: + deleted_any = await store.delete_cycle(cycle_id) or deleted_any + + if deleted_any: + manager.notify_update() + self.hass.async_create_task(store.async_rebuild_all_envelopes()) + + return self.async_create_entry(title="", data=dict(self.config_entry.options)) + + # Generate Preview + preview_md = "" + schema = {} + + if self._editor_action == "split": + cid = self._editor_selected_ids[0] + cycle = next((c for c in store.get_past_cycles() if c["id"] == cid), None) + if not cycle: + return self.async_abort(reason="cycle_not_found") + + # Run analysis (auto) or reuse manual segments + if self._editor_split_mode == "manual": + segments = list(self._editor_split_manual_segments) + else: + segments = await self.hass.async_add_executor_job( + store.analyze_split_sync, cycle, self._editor_split_gap, 2.0 + ) + + if not segments: + return self.async_abort(reason="no_split_segments_found") + + # Generate SVG + split_title = await self._options_text("split_preview_title", "Split Preview") + unlabeled_text = await self._selector_text("unlabeled", "(Unlabeled)") + svg = store.generate_interactive_split_svg( + cycle["id"], + segments, + title_prefix=split_title, + unlabeled_text=unlabeled_text, + ) + b64 = base64.b64encode(svg.encode("utf-8")).decode("utf-8") + + split_found_fmt = await self._options_text( + "split_preview_found_fmt", "Found {count} segments." + ) + split_confirm_fmt = await self._options_text( + "split_preview_confirm_fmt", + "Click Confirm to split this cycle into {count} separate cycles.", + ) + preview_md = f""" +### {split_title} +{split_found_fmt.format(count=len(segments))} +![Preview](data:image/svg+xml;base64,{b64}) + +{split_confirm_fmt.format(count=len(segments))} +""" + schema: dict[Any, Any] = {vol.Required("confirm_commit"): bool} + + # Add profile pickers for each segment + profiles = store.list_profiles() + prof_options = [ + selector.SelectOptionDict( + value="none", + label=await self._selector_text("unlabeled", "(Unlabeled)"), + ) + ] + for p in profiles: + prof_options.append(selector.SelectOptionDict(value=p["name"], label=p["name"])) + + for i, seg in enumerate(segments): + # seg_dur = int(seg[1] - seg[0]) + # label = f"New Cycle {i+1} ({seg_dur}s)" # Unused currently + schema[vol.Optional(f"segment_{i}_profile", default="none")] = selector.SelectSelector( + selector.SelectSelectorConfig(options=prof_options, mode=selector.SelectSelectorMode.DROPDOWN, custom_value=False) + ) + + elif self._editor_action == "merge": + # Get cycles + cycles_to_merge = [c for c in store.get_past_cycles() if c["id"] in self._editor_selected_ids] + cycles_to_merge.sort(key=lambda x: x["start_time"]) + + # Generate SVG + merge_title = await self._options_text("merge_preview_title", "Merge Preview") + no_power_label = await self._options_text( + "no_power_preview", "No power data available for preview" + ) + svg = store.generate_interactive_merge_svg( + [c["id"] for c in cycles_to_merge], + title=merge_title, + no_data_label=no_power_label, + ) + + # Profile Selector + profiles = store.list_profiles() + prof_options = [ + selector.SelectOptionDict( + value="create_new", + label=await self._selector_text( + "create_new_profile", "Create New Profile..." + ), + ), + selector.SelectOptionDict( + value="none", + label=await self._selector_text("unlabeled", "(Unlabeled)"), + ), + ] + for p in profiles: + prof_options.append(selector.SelectOptionDict(value=p["name"], label=p["name"])) + + # Guess best profile? + default_prof = cycles_to_merge[0].get("profile_name") or "none" + + merge_joining_fmt = await self._options_text( + "merge_preview_joining_fmt", + "Joining {count} cycles. Gaps will be filled with 0W readings.", + ) + if svg: + b64 = base64.b64encode(svg.encode("utf-8")).decode("utf-8") + graph_line = f"![Preview](data:image/svg+xml;base64,{b64})" + else: + graph_line = await self._options_text( + "no_power_preview", "*No power data available for preview.*" + ) + preview_md = f""" +### {merge_title} +{merge_joining_fmt.format(count=len(cycles_to_merge))} +{graph_line} +""" + schema = { + vol.Optional("merged_profile", default=default_prof): selector.SelectSelector( + selector.SelectSelectorConfig(options=prof_options, mode=selector.SelectSelectorMode.DROPDOWN) + ), + vol.Optional("new_profile_name"): str, + vol.Required("confirm_commit"): selector.BooleanSelector() + } + + elif self._editor_action == "delete": + cycles_to_delete = [ + c for c in store.get_past_cycles() if c["id"] in self._editor_selected_ids + ] + cycles_to_delete.sort(key=lambda x: x["start_time"], reverse=True) + + delete_title = await self._options_text( + "editor_delete_preview_title", "Delete Preview" + ) + delete_intro = await self._options_text( + "editor_delete_preview_intro", + "The selected cycles will be permanently deleted:", + ) + delete_confirm = await self._options_text( + "editor_delete_preview_confirm", + "Click Confirm to permanently delete these cycle records.", + ) + unlabeled_text = await self._selector_text("unlabeled", "(Unlabeled)") + + delete_rows: list[str] = [] + for cycle in cycles_to_delete: + dt = dt_util.parse_datetime(cycle["start_time"]) + when = ( + dt_util.as_local(dt).strftime("%b %d, %H:%M") + if dt + else str(cycle["start_time"]) + ) + duration = _format_duration_label( + int(cycle.get("manual_duration", cycle["duration"])) + ) + prof = cycle.get("profile_name") or unlabeled_text + delete_rows.append( + "- " + + f"{html.escape(str(when))} | {html.escape(duration)} | {html.escape(str(prof))}" + ) + + preview_md = "\n".join( + [ + f"### {delete_title}", + delete_intro, + *delete_rows, + "", + delete_confirm, + ] + ) + schema = {vol.Required("confirm_commit"): selector.BooleanSelector()} + + return self.async_show_form( + step_id="editor_configure", + data_schema=vol.Schema(schema), + description_placeholders={"preview_md": preview_md} + ) + + + + async def async_step_diagnostics( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Diagnostics submenu as a button menu with storage stats.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + stats = await manager.profile_store.get_storage_stats() + + self._push_menu("diagnostics") + return self.async_show_menu( + step_id="diagnostics", + menu_options=[ + "reprocess_history", + "clear_debug_data", + "wipe_history", + "export_import", + "menu_back", + ], + description_placeholders={ + "file_size_kb": f"{stats.get('file_size_kb', 0):.1f}", + "cycle_count": str(stats.get('total_cycles', 0)), + "profile_count": str(stats.get('total_profiles', 0)), + "debug_count": str(stats.get('debug_traces_count', 0)), + }, + ) + + async def async_step_clear_debug_data( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm clearing debug data.""" + if user_input is not None: + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + count = await manager.profile_store.async_clear_debug_data() + return self.async_abort( + reason="debug_data_cleared", + description_placeholders={"count": str(count)}, + ) + + return self.async_show_form( + step_id="clear_debug_data", description_placeholders={} + ) + + async def async_step_reprocess_history( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle request to reprocess all history.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + + if user_input is not None: + # Execute + count = await manager.profile_store.async_reprocess_all_data() + return self.async_abort( + reason="reprocess_success", + description_placeholders={"count": str(count)}, + ) + + return self.async_show_form( + step_id="reprocess_history", + data_schema=vol.Schema({}), + ) + + async def async_step_export_import( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Export or import profile/cycle data via JSON copy/paste.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + export_payload = manager.profile_store.export_data( + entry_data=dict(self.config_entry.data), + entry_options=dict(self.config_entry.options), + ) + export_str = json.dumps(export_payload, indent=2) + + errors: dict[str, str] = {} + + if user_input is not None: + mode = user_input.get("mode", "export") + payload_str = user_input.get("json_payload", "") + + # Always preserve existing options unless we explicitly update them + options_to_return = dict(self.config_entry.options) + + if mode == "import": + try: + payload = json.loads(payload_str) + config_updates = await manager.profile_store.async_import_data( + payload + ) + + # Apply imported settings to config entry if present + entry_data = config_updates.get("entry_data", {}) + entry_options = config_updates.get("entry_options", {}) + + if entry_data or entry_options: + # Merge imported options with current data/options + new_data = {**self.config_entry.data} + new_options = {**self.config_entry.options} + + # Only update settings that exist in the import + # (don't overwrite power_sensor/name) + for key in [CONF_MIN_POWER, CONF_OFF_DELAY]: + if key in entry_data: + new_data[key] = entry_data[key] + + # Update all options from import + new_options.update(entry_options) + + self.hass.config_entries.async_update_entry( + self.config_entry, + data=new_data, + options=new_options, + ) + _LOGGER.info("Applied imported settings to config entry") + + # Return the merged options so the options flow itself doesn't revert them + options_to_return = dict(new_options) + + except Exception: # noqa: BLE001, pylint: disable=broad-exception-caught + errors["base"] = "import_failed" + # Re-show form with error + return self.async_show_form( + step_id="export_import", + data_schema=vol.Schema( + { + vol.Required( + "mode", default=mode + ): self._translated_select( + options=["export", "import"], + translation_key="export_import_mode", + ), + vol.Optional( + "json_payload", default=payload_str + ): selector.TextSelector( + selector.TextSelectorConfig(multiline=True) + ), + } + ), + errors=errors, + ) + + return self.async_create_entry(title="", data=options_to_return) + + return self.async_show_form( + step_id="export_import", + data_schema=vol.Schema( + { + vol.Required("mode", default="export"): self._translated_select( + options=["export", "import"], + translation_key="export_import_mode", + ), + vol.Optional( + "json_payload", default=export_str + ): selector.TextSelector( + selector.TextSelectorConfig(multiline=True) + ), + } + ), + ) + + async def async_step_manage_cycles( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage cycles submenu (button menu with card-style preview).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + unlabeled_text = await self._selector_text("unlabeled", "(Unlabeled)") + + recent_cycles = store.get_past_cycles()[-8:] + rows: list[str] = [] + for c in reversed(recent_cycles): + dt = dt_util.parse_datetime(c["start_time"]) + when = ( + dt_util.as_local(dt).strftime("%b %d, %H:%M") + if dt + else str(c["start_time"]) + ) + duration_s = int(c.get("manual_duration", c["duration"])) + duration_str = _format_duration_label(duration_s) + prof = c.get("profile_name") or unlabeled_text + safe_prof = html.escape(str(prof)) + safe_when = html.escape(str(when)) + safe_duration = html.escape(duration_str) + status = c.get("status", "completed") + status_icon = ( + "✓" + if status in ("completed", "force_stopped") + else "⚠" if status == "resumed" else "✗" + ) + conf = c.get("match_confidence") + if isinstance(conf, (int, float)) and conf > 0: + conf_text = f"{int(round(float(conf) * 100))}%" + else: + conf_text = "—" + rows.append( + f'{status_icon}' + f'{safe_prof}' + f'{safe_when}' + f'{safe_duration}' + f'{conf_text}' + ) + + recent_program = await self._options_text("table_program", "Program") + recent_when = await self._options_text("table_when", "When") + recent_length = await self._options_text("table_length", "Length") + recent_match = await self._options_text("table_match", "Match") + + if not rows: + return await self.async_step_manage_cycles_empty() + + recent_text = ( + '' + '' + '' + f'' + f'' + f'' + f'' + '' + + "".join(rows) + + '
{html.escape(recent_program)}{html.escape(recent_when)}{html.escape(recent_length)}{html.escape(recent_match)}
' + ) + + self._push_menu("manage_cycles") + return self.async_show_menu( + step_id="manage_cycles", + menu_options=[ + "auto_label_cycles", + "select_cycle_to_label", + "select_cycle_to_delete", + "interactive_editor", + "trim_cycle_select", + "menu_back", + ], + description_placeholders={"recent_cycles": recent_text}, + ) + + async def async_step_manage_cycles_empty( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage cycles submenu when no cycles are present.""" + self._push_menu("manage_cycles_empty") + return self.async_show_menu( + step_id="manage_cycles_empty", + menu_options=["auto_label_cycles", "menu_back"], + ) + + + + async def async_step_manage_profiles( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage profiles submenu as a button menu with profile summary.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profiles = store.list_profiles() + + if not profiles: + return await self.async_step_manage_profiles_empty() + + rows: list[str] = [] + for p in profiles: + avg_s = int(p.get("avg_duration") or 0) + avg_str = _format_duration_label(avg_s) if avg_s > 0 else "—" + count = p.get("cycle_count", 0) + last_run_raw = p.get("last_run") + last_run_str = "—" + if last_run_raw: + dt = dt_util.parse_datetime(str(last_run_raw)) + if dt: + last_run_str = dt_util.as_local(dt).strftime("%b %d") + avg_energy = p.get("avg_energy") + if isinstance(avg_energy, (int, float)) and avg_energy > 0: + if avg_energy >= 1.0: + energy_str = f"{avg_energy:.2f} kWh" + else: + energy_str = f"{int(round(avg_energy * 1000))} Wh" + else: + energy_str = "—" + safe_name = html.escape(str(p["name"])) + safe_avg = html.escape(avg_str) + safe_last_run = html.escape(last_run_str) + safe_energy = html.escape(energy_str) + rows.append( + f'{safe_name}' + f'{count}' + f'{safe_avg}' + f'{safe_last_run}' + f'{safe_energy}' + ) + + profile_header = await self._options_text("table_profile", "Profile") + cycles_header = await self._options_text("table_cycles", "Cycles") + avg_length_header = await self._options_text( + "table_avg_length", "Avg Length" + ) + last_run_header = await self._options_text("table_last_run", "Last Run") + avg_energy_header = await self._options_text( + "table_avg_energy", "Avg Energy" + ) + + summary_text = ( + '' + '' + f'' + f'' + f'' + f'' + f'' + '' + + "".join(rows) + + '
{html.escape(profile_header)}{html.escape(cycles_header)}{html.escape(avg_length_header)}{html.escape(last_run_header)}{html.escape(avg_energy_header)}
' + ) + + self._push_menu("manage_profiles") + return self.async_show_menu( + step_id="manage_profiles", + menu_options=[ + "create_profile", + "edit_profile", + "delete_profile_select", + "profile_stats", + "cleanup_profile", + "assign_profile_phases_select", + "menu_back", + ], + description_placeholders={"profile_summary": summary_text}, + ) + + async def async_step_manage_profiles_empty( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage profiles submenu when no profiles are present.""" + self._push_menu("manage_profiles_empty") + return self.async_show_menu( + step_id="manage_profiles_empty", + menu_options=["create_profile", "menu_back"], + ) + + async def async_step_manage_phase_catalog( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage phase catalog for all device types.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Phase names/descriptions are runtime catalog data, but the static + # wording around them is resolved via _options_text (instance language). + builtin_suffix = await self._options_text("phase_builtin_suffix", "(Built-in)") + no_phases = await self._options_text( + "phase_none_available", "No phases available." + ) + other_types_label = await self._options_text( + "phase_other_device_types", "Other device types:" + ) + + def _device_label(dtype: str) -> str: + # Reuse the (now translated) device-type selector labels. + return self._selector_translations.get( + f"component.{DOMAIN}.selector.device_type.options.{dtype}", + DEVICE_TYPES.get(dtype, dtype), + ) + + # Show this device's phases in full; summarise every other device type + # as a one-line count so the landing page stays short. The create / edit + # / delete steps still operate on every device type's phases. + current_type = manager.device_type + summary_lines: list[str] = [] + current_phases = store.list_phase_catalog(current_type) + if current_phases: + summary_lines.append(f"**{_device_label(current_type)}**") + for phase in current_phases: + icon = "📌 " if phase.get("is_default") else "✏️ " + desc = str(phase.get("description", "")).strip() + short = desc if len(desc) <= 80 else f"{desc[:77]}..." + phase_type = f" {builtin_suffix}" if phase.get("is_default") else "" + name = _escape_markdown(phase.get("name", "")) + summary_lines.append( + f"{icon}**{name}{phase_type}** - {_escape_markdown(short)}" + ) + + other_counts = [] + for device_type in DEVICE_TYPES: + if device_type == current_type: + continue + count = len(store.list_phase_catalog(device_type)) + if count: + other_counts.append(f"{_device_label(device_type)} ({count})") + if other_counts: + if summary_lines: + summary_lines.append("") + summary_lines.append(f"{other_types_label} " + ", ".join(other_counts)) + + summary = "\n".join(summary_lines) if summary_lines else no_phases + + self._push_menu("manage_phase_catalog") + return self.async_show_menu( + step_id="manage_phase_catalog", + menu_options=[ + "phase_catalog_create", + "phase_catalog_edit_select", + "phase_catalog_delete", + "menu_back", + ], + description_placeholders={"phase_summary": summary}, + ) + + async def async_step_phase_catalog_create( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Create custom phase for a selected device type.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + errors: dict[str, str] = {} + all_device_value = "all_devices" + + if user_input is not None: + try: + target_device_type = str(user_input.get("target_device_type", manager.device_type)) + if target_device_type == all_device_value: + target_device_type = "" + await store.async_create_custom_phase( + target_device_type, + user_input["phase_name"], + user_input.get("phase_description", ""), + ) + manager.notify_update() + return await self.async_step_manage_phase_catalog() + except ValueError as err: + errors["base"] = str(err) + + target_options = [ + selector.SelectOptionDict( + value=manager.device_type, + label=( + f"{DEVICE_TYPES.get(manager.device_type, manager.device_type)} " + f"{await self._selector_text('current_suffix', '(current)')}" + ), + ), + selector.SelectOptionDict( + value=all_device_value, + label=await self._selector_text( + "all_device_types", "All Device Types" + ), + ), + ] + for key, label in DEVICE_TYPES.items(): + if key == manager.device_type: + continue + target_options.append(selector.SelectOptionDict(value=key, label=label)) + + return self.async_show_form( + step_id="phase_catalog_create", + data_schema=vol.Schema( + { + vol.Required("target_device_type", default=manager.device_type): selector.SelectSelector( + selector.SelectSelectorConfig( + options=target_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Required("phase_name"): str, + vol.Optional("phase_description", default=""): selector.TextSelector( + selector.TextSelectorConfig(multiline=True) + ), + } + ), + errors=errors, + ) + + async def async_step_phase_catalog_edit_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select phase to edit from all device types.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Gather unique phases from all device types. + # Built-in phases have no device_type field; use the iteration device_type + # as the effective scope for display and deduplication only. + # The actual_scope stored in phase_index is the real phase device_type + # (empty string for built-ins), so that editing a built-in always produces + # a universal override (device_type="") rather than a device-specific one. + all_phases: list[tuple[dict[str, Any], str, str]] = [] + seen: set[tuple[str, str]] = set() + for device_type in DEVICE_TYPES.keys(): + for phase in store.list_phase_catalog(device_type): + phase_name = str(phase.get("name", "")).strip() + phase_scope = str(phase.get("device_type", "")).strip() + is_default = bool(phase.get("is_default")) + # Built-in phases carry no device_type; use iteration device_type + # for display label so they don't all read "[All Devices]". + effective_scope = phase_scope if (phase_scope or not is_default) else device_type + dedupe_key = (phase_name.casefold(), effective_scope) + if not phase_name or dedupe_key in seen: + continue + seen.add(dedupe_key) + all_phases.append((phase, effective_scope, phase_scope)) + + if not all_phases: + return self.async_abort(reason="no_phases_available") + + options: list[selector.SelectOptionDict] = [] + phase_index: dict[str, tuple[str, str, str]] = {} + for phase, effective_scope, actual_scope in all_phases: + phase_name = str(phase.get("name", "")).strip() + phase_id = str(phase.get("id", "")).strip() + scope_label = DEVICE_TYPES.get(effective_scope, "All Devices") if effective_scope else "All Devices" + option_key = phase_id if phase_id else f"{effective_scope}::{phase_name}" + phase_index[option_key] = (phase_name, actual_scope, phase_id) + options.append( + selector.SelectOptionDict( + value=option_key, + label=f"[{scope_label}] {phase_name} - {str(phase.get('description', ''))[:60]}", + ) + ) + + if user_input is not None: + selected_key = user_input["phase_name"] + selected_meta = phase_index.get(selected_key) + if selected_meta is None: + return await self.async_step_phase_catalog_edit_select() + self._selected_phase_name = selected_meta[0] + self._selected_phase_device_type = selected_meta[1] + self._selected_phase_id = selected_meta[2] or None + return await self.async_step_phase_catalog_edit() + + return self.async_show_form( + step_id="phase_catalog_edit_select", + data_schema=vol.Schema( + { + vol.Required("phase_name"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + ) + + async def async_step_phase_catalog_edit( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Edit selected phase from all device types.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + if not self._selected_phase_id and not self._selected_phase_name: + return await self.async_step_phase_catalog_edit_select() + + catalog = store.list_phase_catalog(self._selected_phase_device_type or "") + if self._selected_phase_id: + selected = next( + (p for p in catalog if str(p.get("id", "")) == self._selected_phase_id), + None, + ) + else: + selected = next( + ( + p for p in catalog + if str(p.get("name", "")).strip() == self._selected_phase_name + and str(p.get("device_type", "")).strip() == self._selected_phase_device_type + ), + None, + ) + + if not selected: + return await self.async_step_phase_catalog_edit_select() + + errors: dict[str, str] = {} + if user_input is not None: + try: + phase_id = self._selected_phase_id or str(selected.get("id", "")) + await store.async_update_custom_phase( + phase_id, + user_input["phase_name"], + user_input.get("phase_description", ""), + ) + manager.notify_update() + return await self.async_step_manage_phase_catalog() + except ValueError as err: + errors["base"] = str(err) + + return self.async_show_form( + step_id="phase_catalog_edit", + data_schema=vol.Schema( + { + vol.Required("phase_name", default=selected["name"]): str, + vol.Optional( + "phase_description", + default=selected.get("description", ""), + ): selector.TextSelector(selector.TextSelectorConfig(multiline=True)), + } + ), + errors=errors, + description_placeholders={"phase_name": self._selected_phase_name}, + ) + + async def async_step_phase_catalog_delete( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Delete custom phase from any device type.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Gather unique custom phases from all device types. + # Global phases (device_type="") appear in every per-device list, + # so dedupe by (name, device_type). + # Collect unique user-added phases (not built-in overrides) across all device types. + all_custom_phases: dict[str, dict[str, str]] = {} # phase_id -> {name, scope} + seen_ids: set[str] = set() + for device_type in DEVICE_TYPES.keys(): + custom_phases = store.list_custom_phases(device_type) + for p in custom_phases: + phase_name = str(p.get("name", "")).strip() + phase_scope = str(p.get("device_type", "")).strip() + phase_id = str(p.get("id", "")).strip() + if not phase_name or not phase_id or phase_id in seen_ids: + continue + seen_ids.add(phase_id) + all_custom_phases[phase_id] = {"name": phase_name, "scope": phase_scope} + + if not all_custom_phases: + return self.async_abort(reason="no_custom_phases") + + errors: dict[str, str] = {} + if user_input is not None: + selected_key = user_input["phase_name"] + if selected_key in all_custom_phases: + phase_name = all_custom_phases[selected_key]["name"] + try: + removed = await store.async_delete_custom_phase(selected_key) + manager.notify_update() + _LOGGER.info( + "Deleted custom phase '%s' and removed %s phase assignments", + phase_name, + removed, + ) + return await self.async_step_manage_phase_catalog() + except ValueError as err: + errors["base"] = str(err) + else: + return await self.async_step_phase_catalog_delete() + + options = [] + for phase_id, phase_meta in all_custom_phases.items(): + phase_name = phase_meta["name"] + phase_scope = phase_meta["scope"] + scope_label = DEVICE_TYPES.get(phase_scope, "All Devices") if phase_scope else "All Devices" + usage = store.count_phase_usage(phase_name) + label = f"[{scope_label}] {phase_name} ({usage} assignments)" + options.append(selector.SelectOptionDict(value=phase_id, label=label)) + + return self.async_show_form( + step_id="phase_catalog_delete", + data_schema=vol.Schema( + { + vol.Required("phase_name"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + errors=errors, + ) + + def _phase_assignment_summary(self, ranges: list[dict[str, Any]]) -> str: + """Build compact summary lines for assigned phase ranges.""" + if not ranges: + return "No ranges assigned yet." + + lines = [] + for idx, row in enumerate(ranges, start=1): + start_min = int(float(row.get("start", 0.0)) / 60) + end_min = int(float(row.get("end", 0.0)) / 60) + duration_min = max(1, end_min - start_min) + lines.append(f"{idx}. {row.get('name', '')}: {start_min}-{end_min} min ({duration_min}m)") + return "\n".join(lines) + + def _phase_assignment_timeline(self, ranges: list[dict[str, Any]]) -> str: + """Render a simple text timeline (48 slots) for phase ranges.""" + if not ranges: + return "[................................................]" + + max_end = max(float(r.get("end", 0.0)) for r in ranges) + if max_end <= 0: + return "[................................................]" + + slots = 48 + chars = ["."] * slots + for slot in range(slots): + t = (slot + 0.5) * (max_end / slots) + symbol = "." + for idx, row in enumerate(ranges, start=1): + start = float(row.get("start", 0.0)) + end = float(row.get("end", 0.0)) + if start <= t < end: + symbol = str(idx % 10) + break + chars[slot] = symbol + + total_min = int(max_end / 60) + return f"[{''.join(chars)}] 0..{total_min} min" + + def _phase_assignment_svg_markdown( + self, + profile_name: str, + ranges: list[dict[str, Any]], + envelope: dict[str, Any] | None, + labels: dict[str, str] | None = None, + ) -> str: + """Render phase assignment preview as average power curve + gating lines.""" + + txt = { + "phase_preview": "Phase Preview", + "no_curve": "Average profile curve is not available yet. Run/label more cycles for this profile.", + "min": "min", + "avg_curve": "Average Power Curve", + } + if labels: + txt.update(labels) + + def esc(text: Any) -> str: + return ( + str(text) + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) + + width = 1360 + plot_left = 12 + plot_right = 12 + plot_top = 64 + plot_height = 320 + plot_width = width - plot_left - plot_right + + if not envelope or not isinstance(envelope.get("avg"), list): + empty_svg = ( + "" + "" + f"{esc(txt['phase_preview'])}: {esc(profile_name)}" + "" + f"{esc(txt['no_curve'])}" + "" + "" + ) + encoded_empty = base64.b64encode(empty_svg.encode("utf-8")).decode("ascii") + return f"![Phase timeline](data:image/svg+xml;base64,{encoded_empty})" + + avg_points_raw = envelope.get("avg", []) + avg_points: list[tuple[float, float]] = [] + for point in avg_points_raw: + if not isinstance(point, (list, tuple)) or len(point) < 2: + continue + try: + avg_points.append((float(point[0]), float(point[1]))) + except (TypeError, ValueError): + continue + + if len(avg_points) < 2: + return self._phase_assignment_svg_markdown(profile_name, ranges, None) + + max_time = max(p[0] for p in avg_points) + max_power_curve = max((p[1] for p in avg_points), default=0.0) + max_power_envelope = 0.0 + max_curve = envelope.get("max", []) + if isinstance(max_curve, list): + for point in max_curve: + if isinstance(point, (list, tuple)) and len(point) >= 2: + try: + max_power_envelope = max(max_power_envelope, float(point[1])) + except (TypeError, ValueError): + continue + max_power = max(10.0, max_power_curve, max_power_envelope) * 1.08 + max_time = max(1.0, max_time) + + def to_x(t_sec: float) -> float: + return plot_left + (max(0.0, min(max_time, t_sec)) / max_time) * plot_width + + def to_y(power_w: float) -> float: + clamped = max(0.0, min(max_power, power_w)) + return plot_top + plot_height - (clamped / max_power) * plot_height + + def pick_grid_interval(total_min_val: int) -> int: + label_w_px = 91 # conservative width of widest label at font-size 22 + min_iv = (label_w_px / plot_width) * total_min_val + for candidate in (1, 2, 5, 10, 15, 20, 30, 60): + if candidate > min_iv: + return candidate + return 60 + + ordered = sorted( + ranges, + key=lambda x: (float(x.get("start", 0.0)), float(x.get("end", 0.0))), + ) + palette = [ + "#2563eb", + "#059669", + "#d97706", + "#dc2626", + "#7c3aed", + "#0891b2", + "#4f46e5", + "#16a34a", + "#ea580c", + "#be123c", + ] + + legend_item_height = 30 + legend_item_width = 340 + legend_columns = max(1, int((plot_width - 10) // legend_item_width)) + legend_rows = max(1, (max(1, len(ordered) + 1) + legend_columns - 1) // legend_columns) + marker_y = plot_top + plot_height + 34 + legend_top = marker_y + 52 + height = legend_top + legend_rows * legend_item_height + 24 + + parts = [ + f"", + "", + f"", + ] + + # Adaptive time grid - vertical lines drawn first so they sit behind everything. + total_min = int(max_time / 60) + grid_interval = pick_grid_interval(total_min) + grid_ticks_min = list(range(0, total_min + 1, grid_interval)) + for tick_min in grid_ticks_min: + tx = to_x(tick_min * 60.0) + parts.append( + f"", + ) + + # Phase background spans + gating lines. + for idx, row in enumerate(ordered): + start = max(0.0, float(row.get("start", 0.0))) + end = max(start, float(row.get("end", 0.0))) + if start > max_time: + continue + x1 = to_x(start) + x2 = to_x(min(end, max_time)) + color = palette[idx % len(palette)] + if x2 > x1: + parts.append( + f"", + ) + # Gating lines at phase boundaries. + parts.append( + f"", + ) + if end <= max_time: + parts.append( + f"", + ) + + # Minute labels on each phase boundary gating line, placed above the plot. + # Two-row stagger handles closely-packed boundaries without increasing SVG height. + _gate_label_w = 72 + _gate_row1_y = plot_top - 10 # y=54 for default plot_top=64 + _gate_row2_y = plot_top - 28 # y=36 + _placed: list[tuple[float, float, int]] = [] + for idx, row in enumerate(ordered): + start = max(0.0, float(row.get("start", 0.0))) + end = max(start, float(row.get("end", 0.0))) + if start > max_time: + continue + color = palette[idx % len(palette)] + for t_sec, skip_if_over in ((start, False), (end, True)): + if skip_if_over and end > max_time: + continue + t_min = int(t_sec / 60) + tx = to_x(t_sec) + xl, xr = tx - _gate_label_w / 2, tx + _gate_label_w / 2 + chosen_y = _gate_row1_y + for y_cand in (_gate_row1_y, _gate_row2_y): + if not any(xl < pr and xr > pl for pl, pr, py in _placed if py == y_cand): + chosen_y = y_cand + break + _placed.append((xl, xr, chosen_y)) + if tx - _gate_label_w / 2 < plot_left: + anchor, label_x = "start", float(plot_left) + elif tx + _gate_label_w / 2 > plot_left + plot_width: + anchor, label_x = "end", float(plot_left + plot_width) + else: + anchor, label_x = "middle", tx + parts.append( + f"{t_min} {esc(txt['min'])}", + ) + + # Axis helper lines. + for frac in (0.25, 0.5, 0.75): + y = plot_top + plot_height * frac + parts.append( + f"", + ) + + # Average power curve. + avg_poly = " ".join(f"{to_x(t):.2f},{to_y(p):.2f}" for t, p in avg_points) + parts.append( + f"", + ) + + # Adaptive x-axis tick labels (total_min / grid_interval / grid_ticks_min already computed above). + for tick_min in grid_ticks_min: + tx = to_x(tick_min * 60.0) + half_w = 45 + if tx - half_w < plot_left: + anchor, label_x = "start", float(plot_left) + elif tx + half_w > plot_left + plot_width: + anchor, label_x = "end", float(plot_left + plot_width) + else: + anchor, label_x = "middle", tx + parts.append( + f"{tick_min} {esc(txt['min'])}", + ) + + # Legend: average curve + each phase color. + legend_entries: list[tuple[str, str]] = [("#3498db", txt["avg_curve"])] + for idx, row in enumerate(ordered): + start_min = int(float(row.get("start", 0.0)) / 60) + end_min = int(float(row.get("end", 0.0)) / 60) + legend_entries.append( + ( + palette[idx % len(palette)], + f"{esc(row.get('name', ''))} ({start_min}-{end_min}{esc(txt['min'])})", + ) + ) + + for idx, (color, label) in enumerate(legend_entries): + col = idx % legend_columns + row = idx // legend_columns + lx = plot_left + col * legend_item_width + ly = legend_top + row * legend_item_height + parts.append( + f"", + ) + parts.append( + f"{label}", + ) + + parts.append("") + svg = "".join(parts) + encoded = base64.b64encode(svg.encode("utf-8")).decode("ascii") + return f"![Phase timeline](data:image/svg+xml;base64,{encoded})" + + def _parse_phase_range_input( + self, + user_input: dict[str, Any], + ) -> tuple[float | None, float | None, str | None]: + """Parse offset fields from user input into start/end seconds.""" + start_min = user_input.get("start_min") + end_min = user_input.get("end_min") + if start_min is None or end_min is None: + return None, None, "incomplete_phase_row" + return float(start_min) * 60.0, float(end_min) * 60.0, None + + def _auto_detect_phase_ranges( + self, + avg_points: list[tuple[float, float]], + catalog: list[dict[str, Any]], + power_threshold_frac: float = 0.08, + min_gap_sec: float = 120.0, + min_phase_sec: float = 60.0, + ) -> list[dict[str, Any]]: + """Detect phase ranges from an average power curve using step-change segmentation.""" + if len(avg_points) < 2: + return [] + max_power = max(p[1] for p in avg_points) + if max_power < 1.0: + return [] + # Box-smooth to suppress noise (the avg curve is already relatively clean, + # but individual extremes can still trigger false segment boundaries). + window = max(1, min(15, len(avg_points) // 8)) + smoothed: list[tuple[float, float]] = [] + for i, (t, _p) in enumerate(avg_points): + lo = max(0, i - window // 2) + hi = min(len(avg_points), i + window // 2 + 1) + smoothed.append((t, sum(avg_points[j][1] for j in range(lo, hi)) / (hi - lo))) + threshold = max_power * power_threshold_frac + # Find contiguous active segments. + segments: list[list[float]] = [] + in_seg = False + seg_start = 0.0 + for t, p in smoothed: + if p >= threshold and not in_seg: + seg_start = t + in_seg = True + elif p < threshold and in_seg: + segments.append([seg_start, t]) + in_seg = False + if in_seg: + segments.append([seg_start, smoothed[-1][0]]) + # Merge gaps shorter than min_gap_sec (handles brief dips within a phase). + merged: list[list[float]] = [] + for seg in segments: + if merged and seg[0] - merged[-1][1] < min_gap_sec: + merged[-1][1] = seg[1] + else: + merged.append([seg[0], seg[1]]) + # Drop phases that are too short to be meaningful. + phases = [s for s in merged if s[1] - s[0] >= min_phase_sec] + # Assign names from the phase catalog in order (cycle if more phases than names). + names = [p["name"] for p in catalog] if catalog else [] + result: list[dict[str, Any]] = [] + for i, (start, end) in enumerate(phases): + name = names[i % len(names)] if names else f"phase_{i + 1}" + result.append({"name": name, "start": float(start), "end": float(end)}) + return result + + def _validate_phase_ranges( + self, + ranges: list[dict[str, Any]], + ) -> str | None: + """Validate non-overlapping, strictly increasing phase ranges.""" + normalized = sorted(ranges, key=lambda x: (float(x["start"]), float(x["end"]))) + prev_end: float | None = None + for row in normalized: + start = float(row["start"]) + end = float(row["end"]) + if end <= start: + return "invalid_phase_range" + if prev_end is not None and start < prev_end: + return "overlapping_phase_ranges" + prev_end = end + return None + + async def async_step_assign_profile_phases_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select profile before opening the phase editor.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profiles = [ + p + for p in store.list_profiles() + if str(p.get("device_type", "")).strip() in ("", manager.device_type) + ] + if not profiles: + return self.async_abort(reason="no_profiles_found") + + errors: dict[str, str] = {} + if user_input is not None: + self._phase_assign_profile = user_input["profile"] + self._phase_assign_mode = "offset_mode" + self._phase_assign_cycle_id = None + self._phase_assign_draft = store.get_profile_phase_ranges_for_device( + self._phase_assign_profile, + manager.device_type, + ) + self._phase_assign_edit_index = None + return await self.async_step_assign_profile_phases() + + profile_options = [ + selector.SelectOptionDict(value=p["name"], label=p["name"]) + for p in profiles + ] + + return self.async_show_form( + step_id="assign_profile_phases_select", + data_schema=vol.Schema( + { + vol.Required("profile"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=profile_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + } + ), + errors=errors, + ) + + async def async_step_assign_profile_phases( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Phase assignment editor as a button menu with visualization.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + profile_name = self._phase_assign_profile + if not profile_name: + return await self.async_step_assign_profile_phases_select() + + draft_sorted = sorted(self._phase_assign_draft, key=lambda x: (float(x["start"]), float(x["end"]))) + summary = self._phase_assignment_summary(draft_sorted) + envelope = store.get_envelope(profile_name) + if envelope is None: + try: + await store.async_rebuild_envelope(profile_name) + envelope = store.get_envelope(profile_name) + except Exception: # pylint: disable=broad-exception-caught + envelope = None + timeline_svg = self._phase_assignment_svg_markdown( + profile_name, + draft_sorted, + envelope, + labels={ + "phase_preview": await self._options_text("phase_preview", "Phase Preview"), + "no_curve": await self._options_text( + "phase_preview_no_curve", + "Average profile curve is not available yet. Run/label more cycles for this profile.", + ), + "min": await self._options_text("unit_min", "min"), + "avg_curve": await self._options_text( + "average_power_curve", "Average Power Curve" + ), + }, + ) + + has_draft = bool(self._phase_assign_draft) + menu_options = ["assign_profile_phases_add"] + if has_draft: + menu_options.append("assign_profile_phases_edit_select") + menu_options.append("assign_profile_phases_delete") + menu_options.append("phase_ranges_clear") + menu_options.append("assign_profile_phases_auto_detect") + menu_options.append("phase_ranges_save") + menu_options.append("menu_back") + + self._push_menu("assign_profile_phases") + return self.async_show_menu( + step_id="assign_profile_phases", + menu_options=menu_options, + description_placeholders={ + "profile_name": profile_name, + "current_ranges": summary, + "timeline_svg": timeline_svg, + }, + ) + + async def async_step_phase_ranges_clear( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: clear draft phase ranges and re-show editor.""" + self._phase_assign_draft = [] + return await self.async_step_assign_profile_phases() + + async def async_step_phase_ranges_save( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: commit draft phase ranges; aborts on validation error.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profile_name = self._phase_assign_profile + if not profile_name: + return await self.async_step_assign_profile_phases_select() + try: + await store.async_set_profile_phase_ranges(profile_name, self._phase_assign_draft) + except ValueError: + return self.async_abort(reason="phase_ranges_invalid") + manager.notify_update() + self._phase_assign_profile = None + self._phase_assign_mode = "offset_mode" + self._phase_assign_cycle_id = None + self._phase_assign_draft = [] + self._phase_assign_edit_index = None + return await self.async_step_manage_profiles() + + async def async_step_assign_profile_phases_auto_detect( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Show auto-detected phase ranges with an action choice (name & apply, or go back).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profile_name = self._phase_assign_profile + if not profile_name: + return await self.async_step_assign_profile_phases_select() + + envelope = store.get_envelope(profile_name) + if envelope is None: + try: + await store.async_rebuild_envelope(profile_name) + envelope = store.get_envelope(profile_name) + except Exception: # pylint: disable=broad-exception-caught + envelope = None + + avg_points: list[tuple[float, float]] = [] + if envelope and isinstance(envelope.get("avg"), list): + for pt in envelope["avg"]: + if isinstance(pt, (list, tuple)) and len(pt) >= 2: + try: + avg_points.append((float(pt[0]), float(pt[1]))) + except (TypeError, ValueError): + pass + + catalog = store.list_phase_catalog(manager.device_type) + # Detect once and cache so the naming step can use the same result. + self._phase_assign_auto_detected = self._auto_detect_phase_ranges(avg_points, catalog) + detected = self._phase_assign_auto_detected + + if user_input is not None: + action = user_input.get("action") + if action == "name_phases" and detected: + return await self.async_step_assign_profile_phases_auto_detect_name() + # "cancel" or no phases found - go back without changes. + return await self.async_step_assign_profile_phases() + + svg_labels = { + "phase_preview": await self._options_text("phase_preview", "Phase Preview"), + "no_curve": await self._options_text( + "phase_preview_no_curve", + "Average profile curve is not available yet. Run/label more cycles for this profile.", + ), + "min": await self._options_text("unit_min", "min"), + "avg_curve": await self._options_text("average_power_curve", "Average Power Curve"), + } + timeline_svg = self._phase_assignment_svg_markdown(profile_name, detected, envelope, labels=svg_labels) + + actions = ["name_phases", "cancel"] if detected else ["cancel"] + return self.async_show_form( + step_id="assign_profile_phases_auto_detect", + data_schema=vol.Schema( + { + vol.Required("action"): self._translated_select( + options=actions, + translation_key="assign_profile_phases_auto_detect_action", + ) + } + ), + description_placeholders={ + "profile_name": profile_name, + "detected_count": str(len(detected)), + "timeline_svg": timeline_svg, + }, + ) + + async def async_step_assign_profile_phases_auto_detect_name( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Assign a name to each auto-detected phase range.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profile_name = self._phase_assign_profile + if not profile_name or not self._phase_assign_auto_detected: + return await self.async_step_assign_profile_phases() + + detected = self._phase_assign_auto_detected + + catalog = store.list_phase_catalog(manager.device_type) + phase_options = [ + selector.SelectOptionDict( + value=p["name"], + label=f"{p['name']} – {str(p.get('description', ''))[:52]}", + ) + for p in catalog + ] + + if user_input is not None: + named = [] + for i, phase in enumerate(detected): + name = user_input.get(f"name_{i}", phase["name"]) + named.append({"name": name, "start": phase["start"], "end": phase["end"]}) + self._phase_assign_draft = named + self._phase_assign_auto_detected = [] + return await self.async_step_assign_profile_phases() + + schema_dict: dict[Any, Any] = {} + for i, phase in enumerate(detected): + start_min = int(phase["start"] / 60) + end_min = int(phase["end"] / 60) + schema_dict[ + vol.Required(f"name_{i}", default=phase["name"], + description={"suggested_value": phase["name"]}) + ] = selector.SelectSelector( + selector.SelectSelectorConfig( + options=phase_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + + # Build a summary of time ranges for the description. + ranges_summary = "\n".join( + f"- **Phase {i + 1}**: {int(ph['start'] / 60)}–{int(ph['end'] / 60)} min" + for i, ph in enumerate(detected) + ) + return self.async_show_form( + step_id="assign_profile_phases_auto_detect_name", + data_schema=vol.Schema(schema_dict), + description_placeholders={ + "profile_name": profile_name, + "detected_count": str(len(detected)), + "ranges_summary": ranges_summary, + }, + ) + + async def async_step_assign_profile_phases_add( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Add a phase range to the current draft.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + catalog = store.list_phase_catalog(manager.device_type) + phase_options = [ + selector.SelectOptionDict( + value=p["name"], + label=f"{p['name']} - {str(p.get('description', ''))[:52]}", + ) + for p in catalog + ] + + default_start = 0 + default_end = 10 + if self._phase_assign_draft: + draft_sorted = sorted(self._phase_assign_draft, key=lambda x: float(x["end"])) + default_start = int(float(draft_sorted[-1]["end"]) / 60) + default_end = default_start + 10 + + errors: dict[str, str] = {} + if user_input is not None: + phase_name = user_input.get("phase_name") + if not phase_name: + errors["base"] = "incomplete_phase_row" + else: + start_sec, end_sec, parse_error = self._parse_phase_range_input( + user_input, + ) + if parse_error is not None or start_sec is None or end_sec is None: + errors["base"] = parse_error or "invalid_phase_range" + elif end_sec <= start_sec: + errors["base"] = "invalid_phase_range" + else: + updated = [ + *self._phase_assign_draft, + {"name": phase_name, "start": start_sec, "end": end_sec}, + ] + validation_error = self._validate_phase_ranges(updated) + if validation_error is not None: + errors["base"] = validation_error + else: + self._phase_assign_draft = sorted( + updated, key=lambda x: (float(x["start"]), float(x["end"])) + ) + return await self.async_step_assign_profile_phases() + + schema_dict: dict[Any, Any] = { + vol.Required("phase_name"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=phase_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Required("start_min", default=default_start): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) + ), + vol.Required("end_min", default=default_end): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) + ), + } + + return self.async_show_form( + step_id="assign_profile_phases_add", + data_schema=vol.Schema(schema_dict), + errors=errors, + ) + + async def async_step_assign_profile_phases_edit_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select a phase range index to edit.""" + if not self._phase_assign_draft: + return await self.async_step_assign_profile_phases() + + ranges = sorted(self._phase_assign_draft, key=lambda x: (float(x["start"]), float(x["end"]))) + if user_input is not None: + self._phase_assign_edit_index = int(user_input["range_index"]) + return await self.async_step_assign_profile_phases_edit() + + options = [] + for idx, row in enumerate(ranges): + start_min = int(float(row.get("start", 0.0)) / 60) + end_min = int(float(row.get("end", 0.0)) / 60) + options.append( + selector.SelectOptionDict( + value=str(idx), + label=f"{idx + 1}. {row.get('name', '')} ({start_min}-{end_min} min)", + ) + ) + + return self.async_show_form( + step_id="assign_profile_phases_edit_select", + data_schema=vol.Schema( + { + vol.Required("range_index"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + ) + + async def async_step_assign_profile_phases_edit( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Edit the selected phase range in the current draft.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + ranges = sorted(self._phase_assign_draft, key=lambda x: (float(x["start"]), float(x["end"]))) + if self._phase_assign_edit_index is None or self._phase_assign_edit_index >= len(ranges): + return await self.async_step_assign_profile_phases_edit_select() + + current = ranges[self._phase_assign_edit_index] + + catalog = store.list_phase_catalog(manager.device_type) + phase_options = [ + selector.SelectOptionDict( + value=p["name"], + label=f"{p['name']} - {str(p.get('description', ''))[:52]}", + ) + for p in catalog + ] + + errors: dict[str, str] = {} + if user_input is not None: + phase_name = user_input.get("phase_name") + if not phase_name: + errors["base"] = "incomplete_phase_row" + else: + start_sec, end_sec, parse_error = self._parse_phase_range_input( + user_input, + ) + if parse_error is not None or start_sec is None or end_sec is None: + errors["base"] = parse_error or "invalid_phase_range" + elif end_sec <= start_sec: + errors["base"] = "invalid_phase_range" + else: + updated = list(ranges) + updated[self._phase_assign_edit_index] = { + "name": phase_name, + "start": start_sec, + "end": end_sec, + } + validation_error = self._validate_phase_ranges(updated) + if validation_error is not None: + errors["base"] = validation_error + else: + self._phase_assign_draft = sorted( + updated, key=lambda x: (float(x["start"]), float(x["end"])) + ) + self._phase_assign_edit_index = None + return await self.async_step_assign_profile_phases() + + schema_dict: dict[Any, Any] = { + vol.Required("phase_name", default=current.get("name", "")): selector.SelectSelector( + selector.SelectSelectorConfig( + options=phase_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Required("start_min", default=int(float(current.get("start", 0.0)) / 60)): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) + ), + vol.Required("end_min", default=int(float(current.get("end", 0.0)) / 60)): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=1440, step=1, mode=selector.NumberSelectorMode.BOX) + ), + } + + return self.async_show_form( + step_id="assign_profile_phases_edit", + data_schema=vol.Schema(schema_dict), + errors=errors, + ) + + async def async_step_assign_profile_phases_delete( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Delete one phase range from the current draft.""" + ranges = sorted(self._phase_assign_draft, key=lambda x: (float(x["start"]), float(x["end"]))) + if not ranges: + return await self.async_step_assign_profile_phases() + + if user_input is not None: + idx = int(user_input["range_index"]) + if 0 <= idx < len(ranges): + del ranges[idx] + self._phase_assign_draft = ranges + return await self.async_step_assign_profile_phases() + + options = [] + for idx, row in enumerate(ranges): + start_min = int(float(row.get("start", 0.0)) / 60) + end_min = int(float(row.get("end", 0.0)) / 60) + options.append( + selector.SelectOptionDict( + value=str(idx), + label=f"{idx + 1}. {row.get('name', '')} ({start_min}-{end_min} min)", + ) + ) + + return self.async_show_form( + step_id="assign_profile_phases_delete", + data_schema=vol.Schema( + { + vol.Required("range_index"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + ) + + async def async_step_cleanup_profile( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select a profile to clean up (via graph).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profiles = store.list_profiles() + + if not profiles: + return self.async_abort(reason="no_profiles_found") + + if user_input is not None: + self._selected_profile = user_input["profile"] + return await self.async_step_cleanup_select() + + options = [] + profile_option_fmt = await self._options_text( + "profile_option_fmt", "{name} ({count} cycles, ~{duration}m avg)" + ) + for p in profiles: + count = p["cycle_count"] + duration_min = int(p["avg_duration"] / 60) if p["avg_duration"] else 0 + label = profile_option_fmt.format( + name=p["name"], count=count, duration=duration_min + ) + options.append(selector.SelectOptionDict(value=p["name"], label=label)) + + return self.async_show_form( + step_id="cleanup_profile", + data_schema=vol.Schema( + { + vol.Required("profile"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ), + ) + + async def async_step_cleanup_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Show graph and select cycles to delete.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if user_input is not None: + # Delete selected cycles + cycles_to_delete = user_input.get("cycles_to_delete", []) + count = 0 + for cycle_id in cycles_to_delete: + await store.delete_cycle(cycle_id) + count += 1 + + manager.notify_update() + # Return to manage profiles + return self.async_create_entry( + title="", + data=dict(self.config_entry.options), + description_placeholders={ + "info": ( + await self._options_text( + "deleted_cycles_from_profile", + "Deleted {count} cycles from {profile}.", + ) + ).format(count=count, profile=self._selected_profile), + }, + ) + + # Generate SVG + ts = int(time.time()) + stats_dir = self.hass.config.path("www", "ha_washdata", "profiles") + await self.hass.async_add_executor_job( + lambda: os.makedirs(stats_dir, exist_ok=True) + ) + + safe_name = slugify(self._selected_profile) + # Generate SVG with ALL cycles for this profile (outliers included) + # Returns (svg_string, cycle_metadata_map) where metadata contains colors + # Run in executor to avoid blocking loop + svg_content, cycle_colors = await self.hass.async_add_executor_job( + store.generate_profile_spaghetti_svg, + self._selected_profile, + await self._options_text("overview_suffix", "Overview"), + ) + + if not svg_content: + return self.async_abort( + reason="no_cycles_found", + description_placeholders={ + "info": await self._options_text( + "not_enough_data_graph", "Not enough data to generate graph." + ) + }, + ) + + file_path = f"{stats_dir}/cleanup_{safe_name}.svg" + + def write_svg(path=file_path, content=svg_content): + with open(path, "w", encoding="utf-8") as f: + f.write(content) + + await self.hass.async_add_executor_job(write_svg) + graph_url = f"/local/ha_washdata/profiles/cleanup_{safe_name}.svg?v={ts}" + + # Get cycles for selection + # We need to list ALL cycles for this profile + all_cycles = store.get_past_cycles() + profile_cycles = [ + c for c in all_cycles if c.get("profile_name") == self._selected_profile + ] + + #Sort by start time descending + profile_cycles.sort(key=lambda x: x["start_time"], reverse=True) + + # Map hex colors to emojis for easier identification + hex_to_emoji = { + "#e6194b": "🔴", # Red + "#3cb44b": "🟢", # Green + "#ffe119": "🟡", # Yellow + "#4363d8": "🔵", # Blue + "#f58231": "🟠", # Orange + "#911eb4": "🟣", # Purple + "#42d4f4": "🔵", # Cyan + "#f032e6": "🟣", # Magenta + "#bfef45": "🟢", # Lime + "#fabed4": "🌸", # Pink + "#469990": "teal", # Teal + "#dcbeff": "🟣", # Lavender + "#9A6324": "🟤", # Brown + "#fffac8": "⚪", # Beige + "#800000": "🔴", # Maroon + "#aaffc3": "🟢", # Mint + "#808000": "🟤", # Olive + "#ffd8b1": "🟠", # Apricot + "#000075": "🔵", # Navy + "#a9a9a9": "⚪", # Grey + } + + options = [] + for c in profile_cycles: + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + status = c.get("status", "completed") + + # Status icon + status_icon = ( + "✓" + if status in ("completed", "force_stopped") + else "⚠" if status == "resumed" else "✗" + ) + + # Graph Color + color_hex = cycle_colors.get(c["id"]) + color_emoji = hex_to_emoji.get(color_hex, "⚫") if color_hex else "" + + # Add energy if available to help identify + energy = "" + if "total_energy_kwh" in c: + energy = f" | {c['total_energy_kwh']:.3f} kWh" + + label = f"{color_emoji} {status_icon} {start} - {duration_min}m{energy}" + options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + return self.async_show_form( + step_id="cleanup_select", + data_schema=vol.Schema( + { + vol.Optional("cycles_to_delete"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.LIST, + multiple=True, + ) + ) + } + ), + description_placeholders={ + "graph_url": graph_url, + "profile_name": self._selected_profile or "", + }, + ) + + async def async_step_profile_stats( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Show detailed profile statistics with graphs.""" + if user_input is not None: + # Back to manage profiles + return await self.async_step_manage_profiles() + + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Ensure stats directory exists + stats_dir = self.hass.config.path("www", "ha_washdata", "profiles") + await self.hass.async_add_executor_job( + lambda: os.makedirs(stats_dir, exist_ok=True) + ) + + profiles = store.list_profiles() + sections = [] + ts = int(time.time()) + + # Get all cycles to find last run + cycles = store.get_past_cycles() + + for p in profiles: + name = p["name"] + + # FORCE REFRESH: Rebuild envelope to ensure data is fresh + # This calculates energy, consistency, etc. + await store.async_rebuild_envelope(name) + + # Read profile again after rebuild so table values match refreshed stats. + refreshed = store.get_profile(name) or p + + safe_name = slugify(name) + count = int(refreshed.get("cycle_count", p.get("cycle_count", 0))) + avg_raw = float(refreshed.get("avg_duration") or 0) + min_raw = float(refreshed.get("min_duration") or 0) + max_raw = float(refreshed.get("max_duration") or 0) + avg = int(avg_raw / 60) if avg_raw else 0 + mn = int(min_raw / 60) if min_raw else 0 + mx = int(max_raw / 60) if max_raw else 0 + + # Get envelope for advanced stats + envelope = store.get_envelope(name) + # Retrieve scalar stats + kwh = f"{envelope.get('avg_energy', 0):.2f}" if envelope else "-" + # Calculate Total Energy (Avg * Count) + total_kwh = "-" + if envelope and envelope.get('avg_energy') is not None: + t_kwh = envelope.get('avg_energy', 0) * count + total_kwh = f"{t_kwh:.2f}" + + std_dev = envelope.get("duration_std_dev", 0) if envelope else 0 + consistency = f"±{int(std_dev / 60)}m" if std_dev > 0 else "-" + + # Find last run + last_run = "-" + p_cycles = [c for c in cycles if c.get("profile_name") == name] + if p_cycles: + last_c = max(p_cycles, key=lambda x: x["start_time"]) + dt = last_c["start_time"].split("T")[0] + last_run = dt + + # Generate and Write SVG + # Offload to executor to prevent blocking + svg_content = await self.hass.async_add_executor_job( + store.generate_profile_svg, name + ) + graph_markdown = "" + if svg_content: + file_path = f"{stats_dir}/profile_{safe_name}.svg" + + def write_svg(path=file_path, content=svg_content): + with open(path, "w", encoding="utf-8") as f: + f.write(content) + + await self.hass.async_add_executor_job(write_svg) + graph_markdown = ( + f"![{name}](/local/ha_washdata/profiles/profile_{safe_name}.svg?v={ts})" + ) + + # Build Per-Profile Section + # Headers: Count | Avg | Min | Max | Energy | Consistency | Last Run + # New: Energy (Avg) | Energy (Total) + table_header = ( + "| " + + await self._options_text("tbl_count", "Count") + + " | " + + await self._options_text("tbl_avg", "Avg") + + " | " + + await self._options_text("tbl_min", "Min") + + " | " + + await self._options_text("tbl_max", "Max") + + " | " + + await self._options_text("tbl_energy_avg", "Energy (Avg)") + + " | " + + await self._options_text("tbl_energy_total", "Energy (Total)") + + " | " + + await self._options_text("tbl_consistency", "Consist.") + + " | " + + await self._options_text("tbl_last_run", "Last Run") + + " |" + ) + table_sep = "| --- | --- | --- | --- | --- | --- | --- | --- |" + table_row = ( + f"| {count} | {avg}m | {mn}m | {mx}m | {kwh} kWh | {total_kwh} kWh " + f"| {consistency} | {last_run} |" + ) + + legend = ( + "> **" + + await self._options_text("graph_legend_title", "Graph Legend") + + "**: " + + await self._options_text( + "graph_legend_body", + "The blue band represents the minimum and maximum power draw range observed. The line shows the average power curve.", + ) + ) + + section = ( + f"## {name}\n{table_header}\n{table_sep}\n{table_row}\n\n" + f"{graph_markdown}\n\n{legend}" + ) + sections.append(section) + + content = "\n\n---\n\n".join(sections) if sections else "No profiles found." + if not sections: + content = await self._options_text("no_profiles_found", "No profiles found.") + + return self.async_show_form( + step_id="profile_stats", + data_schema=vol.Schema({}), + # Key 'stats_table' must match the key in translations/strings (description) + description_placeholders={"stats_table": content}, + ) + + async def async_step_create_profile( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Create a new profile.""" + errors = {} + + if user_input is not None: + name = user_input["profile_name"].strip() + reference_cycle = user_input.get("reference_cycle") + + if not name: + errors["profile_name"] = "empty_name" + else: + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + manual_duration_mins = user_input.get("manual_duration") + avg_duration = None + if manual_duration_mins and float(manual_duration_mins) > 0: + avg_duration = float(manual_duration_mins) * 60.0 + + try: + await manager.profile_store.create_profile_standalone( + name, + reference_cycle if reference_cycle != "none" else None, + avg_duration=avg_duration, + ) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + except ValueError: + errors["base"] = "profile_exists" + + # Build cycle options for reference + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + cycles = store.get_past_cycles()[-20:] + + cycle_options = [ + selector.SelectOptionDict( + value="none", + label=await self._selector_text( + "no_reference_cycle", "(No reference cycle)" + ), + ) + ] + for c in reversed(cycles): + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + prof = c.get("profile_name") or await self._selector_text("unlabeled", "(Unlabeled)") + label = f"{start} - {duration_min}m - {prof}" + cycle_options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + return self.async_show_form( + step_id="create_profile", + data_schema=vol.Schema( + { + vol.Required("profile_name"): str, + vol.Optional( + "reference_cycle", default="none" + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=cycle_options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional("manual_duration"): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=480, + mode=selector.NumberSelectorMode.BOX, + unit_of_measurement="min", + ) + ), + } + ), + errors=errors, + ) + + async def async_step_edit_profile( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select profile to edit/rename.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profiles = store.list_profiles() + + if not profiles: + return self.async_abort(reason="no_profiles_found") + + if user_input is not None: + self._selected_profile = user_input["profile"] + return await self.async_step_rename_profile() + + # Build profile options + options = [] + for p in profiles: + count = p["cycle_count"] + duration_min = int(p["avg_duration"] / 60) if p["avg_duration"] else 0 + label = f"{p['name']} ({count} cycles, ~{duration_min}m avg)" + options.append(selector.SelectOptionDict(value=p["name"], label=label)) + + return self.async_show_form( + step_id="edit_profile", + data_schema=vol.Schema( + { + vol.Required("profile"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ), + ) + + async def async_step_rename_profile( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Edit profile settings (Name and Duration).""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + errors = {} + + # Get current profile data + profiles = manager.profile_store.list_profiles() + current_data = next( + (p for p in profiles if p["name"] == self._selected_profile), None + ) + current_duration_mins = ( + int(current_data["avg_duration"] / 60) + if current_data and current_data["avg_duration"] + else 0 + ) + + if user_input is not None: + new_name = user_input["new_name"].strip() + manual_duration_mins = user_input.get("manual_duration") + + if not new_name: + errors["new_name"] = "empty_name" + else: + avg_duration = None + if manual_duration_mins is not None and manual_duration_mins > 0: + avg_duration = float(manual_duration_mins) * 60.0 + + try: + await manager.profile_store.update_profile( + self._selected_profile, new_name, avg_duration=avg_duration + ) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + except ValueError: + errors["base"] = "rename_failed" + + return self.async_show_form( + step_id="rename_profile", + data_schema=vol.Schema( + { + vol.Required("new_name", default=self._selected_profile): str, + vol.Optional( + "manual_duration", default=current_duration_mins + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=480, + unit_of_measurement="min", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + ), + errors=errors, + description_placeholders={"current_name": self._selected_profile or ""}, + ) + + async def async_step_delete_profile_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select profile to delete.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + profiles = store.list_profiles() + + if not profiles: + return self.async_abort(reason="no_profiles_found") + + if user_input is not None: + self._selected_profile = user_input["profile"] + return await self.async_step_delete_profile_confirm() + + # Build profile options + options = [] + for p in profiles: + count = p["cycle_count"] + duration_min = int(p["avg_duration"] / 60) if p["avg_duration"] else 0 + label = f"{p['name']} ({count} cycles, ~{duration_min}m avg)" + options.append(selector.SelectOptionDict(value=p["name"], label=label)) + + return self.async_show_form( + step_id="delete_profile_select", + data_schema=vol.Schema( + { + vol.Required("profile"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ), + ) + + async def async_step_delete_profile_confirm( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Confirm profile deletion.""" + if user_input is not None: + unlabel = user_input["unlabel_cycles"] + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + await manager.profile_store.delete_profile( + self._selected_profile, unlabel + ) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + + return self.async_show_form( + step_id="delete_profile_confirm", + data_schema=vol.Schema( + {vol.Required("unlabel_cycles", default=True): bool} + ), + description_placeholders={ + "profile_name": self._selected_profile or "", + }, + ) + + async def async_step_auto_label_cycles( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Auto-label all cycles retroactively.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Check if there are any profiles to match against + profiles = store.list_profiles() + if not profiles: + return self.async_abort(reason="no_profiles_for_matching") + + total_count = len(store.get_past_cycles()) + + if total_count == 0: + return self.async_abort(reason="no_cycles_found") + + if user_input is not None: + threshold = user_input["confidence_threshold"] + # Always pass overwrite=True as per user request to relabel everything + await store.auto_label_cycles(threshold, overwrite=True) + manager.notify_update() + return self.async_create_entry( + title="", + data=dict(self.config_entry.options), + ) + + return self.async_show_form( + step_id="auto_label_cycles", + data_schema=vol.Schema( + { + vol.Required( + "confidence_threshold", default=0.75 + ): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0.50, + max=0.95, + step=0.05, + mode=selector.NumberSelectorMode.BOX, + ) + ) + } + ), + description_placeholders={ + "total_count": str(total_count), + # Leading blank line so markdown renders the bulleted list + # instead of gluing the profile names onto the "Profiles:" label. + "profiles": "\n\n" + + "\n".join(f"- {_escape_markdown(p['name'])}" for p in profiles), + }, + ) + + async def async_step_select_cycle_to_label( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select a cycle to label.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Get last 20 cycles + cycles = store.get_past_cycles()[-20:] + + # Build readable options with status + options = [] + for c in reversed(cycles): + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + prof = c.get("profile_name") or "Unlabeled" + status = c.get("status", "completed") + # ✓ = completed/force_stopped (natural end), ⚠ = resumed, ✗ = interrupted (user stopped) + status_icon = ( + "✓" + if status in ("completed", "force_stopped") + else "⚠" if status == "resumed" else "✗" + ) + label = f"[{status_icon}] {start} - {duration_min}m - {prof}" + options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + if not options: + return self.async_abort(reason="no_cycles_found") + + if user_input is not None: + self._selected_cycle_id = user_input["cycle_id"] + return await self.async_step_label_cycle() + + return self.async_show_form( + step_id="select_cycle_to_label", + data_schema=vol.Schema( + { + vol.Required("cycle_id"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ), + ) + + async def async_step_select_cycle_to_delete( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select a cycle to delete.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + # Get last 20 cycles + cycles = store.get_past_cycles()[-20:] + + # Build readable options with status + options = [] + for c in reversed(cycles): + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + prof = c.get("profile_name") or "Unlabeled" + status = c.get("status", "completed") + # ✓ = completed/force_stopped (natural end), ⚠ = resumed, ✗ = interrupted (user stopped) + status_icon = ( + "✓" + if status in ("completed", "force_stopped") + else "⚠" if status == "resumed" else "✗" + ) + label = f"[{status_icon}] {start} - {duration_min}m - {prof}" + options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + if not options: + return self.async_abort(reason="no_cycles_found") + + if user_input is not None: + cycle_id = user_input["cycle_id"] + await manager.profile_store.delete_cycle(cycle_id) + # await manager.profile_store.async_save() # Handled inside delete_cycle now + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + + return self.async_show_form( + step_id="select_cycle_to_delete", + data_schema=vol.Schema( + { + vol.Required("cycle_id"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + ), + ) + + async def async_step_label_cycle( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Assign profile to the selected cycle.""" + errors = {} + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if user_input is not None: + profile_choice = user_input["profile_name"] + + # Handle "create new" option + if profile_choice == "__create_new__": + new_name = user_input.get("new_profile_name", "").strip() + if not new_name: + errors["new_profile_name"] = "empty_name" + else: + try: + # Create profile from this cycle + await store.create_profile(new_name, self._selected_cycle_id) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + except ValueError: + errors["base"] = "profile_exists" + elif profile_choice == "__remove_label__": + # Remove label from cycle + await store.assign_profile_to_cycle(self._selected_cycle_id, None) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + else: + # Assign existing profile + try: + await store.assign_profile_to_cycle( + self._selected_cycle_id, profile_choice + ) + manager.notify_update() + return self.async_create_entry( + title="", data=dict(self.config_entry.options) + ) + except ValueError: + errors["base"] = "assignment_failed" + + # Build profile dropdown options + profiles = store.list_profiles() + profile_options = [ + selector.SelectOptionDict( + value="__create_new__", + label=await self._selector_text( + "create_new_profile", "Create New Profile" + ), + ), + selector.SelectOptionDict( + value="__remove_label__", + label=await self._selector_text("remove_label", "Remove Label"), + ), + ] + for p in profiles: + count = p["cycle_count"] + duration_min = int(p["avg_duration"] / 60) if p["avg_duration"] else 0 + label = ( + await self._options_text( + "profile_option_short_fmt", "{name} ({count} cycles, ~{duration}m)" + ) + ).format(name=p["name"], count=count, duration=duration_min) + profile_options.append( + selector.SelectOptionDict(value=p["name"], label=label) + ) + + # Get cycle info for display + cycle = next( + ( + c + for c in store.get_past_cycles() + if c["id"] == self._selected_cycle_id + ), + None, + ) + cycle_info = "" + if cycle: + dt = dt_util.parse_datetime(cycle["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else cycle["start_time"] + duration_min = int(cycle["duration"] / 60) + current_label = cycle.get("profile") or await self._selector_text( + "unlabeled", "(Unlabeled)" + ) + cycle_info = ( + await self._options_text( + "cycle_info_fmt", + "Cycle: {start}, {duration}m, Current: {label}", + ) + ).format(start=start, duration=duration_min, label=current_label) + + schema: dict[Any, Any] = { + vol.Required( + "profile_name", + default="__create_new__" if not profiles else profiles[0]["name"], + ): selector.SelectSelector( + selector.SelectSelectorConfig( + options=profile_options, mode=selector.SelectSelectorMode.DROPDOWN + ) + ) + } + + # Add new profile name field (shown when "__create_new__" selected) + schema[vol.Optional("new_profile_name")] = str + + return self.async_show_form( + step_id="label_cycle", + data_schema=vol.Schema(schema), + errors=errors, + description_placeholders={"cycle_info": cycle_info}, + ) + + async def async_step_post_process( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle post-processing options.""" + if user_input is not None: + choice = user_input["time_range"] + user_gap = user_input["gap_seconds"] + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + + hours = 999999 if choice >= 999999 else int(choice) + + # Use async_run_maintenance to ensure envelopes are rebuilt after merging + stats = await manager.profile_store.async_run_maintenance( + lookback_hours=hours, gap_seconds=user_gap + ) + count_merged = stats.get("merged_cycles", 0) + count_split = stats.get("split_cycles", 0) + msg = f"Merged: {count_merged}, Split: {count_split}" + + return self.async_create_entry( + title="", + data=dict(self.config_entry.options), + description_placeholders={"count": msg}, + ) + + + + return self.async_show_form( + step_id="post_process", + data_schema=vol.Schema( + { + vol.Required("time_range", default=24): selector.NumberSelector( + selector.NumberSelectorConfig( + min=1, + max=9999, + unit_of_measurement="h", + mode=selector.NumberSelectorMode.BOX, + ) + ), + } + ), + ) + + + + async def async_step_wipe_history( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Wipe all stored cycles and profiles for this device (for testing).""" + if user_input is not None: + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + + # Clear all cycles and profiles + await manager.profile_store.clear_all_data() + manager.notify_update() + + return self.async_create_entry( + title="", + data=dict(self.config_entry.options), + description_placeholders={"info": "History cleared"}, + ) + + return self.async_show_form( + step_id="wipe_history", + data_schema=vol.Schema({}), + ) + + async def async_step_record_cycle( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle Record Mode as a button menu with state-aware options.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + + is_recording = manager.recorder.is_recording + has_last_run = manager.recorder.last_run is not None + + menu_options: list[str] = [] + if is_recording: + menu_options.append("record_refresh") + menu_options.append("record_stop") + status = "ACTIVE" + duration = int(manager.recorder.current_duration) + samples = len(getattr(manager.recorder, "_buffer", [])) + else: + menu_options.append("record_start") + status = "STOPPED" + duration = 0 + samples = 0 + + if has_last_run: + menu_options.append("record_process") + menu_options.append("record_discard") + + last_run = manager.recorder.last_run + samples = len(last_run.get("data", [])) + try: + start = dt_util.parse_datetime(last_run["start_time"]) + end = dt_util.parse_datetime(last_run["end_time"]) + duration = int((end - start).total_seconds()) + status = "READY TO PROCESS" + except Exception: # pylint: disable=broad-exception-caught + pass + + menu_options.append("menu_back") + self._push_menu("record_cycle") + return self.async_show_menu( + step_id="record_cycle", + menu_options=menu_options, + description_placeholders={ + "status": status, + "duration": str(duration), + "samples": str(samples), + }, + ) + + async def async_step_record_refresh( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: re-display recorder status.""" + return await self.async_step_record_cycle() + + async def async_step_record_discard( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Menu wrapper: discard the last recording and re-display status.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + await manager.recorder.clear_last_run() + return await self.async_step_record_cycle() + + async def async_step_record_start( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Start a recording.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + await manager.async_start_recording() + return await self.async_step_record_cycle() + + async def async_step_record_stop( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Stop a recording.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + await manager.async_stop_recording() + # Automatically go to process step? Or back to menu? + # User might want to immediately process. + return await self.async_step_record_process() + + async def async_step_record_process( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Process (Trim & Save) the recorded cycle.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + last_run = manager.recorder.last_run + + if not last_run: + return self.async_abort(reason="no_recording_found") + + data = last_run.get("data", []) + + if user_input is not None: + save_mode = user_input["save_mode"] + + if save_mode == "discard": + await manager.recorder.clear_last_run() + return self.async_create_entry(title="", data=dict(self.config_entry.options)) + + head_trim = user_input["head_trim"] + tail_trim = user_input["tail_trim"] + profile_name = user_input["profile_name"].strip() + + # GET ACTUAL RECORDING BOUNDS + rec_start_str = manager.recorder.last_run.get("start_time") + rec_end_str = manager.recorder.last_run.get("end_time") + + # APPLY TRIMS + # Convert data to timestamps + parsed = [] + for t_str, p in data: + t = dt_util.parse_datetime(t_str) + if t: + parsed.append((t.timestamp(), p)) + + if not parsed and not rec_start_str: + return self.async_abort(reason="empty_recording") + + # Use recording bounds if available, fallback to data bounds + data_start_ts = parsed[0][0] if parsed else 0 + data_end_ts = parsed[-1][0] if parsed else 0 + + start_ts = dt_util.parse_datetime(rec_start_str).timestamp() if rec_start_str else data_start_ts + end_ts = dt_util.parse_datetime(rec_end_str).timestamp() if rec_end_str else data_end_ts + + # Ensure bounds cover data + start_ts = min(start_ts, data_start_ts) if parsed else start_ts + end_ts = max(end_ts, data_end_ts) if parsed else end_ts + + # Calculate cut points + keep_start = start_ts + head_trim + keep_end = end_ts - tail_trim + + trimmed_data = [] + for t, p in parsed: + if t >= keep_start and t <= keep_end: + t_iso = dt_util.utc_from_timestamp(t).isoformat() + trimmed_data.append((t_iso, p)) + + duration = max(0.0, (keep_end - keep_start)) + + cycle_data = { + "id": f"rec_{int(time.time())}", + "start_time": dt_util.utc_from_timestamp(keep_start).isoformat(), + "end_time": dt_util.utc_from_timestamp(keep_end).isoformat(), + "duration": duration, + "profile_name": profile_name, + "power_data": trimmed_data, + "status": "completed", + "meta": {"source": "recorder", "original_samples": len(data)} + } + + if save_mode == "new_profile": + await manager.profile_store.create_profile_standalone(profile_name) + await manager.profile_store.async_add_cycle(cycle_data) + await manager.profile_store.async_rebuild_envelope(profile_name) + else: + # Add to existing + await manager.profile_store.async_add_cycle(cycle_data) + await manager.profile_store.async_rebuild_envelope(profile_name) + + await manager.profile_store.async_save() + await manager.recorder.clear_last_run() + + return self.async_create_entry(title="", data=dict(self.config_entry.options)) + + # Calculate suggestions + rec_start_str = manager.recorder.last_run.get("start_time") + rec_end_str = manager.recorder.last_run.get("end_time") + + rec_start = dt_util.parse_datetime(rec_start_str) if rec_start_str else None + rec_end = dt_util.parse_datetime(rec_end_str) if rec_end_str else None + + head_suggest, tail_suggest, sampling_rate = manager.recorder.get_trim_suggestions( + data, recording_start=rec_start, recording_end=rec_end + ) + + # Generate Preview Graph + ts = int(time.time()) + stats_dir = self.hass.config.path("www", "ha_washdata", "preview") + await self.hass.async_add_executor_job( + lambda: os.makedirs(stats_dir, exist_ok=True) + ) + + svg_content = await self.hass.async_add_executor_job( + manager.profile_store.generate_preview_svg, + data, + head_suggest, + tail_suggest, + await self._options_text("recording_preview", "Recording Preview"), + await self._options_text("trim_start", "Trim Start"), + await self._options_text("trim_end", "Trim End"), + ) + + graph_url = "" + if svg_content: + fname = f"preview_{ts}.svg" + path = f"{stats_dir}/{fname}" + + def write_svg(): + with open(path, "w", encoding="utf-8") as f: + f.write(svg_content) + + await self.hass.async_add_executor_job(write_svg) + graph_url = f"/local/ha_washdata/preview/{fname}?v={ts}" + + # Profile options + profiles = list(manager.profile_store.get_profiles().keys()) + profiles.sort(key=profile_sort_key) + + schema = { + vol.Required("head_trim", default=head_suggest): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=300000, step=0.1, mode=selector.NumberSelectorMode.BOX, unit_of_measurement="s") + ), + vol.Required("tail_trim", default=tail_suggest): selector.NumberSelector( + selector.NumberSelectorConfig(min=0, max=300000, step=0.1, mode=selector.NumberSelectorMode.BOX, unit_of_measurement="s") + ), + vol.Required("save_mode", default="existing_profile"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=["new_profile", "existing_profile", "discard"], + mode=selector.SelectSelectorMode.LIST, + translation_key="record_process_save_mode", + ) + ), + # Profile name optional if discarding? No dynamic update, so required logic applies. + # We can't easily make it conditional. User has to pick something or we allow empty? + # If user picks "Create New", they need a name. "Existing", they pick one. + # "Discard", name is ignored. + # Providing a text input that doubles as selector is tricky. + # We used SelectSelector with custom_value=True. + vol.Required("profile_name"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=profiles, + mode=selector.SelectSelectorMode.DROPDOWN, + custom_value=True + ) + ) + } + + # Calculate duration for display + duration_val = 0.0 + if rec_start and rec_end: + duration_val = (rec_end - rec_start).total_seconds() + + return self.async_show_form( + step_id="record_process", + data_schema=vol.Schema(schema), + description_placeholders={ + "samples": str(len(data)), + "duration": f"{duration_val:.1f}", + "graph_url": graph_url, + "sampling_rate": str(sampling_rate) + } + ) + + async def async_step_learning_feedbacks( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Landing menu for pending feedback: Review / Dismiss All / Back.""" + manager = self.hass.data[DOMAIN][self._config_entry.entry_id] + profile_store = manager.profile_store + pending = profile_store.get_pending_feedback() + + if not pending: + self._push_menu("learning_feedbacks_empty") + return self.async_show_menu( + step_id="learning_feedbacks_empty", + menu_options=["menu_back"], + ) + + sorted_pending = sorted( + pending.values(), + key=lambda x: x.get("created_at", ""), + reverse=True, + ) + preview_rows: list[str] = [] + for item in sorted_pending: + prof = item.get("detected_profile", "Unknown") + safe_prof = html.escape(str(prof)) + conf = item.get("confidence", 0.0) + created_raw = item.get("created_at", "") + t_str = str(created_raw) + if created_raw: + try: + dt = dt_util.parse_datetime(str(created_raw)) + if dt: + t_str = dt_util.as_local(dt).strftime("%d %b %H:%M") + except Exception: # pylint: disable=broad-exception-caught + pass + preview_rows.append( + f"{safe_prof}" + f'{int(conf * 100)}%' + f'{html.escape(t_str)}' + ) + + detected_program = await self._options_text( + "table_detected_program", "Detected Program" + ) + confidence_label = await self._options_text( + "table_confidence", "Confidence" + ) + reported_label = await self._options_text("table_reported", "Reported") + preview = ( + '' + '' + f'' + f'' + f'' + '' + + "".join(preview_rows) + + '
{html.escape(detected_program)}{html.escape(confidence_label)}{html.escape(reported_label)}
' + ) + + self._push_menu("learning_feedbacks") + return self.async_show_menu( + step_id="learning_feedbacks", + menu_options=[ + "learning_feedbacks_pick", + "learning_feedbacks_dismiss_all", + "menu_back", + ], + description_placeholders={ + "count": str(len(pending)), + "pending_table": preview, + }, + ) + + async def async_step_learning_feedbacks_pick( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step: pick a specific pending feedback to review.""" + if user_input is not None: + cycle_id = user_input.get("selected_feedback") + if cycle_id: + self._selected_cycle_id = cycle_id + return await self.async_step_resolve_feedback() + return await self.async_step_learning_feedbacks() + + manager = self.hass.data[DOMAIN][self._config_entry.entry_id] + profile_store = manager.profile_store + pending = profile_store.get_pending_feedback() + if not pending: + return await self.async_step_learning_feedbacks() + + sorted_pending = sorted( + pending.values(), + key=lambda x: x.get("created_at", ""), + reverse=True, + ) + options = [] + for item in sorted_pending: + cid = item.get("cycle_id", "unknown") + prof = item.get("detected_profile", "Unknown") + conf = item.get("confidence", 0.0) + created_raw = item.get("created_at", "") + t_str = str(created_raw) + if created_raw: + try: + dt = dt_util.parse_datetime(str(created_raw)) + if dt: + t_str = dt_util.as_local(dt).strftime("%d %b %H:%M") + except Exception: # pylint: disable=broad-exception-caught + pass + label = f"{prof} ({int(conf * 100)}%) - {t_str}" + options.append(selector.SelectOptionDict(value=cid, label=label)) + + return self.async_show_form( + step_id="learning_feedbacks_pick", + data_schema=vol.Schema( + { + vol.Required("selected_feedback"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.LIST, + ) + ), + } + ), + ) + + async def async_step_learning_feedbacks_dismiss_all( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step: Confirm bulk dismissal of all pending learning feedbacks.""" + manager = self.hass.data[DOMAIN][self._config_entry.entry_id] + profile_store = manager.profile_store + pending = profile_store.get_pending_feedback() + count = len(pending) + + if user_input is not None: + if ( + user_input.get("confirm_dismiss_all") + and count > 0 + and hasattr(manager, "learning_manager") + ): + # Snapshot IDs first since submit mutates the pending dict + cycle_ids = list(pending.keys()) + for cid in cycle_ids: + await manager.learning_manager.async_submit_cycle_feedback( + cycle_id=cid, + user_confirmed=False, + corrected_profile=None, + corrected_duration=None, + dismiss=True, + ) + return await self.async_step_init() + + return self.async_show_form( + step_id="learning_feedbacks_dismiss_all", + data_schema=vol.Schema( + { + vol.Optional( + "confirm_dismiss_all", default=False + ): selector.BooleanSelector(), + } + ), + description_placeholders={"count": str(count)}, + ) + + async def async_step_learning_feedbacks_empty( + self, _user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step: Handle empty feedback list (go back).""" + return await self.async_step_init() + + async def async_step_resolve_feedback( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Step: Resolve a specific feedback request.""" + manager = self.hass.data[DOMAIN][self._config_entry.entry_id] + profile_store = manager.profile_store + pending = profile_store.get_pending_feedback() + + cycle_id = self._selected_cycle_id + if not cycle_id or cycle_id not in pending: + return self.async_abort(reason="feedback_not_found") + + item = pending[cycle_id] + + if user_input is not None: + # Process submission based on action + action = user_input.get("action", "confirm") + + if action == "confirm": + # User confirms the detection was correct + if hasattr(manager, "learning_manager"): + await manager.learning_manager.async_submit_cycle_feedback( + cycle_id=cycle_id, + user_confirmed=True, + corrected_profile=None, + corrected_duration=None, + dismiss=False, + ) + elif action == "correct": + # User wants to correct the profile/duration + new_profile = user_input.get("corrected_profile") + new_duration = user_input.get("corrected_duration") + if hasattr(manager, "learning_manager"): + await manager.learning_manager.async_submit_cycle_feedback( + cycle_id=cycle_id, + user_confirmed=False, + corrected_profile=new_profile, + corrected_duration=int(new_duration * 60) if new_duration else None, + dismiss=False, + ) + elif action == "ignore": + # User wants to dismiss/ignore this feedback request + if hasattr(manager, "learning_manager"): + await manager.learning_manager.async_submit_cycle_feedback( + cycle_id=cycle_id, + user_confirmed=False, + corrected_profile=None, + corrected_duration=None, + dismiss=True, + ) + elif action == "delete": + # User wants to delete the cycle + await manager.profile_store.delete_cycle(cycle_id) + + # Return to main menu + return await self.async_step_init() + + # Prepare form data + detected_profile = item.get("detected_profile", "Unknown") + confidence = item.get("confidence", 0.0) + est = item.get("estimated_duration", 0) + act = item.get("actual_duration", 0) + + # Get the actual cycle to access power data for visualization + cycles = profile_store.get_past_cycles() + actual_cycle = next((c for c in cycles if c.get("id") == cycle_id), None) + + # Build description with visualization + comparison_img = "" + candidates_table = "" + safe_cycle_id = slugify(cycle_id) + ts = int(time.time()) + + # 1. Generate a single combined SVG overlaying all profiles vs. actual cycle + if actual_cycle and actual_cycle.get("power_data"): + try: + stats_dir = self.hass.config.path("www", "ha_washdata", "feedback") + await self.hass.async_add_executor_job( + lambda: os.makedirs(stats_dir, exist_ok=True) + ) + + all_profile_names = list(profile_store.get_profiles().keys()) + all_profile_names.sort(key=profile_sort_key) + if detected_profile in all_profile_names: + all_profile_names.remove(detected_profile) + all_profile_names.insert(0, detected_profile) + + svg_content = await self.hass.async_add_executor_job( + profile_store.generate_feedback_multi_profile_svg, + all_profile_names, + detected_profile, + actual_cycle, + "Profile Comparison", + "This cycle (actual)", + ) + + if svg_content: + fpath = f"{stats_dir}/comparison_{safe_cycle_id}.svg" + + def _write(p=fpath, c=svg_content): + with open(p, "w", encoding="utf-8") as f: + f.write(c) + + await self.hass.async_add_executor_job(_write) + url = f"/local/ha_washdata/feedback/comparison_{safe_cycle_id}.svg?v={ts}" + comparison_img = f"![Profile Comparison]({url})\n\n" + + except Exception as e: # pylint: disable=broad-exception-caught + _LOGGER.warning("Failed to generate multi-profile SVG: %s", e) + + # 2. Add candidates table if ranking data available + ranking = item.get("ranking", []) + if ranking: + # Reconstruct MatchResult-like object for the summary method + match_result_data = { + "ranking": ranking, + "expected_duration": est, + } + + # Create a minimal object that has the expected attributes + class _MatchResultLike: + def __init__(self, data): + self.ranking = data.get("ranking", []) + self.expected_duration = data.get("expected_duration", 0) + + match_like = _MatchResultLike(match_result_data) + + candidates = await self.hass.async_add_executor_job( + profile_store.get_match_candidates_summary, match_like, 3 + ) + + if candidates: + candidates_table = ( + "### Top Candidates\n" + "| Profile | Confidence | MAE | Correlation | Duration Match |\n" + "| --- | --- | --- | --- | --- |\n" + ) + + for cand in candidates: + name = cand.get("profile_name", "Unknown") + conf = cand.get("confidence_pct", 0) + mae = cand.get("mae", 0) + corr = cand.get("correlation", 0) + dur_ratio = cand.get("duration_ratio", 0) + dur_sign = "+" if dur_ratio >= 0 else "" + + candidates_table += ( + f"| {name} | {conf}% | {mae} | {corr} | {dur_sign}{dur_ratio}% |\n" + ) + + candidates_table += "\n" + + profiles = list(profile_store.get_profiles().keys()) + profiles.sort(key=profile_sort_key) + + return self.async_show_form( + step_id="resolve_feedback", + description_placeholders={ + "comparison_data": ( + comparison_img + + candidates_table + + f"\n**Detected Profile**: {detected_profile} ({int(confidence * 100)}%)\n" + f"**Estimated Duration**: {int(est / 60)} min\n" + f"**Actual Duration**: {int(act / 60)} min" + ), + "comparison_img": comparison_img, + "candidates_table": candidates_table, + "detected_profile": detected_profile, + "confidence_pct": str(int(confidence * 100)), + "est_duration_min": str(int(est / 60)), + "act_duration_min": str(int(act / 60)), + }, + data_schema=vol.Schema( + { + vol.Required("action", default="confirm"): self._translated_select( + options=["confirm", "correct", "ignore", "delete"], + translation_key="resolve_feedback_action", + mode=selector.SelectSelectorMode.LIST, + ), + vol.Optional("corrected_profile", default=detected_profile): selector.SelectSelector( + selector.SelectSelectorConfig( + options=profiles, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ), + vol.Optional("corrected_duration", default=int(act/60)): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, max=600, unit_of_measurement="min", mode=selector.NumberSelectorMode.BOX + ) + ), + } + ), + ) + + # ------------------------------------------------------------------ + # Cycle Trimmer + # ------------------------------------------------------------------ + + def _wallclock_to_offset( + self, time_str: str, cycle_start_dt: datetime, cycle_end_dt: datetime + ) -> float | None: + """Parse an HH:MM:SS time string and return offset seconds from cycle_start_dt. + + Returns None if the time cannot be parsed or falls outside the recorded cycle. + """ + try: + local_start = dt_util.as_local(cycle_start_dt) + parts = time_str.split(":") + h = int(parts[0]) + m = int(parts[1]) + s = int(parts[2]) if len(parts) > 2 else 0 + candidate = local_start.replace(hour=h, minute=m, second=s, microsecond=0) + if candidate < local_start: + candidate += timedelta(days=1) + offset_seconds = (candidate - local_start).total_seconds() + cycle_duration = (cycle_end_dt - cycle_start_dt).total_seconds() + if offset_seconds < 0 or offset_seconds > cycle_duration: + return None + return offset_seconds + except (ValueError, IndexError, AttributeError): + return None + + def _trim_cycle_svg_markdown( + self, + p_data: list[tuple[float, float]], + trim_start_s: float, + trim_end_s: float, + title: str = "Cycle Trim Preview", + label_min: str = "min", + no_data_text: str = "No power data available for this cycle.", + cycle_start_dt: datetime | None = None, + ) -> str: + """Render the cycle power curve with trim-region overlays as a base64 SVG.""" + + def esc(text: object) -> str: + return ( + str(text) + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) + + width = 1360 + plot_left = 60 + plot_right = 60 + plot_top = 110 + plot_height = 300 + plot_width = width - plot_left - plot_right + axis_y = plot_top + plot_height + 52 + axis_y2 = axis_y + 36 + total_height = (axis_y2 + 32) if cycle_start_dt is not None else (axis_y + 40) + + if not p_data or len(p_data) < 2: + empty_svg = ( + f"" + "" + f"{esc(title)}" + f"" + f"{esc(no_data_text)}" + "" + ) + encoded = base64.b64encode(empty_svg.encode("utf-8")).decode("ascii") + return f"![Trim preview](data:image/svg+xml;base64,{encoded})" + + max_time_s = p_data[-1][0] + max_power = max((p for _, p in p_data), default=0.0) + max_power = max(10.0, max_power) * 1.08 + max_time_s = max(1.0, max_time_s) + + def to_x(t: float) -> float: + return plot_left + (max(0.0, min(max_time_s, t)) / max_time_s) * plot_width + + def to_y(p: float) -> float: + return plot_top + plot_height - (max(0.0, min(max_power, p)) / max_power) * plot_height + + def pick_grid_interval(total_min_val: int) -> int: + label_w_px = 130 + min_iv = (label_w_px / plot_width) * total_min_val + for candidate in (1, 2, 5, 10, 15, 20, 30, 60): + if candidate > min_iv: + return candidate + return 60 + + parts: list[str] = [ + f"", + "", + f"", + ] + + # Title + parts.append( + f"{esc(title)}" + ) + + # Time grid + total_min = int(max_time_s / 60) + grid_interval = pick_grid_interval(total_min) + for tick_min in range(0, total_min + 1, grid_interval): + tx = to_x(tick_min * 60.0) + parts.append( + f"" + ) + if tx <= plot_left + 50: + t_anchor, t_x = "start", plot_left + elif tx >= width - plot_right - 50: + t_anchor, t_x = "end", width - plot_right + else: + t_anchor, t_x = "middle", tx + if cycle_start_dt is not None: + tick_dt = dt_util.as_local(cycle_start_dt + timedelta(seconds=tick_min * 60)) + parts.append( + f"" + f"{esc(tick_dt.strftime('%H:%M'))}" + ) + parts.append( + f"" + f"+{tick_min} {esc(label_min)}" + ) + else: + parts.append( + f"" + f"{tick_min} {esc(label_min)}" + ) + + # Trimmed-region shading (before start and after end) + x_start = to_x(trim_start_s) + x_end = to_x(trim_end_s) + if x_start > plot_left: + parts.append( + f"" + ) + if x_end < plot_left + plot_width: + parts.append( + f"" + ) + + # Power curve polyline + pts = " ".join(f"{to_x(t):.2f},{to_y(p):.2f}" for t, p in p_data) + parts.append( + f"" + ) + + # Trim marker lines and labels + _label_offset_y = plot_top - 10 + _label_half_w = 105 + for t_s, color, key in ( + (trim_start_s, "#22c55e", "S"), + (trim_end_s, "#ef4444", "E"), + ): + tx = to_x(t_s) + if cycle_start_dt is not None: + t_dt = dt_util.as_local(cycle_start_dt + timedelta(seconds=t_s)) + t_label = t_dt.strftime("%H:%M:%S") + else: + t_label = f"{int(t_s / 60)}:{int(t_s % 60):02d}" + label_x = max(plot_left + _label_half_w, min(width - plot_right - _label_half_w, tx)) + parts.append( + f"" + ) + parts.append( + f"{key}: {esc(t_label)}" + ) + + parts.append("") + svg_str = "\n".join(parts) + encoded = base64.b64encode(svg_str.encode("utf-8")).decode("ascii") + return f"![Trim preview](data:image/svg+xml;base64,{encoded})" + + async def async_step_trim_cycle_select( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Select a cycle to trim.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + cycles = store.get_past_cycles()[-20:] + options = [] + for c in reversed(cycles): + dt = dt_util.parse_datetime(c["start_time"]) + start = dt_util.as_local(dt).strftime("%Y-%m-%d %H:%M") if dt else c["start_time"] + duration_min = int(c.get("manual_duration", c["duration"]) / 60) + prof = c.get("profile_name") or await self._selector_text("unlabeled", "Unlabeled") + status = c.get("status", "completed") + status_icon = ( + "✓" if status in ("completed", "force_stopped") + else "⚠" if status == "resumed" + else "✗" + ) + label = f"[{status_icon}] {start} - {duration_min}m - {prof}" + options.append(selector.SelectOptionDict(value=c["id"], label=label)) + + if not options: + return self.async_abort(reason="no_cycles_found") + + if user_input is not None: + cycle_id = user_input["cycle_id"] + p_data = store.get_cycle_power_data(cycle_id) + if not p_data: + return self.async_abort(reason="no_power_data") + self._trim_cycle_id = cycle_id + self._trim_start_s = 0.0 + self._trim_end_s = p_data[-1][0] + cycle_obj = next((c for c in store.get_past_cycles() if c["id"] == cycle_id), None) + if cycle_obj and cycle_obj.get("start_time"): + self._trim_cycle_start_dt = dt_util.parse_datetime(cycle_obj["start_time"]) + else: + self._trim_cycle_start_dt = None + return await self.async_step_trim_cycle() + + return self.async_show_form( + step_id="trim_cycle_select", + data_schema=vol.Schema( + { + vol.Required("cycle_id"): selector.SelectSelector( + selector.SelectSelectorConfig( + options=options, + mode=selector.SelectSelectorMode.DROPDOWN, + ) + ) + } + ), + ) + + async def async_step_trim_cycle( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Main cycle trimmer editor.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if not self._trim_cycle_id: + return await self.async_step_trim_cycle_select() + + p_data = store.get_cycle_power_data(self._trim_cycle_id) + if not p_data: + return self.async_abort(reason="no_power_data") + + full_end_s = p_data[-1][0] + errors: dict[str, str] = {} + + if user_input is not None: + action = user_input.get("action") + if action == "set_start": + return await self.async_step_trim_cycle_start() + if action == "set_end": + return await self.async_step_trim_cycle_end() + if action == "reset": + self._trim_start_s = 0.0 + self._trim_end_s = full_end_s + if action == "apply": + if self._trim_start_s >= self._trim_end_s: + errors["base"] = "trim_range_invalid" + else: + ok = await store.trim_cycle_power_data( + self._trim_cycle_id, + self._trim_start_s, + self._trim_end_s, + ) + if ok: + manager.notify_update() + self._trim_cycle_id = None + return await self.async_step_manage_cycles() + errors["base"] = "trim_failed" + if action == "cancel": + self._trim_cycle_id = None + return await self.async_step_manage_cycles() + + label_min = await self._options_text("unit_min", "min") + svg = self._trim_cycle_svg_markdown( + p_data, + self._trim_start_s, + self._trim_end_s, + title=await self._options_text("trim_cycle_preview_title", "Cycle Trim Preview"), + label_min=label_min, + no_data_text=await self._options_text( + "trim_cycle_preview_no_data", "No power data available for this cycle." + ), + cycle_start_dt=self._trim_cycle_start_dt, + ) + + kept_s = max(0.0, self._trim_end_s - self._trim_start_s) + kept_min = int(kept_s / 60) + kept_sec = int(kept_s % 60) + kept_suffix = await self._options_text("trim_cycle_preview_kept_suffix", "kept") + if self._trim_cycle_start_dt is not None: + start_dt = dt_util.as_local( + self._trim_cycle_start_dt + timedelta(seconds=self._trim_start_s) + ) + end_dt = dt_util.as_local( + self._trim_cycle_start_dt + timedelta(seconds=self._trim_end_s) + ) + summary = ( + f"{start_dt.strftime('%H:%M:%S')} - {end_dt.strftime('%H:%M:%S')}" + f" ({kept_min}:{kept_sec:02d} {kept_suffix})" + ) + else: + start_min = int(self._trim_start_s / 60) + start_sec = int(self._trim_start_s % 60) + end_min = int(self._trim_end_s / 60) + end_sec = int(self._trim_end_s % 60) + summary = ( + f"{start_min}:{start_sec:02d} - {end_min}:{end_sec:02d}" + f" ({kept_min}:{kept_sec:02d} {kept_suffix})" + ) + + return self.async_show_form( + step_id="trim_cycle", + data_schema=vol.Schema( + { + vol.Required("action"): self._translated_select( + options=["set_start", "set_end", "reset", "apply", "cancel"], + translation_key="trim_cycle_action", + ) + } + ), + errors=errors, + description_placeholders={ + "trim_summary": summary, + "trim_svg": svg, + }, + ) + + async def async_step_trim_cycle_start( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Set the trim start point.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if not self._trim_cycle_id: + return await self.async_step_trim_cycle_select() + + p_data = store.get_cycle_power_data(self._trim_cycle_id) + if not p_data: + return self.async_abort(reason="no_power_data") + + full_end_s = p_data[-1][0] + cycle_start_dt = self._trim_cycle_start_dt + errors: dict[str, str] = {} + + if user_input is not None: + time_str = (user_input.get("trim_start_time") or "").strip() + if time_str and cycle_start_dt is not None: + cycle_end_dt = cycle_start_dt + timedelta(seconds=full_end_s) + new_start_s = self._wallclock_to_offset(time_str, cycle_start_dt, cycle_end_dt) + if new_start_s is None: + errors["trim_start_time"] = "trim_range_invalid" + elif new_start_s >= self._trim_end_s: + errors["trim_start_time"] = "trim_range_invalid" + else: + self._trim_start_s = new_start_s + return await self.async_step_trim_cycle() + else: + min_val = user_input.get("trim_start_min") + if min_val is not None: + new_start_s = float(min_val) * 60.0 + if new_start_s >= self._trim_end_s: + errors["trim_start_min"] = "trim_range_invalid" + else: + self._trim_start_s = new_start_s + return await self.async_step_trim_cycle() + + if cycle_start_dt is not None: + local_start = dt_util.as_local(cycle_start_dt) + local_end = dt_util.as_local(cycle_start_dt + timedelta(seconds=full_end_s)) + current_wallclock = dt_util.as_local( + cycle_start_dt + timedelta(seconds=self._trim_start_s) + ).strftime("%H:%M:%S") + current_offset_min = int(self._trim_start_s / 60) + data_schema = vol.Schema({ + vol.Required("trim_start_time", default=current_wallclock): selector.TimeSelector(), + }) + desc_placeholders = { + "cycle_start_wallclock": local_start.strftime("%H:%M:%S"), + "cycle_end_wallclock": local_end.strftime("%H:%M:%S"), + "current_wallclock": current_wallclock, + "current_offset_min": str(current_offset_min), + } + else: + total_min = max(1, int(full_end_s / 60)) + default_min = int(self._trim_start_s / 60) + data_schema = vol.Schema({ + vol.Required("trim_start_min", default=default_min): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=total_min - 1, + step=1, + unit_of_measurement="min", + mode=selector.NumberSelectorMode.BOX, + ) + ), + }) + desc_placeholders = { + "cycle_start_wallclock": "unknown", + "cycle_end_wallclock": "unknown", + "current_wallclock": f"{int(self._trim_start_s / 60)} min", + "current_offset_min": str(int(self._trim_start_s / 60)), + } + + return self.async_show_form( + step_id="trim_cycle_start", + data_schema=data_schema, + errors=errors, + description_placeholders=desc_placeholders, + ) + + async def async_step_trim_cycle_end( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Set the trim end point.""" + manager = self.hass.data[DOMAIN][self.config_entry.entry_id] + store = manager.profile_store + + if not self._trim_cycle_id: + return await self.async_step_trim_cycle_select() + + p_data = store.get_cycle_power_data(self._trim_cycle_id) + if not p_data: + return self.async_abort(reason="no_power_data") + + full_end_s = p_data[-1][0] + cycle_start_dt = self._trim_cycle_start_dt + errors: dict[str, str] = {} + + if user_input is not None: + time_str = (user_input.get("trim_end_time") or "").strip() + if time_str and cycle_start_dt is not None: + cycle_end_dt = cycle_start_dt + timedelta(seconds=full_end_s) + new_end_s = self._wallclock_to_offset(time_str, cycle_start_dt, cycle_end_dt) + if new_end_s is None: + errors["trim_end_time"] = "trim_range_invalid" + elif new_end_s <= self._trim_start_s: + errors["trim_end_time"] = "trim_range_invalid" + else: + self._trim_end_s = new_end_s + return await self.async_step_trim_cycle() + else: + min_val = user_input.get("trim_end_min") + if min_val is not None: + new_end_s = float(min_val) * 60.0 + if new_end_s <= self._trim_start_s: + errors["trim_end_min"] = "trim_range_invalid" + else: + self._trim_end_s = new_end_s + return await self.async_step_trim_cycle() + + if cycle_start_dt is not None: + local_start = dt_util.as_local(cycle_start_dt) + local_end = dt_util.as_local(cycle_start_dt + timedelta(seconds=full_end_s)) + current_wallclock = dt_util.as_local( + cycle_start_dt + timedelta(seconds=self._trim_end_s) + ).strftime("%H:%M:%S") + current_offset_min = int(self._trim_end_s / 60) + data_schema = vol.Schema({ + vol.Required("trim_end_time", default=current_wallclock): selector.TimeSelector(), + }) + desc_placeholders = { + "cycle_start_wallclock": local_start.strftime("%H:%M:%S"), + "cycle_end_wallclock": local_end.strftime("%H:%M:%S"), + "current_wallclock": current_wallclock, + "current_offset_min": str(current_offset_min), + } + else: + total_min = max(1, int(full_end_s / 60) + 1) + default_min = int(self._trim_end_s / 60) + data_schema = vol.Schema({ + vol.Required("trim_end_min", default=default_min): selector.NumberSelector( + selector.NumberSelectorConfig( + min=0, + max=total_min, + step=1, + unit_of_measurement="min", + mode=selector.NumberSelectorMode.BOX, + ) + ), + }) + desc_placeholders = { + "cycle_start_wallclock": "unknown", + "cycle_end_wallclock": "unknown", + "current_wallclock": f"{int(self._trim_end_s / 60)} min", + "current_offset_min": str(int(self._trim_end_s / 60)), + } + + return self.async_show_form( + step_id="trim_cycle_end", + data_schema=data_schema, + errors=errors, + description_placeholders=desc_placeholders, + ) \ No newline at end of file diff --git a/custom_components/ha_washdata/const.py b/custom_components/ha_washdata/const.py new file mode 100644 index 0000000..7ad3ea8 --- /dev/null +++ b/custom_components/ha_washdata/const.py @@ -0,0 +1,461 @@ +"""Constants for the WashData integration.""" + +DOMAIN = "ha_washdata" + +# Configuration keys +CONF_POWER_SENSOR = "power_sensor" +CONF_NAME = "name" +CONF_MIN_POWER = "min_power" +CONF_OFF_DELAY = "off_delay" +CONF_NOTIFY_SERVICE = "notify_service" # Deprecated - kept for migration only +CONF_NOTIFY_ACTIONS = "notify_actions" +CONF_NOTIFY_PEOPLE = "notify_people" +CONF_NOTIFY_ONLY_WHEN_HOME = "notify_only_when_home" +CONF_NOTIFY_FIRE_EVENTS = "notify_fire_events" +CONF_NOTIFY_EVENTS = "notify_events" # Deprecated - kept for migration only +CONF_NOTIFY_START_SERVICES = "notify_start_services" +CONF_NOTIFY_FINISH_SERVICES = "notify_finish_services" +CONF_NOTIFY_LIVE_SERVICES = "notify_live_services" +CONF_NO_UPDATE_ACTIVE_TIMEOUT = "no_update_active_timeout" +CONF_LOW_POWER_NO_UPDATE_TIMEOUT = "low_power_no_update_timeout" +CONF_SMOOTHING_WINDOW = "smoothing_window" +CONF_SAMPLING_INTERVAL = "sampling_interval" +CONF_START_DURATION_THRESHOLD = ( + "start_duration_threshold" # Debounce for start detection +) +CONF_DEVICE_TYPE = "device_type" +CONF_PROFILE_DURATION_TOLERANCE = "profile_duration_tolerance" + + +CONF_INTERRUPTED_MIN_SECONDS = "interrupted_min_seconds" # Internal use only +CONF_PROGRESS_RESET_DELAY = "progress_reset_delay" +CONF_LEARNING_CONFIDENCE = "learning_confidence" +CONF_DURATION_TOLERANCE = "duration_tolerance" +CONF_AUTO_LABEL_CONFIDENCE = "auto_label_confidence" +CONF_AUTO_MAINTENANCE = "auto_maintenance" +CONF_PROFILE_MATCH_INTERVAL = "profile_match_interval" +CONF_PROFILE_MATCH_MIN_DURATION_RATIO = "profile_match_min_duration_ratio" +CONF_PROFILE_MATCH_MAX_DURATION_RATIO = "profile_match_max_duration_ratio" +CONF_MAX_PAST_CYCLES = "max_past_cycles" +CONF_MAX_FULL_TRACES_PER_PROFILE = "max_full_traces_per_profile" +CONF_MAX_FULL_TRACES_UNLABELED = "max_full_traces_unlabeled" +CONF_WATCHDOG_INTERVAL = "watchdog_interval" # Derived from sampling_interval +CONF_MATCH_PERSISTENCE = "match_persistence" +CONF_COMPLETION_MIN_SECONDS = "completion_min_seconds" +CONF_NOTIFY_BEFORE_END_MINUTES = "notify_before_end_minutes" +CONF_APPLY_SUGGESTIONS = "apply_suggestions" +CONF_RUNNING_DEAD_ZONE = "running_dead_zone" # Seconds after start to ignore power dips +CONF_END_REPEAT_COUNT = "end_repeat_count" # Number of times end condition must be met +CONF_SHOW_ADVANCED = "show_advanced" # Toggle advanced settings +CONF_MIN_OFF_GAP = "min_off_gap" # Minimum gap to separate cycles (seconds) +CONF_START_ENERGY_THRESHOLD = "start_energy_threshold" # Wh required to confirm start +CONF_END_ENERGY_THRESHOLD = "end_energy_threshold" # Wh allowed during end candidates +CONF_START_THRESHOLD_W = "start_threshold_w" # Custom power threshold for STARTING +CONF_STOP_THRESHOLD_W = ( + "stop_threshold_w" # Custom power threshold for ENDING (hysteresis) +) +CONF_EXPOSE_DEBUG_ENTITIES = "expose_debug_entities" # Expose detailed debug sensors +CONF_SAVE_DEBUG_TRACES = ( + "save_debug_traces" # Improve historical cycle data with rich debug info +) +# Cycle interruption detection settings (not exposed in UI, but used internally) +CONF_ABRUPT_DROP_WATTS = "abrupt_drop_watts" # Power cliff threshold for interrupted status +CONF_ABRUPT_DROP_RATIO = "abrupt_drop_ratio" # Relative drop ratio for interrupted status +CONF_ABRUPT_HIGH_LOAD_FACTOR = "abrupt_high_load_factor" # High load factor threshold +CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD = "auto_tune_noise_events_threshold" # Noise events before auto-tune +CONF_EXTERNAL_END_TRIGGER_ENABLED = "external_end_trigger_enabled" # Enable external cycle end trigger +CONF_EXTERNAL_END_TRIGGER = "external_end_trigger" # Binary sensor entity for external cycle end +CONF_EXTERNAL_END_TRIGGER_INVERTED = "external_end_trigger_inverted" # Invert external trigger logic (trigger on OFF) +CONF_ANTI_WRINKLE_ENABLED = "anti_wrinkle_enabled" # Dryer anti-wrinkle shielding +CONF_ANTI_WRINKLE_MAX_POWER = "anti_wrinkle_max_power" # W threshold for anti-wrinkle spikes +CONF_ANTI_WRINKLE_MAX_DURATION = "anti_wrinkle_max_duration" # Seconds to treat as anti-wrinkle +CONF_ANTI_WRINKLE_EXIT_POWER = "anti_wrinkle_exit_power" # W threshold for true-off exit +CONF_DELAY_START_DETECT_ENABLED = "delay_start_detect_enabled" # Enable delayed-start detection +CONF_DELAY_CONFIRM_SECONDS = "delay_confirm_seconds" # Seconds power must stay in standby band before DELAY_WAIT engages +CONF_DELAY_TIMEOUT_HOURS = "delay_timeout_hours" # Safety timeout (hours) while waiting to start + +# Deprecated since 0.4.5: drain-spike model replaced by band-based DELAY_WAIT. +# Kept only so older Store/options blobs don't raise KeyError during migration. +CONF_DELAY_DRAIN_MIN_POWER = "delay_drain_min_power" +CONF_DELAY_DRAIN_MAX_POWER = "delay_drain_max_power" +CONF_DELAY_DRAIN_MAX_DURATION = "delay_drain_max_duration" + + +NOTIFY_EVENT_START = "cycle_start" +NOTIFY_EVENT_FINISH = "cycle_finish" +NOTIFY_EVENT_LIVE = "cycle_live" +NOTIFY_EVENT_CLEAN = "cycle_clean" # Laundry still inside after cycle ends + +CONF_NOTIFY_TITLE = "notify_title" +CONF_NOTIFY_ICON = "notify_icon" +CONF_NOTIFY_START_MESSAGE = "notify_start_message" +CONF_NOTIFY_FINISH_MESSAGE = "notify_finish_message" +CONF_NOTIFY_PRE_COMPLETE_MESSAGE = "notify_pre_complete_message" +CONF_NOTIFY_LIVE_INTERVAL_SECONDS = "notify_live_interval_seconds" +CONF_NOTIFY_LIVE_OVERRUN_PERCENT = "notify_live_overrun_percent" +CONF_NOTIFY_LIVE_CHRONOMETER = "notify_live_chronometer" +CONF_NOTIFY_REMINDER_MESSAGE = "notify_reminder_message" # Distinct one-time pre-end alert +CONF_NOTIFY_TIMEOUT_SECONDS = "notify_timeout_seconds" # Auto-dismiss after N seconds (0 = never) +CONF_NOTIFY_CHANNEL = "notify_channel" # Android channel for status/live/reminder +CONF_NOTIFY_FINISH_CHANNEL = "notify_finish_channel" # Distinct Android channel for finished/clean +CONF_ENERGY_PRICE_STATIC = "energy_price_static" +CONF_ENERGY_PRICE_ENTITY = "energy_price_entity" + +# Door sensor & pause +CONF_DOOR_SENSOR_ENTITY = "door_sensor_entity" # Optional binary_sensor for machine door +CONF_PAUSE_CUTS_POWER = "pause_cuts_power" # Also turn off switch entity when pausing +CONF_SWITCH_ENTITY = "switch_entity" # Optional switch entity toggled on pause/resume +CONF_NOTIFY_UNLOAD_DELAY_MINUTES = "notify_unload_delay_minutes" # Minutes before "laundry waiting" nag +CONF_NOTIFY_UNLOAD_MESSAGE = "notify_unload_message" # Template for the clean-laundry nag message + +# Optional link to an existing HA device (e.g. the smart plug or appliance). +# When set, the WashData device is exposed as "Connected via " through +# the device registry's via_device relationship. Stores a device registry id. +CONF_LINKED_DEVICE = "linked_device" + +DEFAULT_NOTIFY_TITLE = "WashData: {device}" +DEFAULT_NOTIFY_START_MESSAGE = "{device} started." +DEFAULT_NOTIFY_FINISH_MESSAGE = "{device} finished. Duration: {duration}m." +DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE = "{device}: Less than {minutes} minutes remaining." +DEFAULT_NOTIFY_REMINDER_MESSAGE = "{device}: about {minutes} minutes left." +DEFAULT_NOTIFY_LIVE_WAITING_MESSAGE = "{device}: No profile matched yet." +DEFAULT_NOTIFY_ONLY_WHEN_HOME = False +DEFAULT_NOTIFY_FIRE_EVENTS = True +DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS = 300 +DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT = 20 +DEFAULT_NOTIFY_LIVE_CHRONOMETER = False +DEFAULT_NOTIFY_TIMEOUT_SECONDS = 0 # 0 = notifications never auto-dismiss +DEFAULT_NOTIFY_CHANNEL = "" # Empty = omit channel (companion app default) +DEFAULT_NOTIFY_FINISH_CHANNEL = "" # Empty = reuse status channel +DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES = 60 # 1 hour before "still waiting" nag notification +DEFAULT_NOTIFY_UNLOAD_MESSAGE = "{device} finished {duration}m ago - laundry is still inside." + +# Defaults +DEFAULT_MIN_POWER = 2.0 # Watts +DEFAULT_OFF_DELAY = 180 # Seconds (3 minutes, safer for 60s polling) +DEFAULT_NAME = "Washing Machine" +# Seconds without updates while active before forced stop (publish-on-change sockets) +DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT = 600 # 10 minutes +DEFAULT_SMOOTHING_WINDOW = 2 +DEFAULT_SAMPLING_INTERVAL = 30.0 # Seconds +DEFAULT_START_DURATION_THRESHOLD = 5.0 # Seconds (debounce) +DEFAULT_START_ENERGY_THRESHOLD = 0.2 # Wh - Require some energy accumulation before starting +DEFAULT_END_ENERGY_THRESHOLD = 0.05 # Wh - Require effectively zero energy to end +DEFAULT_DEVICE_TYPE = "washing_machine" +DEFAULT_PROFILE_DURATION_TOLERANCE = 0.25 + +DEFAULT_INTERRUPTED_MIN_SECONDS = 150 # Internal use only, not exposed + +DEFAULT_PROGRESS_RESET_DELAY = 1800 # Seconds (30 minutes state expiry/unload window) +DEFAULT_LEARNING_CONFIDENCE = 0.6 # Minimum confidence to request user verification +DEFAULT_DURATION_TOLERANCE = 0.10 # Allow ±10% duration variance before flagging +DEFAULT_AUTO_LABEL_CONFIDENCE = 0.9 # High confidence auto-label threshold +DEFAULT_AUTO_MAINTENANCE = True # Enable nightly cleanup by default +DEFAULT_COMPLETION_MIN_SECONDS = 600 # 10 minutes +DEFAULT_NOTIFY_BEFORE_END_MINUTES = 0 # Disabled +DEFAULT_PROFILE_MATCH_INTERVAL = ( + 300 # Seconds between profile matching attempts (5 minutes) +) +DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO = 0.10 # Allow match after 10% of expected duration +DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO = ( + 1.3 # Maximum duration ratio (130% of profile) - hidden default +) +DEFAULT_MAX_PAST_CYCLES = 200 +DEFAULT_MAX_FULL_TRACES_PER_PROFILE = 20 +DEFAULT_MAX_FULL_TRACES_UNLABELED = 20 +DEFAULT_WATCHDOG_INTERVAL = 30 # Derived: 2 * sampling_interval + 1 +DEFAULT_MATCH_PERSISTENCE = 3 +DEFAULT_RUNNING_DEAD_ZONE = 3 # Seconds after start to ignore power dips +DEFAULT_END_REPEAT_COUNT = 1 # 1 = current behavior (no repeat required) + +# Matching & Termination Stability +DEFAULT_MATCH_REVERT_RATIO = 0.4 # Drop from peak score to revert to detecting +DEFAULT_DEFER_FINISH_CONFIDENCE = 0.55 # Minimum confidence to defer cycle finish + +# Cycle interruption detection defaults (internal) +DEFAULT_ABRUPT_DROP_WATTS = 500.0 # Power cliff detection threshold (W) +DEFAULT_ABRUPT_DROP_RATIO = 0.6 # 60% drop considered abrupt +DEFAULT_ABRUPT_HIGH_LOAD_FACTOR = 5.0 # High load factor threshold +DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD = 3 # Ghost cycles before threshold adjustment + +# Anti-wrinkle defaults (advanced; disabled by default) +DEFAULT_ANTI_WRINKLE_ENABLED = False +DEFAULT_ANTI_WRINKLE_MAX_POWER = 400.0 # W +DEFAULT_ANTI_WRINKLE_MAX_DURATION = 60.0 # s +DEFAULT_ANTI_WRINKLE_EXIT_POWER = 0.8 # W + +# Delayed-start detection defaults (disabled by default). +# +# The detector watches for sustained power between stop_threshold_w and +# start_threshold_w (the "standby band"): a machine sitting in that band +# for at least DEFAULT_DELAY_CONFIRM_SECONDS is in delayed-start mode, not +# off and not running. Short menu-navigation peaks above the band are +# ignored because they don't sustain long enough to satisfy the normal +# start-duration gate. +DEFAULT_DELAY_START_DETECT_ENABLED = False +DEFAULT_DELAY_CONFIRM_SECONDS = 60.0 # s — sustained standby before DELAY_WAIT engages +DEFAULT_DELAY_TIMEOUT_HOURS = 8.0 # h — give up waiting after this long + +# Pump Monitor settings (pump device type only) +CONF_PUMP_STUCK_DURATION = "pump_stuck_duration" # Seconds before a running pump is flagged as stuck +DEFAULT_PUMP_STUCK_DURATION = 1800 # 30 min - typical sump pump runs <60 s; 30 min implies motor is jammed +EVENT_PUMP_STUCK = "ha_washdata_pump_stuck" # Fired when stuck-pump threshold is exceeded + +# Profile Matching Thresholds +CONF_PROFILE_MATCH_THRESHOLD = "profile_match_threshold" +CONF_PROFILE_UNMATCH_THRESHOLD = "profile_unmatch_threshold" + +DEFAULT_PROFILE_MATCH_THRESHOLD = 0.4 +DEFAULT_PROFILE_UNMATCH_THRESHOLD = 0.35 + +CONF_DTW_BANDWIDTH = "dtw_bandwidth" +DEFAULT_DTW_BANDWIDTH = 0.20 # 20% Sakoe-Chiba constraint + +CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS = "suppress_feedback_notifications" +DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS = False # Show persistent notifications by default + +# States +STATE_OFF = "off" +STATE_DELAY_WAIT = "delay_wait" +STATE_IDLE = "idle" +STATE_STARTING = "starting" +STATE_RUNNING = "running" +STATE_PAUSED = "paused" +STATE_USER_PAUSED = "user_paused" +STATE_ENDING = "ending" +STATE_FINISHED = "finished" +STATE_ANTI_WRINKLE = "anti_wrinkle" +STATE_INTERRUPTED = "interrupted" +STATE_FORCE_STOPPED = "force_stopped" +STATE_RINSE = "rinse" +STATE_UNKNOWN = "unknown" +STATE_CLEAN = "clean" # Cycle ended but door not yet opened (laundry still inside) + +# Cycle Status (how the cycle ended) +CYCLE_STATUS_COMPLETED = "completed" # Natural completion (power dropped) +CYCLE_STATUS_INTERRUPTED = ( + "interrupted" # Abnormal/short run or abrupt power cliff (likely user/power abort) +) +CYCLE_STATUS_FORCE_STOPPED = "force_stopped" # Watchdog forced end (sensor offline) +CYCLE_STATUS_RESUMED = "resumed" # Cycle was restored from storage after restart + +# Device Types +DEVICE_TYPE_WASHING_MACHINE = "washing_machine" +DEVICE_TYPE_DRYER = "dryer" +DEVICE_TYPE_WASHER_DRYER = "washer_dryer" +DEVICE_TYPE_DISHWASHER = "dishwasher" +DEVICE_TYPE_COFFEE_MACHINE = "coffee_machine" +DEVICE_TYPE_EV = "ev" +DEVICE_TYPE_AIR_FRYER = "air_fryer" +DEVICE_TYPE_HEAT_PUMP = "heat_pump" +DEVICE_TYPE_BREAD_MAKER = "bread_maker" +DEVICE_TYPE_PUMP = "pump" +DEVICE_TYPE_OVEN = "oven" +# Generic / unsupported bucket. Ships intentionally generic defaults that are +# not tuned for any specific appliance, so the user must configure thresholds, +# timeouts, and matching parameters themselves. Also serves as the runtime +# fallback when a deprecated device type is hard-removed (see +# DEPRECATED_DEVICE_TYPE_FALLBACK below). No curated phase catalog and no +# device-type-specific branches in the runtime, so behavior is whatever the +# user dials in. +DEVICE_TYPE_OTHER = "other" + +DEVICE_TYPES = { + DEVICE_TYPE_WASHING_MACHINE: "Washing Machine", + DEVICE_TYPE_DRYER: "Dryer", + DEVICE_TYPE_WASHER_DRYER: "Washer-Dryer Combo", + DEVICE_TYPE_DISHWASHER: "Dishwasher", + DEVICE_TYPE_COFFEE_MACHINE: "Coffee Machine", + DEVICE_TYPE_EV: "Electric Vehicle", + DEVICE_TYPE_AIR_FRYER: "Air Fryer", + DEVICE_TYPE_HEAT_PUMP: "Heat Pump", + DEVICE_TYPE_BREAD_MAKER: "Bread Maker", + DEVICE_TYPE_PUMP: "Pump / Sump Pump", + DEVICE_TYPE_OVEN: "Oven", + DEVICE_TYPE_OTHER: "Other (Advanced)", +} + +# Device types that ship as deprecated. They fail one of WashData's three fit +# tests (user-selected discrete program, reproducible power signature, clean +# return to OFF) so profile matching and time-remaining estimation produce +# noise rather than signal. Kept in DEVICE_TYPES so existing config entries +# load unchanged; filtered out of the new-entry picker in the config flow, +# shown with a "(deprecated)" suffix when an existing entry already uses one, +# and surfaced via a one-shot persistent_notification on integration startup. +# Planned hard removal: 0.4.6 (two release cycles after this deprecation). +DEPRECATED_DEVICE_TYPES = frozenset({ + DEVICE_TYPE_COFFEE_MACHINE, + DEVICE_TYPE_EV, + DEVICE_TYPE_HEAT_PUMP, + DEVICE_TYPE_OVEN, +}) + +# Fallback device_type used at runtime once a deprecated type is hard-removed. +# "Other (Advanced)" intentionally ships generic defaults so the integration +# does not silently pretend an orphaned entry behaves like a washing machine. +# Stored options are preserved as-is, so a user who had hand-tuned thresholds +# on the old deprecated type keeps those values; the integration just stops +# layering device-specific defaults underneath them. +DEPRECATED_DEVICE_TYPE_FALLBACK = DEVICE_TYPE_OTHER + +# Device Type Defaults +# Device Type Defaults (Maps) + +DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE = { + DEVICE_TYPE_DISHWASHER: 14400, # 4 hours (Drying can be long) + DEVICE_TYPE_HEAT_PUMP: 14400, # 4 hours (Heat pumps can run a long time with slow updates) + DEVICE_TYPE_BREAD_MAKER: 7200, # 2 hours (Proving/Rising is very low-power for extended periods) + DEVICE_TYPE_PUMP: DEFAULT_PUMP_STUCK_DURATION + 60, # Must exceed stuck-alarm threshold so the alarm fires before the watchdog + DEVICE_TYPE_OVEN: 14400, # 4 hours (Slow roasts and pyrolytic self-clean can run for hours with thermostat-driven silence) +} + +DEFAULT_MAX_DEFERRAL_SECONDS = 14400 # 4 hours max safe deferral + +# Issue #43: dishwasher end-of-cycle pump-out handling. +# +# A dishwasher's wash→drying drain wind-down produces brief power spikes mid +# ENDING that, prior to the issue #43 fix, would set _end_spike_seen=True and +# pre-arm Smart Termination — so the cycle closed at 99% of expected, BEFORE +# the real end-of-cycle pump-out at ~99.5% of expected. The pump-out then +# registered as a brand-new cycle. +# +# Two coordinated thresholds gate the fix. They MUST agree: the wait window +# in _should_defer_finish (DISHWASHER_END_SPIKE_WAIT_SECONDS) is the upper +# bound for keeping the cycle open without an end spike, and Smart +# Termination's own wait branch in STATE_ENDING uses the SAME constant so the +# two paths release the cycle at the same moment. +# +# A spike at < DISHWASHER_END_SPIKE_MIN_PROGRESS of expected duration is +# ignored for end-spike tracking (the cycle still stays in ENDING via the +# existing long_ending_tail path - this only governs the smart-termination +# pre-arming). +DISHWASHER_END_SPIKE_MIN_PROGRESS = 0.85 +# Widened from 300s to 1800s after issue #43 follow-up: +# real-world user reports showed Smart Termination misfiring ~4 min before the +# end-of-cycle pump-out, and the original 5-min escape hatch wasn't generous +# enough to cover that gap before the next reading arrived. 30 min is plenty +# to capture even the latest pump-outs while still guaranteeing the cycle +# closes eventually for dishwashers that have no pump-out at all. +DISHWASHER_END_SPIKE_WAIT_SECONDS = 1800.0 + +DEFAULT_OFF_DELAY_BY_DEVICE = { + DEVICE_TYPE_DISHWASHER: 1800, # 30 min (Drying) + DEVICE_TYPE_COFFEE_MACHINE: 300, # 5 min (Warming/Pause handling) + DEVICE_TYPE_HEAT_PUMP: 600, # 10 min (Defrosting pauses) + DEVICE_TYPE_BREAD_MAKER: 300, # 5 min (Keep-warm phase after baking) + DEVICE_TYPE_PUMP: 20, # 20 s (Pumps cut off sharply; no warm-down phase) + DEVICE_TYPE_OVEN: 600, # 10 min (Thermostat off-cycles can be long while holding temp) +} + +# Device-specific progress smoothing thresholds (percentage points) +# These control how much backward progress is allowed before heavy damping kicks in +DEVICE_SMOOTHING_THRESHOLDS = { + DEVICE_TYPE_WASHING_MACHINE: 5.0, # Can have repeating phases (rinse cycles) + DEVICE_TYPE_DRYER: 3.0, # More linear, less phase repetition + DEVICE_TYPE_WASHER_DRYER: 5.0, # Combined washer+dryer, use washer defaults + DEVICE_TYPE_DISHWASHER: 5.0, # Similar to washing machine with distinct phases + DEVICE_TYPE_COFFEE_MACHINE: 2.0, # Short cycles, rapid transitions, less tolerance + DEVICE_TYPE_AIR_FRYER: 2.0, # Constant load with sudden drop + DEVICE_TYPE_HEAT_PUMP: 5.0, # Variable load, long periods + DEVICE_TYPE_BREAD_MAKER: 5.0, # Large power swings between kneading, proving, baking + DEVICE_TYPE_PUMP: 2.0, # Binary on/off spikes; minimal smoothing needed + DEVICE_TYPE_OVEN: 5.0, # Bistable thermostat cycling between full heat and 0 W +} + +CONF_VERIFICATION_POLL_INTERVAL = "verification_poll_interval" # Internal setting +DEFAULT_VERIFICATION_POLL_INTERVAL = 15 # Seconds (rapid checks after delay) + +# Device specific completion thresholds (min run time to be considered a valid "completed" cycle) +DEVICE_COMPLETION_THRESHOLDS = { + DEVICE_TYPE_WASHING_MACHINE: 600, # 10 min + DEVICE_TYPE_DRYER: 600, # 10 min + DEVICE_TYPE_WASHER_DRYER: 600, # 10 min (same as washer) + DEVICE_TYPE_DISHWASHER: 900, # 15 min + DEVICE_TYPE_COFFEE_MACHINE: 60, # 1 min (Filter coffee cycle) + DEVICE_TYPE_EV: 600, # 10 min + DEVICE_TYPE_AIR_FRYER: 300, # 5 min minimum + DEVICE_TYPE_HEAT_PUMP: 900, # 15 min minimum + DEVICE_TYPE_BREAD_MAKER: 1800, # 30 min (even express bread takes 30+ min) + DEVICE_TYPE_PUMP: 5, # 5 s - pump cycles can be under 30 seconds + DEVICE_TYPE_OVEN: 600, # 10 min (covers quick reheats and ignores brief preheating tests) +} + +# Default min_off_gap by device type (seconds) +# If gap between cycles is larger than this, force new cycle. +# If smaller, and we deemed previous as 'ended' but technically could be same, +# we might want to handle that (though strict state machine usually suffices if tuned well). +# Default min_off_gap by device type (seconds) +# Default min_off_gap by device type (seconds) +DEFAULT_MIN_OFF_GAP_BY_DEVICE = { + DEVICE_TYPE_WASHING_MACHINE: 480, # 8 min (Soak handling) + DEVICE_TYPE_DRYER: 300, # 5 min (Cool down gaps?) + DEVICE_TYPE_WASHER_DRYER: 600, # 10 min (longer for combined cycles) + DEVICE_TYPE_DISHWASHER: 3600, # 1 hour (Drying pauses) + DEVICE_TYPE_COFFEE_MACHINE: 120, # 2 min (Session grouping) + DEVICE_TYPE_EV: 900, # 15 min (Brief unplug/replug) + DEVICE_TYPE_AIR_FRYER: 120, # 2 min (Shaking food) + DEVICE_TYPE_HEAT_PUMP: 1800, # 30 min (Defrost cycle / resting gap) + DEVICE_TYPE_BREAD_MAKER: 600, # 10 min (Resting between knead/prove keeps same cycle together) + DEVICE_TYPE_PUMP: 60, # 1 min (Pumps can cycle every 3-5 min in heavy rain) + DEVICE_TYPE_OVEN: 900, # 15 min (Bridge thermostat off-windows so one bake stays a single cycle) +} +DEFAULT_MIN_OFF_GAP = 60 # Scalar fallback + +# Default start energy threshold by device type (Wh) +# Filter noise spikes (1000W * 0.01s = 0.002Wh). +# Must be significant enough to imply mechanical work. +DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE = { + DEVICE_TYPE_WASHING_MACHINE: 0.2, # ~50W for 15s or 200W for 3s + DEVICE_TYPE_DRYER: 0.5, # Heater kicks in hard + DEVICE_TYPE_WASHER_DRYER: 0.3, # Mix of washer and dryer + DEVICE_TYPE_DISHWASHER: 0.2, # Pump/Heater + DEVICE_TYPE_COFFEE_MACHINE: 0.05, # Short heater burst + DEVICE_TYPE_EV: 0.5, # High power charging + DEVICE_TYPE_AIR_FRYER: 0.2, # Heater kicks in + DEVICE_TYPE_HEAT_PUMP: 0.2, # Compressor spins up + DEVICE_TYPE_BREAD_MAKER: 0.2, # Kneading motor starts (~200W for a few seconds) + DEVICE_TYPE_PUMP: 0.003, # ~100W motor for ~0.1 s is enough to confirm a pump cycle + DEVICE_TYPE_OVEN: 0.5, # Heating element kicks in hard (~2-3 kW) - high gate filters incidental light/fan draws +} +# Default sampling interval by device type +DEFAULT_SAMPLING_INTERVAL_BY_DEVICE = { + # 2s captures the rapid 0<->150W motor/heater oscillation in wet appliances; + # the 30s global default discards those spikes and undersamples the cycle. + DEVICE_TYPE_WASHING_MACHINE: 2.0, + DEVICE_TYPE_WASHER_DRYER: 2.0, + DEVICE_TYPE_DISHWASHER: 2.0, + DEVICE_TYPE_COFFEE_MACHINE: 10.0, # 10s is sufficient for brew cycles + DEVICE_TYPE_PUMP: 10.0, # 10s - pump cycles can be <30 s; 30s default would miss them +} + +# Default profile match min duration ratio by device type +DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE = { + DEVICE_TYPE_DISHWASHER: 0.10, +} + +# Storage +STORAGE_VERSION = 5 +STORAGE_KEY = "ha_washdata" + +# Notification events +EVENT_CYCLE_STARTED = "ha_washdata_cycle_started" +EVENT_CYCLE_ENDED = "ha_washdata_cycle_ended" + +# Signals +SIGNAL_WASHER_UPDATE = "ha_washdata_update_{}" + +# Learning & Feedback + +SERVICE_SUBMIT_FEEDBACK = ( + "ha_washdata.submit_cycle_feedback" # Service to submit feedback +) + +# Recorder +STATE_RECORDING = "recording" +CONF_RECORD_MODE = "record_mode" +SERVICE_RECORD_START = "record_start" +SERVICE_RECORD_STOP = "record_stop" + +# Thresholds for trim suggestions +SHORT_SILENCE_THRESHOLD_S = 600 # 10 minutes +TRIM_BUFFER_S = 60.0 # 1 minute buffer diff --git a/custom_components/ha_washdata/cycle_detector.py b/custom_components/ha_washdata/cycle_detector.py new file mode 100644 index 0000000..f50b5c8 --- /dev/null +++ b/custom_components/ha_washdata/cycle_detector.py @@ -0,0 +1,1635 @@ +"""Cycle detection logic for WashData.""" + +from __future__ import annotations + +import logging +import math +from dataclasses import dataclass +from datetime import datetime +from typing import Any, Callable, cast +import numpy as np + +from homeassistant.util import dt as dt_util + +from .log_utils import DeviceLoggerAdapter +from .const import ( + STATE_OFF, + STATE_DELAY_WAIT, + STATE_STARTING, + STATE_RUNNING, + STATE_PAUSED, + STATE_ENDING, + STATE_FINISHED, + STATE_ANTI_WRINKLE, + STATE_INTERRUPTED, + STATE_FORCE_STOPPED, + STATE_UNKNOWN, + DEVICE_TYPE_WASHING_MACHINE, + DEVICE_TYPE_DRYER, + DEVICE_TYPE_WASHER_DRYER, + DEFAULT_MAX_DEFERRAL_SECONDS, + DEFAULT_DEFER_FINISH_CONFIDENCE, + DISHWASHER_END_SPIKE_MIN_PROGRESS, + DISHWASHER_END_SPIKE_WAIT_SECONDS, +) + +# The dishwasher end-spike wait window is shared between two code paths +# (Smart Termination's wait branch and _should_defer_finish's no-end-spike +# branch). They MUST release the cycle at the same instant — sanity-check +# that the constants module loaded a sensible value rather than allowing the +# paths to silently drift if one was changed and the other forgotten. +assert DISHWASHER_END_SPIKE_WAIT_SECONDS > 0, ( + "DISHWASHER_END_SPIKE_WAIT_SECONDS must be positive" +) +assert 0 < DISHWASHER_END_SPIKE_MIN_PROGRESS < 1, ( + "DISHWASHER_END_SPIKE_MIN_PROGRESS must be a fraction in (0, 1)" +) +from .signal_processing import integrate_wh + +_LOGGER = logging.getLogger(__name__) + +# After a user/external stop the manual-stop lockout swallows the machine's +# spin-down/drain so it is not logged as a fresh cycle. The lockout normally +# clears as soon as power drops to idle. As a safety net, if power instead stays +# at or above the start threshold for longer than any plausible spin-down, the +# device is running a genuinely new (back-to-back) load: release the lockout so +# the new cycle is detected instead of being pinned until the progress-reset +# window expires (issue #267). +STOP_LOCKOUT_RELEASE_SECONDS = 180.0 + + +@dataclass +class CycleDetectorConfig: + """Configuration for cycle detection.""" + + min_power: float + off_delay: int + device_type: str = DEVICE_TYPE_WASHING_MACHINE + smoothing_window: int = 5 + interrupted_min_seconds: int = 150 + abrupt_drop_watts: float = 500.0 + abrupt_drop_ratio: float = 0.6 + abrupt_high_load_factor: float = 5.0 + completion_min_seconds: int = 600 + start_duration_threshold: float = 5.0 + start_energy_threshold: float = 0.005 + end_energy_threshold: float = 0.05 # 50 Wh threshold for "still active" + running_dead_zone: int = 0 + end_repeat_count: int = 1 + min_off_gap: int = 60 + start_threshold_w: float = 2.0 + stop_threshold_w: float = 2.0 + min_duration_ratio: float = 0.8 # Default deferred finish ratio + match_interval: int = 300 # Default profile match interval + profile_duration_tolerance: float = 0.25 # Default tolerance (±25%) + anti_wrinkle_enabled: bool = False + anti_wrinkle_max_power: float = 400.0 + anti_wrinkle_max_duration: float = 60.0 + anti_wrinkle_exit_power: float = 0.8 + delay_detect_enabled: bool = False + # Sustained seconds power must stay in the standby band (between + # stop_threshold_w and start_threshold_w) before DELAY_WAIT engages. + # Tuned to filter out brief menu-navigation peaks at the start of a + # delayed program. + delay_confirm_seconds: float = 60.0 + delay_timeout_seconds: float = 28800.0 + + +@dataclass +class CycleDetectorState: + """Internal state storage for save/restore.""" + + state: str = STATE_OFF + sub_state: str | None = None + accumulated_energy_wh: float = 0.0 + # Add other fields as needed + + +def trim_zero_readings( + readings: list[tuple[datetime, float]], + threshold: float = 0.5, + trim_start: bool = True, + trim_end: bool = True, +) -> list[tuple[datetime, float]]: + """Trim continuous zero/near-zero readings from start and end of cycle. + + Args: + readings: List of (timestamp, power) tuples + threshold: Power values below this are considered "zero" + trim_start: Whether to trim zeros from the beginning + trim_end: Whether to trim zeros from the end + + Returns: + Trimmed list + """ + if not readings: + return readings + + start_idx = 0 + if trim_start: + for i, (_, power) in enumerate(readings): + if power > threshold: + start_idx = i + break + else: + # All readings are zero - return single point if list not empty + return readings[:1] if readings else [] + + end_idx = len(readings) - 1 + if trim_end: + # Find last non-zero reading + found_end = False + for i in range(len(readings) - 1, -1, -1): + if readings[i][1] > threshold: + end_idx = i + found_end = True + break + + if not found_end and trim_start: + # If all zeros and trim_start was checked, it would return early. + # But if safety fallback needed: + end_idx = start_idx + elif not found_end and not trim_start: + # Trimming end but not start, and all zeros? + # Keep first point + end_idx = 0 + + # Return trimmed slice (inclusive of end) + return readings[start_idx : end_idx + 1] + + +class CycleDetector: + """Detects washing machine cycles based on power usage. + + Implements a robust state machine: + OFF -> STARTING -> RUNNING <-> PAUSED -> ENDING -> OFF + """ + + def __init__( + self, + config: CycleDetectorConfig, + on_state_change: Callable[[str, str], None], + on_cycle_end: Callable[[dict[str, Any]], None], + profile_matcher: ( + Callable[ + [list[tuple[datetime, float]]], + tuple[str | None, float, float, str | None], + ] + | None + ) = None, + device_name: str = "", + ) -> None: + """Initialize the cycle detector.""" + self._logger = DeviceLoggerAdapter(_LOGGER, device_name) + self._config = config + self._on_state_change = on_state_change + self._on_cycle_end = on_cycle_end + self._profile_matcher = profile_matcher + + # State + self._state = STATE_OFF + self._sub_state: str | None = None + self._ignore_power_until_idle: bool = False + # Sustained high-power time accrued while the stop lockout is armed; used + # to release the lockout for a genuinely new back-to-back load (#267). + self._lockout_high_seconds: float = 0.0 + + # Data + self._power_readings: list[tuple[datetime, float]] = [] # (time, raw_power) + self._current_cycle_start: datetime | None = None + self._last_active_time: datetime | None = None + self._cycle_max_power: float = 0.0 + + # Accumulators (dt-aware) + self._energy_since_idle_wh: float = 0.0 + self._time_above_threshold: float = 0.0 + self._time_below_threshold: float = 0.0 + self._last_process_time: datetime | None = None + + # New State Machine trackers + self._state_enter_time: datetime | None = None + self._matched_profile: str | None = None + self._verified_pause: bool = False + + self._abrupt_drop: bool = False + self._last_power: float | None = None + self._time_in_state: float = 0.0 + + # Smoothing buffer + self._ma_buffer: list[float] = [] + + # Adaptive Sampling Tracker + self._recent_dts: list[float] = [] # Track last 20 dt values + self._p95_dt: float = 1.0 # Default assumption + + # Profile Matching Tracker + self._last_match_time: datetime | None = None + self._expected_duration: float = 0.0 + self._last_match_confidence: float = 0.0 + self._end_spike_seen: bool = False + + # Anti-wrinkle tracking (dryers only) + self._anti_wrinkle_candidate_start: datetime | None = None + self._anti_wrinkle_candidate_peak: float = 0.0 + self._anti_wrinkle_candidate_start_power: float = 0.0 + self._anti_wrinkle_idle_time: float = 0.0 # Track time spent below exit_power while in ANTI_WRINKLE + self._anti_wrinkle_idle_timeout: float = 120.0 + + # Delayed-start band tracking. + # _delay_band_start anchors the first reading in the standby band + # [stop_threshold_w, start_threshold_w) while still in STATE_OFF. + # _delay_band_seconds mirrors the anchored elapsed time for + # diagnostics and tests. + self._delay_band_start: datetime | None = None + self._delay_band_seconds: float = 0.0 + # _delay_band_peak is purely diagnostic — surfaced in the log line + # when the transition fires so users can see what their machine's + # actual standby plateau looked like. + self._delay_band_peak: float = 0.0 + # _delay_wait_true_off_seconds tracks sustained "true off" (power + # below stop_threshold_w) inside DELAY_WAIT, so we can drop back to + # OFF only when the machine has clearly been switched off rather + # than briefly dipped. + self._delay_wait_true_off_seconds: float = 0.0 + # _delay_wait_high_start anchors the first high-power reading + # observed inside DELAY_WAIT. We only transition to STARTING + # when the high-power streak has lasted at least + # start_duration_threshold real seconds — measured between two + # consecutive high readings, not from the dt to the previous + # (low) reading. This prevents a single isolated spike from + # tripping STARTING just because the sampling interval is long. + self._delay_wait_high_start: datetime | None = None + self._delay_wait_high_power: float | None = None + # Preserve a delayed-start candidate across a false STARTING probe + # that drops back into the standby band without the machine truly + # turning off. + self._preserve_delay_band_on_off: bool = False + + @property + def _dynamic_pause_threshold(self) -> float: + """Calculate dynamic pause threshold based on sampling cadence.""" + # User requirement: T_pause >= 3 * p95_update_interval + # Default 15s or 3 * p95 + return max(15.0, 3.0 * self._p95_dt) + + @property + def _dynamic_end_threshold(self) -> float: + """Calculate dynamic end candidate threshold.""" + # Keep this generic for pause->ending transitions across all device types. + base = 3.0 * self._p95_dt + # Ensure end threshold is at least 15s greater than pause threshold + return max(base, self._dynamic_pause_threshold + 15.0) + + def _update_cadence(self, dt: float) -> None: + """Update rolling cadence statistics.""" + if dt <= 0.1: + return + self._recent_dts.append(dt) + if len(self._recent_dts) > 20: + self._recent_dts.pop(0) + + # Calculate p95 if enough samples + if len(self._recent_dts) >= 5: + self._p95_dt = float(np.percentile(self._recent_dts, 95)) + else: + self._p95_dt = max(dt, 1.0) + + def _try_profile_match(self, timestamp: datetime, force: bool = False) -> None: + """Attempt to invoke the profile matcher if conditions are met. + + Args: + timestamp: Current timestamp. + force: If True, run match immediately regardless of interval. + """ + if not self._profile_matcher: + return + if not self._power_readings: + return + + # Rate limiting + if not force and self._last_match_time: + elapsed = (timestamp - self._last_match_time).total_seconds() + if elapsed < self._config.match_interval: + return + + self._last_match_time = timestamp + + # Call the matcher + try: + result = self._profile_matcher(self._power_readings) + # If synchronous result returned, process it. + # If None returned (async offload), the matcher is responsible for + # calling update_match later. + if result: + self.update_match(result) + + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.debug("Profile match failed: %s", e) + + # Maximum reasonable cycle duration accepted by the detector. Anything + # longer is rejected as corrupted data and replaced with the + # _SANITIZE_INVALID_SENTINEL so downstream gates fall through to the + # unmatched / no-expected-duration path. + _SANITIZE_MAX_EXPECTED_DURATION = 6 * 3600.0 # 6 hours + _SANITIZE_INVALID_SENTINEL = 0.0 # 0 == "no valid expected_duration" + + def _sanitize_expected_duration( + self, raw: Any, *, source: str = "update_match" + ) -> float: + """Coerce ``raw`` into a finite float in (0, 6h] or return 0.0. + + The class invariant is that ``self._expected_duration`` is either a + finite, strictly positive float ≤ 6 hours, or 0.0 meaning "no valid + expected duration". Every code path that assigns ``_expected_duration`` + (live profile-match callbacks AND restored snapshots) routes through + this helper so the gates in STATE_ENDING and ``_should_defer_finish`` + can trust the value without re-validating. + + Emits a DEBUG log line distinguishing the rejection reason — the + ``<= 0`` and ``> 6h`` markers are part of issue #197's regression + contract and tests assert on them. + """ + try: + value = float(raw) + except (TypeError, ValueError): + self._logger.debug( + "%s: invalid raw_expected_duration %r, defaulting to 0.0", + source, raw, + ) + return self._SANITIZE_INVALID_SENTINEL + if not math.isfinite(value): + self._logger.debug( + "%s: invalid raw_expected_duration %r, defaulting to 0.0", + source, raw, + ) + return self._SANITIZE_INVALID_SENTINEL + if value <= 0: + self._logger.debug( + "%s: invalid raw_expected_duration %r (<= 0), defaulting to 0.0", + source, raw, + ) + return self._SANITIZE_INVALID_SENTINEL + if value > self._SANITIZE_MAX_EXPECTED_DURATION: + self._logger.debug( + "%s: invalid raw_expected_duration %r (> 6h), defaulting to 0.0", + source, raw, + ) + return self._SANITIZE_INVALID_SENTINEL + return value + + def update_match(self, result: tuple[Any, ...] | list[Any] | Any) -> None: # type: ignore[misc] + """Process a match result (synchronously). + + Can be called by the matcher callback directly or asynchronously. + """ + # Unpack 5 elements (or 4 for backward compatibility if needed, but wrapper is updated) + # wrapper returns (name, confidence, duration, phase, is_mismatch) + # Or MatchResult object if refactored, but currently wrapper returns tuple. + + is_match_mismatch = False + match_name: str | None = None + phase_name: str | None = None + confidence: float = 0.0 + expected_duration: float = 0.0 + + if isinstance(result, (list, tuple)): # type: ignore[misc] + result_seq = cast(tuple[Any, ...] | list[Any], result) + if len(result_seq) >= 5: + ( + raw_name, + raw_confidence, + raw_expected_duration, + raw_phase_name, + raw_mismatch, + ) = result_seq[:5] + match_name = str(raw_name) if raw_name is not None else None + try: + confidence = float(raw_confidence) + if not math.isfinite(confidence): + confidence = 0.0 + self._logger.debug("update_match: invalid raw_confidence %r, defaulting to 0.0", raw_confidence) + except (TypeError, ValueError): + confidence = 0.0 + self._logger.debug("update_match: invalid raw_confidence %r, defaulting to 0.0", raw_confidence) + expected_duration = self._sanitize_expected_duration( + raw_expected_duration, source="update_match" + ) + phase_name = str(raw_phase_name) if raw_phase_name is not None else None + is_match_mismatch = raw_mismatch if isinstance(raw_mismatch, bool) else bool(raw_mismatch) + else: + # Fallback for old signature + if len(result_seq) >= 4: + ( + raw_name, + raw_confidence, + raw_expected_duration, + raw_phase_name, + ) = result_seq[:4] + match_name = str(raw_name) if raw_name is not None else None + try: + confidence = float(raw_confidence) + if not math.isfinite(confidence): + confidence = 0.0 + self._logger.debug("update_match: invalid raw_confidence %r, defaulting to 0.0", raw_confidence) + except (TypeError, ValueError): + confidence = 0.0 + self._logger.debug("update_match: invalid raw_confidence %r, defaulting to 0.0", raw_confidence) + expected_duration = self._sanitize_expected_duration( + raw_expected_duration, source="update_match" + ) + phase_name = ( + str(raw_phase_name) if raw_phase_name is not None else None + ) + is_match_mismatch = False + + # Store confidence for Smart Termination checks + self._last_match_confidence = confidence or 0.0 + else: + # Assume MatchResult object or similar (future proofing) + # But for now wrapper returns tuple + return + + if is_match_mismatch and self._matched_profile: + # Confident non-match - revert to detecting if previously matched + self._matched_profile = None + + elif match_name: + # If sanitization rejected the expected_duration, treat the match + # as invalid: setting _matched_profile while _expected_duration is + # the 0.0 sentinel would let Smart Termination fire on the + # `current_duration >= 0` always-true comparison. Drop both so + # the cycle stays in detecting/unmatched mode. + if expected_duration == self._SANITIZE_INVALID_SENTINEL: + self._logger.debug( + "update_match: match %r ignored — expected_duration " + "sanitized to invalid sentinel; treating as unmatched", + match_name, + ) + self._matched_profile = None + self._expected_duration = self._SANITIZE_INVALID_SENTINEL + else: + self._matched_profile = match_name + # Sub-state can be set from phase_name if available + if phase_name: + self._sub_state = phase_name + # Wrapper provides it + self._expected_duration = expected_duration + + def set_verified_pause(self, verified: bool) -> None: + """Set or clear the verified pause flag.""" + self._verified_pause = verified + + def reset(self, target_state: str = STATE_OFF) -> None: + """Force reset the detector state to target state.""" + self._transition_to(target_state, dt_util.now()) + self._power_readings = [] + self._current_cycle_start = None + self._last_active_time = None + self._cycle_max_power = 0.0 + self._ma_buffer = [] + self._energy_since_idle_wh = 0.0 + self._time_above_threshold = 0.0 + # Only reset time_below_threshold if not transitioning to ANTI_WRINKLE + # (ANTI_WRINKLE needs to track idle time to determine true-off) + if target_state != STATE_ANTI_WRINKLE: + self._time_below_threshold = 0.0 + self._last_match_time = None + self._matched_profile = None + self._ignore_power_until_idle = False # Reset lockout + self._lockout_high_seconds = 0.0 + self._anti_wrinkle_candidate_start = None + self._anti_wrinkle_candidate_peak = 0.0 + self._anti_wrinkle_candidate_start_power = 0.0 + # Reset idle time tracker for anti-wrinkle + self._anti_wrinkle_idle_time = 0.0 + # Reset delayed-start tracking + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._delay_wait_true_off_seconds = 0.0 + self._delay_wait_high_start = None + + @property + def state(self) -> str: + """Return current state.""" + return self._state + + @property + def sub_state(self) -> str | None: + """Return current sub-state.""" + return self._sub_state + + @property + def config(self) -> CycleDetectorConfig: + """Return current configuration.""" + return self._config + + @property + def matched_profile(self) -> str | None: + """Return the name of the matched profile, if any.""" + return self._matched_profile + + @property + def current_cycle_start(self) -> datetime | None: + """Return the start timestamp of the current cycle.""" + return self._current_cycle_start + + @property + def samples_recorded(self) -> int: + """Return the number of power samples recorded in current cycle.""" + return len(self._power_readings) + + @property + def expected_duration_seconds(self) -> float: + """Return the expected duration of the current cycle in seconds.""" + return self._expected_duration + + def process_reading(self, power: float, timestamp: datetime) -> None: + """Process a new power reading using robust dt-aware logic.""" + + # Calculate dt (needed by the stop lockout below and the state machine). + dt = 0.0 + if self._last_process_time: + dt = (timestamp - self._last_process_time).total_seconds() + + # Sanity check for negative dt + if dt < 0: + self._last_process_time = timestamp + return + + # Manual Stop Lockout: + # If user/external stop forced an end, ignore the machine's spin-down so + # it is not logged as a new cycle. The lockout clears the moment power + # drops to idle. As a safety net, if power instead stays high far longer + # than any plausible spin-down, treat it as a genuinely new back-to-back + # load and release the lockout so the cycle is detected immediately + # rather than pinned until the progress-reset window expires (#267). + if self._ignore_power_until_idle: + if power < self._config.start_threshold_w: + self._ignore_power_until_idle = False + self._lockout_high_seconds = 0.0 + self._logger.debug( + "Power dropped below start threshold. Manual stop lockout cleared." + ) + else: + self._lockout_high_seconds += dt + if self._lockout_high_seconds < STOP_LOCKOUT_RELEASE_SECONDS: + # Still within the spin-down window - ignore reading. + self._last_process_time = timestamp + return + self._ignore_power_until_idle = False + self._lockout_high_seconds = 0.0 + self._logger.info( + "Manual stop lockout released after sustained power " + "(>= %.1fs at/above start threshold): treating as a new " + "cycle (#267).", + STOP_LOCKOUT_RELEASE_SECONDS, + ) + # Fall through: the state machine will start a new cycle. + + self._update_cadence(dt) + self._last_process_time = timestamp + + # 1. Smoothing (Legacy buffer for debug/display, logic uses raw + time accumulators) + self._ma_buffer.append(power) + if len(self._ma_buffer) > self._config.smoothing_window: + self._ma_buffer.pop(0) + + # 2. Accumulators Update + # Hysteresis Logic + if self._state in (STATE_OFF, STATE_DELAY_WAIT, STATE_STARTING, STATE_UNKNOWN): + threshold = self._config.start_threshold_w + else: + threshold = self._config.stop_threshold_w + + is_high = power >= threshold + + if is_high: + self._time_above_threshold += dt + self._time_below_threshold = 0.0 + # Energy integration (trapezoidal approx for this single step) + # prev_p = self._last_power if self._last_power is not None else power + # step_wh = ((power + prev_p) / 2.0) * (dt / 3600.0) + # Simplified: just P * dt for short steps is fine, + # or call integrate_wh on buffer if needed. + # Let's use simple rect/trapz here for running sum + step_wh = power * (dt / 3600.0) + self._energy_since_idle_wh += step_wh + self._last_active_time = timestamp + else: + self._time_below_threshold += dt + self._time_above_threshold = 0.0 + + self._time_in_state += dt + + self._last_power = power + + anti_wrinkle_active = ( + self._config.anti_wrinkle_enabled + and self._config.device_type in (DEVICE_TYPE_DRYER, DEVICE_TYPE_WASHER_DRYER) + ) + + # 3. State Machine + + if self._state in ( + STATE_OFF, + STATE_FINISHED, + STATE_INTERRUPTED, + STATE_FORCE_STOPPED, + STATE_ANTI_WRINKLE, + ): + started_from_anti_wrinkle = False + if anti_wrinkle_active and self._state == STATE_ANTI_WRINKLE and is_high: + if self._anti_wrinkle_candidate_start is None: + self._anti_wrinkle_candidate_start = timestamp + self._anti_wrinkle_candidate_peak = power + self._anti_wrinkle_candidate_start_power = power + else: + self._anti_wrinkle_candidate_peak = max( + self._anti_wrinkle_candidate_peak, power + ) + + candidate_duration = ( + timestamp - self._anti_wrinkle_candidate_start + ).total_seconds() + exceeds = ( + self._anti_wrinkle_candidate_peak + > self._config.anti_wrinkle_max_power + or power > self._config.anti_wrinkle_max_power + or candidate_duration > self._config.anti_wrinkle_max_duration + ) + + if exceeds: + candidate_start = self._anti_wrinkle_candidate_start + candidate_peak = self._anti_wrinkle_candidate_peak + candidate_start_power = self._anti_wrinkle_candidate_start_power + self._anti_wrinkle_candidate_start = None + self._anti_wrinkle_candidate_peak = 0.0 + self._anti_wrinkle_candidate_start_power = 0.0 + self._transition_to(STATE_STARTING, timestamp) + started_from_anti_wrinkle = True + self._current_cycle_start = candidate_start or timestamp + + # Preserve the anti-wrinkle candidate window instead of dropping ramp-up samples. + if candidate_start and candidate_start < timestamp: + start_power = candidate_start_power if candidate_start_power > 0 else power + self._power_readings = [(candidate_start, start_power), (timestamp, power)] + interval_s = (timestamp - candidate_start).total_seconds() + avg_power = (start_power + power) / 2.0 + self._energy_since_idle_wh = max(0.0, avg_power * (interval_s / 3600.0)) + else: + self._power_readings = [(timestamp, power)] + self._energy_since_idle_wh = power * (dt / 3600.0) if dt > 0 else 0.0 + + self._cycle_max_power = max(candidate_peak, power) + self._abrupt_drop = False + elif self._state != STATE_ANTI_WRINKLE: + self._anti_wrinkle_candidate_start = None + self._anti_wrinkle_candidate_peak = 0.0 + self._anti_wrinkle_candidate_start_power = 0.0 + + if self._state == STATE_ANTI_WRINKLE: + # Track time in idle (below exit_power threshold) + effective_exit = max(self._config.anti_wrinkle_exit_power, self._config.stop_threshold_w) + if power < effective_exit: + # Low-power gap invalidates any burst candidate collected while in anti-wrinkle. + self._anti_wrinkle_candidate_start = None + self._anti_wrinkle_candidate_peak = 0.0 + self._anti_wrinkle_candidate_start_power = 0.0 + self._anti_wrinkle_idle_time += dt + anti_wrinkle_end_threshold = max( + self._dynamic_end_threshold, + self._anti_wrinkle_idle_timeout, + ) + if self._anti_wrinkle_idle_time >= anti_wrinkle_end_threshold: + self._transition_to(STATE_OFF, timestamp) + return + else: + # Reset idle timer when power rises (burst detected) + self._anti_wrinkle_idle_time = 0.0 + + # Exit conditions: + # 1. Idle duration exceeded (handled above), OR + # 2. Safety timeout (2 hours in anti-wrinkle), OR + # 3. External trigger (user_stop, external triggers handled by manager) + if ( + self._state_enter_time + and (timestamp - self._state_enter_time).total_seconds() > 7200 + ): + # Safety timeout: 2 hours in anti-wrinkle + self._transition_to(STATE_OFF, timestamp) + return + + # Delayed-start "standby band" detection (only from STATE_OFF). + # + # A machine in delayed-start mode sits in a power band between + # the off-noise floor (stop_threshold_w) and the cycle-start + # threshold (start_threshold_w) — display, electronics, the + # occasional anti-damp tumble — for minutes to hours. We + # track anchored elapsed time while power is in that band; once + # it crosses delay_confirm_seconds we transition to DELAY_WAIT. + # + # Brief high-power excursions (menu navigation, button presses) + # don't break the candidate: they fall through to the normal + # start logic below, and unless they sustain for + # start_duration_threshold they get aborted as a false start + # and we re-enter the band on the next reading. Excursions + # below stop_threshold_w (machine momentarily idle on the noise + # floor) DO reset the candidate, because that's the same + # signal we use to define "off". + if ( + self._config.delay_detect_enabled + and self._state == STATE_OFF + and not started_from_anti_wrinkle + and self._config.stop_threshold_w < self._config.start_threshold_w + ): + in_band = ( + self._config.stop_threshold_w + <= power + < self._config.start_threshold_w + ) + if in_band: + if self._delay_band_start is None: + self._delay_band_start = timestamp + self._delay_band_seconds = 0.0 + else: + self._delay_band_seconds = ( + timestamp - self._delay_band_start + ).total_seconds() + self._delay_band_peak = max(self._delay_band_peak, power) + if self._delay_band_seconds >= self._config.delay_confirm_seconds: + self._logger.info( + "Delayed start detected: standby band held for %.0fs " + "(peak %.1fW, current %.1fW) → DELAY_WAIT", + self._delay_band_seconds, + self._delay_band_peak, + power, + ) + self._transition_to(STATE_DELAY_WAIT, timestamp) + return + # Stay in OFF while we accumulate evidence — do not + # fall through to the high-power start logic, the + # reading is below threshold by definition. + return + elif power < self._config.stop_threshold_w: + # Machine genuinely idle: forget any band history. + self._delay_band_start = None + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._preserve_delay_band_on_off = False + # power >= start_threshold_w: fall through to the normal + # start path below. If it turns out to be a brief peak, + # STATE_STARTING will abort it as a false start and we'll + # re-enter the band check on the next sample without + # losing accumulated time (we don't reset on a high + # excursion — most users' "menu navigation" peaks last + # less than a sample interval anyway). + + if is_high and not started_from_anti_wrinkle: + # Transition to STARTING + self._preserve_delay_band_on_off = self._delay_band_start is not None + self._transition_to(STATE_STARTING, timestamp) + self._current_cycle_start = timestamp + self._power_readings = [(timestamp, power)] + self._energy_since_idle_wh = power * (dt / 3600.0) if dt > 0 else 0.0 + self._cycle_max_power = power + self._abrupt_drop = False + elif self._state != STATE_OFF: + # Auto-expire terminal states after 30 minutes + if ( + self._state_enter_time + and (timestamp - self._state_enter_time).total_seconds() > 1800 + ): + self._transition_to(STATE_OFF, timestamp) + + elif self._state == STATE_DELAY_WAIT: + if power >= self._config.start_threshold_w: + # Power is in cycle-start territory. Require at least + # two consecutive high readings spanning + # start_duration_threshold real seconds before committing + # to STARTING, so a single isolated spike (a heavy menu + # interaction, an anti-damp pulse briefly crossing the + # threshold) doesn't false-trigger. We anchor on the + # FIRST high reading instead of accumulating dt, because + # dt to the previous (low) reading is unrelated to how + # long the high power has actually persisted. + self._delay_wait_true_off_seconds = 0.0 + if self._delay_wait_high_start is None: + self._delay_wait_high_start = timestamp + self._delay_wait_high_power = power + else: + elapsed_high = ( + timestamp - self._delay_wait_high_start + ).total_seconds() + if elapsed_high >= self._config.start_duration_threshold: + self._logger.info( + "Delayed start: cycle starting (power %.1fW sustained ≥ %.1fW for %.0fs)", + power, + self._config.start_threshold_w, + elapsed_high, + ) + self._transition_to(STATE_STARTING, timestamp) + start_timestamp = self._delay_wait_high_start or timestamp + start_power = self._delay_wait_high_power or power + self._current_cycle_start = start_timestamp + self._power_readings = [(start_timestamp, start_power)] + elapsed_from_anchor = (timestamp - start_timestamp).total_seconds() + self._energy_since_idle_wh = ( + start_power * (elapsed_from_anchor / 3600.0) + if elapsed_from_anchor > 0 + else 0.0 + ) + if timestamp != start_timestamp: + self._power_readings.append((timestamp, power)) + self._cycle_max_power = max(start_power, power) + self._abrupt_drop = False + else: + # Power dropped back below start threshold — clear the + # high-power streak anchor so the next high reading + # starts a fresh confirmation window. + self._delay_wait_high_start = None + self._delay_wait_high_power = None + if power < self._config.stop_threshold_w: + # Power near zero: machine genuinely turned off, not + # just waiting. + self._delay_wait_true_off_seconds += dt + if self._delay_wait_true_off_seconds >= 30.0: + self._logger.info( + "Delayed start cancelled: power dropped to off (%.1fW) for %.0fs", + power, + self._delay_wait_true_off_seconds, + ) + self._transition_to(STATE_OFF, timestamp) + return + else: + self._delay_wait_true_off_seconds = 0.0 + + # Safety timeout + if ( + self._state_enter_time + and (timestamp - self._state_enter_time).total_seconds() + >= self._config.delay_timeout_seconds + ): + self._logger.info( + "Delayed start timeout after %.0fh → OFF", + self._config.delay_timeout_seconds / 3600.0, + ) + self._transition_to(STATE_OFF, timestamp) + + elif self._state == STATE_STARTING: + self._power_readings.append((timestamp, power)) + self._cycle_max_power = max(self._cycle_max_power, power) + + if self._time_above_threshold >= self._config.start_duration_threshold: + if self._energy_since_idle_wh >= self._config.start_energy_threshold: + self._transition_to(STATE_RUNNING, timestamp) + + # Abort if power drops below threshold before confirmation + if not is_high and self._time_below_threshold > 1.0: # 1s grace period + # False start + self._logger.debug( + "False start detected: power dropped after %.2fs", + self._time_above_threshold, + ) + self._delay_band_start = None + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._preserve_delay_band_on_off = False + self._transition_to(STATE_OFF, timestamp) + + elif self._state == STATE_RUNNING: + self._power_readings.append((timestamp, power)) + self._cycle_max_power = max(self._cycle_max_power, power) + + # Use dynamic threshold + thresh = self._dynamic_pause_threshold + if self._time_below_threshold >= thresh: + self._try_profile_match(timestamp, force=True) # Refine match on pause + self._transition_to(STATE_PAUSED, timestamp) + + # Periodic profile matching + self._try_profile_match(timestamp) + + # Max duration safety + if ( + self._current_cycle_start + and (timestamp - self._current_cycle_start).total_seconds() > 28800 + ): # 8h safety + self._finish_cycle(timestamp, status="force_stopped") + + elif self._state == STATE_PAUSED: + self._power_readings.append((timestamp, power)) + + if is_high: + # Resume to RUNNING + self._transition_to(STATE_RUNNING, timestamp) + else: + # Periodic profile matching during pause + self._try_profile_match(timestamp) + + thresh = self._dynamic_end_threshold + if self._time_below_threshold >= thresh: + self._transition_to(STATE_ENDING, timestamp) + + elif self._state == STATE_ENDING: + self._power_readings.append((timestamp, power)) + + if is_high: + start_time = self._current_cycle_start or timestamp + current_duration = (timestamp - start_time).total_seconds() + + is_dishwasher = self._config.device_type == "dishwasher" + + # Issue #43: only treat this as a *terminal* end spike (which then + # pre-arms Smart Termination) when it occurs near the end of the + # expected cycle. Mid-cycle spikes - e.g. the dishwasher + # wash→drying drain wind-down at ~50% of expected duration - must + # not arm smart termination, otherwise the cycle finishes at 99% + # of expected *before* the real end-of-cycle pump-out, and that + # pump-out is then misread as a brand-new cycle. Without a + # matched profile (expected==0) the gating is bypassed so the + # legacy "any spike counts" behaviour is preserved for unmatched + # cycles (relied on by the dishwasher unmatched-cap path). + if ( + self._expected_duration <= 0 + or current_duration + >= self._expected_duration * DISHWASHER_END_SPIKE_MIN_PROGRESS + ): + self._end_spike_seen = True + self._logger.debug( + "End spike detected (power high in ENDING state, " + "%.0fs/%.0fs)", + current_duration, + self._expected_duration, + ) + else: + self._logger.debug( + "Mid-cycle spike in ENDING ignored for end-spike " + "tracking (%.0fs < %.0f%% of expected %.0fs)", + current_duration, + DISHWASHER_END_SPIKE_MIN_PROGRESS * 100, + self._expected_duration, + ) + + # Sanity check: if expected_duration is unreasonable (>6 hours), use fallback + max_reasonable = 21600.0 # 6 hours + effective_expected = self._expected_duration + + if effective_expected <= 0 or effective_expected > max_reasonable: + # Fallback: use current duration + buffer if we've run > 3 hours + # (Assumes any cycle over 3 hours running is near completion when in ENDING) + if current_duration > 10800: # 3 hours + effective_expected = current_duration * 0.99 # Always past threshold + self._logger.debug( + "End spike check using fallback: expected_duration=%ds is unreasonable, " + "using current_duration=%ds as reference", + int(self._expected_duration), int(current_duration) + ) + + past_expected = ( + effective_expected > 0 + and current_duration >= (effective_expected * 0.98) + ) + + # If ENDING has already lasted long enough, treat any power burst as + # terminal (applies to all device types). Dishwashers additionally check + # proximity to the expected duration. + long_ending_tail = self._time_in_state >= 120.0 + terminal_spike = long_ending_tail + + if is_dishwasher: + near_expected = ( + effective_expected > 0 + and current_duration >= (effective_expected * 0.90) + ) + terminal_spike = near_expected or long_ending_tail + + if terminal_spike: + self._logger.debug( + "End spike kept in ENDING (duration %.0fs/%.0fs, time_in_ending %.0fs)", + current_duration, + effective_expected, + self._time_in_state, + ) + return + + if past_expected: + self._logger.debug( + "End spike ignored for state transition (past expected duration %.0fs/%.0fs)", + current_duration, effective_expected + ) + # Stay in ENDING, the spike is recorded but doesn't resume cycle + else: + # Resume -> RUNNING (spike is genuine mid-cycle activity) + self._transition_to(STATE_RUNNING, timestamp) + else: + # Periodic profile matching during ending + self._try_profile_match(timestamp) + + # --- SMART TERMINATION CHECK --- + # If we have a confident profile match and duration meets expectations, + # we terminate early (after appropriate debounce), ignoring long arbitrary timeouts. + if self._matched_profile: + start_time = self._current_cycle_start or timestamp + current_duration = (timestamp - start_time).total_seconds() + + # --- ROBUSTNESS UPGRADE --- + # 1. Require higher duration ratio for Smart path + # 2. Require debounce to be measured FROM entry into ENDING state + + if self._config.device_type == "dishwasher": + smart_ratio = ( + 0.99 # Very conservative for dishwashers to catch end spikes + ) + else: + smart_ratio = 0.98 + + is_confident_match = ( + getattr(self, "_last_match_confidence", 0.0) >= 0.4 + ) + + if ( + current_duration >= (self._expected_duration * smart_ratio) + and is_confident_match + ): + # Dynamic confirmation window + if self._config.device_type == "dishwasher": + smart_debounce = max(300.0, self._config.off_delay * 0.25) + else: + smart_debounce = 120.0 + + if self._time_in_state >= smart_debounce: + # --- END SPIKE WAIT PERIOD (Dishwashers) --- + # Dishwashers should see the real end-of-cycle + # pump-out (which arms _end_spike_seen via the 85% + # progress gate) before Smart Termination fires — + # otherwise the pump-out arrives AFTER the cycle + # has already closed and registers as a brand-new + # "ghost" cycle. User reports (issue #43) showed + # the original 5-min past_wait_period escape hatch + # closing the cycle ~4 min before the real pump-out + # at ~99.5% of expected. Widen the escape hatch + # substantially (DISHWASHER_END_SPIKE_WAIT_SECONDS, + # currently 30 min past expected) so it cannot + # short-circuit a pump-out that fires within a + # reasonable window around expected end, but still + # guarantees the cycle terminates eventually for + # dishwashers that have no pump-out at all. + end_spike_seen = getattr(self, "_end_spike_seen", False) + past_wait_period = current_duration >= ( + self._expected_duration + + DISHWASHER_END_SPIKE_WAIT_SECONDS + ) + if ( + self._config.device_type == "dishwasher" + and not end_spike_seen + and not past_wait_period + ): + self._logger.debug( + "Waiting for end spike (duration %.0fs, " + "expected %.0fs + %.0fs wait)", + current_duration, + self._expected_duration, + DISHWASHER_END_SPIKE_WAIT_SECONDS, + ) + return # Don't finish yet, wait for spike + + self._logger.info( + "Smart Termination: Profile '%s' match confirmed (duration %.0fs, " + "conf %.2f, spike_seen=%s), ending.", + self._matched_profile, + current_duration, + getattr(self, "_last_match_confidence", 0.0), + end_spike_seen, + ) + # Keep tail when smart terminating (matches profile duration) + self._finish_cycle( + timestamp, + status="completed", + termination_reason="smart", + keep_tail=True, + ) + return + + # --- FALLBACK TIMEOUT CHECK --- + # Rule: To separate cycles, we must wait at least min_off_gap. + effective_off_delay = max(self._config.off_delay, self._config.min_off_gap) + + # Energy gate always looks back off_delay seconds by default; + # overridden below for the dishwasher cap case so the window + # is consistent with the shortened effective_off_delay. + gate_window = self._config.off_delay + + # Dishwasher-specific: after a terminal end spike (pump-out), an + # unmatched cycle doesn't need to wait the full min_off_gap (up to + # 9000s) before closing. Cap at 30 min so cycle 3 ends cleanly + # ~30 min after the pump-out rather than sitting open for hours. + if ( + self._config.device_type == "dishwasher" + and not self._matched_profile + and self._end_spike_seen + ): + effective_off_delay = min(effective_off_delay, 1800) + gate_window = effective_off_delay + + if self._time_below_threshold >= effective_off_delay: + + recent_window = [ + r + for r in self._power_readings + if (timestamp - r[0]).total_seconds() <= gate_window + ] + + if not recent_window: + # Check deferred finish for matched profiles + start_time = self._current_cycle_start or timestamp + current_duration = (timestamp - start_time).total_seconds() + + if self._should_defer_finish(current_duration): + return + + # For dishwashers, use the timeout timestamp as end_time + # (keep_tail=True) so that the stored cycle duration includes + # the passive drying phase. Without this, end_time snaps back + # to _last_active_time which may be set by a terminal drain + # spike mid-ENDING, producing a falsely short cycle duration. + keep_tail = self._config.device_type == "dishwasher" + self._finish_cycle(timestamp, status="completed", keep_tail=keep_tail) + return + + # Compute energy in recent window + recent_ts = np.array([r[0].timestamp() for r in recent_window]) + recent_p = np.array([r[1] for r in recent_window]) + recent_e = integrate_wh(recent_ts, recent_p) + + if recent_e <= self.config.end_energy_threshold: + start_time = self._current_cycle_start or timestamp + current_duration = (timestamp - start_time).total_seconds() + + if self._should_defer_finish(current_duration): + return + + keep_tail = self._config.device_type == "dishwasher" + self._finish_cycle(timestamp, status="completed", keep_tail=keep_tail) + else: + + self._logger.debug( + "Cycle ending prevented by energy gate: %.4fWh > %.4fWh", + recent_e, + self._config.end_energy_threshold, + ) + + def _transition_to(self, new_state: str, timestamp: datetime) -> None: + """Handle state transitions.""" + if self._state == new_state: + return + + old_state = self._state + self._state = new_state + self._state_enter_time = timestamp + self._time_in_state = 0.0 + self._sub_state = new_state.capitalize() # Default substate + + # Reset energy accumulator on transition to OFF + if new_state == STATE_OFF: + self._energy_since_idle_wh = 0.0 + # Also reset idle time tracker when leaving ANTI_WRINKLE + self._anti_wrinkle_idle_time = 0.0 + if not self._preserve_delay_band_on_off: + self._delay_band_start = None + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._delay_wait_true_off_seconds = 0.0 + self._delay_wait_high_start = None + self._delay_wait_high_power = None + self._preserve_delay_band_on_off = False + + # Reset end spike tracker when entering ENDING state + if new_state == STATE_ENDING: + self._end_spike_seen = False + elif new_state == STATE_DELAY_WAIT: + # Band-accumulation tracker already played its role getting us + # here; reset it so a future OFF→band cycle starts fresh. + self._delay_band_start = None + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._delay_wait_true_off_seconds = 0.0 + self._delay_wait_high_start = None + self._delay_wait_high_power = None + self._sub_state = "Waiting to Start" + self._preserve_delay_band_on_off = False + elif new_state == STATE_ANTI_WRINKLE: + self._anti_wrinkle_candidate_start = None + self._anti_wrinkle_candidate_peak = 0.0 + self._anti_wrinkle_candidate_start_power = 0.0 + self._anti_wrinkle_idle_time = 0.0 # Reset idle time when entering ANTI_WRINKLE + self._sub_state = "Anti-Wrinkle" + elif new_state == STATE_STARTING: + # Reset idle time if exiting ANTI_WRINKLE to STARTING (high-power burst resumed) + self._anti_wrinkle_idle_time = 0.0 + elif new_state == STATE_RUNNING: + self._delay_band_start = None + self._delay_band_seconds = 0.0 + self._delay_band_peak = 0.0 + self._preserve_delay_band_on_off = False + + self._logger.debug("Transition: %s -> %s at %s", old_state, new_state, timestamp) + self._on_state_change(old_state, new_state) + + def should_defer_for_profile(self) -> bool: + """Check if we should defer termination for profile matching (public).""" + start_time = self._current_cycle_start + if not self._matched_profile or self._expected_duration <= 0 or not start_time: + return False + + current_duration = (dt_util.now() - start_time).total_seconds() + return self._should_defer_finish(current_duration) + + def _should_defer_finish(self, duration: float) -> bool: + """Check if we should defer termination based on expected duration.""" + # Check explicit verified pause override from manager + if getattr(self, "_verified_pause", False): + self._logger.debug("Deferring cycle finish: Verified pause active") + return True + + if not self._matched_profile or self._expected_duration <= 0: + return False + + # Safety: Don't defer forever + if duration > (self._expected_duration + DEFAULT_MAX_DEFERRAL_SECONDS): + self._logger.warning( + "Deferral limit exceeded (%.0fs > expected %.0f + %s), allowing finish", + duration, + self._expected_duration, + DEFAULT_MAX_DEFERRAL_SECONDS, + ) + return False + + # Dishwasher passive drying protection: + # Dishwashers can have 2+ hour passive drying phases at near-0W. A terminal + # drain spike that fires early in the ENDING state (e.g. at 120 min of a + # 233-min ECO cycle) resets _time_below_threshold, and the subsequent 60-min + # silence timeout would otherwise end the cycle at ~180 min — well before the + # real finish. Defer until the cycle reaches the late-phase threshold (the + # same one used by the end-spike arm gate, so both move together) so that + # smart termination can catch the true end (~99% of expected) instead. + # Confidence may be low this early, so the normal confidence gate is + # bypassed here. + if ( + self._config.device_type == "dishwasher" + and self._matched_profile + and self._expected_duration > 0 + and duration + < (self._expected_duration * DISHWASHER_END_SPIKE_MIN_PROGRESS) + ): + self._logger.debug( + "Deferring cycle finish: dishwasher drying phase protection " + "(%.0fs < %.0f%% of expected %.0fs, profile: %s, conf %.2f)", + duration, + DISHWASHER_END_SPIKE_MIN_PROGRESS * 100, + self._expected_duration, + self._matched_profile, + self._last_match_confidence, + ) + return True + + # Issue #43: dishwasher end-spike wait protection. Once past the 85% + # passive-drying gate above, we still keep the cycle deferred until + # the real end-of-cycle pump-out fires (sets _end_spike_seen=True via + # the 85% progress gate in STATE_ENDING) or we cross the + # smart-termination wait window (expected + 30 min) — whichever comes + # first. Shares DISHWASHER_END_SPIKE_WAIT_SECONDS with Smart + # Termination's wait branch so the two paths release the cycle at the + # same instant. Beyond the wait window, Smart Termination's + # past_wait_period kicks in and finalises; below it, the fallback + # timeout's energy gate is the safety net for cycles whose pump-out + # never arrives. + if ( + self._config.device_type == "dishwasher" + and self._matched_profile + and self._expected_duration > 0 + and not self._end_spike_seen + and duration + < (self._expected_duration + DISHWASHER_END_SPIKE_WAIT_SECONDS) + ): + self._logger.debug( + "Deferring cycle finish: dishwasher waiting for end-of-cycle " + "pump-out (%.0fs < expected %.0fs + %.0fs wait, profile: %s)", + duration, + self._expected_duration, + DISHWASHER_END_SPIKE_WAIT_SECONDS, + self._matched_profile, + ) + return True + + # If matched profile, enforce min duration ratio + ratio = self._config.min_duration_ratio + + # --- STRICTER DEFERRAL --- + # If we are NOT in a verified pause, but power has been low for a long time (ENDING state), + # we only defer if we are VERY confident this profile is correct. + # This prevents hanging on too-long profiles that matched early but are now diverging. + if self._last_match_confidence < DEFAULT_DEFER_FINISH_CONFIDENCE: + self._logger.debug( + "Not deferring finish: confidence %.2f too low for unverified pause (profile: %s)", + self._last_match_confidence, + self._matched_profile, + ) + return False + + # Also use profile tolerance to handle variable cycle lengths (e.g. long drying) + # Allow deferral up to Expected * (1 + tolerance) + upper_threshold = self._expected_duration * ( + 1.0 + self._config.profile_duration_tolerance + ) + + # Primary check: Is duration significantly below expectation? + if duration < (self._expected_duration * ratio): + self._logger.debug( + "Deferring cycle finish: duration %.0fs < %.0f%% of expected %.0fs (profile: %s, confidence %.2f)", + duration, + ratio * 100, + self._expected_duration, + self._matched_profile, + self._last_match_confidence, + ) + return True + + # Secondary check: If within valid completion window (ratio to tolerance), allow finish. + if duration <= upper_threshold: + return False + + # Tertiary check: If duration exceeded max tolerance, allow finish (failsafe). + return False + + def _finish_cycle( + self, + timestamp: datetime, + status: str = "completed", + termination_reason: str = "timeout", + keep_tail: bool = False, + ) -> None: + """Finalize cycle. + + Args: + timestamp: Time of completion + status: Cycle status string + termination_reason: Reason for termination + keep_tail: If True, use current timestamp as end time and preserve + trailing zero readings (e.g. Smart Termination). + If False (default), snap back to last active time and trim + trailing zeros (e.g. Timeout). + """ + + # Capture data before reset + if keep_tail: + end_time = timestamp + else: + end_time = self._last_active_time or timestamp + + if not self._current_cycle_start: + self.reset() + return + + duration = (end_time - self._current_cycle_start).total_seconds() + + # "Interrupted" logic (short cycle etc) + if duration < self._config.interrupted_min_seconds: + status = "interrupted" + elif duration < self._config.completion_min_seconds: + status = "interrupted" + elif self._abrupt_drop and duration < ( + self._config.interrupted_min_seconds + 90 + ): + status = "interrupted" + + # Trim leading/trailing zero readings for cleaner data + # If we keep tail, we explicitly do NOT trim end zeros + trimmed_readings = trim_zero_readings( + self._power_readings, + threshold=self._config.stop_threshold_w, + trim_end=not keep_tail, + ) + + # Ensure power_data covers the full duration until end_time + # (especially important for manual recordings or drying phases with no sensor updates) + final_readings = list(trimmed_readings) + if final_readings: + last_t, last_p = final_readings[-1] + if last_t < end_time: + final_readings.append((end_time, last_p)) + + start_ts = self._current_cycle_start.timestamp() + cycle_data: dict[str, Any] = { + "start_time": self._current_cycle_start.isoformat(), + "end_time": end_time.isoformat(), + "duration": duration, + "max_power": self._cycle_max_power, + "status": status, + "termination_reason": termination_reason, + "power_data": [[round(t.timestamp() - start_ts, 1), p] for t, p in final_readings], + } + + self._logger.info("Cycle Finished: %s, %.1f min", status, duration / 60) + self._on_cycle_end(cycle_data) + + target = STATE_FINISHED + if status == "interrupted": + target = STATE_INTERRUPTED + elif status == "force_stopped": + target = STATE_FORCE_STOPPED + elif ( + status == "completed" + and termination_reason in {"timeout", "smart"} + and self._config.anti_wrinkle_enabled + and self._config.device_type in (DEVICE_TYPE_DRYER, DEVICE_TYPE_WASHER_DRYER) + ): + target = STATE_ANTI_WRINKLE + + self.reset(target_state=target) + + # Stub methods for compatibility or simpler logic + def force_end(self, timestamp: datetime) -> None: + """Force the cycle to end immediately.""" + if self._state != STATE_OFF: + self._finish_cycle( + timestamp, + status="force_stopped", + termination_reason="force_stopped", + keep_tail=False, # Force stop usually implies snap back to reality + ) + self._ignore_power_until_idle = False + + def user_stop(self) -> None: + """Handle user-initiated stop.""" + if self._state != STATE_OFF: + now = dt_util.now() + self._finish_cycle( + now, + status="completed", + termination_reason="user", + keep_tail=True, # User implies "Done Now" + ) + # Prevent immediate restart if power is still high + self._ignore_power_until_idle = True + # Anchor the lockout clock to this stop instant. The next reading's + # dt is measured from the last processed sample, which predates the + # stop, so without this the high-power accumulator would count the + # pre-stop gap and release the lockout early (#267). + self._lockout_high_seconds = 0.0 + self._last_process_time = now + + + def get_power_trace(self) -> list[tuple[datetime, float]]: + """Return the current power trace.""" + return list(self._power_readings) + + def get_state_snapshot(self) -> dict[str, Any]: + """Get a snapshot of the current state for persistence.""" + return { + "state": self._state, + "sub_state": self._sub_state, + "current_cycle_start": ( + self._current_cycle_start.isoformat() + if self._current_cycle_start + else None + ), + "power_readings": [(t.isoformat(), p) for t, p in self._power_readings], + "accumulated_energy_wh": self._energy_since_idle_wh, + "time_above": self._time_above_threshold, + "time_below": self._time_below_threshold, + "cycle_max_power": self._cycle_max_power, + "last_active_time": ( + self._last_active_time.isoformat() if self._last_active_time else None + ), + "expected_duration": self._expected_duration, + "matched_profile": self._matched_profile, + "state_enter_time": ( + self._state_enter_time.isoformat() if self._state_enter_time else None + ), + "end_spike_seen": self._end_spike_seen, + } + + def get_elapsed_seconds(self) -> float: + """Return seconds elapsed in current cycle.""" + if self._current_cycle_start: + return (dt_util.now() - self._current_cycle_start).total_seconds() + return 0.0 + + def is_waiting_low_power(self) -> bool: + """Return True if we are pending end/pause due to low power.""" + return ( + self._state in (STATE_RUNNING, STATE_PAUSED, STATE_ENDING) + and self._time_below_threshold > 0 + ) + + def low_power_elapsed(self, now: datetime) -> float: + """Return duration of current low power spell including time since last process.""" + if self._time_below_threshold > 0 and self._last_process_time: + # Add time since last processing + return ( + self._time_below_threshold + + (now - self._last_process_time).total_seconds() + ) + return self._time_below_threshold + + def restore_state_snapshot(self, snapshot: dict[str, Any]) -> None: + """Restore state from snapshot.""" + try: + self._state = snapshot.get("state", STATE_OFF) + self._sub_state = snapshot.get("sub_state") + self._energy_since_idle_wh = snapshot.get("accumulated_energy_wh", 0.0) + self._time_above_threshold = snapshot.get("time_above", 0.0) + self._time_below_threshold = snapshot.get("time_below", 0.0) + self._cycle_max_power = snapshot.get("cycle_max_power", 0.0) + # Sanitize via the same helper as update_match so the class + # invariant on _expected_duration holds across restarts and the + # gates in STATE_ENDING / _should_defer_finish can trust the value. + # If sanitization rejects the snapshot's expected_duration, also + # clear the matched_profile so we don't restore a half-valid state + # where Smart Termination can fire on _expected_duration == 0.0. + restored_match = snapshot.get("matched_profile") + sanitized_expected = self._sanitize_expected_duration( + snapshot.get("expected_duration", 0.0), + source="restore_state_snapshot", + ) + if ( + restored_match is not None + and sanitized_expected == self._SANITIZE_INVALID_SENTINEL + ): + self._logger.debug( + "restore_state_snapshot: dropping matched_profile %r " + "because expected_duration sanitized to invalid sentinel", + restored_match, + ) + self._matched_profile = None + else: + self._matched_profile = restored_match + self._expected_duration = sanitized_expected + self._end_spike_seen = snapshot.get("end_spike_seen", False) + + # Restore state enter time and recompute time_in_state from it + enter_time = snapshot.get("state_enter_time") + if enter_time: + try: + self._state_enter_time = dt_util.parse_datetime(enter_time) + if self._state_enter_time: + elapsed = (dt_util.now() - self._state_enter_time).total_seconds() + self._time_in_state = max(0.0, elapsed) + except Exception: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to parse state enter time") + + start = snapshot.get("current_cycle_start") + self._current_cycle_start = None + if start: + try: + dt_start = dt_util.parse_datetime(start) + if dt_start and dt_start.tzinfo is None: + # Fix Naive Timestamp (Legacy Data) + dt_start = dt_start.replace(tzinfo=dt_util.now().tzinfo) + self._logger.warning("Restored Naive start_time, assuming local: %s", dt_start) + self._current_cycle_start = dt_start + except Exception: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to parse start time: %s", start) + + readings = snapshot.get("power_readings", []) + self._power_readings = [] + + # Detect naive readings once + has_naive_readings = False + + for r in readings: + if isinstance(r, (list, tuple)): + reading = cast(list[Any] | tuple[Any, ...], r) + if len(reading) < 2: + continue + try: + t = dt_util.parse_datetime(str(reading[0])) + if t: + if t.tzinfo is None: + t = t.replace(tzinfo=dt_util.now().tzinfo) + has_naive_readings = True + value = float(reading[1]) + if math.isfinite(value): + self._power_readings.append((t, value)) + except (TypeError, ValueError) as exc: + self._logger.debug("Skipping malformed power reading %s: %s", r, exc) + + if has_naive_readings: + self._logger.warning( + "Restored %d power readings with Naive timestamps (fixed to local)", + len(self._power_readings), + ) + + # Restore last active + last_active = snapshot.get("last_active_time") + if last_active: + dt_last = dt_util.parse_datetime(last_active) + if dt_last and dt_last.tzinfo is None: + dt_last = dt_last.replace(tzinfo=dt_util.now().tzinfo) + self._last_active_time = dt_last + else: + self._last_active_time = self._current_cycle_start + + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Failed restore: %s", e) + self.reset() \ No newline at end of file diff --git a/custom_components/ha_washdata/diag_buffer.py b/custom_components/ha_washdata/diag_buffer.py new file mode 100644 index 0000000..56ee35d --- /dev/null +++ b/custom_components/ha_washdata/diag_buffer.py @@ -0,0 +1,187 @@ +"""Diagnostic ring buffers for WashData - rolling 24-hour window. + +Each WashDataManager owns one DiagBuffer instance that accumulates three +independent time-series for the last 24 hours: + + power_trace - every raw power-sensor reading, before throttling + state_history - detector state transitions (off/starting/running/...) + logs - DEBUG-and-above log lines emitted by this integration + +All buffers are in-memory only (no disk writes) so they impose zero I/O +overhead and vanish cleanly on HA restart. The 24-hour window caps memory +at a predictable ceiling regardless of sensor polling rate. +""" + +from __future__ import annotations + +import logging +from collections import deque +from datetime import datetime, timedelta, timezone +from typing import Any + +from homeassistant.util import dt as dt_util + +_WINDOW = timedelta(hours=24) + +# Hard upper-bound on entries per buffer. At a 1-second sensor interval, +# 100 000 power entries ~= 27 hours - enough to always cover the window. +_MAX_POWER = 100_000 +_MAX_LOGS = 5_000 +_MAX_STATES = 2_000 + +_INTEGRATION_LOGGER_NAME = "custom_components.ha_washdata" + + +def _ts_iso(unix: float) -> str: + return datetime.fromtimestamp(unix, tz=timezone.utc).isoformat() + + +class _LogHandler(logging.Handler): + """Logging handler that buffers records for a single named device. + + Installed on the integration root logger so it receives all records + produced anywhere inside *custom_components.ha_washdata*, then keeps + only the ones whose formatted message contains ``[device_name]`` - + the prefix injected by :class:`~.log_utils.DeviceLoggerAdapter`. + """ + + def __init__(self, device_name: str) -> None: + super().__init__() + # Match the exact prefix added by DeviceLoggerAdapter + self._tag = f"[{device_name}]" + # Store (created_float, levelname, message) + self._buf: deque[tuple[float, str, str]] = deque(maxlen=_MAX_LOGS) + + def emit(self, record: logging.LogRecord) -> None: + try: + msg = record.getMessage() + if self._tag not in msg: + return + self._buf.append((record.created, record.levelname, msg)) + except Exception: # pylint: disable=broad-except + self.handleError(record) + + def snapshot(self, cutoff: float) -> list[dict[str, Any]]: + items = list(self._buf) + return [ + {"ts": _ts_iso(ts), "lvl": lvl, "msg": msg} + for ts, lvl, msg in items + if ts >= cutoff + ] + + +class DiagBuffer: + """Per-device diagnostic ring buffer aggregating power, states, and logs. + + Lifecycle:: + + # on manager creation + self.diag_buffer = DiagBuffer(config_entry.title) + + # on each raw power reading + self.diag_buffer.record_power(watts, timestamp) + + # on each detector state transition + self.diag_buffer.record_state(old, new, program, timestamp) + + # on manager shutdown + self.diag_buffer.uninstall() + + # in diagnostics.py + snapshot = manager.diag_buffer.snapshot() + """ + + def __init__(self, device_name: str) -> None: + self._device_name = device_name + + # Raw power readings: (unix_ts_float, watts) + self._power: deque[tuple[float, float]] = deque(maxlen=_MAX_POWER) + + # State transitions: (unix_ts_float, from_state, to_state, program) + self._states: deque[tuple[float, str, str, str]] = deque(maxlen=_MAX_STATES) + + # Log handler - installed on the integration root logger + self._log_handler = _LogHandler(device_name) + logging.getLogger(_INTEGRATION_LOGGER_NAME).addHandler(self._log_handler) + + # ------------------------------------------------------------------ + # Recording helpers + # ------------------------------------------------------------------ + + def record_power(self, watts: float, ts: datetime) -> None: + """Record one raw power-sensor reading (call *before* any throttling).""" + self._power.append((ts.timestamp(), watts)) + + def record_state( + self, + from_state: str, + to_state: str, + program: str, + ts: datetime, + ) -> None: + """Record a detector state transition.""" + self._states.append((ts.timestamp(), from_state, to_state, program)) + + # ------------------------------------------------------------------ + # Snapshot + # ------------------------------------------------------------------ + + def redacted_snapshot(self) -> dict[str, Any]: + """Like :meth:`snapshot` but with identifying fields removed. + + Strips ``device_name`` from the top-level dict and removes the ``msg`` + field from each log entry so raw log text (which contains the device + name prefix injected by :class:`~.log_utils.DeviceLoggerAdapter`) is + not included in exported diagnostics. + """ + data = self.snapshot() + data.pop("device_name", None) + data["logs"] = [ + {k: v for k, v in entry.items() if k != "msg"} + for entry in data.get("logs", []) + ] + return data + + def snapshot(self) -> dict[str, Any]: + """Return all three buffers filtered to the last 24 hours. + + Returned structure:: + + { + "window_hours": 24, + "device_name": "...", + "power_trace": [[iso_ts, watts], ...], + "state_history": [{"ts": ..., "from": ..., "to": ..., "program": ...}, ...], + "logs": [{"ts": ..., "lvl": ..., "msg": ...}, ...], + } + """ + cutoff = (dt_util.now() - _WINDOW).timestamp() + power_items = list(self._power) + state_items = list(self._states) + return { + "window_hours": 24, + "device_name": self._device_name, + "power_trace": [ + [_ts_iso(ts), w] + for ts, w in power_items + if ts >= cutoff + ], + "state_history": [ + {"ts": _ts_iso(ts), "from": f, "to": t, "program": prog} + for ts, f, t, prog in state_items + if ts >= cutoff + ], + "logs": self._log_handler.snapshot(cutoff), + } + + # ------------------------------------------------------------------ + # Lifecycle + # ------------------------------------------------------------------ + + def uninstall(self) -> None: + """Remove the log handler from the integration logger. + + Must be called from ``WashDataManager.async_shutdown()`` to avoid + accumulating stale handlers across config-entry reloads. + """ + logging.getLogger(_INTEGRATION_LOGGER_NAME).removeHandler(self._log_handler) diff --git a/custom_components/ha_washdata/diagnostics.py b/custom_components/ha_washdata/diagnostics.py new file mode 100644 index 0000000..08948f2 --- /dev/null +++ b/custom_components/ha_washdata/diagnostics.py @@ -0,0 +1,90 @@ +"""Diagnostics support for WashData.""" + +from __future__ import annotations + +from typing import Any + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant + +from .const import DOMAIN +from .manager import WashDataManager + +# Keys that can identify the user or their home network - redacted in all contexts. +_SENSITIVE_KEYS = { + "auth", + "entry_id", + "flow_id", + "flow_title", + "handler", + "name", + "source", + "title", + "unique_id", + "user_id", + # HA entity / service references that reveal home topology. + "notify_service", + "notify_start_services", + "notify_finish_services", + "notify_live_services", + "notify_people", + "notify_actions", + "power_sensor", + "external_end_trigger", + "door_sensor_entity", + "switch_entity", + "energy_price_entity", +} + + +def _redact(obj: Any) -> Any: + if isinstance(obj, dict): + return { + k: "**REDACTED**" if k in _SENSITIVE_KEYS else _redact(v) + for k, v in obj.items() + } + if isinstance(obj, list): + return [_redact(v) for v in obj] + return obj + + +async def async_get_config_entry_diagnostics( + hass: HomeAssistant, entry: ConfigEntry +) -> dict[str, Any]: + """Return diagnostics for a config entry.""" + manager: WashDataManager = hass.data[DOMAIN][entry.entry_id] + + # Full store export - same payload as the export_config service, but the + # entry_data / entry_options pass through the redactor to strip personal keys. + exported: dict[str, Any] = manager.profile_store.export_data( + entry_data=dict(entry.data), + entry_options=dict(entry.options), + ) + + return { + "entry": _redact(entry.as_dict()), + "manager_state": { + "current_state": manager.check_state(), + "current_program": manager.current_program, + "time_remaining": manager.time_remaining, + "cycle_progress": manager.cycle_progress, + "sample_interval_stats": ( + dict(manager.sample_interval_stats) + if isinstance(manager.sample_interval_stats, dict) + else {} + ), + "profile_sample_repair_stats": manager.profile_sample_repair_stats, + "suggestions": manager.profile_store.get_suggestions(), + "feature_flags": { + "auto_maintenance": bool(getattr(manager, "_auto_maintenance", False)), + "save_debug_traces": bool(getattr(manager, "_save_debug_traces", False)), + "notify_fire_events": bool(getattr(manager, "_notify_fire_events", False)), + }, + }, + "store_export": _redact(exported), + # Rolling 24-hour in-memory buffers (redacted: msg fields stripped from logs). + # power_trace: [[iso_ts, watts], ...] - every raw sensor reading + # state_history: [{ts, from, to, program}, ...] - detector state changes + # logs: [{ts, lvl}, ...] - log timestamps and levels (msg removed) + "live_diagnostics": manager.diag_buffer.redacted_snapshot(), + } diff --git a/custom_components/ha_washdata/features.py b/custom_components/ha_washdata/features.py new file mode 100644 index 0000000..25a0462 --- /dev/null +++ b/custom_components/ha_washdata/features.py @@ -0,0 +1,249 @@ +"""Feature extraction logic for WashData. + +Constraint: NumPy only. +Constraint: All computations must be dt-aware. +""" + +from dataclasses import dataclass +import numpy as np + + +@dataclass +class PowerEvent: + """Represent a detected power change event.""" + + timestamp: float + magnitude: float # Absolute change in Watts + rate: float # Slope W/s + direction: str # "rising" or "falling" + + +@dataclass +class CyclePhase: + """Represent a distinct phase within a cycle.""" + + start_ts: float + end_ts: float + label: str # HEATER, MOTOR, IDLE, etc. + avg_power: float + + +@dataclass +class CycleSignature: + """Compact signature for fast matching/rejection.""" + + duration: float + total_energy: float + max_power: float + event_density: float # Events per minute + time_to_first_high: float # Seconds to first HEATER/HIGH phase + high_phase_ratio: float # Duration of high phases / total duration + # Distributions (quantiles of power) + p05: float + p25: float + p50: float + p75: float + p95: float + + +def detect_events( + timestamps: np.ndarray, + power: np.ndarray, + idle_mad: float, + min_event_watts: float = 50.0, +) -> list[PowerEvent]: + """Detect significant power events using dp/dt. + + Args: + timestamps: Time array (seconds). + power: Power array (Watts). + idle_mad: Media Absolute Deviation of idle baseline (noise floor). + min_event_watts: Absolute floor for an event to be considered. + """ + if len(power) < 2: + return [] + + dt = np.diff(timestamps) + dp = np.diff(power) + + # Avoid div by zero + valid = dt > 0.1 + rate = np.zeros_like(dp) + rate[valid] = dp[valid] / dt[valid] + + # Adaptive threshold + # 3-sigma equivalent: 3 * 1.4826 * MAD ~= 4.5 * MAD + # But for dp/dt, noise scales differently. + # Let's use absolute threshold + noise factor. + noise_allowance = max(10.0, 5.0 * idle_mad) + + events: list[PowerEvent] = [] + + for i, r in enumerate(rate): + if not valid[i]: + continue + + mag = abs(dp[i]) + + # Criteria: Significant rate AND significant magnitude + # We want to ignore small jitter even if rate is high (dt small) + if mag > min_event_watts and abs(r) > noise_allowance: # Rate threshold W/s + # Basic check: if dt is tiny (1s) and power jump is 50W, rate is 50 W/s. + # If dt is 10s and power jump is 50W, rate is 5 W/s. + # Real heater on: 2000W in ~2s => 1000 W/s. + # Motor tumble: 200W in 1s => 200 W/s. + + direction = "rising" if r > 0 else "falling" + events.append( + PowerEvent( + timestamp=timestamps[i], magnitude=mag, rate=r, direction=direction + ) + ) + + return events + + +def segment_phases(timestamps: np.ndarray, power: np.ndarray) -> list[CyclePhase]: + """Segment cycle into phases using quantile-based thresholds. + + Labels: + - IDLE: < p10 (or min threshold) + - MOTOR: p25 - p75 approx + - HEATER/HIGH: > p90 + + Refined logic: + 1. Calculate cycle quantiles. + 2. Define levels: LOW, MED, HIGH. + 3. Run-length encoding or simple state machine. + """ + if len(power) < 10: + return [] + + # Quantiles + q_low = np.percentile(power, 25) + q_high = np.percentile(power, 90) + + # Enforce device minimums to avoid "High" label on a 5W phone charger cycle + min_high = 500.0 + min_motor = 50.0 + + # Adjust thresholds + thresh_high = max(q_high, min_high) + thresh_med = max(q_low, min_motor) + + labels: list[str] = [] + for p in power: + if p >= thresh_high: + labels.append("HEATER") + elif p >= thresh_med: + labels.append("MOTOR") + else: + labels.append("IDLE") + + # Merge consecutive + phases: list[CyclePhase] = [] + if not labels: + return [] + + current_label: str = labels[0] + start_idx = 0 + + for i in range(1, len(labels)): + if labels[i] != current_label: + # End current phase + phases.append( + CyclePhase( + start_ts=timestamps[start_idx], + end_ts=timestamps[i - 1], + label=current_label, + avg_power=float(np.mean(power[start_idx:i])), + ) + ) + current_label = labels[i] + start_idx = i + + # Last one + phases.append( + CyclePhase( + start_ts=timestamps[start_idx], + end_ts=timestamps[-1], + label=current_label, + avg_power=float(np.mean(power[start_idx:])), + ) + ) + + return phases + + +def compute_signature( + timestamps: np.ndarray, power: np.ndarray, events: list[PowerEvent] | None = None +) -> CycleSignature: + """Compute compact signature for candidate rejection/matching. + + Args: + timestamps: Timestamps (seconds) + power: Power (Watts) + events: Pre-computed events (optional) + """ + if len(power) == 0: + # Return empty/zero signature + return CycleSignature(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) + + duration = timestamps[-1] - timestamps[0] + + # Energy approx + dt = np.diff(timestamps) + # Simple rectangular for speed here, or integrate_wh + if len(dt) > 0: + p_avg = (power[:-1] + power[1:]) / 2 + total_energy = np.sum(p_avg * (dt / 3600.0)) + else: + total_energy = 0.0 + + max_p = np.max(power) + + # Quantiles + qs = np.percentile(power, [5, 25, 50, 75, 95]) + + # Time to first HIGH (heater) + # Heuristic: first time power > 800W or > 0.8 * max_p + thresh_high = max(800.0, 0.8 * max_p) + high_indices = np.where(power > thresh_high)[0] + if len(high_indices) > 0: + time_to_first_high = timestamps[high_indices[0]] - timestamps[0] + else: + time_to_first_high = duration # No high phase detected + + # High Phase Ratio + high_mask = power > thresh_high + # Time in high / total time + # Check dt where high_mask holds + if len(dt) > 0: + # Align mask with intervals + # mask[i] corresponds to interval i? roughly + high_dur = np.sum(dt[high_mask[:-1]]) + high_phase_ratio = high_dur / duration if duration > 0 else 0 + else: + high_phase_ratio = 0.0 + + # Event density + if not events: + # Compute locally if needed, but ideally passed in + pass + + event_count = len(events) if events else 0 + event_density = (event_count / (duration / 60.0)) if duration > 60 else 0 + + return CycleSignature( + duration=float(duration), + total_energy=float(total_energy), + max_power=float(max_p), + event_density=float(event_density), + time_to_first_high=float(time_to_first_high), + high_phase_ratio=float(high_phase_ratio), + p05=float(qs[0]), + p25=float(qs[1]), + p50=float(qs[2]), + p75=float(qs[3]), + p95=float(qs[4]), + ) diff --git a/custom_components/ha_washdata/frontend.py b/custom_components/ha_washdata/frontend.py new file mode 100644 index 0000000..7a30da4 --- /dev/null +++ b/custom_components/ha_washdata/frontend.py @@ -0,0 +1,223 @@ +"""Frontend card registration for WashData.""" + +import logging +import os +from pathlib import Path +from typing import Any, Literal, TypedDict, cast +from homeassistant.core import HomeAssistant, Event +from homeassistant.const import EVENT_COMPONENT_LOADED + +_LOGGER = logging.getLogger(__name__) + +LOCAL_SUBDIR = "ha_washdata" +CARD_NAME = "ha-washdata-card.js" +INTEGRATION_URL = f"/{LOCAL_SUBDIR}/{CARD_NAME}" +CARD_REGISTERED = "registered" +CARD_DEFERRED = "deferred" +CARD_FAILED = "failed" +CardRegisterResult = Literal["registered", "deferred", "failed"] + + +class LovelaceResourceItem(TypedDict, total=False): + """Known lovelace resource item shape used by this integration.""" + + id: str + url: str + res_type: str + + +def get_cache_buster() -> str: + """Generate a stable cache buster based on card asset mtime.""" + try: + src = Path(__file__).parent / "www" / CARD_NAME + return str(int(os.path.getmtime(src))) + except OSError: + # Deterministic fallback when file is unavailable. + return "1" + + +def _register_static_path(hass: HomeAssistant, url_path: str, path: str) -> None: + """Register a static path with the HA HTTP component, compatible with multiple HA versions.""" + try: + # pylint: disable=import-outside-toplevel + from homeassistant.components.http import StaticPathConfig + + if hasattr(hass.http, "async_register_static_paths"): + + async def _safe_register(): + try: + await hass.http.async_register_static_paths( + [StaticPathConfig(url_path, path, True)] + ) + except Exception as exc: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Failed to async register static path %s -> %s: %s", + url_path, + path, + exc, + ) + + hass.async_create_task(_safe_register()) + return + except Exception as exc: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Async static path registration not available; falling back to " + "sync registration for %s -> %s (%s)", + url_path, + path, + exc, + ) + + # Fallback for older HA + try: + http_obj = cast(Any, hass.http) + register_static_path = getattr(http_obj, "register_static_path", None) + if callable(register_static_path): + register_static_path(url_path, path, cache_headers=True) + except Exception: # pylint: disable=broad-exception-caught + _LOGGER.debug("Failed to register static path %s -> %s", url_path, path) + + +async def _init_resource(hass: HomeAssistant, url: str, ver: str) -> bool: + """Safely add or update a Lovelace resource for the given URL.""" + try: + # pylint: disable=import-outside-toplevel + from homeassistant.components.frontend import add_extra_js_url + from homeassistant.components.lovelace.resources import ( + ResourceStorageCollection, + ) + except Exception: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Lovelace resource helpers unavailable; skipping auto resource init" + ) + return False + + lovelace = hass.data.get("lovelace") + if not lovelace: + _LOGGER.debug("Lovelace storage not available; skipping auto resource init") + return False + + resources = ( + lovelace.resources if hasattr(lovelace, "resources") else lovelace["resources"] + ) + + url2 = f"{url}?v={ver}" + + if not isinstance(resources, ResourceStorageCollection): + _LOGGER.debug("Add extra JS module (non-storage): %s", url2) + add_extra_js_url(hass, url2) + return True + + resources_obj = resources + await resources_obj.async_get_info() + + for raw_item in resources_obj.async_items(): + if not isinstance(raw_item, dict): + continue + + item = cast(LovelaceResourceItem, raw_item) + item_url = item.get("url") + if not isinstance(item_url, str) or not item_url.startswith(url): + continue + + if item_url == url2 and item.get("res_type") == "module": + return True + + item_id = item.get("id") + if not isinstance(item_id, str): + continue + + _LOGGER.debug("Update lovelace resource to: %s", url2) + await resources_obj.async_update_item( + item_id, {"res_type": "module", "url": url2} + ) + + return True + + _LOGGER.debug("Add new lovelace resource: %s", url2) + await resources_obj.async_create_item({"res_type": "module", "url": url2}) + + return True + + +class WashDataCardRegistration: + """Serve ha-washdata-card.js from the integration package.""" + + def __init__(self, hass: HomeAssistant) -> None: + self.hass = hass + + def _src_path(self) -> Path: + return Path(__file__).parent / "www" / CARD_NAME + + async def async_register(self) -> CardRegisterResult: + """Register card assets/resources and report registration outcome.""" + src = self._src_path() + if not src.exists(): + _LOGGER.warning("Card file not found: %s", src) + return CARD_FAILED + + _register_static_path(self.hass, INTEGRATION_URL, str(src)) + + version = get_cache_buster() + + # Try auto-registration of the lovelace resource + # If lovelace is not yet loaded, wait for it + if not self.hass.data.get("lovelace"): + _LOGGER.debug("Lovelace not loaded yet; waiting for component loaded event") + + unsubscribe_on_lovelace_loaded: Any = None + + async def _on_lovelace_loaded(event: Event) -> None: + if event.data.get("component") == "lovelace": + _LOGGER.debug( + "Lovelace component loaded; retrying resource registration" + ) + if unsubscribe_on_lovelace_loaded: + unsubscribe_on_lovelace_loaded() + try: + if await _init_resource(self.hass, INTEGRATION_URL, version): + self.hass.data["ha_washdata_card_registered"] = True + self.hass.data["ha_washdata_card_deferred"] = False + else: + self.hass.data["ha_washdata_card_deferred"] = False + except Exception: # pylint: disable=broad-exception-caught + self.hass.data["ha_washdata_card_deferred"] = False + _LOGGER.debug( + "Delayed auto-registration of lovelace resource failed for %s", + INTEGRATION_URL, + ) + + unsubscribe_on_lovelace_loaded = self.hass.bus.async_listen(EVENT_COMPONENT_LOADED, _on_lovelace_loaded) + + # Re-check in case lovelace loaded between the initial check and listener registration. + if self.hass.data.get("lovelace"): + unsubscribe_on_lovelace_loaded() + _LOGGER.debug("Lovelace already loaded after deferred listener; registering now") + try: + if await _init_resource(self.hass, INTEGRATION_URL, version): + self.hass.data["ha_washdata_card_registered"] = True + self.hass.data["ha_washdata_card_deferred"] = False + return CARD_REGISTERED + self.hass.data["ha_washdata_card_deferred"] = False + return CARD_FAILED + except Exception: # pylint: disable=broad-exception-caught + self.hass.data["ha_washdata_card_deferred"] = False + return CARD_FAILED + + return CARD_DEFERRED + + # Lovelace is already loaded + try: + registered = await _init_resource(self.hass, INTEGRATION_URL, version) + except Exception as err: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Auto-registration of lovelace resource failed for %s: %s", + INTEGRATION_URL, + err, + ) + return CARD_FAILED + + if registered: + _LOGGER.debug("Auto-registered lovelace resource for %s", INTEGRATION_URL) + return CARD_REGISTERED + return CARD_FAILED diff --git a/custom_components/ha_washdata/learning.py b/custom_components/ha_washdata/learning.py new file mode 100644 index 0000000..78fae2b --- /dev/null +++ b/custom_components/ha_washdata/learning.py @@ -0,0 +1,791 @@ +"""Learning and self-tuning logic for WashData.""" + +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any, Optional, TYPE_CHECKING, cast + +import numpy as np +from homeassistant.core import HomeAssistant +from homeassistant.helpers import translation +from homeassistant.helpers.dispatcher import async_dispatcher_send +import homeassistant.util.dt as dt_util + +from .const import ( + CONF_AUTO_LABEL_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_END_ENERGY_THRESHOLD, + CONF_LEARNING_CONFIDENCE, + CONF_MIN_OFF_GAP, + CONF_MIN_POWER, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_OFF_DELAY, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_RUNNING_DEAD_ZONE, + CONF_SAMPLING_INTERVAL, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS, + CONF_WATCHDOG_INTERVAL, + DEFAULT_AUTO_LABEL_CONFIDENCE, + DEFAULT_DURATION_TOLERANCE, + DEFAULT_LEARNING_CONFIDENCE, + DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS, + DOMAIN, + SIGNAL_WASHER_UPDATE, +) +from .suggestion_engine import SuggestionEngine +from .log_utils import DeviceLoggerAdapter + +if TYPE_CHECKING: + from .profile_store import ProfileStore + + +_LOGGER = logging.getLogger(__name__) + + +class StatisticalModel: + """Helper to track running stats for a metric.""" + + def __init__(self, max_samples: int = 200) -> None: + self._samples: list[float] = [] + self._max_samples = max_samples + self._last_update: datetime | None = None + self._stats: dict[str, Any] = {"median": None, "p95": None, "count": 0} + + def add_sample(self, value: float, now: datetime) -> None: + """Add a sample and update stats.""" + self._samples.append(value) + if len(self._samples) > self._max_samples: + self._samples = self._samples[-self._max_samples:] + self._last_update = now + self._compute_stats() + + def _compute_stats(self) -> None: + if not self._samples: + self._stats = {"median": None, "p95": None, "count": 0} + return + + arr = np.array(self._samples) + self._stats = { + "median": float(np.median(arr)), + "p95": float(np.percentile(arr, 95)), + "count": int(len(self._samples)), + } + + @property + def median(self) -> float | None: + """Return the median of samples.""" + return self._stats.get("median") + + @property + def p95(self) -> float | None: + """Return the 95th percentile of samples.""" + return self._stats.get("p95") + + @property + def count(self) -> int: + """Return the number of samples.""" + return self._stats.get("count", 0) + + +class LearningManager: + """Manages cycle learning, user feedback, and auto-tuning.""" + + def __init__( + self, + hass: HomeAssistant, + entry_id: str, + profile_store: "ProfileStore", + device_type: str | None = None, + device_name: str = "", + ) -> None: + """Initialize the learning manager.""" + self._logger = DeviceLoggerAdapter(_LOGGER, device_name) + self.hass = hass + self.entry_id = entry_id + self.profile_store = profile_store + self.device_type = device_type + self.suggestion_engine = SuggestionEngine( + hass, entry_id, profile_store, device_type + ) + + # Operational Stats + self._sample_interval_model = StatisticalModel(max_samples=200) + self._last_suggestion_update: datetime | None = None + self._last_batch_simulation_count: int = 0 # track when to re-run batch + + def _apply_suggestions_and_notify(self, suggestions: dict[str, Any]) -> None: + """Apply suggestions and notify once when they become actionable.""" + if not suggestions: + return + + _actionable_keys = ( + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SAMPLING_INTERVAL, + CONF_PROFILE_MATCH_INTERVAL, + CONF_AUTO_LABEL_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_MIN_OFF_GAP, + CONF_STOP_THRESHOLD_W, + CONF_START_THRESHOLD_W, + CONF_END_ENERGY_THRESHOLD, + CONF_RUNNING_DEAD_ZONE, + ) + + # Drop suggestions whose value already matches the current config - so + # that applied suggestions don't immediately reappear on the next cycle. + entry = self.hass.config_entries.async_get_entry(self.entry_id) + current_options: dict[str, Any] = {} + if entry: + current_options = {**entry.data, **entry.options} + + filtered_suggestions: dict[str, Any] = {} + for key, data in suggestions.items(): + if isinstance(data, dict) and "value" in data: + current_val = current_options.get(key) + suggested_val = data["value"] + if current_val is not None and suggested_val is not None: + try: + if float(current_val) == float(suggested_val): + self.profile_store.delete_suggestion(key) + continue # already applied, remove stale entry + except (TypeError, ValueError): + pass + filtered_suggestions[key] = data + + if not filtered_suggestions: + return + + def _count_actionable(s: dict) -> int: + return sum( + 1 for k in _actionable_keys + if isinstance(s.get(k), dict) and s[k].get("value") is not None + ) + + current = self.profile_store.get_suggestions() + before_count = _count_actionable(current) if isinstance(current, dict) else 0 + + self.suggestion_engine.apply_suggestions(filtered_suggestions) + + updated = self.profile_store.get_suggestions() + after_count = _count_actionable(updated) if isinstance(updated, dict) else 0 + + if before_count == 0 and after_count > 0: + device_title = entry.title if entry else DOMAIN + self.hass.async_create_task( + self._async_send_suggestions_ready_notification(device_title, after_count) + ) + + def process_power_reading( + self, _power: float, now: datetime, last_reading_time: datetime | None + ) -> None: + """Ingest power reading metadata for statistical analysis.""" + if last_reading_time: + delta = (now - last_reading_time).total_seconds() + # Ignore ultra-small jitter (<0.1s) and massive gaps (>1800s - likely downtime) + if 0.1 < delta < 1800: + self._sample_interval_model.add_sample(delta, now) + + # Periodically update suggestions based on operational stats + if ( + self._last_suggestion_update is None + or (now - self._last_suggestion_update).total_seconds() > 300 # Check every 5 mins + ): + self._update_operational_suggestions(now) + + def process_cycle_end( + self, + cycle_data: dict[str, Any], + detected_profile: str | None = None, + confidence: float = 0.0, + predicted_duration: float | None = None, + match_result: Any | None = None, + ) -> None: + """Analyze completed cycle for learning. + + Args: + cycle_data: Completed cycle data + detected_profile: Profile name detected + confidence: Match confidence score (0.0-1.0) + predicted_duration: Expected duration in seconds + match_result: MatchResult from profile_store.async_match_profile() (optional) + """ + # 1. Trigger background simulation to find optimal parameters for this cycle + if cycle_data.get("power_data"): + # Offload to executor since simulation can be heavy + self.hass.async_create_task(self._async_run_simulation(cycle_data)) + + # 2. Check if we should request feedback + self._maybe_request_feedback( + cycle_data, detected_profile, confidence, predicted_duration, match_result + ) + + # 3. Update model-based suggestions (durations etc) + self._update_model_suggestions(dt_util.now()) + + # 4. Run multi-cycle batch simulation when enough new labeled cycles have accumulated + self._maybe_run_batch_simulation() + + def _maybe_run_batch_simulation(self) -> None: + """Schedule a batch simulation when enough new labeled cycles have arrived.""" + _BATCH_MIN = 5 + _BATCH_RERUN_DELTA = 5 # Re-run every 5 new labeled cycles + + labeled_cycles = [ + c for c in self.profile_store.get_past_cycles() + if isinstance(c, dict) + and c.get("profile_name") + and c.get("profile_name") != "noise" + and c.get("power_data") + and c.get("status") in ("completed", "force_stopped") + ] + current_count = len(labeled_cycles) + + if current_count < _BATCH_MIN: + return + if (current_count - self._last_batch_simulation_count) < _BATCH_RERUN_DELTA: + return + + self._last_batch_simulation_count = current_count + self.hass.async_create_task(self._async_run_batch_simulation(labeled_cycles, current_count)) + + async def _async_run_batch_simulation(self, cycles: list[dict[str, Any]], expected_count: int) -> None: + """Run multi-cycle batch simulation asynchronously.""" + try: + new_suggestions = await self.hass.async_add_executor_job( + self.suggestion_engine.run_batch_simulation, cycles + ) + if new_suggestions: + self._apply_suggestions_and_notify(new_suggestions) + self._logger.debug( + "Batch simulation (%d cycles) produced suggestions: %s", + len(cycles), + list(new_suggestions.keys()), + ) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Batch simulation failed: %s", e) + + async def _async_run_simulation(self, cycle_data: dict[str, Any]) -> None: + """Run simulation asynchronously.""" + try: + # Simulation runner derives optimal thresholds + # Offload to executor since simulation can be heavy (CPU bound) + new_suggestions = await self.hass.async_add_executor_job( + self.suggestion_engine.run_simulation, cycle_data + ) + if new_suggestions: + self._apply_suggestions_and_notify(new_suggestions) + self._logger.debug("Post-cycle simulation completed with suggestions: %s", new_suggestions.keys()) + except Exception as e: + self._logger.error("Background simulation failed: %s", e) + + def _update_operational_suggestions(self, now: datetime) -> None: + """Generate suggestions for operational parameters (intervals, timeouts).""" + if self._sample_interval_model.count < 20: + return + + p95 = self._sample_interval_model.p95 + median = self._sample_interval_model.median + + if p95 is None or median is None: + return + + suggestions = self.suggestion_engine.generate_operational_suggestions(p95, median) + self._apply_suggestions_and_notify(suggestions) + self._last_suggestion_update = now + + def _update_model_suggestions(self, now: datetime) -> None: + """Generate suggestions for model parameters (tolerances, ratios).""" + suggestions = self.suggestion_engine.generate_model_suggestions() + self._apply_suggestions_and_notify(suggestions) + + async def _async_send_suggestions_ready_notification( + self, device_title: str, suggestions_count: int + ) -> None: + """Send a one-time persistent notification when suggestions become available.""" + try: + notification_id = f"ha_washdata_suggestions_ready_{self.entry_id}" + + translations = await translation.async_get_translations( + self.hass, self.hass.config.language, "options", {DOMAIN} + ) + + default_title = "WashData: Suggested Settings Ready ({device})" + default_msg = ( + "The **Suggested Settings** sensor now reports **{count}** actionable recommendations.\n\n" + "To review and apply them: **Settings > Devices & Services > WashData > Configure > " + "Advanced Settings > Apply Suggested Values**.\n\n" + "Suggestions are optional and shown for review before you save." + ) + + title_template = translations.get( + f"component.{DOMAIN}.options.error.suggestions_ready_notification_title", + default_title, + ) + msg_template = translations.get( + f"component.{DOMAIN}.options.error.suggestions_ready_notification_message", + default_msg, + ) + + title = title_template.format(device=device_title) + message = msg_template.format(count=suggestions_count) + + await self.hass.services.async_call( + "persistent_notification", + "create", + { + "message": message, + "title": title, + "notification_id": notification_id, + }, + ) + except Exception: # pylint: disable=broad-exception-caught + self._logger.exception("Failed to create suggestions-ready notification") + + def _set_suggestion(self, key: str, value: Any, reason: str) -> None: + """Persist a suggested setting.""" + current: Any = self.profile_store.get_suggestions().get(key, {}) + if isinstance(current, dict): + current_dict = cast(dict[str, Any], current) + if current_dict.get("value") == value: + return # No change + + self.profile_store.set_suggestion(key, value, reason=reason) + # We fire a background save task if possible, or rely on next periodic save. + # Since learning manager doesn't hold reference to hass task creation easily, + # we can just rely on ProfileStore's periodic save or trigger one if referenced. + # Ideally ProfileStore handles dirtiness. + # But wait, Manager calls save periodically. We should just mark it dirty? + # ProfileStore.async_save() is needed. + # We'll just trigger it via hass if available. + if self.hass: + self.hass.async_create_task(self.profile_store.async_save()) + + def _maybe_request_feedback( + self, + cycle_data: dict[str, Any], + detected_profile: str | None, + confidence: float, + predicted_duration: float | None, + match_result: Any | None = None, + ) -> None: + """Check if feedback should be requested for this completed cycle.""" + if ( + not predicted_duration + or not detected_profile + or detected_profile in ("off", "detecting...") + ): + # No match was made, don't request feedback + return + + # Get the cycle ID from the cycle_data + cycle_id = cycle_data.get("id") + if not cycle_id: + self._logger.warning("Cycle data missing ID, cannot request feedback") + return + + # Get Configured Thresholds + entry = self.hass.config_entries.async_get_entry(self.entry_id) + if not entry: + return + + auto_label_conf = entry.options.get( + CONF_AUTO_LABEL_CONFIDENCE, DEFAULT_AUTO_LABEL_CONFIDENCE + ) + learning_conf = entry.options.get( + CONF_LEARNING_CONFIDENCE, DEFAULT_LEARNING_CONFIDENCE + ) + duration_tol = entry.options.get( + CONF_DURATION_TOLERANCE, DEFAULT_DURATION_TOLERANCE + ) + + # Auto-label if very high confidence + if confidence >= auto_label_conf: + labeled = self.auto_label_high_confidence( + cycle_id=cycle_id, + profile_name=detected_profile, + confidence=confidence, + confidence_threshold=auto_label_conf, + ) + if labeled: + # Rebuild envelope first, then persist (issue #131) + self.hass.async_create_task( + self._async_rebuild_and_save_profile(detected_profile) + ) + self._logger.debug("Auto-labeled high-confidence cycle %s", cycle_id) + return + + # Skip low-confidence matches below learning threshold + if confidence < learning_conf: + self._logger.debug( + "Skipping feedback for low-confidence match (conf=%.2f < %.2f)", + confidence, + learning_conf, + ) + return + + actual_duration = cycle_data.get("duration", 0) + + # Request feedback via learning manager for moderate confidence + self.request_cycle_verification( + cycle_id=cycle_id, + detected_profile=detected_profile, + confidence=confidence, + estimated_duration=predicted_duration, + actual_duration=actual_duration, + duration_tolerance=duration_tol, + match_result=match_result, + ) + + # Persist pending feedback request so it survives restart + self.hass.async_create_task(self.profile_store.async_save()) + + # Create user-visible notification (skipped when suppressed via option). + # Use `is True` so that un-configured mock objects in tests don't + # accidentally suppress notifications by being truthy. + suppress = entry.options.get( + CONF_SUPPRESS_FEEDBACK_NOTIFICATIONS, + DEFAULT_SUPPRESS_FEEDBACK_NOTIFICATIONS, + ) is True + if not suppress: + self.hass.async_create_task( + self._async_send_feedback_notification( + entry.title, cycle_data, detected_profile, confidence + ) + ) + + async def _async_send_feedback_notification( + self, device_title: str, cycle_data: dict[str, Any], profile: str, confidence: float + ) -> None: + """Send a persistent notification for feedback (Async with translation).""" + try: + cycle_id = cycle_data.get("id", "unknown") + start_ts = cycle_data.get("start_time") + end_ts = dt_util.now() # Approximate, or pass actual end time + + # Format times + t_str = "" + if start_ts: + try: + s_dt = datetime.fromisoformat(str(start_ts)) if isinstance(start_ts, str) else start_ts + s_local = dt_util.as_local(s_dt) + e_local = dt_util.as_local(end_ts) + t_str = f"{s_local.strftime('%H:%M')} - {e_local.strftime('%H:%M')}" + except Exception: + t_str = "Just now" + + notification_id = f"ha_washdata_feedback_{self.entry_id}_{cycle_id}" + + # Load translations (from en.json / localization files) + # We use "options" category to access the error keys where we stored these strings + translations = await translation.async_get_translations( + self.hass, self.hass.config.language, "options", {DOMAIN} + ) + + # Default templates + default_title = "WashData: Verify Cycle ({device})" + default_msg = ( + "**Device**: {device}\n" + "**Program**: {program} ({confidence}% confidence)\n" + "**Time**: {time}\n\n" + "WashData needs your help to verify this detected cycle.\n\n" + "Please go to **Settings > Devices & Services > WashData > Configure > Learning Feedbacks** to confirm or correct this result." + ) + + title_template = translations.get( + f"component.{DOMAIN}.options.error.feedback_notification_title", default_title + ) + msg_template = translations.get( + f"component.{DOMAIN}.options.error.feedback_notification_message", default_msg + ) + + # Confidence as percentage + conf_pct = int(confidence * 100) + + title = title_template.format(device=device_title) + message = msg_template.format( + device=device_title, + program=profile, + confidence=conf_pct, + time=t_str + ) + + # Use standard service call + await self.hass.services.async_call( + "persistent_notification", + "create", + { + "message": message, + "title": title, + "notification_id": notification_id, + }, + ) + except Exception: # pylint: disable=broad-exception-caught + self._logger.exception("Failed to create feedback notification") + + def _send_feedback_notification( + self, device_title: str, cycle_data: dict[str, Any], profile: str, confidence: float + ) -> None: + """Deprecated sync wrapper.""" + self.hass.async_create_task( + self._async_send_feedback_notification( + device_title, cycle_data, profile, confidence + ) + ) + + def request_cycle_verification( + self, + cycle_id: str, + detected_profile: Optional[str], + confidence: float, + estimated_duration: Optional[float], + actual_duration: float, + duration_tolerance: float = 0.10, + match_result: Any | None = None, + ) -> None: + """Request user verification for a detected cycle.""" + duration_match_pct = ( + (actual_duration / estimated_duration * 100) if estimated_duration else 0 + ) + tolerance_pct = duration_tolerance * 100 + is_close_match = ( + estimated_duration and abs(duration_match_pct - 100) <= tolerance_pct + ) + + # Extract match ranking from MatchResult if available (for UI visualization) + ranking_summary: list[dict[str, Any]] = [] + if match_result and hasattr(match_result, "ranking") and match_result.ranking: + for cand in match_result.ranking[:5]: # Store top 5 + try: + ranking_summary.append({ + "name": cand.get("name", "Unknown"), + "score": float(cand.get("score", 0.0)), + "metrics": cand.get("metrics", {}), + "profile_duration": float(cand.get("profile_duration", 0.0)), + }) + except (TypeError, ValueError, KeyError, AttributeError): + continue + + feedback_req: dict[str, Any] = { + "cycle_id": cycle_id, + "detected_profile": detected_profile, + "confidence": confidence, + "estimated_duration": estimated_duration, + "actual_duration": actual_duration, + "duration_match_pct": duration_match_pct, + "is_close_match": is_close_match, + "created_at": dt_util.now().isoformat(), + "user_response": None, + "expires_at": None, + "ranking": ranking_summary, # Top candidates for UI display + } + + self.profile_store.add_pending_feedback(cycle_id, feedback_req) + + est_min = int(estimated_duration / 60) if estimated_duration else 0 + self._logger.info( + "Feedback requested for cycle %s: profile='%s' (conf=%.2f), " + "est=%smin, actual=%smin (%.0f%%)", + cycle_id, + detected_profile, + confidence, + est_min, + int(actual_duration / 60), + duration_match_pct, + ) + + def auto_label_high_confidence( + self, + cycle_id: str, + profile_name: str, + confidence: float, + confidence_threshold: float, + ) -> bool: + """Auto-label a cycle with high confidence.""" + if confidence < confidence_threshold: + return False + + # Reuse existing internal logic + self._auto_label_cycle(cycle_id, profile_name) + + # Verify it was labeled (cycle found) + cycles = self.profile_store.get_past_cycles() + cycle = next((c for c in cycles if c["id"] == cycle_id), None) + + return bool(cycle and cycle.get("auto_labeled")) + + async def async_submit_cycle_feedback( + self, + cycle_id: str, + user_confirmed: bool, + corrected_profile: Optional[str] = None, + corrected_duration: Optional[float] = None, + notes: str = "", + dismiss: bool = False, + ) -> bool: + """Submit user feedback for a cycle.""" + pending = self.profile_store.get_pending_feedback().get(cycle_id) + if not pending: + return False + + # Parse corrected_duration before writing to history so a bad value + # never leaves a partially-applied state. + duration_sec: float | None = None + if corrected_duration is not None: + try: + duration_sec = float(corrected_duration) + except (TypeError, ValueError): + self._logger.warning( + "Invalid corrected_duration %r for cycle %s, ignoring", + corrected_duration, + cycle_id, + ) + + feedback_record: dict[str, Any] = { + "cycle_id": cycle_id, + "original_detected_profile": pending["detected_profile"], + "original_confidence": pending["confidence"], + "user_confirmed": user_confirmed, + "corrected_profile": corrected_profile, + "corrected_duration": duration_sec, + "notes": notes, + "submitted_at": dt_util.now().isoformat(), + } + + self.profile_store.get_feedback_history()[cycle_id] = feedback_record + + # Track which profiles need envelope rebuild (issue #131) + profiles_to_rebuild: set[str] = set() + + if dismiss: + # Just dismiss, no action + pass + elif user_confirmed: + profile_name = pending.get("detected_profile") + if isinstance(profile_name, str) and profile_name: + self._auto_label_cycle(cycle_id, profile_name, duration_sec) + if duration_sec is not None: + cycles = self.profile_store.get_past_cycles() + confirmed_cycle = next((c for c in cycles if c["id"] == cycle_id), None) + if confirmed_cycle: + confirmed_cycle["duration"] = duration_sec + profiles_to_rebuild.add(profile_name) + else: + # Correction path: only use corrected_profile when user_confirmed is False. + # Duration-only corrections (no profile specified) are handled by the elif branch below. + target_profile = corrected_profile + detected_profile_name = pending.get("detected_profile") + + if isinstance(target_profile, str) and target_profile: + self._apply_correction_learning( + cycle_id, target_profile, duration_sec + ) + profiles_to_rebuild.add(target_profile) + if ( + isinstance(detected_profile_name, str) + and detected_profile_name + and detected_profile_name != target_profile + ): + profiles_to_rebuild.add(detected_profile_name) + elif duration_sec is not None: + # No valid profile could be determined, but a duration correction was + # explicitly provided - apply it directly to the cycle so the value + # is never silently dropped. + cycles = self.profile_store.get_past_cycles() + cycle_to_fix = next((c for c in cycles if c["id"] == cycle_id), None) + if cycle_to_fix: + cycle_to_fix["duration"] = duration_sec + cycle_to_fix["manual_duration"] = duration_sec + existing_profile = cycle_to_fix.get("profile_name") + if isinstance(existing_profile, str) and existing_profile: + profiles_to_rebuild.add(existing_profile) + else: + self._logger.warning( + "Duration correction skipped: cycle %s not found in past_cycles", + cycle_id, + ) + + # Remove from pending (add_pending_feedback was wrapper, remove is direct) + if cycle_id in self.profile_store.get_pending_feedback(): + del self.profile_store.get_pending_feedback()[cycle_id] + + # Rebuild envelopes for all modified profiles to recalculate min/max/avg (issue #131) + for profile_name in profiles_to_rebuild: + try: + await self.profile_store.async_rebuild_envelope(profile_name) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Failed to rebuild envelope for profile '%s': %s", profile_name, e) + + # Persist changes + await self.profile_store.async_save() + + # Trigger UI and sensor refresh (Issue #155) + async_dispatcher_send(self.hass, f"ha_washdata_update_{self.entry_id}") + + return True + + def _auto_label_cycle(self, cycle_id: str, profile_name: str, manual_duration: float | None = None) -> None: + cycles = self.profile_store.get_past_cycles() + cycle = next((c for c in cycles if c["id"] == cycle_id), None) + if cycle: + cycle["profile_name"] = profile_name + cycle["auto_labeled"] = True + if manual_duration: + cycle["manual_duration"] = manual_duration + + def _apply_correction_learning( + self, + cycle_id: str, + corrected_profile: str, + corrected_duration: Optional[float] = None, + ) -> None: + """Apply user correction to a cycle (fix for issue #131). + + Note: We do not update avg_duration here with EMA. Instead, the envelope + rebuild in async_submit_cycle_feedback() will recalculate all statistics + (min/max/avg) from labeled cycles, ensuring accuracy. + """ + self._auto_label_cycle(cycle_id, corrected_profile, corrected_duration) + if corrected_duration is not None: + cycles = self.profile_store.get_past_cycles() + cycle = next((c for c in cycles if c["id"] == cycle_id), None) + if cycle: + cycle["duration"] = corrected_duration + # Profile stats will be recalculated when envelope is rebuilt + + async def _async_rebuild_profile_envelope(self, profile_name: str) -> None: + """Async helper to rebuild a profile's envelope (issue #131 fix). + + This wraps async_rebuild_envelope with error handling for safe task scheduling. + """ + try: + await self.profile_store.async_rebuild_envelope(profile_name) + self._logger.debug("Rebuilt envelope for profile '%s'", profile_name) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Failed to rebuild envelope for profile '%s': %s", profile_name, e) + + async def _async_rebuild_and_save_profile(self, detected_profile: str) -> None: + """Rebuild profile envelope then persist in deterministic order.""" + await self._async_rebuild_profile_envelope(detected_profile) + await self.profile_store.async_save() + + def get_pending_feedback(self) -> dict[str, dict[str, Any]]: + """Return pending feedback requests.""" + return dict(self.profile_store.get_pending_feedback()) + + def get_feedback_history(self, limit: int = 20) -> list[dict[str, Any]]: + """Return submitted feedback history.""" + items = list(self.profile_store.get_feedback_history().values()) + items.sort(key=lambda x: x.get("submitted_at", ""), reverse=True) + return items[:limit] diff --git a/custom_components/ha_washdata/log_utils.py b/custom_components/ha_washdata/log_utils.py new file mode 100644 index 0000000..1bde425 --- /dev/null +++ b/custom_components/ha_washdata/log_utils.py @@ -0,0 +1,28 @@ +"""Logging utilities for WashData.""" +from __future__ import annotations + +import logging + + +class DeviceLoggerAdapter(logging.LoggerAdapter): + """Logger adapter that prepends the device name to every log message. + + Usage:: + + _LOGGER = logging.getLogger(__name__) + + class MyClass: + def __init__(self, device_name: str) -> None: + self._logger = DeviceLoggerAdapter(_LOGGER, device_name) + + def do_thing(self) -> None: + self._logger.info("Something happened") + # emits: "[My Device] Something happened" + """ + + def __init__(self, logger: logging.Logger, device_name: str) -> None: + super().__init__(logger, {"device_name": device_name}) + + def process(self, msg: str, kwargs: dict) -> tuple[str, dict]: + device = self.extra.get("device_name") or "unknown" # type: ignore[union-attr] + return f"[{device}] {msg}", kwargs diff --git a/custom_components/ha_washdata/manager.py b/custom_components/ha_washdata/manager.py new file mode 100644 index 0000000..dc99c09 --- /dev/null +++ b/custom_components/ha_washdata/manager.py @@ -0,0 +1,4553 @@ +"""Manager for WashData.""" + +# pylint: disable=broad-exception-caught + +from __future__ import annotations + +import logging +import hashlib +import inspect +import math +from asyncio import Task +from datetime import datetime, timedelta +from typing import Any, cast +import numpy as np + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import Context, Event, HomeAssistant, State, callback +from homeassistant.helpers.event import ( + async_track_state_change_event, + async_track_time_interval, +) +from homeassistant.helpers.dispatcher import async_dispatcher_send +from homeassistant.exceptions import HomeAssistantError +from homeassistant.const import STATE_UNAVAILABLE, STATE_HOME +from homeassistant.util import dt as dt_util +import homeassistant.helpers.event as evt +from homeassistant.helpers import script as script_helper +from homeassistant.helpers import translation + +from .const import ( + DOMAIN, + CONF_POWER_SENSOR, + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_NOTIFY_SERVICE, + CONF_NOTIFY_ACTIONS, + CONF_NOTIFY_START_SERVICES, + CONF_NOTIFY_FINISH_SERVICES, + CONF_NOTIFY_LIVE_SERVICES, + CONF_NOTIFY_PEOPLE, + CONF_NOTIFY_ONLY_WHEN_HOME, + CONF_NOTIFY_FIRE_EVENTS, + CONF_NOTIFY_EVENTS, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_LOW_POWER_NO_UPDATE_TIMEOUT, # Import new constant + CONF_SMOOTHING_WINDOW, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_INTERRUPTED_MIN_SECONDS, + CONF_ABRUPT_DROP_WATTS, + CONF_ABRUPT_DROP_RATIO, + CONF_ABRUPT_HIGH_LOAD_FACTOR, + CONF_PROGRESS_RESET_DELAY, + CONF_LEARNING_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_AUTO_LABEL_CONFIDENCE, + CONF_AUTO_MAINTENANCE, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_MAX_PAST_CYCLES, + CONF_MAX_FULL_TRACES_PER_PROFILE, + CONF_MAX_FULL_TRACES_UNLABELED, + CONF_WATCHDOG_INTERVAL, + CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + CONF_COMPLETION_MIN_SECONDS, + CONF_NOTIFY_BEFORE_END_MINUTES, + CONF_PROFILE_MATCH_THRESHOLD, + CONF_PROFILE_UNMATCH_THRESHOLD, + CONF_DEVICE_TYPE, + CONF_START_DURATION_THRESHOLD, + CONF_RUNNING_DEAD_ZONE, + CONF_END_REPEAT_COUNT, + CONF_MIN_OFF_GAP, + CONF_START_ENERGY_THRESHOLD, + CONF_END_ENERGY_THRESHOLD, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + CONF_SAMPLING_INTERVAL, + CONF_SAVE_DEBUG_TRACES, + CONF_DTW_BANDWIDTH, + CONF_EXTERNAL_END_TRIGGER_ENABLED, + CONF_EXTERNAL_END_TRIGGER, + CONF_EXTERNAL_END_TRIGGER_INVERTED, + CONF_ANTI_WRINKLE_ENABLED, + CONF_ANTI_WRINKLE_MAX_POWER, + CONF_ANTI_WRINKLE_MAX_DURATION, + CONF_ANTI_WRINKLE_EXIT_POWER, + CONF_DELAY_START_DETECT_ENABLED, + CONF_DELAY_CONFIRM_SECONDS, + CONF_DELAY_TIMEOUT_HOURS, + CONF_PUMP_STUCK_DURATION, + DEFAULT_PUMP_STUCK_DURATION, + EVENT_PUMP_STUCK, + DEVICE_TYPE_PUMP, + SIGNAL_WASHER_UPDATE, + NOTIFY_EVENT_START, + NOTIFY_EVENT_FINISH, + NOTIFY_EVENT_LIVE, + NOTIFY_EVENT_CLEAN, + EVENT_CYCLE_STARTED, + EVENT_CYCLE_ENDED, + DEFAULT_MIN_POWER, + DEFAULT_OFF_DELAY, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE, + DEFAULT_SMOOTHING_WINDOW, + DEFAULT_PROFILE_DURATION_TOLERANCE, + DEFAULT_INTERRUPTED_MIN_SECONDS, + DEFAULT_ABRUPT_DROP_WATTS, + DEFAULT_ABRUPT_DROP_RATIO, + DEFAULT_ABRUPT_HIGH_LOAD_FACTOR, + DEFAULT_COMPLETION_MIN_SECONDS, + DEFAULT_NOTIFY_BEFORE_END_MINUTES, + DEFAULT_PROFILE_MATCH_THRESHOLD, + DEFAULT_PROFILE_UNMATCH_THRESHOLD, + DEFAULT_SAMPLING_INTERVAL, + DEFAULT_PROGRESS_RESET_DELAY, + DEFAULT_LEARNING_CONFIDENCE, + DEFAULT_DURATION_TOLERANCE, + DEFAULT_AUTO_LABEL_CONFIDENCE, + DEFAULT_AUTO_MAINTENANCE, + DEFAULT_PROFILE_MATCH_INTERVAL, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE, + DEFAULT_ANTI_WRINKLE_ENABLED, + DEFAULT_ANTI_WRINKLE_MAX_POWER, + DEFAULT_ANTI_WRINKLE_MAX_DURATION, + DEFAULT_ANTI_WRINKLE_EXIT_POWER, + DEFAULT_DELAY_START_DETECT_ENABLED, + DEFAULT_DELAY_CONFIRM_SECONDS, + DEFAULT_DELAY_TIMEOUT_HOURS, + DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO, + DEFAULT_MAX_PAST_CYCLES, + DEFAULT_MAX_FULL_TRACES_PER_PROFILE, + CONF_NOTIFY_TITLE, + CONF_NOTIFY_ICON, + CONF_NOTIFY_START_MESSAGE, + CONF_NOTIFY_FINISH_MESSAGE, + CONF_NOTIFY_PRE_COMPLETE_MESSAGE, + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + CONF_NOTIFY_LIVE_CHRONOMETER, + CONF_NOTIFY_REMINDER_MESSAGE, + CONF_NOTIFY_TIMEOUT_SECONDS, + CONF_NOTIFY_CHANNEL, + CONF_NOTIFY_FINISH_CHANNEL, + CONF_ENERGY_PRICE_STATIC, + CONF_ENERGY_PRICE_ENTITY, + CONF_DOOR_SENSOR_ENTITY, + CONF_PAUSE_CUTS_POWER, + CONF_SWITCH_ENTITY, + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, + CONF_NOTIFY_UNLOAD_MESSAGE, + DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES, + DEFAULT_NOTIFY_UNLOAD_MESSAGE, + STATE_CLEAN, + DEFAULT_NOTIFY_TITLE, + DEFAULT_NOTIFY_START_MESSAGE, + DEFAULT_NOTIFY_FINISH_MESSAGE, + DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE, + DEFAULT_NOTIFY_LIVE_WAITING_MESSAGE, + DEFAULT_NOTIFY_ONLY_WHEN_HOME, + DEFAULT_NOTIFY_FIRE_EVENTS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_LIVE_CHRONOMETER, + DEFAULT_NOTIFY_REMINDER_MESSAGE, + DEFAULT_NOTIFY_TIMEOUT_SECONDS, + DEFAULT_NOTIFY_CHANNEL, + DEFAULT_NOTIFY_FINISH_CHANNEL, + + DEFAULT_MAX_FULL_TRACES_UNLABELED, + DEFAULT_DTW_BANDWIDTH, + DEFAULT_WATCHDOG_INTERVAL, + CONF_MATCH_PERSISTENCE, + DEFAULT_MATCH_PERSISTENCE, + DEFAULT_MATCH_REVERT_RATIO, + DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + DEFAULT_DEVICE_TYPE, + DEFAULT_START_DURATION_THRESHOLD, + DEFAULT_RUNNING_DEAD_ZONE, + DEFAULT_END_REPEAT_COUNT, + DEFAULT_MIN_OFF_GAP, + DEFAULT_MIN_OFF_GAP_BY_DEVICE, + DEFAULT_MAX_DEFERRAL_SECONDS, + DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE, + DEFAULT_END_ENERGY_THRESHOLD, + DEVICE_SMOOTHING_THRESHOLDS, + DEVICE_COMPLETION_THRESHOLDS, + STATE_RUNNING, + STATE_OFF, + STATE_STARTING, + STATE_PAUSED, + STATE_USER_PAUSED, + STATE_ENDING, + STATE_ANTI_WRINKLE, + STATE_DELAY_WAIT, + STATE_IDLE, + STATE_UNKNOWN, +) +from .cycle_detector import CycleDetector, CycleDetectorConfig +from .learning import LearningManager +from .profile_store import ProfileStore, decompress_power_data +from .recorder import CycleRecorder +from .diag_buffer import DiagBuffer +from .log_utils import DeviceLoggerAdapter +from .time_utils import power_data_to_offsets + +_LOGGER = logging.getLogger(__name__) + + +def _pn_create( + hass: HomeAssistant, + message: str, + *, + title: str | None = None, + notification_id: str | None = None, +) -> None: + """Best-effort persistent notification creation. + + Tests stub out the entire `homeassistant` module, so we can't import + `homeassistant.components.persistent_notification` here. + """ + try: + components = getattr(cast(Any, hass), "components", None) + pn = getattr(cast(Any, components), "persistent_notification", None) + if pn is None: + return + result = pn.async_create(message, title=title, notification_id=notification_id) + if inspect.iscoroutine(result): + hass.async_create_task(result) + except Exception: + return + + +class WashDataManager: + """Manages a single washing machine instance.""" + + def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry) -> None: + """Initialize the manager.""" + self.hass = hass + self.config_entry = config_entry + self.entry_id = config_entry.entry_id + self._logger = DeviceLoggerAdapter(_LOGGER, config_entry.title) + self.diag_buffer = DiagBuffer(config_entry.title) + + # Prioritize options -> data for power sensor (allows changing it) + self.power_sensor_entity_id = config_entry.options.get( + CONF_POWER_SENSOR, config_entry.data.get(CONF_POWER_SENSOR) + ) + self.device_type = config_entry.options.get( + CONF_DEVICE_TYPE, + config_entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE), + ) + + # Initialize attributes to satisfy pylint + self._off_delay = float(DEFAULT_OFF_DELAY) + self._no_update_active_timeout = float(DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT) + self._low_power_no_update_timeout = 3600.0 # Default 1h + self._notify_before_end_minutes = float(DEFAULT_NOTIFY_BEFORE_END_MINUTES) + self._notify_start_services: list[str] = [] + self._notify_finish_services: list[str] = [] + self._notify_live_services: list[str] = [] + self._notify_actions: list[dict[str, Any]] = [] + self._notify_people: list[str] = [] + self._notify_only_when_home = DEFAULT_NOTIFY_ONLY_WHEN_HOME + self._notify_fire_events = DEFAULT_NOTIFY_FIRE_EVENTS + self._notify_live_interval_seconds = DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS + self._notify_live_overrun_percent = DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT + self._notify_live_chronometer = DEFAULT_NOTIFY_LIVE_CHRONOMETER + self._notify_timeout_seconds = DEFAULT_NOTIFY_TIMEOUT_SECONDS + self._pending_notifications: list[dict[str, Any]] = [] + self._remove_notify_people_listener = None + self._live_notification_sent_count = 0 + + # Pause tracking (user-triggered) + self._user_pause_start: datetime | None = None + self._total_user_paused_seconds: float = 0.0 + self._is_user_paused: bool = False + self._pause_cuts_power: bool = bool( + config_entry.options.get(CONF_PAUSE_CUTS_POWER, False) + ) + + # Door sensor + clean state + self._door_sensor_entity: str | None = config_entry.options.get( + CONF_DOOR_SENSOR_ENTITY + ) or None + self._remove_door_sensor_listener = None + self._is_clean_state: bool = False + self._clean_state_start: datetime | None = None + self._notified_clean_laundry: bool = False + self._notify_unload_delay_minutes: int = int( + config_entry.options.get( + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES + ) + ) + self._live_notification_cap = 0 + self._last_live_notification_time: datetime | None = None + self._live_waiting_notification_sent = False + self._live_chronometer_overrun_sent = False + # Single per-device identity shared by start/live/reminder/finished so each + # replaces the previous on the mobile app (and collapses to one entry on the + # persistent-notification fallback). The clean-laundry nag uses its own tag + # since it fires up to an hour after finish and should not clobber the thread. + self._lifecycle_tag = f"ha_washdata_{self.entry_id}_lifecycle" + self._lifecycle_pn_id = self._lifecycle_tag + self._clean_tag = f"ha_washdata_{self.entry_id}_clean" + # Backwards-compatible alias for existing live-notification call sites/tests. + self._live_notification_tag = self._lifecycle_tag + self._start_event_fired = False + self._cycle_start_time: datetime | None = None + + # State + self._current_power = 0.0 + self._last_reading_time: datetime | None = None + self._last_real_reading_time: datetime | None = None # Track last real sensor update + self._noise_events: list[datetime] = [] + self._noise_max_powers: list[float] = [] + self._last_match_result = None + self._last_phase_estimate_time = None + self._sample_intervals: list[float] = [] + self._sample_interval_stats: dict[str, Any] = {} + self._matching_task: Task[Any] | None = None + self._last_state_save = 0.0 + self._last_cycle_end_time: datetime | None = None + self._remove_state_expiry_timer = None + + # Components + match_threshold = config_entry.options.get( + CONF_PROFILE_MATCH_THRESHOLD, DEFAULT_PROFILE_MATCH_THRESHOLD + ) + unmatch_threshold = config_entry.options.get( + CONF_PROFILE_UNMATCH_THRESHOLD, DEFAULT_PROFILE_UNMATCH_THRESHOLD + ) + self._unmatch_threshold = unmatch_threshold + + self.profile_store = ProfileStore( + hass, + self.entry_id, + min_duration_ratio=config_entry.options.get( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO, + ), + max_duration_ratio=config_entry.options.get( + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO, + ), + save_debug_traces=config_entry.options.get(CONF_SAVE_DEBUG_TRACES, False), + match_threshold=match_threshold, + unmatch_threshold=unmatch_threshold, + device_name=config_entry.title, + ) + self.profile_store.dtw_bandwidth = float( + config_entry.options.get(CONF_DTW_BANDWIDTH, DEFAULT_DTW_BANDWIDTH) + ) + self.learning_manager = LearningManager( + hass, self.entry_id, self.profile_store, self.device_type, + device_name=config_entry.title, + ) + self.recorder = CycleRecorder(hass, self.entry_id, device_name=config_entry.title) + + # Priority: Options > Data > Default + min_power = config_entry.options.get( + CONF_MIN_POWER, config_entry.data.get(CONF_MIN_POWER, DEFAULT_MIN_POWER) + ) + off_delay = config_entry.options.get( + CONF_OFF_DELAY, config_entry.data.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY) + ) + progress_reset_delay = config_entry.options.get( + CONF_PROGRESS_RESET_DELAY, DEFAULT_PROGRESS_RESET_DELAY + ) + self._no_update_active_timeout = float( + config_entry.options.get( + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT, + ) + ) + self._low_power_no_update_timeout = float( + config_entry.options.get(CONF_LOW_POWER_NO_UPDATE_TIMEOUT, 3600.0) + ) + self._off_delay = float(config_entry.options.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY)) + self._learning_confidence = config_entry.options.get( + CONF_LEARNING_CONFIDENCE, DEFAULT_LEARNING_CONFIDENCE + ) + self._duration_tolerance = config_entry.options.get( + CONF_DURATION_TOLERANCE, DEFAULT_DURATION_TOLERANCE + ) + self._auto_label_confidence = config_entry.options.get( + CONF_AUTO_LABEL_CONFIDENCE, DEFAULT_AUTO_LABEL_CONFIDENCE + ) + + self._profile_match_interval = int( + config_entry.options.get( + CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL + ) + ) + self._notify_before_end_minutes = int( + config_entry.options.get( + CONF_NOTIFY_BEFORE_END_MINUTES, DEFAULT_NOTIFY_BEFORE_END_MINUTES + ) + ) + self._load_notify_services(config_entry) + self._notify_actions = list( + cast(list[dict[str, Any]], config_entry.options.get(CONF_NOTIFY_ACTIONS, []) or []) + ) + self._notify_people = list( + config_entry.options.get(CONF_NOTIFY_PEOPLE, []) or [] + ) + self._notify_only_when_home = bool( + config_entry.options.get( + CONF_NOTIFY_ONLY_WHEN_HOME, DEFAULT_NOTIFY_ONLY_WHEN_HOME + ) + ) + self._notify_fire_events = bool( + config_entry.options.get(CONF_NOTIFY_FIRE_EVENTS, DEFAULT_NOTIFY_FIRE_EVENTS) + ) + self._notify_live_interval_seconds = int( + config_entry.options.get( + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + ) + ) + self._notify_live_overrun_percent = int( + config_entry.options.get( + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + ) + ) + self._notify_live_chronometer = bool( + config_entry.options.get( + CONF_NOTIFY_LIVE_CHRONOMETER, + DEFAULT_NOTIFY_LIVE_CHRONOMETER, + ) + ) + self._notify_timeout_seconds = int( + config_entry.options.get( + CONF_NOTIFY_TIMEOUT_SECONDS, DEFAULT_NOTIFY_TIMEOUT_SECONDS + ) + ) + + # Advanced options + smoothing_window = int(config_entry.options.get("smoothing_window", 5)) + interrupted_min_seconds = int( + config_entry.options.get("interrupted_min_seconds", 150) + ) + abrupt_drop_watts = float(config_entry.options.get("abrupt_drop_watts", 500.0)) + abrupt_drop_ratio = float(config_entry.options.get("abrupt_drop_ratio", 0.6)) + abrupt_high_load_factor = float( + config_entry.options.get("abrupt_high_load_factor", 5.0) + ) + + # Get device specific default for completion threshold + device_default_completion = DEVICE_COMPLETION_THRESHOLDS.get( + self.device_type, DEFAULT_COMPLETION_MIN_SECONDS + ) + completion_min_seconds = int( + config_entry.options.get( + CONF_COMPLETION_MIN_SECONDS, device_default_completion + ) + ) + + start_duration_threshold = float( + config_entry.options.get( + CONF_START_DURATION_THRESHOLD, DEFAULT_START_DURATION_THRESHOLD + ) + ) + running_dead_zone = int( + config_entry.options.get(CONF_RUNNING_DEAD_ZONE, DEFAULT_RUNNING_DEAD_ZONE) + ) + end_repeat_count = int( + config_entry.options.get(CONF_END_REPEAT_COUNT, DEFAULT_END_REPEAT_COUNT) + ) + + self._logger.info( + "Manager init: min_power=%sW, off_delay=%ss, type=%s", + min_power, + off_delay, + self.device_type, + ) + + config = CycleDetectorConfig( + min_power=float(min_power), + off_delay=int(off_delay), + smoothing_window=smoothing_window, + interrupted_min_seconds=interrupted_min_seconds, + abrupt_drop_watts=abrupt_drop_watts, + abrupt_drop_ratio=abrupt_drop_ratio, + abrupt_high_load_factor=abrupt_high_load_factor, + completion_min_seconds=completion_min_seconds, + start_duration_threshold=start_duration_threshold, + running_dead_zone=running_dead_zone, + end_repeat_count=end_repeat_count, + min_off_gap=int( + config_entry.options.get( + CONF_MIN_OFF_GAP, + DEFAULT_MIN_OFF_GAP_BY_DEVICE.get( + self.device_type, DEFAULT_MIN_OFF_GAP + ), + ) + ), + start_energy_threshold=float( + config_entry.options.get( + CONF_START_ENERGY_THRESHOLD, + DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE.get(self.device_type, 0.2) + ) + ), + end_energy_threshold=float( + config_entry.options.get(CONF_END_ENERGY_THRESHOLD, DEFAULT_END_ENERGY_THRESHOLD) + ), + start_threshold_w=float( + config_entry.options.get( + CONF_START_THRESHOLD_W, + float(min_power) + max(1.0, 0.1 * float(min_power)), + ) + ), + stop_threshold_w=float( + config_entry.options.get( + CONF_STOP_THRESHOLD_W, + float(min_power) * 0.6 if float(min_power) > 0 else 2.0, + ) + ), + min_duration_ratio=float( + config_entry.options.get( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO_BY_DEVICE.get( + self.device_type, DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO + ), + ) + ), + match_interval=int( + config_entry.options.get( + CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL + ) + ), + anti_wrinkle_enabled=bool( + config_entry.options.get( + CONF_ANTI_WRINKLE_ENABLED, DEFAULT_ANTI_WRINKLE_ENABLED + ) + ), + anti_wrinkle_max_power=float( + config_entry.options.get( + CONF_ANTI_WRINKLE_MAX_POWER, DEFAULT_ANTI_WRINKLE_MAX_POWER + ) + ), + anti_wrinkle_max_duration=float( + config_entry.options.get( + CONF_ANTI_WRINKLE_MAX_DURATION, DEFAULT_ANTI_WRINKLE_MAX_DURATION + ) + ), + anti_wrinkle_exit_power=float( + config_entry.options.get( + CONF_ANTI_WRINKLE_EXIT_POWER, DEFAULT_ANTI_WRINKLE_EXIT_POWER + ) + ), + delay_detect_enabled=bool( + config_entry.options.get( + CONF_DELAY_START_DETECT_ENABLED, DEFAULT_DELAY_START_DETECT_ENABLED + ) + ), + delay_confirm_seconds=float( + config_entry.options.get( + CONF_DELAY_CONFIRM_SECONDS, DEFAULT_DELAY_CONFIRM_SECONDS + ) + ), + delay_timeout_seconds=float( + config_entry.options.get( + CONF_DELAY_TIMEOUT_HOURS, DEFAULT_DELAY_TIMEOUT_HOURS + ) + ) * 3600.0, + ) + self._config = config + + + def profile_matcher_wrapper( + readings: list[tuple[datetime, float]], + ) -> tuple[str | None, float, float, str | None]: + """Wraps profile store matching logic with detector callback signature. + + Returns: None (async offload) + """ + # Manual program override + if self._manual_program_active and self._current_program: + elapsed_seconds = 0.0 + if len(readings) > 1: + elapsed_seconds = max( + 0.0, + (readings[-1][0] - readings[0][0]).total_seconds(), + ) + + expected_duration = float(self._matched_profile_duration or 0.0) + manual_phase = self.profile_store.check_phase_match( + self._current_program, + elapsed_seconds, + ) + return ( + self._current_program, + 1.0, + expected_duration, + manual_phase or "Manual", + ) + + if not readings: + return (None, 0.0, 0.0, None) + + # Snapshotted for thread safety indirectly by task logic + # We don't need a wrapper task if we unify with _update_estimates matching + # but for now let's keep the detector callback as a trigger + self.hass.async_create_task(self._async_perform_combined_matching(readings)) + return (None, 0.0, 0.0, None) + + self.detector = CycleDetector( + config, + self._on_state_change, + self._on_cycle_end, + profile_matcher=profile_matcher_wrapper, + device_name=config_entry.title, + ) + + self._remove_listener = None + self._remove_external_trigger_listener = None # External cycle end trigger + self._remove_watchdog = None + self._watchdog_interval = int( + config_entry.options.get(CONF_WATCHDOG_INTERVAL, DEFAULT_WATCHDOG_INTERVAL) + ) + self._match_persistence = int( + config_entry.options.get(CONF_MATCH_PERSISTENCE, DEFAULT_MATCH_PERSISTENCE) + ) + self._sampling_interval = float( + config_entry.options.get(CONF_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL) + ) + self._noise_events_threshold = int( + config_entry.options.get( + CONF_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + DEFAULT_AUTO_TUNE_NOISE_EVENTS_THRESHOLD, + ) + ) + self._current_program: str = "off" + self._time_remaining: float | None = None + self._total_duration: float | None = None + self._last_total_duration_update: datetime | None = None + self._cycle_progress: float = 0.0 + self._smoothed_progress: float = 0.0 # Smoothed progress tracking for EMA + self._cycle_completed_time: datetime | None = None # Track when cycle finished + self._progress_reset_delay: int = int( + progress_reset_delay + ) # Reset progress after idle + self._last_reading_time: datetime | None = None + self._current_power: float = 0.0 + self._last_estimate_time: datetime | None = None + self._last_match_ambiguous: bool = False + self._matched_profile_duration: float | None = None + self._last_match_confidence: float = 0.0 + # Sample interval tracking (seconds) for adaptive timing + # Profile matching duration tolerance (0.25 = ±25%) + self._profile_duration_tolerance: float = float( + config_entry.options.get("profile_duration_tolerance", 0.25) + ) + + + self._remove_maintenance_scheduler = None + self._profile_sample_repair_stats: dict[str, int] | None = None + + self._last_suggestion_update: datetime | None = None + + # Pump Monitor state + self._pump_stuck_duration: int = int( + config_entry.options.get(CONF_PUMP_STUCK_DURATION, DEFAULT_PUMP_STUCK_DURATION) + ) + self._pump_stuck: bool = False # True once the stuck threshold has fired for this cycle + + self._manual_program_active: bool = False + self._notified_start: bool = False + self._notified_pre_completion: bool = False + self._last_match_result: Any = None # Stores full MatchResult object + self._score_history: dict[str, list[float]] = {} # Tracks recent scores for trend analysis + self._match_persistence_counter: dict[str, int] = {} # Tracks consecutive matches + self._unmatch_persistence_counter: int = 0 # Tracks consecutive low-confidence matches + self._current_match_candidate: str | None = None # Pending profile name + + async def _async_perform_combined_matching( + self, readings: list[tuple[datetime, float]] + ) -> None: + """PRIMARY matching task: Updates both Manager and Detector using best method.""" + self._logger.debug( + "Matching trigger: readings=%d, task_exists=%s", + len(readings) if readings else 0, + getattr(self, "_matching_task", None) is not None + ) + # Prevent concurrent matching tasks + current_task = self._matching_task + if current_task is not None and not current_task.done(): + self._logger.debug("Matching skipped: previous task still running") + return + + try: + if not readings: + self._logger.debug("Matching skipped: no readings") + return + + self._matching_task = self.hass.async_create_task(self._async_do_perform_matching(readings)) + except Exception as e: + self._logger.error("Perform combined matching trigger failed: %s", e) + + async def _async_do_perform_matching(self, readings: list[tuple[datetime, float]]) -> None: + """Inner task to handle actual matching logic.""" + try: + end_time = readings[-1][0] + start_time = readings[0][0] + current_duration = (end_time - start_time).total_seconds() + + # 1. RUN BETTER ASYNC MATCHING + result = await self.profile_store.async_match_profile( + readings, + current_duration + ) + + # 2. UPDATE MANAGER STATE (Estimates, Program Name, etc.) + self._last_match_result = result + self._last_match_ambiguous = result.is_ambiguous + + profile_name = result.best_profile + confidence = result.confidence + matched_duration = result.expected_duration + phase_name = result.matched_phase + + # --- Switching Logic (Temporal Persistence) --- + should_switch = False + switch_reason = "" + + # Identify current program score from results + current_program_score = 0.0 + for c in result.candidates: + if c.get("name") == self._current_program: + current_program_score = c.get("score", 0.0) + break + + # CASE: Divergence Detection (Score Drop) + # If current matched program has a significant drop from its own peak score, + # we should consider unmatching it even if it's still the "best" candidate. + if ( + self._current_program not in ("detecting...", "off", "starting", "unknown") + and profile_name == self._current_program + ): + history: list[float] = self._score_history.get(self._current_program, []) + if len(history) > 3: + peak_score = max(history) + # If score drops by more than 40% from peak AND is below threshold, unmatch. + # This catches divergence faster than waiting for fixed unmatch_threshold. + if confidence < peak_score * (1.0 - DEFAULT_MATCH_REVERT_RATIO): + self._unmatch_persistence_counter += 1 + if self._unmatch_persistence_counter >= self._match_persistence: + self._current_program = "detecting..." + self._matched_profile_duration = None + self._unmatch_persistence_counter = 0 + self._logger.info( + "Divergence detected for profile '%s' (confidence %.3f < 60%% of peak %.3f). " + "Reverting to detection.", + profile_name, confidence, peak_score + ) + # Reset profile_name so Case 3 doesn't re-trigger + profile_name = "detecting..." + + # Update persistence for the best profile + if profile_name and profile_name != "detecting...": + self._match_persistence_counter[profile_name] = self._match_persistence_counter.get(profile_name, 0) + 1 + + # Check if this is the same candidate as before + if profile_name != self._current_match_candidate: + # Reset counter for old candidate if it wasn't locked in + self._current_match_candidate = profile_name + self._match_persistence_counter[profile_name] = 1 + else: + self._current_match_candidate = None + + is_persistent = profile_name and self._match_persistence_counter.get(profile_name, 0) >= self._match_persistence + + # Case 1: Initial Match from "detecting..." + if ( + profile_name + and confidence >= 0.15 + and (not result.is_ambiguous or is_persistent) + and (not self._matched_profile_duration or self._current_program == "detecting...") + ): + if is_persistent: + should_switch = True + switch_reason = f"initial_match (persistent {self._match_persistence_counter[profile_name]}x)" + else: + self._logger.debug( + "Match persistence: %s at %d/%d matches. Stay at detecting...", + profile_name, self._match_persistence_counter.get(profile_name, 0), self._match_persistence + ) + + # Case 2: Mid-cycle override (different profile) + elif ( + profile_name + and self._current_program != profile_name + and self._current_program not in ("detecting...", "off", "starting", "unknown") + ): + # High Confidence Override: Bypass persistence if match is VERY strong + if confidence > 0.8 and (confidence - current_program_score) > 0.15: + should_switch = True + switch_reason = f"high_confidence_override ({confidence:.3f} vs {current_program_score:.3f})" + + # Normal Switch: Requires persistence AND either better score + trend + elif is_persistent: + if confidence > current_program_score and self._analyze_trend(profile_name): + # Add a minimum score gap for mid-cycle switching (0.05) to prevent flapping + if (confidence - current_program_score) > 0.05: + should_switch = True + switch_reason = f"positive_trend_persistent ({confidence:.3f} > {current_program_score:.3f})" + + # Case 3: Unmatching (confidence drop) + elif ( + self._current_program not in ("detecting...", "off", "starting", "unknown") + and profile_name == self._current_program + and confidence < self._unmatch_threshold + ): + self._unmatch_persistence_counter += 1 + is_unmatch_persistent = self._unmatch_persistence_counter >= self._match_persistence + + if is_unmatch_persistent: + self._current_program = "detecting..." + self._matched_profile_duration = None + self._unmatch_persistence_counter = 0 + self._logger.info( + "Unmatched profile '%s' (confidence %.3f < threshold %.3f persistent %dx). " + "Reverting to detection.", + profile_name, + confidence, + self._unmatch_threshold, + self._match_persistence + ) + else: + self._logger.debug( + "Unmatch persistence: %s at %d/%d low-confidence matches. Stay at %s...", + profile_name, self._unmatch_persistence_counter, self._match_persistence, profile_name + ) + + # Reset unmatch counter if confidence is healthy + # AND we didn't just detect a divergence + elif ( + profile_name == self._current_program + and confidence >= self._unmatch_threshold + and not (len(self._score_history.get(self._current_program, [])) > 3 and confidence < max(self._score_history[self._current_program]) * (1.0 - DEFAULT_MATCH_REVERT_RATIO)) + ): + self._unmatch_persistence_counter = 0 + + if should_switch: + if profile_name is None: + self._current_program = "detecting..." + else: + self._current_program = profile_name + self._last_match_confidence = confidence + self._unmatch_persistence_counter = 0 # Reset on switch + if profile_name in self._match_persistence_counter: + self._match_persistence_counter[profile_name] = self._match_persistence # Lock it in + + avg_duration = float(matched_duration) + self._matched_profile_duration = avg_duration if avg_duration > 0 else None + self._logger.info( + "Switching to profile '%s' (reason: %s). Expected duration: %.0fs (%smin)", + profile_name, switch_reason, avg_duration, int(avg_duration / 60), + ) + elif profile_name == self._current_program: + # Same program, but update confidence for sensors + self._last_match_confidence = confidence + elif not self._matched_profile_duration: + self._current_program = "detecting..." + + self._last_estimate_time = dt_util.now() + + # Update score history for all candidates to track trends + for cand in result.candidates: + cname = cand.get("name") + if cname: + history = self._score_history.setdefault(cname, []) + history.append(float(cand.get("score", 0.0))) + if len(history) > 20: + history.pop(0) + + # Note: _update_remaining_only() and notify move to end of flow + + # 3. UPDATE DETECTOR (Envelopes, Deferral, State Transitions) + current_matched = self.detector.matched_profile + verified_pause = getattr(self.detector, "_verified_pause", False) + current_power = readings[-1][1] if readings else 0.0 + + # --- Envelope Verification for Mismatches & Pauses --- + # ALWAYS check alignment if we have a match and power is low, + # to confirm if this is a legitimate pause or a mismatch. + stop_thresh = float(self.detector.config.stop_threshold_w) + if current_matched and current_power < stop_thresh: + formatted = power_data_to_offsets(cast(list[list[Any] | tuple[Any, ...]], readings)) + try: + profile_store_any = cast(Any, self.profile_store) + verify_alignment = profile_store_any.async_verify_alignment + is_confirmed, mapped_time, _ = ( + await verify_alignment(current_matched, formatted) + ) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error( + "Alignment verification crashed for profile %s: %s", + current_matched, e, exc_info=True + ) + is_confirmed = False + mapped_time = 0.0 + + if is_confirmed: + if not verified_pause: + self._logger.info( + "Envelope verified expected low power phase for %s. Enabling verified pause.", + current_matched + ) + verified_pause = True + # Smart Termination within Envelope block + try: + profile = self.profile_store.get_profile(current_matched) + if profile: + avg_dur = profile.get("avg_duration", 0) + if avg_dur > 0 and (mapped_time / avg_dur) > 0.95: + verified_pause = False + self._logger.info("Smart Termination: Near end of profile. Releasing pause lock.") + except Exception as e: + self._logger.debug("Smart Termination alignment verification failed: %s", e) + else: + if verified_pause: + self._logger.info( + "Envelope indicates UNEXPECTED low power for %s. Disabling verified pause.", + current_matched + ) + verified_pause = False + + # --- High Power Clear --- + stop_threshold = getattr(self.detector.config, "stop_threshold_w", 5.0) + + if current_power > stop_threshold * 10: + verified_pause = False + + # --- Consistency Override --- + # If envelope verified or mismatched, ensure manager program matches + if profile_name != self._current_program and (verified_pause or result.is_confident_mismatch): + if profile_name: + self._current_program = profile_name + self._last_match_confidence = confidence + # Try to fetch duration if we switched back to matched + try: + prof = self.profile_store.get_profile(profile_name) + if prof: + self._matched_profile_duration = float(prof.get("avg_duration", 0)) + except Exception as e: + self._logger.debug("Failed to fetch profile duration on switch: %s", e) + else: + self._current_program = "detecting..." + self._matched_profile_duration = None + + # --- HEURISTICS (Descriptive Phases) --- + if not phase_name: + if self.device_type == "dishwasher" and self.detector.is_waiting_low_power(): + phase_name = "Drying" + elif self.device_type == "washing_machine" and current_power > 200: + phase_name = "Spinning" + elif self.device_type == "washing_machine" and self.detector.is_waiting_low_power(): + phase_name = "Rinsing/Soaking" + elif self.device_type == "ev": + if current_power > 100: + phase_name = "Charging" + elif self.detector.is_waiting_low_power(): + phase_name = "Maintenance" + + # Push updates to detector + self.detector.set_verified_pause(verified_pause) + self.detector.update_match( + (profile_name, confidence, matched_duration, phase_name, result.is_confident_mismatch) + ) + + # --- LOGGING (Unified) --- + self._logger.info( + "Profile match attempt: name=%s, confidence=%.3f, duration=%.0fs, samples=%d", + profile_name, confidence, current_duration, len(readings), + ) + + self._update_remaining_only() + + # --- START NOTIFICATION LOGIC --- + # Fallback for restart-recovery: fires only if the immediate notification in + # _on_state_change was missed (e.g., HA restarted mid-cycle before snapshot). + if not getattr(self, "_notified_start", False): + if self._notify_fire_events and not self._start_event_fired: + self.hass.bus.async_fire( + EVENT_CYCLE_STARTED, + { + "entry_id": self.entry_id, + "device_name": self.config_entry.title, + "device_type": self.device_type, + "program": self._current_program, + "start_time": ( + self._cycle_start_time or dt_util.now() + ).isoformat(), + }, + ) + self._start_event_fired = True + + if self._notify_start_services or self._notify_actions: + msg_template = self.config_entry.options.get( + CONF_NOTIFY_START_MESSAGE, DEFAULT_NOTIFY_START_MESSAGE + ) + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_START_MESSAGE, + device=self.config_entry.title, + program=self._current_program, + ) + + self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_START, + extra_vars={ + "program": self._current_program, + "tag": self._lifecycle_tag, + }, + ) + self._notified_start = True + self._logger.info("Sent start notification for program '%s'", self._current_program) + + # Ensure pre-completion notifications never precede cycle-start signaling. + self._check_pre_completion_notification() + + self._check_live_progress_notification() + self._notify_update() + + except Exception as e: + self._logger.error("Perform combined matching failed: %s", e, exc_info=True) + + @property + def top_candidates(self) -> list[dict[str, Any]]: + """Return a lightweight list of top candidates from the last match.""" + if not self._last_match_result: + return [] + + # Get raw list from ranking (best) or candidates + raw_list: list[dict[str, Any]] = [] + if hasattr(self._last_match_result, "ranking") and self._last_match_result.ranking: + raw_list = self._last_match_result.ranking + elif hasattr(self._last_match_result, "candidates"): + raw_list = self._last_match_result.candidates + + # SANITIZE: Remove heavy power arrays before sending to Home Assistant attributes + sanitized: list[dict[str, Any]] = [] + for cand in raw_list[:5]: + sanitized.append({ + "name": cand.get("name"), + "score": round(float(cand.get("score", 0.0)), 3), + "profile_duration": cand.get("profile_duration"), + # Explicitly exclude "current" and "sample" keys which are big lists + }) + return sanitized + + @property + def phase_description(self) -> str: + """Return a description of the current phase.""" + if self._last_match_result and self._last_match_result.matched_phase: + return self._last_match_result.matched_phase + if self.detector.sub_state: + return self.detector.sub_state + return self.detector.state + + @property + def match_ambiguity(self) -> bool: + """Return True if the last match was ambiguous.""" + if self._last_match_result and hasattr(self._last_match_result, "is_ambiguous"): + return self._last_match_result.is_ambiguous + return False + + # Note: last_match_details property is defined later in the class + # It returns MatchResult from _last_match_result + async def _attempt_state_restoration(self) -> None: + """Attempt to restore active cycle state from storage.""" + active_snapshot = self.profile_store.get_active_cycle() + + # Check current power state first + state = self.hass.states.get(self.power_sensor_entity_id) + current_power = 0.0 + power_is_valid = False + + if state and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE): + try: + current_power = float(state.state) + power_is_valid = True + except (ValueError, TypeError): + # Power sensor state is not numeric during restoration; treat as 0W + self._logger.debug( + "Power sensor %s state %r is not numeric during restoration; " + "treating as 0W and not restoring by power", + self.power_sensor_entity_id, + getattr(state, "state", None), + ) + + should_restore = False + active_snapshot_to_restore: dict[str, Any] | None = ( + active_snapshot if isinstance(active_snapshot, dict) else None + ) + + # Helper to check if a snapshot is viable + def is_viable_restore(last_save_time: datetime) -> bool: + now = dt_util.now() + # Handle timezone mismatch gracefully + if last_save_time.tzinfo is None: + # Assume naive means local system time, convert to aware + last_save_time = last_save_time.replace(tzinfo=now.tzinfo) + + age = (now - last_save_time).total_seconds() + + # Unconditional restore window (30 mins) + if age < 1800: + return True + # Extended window if power is confirmed HIGH (60 mins) + if ( + age < 3600 + and power_is_valid + and current_power >= self._config.min_power + ): + return True + return False + + last_save = self.profile_store.get_last_active_save() + if last_save and last_save.tzinfo is None: + # Normalize naive legacy timestamps to system time + last_save = last_save.replace(tzinfo=dt_util.now().tzinfo) + + if active_snapshot_to_restore is not None and last_save and is_viable_restore(last_save): + should_restore = True + age = (dt_util.now() - last_save).total_seconds() + age = (dt_util.now() - last_save).total_seconds() + self._logger.info( + "Found recently saved active cycle (last_save=%s, age=%.0fs), restoring...", + last_save, + age + ) + # strict extension logic unless the user wants to enforce it. + active_snapshot_to_restore["sub_state"] = ( + active_snapshot_to_restore.get("sub_state") or "Restored" + ) + # NOTE: We disable dynamic min duration enforcement on recovery since we + # might have missed data + active_snapshot_to_restore["dynamic_min_duration"] = None + + # FALLBACK: Resurrection Logic + if not should_restore: + past_cycles = self.profile_store.get_past_cycles() + if past_cycles: + last_cycle = past_cycles[-1] + last_end_str = last_cycle.get("end_time") + if last_end_str: + last_end = dt_util.parse_datetime(last_end_str) + if last_end: + gap = (dt_util.now() - last_end).total_seconds() + is_recent = gap < 1200 # 20 mins + status = last_cycle.get("status") + + if is_recent and status != "completed": + self._logger.info( + "Found recent interrupted cycle in history " + "(id=%s, gap=%.0fs). Resurrecting...", + last_cycle["id"], + gap, + ) + try: + power_data = decompress_power_data(last_cycle) + if power_data: + active_snapshot_to_restore = { + # Reconstruct basic running state + "state": "running", + "sub_state": "Resurrected", + "current_cycle_start": last_cycle["start_time"], + "last_active_time": last_cycle["end_time"], + "low_power_start": None, + "cycle_max_power": ( + max([p for _, p in power_data]) + if power_data + else 0 + ), + "power_readings": power_data, + "ma_buffer": ( + [p for _, p in power_data[-10:]] + if power_data + else [] + ), + "end_condition_count": 0, + "extension_count": 0, + "dynamic_min_duration": None, + "matched_profile": last_cycle.get( + "profile_name" + ), + } + should_restore = True + past_cycles.pop() + await self.profile_store.async_save() + except Exception as e: + self._logger.error("Failed to resurrect cycle: %s", e) + + if should_restore and active_snapshot_to_restore: + try: + self.detector.restore_state_snapshot(active_snapshot_to_restore) + + # Restore if in any active state (Running, Paused, Ending) + if self.detector.state in (STATE_RUNNING, STATE_PAUSED, STATE_ENDING): + # Restore manual program flag if present + self._manual_program_active = active_snapshot_to_restore.get( + "manual_program", False + ) + + # If we restored into a low-power state, ensure we don't + # immediately quit. For now we just log this; the cycle + # detector's off_delay will handle actual shutdown. + if power_is_valid and current_power < self._config.min_power: + self._logger.debug( + "Restored active cycle in low-power state " + "(power=%.2fW < min_power=%.2fW); waiting for " + "detector off_delay before marking as finished", + current_power, + self._config.min_power, + ) + + if self.detector.matched_profile: + self._current_program = self.detector.matched_profile + self._logger.info( + "Restored/Resurrected washer cycle with profile: %s", + self._current_program, + ) + else: + self._current_program = "detecting..." + + # Restore persisted start-notification/event flags from snapshot. + self._notified_start = bool( + active_snapshot_to_restore.get("notified_start", False) + ) + self._start_event_fired = bool( + active_snapshot_to_restore.get("start_event_fired", False) + ) + + # Restore user-pause state from snapshot. + self._is_user_paused = bool( + active_snapshot_to_restore.get("is_user_paused", False) + ) + _pause_start_raw = active_snapshot_to_restore.get("user_pause_start") + self._user_pause_start = ( + dt_util.parse_datetime(_pause_start_raw) + if isinstance(_pause_start_raw, str) and _pause_start_raw + else None + ) + self._total_user_paused_seconds = float( + active_snapshot_to_restore.get("total_user_paused_seconds", 0.0) + ) + + self._start_watchdog() + else: + await self.profile_store.async_clear_active_cycle() + except Exception as err: + self._logger.warning("Failed to restore active cycle: %s, clearing", err) + await self.profile_store.async_clear_active_cycle() + else: + if last_save: + age = (dt_util.now() - last_save).total_seconds() + self._logger.info("Active cycle too stale (age=%.0fs), clearing", age) + await self.profile_store.async_clear_active_cycle() + + async def async_setup(self) -> None: + """Set up the manager.""" + await self.profile_store.async_load() + # Apply configurable duration tolerance to profile store + try: + self.profile_store.set_duration_tolerance(self._profile_duration_tolerance) + self.profile_store.set_retention_limits( + max_past_cycles=int( + self.config_entry.options.get( + CONF_MAX_PAST_CYCLES, DEFAULT_MAX_PAST_CYCLES + ) + ), + max_full_traces_per_profile=int( + self.config_entry.options.get( + CONF_MAX_FULL_TRACES_PER_PROFILE, + DEFAULT_MAX_FULL_TRACES_PER_PROFILE, + ) + ), + max_full_traces_unlabeled=int( + self.config_entry.options.get( + CONF_MAX_FULL_TRACES_UNLABELED, + DEFAULT_MAX_FULL_TRACES_UNLABELED, + ) + ), + ) + except Exception: + pass + + # Repair broken sample_cycle_id references (can happen after aggressive retention) + try: + stats = await self.profile_store.async_repair_profile_samples() + self._profile_sample_repair_stats = stats + if stats.get("profiles_repaired", 0) or stats.get( + "cycles_labeled_as_sample", 0 + ): + self._logger.warning( + "Repaired profile sample references for %s: %s", + self.entry_id, + stats, + ) + await self.profile_store.async_save() + except Exception: + self._logger.exception( + "Failed repairing profile sample references for %s", self.entry_id + ) + + # Subscribe to power sensor updates + self._remove_listener = async_track_state_change_event( + self.hass, [self.power_sensor_entity_id], self._async_power_changed + ) + + # Attempt to restore state (BEFORE starting listener) + await self._attempt_state_restoration() + + # Restore last cycle end time to ensure ghost cycle suppression works after restart + try: + cycles = self.profile_store.get_past_cycles() + if cycles: + # Find last completed cycle with a valid end time + for cycle in reversed(cycles): + if cycle.get("end_time") and cycle.get("status") == "completed": + ts = dt_util.parse_datetime(cycle["end_time"]) + if ts: + self._last_cycle_end_time = ts + self._logger.debug("Restored last cycle end time: %s", ts) + break + except Exception: # pylint: disable=broad-exception-caught + self._logger.debug("Failed to restore last cycle end time") + + # Load recorder state + await self.recorder.async_load() + + # Force initial update from current state (in case it's already stable) + state = self.hass.states.get(self.power_sensor_entity_id) + if state and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE): + try: + power = float(state.state) + now = dt_util.now() + self.detector.process_reading(power, now) + except (ValueError, TypeError): + pass + + # Trigger migration/compression of old cycle format + # This is safe to run repeatedly (it skips already compressed cycles) + await self.profile_store.async_migrate_cycles_to_compressed() + + # Backfill match_confidence for labeled cycles that predate the field + self.hass.async_create_task( + self.profile_store.async_backfill_match_confidence() + ) + + # Subscribe to external cycle end trigger (if enabled) + await self._setup_external_end_trigger() + + # Subscribe to door sensor (if configured) + await self._setup_door_sensor_listener() + + # Subscribe to person presence changes for notification gating + await self._setup_notify_people_listener() + + def _load_notify_services(self, config_entry: ConfigEntry) -> None: + """Load notification service lists, migrating legacy single-service config.""" + self._notify_start_services = list(config_entry.options.get(CONF_NOTIFY_START_SERVICES, []) or []) + self._notify_finish_services = list(config_entry.options.get(CONF_NOTIFY_FINISH_SERVICES, []) or []) + self._notify_live_services = list(config_entry.options.get(CONF_NOTIFY_LIVE_SERVICES, []) or []) + # Backward compat: migrate old single notify_service + notify_events to new per-event lists + if not (self._notify_start_services or self._notify_finish_services or self._notify_live_services): + _old_svc = config_entry.options.get(CONF_NOTIFY_SERVICE, "") + _old_events = list(config_entry.options.get(CONF_NOTIFY_EVENTS, []) or []) + if _old_svc: + if not _old_events or NOTIFY_EVENT_START in _old_events: + self._notify_start_services = [_old_svc] + if not _old_events or NOTIFY_EVENT_FINISH in _old_events: + self._notify_finish_services = [_old_svc] + if not _old_events or NOTIFY_EVENT_LIVE in _old_events: + self._notify_live_services = [_old_svc] + + async def async_reload_config(self, config_entry: ConfigEntry) -> None: + """ + Reload configuration options without interrupting running cycle detection. + + Updates detector config in-place. + Handles Power Sensor entity change by reconnecting listener. + """ + self._logger.info("Reloading configuration for %s", self.entry_id) + # Replace reference + self.config_entry = config_entry + + # Check if power sensor changed + new_sensor = config_entry.options.get( + CONF_POWER_SENSOR, config_entry.data.get(CONF_POWER_SENSOR) + ) + if new_sensor and new_sensor != self.power_sensor_entity_id: + # Block sensor changes when a cycle is active to prevent inconsistent state + d_state = self.detector.state + self._logger.debug( + "Reloading config: detector.state=%r (type=%s), RUNNING=%r", + d_state, + type(d_state), + STATE_RUNNING, + ) + if d_state == STATE_RUNNING: + self._logger.warning( + "Cannot change power sensor from %s to %s while a cycle " + "is active. Please wait for the current cycle to complete " + "before changing the power sensor.", + self.power_sensor_entity_id, + new_sensor, + ) + # Skip sensor change but continue with other config updates + return + + self._logger.info( + "Power sensor changed: %s -> %s", self.power_sensor_entity_id, new_sensor + ) + self.power_sensor_entity_id = new_sensor + # Remove old listener + if self._remove_listener: + self._remove_listener() + # Attach new listener + self._remove_listener = async_track_state_change_event( + self.hass, [self.power_sensor_entity_id], self._async_power_changed + ) + # Force update from new sensor + state = self.hass.states.get(self.power_sensor_entity_id) + if state and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE): + try: + power = float(state.state) + self.detector.process_reading(power, dt_util.now()) + except ValueError: + self._logger.debug( + "Initial power value for %s after config reload is not numeric: %r", + self.power_sensor_entity_id, + state.state, + ) + + # Update device type + self.device_type = config_entry.options.get( + CONF_DEVICE_TYPE, + config_entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE), + ) + # Propagate to learning pipeline (captured at construction time) + self.learning_manager.device_type = self.device_type + self.learning_manager.suggestion_engine.device_type = self.device_type + + # Update detector config in-place + old_min_power = self.detector.config.min_power + old_off_delay = self.detector.config.off_delay + old_smoothing = self.detector.config.smoothing_window + old_interrupted_min = self.detector.config.interrupted_min_seconds + old_abrupt_drop_watts = self.detector.config.abrupt_drop_watts + old_abrupt_drop_ratio = self.detector.config.abrupt_drop_ratio + old_abrupt_high_load = self.detector.config.abrupt_high_load_factor + + # Get new values from config + new_min_power = float( + config_entry.options.get(CONF_MIN_POWER, DEFAULT_MIN_POWER) + ) + new_off_delay = int(config_entry.options.get(CONF_OFF_DELAY, DEFAULT_OFF_DELAY)) + new_smoothing = int( + config_entry.options.get(CONF_SMOOTHING_WINDOW, DEFAULT_SMOOTHING_WINDOW) + ) + new_interrupted_min = int( + config_entry.options.get( + CONF_INTERRUPTED_MIN_SECONDS, DEFAULT_INTERRUPTED_MIN_SECONDS + ) + ) + new_abrupt_drop_watts = float( + config_entry.options.get(CONF_ABRUPT_DROP_WATTS, DEFAULT_ABRUPT_DROP_WATTS) + ) + new_abrupt_drop_ratio = float( + config_entry.options.get(CONF_ABRUPT_DROP_RATIO, DEFAULT_ABRUPT_DROP_RATIO) + ) + self.detector.config.match_interval = int( + config_entry.options.get( + CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL + ) + ) + self.profile_store.dtw_bandwidth = float( + config_entry.options.get(CONF_DTW_BANDWIDTH, DEFAULT_DTW_BANDWIDTH) + ) + new_abrupt_high_load = float( + config_entry.options.get( + CONF_ABRUPT_HIGH_LOAD_FACTOR, DEFAULT_ABRUPT_HIGH_LOAD_FACTOR + ) + ) + + # Device default + dev_def = DEVICE_COMPLETION_THRESHOLDS.get( + self.device_type, DEFAULT_COMPLETION_MIN_SECONDS + ) + new_completion_min = int( + config_entry.options.get(CONF_COMPLETION_MIN_SECONDS, dev_def) + ) + + new_start_threshold = float( + config_entry.options.get( + CONF_START_DURATION_THRESHOLD, DEFAULT_START_DURATION_THRESHOLD + ) + ) + new_running_dead_zone = int( + config_entry.options.get(CONF_RUNNING_DEAD_ZONE, DEFAULT_RUNNING_DEAD_ZONE) + ) + new_end_repeat_count = int( + config_entry.options.get(CONF_END_REPEAT_COUNT, DEFAULT_END_REPEAT_COUNT) + ) + + # Power Hysteresis Thresholds + new_start_threshold_w = float( + config_entry.options.get( + CONF_START_THRESHOLD_W, + float(new_min_power) + max(1.0, 0.1 * float(new_min_power)), + ) + ) + new_stop_threshold_w = float( + config_entry.options.get( + CONF_STOP_THRESHOLD_W, + max(0.0, float(new_min_power) - max(0.5, 0.1 * float(new_min_power))), + ) + ) + + new_start_energy = float( + config_entry.options.get( + CONF_START_ENERGY_THRESHOLD, + DEFAULT_START_ENERGY_THRESHOLDS_BY_DEVICE.get(self.device_type, 0.2) + ) + ) + new_end_energy = float( + config_entry.options.get(CONF_END_ENERGY_THRESHOLD, DEFAULT_END_ENERGY_THRESHOLD) + ) + + new_anti_wrinkle_enabled = bool( + config_entry.options.get( + CONF_ANTI_WRINKLE_ENABLED, DEFAULT_ANTI_WRINKLE_ENABLED + ) + ) + new_anti_wrinkle_max_power = float( + config_entry.options.get( + CONF_ANTI_WRINKLE_MAX_POWER, DEFAULT_ANTI_WRINKLE_MAX_POWER + ) + ) + new_anti_wrinkle_max_duration = float( + config_entry.options.get( + CONF_ANTI_WRINKLE_MAX_DURATION, DEFAULT_ANTI_WRINKLE_MAX_DURATION + ) + ) + new_anti_wrinkle_exit_power = float( + config_entry.options.get( + CONF_ANTI_WRINKLE_EXIT_POWER, DEFAULT_ANTI_WRINKLE_EXIT_POWER + ) + ) + new_delay_detect_enabled = bool( + config_entry.options.get( + CONF_DELAY_START_DETECT_ENABLED, DEFAULT_DELAY_START_DETECT_ENABLED + ) + ) + new_delay_confirm_seconds = float( + config_entry.options.get( + CONF_DELAY_CONFIRM_SECONDS, DEFAULT_DELAY_CONFIRM_SECONDS + ) + ) + new_delay_timeout_seconds = float( + config_entry.options.get( + CONF_DELAY_TIMEOUT_HOURS, DEFAULT_DELAY_TIMEOUT_HOURS + ) + ) * 3600.0 + + # Apply all detector config updates + self.detector.config.min_power = new_min_power + self.detector.config.off_delay = new_off_delay + self.detector.config.smoothing_window = new_smoothing + self.detector.config.interrupted_min_seconds = new_interrupted_min + self.detector.config.abrupt_drop_watts = new_abrupt_drop_watts + self.detector.config.abrupt_drop_ratio = new_abrupt_drop_ratio + self.detector.config.abrupt_high_load_factor = new_abrupt_high_load + self.detector.config.completion_min_seconds = new_completion_min + self.detector.config.start_duration_threshold = new_start_threshold + self.detector.config.running_dead_zone = new_running_dead_zone + self.detector.config.end_repeat_count = new_end_repeat_count + self.detector.config.start_threshold_w = new_start_threshold_w + self.detector.config.stop_threshold_w = new_stop_threshold_w + self.detector.config.start_energy_threshold = new_start_energy + self.detector.config.end_energy_threshold = new_end_energy + self.detector.config.anti_wrinkle_enabled = new_anti_wrinkle_enabled + self.detector.config.anti_wrinkle_max_power = new_anti_wrinkle_max_power + self.detector.config.anti_wrinkle_max_duration = new_anti_wrinkle_max_duration + self.detector.config.anti_wrinkle_exit_power = new_anti_wrinkle_exit_power + self.detector.config.delay_detect_enabled = new_delay_detect_enabled + self.detector.config.delay_confirm_seconds = new_delay_confirm_seconds + self.detector.config.delay_timeout_seconds = new_delay_timeout_seconds + + # Pump Monitor setting + self._pump_stuck_duration = int( + config_entry.options.get(CONF_PUMP_STUCK_DURATION, DEFAULT_PUMP_STUCK_DURATION) + ) + + if ( + old_min_power != new_min_power + or old_off_delay != new_off_delay + or old_smoothing != new_smoothing + or old_interrupted_min != new_interrupted_min + or old_abrupt_drop_watts != new_abrupt_drop_watts + or old_abrupt_drop_ratio != new_abrupt_drop_ratio + or old_abrupt_high_load != new_abrupt_high_load + ): + self._logger.info( + "Updated detector config: min_power %.1fW→%.1fW, off_delay %ds→%ds, " + "smoothing %d→%d, interrupted_min %ds→%ds, abrupt_drop %.0fW→%.0fW, " + "abrupt_ratio %.2f→%.2f, high_load %.1f→%.1f", + old_min_power, + new_min_power, + old_off_delay, + new_off_delay, + old_smoothing, + new_smoothing, + old_interrupted_min, + new_interrupted_min, + old_abrupt_drop_watts, + new_abrupt_drop_watts, + old_abrupt_drop_ratio, + new_abrupt_drop_ratio, + old_abrupt_high_load, + new_abrupt_high_load, + ) + + # Update profile matching parameters + old_min_ratio, old_max_ratio = self.profile_store.get_duration_ratio_limits() + + new_min_ratio = float( + config_entry.options.get( + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MIN_DURATION_RATIO, + ) + ) + new_max_ratio = float( + config_entry.options.get( + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + DEFAULT_PROFILE_MATCH_MAX_DURATION_RATIO, + ) + ) + + if old_min_ratio != new_min_ratio or old_max_ratio != new_max_ratio: + self.profile_store.set_duration_ratio_limits( + min_ratio=new_min_ratio, max_ratio=new_max_ratio + ) + self._logger.info( + "Updated duration ratios: min %.2f→%.2f, max %.2f→%.2f", + old_min_ratio, + new_min_ratio, + old_max_ratio, + new_max_ratio, + ) + + # Update match interval + old_interval = self._profile_match_interval + new_interval = int( + config_entry.options.get( + CONF_PROFILE_MATCH_INTERVAL, DEFAULT_PROFILE_MATCH_INTERVAL + ) + ) + if old_interval != new_interval: + self._profile_match_interval = new_interval + self._logger.info("Updated match interval: %ds→%ds", old_interval, new_interval) + + # Update other configurable options + self._profile_duration_tolerance = float( + config_entry.options.get( + CONF_PROFILE_DURATION_TOLERANCE, DEFAULT_PROFILE_DURATION_TOLERANCE + ) + ) + + + # Update notification settings + self._load_notify_services(config_entry) + self._notify_actions = list( + cast(list[dict[str, Any]], config_entry.options.get(CONF_NOTIFY_ACTIONS, []) or []) + ) + self._notify_people = list( + config_entry.options.get(CONF_NOTIFY_PEOPLE, []) or [] + ) + self._notify_only_when_home = bool( + config_entry.options.get( + CONF_NOTIFY_ONLY_WHEN_HOME, DEFAULT_NOTIFY_ONLY_WHEN_HOME + ) + ) + self._notify_fire_events = bool( + config_entry.options.get(CONF_NOTIFY_FIRE_EVENTS, DEFAULT_NOTIFY_FIRE_EVENTS) + ) + self._notify_before_end_minutes = int( + config_entry.options.get( + CONF_NOTIFY_BEFORE_END_MINUTES, DEFAULT_NOTIFY_BEFORE_END_MINUTES + ) + ) + self._notify_live_interval_seconds = int( + config_entry.options.get( + CONF_NOTIFY_LIVE_INTERVAL_SECONDS, + DEFAULT_NOTIFY_LIVE_INTERVAL_SECONDS, + ) + ) + self._notify_live_overrun_percent = int( + config_entry.options.get( + CONF_NOTIFY_LIVE_OVERRUN_PERCENT, + DEFAULT_NOTIFY_LIVE_OVERRUN_PERCENT, + ) + ) + self._notify_live_chronometer = bool( + config_entry.options.get( + CONF_NOTIFY_LIVE_CHRONOMETER, + DEFAULT_NOTIFY_LIVE_CHRONOMETER, + ) + ) + self._notify_timeout_seconds = int( + config_entry.options.get( + CONF_NOTIFY_TIMEOUT_SECONDS, DEFAULT_NOTIFY_TIMEOUT_SECONDS + ) + ) + + # Reload door sensor / pause config + self._pause_cuts_power = bool(config_entry.options.get(CONF_PAUSE_CUTS_POWER, False)) + self._door_sensor_entity = config_entry.options.get(CONF_DOOR_SENSOR_ENTITY) or None + self._notify_unload_delay_minutes = int( + config_entry.options.get( + CONF_NOTIFY_UNLOAD_DELAY_MINUTES, DEFAULT_NOTIFY_UNLOAD_DELAY_MINUTES + ) + ) + + # Re-subscribe to external cycle end trigger + await self._setup_external_end_trigger() + + # Re-subscribe to door sensor + await self._setup_door_sensor_listener() + + # Re-subscribe to person presence changes for notification gating + await self._setup_notify_people_listener() + + # If a cycle is currently active and live notifications are now enabled, + # reset counters and fire the first live notification immediately so the + # user doesn't have to wait for the next power sensor poll. + if self.detector.state in (STATE_RUNNING, STATE_PAUSED, STATE_ENDING): + if self._notify_live_services or self._notify_actions: + self._reset_live_notification_state() + self._check_live_progress_notification() + + self._logger.info("Configuration reloaded successfully") + + # Trigger entity updates to reflect any changes + async_dispatcher_send(self.hass, f"ha_washdata_update_{self.entry_id}") + + if self.detector: + self.detector.config.profile_duration_tolerance = self._profile_duration_tolerance + + # Schedule midnight maintenance if enabled + await self._setup_maintenance_scheduler() + + # Update sampling interval + old_sampling = self._sampling_interval + new_sampling = float( + config_entry.options.get(CONF_SAMPLING_INTERVAL, DEFAULT_SAMPLING_INTERVAL) + ) + if old_sampling != new_sampling: + self._sampling_interval = new_sampling + self._logger.info( + "Updated sampling interval: %.1fs -> %.1fs", old_sampling, new_sampling + ) + + # RESTORE STATE (only if recent enough, otherwise treat as stale) + await self._attempt_state_restoration() + + self._logger.info("Configuration reloaded successfully") + + async def async_shutdown(self) -> None: + """Shutdown.""" + if self._remove_listener: + self._remove_listener() + if self._remove_external_trigger_listener: + self._remove_external_trigger_listener() + if self._remove_door_sensor_listener: + self._remove_door_sensor_listener() + self._remove_door_sensor_listener = None + if self._remove_notify_people_listener: + self._remove_notify_people_listener() + self._remove_notify_people_listener = None + self._pending_notifications = [] + if self._remove_watchdog: + self._remove_watchdog() + if ( + hasattr(self, "_remove_state_expiry_timer") + and self._remove_state_expiry_timer + ): + self._remove_state_expiry_timer() + if self._remove_maintenance_scheduler: + self._remove_maintenance_scheduler() + + self.diag_buffer.uninstall() + + # Dismiss any active live/progress notification so it doesn't linger on + # mobile devices across HA restarts or integration unloads with a stale + # (and eventually negative) chronometer. + try: + self._clear_live_progress_notification() + except Exception: # noqa: BLE001 + self._logger.debug("Failed to clear live notification on shutdown", exc_info=True) + + # Save active state before shutdown + if self.detector.state in {STATE_RUNNING, STATE_PAUSED, STATE_STARTING, STATE_ENDING}: + snapshot = self.detector.get_state_snapshot() + snapshot["manual_program"] = self._manual_program_active + snapshot["notified_start"] = self._notified_start + snapshot["start_event_fired"] = self._start_event_fired + snapshot["is_user_paused"] = self._is_user_paused + snapshot["user_pause_start"] = ( + self._user_pause_start.isoformat() if self._user_pause_start else None + ) + snapshot["total_user_paused_seconds"] = self._total_user_paused_seconds + await self.profile_store.async_save_active_cycle(snapshot) + + self._last_reading_time = None + + async def _setup_external_end_trigger(self) -> None: + """Set up listener for external cycle end trigger binary sensor.""" + # Remove existing listener if any + if self._remove_external_trigger_listener: + self._remove_external_trigger_listener() + self._remove_external_trigger_listener = None + + # Check if enabled + enabled = self.config_entry.options.get( + CONF_EXTERNAL_END_TRIGGER_ENABLED, False + ) + if not enabled: + self._logger.debug("External cycle end trigger is disabled") + return + + # Get entity ID + entity_id = self.config_entry.options.get(CONF_EXTERNAL_END_TRIGGER, "") + if not entity_id: + self._logger.debug("External cycle end trigger: no entity configured") + return + + self._logger.info( + "Setting up external cycle end trigger: %s", entity_id + ) + + # Subscribe to state changes + self._remove_external_trigger_listener = async_track_state_change_event( + self.hass, [entity_id], self._handle_external_trigger_change + ) + + async def _setup_door_sensor_listener(self) -> None: + """Set up listener for optional door sensor binary sensor.""" + if self._remove_door_sensor_listener: + self._remove_door_sensor_listener() + self._remove_door_sensor_listener = None + + entity_id = self._door_sensor_entity + if not entity_id: + self._logger.debug("Door sensor not configured") + return + + self._logger.info("Setting up door sensor listener: %s", entity_id) + self._remove_door_sensor_listener = async_track_state_change_event( + self.hass, [entity_id], self._handle_door_sensor_change + ) + + @callback + def _handle_door_sensor_change(self, event: Event[evt.EventStateChangedData]) -> None: + """Handle door sensor state changes. + + Opening the door during an active cycle confirms an intentional pause (verified_pause). + Opening the door after a cycle clears the 'Clean' state. + Note: door closing does NOT auto-resume a cycle - the user must do this explicitly. + """ + new_state = event.data.get("new_state") + old_state = event.data.get("old_state") + + if new_state is None: + return + + new_val = new_state.state + old_val = old_state.state if old_state else None + + # Ignore unavailability transitions + if new_val in ("unavailable", "unknown") or ( + old_val in ("unavailable", "unknown") + ): + return + + door_open = new_val == "on" # binary_sensor: on = open + + if door_open: + if self._is_clean_state: + # User opened the door after the cycle - laundry retrieved + self._logger.debug("Door opened: clearing Clean state") + self._is_clean_state = False + self._clean_state_start = None + self._notified_clean_laundry = False + # Dismiss a delivered clean reminder (and purge any queued ones) + # so it does not linger on the phone after the laundry is taken. + self._clear_clean_notification() + self._notify_update() + elif self.detector.state in (STATE_RUNNING, STATE_STARTING, STATE_PAUSED, STATE_ENDING): + # Door opened during active cycle → soft pause confirmation + self._logger.debug( + "Door opened during active cycle: setting verified_pause=True" + ) + self.detector.set_verified_pause(True) + if not self._is_user_paused: + self._is_user_paused = True + self._user_pause_start = dt_util.now() + self._notify_update() + # Door closing is intentionally not handled - no auto-resume + + async def _setup_notify_people_listener(self) -> None: + """Set up listener for person presence changes used by notification gating.""" + if self._remove_notify_people_listener: + self._remove_notify_people_listener() + self._remove_notify_people_listener = None + + if self._notify_only_when_home and self._notify_people: + self._remove_notify_people_listener = async_track_state_change_event( + self.hass, self._notify_people, self._handle_notify_person_change + ) + # If someone is already home when (re-)attaching, flush any queued + # notifications immediately so they aren't stranded. + if self._pending_notifications and self._is_any_notify_person_home(): + person_entity_id: str | None = None + person_name: str | None = None + for eid in self._notify_people: + state = self.hass.states.get(eid) + if state and state.state == STATE_HOME: + person_entity_id = eid + person_name = state.name or state.attributes.get( + "friendly_name", eid + ) + break + pending = list(self._pending_notifications) + self._pending_notifications = [] + for entry in pending: + self._dispatch_notification( + entry["message"], + title=entry.get("title"), + icon=entry.get("icon"), + event_type=entry.get("event_type"), + person_entity_id=person_entity_id, + person_name=person_name, + extra_vars=entry.get("extra_vars"), + allow_deferral=False, + ) + else: + self._pending_notifications = [] + + @callback + def _handle_external_trigger_change(self, event: Event[evt.EventStateChangedData]) -> None: + """Handle external trigger sensor state change.""" + new_state = event.data.get("new_state") + old_state = event.data.get("old_state") + + if new_state is None: + return + + inverted = self.config_entry.options.get( + CONF_EXTERNAL_END_TRIGGER_INVERTED, False + ) + + new_value = new_state.state + old_value = old_state.state if old_state else None + + # Ignore unavailability/unknown transitions (reconnects, disconnects) + if old_value is None or old_value in ("unavailable", "unknown") or new_value in ( + "unavailable", + "unknown", + ): + return + + # Determine if triggered based on inversion setting + triggered = False + if not inverted: + # Normal: Trigger on transition to "on" + if new_value == "on" and old_value != "on": + triggered = True + else: + # Inverted: Trigger on transition to "off" + if new_value == "off" and old_value != "off": + triggered = True + + if triggered: + self._logger.info( + "External cycle end trigger activated by %s (inverted=%s)", + event.data.get("entity_id"), + inverted + ) + # End cycle with "completed" status (not interrupted) + if self.detector.state in (STATE_ANTI_WRINKLE, STATE_DELAY_WAIT): + self.detector.reset(STATE_OFF) + self._logger.info("%s exited via external trigger", self.detector.state) + elif self.detector.state != STATE_OFF: + self.detector.user_stop() + self._logger.info("Cycle completed via external trigger") + + async def _setup_maintenance_scheduler(self) -> None: + """Set up daily maintenance task at midnight.""" + auto_maintenance = self.config_entry.options.get( + CONF_AUTO_MAINTENANCE, + self.config_entry.data.get(CONF_AUTO_MAINTENANCE, DEFAULT_AUTO_MAINTENANCE), + ) + + # Cancel existing scheduler if any + if self._remove_maintenance_scheduler: + self._remove_maintenance_scheduler() + self._remove_maintenance_scheduler = None + + if not auto_maintenance: + self._logger.debug("Auto-maintenance disabled") + return + + # Calculate next midnight + now = dt_util.now() + tomorrow = now + timedelta(days=1) + next_midnight = tomorrow.replace(hour=0, minute=0, second=0, microsecond=0) + + # Schedule first run at midnight + async def run_maintenance(_now: datetime | None = None) -> None: + """Run maintenance task.""" + self._logger.info("Running scheduled maintenance") + try: + stats = await self.profile_store.async_run_maintenance() + self._logger.info("Maintenance completed: %s", stats) + except Exception as err: + self._logger.error("Maintenance failed: %s", err, exc_info=True) + + # Use async_track_point_in_time for midnight, then reschedule daily + self._remove_maintenance_scheduler = evt.async_track_point_in_time( + self.hass, run_maintenance, next_midnight + ) + + self._logger.info("Scheduled maintenance at %s", next_midnight) + + # Also schedule daily repeat after first run + async def maintenance_wrapper(_now: datetime) -> None: + await run_maintenance(_now) + # Reschedule for next day + next_run = dt_util.now() + timedelta(days=1) + next_run = next_run.replace(hour=0, minute=0, second=0, microsecond=0) + self._remove_maintenance_scheduler = evt.async_track_point_in_time( + self.hass, maintenance_wrapper, next_run + ) + + self._remove_maintenance_scheduler = evt.async_track_point_in_time( + self.hass, maintenance_wrapper, next_midnight + ) + + @callback + def _async_power_changed(self, event: Any) -> None: + """Handle power sensor state change.""" + event_data = cast(dict[str, Any], getattr(event, "data", {})) + new_state = cast(State | None, event_data.get("new_state")) + if new_state is None or new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE): + return + + try: + power = float(new_state.state) + except ValueError: + return + + # Capture every raw sensor reading before any throttling or processing. + # Use the sensor's own last_updated timestamp so the trace reflects + # when the plug actually reported the value, not when we received it. + self.diag_buffer.record_power(power, new_state.last_updated) + + # RECORD MODE INTERCEPTION + if self.recorder.is_recording: + self.recorder.process_reading(power) + self._current_power = power + self._last_reading_time = dt_util.now() + self._notify_update() + return + + now = dt_util.now() + + # Throttle updates to avoid CPU overload on noisy sensors + # BUT always allow updates if power is below min_power (critical end-of-cycle signal). + min_p = float(self.detector.config.min_power) + is_low_power = power < min_p + + if ( + not is_low_power + and self._last_reading_time + and (now - self._last_reading_time).total_seconds() < self._sampling_interval + ): + return + + # Track observed power readings for learning + self.learning_manager.process_power_reading(power, now, self._last_reading_time) + self._last_reading_time = now + self._last_real_reading_time = now # Track real update + self._current_power = power + self.detector.process_reading(power, now) + + if self._cycle_start_time is None and self.detector.current_cycle_start is not None: + self._cycle_start_time = self.detector.current_cycle_start + + # If running (or paused/ending), try to match profile and update estimates + if self.detector.state in ( + STATE_RUNNING, + STATE_PAUSED, + STATE_ENDING, + STATE_STARTING, + ): + self._update_estimates() + # Periodically save state every 60s to avoid flash wear + # We need a tracker. + self._check_state_save(now) + + self._notify_update() + + def _check_state_save(self, now: datetime) -> None: + """Periodically save active state.""" + last_save = getattr(self, "_last_state_save", None) + if not last_save or (now - last_save).total_seconds() > 60: + # Fire and forget save task + # Inject manual program flag into snapshot before saving + snapshot = self.detector.get_state_snapshot() + snapshot["manual_program"] = self._manual_program_active + snapshot["notified_start"] = self._notified_start + snapshot["start_event_fired"] = self._start_event_fired + snapshot["is_user_paused"] = self._is_user_paused + snapshot["user_pause_start"] = ( + self._user_pause_start.isoformat() if self._user_pause_start else None + ) + snapshot["total_user_paused_seconds"] = self._total_user_paused_seconds + + self.hass.async_create_task( + self.profile_store.async_save_active_cycle(snapshot) + ) + self._last_state_save = now + + async def _run_final_match_from_cycle_data(self, cycle_data: dict[str, Any]) -> None: + """Run final profile match using the cycle's power data before it's saved. + + This is called from _on_cycle_end when _current_program is still 'detecting...' + to ensure we try matching with complete cycle data before persistence. + """ + # Cycle data from detector stores power_data as [[offset_seconds, power], ...], + # where offsets are relative to cycle start. + power_data = cycle_data.get("power_data", []) + duration = cycle_data.get("duration", 0) + + if not power_data or len(power_data) < 10: + self._logger.debug("Insufficient power data for final match (< 10 readings)") + return + + # power_data is already in [[offset_seconds, power], ...] format for matching. + self._logger.info( + "Running final match from cycle data: %s samples, %.0fs duration", + len(power_data), + duration, + ) + + result = await self.profile_store.async_match_profile(power_data, duration) + profile_name = result.best_profile + confidence = result.confidence + + # Store result for debug data + self._last_match_result = result + + # Accept match at lower threshold since cycle is complete + # Also ignore ambiguity for completed cycles - pick the best match + if profile_name and confidence >= 0.15: + self._logger.info( + "Final match from cycle data: '%s' with confidence %.3f", + profile_name, + confidence, + ) + self._current_program = profile_name + self._last_match_confidence = confidence + else: + self._logger.info( + "No confident match from cycle data (best: %s, conf=%.3f)", + profile_name, + confidence, + ) + + def _start_watchdog(self) -> None: + """Start the watchdog timer when a cycle begins.""" + if self._remove_watchdog: + return # Already running + + interval = self._watchdog_interval + self._logger.debug( + "Starting watchdog timer (configured=%ss)", + self._watchdog_interval, + ) + self._remove_watchdog = async_track_time_interval( + self.hass, self._watchdog_check_stuck_cycle, timedelta(seconds=interval) + ) + + def _stop_watchdog(self) -> None: + """Stop the watchdog timer when cycle ends.""" + if self._remove_watchdog: + self._logger.debug("Stopping watchdog timer") + self._remove_watchdog() + self._remove_watchdog = None + + def _start_state_expiry_timer(self) -> None: + """Start timer to reset state to OFF and progress to 0% after idle period.""" + if not hasattr(self, "_remove_state_expiry_timer"): + self._remove_state_expiry_timer = None + + if self._remove_state_expiry_timer: + return # Already running + + self._logger.debug( + "Starting state expiry timer (will reset after %ss)", + self._progress_reset_delay, + ) + self._remove_state_expiry_timer = async_track_time_interval( + self.hass, + self._handle_state_expiry, + timedelta(seconds=60), # Check every minute + ) + + def _stop_state_expiry_timer(self) -> None: + """Stop the state expiry timer.""" + if ( + hasattr(self, "_remove_state_expiry_timer") + and self._remove_state_expiry_timer + ): + self._logger.debug("Stopping state expiry timer") + self._remove_state_expiry_timer() + self._remove_state_expiry_timer = None + + async def _handle_state_expiry(self, now: datetime) -> None: + """Check if state and progress should be reset (auto-expiration).""" + if ( + not self._cycle_completed_time + or self.detector.state == STATE_RUNNING + or self.detector.state == STATE_ANTI_WRINKLE + or self.detector.state == STATE_DELAY_WAIT + ): + # Cycle is running or not completed, don't reset + return + + time_since_complete = (now - self._cycle_completed_time).total_seconds() + + # Clean laundry nag notification + if ( + self._is_clean_state + and not self._notified_clean_laundry + and self._clean_state_start is not None + and self._notify_unload_delay_minutes > 0 + ): + time_in_clean = (now - self._clean_state_start).total_seconds() + if time_in_clean >= self._notify_unload_delay_minutes * 60: + if self._notify_finish_services or self._notify_actions: + duration_min = int(time_since_complete / 60) + msg_template = self.config_entry.options.get( + CONF_NOTIFY_UNLOAD_MESSAGE, DEFAULT_NOTIFY_UNLOAD_MESSAGE + ) + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_UNLOAD_MESSAGE, + device=self.config_entry.title, + duration=duration_min, + delay=self._notify_unload_delay_minutes, + ) + sent = self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_CLEAN, + extra_vars={"tag": self._clean_tag}, + ) + if sent: + self._notified_clean_laundry = True + self._logger.info( + "Sent clean laundry nag notification (%.0f min after cycle end)", + time_since_complete / 60, + ) + else: + self._notified_clean_laundry = True + + if time_since_complete > self._progress_reset_delay: + # Defer the reset when a clean-state unload notification is still pending. + # Without this guard the 30-min progress reset fires before the 60-min + # unload nag, clearing _is_clean_state before the notification can fire. + if ( + self._is_clean_state + and not self._notified_clean_laundry + and self._notify_unload_delay_minutes > 0 + and time_since_complete < self._notify_unload_delay_minutes * 60 + ): + return + # Auto-expire the "Finished" (or other terminal) state + self._logger.debug( + "State expiry: cycle idle for %.0fs (threshold: %ss). Resetting to OFF.", + time_since_complete, + self._progress_reset_delay, + ) + self._cycle_progress = 0.0 + self._cycle_completed_time = None + # Clear clean state when progress expires + self._is_clean_state = False + self._clean_state_start = None + self._notified_clean_laundry = False + self.detector.reset(STATE_OFF) + self._stop_state_expiry_timer() + self._notify_update() + + async def _watchdog_check_stuck_cycle(self, now: datetime) -> None: + """Watchdog: check if cycle is stuck (no updates for too long).""" + if self.detector.state not in (STATE_RUNNING, STATE_STARTING, STATE_PAUSED, STATE_ENDING): + return + + if not self._last_reading_time: + return + + time_since_any_update = (now - self._last_reading_time).total_seconds() + + # Calculate time since REAL update (if available, else fallback to any update) + last_real = self._last_real_reading_time or self._last_reading_time + time_since_real_update = (now - last_real).total_seconds() + + elapsed = self.detector.get_elapsed_seconds() + expected = getattr(self.detector, "expected_duration_seconds", 0) + + # 0a. PUMP STUCK DETECTION (Pump Monitor only) + # If a pump cycle has been running longer than the configured stuck threshold, + # fire a single warning event so the user can wire an automation/alert. + # Skip while user-paused or detector-verified-pause to avoid false positives. + _verified_pause = getattr(self.detector, "_verified_pause", False) + if self.device_type == DEVICE_TYPE_PUMP and not self._pump_stuck and not self._is_user_paused and not _verified_pause: + adjusted_elapsed = elapsed - self._total_user_paused_seconds + if adjusted_elapsed >= self._pump_stuck_duration: + self._pump_stuck = True + self._logger.warning( + "Pump stuck detected: cycle has been running for %.0fs net " + "(threshold: %ds). Firing %s event.", + adjusted_elapsed, + self._pump_stuck_duration, + EVENT_PUMP_STUCK, + ) + self.hass.bus.async_fire( + EVENT_PUMP_STUCK, + { + "device": self.config_entry.title, + "entry_id": self.entry_id, + "elapsed_seconds": round(adjusted_elapsed), + "threshold_seconds": self._pump_stuck_duration, + }, + ) + self._notify_update() + + # 0. ZOMBIE KILLER (Hard Limit) + # If cycle has run significantly longer than expected (300%), kill it. + # Only applies if we have a profile match. Skip while user-paused or + # detector-verified-pause to avoid killing legitimately paused cycles. + _verified_pause_zombie = getattr(self.detector, "_verified_pause", False) + if ( + expected > 0 + and not self._is_user_paused + and not _verified_pause_zombie + ): + adjusted_elapsed = elapsed - self._total_user_paused_seconds + if adjusted_elapsed > (expected * 3.0) and adjusted_elapsed > 14400: + self._logger.warning( + "Watchdog: Zombie cycle detected (%.0fs net > 300%% of expected %.0fs). Force-ending.", + adjusted_elapsed, expected + ) + self.detector.force_end(now) + self._current_power = 0.0 # Force 0W + self._notify_update() + return + + # 1. GHOST CYCLE SUPPRESSOR + # If we are "detecting" for more than 10 minutes and haven't seen an update for 5 minutes, + # it's likely a pump-out spike or an accidental start (ghost cycle). + # We end it aggressively ONLY if it started shortly after another cycle ended (Suspicious Window). + cycle_start = self.detector.current_cycle_start + is_suspicious = False + if cycle_start and self._last_cycle_end_time: + # Dishwashers have a drain pump-out that fires 3-8 min after the main + # cycle ends; use a wider suspicious window so the ghost suppressor can + # catch it without false-positives on washing machines / dryers. + suspicious_window = 600 if self.device_type == "dishwasher" else 180 + if (cycle_start - self._last_cycle_end_time).total_seconds() < suspicious_window: + is_suspicious = True + + # For dishwashers in the suspicious window, kill pump-out ghosts faster. + # Pump-outs last 1-3 min then go silent; the standard 10-min wait allows + # them to accumulate too much runtime before suppression fires. + dishwasher_pump_out = ( + is_suspicious + and self.device_type == "dishwasher" + and elapsed > 180 # 3 minutes + and time_since_real_update > 60 # 1 minute of silence + ) + + if ( + self._current_program == "detecting..." + and is_suspicious + and ( + dishwasher_pump_out + or (elapsed > 600 and time_since_real_update > 300) + ) + ): + self._logger.warning( + "Watchdog: Ghost cycle suppressed (within suspicious window). Detecting for %.0fs with %.0fs silence.", + elapsed, time_since_real_update + ) + self.detector.force_end(now) + self._current_power = 0.0 + self._notify_update() + return + + # --- LOW POWER HANDLING --- + # If we are in a low power state (waiting for off_delay or drying profile), + # we treat silence leniently. We inject keepalives until the stricter + # low_power_no_update_timeout is reached. + + # Dishwashers can have very long silent drying phases (up to 2h) + # We use the device-specific timeout as the floor for this effective timeout. + # The floor is applied unconditionally - dishwashers have passive drying phases + # even when no profile has been matched yet. The original restriction to matched + # cycles caused premature kills: with the default 3600s timeout, an unmatched + # dishwasher cycle was killed ~1h after the last sensor update, while the + # physical drying phase could still have 1-2h of silent runtime remaining. + low_power_floor = DEFAULT_NO_UPDATE_ACTIVE_TIMEOUT_BY_DEVICE.get( + self.device_type, 0 + ) + effective_low_power_timeout = max( + low_power_floor, self._low_power_no_update_timeout + ) + + # Profile-Aware Extension: + # If we have a matched profile, ensure we don't kill during the expected duration. + if expected > 0 and elapsed < expected: + # Extend timeout to cover the remaining expected duration + buffer + remaining = expected - elapsed + # Allow silence up to remaining + 1800s (30m buffer for drying/pause) + extended_timeout = remaining + 1800 + if extended_timeout > effective_low_power_timeout: + effective_low_power_timeout = extended_timeout + + # Verified Pause Extension: + # If the manager/store has confirmed this is a legitimate pause (e.g. Drying), + # allow even more leniency up to the global deferral limit. + if getattr(self.detector, "_verified_pause", False): + # Allow silence up to DEFAULT_MAX_DEFERRAL_SECONDS (default 2h) + buffer + pause_limit = DEFAULT_MAX_DEFERRAL_SECONDS + 1800 + if pause_limit > effective_low_power_timeout: + effective_low_power_timeout = pause_limit + self._logger.debug( + "Watchdog: Extending timeout to %.0fs due to verified pause", + effective_low_power_timeout + ) + + if self.detector.is_waiting_low_power(): + + # 2. Staleness Check + if time_since_real_update > effective_low_power_timeout: + self._logger.warning( + "Watchdog: Force-ending cycle. Low-power state stale for %.0fs (> %.0fs).", + time_since_real_update, + effective_low_power_timeout + ) + self.detector.force_end(now) + self._last_reading_time = now + self._current_power = 0.0 + self._notify_update() + return + + # 3. Injection Check (Keepalive) + # 3a. Honour the user-configured no_update_active_timeout for low-power silence. + # Publish-on-change sensors go completely silent once they stabilise at a low + # standby value (e.g. 1 W). The existing off_delay-based injection fires + # every 2 watchdog ticks, which is fine with a short watchdog interval but can + # take many minutes with a larger one. Respecting no_update_active_timeout + # here gives users a predictable upper bound on how long a cycle lingers after + # the appliance reaches standby, consistent with what the setting implies. + # Verified pauses (e.g. dishwasher drying confirmed by envelope) are excluded + # so that legitimate long silent phases are not prematurely terminated. + if ( + not getattr(self.detector, "_verified_pause", False) + and time_since_real_update > self._no_update_active_timeout + ): + self._logger.debug( + "Watchdog: Low-power real-update silence (%.0fs) > no_update_active_timeout (%.0fs). " + "Injecting 0W keepalive to advance accumulator.", + time_since_real_update, + self._no_update_active_timeout, + ) + self.detector.process_reading(0.0, now) + self._last_reading_time = now + self._current_power = 0.0 + self._notify_update() + return + + # 3b. Fallback: inject 0W when any-update silence exceeds off_delay. + # This keeps the accumulator moving even when no_update_active_timeout has + # not been exceeded (e.g. the user left it at the default 600 s). + if time_since_any_update > self._config.off_delay: + self._logger.debug( + "Watchdog: Low power silence (%.0fs). Injecting 0W keepalive.", + time_since_any_update + ) + # Ensure we handle the injection cleanly + # Do NOT update _last_real_reading_time here + self.detector.process_reading(0.0, now) + self._last_reading_time = now # Resets 'any' timer so we don't spam + self._current_power = 0.0 + self._notify_update() + return + + return + + # Fallback for old "Case 1.5" logic (Low Power but NOT is_waiting_low_power) + # Check this BEFORE High Power timeout to prevent trapping "Not Yet Waiting" states + # Inject as soon as the earliest of: off_delay silence OR no_update_active_timeout. + if self._current_power <= self.detector.config.min_power and ( + time_since_any_update > self._config.off_delay + or time_since_real_update > self._no_update_active_timeout + ): + # Treating as start of low power wait + self._logger.debug("Watchdog: Silence at low power (%.0fs). Injecting 0W.", time_since_any_update) + self.detector.process_reading(0.0, now) + self._last_reading_time = now + self._current_power = 0.0 + self._notify_update() + return + + # --- HIGH POWER HANDLING (Normal) --- + # If power is high, we expect frequent updates. + + if time_since_any_update > self._no_update_active_timeout: + + # Check if high power (running) + if self._current_power > self.detector.config.min_power: + # Allow extended silence if within reasonable cycle bounds + expected = getattr(self.detector, "expected_duration_seconds", 0) + elapsed = self.detector.get_elapsed_seconds() + limit = (expected + 14400) if expected > 0 else 14400 # 4h default + + if elapsed < limit: + self._logger.info( + "Watchdog: High power (%.1fW) stale (%.0fs). Injecting refresh.", + self._current_power, time_since_any_update + ) + self.detector.process_reading(self._current_power, now) + self._last_reading_time = now + self._notify_update() + return + + # If we get here, it's truly stuck/offline + self._logger.warning( + "Watchdog: Force-ending cycle. Active state stale for %.0fs (> timeout).", + time_since_any_update + ) + self.detector.force_end(now) + self._current_power = 0.0 # FIX: Reset current power + self._notify_update() + return + + + def _on_state_change(self, old_state: str, new_state: str) -> None: + """Handle state change from detector.""" + self._logger.debug("Washer state changed: %s -> %s", old_state, new_state) + self.diag_buffer.record_state( + old_state, new_state, self._current_program, dt_util.now() + ) + # A new cycle starting while we are still showing the completed/Clean + # overlay (the progress-reset window) must clear that overlay and cancel + # the expiry timer right away, so the UI leaves "Finished" and the unload + # nag stops immediately instead of waiting for the reset window - and so + # the expiry timer cannot race the new cycle and reset us to OFF (#267). + if new_state == STATE_STARTING and self._cycle_completed_time is not None: + self._cycle_completed_time = None + self._is_clean_state = False + self._clean_state_start = None + self._notified_clean_laundry = False + self._cycle_progress = 0.0 + self._stop_state_expiry_timer() + if new_state == STATE_RUNNING: + new_cycle_detected = old_state in (STATE_OFF, STATE_STARTING, STATE_UNKNOWN) + # Only reset estimates if we are truly starting a NEW cycle (from off or starting) + # If we transition from PAUSED or ENDING, it's a resume - keep estimates! + if new_cycle_detected: + self._cycle_completed_time = None + self._stop_state_expiry_timer() + + self._current_program = "detecting..." + self._manual_program_active = False + self._notified_pre_completion = False + self._time_remaining = None + self._total_duration = None + self._cycle_progress = 0 + self._matched_profile_duration = None + self._last_estimate_time = None + self._score_history = {} # Reset score history on new cycle + self._match_persistence_counter = {} # Reset persistence counter + self._unmatch_persistence_counter = 0 # Reset unmatch counter + self._current_match_candidate = None # Reset candidate + self._notified_start = False # Reset start notification state + self._start_event_fired = False + self._cycle_start_time = self.detector.current_cycle_start or dt_util.now() + self._reset_live_notification_state() + + # Reset pause tracking and clean state for new cycle + self._is_user_paused = False + self._user_pause_start = None + self._total_user_paused_seconds = 0.0 + self._is_clean_state = False + self._clean_state_start = None + self._notified_clean_laundry = False + + self._start_watchdog() # Start watchdog when cycle starts + + # Fire the start event immediately on cycle detection so listeners always + # receive it, even when no profile match occurs yet. + if self._notify_fire_events: + self.hass.bus.async_fire( + EVENT_CYCLE_STARTED, + { + "entry_id": self.entry_id, + "device_name": self.config_entry.title, + "device_type": self.device_type, + "program": self._current_program or "unknown", + "start_time": self._cycle_start_time.isoformat(), + }, + ) + self._start_event_fired = True + + # Fire push notification immediately - do not wait for profile matching. + if not self._notified_start and (self._notify_start_services or self._notify_actions): + msg_template = self.config_entry.options.get( + CONF_NOTIFY_START_MESSAGE, DEFAULT_NOTIFY_START_MESSAGE + ) + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_START_MESSAGE, + device=self.config_entry.title, + program=self._current_program, + ) + self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_START, + extra_vars={ + "program": self._current_program, + "tag": self._lifecycle_tag, + }, + ) + self._notified_start = True + self._logger.info( + "Sent start notification for program '%s'", self._current_program + ) + self._check_pre_completion_notification() + else: + self._logger.debug("Cycle resumed from %s, preserving estimates", old_state) + # Ensure watchdog is running + self._start_watchdog() + + # Stop watchdog when transitioning to OFF from any active state + if new_state == STATE_OFF: + self._stop_watchdog() # Stop watchdog regardless of previous state + self._cycle_start_time = None + + self._notify_update() + + def _on_cycle_end(self, cycle_data: dict[str, Any]) -> None: + """Handle cycle end - clear all active timers and state.""" + duration = cycle_data["duration"] + max_power = cycle_data.get("max_power", 0) + + # IMMEDIATELY stop all active timers when cycle determined to have ended + self._stop_watchdog() # Stop active cycle watchdog + self._stop_state_expiry_timer() # Cancel any pending progress reset + prev_cycle_end_time = self._last_cycle_end_time + self._last_cycle_end_time = dt_util.now() + self._pump_stuck = False # Reset for next pump cycle + + # Auto-Tune: Check for ghost cycles (short duration AND low energy) + # Ghost = duration < 60s AND total energy < 0.05 Wh (avoids killing pump-out spikes) + power_data = cycle_data.get("power_data", []) + cycle_energy_wh = 0.0 + if power_data and len(power_data) >= 2: + valid: list[tuple[float, float]] = [] + for p in power_data: + try: + valid.append((float(p[0]), float(p[1]))) + except (TypeError, ValueError, IndexError): + pass + if len(valid) >= 2: + try: + valid.sort(key=lambda x: x[0]) + ts = np.array([v[0] for v in valid]) + ps = np.array([v[1] for v in valid]) + dt_h = np.diff(ts) / 3600.0 + _MAX_GAP_H = 1.0 # skip segments longer than 1 hour + mask = (dt_h > 0) & (dt_h <= _MAX_GAP_H) + avg_p = (ps[:-1] + ps[1:]) / 2 + cycle_energy_wh = float(np.sum(avg_p[mask] * dt_h[mask])) + except (TypeError, ValueError, ArithmeticError): + cycle_energy_wh = 0.0 + + # Ghost cycle: short AND low energy (real cycles have energy even if short) + if duration < 60 and cycle_energy_wh < 0.05: + self._handle_noise_cycle(max_power) + elif self.device_type == "dishwasher" and prev_cycle_end_time is not None: + # Pump-out suppression: dishwashers end cycles with a brief drain pump + # (typically 30-300 s, < 1 Wh) a few minutes after the main cycle + # finishes. If a short, low-energy cycle starts within 10 minutes of + # the previous cycle, treat it as a pump-out ghost and do not store it. + cycle_start_str = cycle_data.get("start_time") + cycle_start_dt = ( + dt_util.parse_datetime(cycle_start_str) if cycle_start_str else None + ) + if cycle_start_dt is not None: + gap = (cycle_start_dt - prev_cycle_end_time).total_seconds() + if 0 < gap < 600 and duration < 300 and cycle_energy_wh < 1.0: + self._logger.info( + "Suppressing dishwasher pump-out ghost: " + "gap=%.0fs, duration=%.0fs, energy=%.3f Wh", + gap, + duration, + cycle_energy_wh, + ) + self._handle_noise_cycle(max_power) + return # Do not store this as a real cycle + + # Store energy for notification and persistence (calculated above for ghost detection) + cycle_data["energy_wh"] = round(cycle_energy_wh, 3) + + # Schedule heavy post-processing asynchronously + self.hass.async_create_task(self._async_process_cycle_end(cycle_data)) + + async def _async_process_cycle_end(self, cycle_data: dict[str, Any]) -> None: + """Process cycle completion asynchronously (heavy tasks).""" + + # FINAL PROFILE MATCH: If still detecting, try one last match with complete cycle data + if self._current_program in ("detecting...", "restored..."): + await self._run_final_match_from_cycle_data(cycle_data) + + # If we had a runtime match, attach the profile name for persistence + if ( + self._current_program + and self._current_program not in ("off", "detecting...", "restored...") + and self._current_program in self.profile_store.get_profiles() + ): + cycle_data["profile_name"] = self._current_program + if self._last_match_confidence: + cycle_data["match_confidence"] = float(self._last_match_confidence) + + # Attach extensive debug data if available (and configured) + if self._last_match_result: + cycle_data["debug_data"] = { + "ranking": getattr(self._last_match_result, "ranking", []), + "details": getattr(self._last_match_result, "debug_details", {}), + "ambiguous": getattr(self._last_match_result, "is_ambiguous", False), + } + + # Post-Cycle Auto-Labeling (if not already matched) + # Offload this match too if needed + if not cycle_data.get("profile_name") and self._auto_label_confidence > 0: + res = await self.profile_store.async_match_profile( + cycle_data["power_data"], cycle_data["duration"] + ) + if res.best_profile and res.confidence >= self._auto_label_confidence: + cycle_data["profile_name"] = res.best_profile + cycle_data["match_confidence"] = float(res.confidence) + self._logger.info( + "Post-cycle auto-labeled as '%s' (confidence: %.2f)", + res.best_profile, + res.confidence, + ) + + # Add cycle to store immediately (still sync but offloadable parts optimized + # internally if possible) + # Note: add_cycle is mostly safe (signature calc is O(N) but fast enough for + # single cycle). + # We could offload signature calc to analysis logic if really needed, but let's + # stick to match profile optimization first. + try: + await self.profile_store.async_add_cycle(cycle_data) + profile_name = cycle_data.get("profile_name") + if profile_name: + await self.profile_store.async_rebuild_envelope(profile_name) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Failed to add cycle to store: %s", e) + + # Ensure cycle has a stable ID even if store add failed (or did not mutate). + if not cycle_data.get("id"): + try: + unique_str = f"{cycle_data['start_time']}_{cycle_data['duration']}" + cycle_data["id"] = hashlib.sha256(unique_str.encode()).hexdigest()[:12] + except Exception: # noqa: BLE001 + pass + + self.hass.async_create_task(self.profile_store.async_clear_active_cycle()) + + # Auto post-process: merge fragmented cycles from last 3 hours + self.hass.async_create_task(self._run_post_cycle_processing()) + + # Prepare cycle data for event (enrich if needed) + # IMPORTANT: Exclude large fields to prevent exceeding HA's 32KB event data limit + excluded_fields = {"power_data", "debug_data", "power_trace"} + event_cycle_data = { + k: v for k, v in cycle_data.items() if k not in excluded_fields + } + event_cycle_data["device_type"] = self.device_type + # Add program if missing or generic + if "profile_name" not in event_cycle_data and self._current_program: + event_cycle_data["profile_name"] = self._current_program + + if self._notify_fire_events: + self.hass.bus.async_fire( + EVENT_CYCLE_ENDED, + { + "entry_id": self.entry_id, + "device_name": self.config_entry.title, + "cycle_data": event_cycle_data, + "program": event_cycle_data.get("profile_name", "unknown"), + "duration": event_cycle_data.get("duration"), + "start_time": event_cycle_data.get("start_time"), + "end_time": event_cycle_data.get("end_time") or dt_util.now().isoformat(), + }, + ) + + # Purge pending live entries and reset counters, but don't send a service-level + # clear: the finished notification below reuses the lifecycle tag and replaces + # the live card in place (sending a clear first would cause a dismiss/recreate + # flicker). The action-based clear marker still fires for action templates. + self._clear_live_progress_notification(clear_services=False) + + # Send notification if enabled + if self._notify_finish_services or self._notify_actions: + msg_template = self.config_entry.options.get(CONF_NOTIFY_FINISH_MESSAGE, DEFAULT_NOTIFY_FINISH_MESSAGE) + duration_min = int(cycle_data['duration'] / 60) + program_name = event_cycle_data.get("profile_name", "unknown") + + energy_kwh = round(cycle_data.get("energy_wh", 0.0) / 1000, 3) + + # Resolve energy price: entity takes precedence over static value + options = self.config_entry.options + price: float | None = None + price_entity = options.get(CONF_ENERGY_PRICE_ENTITY) + if price_entity: + state = self.hass.states.get(price_entity) + if state is not None: + try: + price = float(state.state) + except (ValueError, TypeError): + price = None + if price is None: + static = options.get(CONF_ENERGY_PRICE_STATIC) + if static is not None: + try: + price = float(static) + except (ValueError, TypeError): + price = None + cost_str = f"{energy_kwh * price:.2f}" if price is not None else "" + + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_FINISH_MESSAGE, + device=self.config_entry.title, + duration=duration_min, + program=program_name, + energy_kwh=f"{energy_kwh:.3f}", + cost=cost_str, + ) + self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_FINISH, + extra_vars={ + "duration_minutes": duration_min, + "duration_seconds": cycle_data["duration"], + "program": program_name, + "energy_kwh": energy_kwh, + "cost": cost_str, + # Same lifecycle tag as start/live so the finished alert replaces + # the live notification in place. No live_update/alert_once here, + # so the companion app surfaces it with sound. + "tag": self._lifecycle_tag, + }, + ) + + # Request user feedback if we had a confident match. + # AND perform learning analysis on the completed cycle. + # IMPORTANT: this must happen before we clear match state. + self.learning_manager.process_cycle_end( + cycle_data, + detected_profile=self._current_program, + confidence=self._last_match_confidence or 0.0, + predicted_duration=self._matched_profile_duration, + match_result=self._last_match_result, + ) + + # Clear all state and timers - zero everything out + self._current_program = "off" + self._manual_program_active = False + self._notified_pre_completion = False + self._time_remaining = None + self._matched_profile_duration = None + self._last_estimate_time = None + self._last_match_result = None # Clear so phase sensor resets to "Off" (issue #192) + self._cycle_progress = 100.0 # 100% = cycle complete + self._cycle_completed_time = dt_util.now() + self._cycle_start_time = None + self._reset_live_notification_state() + + # Reset pause tracking for the next cycle + self._is_user_paused = False + self._user_pause_start = None + self._total_user_paused_seconds = 0.0 + + # Enter Clean state if door sensor is configured and door is currently closed + self._is_clean_state = False + self._clean_state_start = None + self._notified_clean_laundry = False + if self._door_sensor_entity: + door_state = self.hass.states.get(self._door_sensor_entity) + if door_state and door_state.state == "off": # binary_sensor: off = closed + self._is_clean_state = True + self._clean_state_start = dt_util.now() + self._logger.debug( + "Cycle ended with door closed: entering Clean state" + ) + + # Start progress reset timer to go back to 0% after user unload window + self._start_state_expiry_timer() + + self._notify_update() + + @property + def profile_sample_repair_stats(self) -> dict[str, int] | None: + """Return statistics from profile sample repair operation.""" + return self._profile_sample_repair_stats + + @property + def suggestions(self) -> dict[str, Any]: + """Suggested settings computed by learning/heuristics (never auto-applied).""" + return self.profile_store.get_suggestions() + + def _send_notification(self, message: str, title: str | None = None, icon: str | None = None) -> None: + """Dispatch notification through actions or notify service.""" + self._dispatch_notification(message, title=title, icon=icon) + + def _safe_format_template( + self, + template: Any, + *, + fallback_template: str | None = None, + **kwargs: Any, + ) -> str: + """Format templates safely and return a resilient fallback on any error.""" + text_template = str(template) + try: + return text_template.format(**kwargs) + except Exception as err: # pylint: disable=broad-exception-caught + self._logger.debug( + "Failed to format notification template %r with %s: %s", + text_template, + kwargs, + err, + ) + + if fallback_template: + try: + return fallback_template.format(**kwargs) + except Exception as err: # pylint: disable=broad-exception-caught + self._logger.debug( + "Failed to format fallback notification template %r with %s: %s", + fallback_template, + kwargs, + err, + ) + + device = str(kwargs.get("device") or self.config_entry.title) + program = kwargs.get("program") + if program: + return f"{device}: {program}" + return device + + def _get_services_for_event(self, event_type: str | None) -> list[str]: + """Return the configured notify service list for the given event type.""" + if event_type == NOTIFY_EVENT_START: + return self._notify_start_services + if event_type in (NOTIFY_EVENT_FINISH, "pre_complete", NOTIFY_EVENT_CLEAN): + return self._notify_finish_services + if event_type == NOTIFY_EVENT_LIVE: + return self._notify_live_services + return [] + + def _resolve_channel(self, event_type: str | None) -> str | None: + """Resolve the Android notification channel name for an event type. + + Finished, the clean-laundry nag, and the pre-completion reminder route to the + dedicated finish channel (so they can carry their own sound), falling back to + the status channel. Start/live use the status channel. An empty configured + value means "omit channel" so existing setups are unchanged. + """ + status_channel = self.config_entry.options.get( + CONF_NOTIFY_CHANNEL, DEFAULT_NOTIFY_CHANNEL + ) + finish_channel = self.config_entry.options.get( + CONF_NOTIFY_FINISH_CHANNEL, DEFAULT_NOTIFY_FINISH_CHANNEL + ) + if event_type in (NOTIFY_EVENT_FINISH, NOTIFY_EVENT_CLEAN, "pre_complete"): + return (finish_channel or status_channel) or None + return status_channel or None + + def _dispatch_notification( + self, + message: str, + *, + title: str | None = None, + icon: str | None = None, + event_type: str | None = None, + person_entity_id: str | None = None, + person_name: str | None = None, + extra_vars: dict[str, Any] | None = None, + allow_deferral: bool = True, + ) -> bool: + """Route notification via actions or notify service with optional gating.""" + if not title: + title_template = self.config_entry.options.get(CONF_NOTIFY_TITLE, DEFAULT_NOTIFY_TITLE) + title = self._safe_format_template( + title_template, + fallback_template=DEFAULT_NOTIFY_TITLE, + device=self.config_entry.title, + ) + + if not icon: + icon = self.config_entry.options.get(CONF_NOTIFY_ICON) + + if person_entity_id is None and self._notify_people: + for candidate in self._notify_people: + state = self.hass.states.get(candidate) + if state and state.state == STATE_HOME: + person_entity_id = candidate + person_name = state.name or state.attributes.get( + "friendly_name", candidate + ) + break + + variables: dict[str, Any] = { + "device": self.config_entry.title, + "program": self._current_program, + "message": message, + "title": title, + "icon": icon, + "event_type": event_type, + "person_entity_id": person_entity_id, + "person_name": person_name, + } + if extra_vars: + variables.update(extra_vars) + + # Channel + auto-dismiss timeout apply to every event type. Inject into both + # the action variables and the notify-service extra_vars so both delivery + # paths honour them. Empty channel / zero timeout are omitted (no-op default). + channel = self._resolve_channel(event_type) + if channel: + variables["channel"] = channel + extra_vars = {**(extra_vars or {}), "channel": channel} + if self._notify_timeout_seconds > 0: + variables["timeout"] = self._notify_timeout_seconds + extra_vars = {**(extra_vars or {}), "timeout": self._notify_timeout_seconds} + + if allow_deferral and self._notify_only_when_home and self._notify_people: + if not self._is_any_notify_person_home(): + if event_type == NOTIFY_EVENT_LIVE: + self._pending_notifications = [ + entry + for entry in self._pending_notifications + if entry.get("event_type") != NOTIFY_EVENT_LIVE + ] + self._pending_notifications.append( + { + "message": message, + "title": title, + "icon": icon, + "event_type": event_type, + "extra_vars": extra_vars, + } + ) + return False + + actions_sent = False + if self._notify_actions: + actions_sent = bool(self._run_notification_actions(variables)) + + # If actions fired and there are no per-event services, skip the + # service/persistent-notification path entirely. + services = self._get_services_for_event(event_type) + if actions_sent and not services: + return True + + service_sent = self._send_notification_service( + message, + services=services, + title=title, + icon=icon, + event_type=event_type, + extra_vars=extra_vars, + ) + return actions_sent or service_sent + + def _send_notification_service( + self, + message: str, + *, + services: list[str], + title: str | None = None, + icon: str | None = None, + event_type: str | None = None, + extra_vars: dict[str, Any] | None = None, + ) -> bool: + """Send a notification to each configured notify service, or fall back to persistent notification.""" + data: dict[str, Any] = {} + if icon: + data["icon"] = icon + + ev = extra_vars or {} + # Common payload keys forwarded for every event type so start/live/reminder/ + # finished share a tag (replace each other) and honour timeout/channel/priority. + for key in ("tag", "timeout", "channel", "priority"): + if key in ev: + data[key] = ev[key] + + # Live-progress-only payload keys (countdown, progress bar, throttle markers). + if event_type == NOTIFY_EVENT_LIVE: + for key in ( + "progress", + "progress_max", + "live_update", + "alert_once", + "cycle_seconds", + "time_remaining_seconds", + "minutes_left", + "live_updates_sent", + "live_updates_cap", + "chronometer", + "when", + "countdown", + ): + if key in ev: + data[key] = ev[key] + + sent = False + for notify_service in services: + if event_type == NOTIFY_EVENT_LIVE and not self._is_mobile_notify_service( + notify_service + ): + self._logger.debug( + "Skipping live notification for non-mobile notify service: %s", + notify_service, + ) + continue + + state = ( + self.hass.states.get(notify_service) + if notify_service.startswith("notify.") + else None + ) + if state is not None and getattr(state, "domain", None) == "notify": + service_data: dict[str, Any] = { + "entity_id": notify_service, + "message": message, + } + if title: + service_data["title"] = title + if data: + service_data["data"] = data + self.hass.async_create_task( + self.hass.services.async_call( + "notify", "send_message", service_data + ) + ) + else: + domain, service = ( + notify_service.split(".", 1) + if "." in notify_service + else ("notify", notify_service) + ) + service_data = {"message": message, "title": title} + if data: + service_data["data"] = data + self.hass.async_create_task( + self.hass.services.async_call(domain, service, service_data) + ) + sent = True + + if not sent: + if event_type == NOTIFY_EVENT_LIVE: + return False + # Reuse the notification's tag as a stable persistent-notification id so + # the HA notifications tab collapses the lifecycle thread to one entry + # instead of accumulating a new card per cycle (issue #248/#249 clutter). + _pn_create( + self.hass, + message, + title=title, + notification_id=ev.get("tag"), + ) + return True + + return sent + + def _run_notification_actions(self, variables: dict[str, Any]) -> bool: + """Run configured notification actions.""" + actions: list[dict[str, Any]] = self._notify_actions + if not actions: + return False + + try: + script = script_helper.Script( + self.hass, + actions, + name=f"{self.config_entry.title} notification", + domain=DOMAIN, + logger=_LOGGER, + ) + except (ValueError, TypeError, HomeAssistantError) as err: + self._logger.error( + "Invalid notification action configuration for %s: %s", + self.config_entry.title, + err, + ) + return False + except Exception as err: + self._logger.exception( + "Unexpected error while building notification actions for %s: %s", + self.config_entry.title, + err, + ) + return False + + try: + self.hass.async_create_task( + script.async_run(variables, context=Context()) + ) + return True + except HomeAssistantError as err: + self._logger.warning( + "Notification action execution failed for %s: %s", + self.config_entry.title, + err, + ) + return False + except Exception as err: + self._logger.exception( + "Unexpected error while scheduling notification actions for %s: %s", + self.config_entry.title, + err, + ) + return False + + def _is_any_notify_person_home(self) -> bool: + """Return True when any configured person is home.""" + for person_entity_id in self._notify_people: + state = self.hass.states.get(person_entity_id) + if state and state.state == STATE_HOME: + return True + return False + + @callback + def _handle_notify_person_change(self, event: Event[evt.EventStateChangedData]) -> None: + """Handle person state changes to release pending notifications.""" + new_state = event.data.get("new_state") + if not new_state or new_state.state != STATE_HOME: + return + + if not self._pending_notifications: + return + + person_entity_id = new_state.entity_id + person_name = new_state.name or new_state.attributes.get( + "friendly_name", person_entity_id + ) + pending: list[dict[str, Any]] = list(self._pending_notifications) + self._pending_notifications = [] + for entry in pending: + sent = self._dispatch_notification( + entry["message"], + title=entry.get("title"), + icon=entry.get("icon"), + event_type=entry.get("event_type"), + person_entity_id=person_entity_id, + person_name=person_name, + extra_vars=entry.get("extra_vars"), + allow_deferral=False, + ) + if sent and entry.get("event_type") == NOTIFY_EVENT_LIVE: + ev_raw = entry.get("extra_vars") + ev: dict[str, Any] = ev_raw if isinstance(ev_raw, dict) else {} + if "progress" not in ev: + self._live_waiting_notification_sent = True + else: + self._live_notification_sent_count += 1 + self._last_live_notification_time = dt_util.now() + + def _handle_noise_cycle(self, max_power: float) -> None: + """Handle a detected noise cycle.""" + # Clean up old noise events > 24h + now = dt_util.now() + self._noise_events = [ + t + for t in getattr(self, "_noise_events", []) + if (now - t).total_seconds() < 86400 + ] + self._noise_events.append(now) + + # Track max power of noise + self._noise_max_powers = getattr(self, "_noise_max_powers", []) + self._noise_max_powers.append(max_power) + + # If noise events exceed threshold in 24h, trigger tune + if len(self._noise_events) >= self._noise_events_threshold: + self.hass.async_create_task(self._tune_threshold()) + + async def _tune_threshold(self) -> None: + """Increase the minimum power threshold.""" + current_min = self.detector.config.min_power + + # Calculate new suggested threshold + # Max of observed noise * 1.2 safety factor + noise_max = max(self._noise_max_powers) + new_min = noise_max * 1.2 + + # Cap absolute max to avoid runaway (e.g. 50W) + if new_min > 50.0: + new_min = 50.0 + + if new_min <= current_min: + # Clear events so we don't loop try to update + self._noise_events = [] + self._noise_max_powers = [] + return + + self._logger.info( + "Auto-Tune suggestion: min_power from %.1fW -> %.1fW due to noise", + current_min, + new_min, + ) + + # Store a suggestion (do not mutate user-set options) + self.profile_store.set_suggestion( + CONF_MIN_POWER, + float(new_min), + f"Auto-tune: {len(self._noise_events)} ghost cycles detected in 24h", + ) + await self.profile_store.async_save() + + # Notify user - use finish services as the natural channel for device suggestions + _translations = await translation.async_get_translations( + self.hass, self.hass.config.language, "options", {DOMAIN} + ) + _default_msg = ( + "{device_type} {device_title} detected ghost cycles. " + "Suggested min_power change: {current_min}W -> {new_min}W " + "(not applied automatically)." + ) + _default_title = "WashData Auto-Tune" + _msg_template = _translations.get( + f"component.{DOMAIN}.options.error.auto_tune_suggestion", _default_msg + ) + _title = _translations.get( + f"component.{DOMAIN}.options.error.auto_tune_title", _default_title + ) + message = _msg_template.format( + device_type=self.device_type, + device_title=self.config_entry.title, + current_min=f"{current_min:.1f}", + new_min=f"{new_min:.1f}", + ) + if self._notify_finish_services or self._notify_start_services or self._notify_actions: + _event_type = ( + NOTIFY_EVENT_FINISH if self._notify_finish_services + else NOTIFY_EVENT_START + ) + self._dispatch_notification(message, title=_title, event_type=_event_type) + else: + _pn_create(self.hass, message, title=_title) + + # Reset trackers + self._noise_events = [] + self._noise_max_powers = [] + + def _update_estimates(self) -> None: + """Update time remaining and profile estimates.""" + if self.detector.state in ( + STATE_OFF, + STATE_UNKNOWN, + STATE_IDLE, + STATE_STARTING, + STATE_ANTI_WRINKLE, + STATE_DELAY_WAIT, + ): + self._current_program = "off" + self._time_remaining = None + self._total_duration = None + self._cycle_progress = 0.0 + self._last_match_result = None + self._notify_update() + return + + now = dt_util.now() + + # Throttle heavy matching to configured interval (default: 5 minutes) + effective_match_interval = self._profile_match_interval + if ( + self._last_estimate_time + and (now - self._last_estimate_time).total_seconds() + < effective_match_interval + ): + # Still update remaining/progress if we already have a match + self._update_remaining_only() + self._check_pre_completion_notification() + self._check_live_progress_notification() + return + + # SKIP matching if manual program is active + if self._manual_program_active: + self._last_estimate_time = now # touch timestamp to throttle estimates loop + self._update_remaining_only() + # Also check notifications in loop + self._check_pre_completion_notification() + self._check_live_progress_notification() + self._notify_update() + return + + # No matching task trigger here anymore! + # The detector callback handles it. + # Just update progress/remaining based on existing match. + self._update_remaining_only() + self._check_pre_completion_notification() + self._check_live_progress_notification() + self._notify_update() + + # _async_run_matching removed in favor of _async_perform_combined_matching + + def _analyze_trend(self, profile_name: str) -> bool: + """Analyze score history to detect positive trend. + + Returns True if score has increased in at least 7 of the last 10 intervals. + Requires at least 5 samples history to make a determination. + """ + history = self._score_history.get(profile_name, []) + if len(history) < 5: + return False + + # Use last 11 points to get 10 intervals (or fewer if history short) + recent = history[-11:] + if len(recent) < 2: + return False + + up_count = sum(1 for i in range(1, len(recent)) if recent[i] > recent[i - 1]) + total_intervals = len(recent) - 1 + + # Proportional threshold (7/10 => 0.7) + return (up_count / total_intervals) >= 0.70 + + def _reset_live_notification_state(self) -> None: + """Reset per-cycle live notification counters and timers.""" + self._live_notification_sent_count = 0 + self._live_notification_cap = 0 + self._last_live_notification_time = None + self._live_waiting_notification_sent = False + self._live_chronometer_overrun_sent = False + + @staticmethod + def _is_mobile_notify_service(notify_service: str | None) -> bool: + """Return True when configured notify target is a mobile app service.""" + if not notify_service: + return False + _, service = ( + notify_service.split(".", 1) + if "." in notify_service + else ("notify", notify_service) + ) + return service.startswith("mobile_app") + + def _estimate_live_notification_cap(self) -> int: + """Compute hard cap for live updates from estimated cycle duration and overrun margin.""" + interval = max(30, int(self._notify_live_interval_seconds)) + estimated_duration = float( + self._matched_profile_duration + or self._total_duration + or max(float(self.detector.get_elapsed_seconds()), float(interval)) + ) + estimated_updates = max(1, int(np.ceil(estimated_duration / interval))) + overrun_ratio = max(0, float(self._notify_live_overrun_percent)) / 100.0 + return max(1, int(np.ceil(estimated_updates * (1.0 + overrun_ratio)))) + + def _check_live_progress_notification(self) -> None: + """Send throttled live progress notifications for compatible mobile targets.""" + if not self._notify_live_services and not self._notify_actions: + return + if self.detector.state not in (STATE_RUNNING, STATE_PAUSED, STATE_ENDING): + return + + has_profile_match = bool( + self._matched_profile_duration and self._matched_profile_duration > 0 + ) + if has_profile_match: + # A profile has been matched - reset the waiting latch so future + # "no profile yet" phases (e.g. after a cycle restart) will send + # the waiting message again. + self._live_waiting_notification_sent = False + if not has_profile_match: + if self._live_waiting_notification_sent: + return + + msg = self._safe_format_template( + DEFAULT_NOTIFY_LIVE_WAITING_MESSAGE, + fallback_template=DEFAULT_NOTIFY_LIVE_WAITING_MESSAGE, + device=self.config_entry.title, + program=self._current_program, + ) + sent = self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_LIVE, + extra_vars={ + "tag": self._live_notification_tag, + "live_update": True, + "alert_once": True, + }, + ) + self._live_waiting_notification_sent = sent + return + + interval = max(30, int(self._notify_live_interval_seconds)) + now = dt_util.now() + if self._last_live_notification_time and ( + now - self._last_live_notification_time + ).total_seconds() < interval: + return + + cap_candidate = self._estimate_live_notification_cap() + if cap_candidate > self._live_notification_cap: + self._live_notification_cap = cap_candidate + + total_seconds = int( + max( + 1, + round( + float( + self._total_duration + or self._matched_profile_duration + or self.detector.get_elapsed_seconds() + ) + ), + ) + ) + remaining_seconds = int(max(0, round(float(self._time_remaining or 0.0)))) + elapsed_seconds = max(0, total_seconds - remaining_seconds) + + # When a chronometer notification is on the phone but the estimate has + # expired, bypass the cap once to replace the frozen "0:00" countdown + # with a plain text update so the user isn't left with a stale timer. + chronometer_overrun = ( + self._notify_live_chronometer + and remaining_seconds <= 0 + and self._live_notification_sent_count > 0 + and not self._live_chronometer_overrun_sent + ) + if not chronometer_overrun and self._live_notification_sent_count >= self._live_notification_cap: + return + minutes_left = max(1, math.ceil(remaining_seconds / 60)) + + msg_template = self.config_entry.options.get( + CONF_NOTIFY_PRE_COMPLETE_MESSAGE, + DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE, + ) + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_PRE_COMPLETE_MESSAGE, + device=self.config_entry.title, + minutes=minutes_left, + program=self._current_program, + ) + + extra_vars: dict[str, Any] = { + "tag": self._live_notification_tag, + "progress": elapsed_seconds, + "progress_max": total_seconds, + "live_update": True, + "alert_once": True, + "cycle_seconds": total_seconds, + "time_remaining_seconds": remaining_seconds, + "minutes_left": minutes_left, + "live_updates_sent": self._live_notification_sent_count + 1, + "live_updates_cap": self._live_notification_cap, + } + if self._notify_live_chronometer and remaining_seconds > 0: + extra_vars["chronometer"] = True + extra_vars["when"] = int(now.timestamp()) + remaining_seconds + extra_vars["countdown"] = True + sent = self._dispatch_notification( + msg, + event_type=NOTIFY_EVENT_LIVE, + extra_vars=extra_vars, + ) + if sent: + if chronometer_overrun: + self._live_chronometer_overrun_sent = True + else: + self._live_notification_sent_count += 1 + self._last_live_notification_time = now + + def _clear_live_progress_notification(self, clear_services: bool = True) -> None: + """Clear active live/progress notifications and purge stale deferred alerts. + + On cycle finish (``clear_services=False``) the finished notification carries + the same lifecycle tag and replaces the live notification in place, so we must + NOT also send a service-level ``clear_notification`` (it would briefly dismiss + then re-create the card). The pending-purge, the action-based clear marker + (kept for backward compatibility with custom action templates), and the state + reset still run. On shutdown (``clear_services=True``) no finished notification + follows, so the explicit service clear is required to dismiss the live card. + """ + # Purge queued live-progress entries and stale start/pre-complete entries + # so a completed cycle cannot replay them later. + live_tag = self._live_notification_tag + self._pending_notifications = [ + entry + for entry in self._pending_notifications + if not ( + ( + entry.get("event_type") == NOTIFY_EVENT_LIVE + and isinstance(entry.get("extra_vars"), dict) + and entry["extra_vars"].get("tag") == live_tag + and entry["extra_vars"].get("live_update") is True + ) + or entry.get("event_type") in {NOTIFY_EVENT_START, "pre_complete"} + ) + ] + + # Always emit the clear when the user has any live channel configured. + # The in-memory sent-count is unreliable after an HA restart (it resets + # to 0 while the notification still lives on the phone), and a no-op + # clear for a non-existent tag is harmless on the mobile_app side. + if not self._notify_live_services and not self._notify_actions: + self._reset_live_notification_state() + return + + # Invoke notification actions to clear live notification in action-based setups + # Include full context variables expected by notification action handlers + self._run_notification_actions( + { + "device": self.config_entry.title, + "program": "", # Cleared marker + "message": "clear_notification", # Clear marker for action handlers + "title": "", # Clear title + "icon": None, + "event_type": NOTIFY_EVENT_LIVE, + "person_entity_id": None, + "person_name": None, + "tag": self._live_notification_tag, + "live_update": True, + "alert_once": True, + } + ) + + if clear_services: + self._send_notification_service( + "clear_notification", + services=self._notify_live_services, + event_type=NOTIFY_EVENT_LIVE, + extra_vars={ + "tag": self._live_notification_tag, + "live_update": True, + "alert_once": True, + }, + ) + + # Reset live-update state flags and counters. + self._reset_live_notification_state() + + def _clear_clean_notification(self) -> None: + """Dismiss a delivered clean-laundry reminder and purge any queued ones. + + The clean nag uses its own tag (``_clean_tag``) rather than the lifecycle + tag, so nothing replaces it once the clean state resolves. Mirror the + lifecycle clear here so a delivered reminder is removed from the mobile + app instead of lingering. A clear for a non-existent tag is harmless, so + this runs whenever the user has any clean/finish delivery configured. + """ + # Drop any still-queued clean entries so they cannot replay later. + self._pending_notifications = [ + n for n in self._pending_notifications + if n.get("event_type") != NOTIFY_EVENT_CLEAN + ] + + services = self._get_services_for_event(NOTIFY_EVENT_CLEAN) + if not services and not self._notify_actions: + return + + if self._notify_actions: + self._run_notification_actions( + { + "device": self.config_entry.title, + "program": "", + "message": "clear_notification", + "title": "", + "icon": None, + "event_type": NOTIFY_EVENT_CLEAN, + "person_entity_id": None, + "person_name": None, + "tag": self._clean_tag, + } + ) + if services: + self._send_notification_service( + "clear_notification", + services=services, + event_type=NOTIFY_EVENT_CLEAN, + extra_vars={"tag": self._clean_tag}, + ) + + def _check_pre_completion_notification(self) -> None: + """Check and send pre-completion notification.""" + if ( + self._notify_before_end_minutes > 0 + and not self._notified_pre_completion + and self._time_remaining is not None + and self._time_remaining <= (self._notify_before_end_minutes * 60) + and self._cycle_progress < 100 + and not self._last_match_ambiguous + ): + # Send notification! + self._notified_pre_completion = True + + # Distinct reminder message (not the live-update template) so the one-time + # "X minutes left" alert is not confused with the recurring live ticks that + # reuse CONF_NOTIFY_PRE_COMPLETE_MESSAGE. + msg_template = self.config_entry.options.get( + CONF_NOTIFY_REMINDER_MESSAGE, DEFAULT_NOTIFY_REMINDER_MESSAGE + ) + minutes_left = self._notify_before_end_minutes + + msg = self._safe_format_template( + msg_template, + fallback_template=DEFAULT_NOTIFY_REMINDER_MESSAGE, + device=self.config_entry.title, + minutes=minutes_left, + program=self._current_program, + ) + self._dispatch_notification( + msg, + event_type="pre_complete", + extra_vars={ + # Share the lifecycle tag so the reminder updates the live thread in + # place. No alert_once -> the companion app makes a sound once; it is + # routed to the finish channel (see _resolve_channel) for audibility. + "tag": self._lifecycle_tag, + "minutes_left": minutes_left, + "minutes": minutes_left, + "priority": "high", + }, + ) + self._logger.info("Sent pre-completion notification: %s", msg) + + def _update_remaining_only(self) -> None: + """Recompute remaining/progress using phase-aware estimation.""" + # Throttle updates and only clear on truly dead states + if self.detector.state in (STATE_OFF, STATE_UNKNOWN, STATE_IDLE): + self._time_remaining = None + self._total_duration = None + self._cycle_progress = 0.0 + self._smoothed_progress = 0.0 + return + + now = dt_util.now() + if ( + self._last_phase_estimate_time + and (now - self._last_phase_estimate_time).total_seconds() < 5.0 + ): + return + self._last_phase_estimate_time = now + + # Use net elapsed (wall-clock minus user-paused time) for all time estimates + # so that paused time is excluded from progress / remaining / total duration. + duration_so_far = float(self.net_elapsed_seconds) + + if self._matched_profile_duration and self._matched_profile_duration > 0: + # Get current power trace for phase analysis + trace = self.detector.get_power_trace() + # current_power_data = [(t.isoformat(), p) for t, p in trace] + # DEPRECATED: avoid O(N) conversion + + # --- PHASE-AWARE ESTIMATION --- + if len(trace) >= 10 and self._current_program != "detecting...": + phase_result = self._estimate_phase_progress( + trace, duration_so_far, self._current_program + ) + if phase_result is not None: + phase_progress, phase_variance = phase_result + + # Smoothing: Exponential Moving Average + # If this is the first reliable estimate, snap to it. + # Otherwise, blend 20% new, 80% old. + if self._smoothed_progress == 0.0: + self._smoothed_progress = phase_progress + else: + current_smoothed = self._smoothed_progress + # Smart Time Prediction (Variance-Based Locking) + # If variance is high (e.g. > 50W std dev), this phase + # is unpredictable. DAMP HEAVILY. + # If variance is low (< 10W), trust the estimate more. + alpha = 0.2 # Default + if phase_variance > 100.0: + alpha = 0.05 # Very slow updates (mostly locked) + self._logger.debug( + "High variance phase (std=%.1fW), " + "locking time estimate (alpha=0.05)", + phase_variance, + ) + elif phase_variance > 50.0: + alpha = 0.1 + + # Monotonicity check: don't let it jump BACKWARD + # significantly unless the profile changed (handled + # elsewhere). Allow small fluctuations, but prevent + # large drops. Use device-type-specific threshold to + # handle different cycle characteristics. + smoothing_threshold = DEVICE_SMOOTHING_THRESHOLDS.get( + self.device_type, 5.0 + ) + if phase_progress < current_smoothed - smoothing_threshold: + # Let's damp it heavily (keep mostly old value). + self._smoothed_progress = (current_smoothed * 0.95) + ( + phase_progress * 0.05 + ) + self._logger.debug( + "Progress drop detected (%.1f%% < %.1f%% - %.1f%%), " + "applying heavy damping for %s", + phase_progress, + current_smoothed, + smoothing_threshold, + self.device_type, + ) + else: + # Normal estimate update with dynamic alpha + self._smoothed_progress = ( + self._smoothed_progress * (1.0 - alpha) + ) + (phase_progress * alpha) + + # Ensure we don't exceed 99% until actually finished + self._smoothed_progress = min(99.0, self._smoothed_progress) + + # Update User-Facing Progress from Smoothed Value + self._cycle_progress = self._smoothed_progress + + # Back-calculate "Time Remaining" from the smoothed progress + # exact_remaining = duration * (1 - progress) + # This prevents "progress says 90% but time says 20 mins" mismatch + remaining = self._matched_profile_duration * ( + 1.0 - (self._cycle_progress / 100.0) + ) + self._time_remaining = max(0.0, remaining) + self._total_duration = duration_so_far + remaining + self._last_total_duration_update = now + + self._logger.debug( + "Phase-aware estimate: raw=%.1f%%, smoothed=%.1f%%, remaining=%smin", + phase_progress, + self._cycle_progress, + int(remaining / 60), + ) + return + + # --- LINEAR FALLBACK (if phase analysis unavailable) --- + matched_dur = float(self._matched_profile_duration) + remaining = max(matched_dur - duration_so_far, 0.0) + progress = (duration_so_far / matched_dur) * 100.0 + + # Blend linear estimate into smoothed tracker too, to prevent + # jumps if we lose phase lock + if self._smoothed_progress > 0: + # Blend gently + self._smoothed_progress = (self._smoothed_progress * 0.9) + ( + progress * 0.1 + ) + else: + self._smoothed_progress = progress + + self._time_remaining = remaining + self._total_duration = duration_so_far + remaining + self._last_total_duration_update = now + self._cycle_progress = max(0.0, min(self._smoothed_progress, 100.0)) + self._logger.debug( + "Linear estimate: remaining=%smin, progress=%.1f%%", + int(remaining / 60), + self._cycle_progress, + ) + else: + # No profile matched - don't provide misleading time estimates + # Just show that we're detecting (no Smart Resume based on history) + self._time_remaining = None + self._total_duration = None + self._cycle_progress = 0.0 + self._smoothed_progress = 0.0 + self._logger.debug( + "No profile matched yet, elapsed=%smin", int(duration_so_far / 60) + ) + + def _estimate_phase_progress( + self, + current_power_data: list[tuple[datetime, float]] | list[tuple[str, float]], + current_duration: float, + profile_name: str, + ) -> tuple[float, float] | None: + """ + Estimate cycle progress by analyzing which phase we're in. + + Uses cached statistical envelope built from ALL cycles labeled with + this profile, normalized by TIME to account for different sampling rates. + + Returns progress percentage (0-100) or None if estimation fails. + """ + # Get cached envelope (fast - already computed and stored) + envelope = self.profile_store.get_envelope(profile_name) + + if envelope is None: + self._logger.debug("No envelope cached for profile %s", profile_name) + return None + + # Convert cached lists back to numpy arrays + # Envelope curves are stored as [[t, y], ...] points, extract Y values only + try: + env_min = envelope.get("min", []) + env_max = envelope.get("max", []) + env_avg = envelope.get("avg", []) + env_std = envelope.get("std", []) + + # Handle both formats: [[t, y], ...] (new) or [y, ...] (legacy) + def extract_y_values(data: list[Any]) -> np.ndarray[Any, np.dtype[np.float64]]: + if not data: + return np.array([], dtype=float) + first = data[0] + if isinstance(first, (list, tuple)): + first_seq = cast(list[Any] | tuple[Any, ...], first) + if len(first_seq) < 2: + return np.array([], dtype=float) + # New format: [[t, y], ...] + points = cast(list[list[Any] | tuple[Any, ...]], data) + return np.array([float(pt[1]) for pt in points], dtype=float) + # Legacy format: [y, ...] + scalars = cast(list[float | int], data) + return np.array(scalars, dtype=float) + + envelope_arrays: dict[str, np.ndarray[Any, np.dtype[np.float64]]] = { + "min": extract_y_values(env_min), + "max": extract_y_values(env_max), + "avg": extract_y_values(env_avg), + "std": extract_y_values(env_std), + } + time_grid: np.ndarray[Any, np.dtype[np.float64]] = np.array( + envelope.get("time_grid", []), dtype=float + ) + target_duration = float(envelope.get("target_duration", 0.0) or 0.0) + except (KeyError, ValueError, TypeError, IndexError) as e: + self._logger.warning("Invalid envelope format for %s: %s", profile_name, e) + return None + + if len(time_grid) == 0 or target_duration <= 0: + if target_duration > 0 and len(envelope_arrays["avg"]) > 0: + # Reconstruct time_grid if missing (Legacy envelope support) + count = len(envelope_arrays["avg"]) + time_grid = np.linspace(0, target_duration, count) + self._logger.debug( + "Reconstructed missing time_grid for %s (n=%d)", + profile_name, + count, + ) + else: + self._logger.debug("Envelope missing time grid/duration, cannot estimate phase") + return None + + # Extract power offsets from current cycle (any format → [offset, power]) + current_offsets_list = power_data_to_offsets( + cast(list[list[Any] | tuple[Any, ...]], current_power_data) + ) + current_offsets = np.array([o for o, _ in current_offsets_list]) + current_values = np.array([p for _, p in current_offsets_list]) + + # Use sliding window on TIME, not sample count + # Look at last ~1 minute of data or 25% of expected duration, whichever is smaller + window_duration = min(60.0, target_duration * 0.25) + current_time = current_offsets[-1] + window_start_time = max(0, current_time - window_duration) + + # Get current window (last N seconds of data) + window_mask = current_offsets >= window_start_time + current_window_values = current_values[window_mask] + + if len(current_window_values) < 3: + self._logger.debug("Insufficient data in current window for phase estimation") + return None + + best_progress: float | None = None + best_score = -1.0 + in_bounds = False + best_time_window_start: float | None = None + + # Search through envelope TIME grid for best matching position + for i in range(len(time_grid) - 1): + time_window_start = float(time_grid[i]) + + # Get envelope values for this time window + envelope_window_start = i + envelope_window_end = min( + i + len(current_window_values), len(envelope_arrays["avg"]) + ) + + if envelope_window_end <= envelope_window_start: + continue + + avg_window = envelope_arrays["avg"][ + envelope_window_start:envelope_window_end + ] + min_window = envelope_arrays["min"][ + envelope_window_start:envelope_window_end + ] + max_window = envelope_arrays["max"][ + envelope_window_start:envelope_window_end + ] + + # Interpolate envelope to match current window length if needed + if len(avg_window) != len(current_window_values): + x_old = np.linspace(0, 1, len(avg_window)) + x_new = np.linspace(0, 1, len(current_window_values)) + avg_window = np.interp(x_new, x_old, avg_window) + min_window = np.interp(x_new, x_old, min_window) + max_window = np.interp(x_new, x_old, max_window) + + # Check if current power is within expected bounds (±20% tolerance) + within_bounds = np.all( + (current_window_values >= min_window * 0.8) + & (current_window_values <= max_window * 1.2) + ) + bounds_score = np.mean( + (current_window_values >= min_window) + & (current_window_values <= max_window) + ) + + # Calculate shape similarity to average + try: + if np.std(current_window_values) > 0 and np.std(avg_window) > 0: + correlation = np.corrcoef(current_window_values, avg_window)[0, 1] + else: + correlation = 0.0 + + # MAE against average + mae = np.mean(np.abs(current_window_values - avg_window)) + max_power = max(np.max(avg_window), np.max(current_window_values), 1.0) + mae_normalized = 1.0 - min(mae / max_power, 1.0) + + # Combined score: shape + amplitude + bounds compliance + score = ( + 0.4 * max(correlation, 0.0) # Shape matching + + 0.3 * mae_normalized # Amplitude matching + + 0.3 * bounds_score # Within expected range + ) + + # Penalize matches that are far from current elapsed time + # (assume linear progress is roughly correct). This prevents + # wild jumps in time remaining when patterns repeat + time_diff = abs(time_window_start - current_duration) + # Max penalty at 30% duration diff + time_penalty = min(1.0, time_diff / (target_duration * 0.3)) + + # Apply time penalty (reduce score by up to 40%) + score = score * (1.0 - 0.4 * time_penalty) + + if score > best_score: + best_score = score + best_progress = (time_window_start / target_duration) * 100.0 + in_bounds = within_bounds + best_time_window_start = float(time_window_start) + except Exception: # pylint: disable=broad-exception-caught + continue + + if best_progress is None or best_score < 0.4: + self._logger.debug("Phase detection failed: best_score=%.3f", best_score) + return None + + # Calculate variance for the best window (Smart Time Prediction) + # Low variance = high confidence in timing. High variance = low confidence. + best_variance = 0.0 + if best_time_window_start is not None: + # Find index in time_grid again (approx) + # Optimization: store best_index in loop? + # Just map time back to index + idx_start = int((best_time_window_start / target_duration) * len(time_grid)) + idx_end = min( + idx_start + len(current_window_values), len(envelope_arrays["std"]) + ) + if idx_end > idx_start: + window_std = envelope_arrays["std"][idx_start:idx_end] + if len(window_std) > 0: + best_variance = float(np.mean(window_std)) + + # Cap progress at 99% until actual completion + best_progress = max(0.0, min(best_progress, 99.0)) + + # Log with envelope metadata + cycle_count = envelope.get("cycle_count", 0) + avg_sample_rates_raw = envelope.get("sampling_rates", [1.0]) + avg_sample_rates = ( + cast(list[float | int], avg_sample_rates_raw) + if isinstance(avg_sample_rates_raw, list) + else [1.0] + ) + avg_sample_rate = ( + float(np.median(np.array(avg_sample_rates, dtype=float))) + if avg_sample_rates + else 1.0 + ) + + tws = ( + best_time_window_start + if best_time_window_start is not None + else float(current_duration) + ) + if not in_bounds: + self._logger.debug( + "Phase detection: progress=%.1f%%, score=%.3f, var=%.1fW, " + "time=%.0f/%.0fs [OUT OF BOUNDS, %s cycles, avg_sample_rate=%.1fs]", + best_progress, + best_score, + best_variance, + tws, + target_duration, + cycle_count, + avg_sample_rate, + ) + else: + self._logger.debug( + "Phase detection: progress=%.1f%%, score=%.3f, var=%.1fW, " + "time=%.0f/%.0fs [IN BOUNDS, %s cycles, avg_sample_rate=%.1fs]", + best_progress, + best_score, + best_variance, + tws, + target_duration, + cycle_count, + avg_sample_rate, + ) + + return (best_progress, best_variance) + + def _notify_update(self) -> None: + """Notify entities of update.""" + async_dispatcher_send(self.hass, SIGNAL_WASHER_UPDATE.format(self.entry_id)) + + def notify_update(self) -> None: + """Public method to notify entities of update.""" + self._notify_update() + + @property + def is_user_paused(self) -> bool: + """Return True if cycle is currently user-paused.""" + return self._is_user_paused + + @property + def is_clean_state(self) -> bool: + """Return True if machine is in Clean state (cycle ended, door not yet opened).""" + return self._is_clean_state + + @property + def net_elapsed_seconds(self) -> float: + """Elapsed seconds in the current cycle, excluding user-paused time.""" + raw = float(self.detector.get_elapsed_seconds()) + paused = self._total_user_paused_seconds + if self._user_pause_start is not None: + paused += (dt_util.now() - self._user_pause_start).total_seconds() + return max(0.0, raw - paused) + + def check_state(self): + """Return current detector state.""" + if self.recorder.is_recording: + return STATE_RUNNING + if self._is_clean_state and self.detector.state == STATE_OFF: + return STATE_CLEAN + if self._is_user_paused: + return STATE_USER_PAUSED + return self.detector.state + + def list_phase_catalog(self, device_type: str) -> list[dict[str, Any]]: + """Return the merged phase catalog for a device type.""" + return self.profile_store.list_phase_catalog(device_type) + + def get_profile_phase_ranges_for_device( + self, + profile_name: str, + device_type: str, + ) -> list[dict[str, Any]]: + """Return phase ranges assigned to a profile for a given device type.""" + return self.profile_store.get_profile_phase_ranges_for_device( + profile_name, + device_type, + ) + + @property + def sub_state(self) -> str | None: + """Return more granular state info (e.g. current phase).""" + if self.recorder.is_recording: + return "Recording" + return self.detector.sub_state + + @property + def current_program(self): + """Return the current program name.""" + return self._current_program + + @property + def time_remaining(self): + """Return estimated time remaining in seconds.""" + return self._time_remaining + + @property + def total_duration(self) -> float | None: + """Return total predicted duration in seconds.""" + return self._total_duration + + @property + def last_total_duration_update(self) -> datetime | None: + """Return when total duration was last refined.""" + return self._last_total_duration_update + + @property + def cycle_progress(self): + """Return cycle progress as a percentage.""" + return self._cycle_progress + + @property + def current_power(self): + """Return current power reading in watts.""" + return self._current_power + + @property + def cycle_start_time(self) -> datetime | None: + """Return the start time of the current cycle.""" + return self.detector.current_cycle_start + + @property + def last_match_details(self) -> dict[str, Any] | None: + """Return details of the last profile match.""" + res = getattr(self, "_last_match_result", None) + return res.to_dict() if res else None + + @property + def samples_recorded(self): + """Return the number of power samples recorded in current cycle.""" + return len(self.detector.get_power_trace()) + + @property + def sample_interval_stats(self): + """Return statistics about sampling intervals.""" + return self._sample_interval_stats + + @property + def pump_stuck(self) -> bool: + """Return True if the pump stuck threshold has fired for the current cycle.""" + return self._pump_stuck + + @property + def pump_runs_today(self) -> int: + """Return the number of completed pump cycles that started in the last 24 hours. + + Counts all past cycles whose ``start_time`` falls within the rolling 24-hour + window ending now. Returns 0 for non-pump device types. + """ + if self.device_type != DEVICE_TYPE_PUMP: + return 0 + cutoff = dt_util.now().timestamp() - 86400.0 + count = 0 + for cycle in self.profile_store.get_past_cycles(): + start_raw = cycle.get("start_time") + if not start_raw: + continue + try: + if isinstance(start_raw, str): + parsed = dt_util.parse_datetime(start_raw) + if parsed is None: + continue + ts = parsed.timestamp() + else: + ts = float(start_raw) + if ts >= cutoff: + count += 1 + except (TypeError, ValueError): + continue + return count + + @property + def cycle_count(self) -> int: + """Return the total number of completed cycles stored for this device.""" + return len(self.profile_store.get_past_cycles()) + + @property + def manual_program_active(self) -> bool: + """Return True if a manual program override is active.""" + return getattr(self, "_manual_program_active", False) + + def set_manual_program(self, profile_name: str) -> None: + """Manually set the current program.""" + if self.detector.state != "running": + return + profiles_raw: Any = None + try: + profiles_raw = self.profile_store.get_profiles() + except Exception: # pylint: disable=broad-exception-caught + profiles_raw = None + + if isinstance(profiles_raw, dict): + profiles: dict[str, Any] = cast(dict[str, Any], profiles_raw) + else: + profiles_fallback = getattr(self.profile_store, "_data", {}).get( + "profiles", {} + ) + profiles = ( + cast(dict[str, Any], profiles_fallback) + if isinstance(profiles_fallback, dict) + else {} + ) + + if profile_name not in profiles: + self._logger.warning("Cannot set manual program: '%s' not found", profile_name) + return + + self._current_program = profile_name + self._manual_program_active = True + + # Update expected duration immediately + profile = profiles.get(profile_name) + if profile: + avg = float(profile.get("avg_duration", 0.0)) + if avg > 0: + self._matched_profile_duration = avg + self._logger.info( + "Manual program set to %s, duration=%.0fs", profile_name, avg + ) + + # Update estimates if running + if self.detector.state == "running": + self._update_estimates() + + async def async_pause_cycle(self) -> None: + """Pause the current cycle (user-triggered). + + Sets verified_pause so the cycle is not finalized when power drops. + Optionally cuts power to the switch entity if CONF_PAUSE_CUTS_POWER is enabled. + """ + if self.detector.state not in (STATE_RUNNING, STATE_STARTING, STATE_PAUSED, STATE_ENDING): + self._logger.debug( + "async_pause_cycle: ignored (detector state=%s)", self.detector.state + ) + return + + if self._is_user_paused: + self._logger.debug("async_pause_cycle: already user-paused, ignoring") + return + + self._logger.info("Cycle paused by user") + prev_verified = self.detector._verified_pause + self._is_user_paused = True + self._user_pause_start = dt_util.now() + self.detector.set_verified_pause(True) + + if self._pause_cuts_power: + switch_entity = self.config_entry.options.get( + CONF_SWITCH_ENTITY + ) or self.config_entry.data.get(CONF_SWITCH_ENTITY) + if switch_entity: + self._logger.info( + "pause_cuts_power: turning off switch %s", switch_entity + ) + try: + await self.hass.services.async_call( + "switch", "turn_off", {"entity_id": switch_entity}, blocking=True + ) + except HomeAssistantError as err: + self._logger.warning( + "pause_cuts_power: failed to turn off %s: %s - rolling back pause state", + switch_entity, err, + ) + self._is_user_paused = False + self._user_pause_start = None + self.detector.set_verified_pause(prev_verified) + return + + snapshot = self.detector.get_state_snapshot() + snapshot["manual_program"] = self._manual_program_active + snapshot["notified_start"] = self._notified_start + snapshot["start_event_fired"] = self._start_event_fired + snapshot["is_user_paused"] = self._is_user_paused + snapshot["user_pause_start"] = ( + self._user_pause_start.isoformat() if self._user_pause_start else None + ) + snapshot["total_user_paused_seconds"] = self._total_user_paused_seconds + self.hass.async_create_task(self.profile_store.async_save_active_cycle(snapshot)) + self._notify_update() + + async def async_resume_cycle(self) -> None: + """Resume a user-paused cycle. + + Accumulates elapsed paused time and clears the verified pause flag. + Optionally restores power via the switch entity if CONF_PAUSE_CUTS_POWER is enabled. + """ + if not self._is_user_paused: + self._logger.debug("async_resume_cycle: not user-paused, ignoring") + return + + now = dt_util.now() + prev_pause_start = self._user_pause_start + accumulated = ( + (now - prev_pause_start).total_seconds() + if prev_pause_start is not None else 0.0 + ) + + self._total_user_paused_seconds += accumulated + self._user_pause_start = None + self._is_user_paused = False + self.detector.set_verified_pause(False) + self._logger.info( + "Cycle resumed by user (total paused: %.0fs)", self._total_user_paused_seconds + ) + + if self._pause_cuts_power: + switch_entity = self.config_entry.options.get( + CONF_SWITCH_ENTITY + ) or self.config_entry.data.get(CONF_SWITCH_ENTITY) + if switch_entity: + self._logger.info( + "pause_cuts_power: turning on switch %s", switch_entity + ) + try: + await self.hass.services.async_call( + "switch", "turn_on", {"entity_id": switch_entity}, blocking=True + ) + except HomeAssistantError as err: + self._logger.warning( + "pause_cuts_power: failed to turn on %s: %s - rolling back resume state", + switch_entity, err, + ) + self._total_user_paused_seconds -= accumulated + self._user_pause_start = prev_pause_start + self._is_user_paused = True + self.detector.set_verified_pause(True) + return + + snapshot = self.detector.get_state_snapshot() + snapshot["manual_program"] = self._manual_program_active + snapshot["notified_start"] = self._notified_start + snapshot["start_event_fired"] = self._start_event_fired + snapshot["is_user_paused"] = self._is_user_paused + snapshot["user_pause_start"] = ( + self._user_pause_start.isoformat() if self._user_pause_start else None + ) + snapshot["total_user_paused_seconds"] = self._total_user_paused_seconds + self.hass.async_create_task(self.profile_store.async_save_active_cycle(snapshot)) + self._notify_update() + + async def async_terminate_cycle(self) -> None: + """Force terminate the current cycle via user request.""" + self._logger.warning("Force terminating cycle by user request") + + # Trigger natural cycle end via detector + # This will call _on_cycle_end callback, which handles: + # - Saving to profile store + # - Clearing active cycle persistence + # - Post-processing/Merging + # - Notifications + self.detector.user_stop() + + # We DO NOT clear manager state manually here (e.g. self._current_program) + # because we want the UI to show the "Clean" state with the just-finished + # program info. The standard reset timers in _on_cycle_end / + # _async_power_changed will handle cleanup after delay. + + # Force a state update to reflect the change immediately + self._notify_update() + + async def async_start_recording(self) -> None: + """Start manual recording of a cycle.""" + if self.recorder.is_recording: + self._logger.warning("Already recording") + return + + # Ensure we are in a clean state (stop any running cycle first?) + # If running, user should probably stop it? Or force stop? + # Plan said "unregulated", so we just start recording. + # But if cycle_detector thinks it's running, we should probably "pause" it + # or just override state. My override in checks_state handles UI. + # But should we clear current program? + if self.detector.state != "off": + self._logger.info("Forcing detector reset before recording") + self.detector.reset() + + await self.recorder.start_recording() + self._notify_update() + + async def async_stop_recording(self) -> None: + """Stop manual recording.""" + if not self.recorder.is_recording: + return + + await self.recorder.stop_recording() + self._notify_update() + + def clear_manual_program(self) -> None: + """Clear manual program override.""" + if not self._manual_program_active: + return + + self._manual_program_active = False + # If running, revert to detecting so auto-detection can resume? + if self.detector.state == "running": + self._current_program = "detecting..." + self._matched_profile_duration = None + self._update_estimates() # Trigger immediate re-detection attempt + else: + # If not running, clear the forced program + self._current_program = "off" + self._matched_profile_duration = None + + self._notify_update() + self._logger.info("Manual program cleared, reverting to auto-detection") + + async def _run_post_cycle_processing(self) -> None: + """Run post-cycle processing (merge fragments, split anomalies).""" + try: + # User Feedback: Use 5 hour lookback and configured gap settings + stats = await self.profile_store.async_run_maintenance() + + # Log significant actions + merged = stats.get("merged_cycles", 0) + split = stats.get("split_cycles", 0) + if merged > 0 or split > 0: + self._logger.info( + "Post-cycle processing: Merged %s, Split %s cycle(s)", merged, split + ) + + # Note: async_run_maintenance saves automatically if changes occur + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Post-cycle processing failed: %s", e) \ No newline at end of file diff --git a/custom_components/ha_washdata/manifest.json b/custom_components/ha_washdata/manifest.json new file mode 100644 index 0000000..15ca9ad --- /dev/null +++ b/custom_components/ha_washdata/manifest.json @@ -0,0 +1,20 @@ +{ + "domain": "ha_washdata", + "name": "WashData", + "after_dependencies": [ + "lovelace", + "http" + ], + "codeowners": [ + "@3dg1luk43" + ], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/3dg1luk43/ha_washdata", + "iot_class": "local_polling", + "issue_tracker": "https://github.com/3dg1luk43/ha_washdata/issues", + "requirements": [ + "numpy" + ], + "version": "0.4.5.1" +} \ No newline at end of file diff --git a/custom_components/ha_washdata/phase_assignment.py b/custom_components/ha_washdata/phase_assignment.py new file mode 100644 index 0000000..783e91e --- /dev/null +++ b/custom_components/ha_washdata/phase_assignment.py @@ -0,0 +1,50 @@ +"""Helpers for phase range assignment and timestamp conversion.""" + +from __future__ import annotations + +from datetime import datetime + +from homeassistant.util import dt as dt_util + + +def parse_phase_timestamp(value: str, cycle_start_dt: datetime) -> datetime | None: + """Parse timestamp text used in phase assignment. + + Supported formats: + - Full parseable datetime (via Home Assistant parser) + - YYYY-MM-DD HH:MM + - YYYY-MM-DD HH:MM:SS + - HH:MM (on cycle start date) + - HH:MM:SS (on cycle start date) + """ + text = str(value or "").strip() + if not text: + return None + + parsed = dt_util.parse_datetime(text) + if parsed is not None: + if parsed.tzinfo is None: + parsed = parsed.replace(tzinfo=cycle_start_dt.tzinfo) + return parsed + + for fmt in ("%Y-%m-%d %H:%M", "%Y-%m-%d %H:%M:%S"): + try: + dt_val = datetime.strptime(text, fmt) + return dt_val.replace(tzinfo=cycle_start_dt.tzinfo) + except ValueError: + continue + + for fmt in ("%H:%M", "%H:%M:%S"): + try: + t_val = datetime.strptime(text, fmt) + base = dt_util.as_local(cycle_start_dt) + return base.replace( + hour=t_val.hour, + minute=t_val.minute, + second=t_val.second, + microsecond=0, + ) + except ValueError: + continue + + return None diff --git a/custom_components/ha_washdata/phase_catalog.py b/custom_components/ha_washdata/phase_catalog.py new file mode 100644 index 0000000..e7a623a --- /dev/null +++ b/custom_components/ha_washdata/phase_catalog.py @@ -0,0 +1,512 @@ +"""Phase catalog defaults and helpers for WashData.""" + +from __future__ import annotations + +import re +from copy import deepcopy +from typing import Any + +from .const import ( + DEVICE_TYPE_AIR_FRYER, + DEVICE_TYPE_BREAD_MAKER, + DEVICE_TYPE_COFFEE_MACHINE, + DEVICE_TYPE_DISHWASHER, + DEVICE_TYPE_DRYER, + DEVICE_TYPE_EV, + DEVICE_TYPE_HEAT_PUMP, + DEVICE_TYPE_OVEN, + DEVICE_TYPE_WASHER_DRYER, + DEVICE_TYPE_WASHING_MACHINE, +) + +PhaseItem = dict[str, Any] + + +def _builtin_phase_id(device_type: str, name: str) -> str: + """Return a stable ID like 'washing_machine.pre_wash' for a built-in phase.""" + slug = re.sub(r"[^a-z0-9]+", "_", name.strip().lower()).strip("_") + return f"{device_type}.{slug}" + +DEFAULT_PHASES_BY_DEVICE: dict[str, list[PhaseItem]] = { + DEVICE_TYPE_WASHING_MACHINE: [ + { + "name": "Pre-Wash", + "description": "Initial soak or pre-treatment before the main wash.", + "is_default": True, + }, + { + "name": "Wash", + "description": "Main washing cycle with drum movement and optional heating.", + "is_default": True, + }, + { + "name": "Rinse", + "description": "Clean-water rinse stage. This phase may repeat multiple times.", + "is_default": True, + }, + { + "name": "Spin", + "description": "High-speed extraction to remove water from the load.", + "is_default": True, + }, + { + "name": "Soak", + "description": "Low-activity soaking period between active wash stages.", + "is_default": True, + }, + { + "name": "Anti-Crease", + "description": "Occasional short tumbles after completion to reduce wrinkles.", + "is_default": True, + }, + ], + DEVICE_TYPE_DRYER: [ + { + "name": "Heat Up", + "description": "Initial heater warm-up before full drying begins.", + "is_default": True, + }, + { + "name": "Drying", + "description": "Main heated tumbling period.", + "is_default": True, + }, + { + "name": "Cool Down", + "description": "Tumbling without heat near cycle end.", + "is_default": True, + }, + { + "name": "Anti-Wrinkle", + "description": "Periodic post-cycle tumbling to reduce wrinkles.", + "is_default": True, + }, + { + "name": "Sensor Check", + "description": "Short low-power pause while dryness is measured.", + "is_default": True, + }, + ], + DEVICE_TYPE_WASHER_DRYER: [ + { + "name": "Pre-Wash", + "description": "Initial soak or pre-treatment before the main wash.", + "is_default": True, + }, + { + "name": "Wash", + "description": "Main washing cycle with drum movement and optional heating.", + "is_default": True, + }, + { + "name": "Rinse", + "description": "Clean-water rinse stage. This phase may repeat multiple times.", + "is_default": True, + }, + { + "name": "Spin", + "description": "High-speed extraction before drying transition.", + "is_default": True, + }, + { + "name": "Drain & Switch", + "description": "Transition period from washing to drying mode.", + "is_default": True, + }, + { + "name": "Heat Up", + "description": "Initial heater warm-up before full drying begins.", + "is_default": True, + }, + { + "name": "Drying", + "description": "Main heated tumbling period.", + "is_default": True, + }, + { + "name": "Cool Down", + "description": "Tumbling without heat near cycle end.", + "is_default": True, + }, + { + "name": "Anti-Wrinkle", + "description": "Periodic post-cycle tumbling to reduce wrinkles.", + "is_default": True, + }, + ], + DEVICE_TYPE_DISHWASHER: [ + { + "name": "Pre-Rinse", + "description": "Initial spray-down before detergent wash.", + "is_default": True, + }, + { + "name": "Wash", + "description": "Main detergent wash with heating.", + "is_default": True, + }, + { + "name": "Rinse", + "description": "Clean-water rinse stage. This phase may repeat multiple times.", + "is_default": True, + }, + { + "name": "Dry", + "description": "Drying stage using heater and/or residual heat.", + "is_default": True, + }, + { + "name": "Sanitize", + "description": "High-temperature cleaning stage for sanitization programs.", + "is_default": True, + }, + { + "name": "Soak", + "description": "Extended soak period for heavy soil.", + "is_default": True, + }, + ], + DEVICE_TYPE_COFFEE_MACHINE: [ + { + "name": "Heat Up", + "description": "Boiler heating to reach operating temperature.", + "is_default": True, + }, + { + "name": "Brewing", + "description": "Water pumping through coffee grounds.", + "is_default": True, + }, + { + "name": "Keep Warm", + "description": "Maintaining temperature after brew completion.", + "is_default": True, + }, + { + "name": "Grinding", + "description": "Bean grinding stage on machines with integrated grinder.", + "is_default": True, + }, + { + "name": "Steaming", + "description": "Steam generation for milk frothing.", + "is_default": True, + }, + { + "name": "Idle", + "description": "Ready/standby period with low power use.", + "is_default": True, + }, + ], + DEVICE_TYPE_EV: [ + { + "name": "Initialization", + "description": "Vehicle and charger handshake before power transfer.", + "is_default": True, + }, + { + "name": "Charging", + "description": "Main charging period at available power.", + "is_default": True, + }, + { + "name": "Taper", + "description": "Reduced charging rate near high state of charge.", + "is_default": True, + }, + { + "name": "Maintenance", + "description": "Battery balancing or conditioning activity.", + "is_default": True, + }, + { + "name": "Complete", + "description": "Charge complete with minimal top-up activity.", + "is_default": True, + }, + { + "name": "Pre-Conditioning", + "description": "Battery temperature conditioning before or during charge.", + "is_default": True, + }, + ], + DEVICE_TYPE_AIR_FRYER: [ + { + "name": "Pre-Heat", + "description": "Initial chamber heating before full cooking.", + "is_default": True, + }, + { + "name": "Cooking", + "description": "Main cooking phase with active heater and fan.", + "is_default": True, + }, + { + "name": "Pause", + "description": "Short pause for shaking or inspection.", + "is_default": True, + }, + { + "name": "Cool Down", + "description": "Fan-only cool-down stage after heating.", + "is_default": True, + }, + { + "name": "Keep Warm", + "description": "Low-heat holding stage to keep food warm.", + "is_default": True, + }, + ], + DEVICE_TYPE_HEAT_PUMP: [ + { + "name": "Start-Up", + "description": "Compressor and system stabilization at cycle start.", + "is_default": True, + }, + { + "name": "Heating", + "description": "Active heating operation.", + "is_default": True, + }, + { + "name": "Cooling", + "description": "Active cooling operation.", + "is_default": True, + }, + { + "name": "Defrost", + "description": "Defrost routine to clear outdoor coil ice.", + "is_default": True, + }, + { + "name": "Standby", + "description": "Low-activity temperature holding period.", + "is_default": True, + }, + { + "name": "Fan Only", + "description": "Air circulation without compressor heating/cooling.", + "is_default": True, + }, + { + "name": "Boost", + "description": "High-output operation for rapid temperature change.", + "is_default": True, + }, + ], + DEVICE_TYPE_BREAD_MAKER: [ + { + "name": "Kneading", + "description": "Motor-driven dough mixing and development. High power draw.", + "is_default": True, + }, + { + "name": "Resting", + "description": "Short low-power pause between kneading stages for gluten relaxation.", + "is_default": True, + }, + { + "name": "Proving", + "description": "Low-heat rising period to allow yeast fermentation and dough expansion.", + "is_default": True, + }, + { + "name": "Baking", + "description": "High-temperature heating element active for crust and crumb formation.", + "is_default": True, + }, + { + "name": "Keep Warm", + "description": "Low-heat holding stage to keep the loaf warm after baking.", + "is_default": True, + }, + ], + DEVICE_TYPE_OVEN: [ + { + "name": "Pre-Heat", + "description": "Heating element runs continuously to bring the cavity up to the target temperature.", + "is_default": True, + }, + { + "name": "Heating", + "description": "Active heater bursts during cooking when the thermostat calls for heat.", + "is_default": True, + }, + { + "name": "Maintaining Temp", + "description": "Thermostat-regulated holding period: heater cycles on and off to keep the set temperature.", + "is_default": True, + }, + { + "name": "Cool Down", + "description": "Heater off after the cycle ends; residual heat dissipates and the cooling fan may continue to run.", + "is_default": True, + }, + { + "name": "Pyrolytic Clean", + "description": "High-temperature self-clean phase that burns off residue. Optional and only active during pyrolytic programs.", + "is_default": True, + }, + ], +} + + +def normalize_phase_name(name: str) -> str: + """Normalize and validate phase names.""" + normalized = " ".join(name.strip().split()) + if not normalized: + raise ValueError("invalid_phase_name") + if len(normalized) > 48: + raise ValueError("phase_name_too_long") + return normalized + + +def get_default_phase_catalog(device_type: str) -> list[PhaseItem]: + """Return default phase catalog for a device type, with id and device_type injected.""" + phases = deepcopy(DEFAULT_PHASES_BY_DEVICE.get(device_type, [])) + for phase in phases: + phase["id"] = _builtin_phase_id(device_type, str(phase.get("name", ""))) + phase["device_type"] = device_type + return phases + + +def get_shared_default_phase_catalog() -> list[PhaseItem]: + """Return a shared default catalog deduplicated across all device types.""" + merged: list[PhaseItem] = [] + seen: set[str] = set() + for device_type, device_phases in DEFAULT_PHASES_BY_DEVICE.items(): + for item in device_phases: + name = str(item.get("name", "")).strip() + if not name: + continue + key = name.casefold() + if key in seen: + continue + seen.add(key) + merged.append( + { + "id": _builtin_phase_id(device_type, name), + "device_type": device_type, + "name": name, + "description": str(item.get("description", "")).strip(), + "is_default": True, + } + ) + return merged + + +def get_builtin_phase_by_id(phase_id: str) -> PhaseItem | None: + """Return a copy of the built-in phase with the given id, or None.""" + for device_type, device_phases in DEFAULT_PHASES_BY_DEVICE.items(): + for item in device_phases: + name = str(item.get("name", "")).strip() + if _builtin_phase_id(device_type, name) == phase_id: + result = deepcopy(item) + result["id"] = phase_id + result["device_type"] = device_type + return result + return None + + +def merge_phase_catalog(device_type: str, custom_phases: list[PhaseItem] | None) -> list[PhaseItem]: + """Merge device defaults with custom phases. Uses 'id' as the primary collision key.""" + merged = ( + get_default_phase_catalog(device_type) + if device_type in DEFAULT_PHASES_BY_DEVICE + else get_shared_default_phase_catalog() + ) + + # Index built-ins by id and by (device_type, name) for the name-based fallback. + builtin_by_id: dict[str, int] = {} + builtin_by_name: dict[tuple[str, str], int] = {} + for idx, item in enumerate(merged): + item_id = str(item.get("id", "")) + if item_id: + builtin_by_id[item_id] = idx + item_dt = str(item.get("device_type", "")).casefold() + item_name = str(item.get("name", "")).strip().casefold() + if item_name: + builtin_by_name[(item_dt, item_name)] = idx + + seen_ids: set[str] = set(builtin_by_id.keys()) + seen_names: set[tuple[str, str]] = set(builtin_by_name.keys()) + + # All known built-in names - used to guard against polluting unrelated catalogs. + all_builtin_names = { + str(p.get("name", "")).strip().casefold() + for phases_list in DEFAULT_PHASES_BY_DEVICE.values() + for p in phases_list + } + + for item in (custom_phases or []): + try: + normalized_name = normalize_phase_name(str(item.get("name", ""))) + except ValueError: + continue + if not normalized_name: + continue + + item_device_type = str(item.get("device_type", "")).strip() + # Skip if this custom phase targets a different specific device type. + if item_device_type: + if item_device_type.casefold() != str(device_type or "").strip().casefold(): + continue + + phase_id = str(item.get("id", "")).strip() + + # Primary: id-based in-place replacement of a built-in entry. + if phase_id and phase_id in builtin_by_id: + idx = builtin_by_id[phase_id] + original_device_type = str(merged[idx].get("device_type", item_device_type)) + merged[idx] = { + "id": phase_id, + "device_type": original_device_type, + "name": normalized_name, + "description": str(item.get("description", "")).strip(), + "is_default": False, + } + continue + + # Fallback: name-based match for old data without ids. + name_key = (item_device_type.casefold(), normalized_name.casefold()) + if name_key in builtin_by_name: + idx = builtin_by_name[name_key] + new_desc = str(item.get("description", "")).strip() + if new_desc: + merged[idx]["description"] = new_desc + merged[idx]["is_default"] = False + continue + + # New phase: guard against universal overrides leaking into unrelated catalogs. + # For legacy items with no device_type, first try matching against the active + # catalog device_type before discarding, so legacy overrides are preserved. + if not item_device_type and normalized_name.casefold() in all_builtin_names: + active_dt_key = (str(device_type or "").strip().casefold(), normalized_name.casefold()) + if active_dt_key in builtin_by_name: + idx = builtin_by_name[active_dt_key] + new_desc = str(item.get("description", "")).strip() + if new_desc: + merged[idx]["description"] = new_desc + merged[idx]["is_default"] = False + continue + + # Deduplicate before appending. + if phase_id and phase_id in seen_ids: + continue + append_name_key = (item_device_type.casefold(), normalized_name.casefold()) + if append_name_key in seen_names: + continue + + new_phase: PhaseItem = { + "name": normalized_name, + "description": str(item.get("description", "")).strip(), + "device_type": item_device_type, + "is_default": False, + } + if phase_id: + new_phase["id"] = phase_id + seen_ids.add(phase_id) + seen_names.add(append_name_key) + merged.append(new_phase) + + return [p for p in merged if p.get("name")] diff --git a/custom_components/ha_washdata/profile_store.py b/custom_components/ha_washdata/profile_store.py new file mode 100644 index 0000000..f4c3a78 --- /dev/null +++ b/custom_components/ha_washdata/profile_store.py @@ -0,0 +1,4316 @@ +"""Profile storage and matching logic for WashData.""" + +from __future__ import annotations + +import dataclasses +import hashlib +import html +import logging +import os +import re +import uuid +from datetime import datetime, timedelta +from typing import Any, TypeAlias, cast +import json + +import numpy as np + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.storage import Store +from homeassistant.util import dt as dt_util + +from .const import ( + STORAGE_KEY, + STORAGE_VERSION, + DEFAULT_MAX_PAST_CYCLES, + DEFAULT_MAX_FULL_TRACES_PER_PROFILE, + DEFAULT_MAX_FULL_TRACES_UNLABELED, + DEFAULT_DTW_BANDWIDTH, +) +from .features import compute_signature +from .signal_processing import resample_uniform, resample_adaptive, Segment +from . import analysis +from .time_utils import ( + migrate_power_data_to_offsets, + power_data_to_offsets, +) +from .phase_catalog import ( + DEFAULT_PHASES_BY_DEVICE, + _builtin_phase_id, + get_builtin_phase_by_id, + merge_phase_catalog, + normalize_phase_name, +) +from .log_utils import DeviceLoggerAdapter + +_LOGGER = logging.getLogger(__name__) + +JSONDict: TypeAlias = dict[str, Any] +CycleDict: TypeAlias = dict[str, Any] + + +def _empty_ranking() -> list[dict[str, Any]]: + """Typed default factory for ranking entries.""" + return [] + + +def _parse_start_dt(value: Any) -> datetime | None: + """Parse a start_time value (ISO string or numeric timestamp) into a datetime. + + Handles the case where legacy cycles stored numeric unix timestamps instead + of ISO-formatted strings, which dt_util.parse_datetime cannot handle. + """ + if isinstance(value, datetime): + return value + if isinstance(value, (int, float)) and not isinstance(value, bool): + try: + return datetime.fromtimestamp(float(value), tz=dt_util.UTC) + except (OSError, OverflowError, ValueError): + return None + if isinstance(value, str) and value: + parsed = dt_util.parse_datetime(value) + if parsed is not None: + return parsed + try: + return datetime.fromtimestamp(float(value), tz=dt_util.UTC) + except (TypeError, ValueError): + return None + return None + + +def _empty_debug_details() -> dict[str, Any]: + """Typed default factory for debug details.""" + return {} + + +def _value_to_timestamp(value: Any) -> float | None: + """Parse supported datetime-like values into unix seconds.""" + if isinstance(value, datetime): + return value.timestamp() + if isinstance(value, (int, float)): + return float(value) + if isinstance(value, str) and value: + parsed = dt_util.parse_datetime(value) + if parsed is not None: + return parsed.timestamp() + try: + return float(value) + except (TypeError, ValueError): + return None + return None + + +def profile_sort_key(name: str) -> tuple[int, int, str]: + """Sort key for profile names: numeric-prefixed first (by number), then alphabetically.""" + match = re.match(r'^(\d+)', name) + if match: + return (0, int(match.group(1)), name) + return (1, 0, name) + + + + +def trim_zero_power_data( + data: list[list[float]], + threshold: float = 0.5 +) -> list[list[float]]: + """Trim leading/trailing zero/near-zero power readings from stored data. + + Args: + data: List of [offset, power] pairs + threshold: Power values <= this are considered "zero" + + Returns: + Trimmed list with leading/trailing zeros removed + """ + if not data: + return data + + # Find first non-zero reading + start_idx = 0 + for i, point in enumerate(data): + if point[1] > threshold: + start_idx = i + break + else: + # All readings are zero - keep at least one + return data[:1] if data else [] + + # Find last non-zero reading + end_idx = len(data) - 1 + for i in range(len(data) - 1, -1, -1): + if data[i][1] > threshold: + end_idx = i + break + + # Return trimmed slice (inclusive of end) + return data[start_idx:end_idx + 1] + + +def filter_duration_outliers(durations: list[float]) -> list[float]: + """Return a robust duration set with extreme outliers removed. + + Uses Tukey IQR fences for normal spread and falls back to a MAD-based + filter when IQR collapses (common when most cycles are identical). + """ + if len(durations) < 4: + return durations + + arr = np.array(durations, dtype=float) + + q1 = float(np.percentile(arr, 25)) + q3 = float(np.percentile(arr, 75)) + iqr = q3 - q1 + + if iqr > 0: + lower = max(60.0, q1 - 1.5 * iqr) + upper = q3 + 1.5 * iqr + filtered = arr[(arr >= lower) & (arr <= upper)] + else: + # Degenerate spread (e.g. many identical durations); keep values + # close to median and drop only extreme anomalies. + median = float(np.median(arr)) + abs_dev = np.abs(arr - median) + mad = float(np.median(abs_dev)) + if mad == 0: + tol = max(300.0, median * 0.15) + filtered = arr[np.abs(arr - median) <= tol] + else: + robust_z = abs_dev / (1.4826 * mad) + filtered = arr[robust_z <= 3.5] + + # Guardrail: do not over-filter sparse datasets. + if len(filtered) >= max(3, int(len(arr) * 0.6)): + return filtered.astype(float).tolist() + + return durations + + +@dataclasses.dataclass +class SVGCurve: + """Definition for a curve in the SVG chart.""" + points: list[tuple[float, float]] # (x, y) + color: str + opacity: float = 1.0 + stroke_width: int = 2 + dasharray: str | None = None + is_polygon: bool = False + + +def _generate_generic_svg( + title: str, + curves: list[SVGCurve], + width: int = 800, + height: int = 400, + max_x_override: float | None = None, + max_y_override: float | None = None, + markers: list[dict[str, Any]] | None = None, # {x, label, color} +) -> str: + """Generate a generic time-series SVG chart.""" + if not curves: + return "" + + padding_x = 50 + padding_y = 40 + graph_w = width - 2 * padding_x + graph_h = height - 2 * padding_y + + # Determine bounds + all_x = [p[0] for c in curves for p in c.points] + all_y = [p[1] for c in curves for p in c.points] + + if not all_x: + return "" + + max_x = max_x_override if max_x_override is not None else max(all_x) + max_y = max_y_override if max_y_override is not None else max(all_y, default=1.0) + + # Headroom + max_y = max(max_y, 10.0) * 1.05 + max_x = max(max_x, 1.0) # Ensure no div by zero + + def to_x(t: float) -> float: + return padding_x + (t / max_x) * graph_w + + def to_y(p: float) -> float: + return height - padding_y - (p / max_y) * graph_h + + # Build Paths + paths: list[str] = [] + for c in curves: + if not c.points: + continue + + pts: list[str] = [] + # Optimization: verify step size if huge data + for x_val, y_val in c.points: + pts.append(f"{to_x(x_val):.1f},{to_y(y_val):.1f}") + + path_d = " ".join(pts) + if c.is_polygon: + style = f'fill="{c.color}" fill-opacity="{c.opacity}" stroke="none"' + paths.append(f'') + else: + style = f'stroke="{c.color}" stroke-width="{c.stroke_width}" stroke-opacity="{c.opacity}" fill="none"' + if c.dasharray: + style += f' stroke-dasharray="{c.dasharray}"' + paths.append(f'') + + # Build Markers + marker_svgs: list[str] = [] + if markers: + for m in markers: + mx = m["x"] + if 0 <= mx <= max_x: + screen_x = to_x(mx) + color = m.get("color", "#aaa") + label = m.get("label", "") + marker_svgs.append( + f'' + ) + if label: + marker_svgs.append( + f'{label}' + ) + + # Grid & Axes (border + mid lines) + grid = f""" + + + + {int(max_y)}W + {int(max_x)}s + {title} + """ + + header = ( + f'' + ) + + return header + grid + "".join(paths) + "".join(marker_svgs) + "" + + + +@dataclasses.dataclass +class MatchResult: + """Result of a profile matching attempt.""" + + best_profile: str | None + confidence: float + expected_duration: float + matched_phase: str | None + candidates: list[dict[str, Any]] + is_ambiguous: bool + ambiguity_margin: float + ranking: list[dict[str, Any]] = dataclasses.field(default_factory=_empty_ranking) + debug_details: dict[str, Any] = dataclasses.field(default_factory=_empty_debug_details) + is_confident_mismatch: bool = False + mismatch_reason: str | None = None + + def to_dict(self) -> dict[str, Any]: + """Convert to dictionary with JSON-serializable types, excluding heavy arrays.""" + def _convert(obj: Any) -> Any: + if isinstance(obj, np.generic): + return cast(np.generic, obj).item() + if isinstance(obj, np.ndarray): + # Fallback for unexpected arrays: just describe shape + arr = cast(np.ndarray[Any, Any], obj) + return f"" + if isinstance(obj, dict): + # Exclude huge raw data arrays from cycle candidates + obj_dict = cast(dict[str, Any], obj) + return { + k: _convert(v) + for k, v in obj_dict.items() + if k not in ("current", "sample", "metrics", "warping_path") + } + if isinstance(obj, list): + obj_list = cast(list[Any], obj) + return [_convert(v) for v in obj_list] + if dataclasses.is_dataclass(obj): + return text_type_safe_asdict(obj) + return obj + + def text_type_safe_asdict(d_obj: Any) -> dict[str, Any]: + return {f.name: _convert(getattr(d_obj, f.name)) for f in dataclasses.fields(d_obj)} + + return text_type_safe_asdict(self) + + + +def decompress_power_data(cycle: CycleDict) -> list[tuple[float, float]]: + """Return power data as ``[(offset_seconds, power), ...]`` for a cycle. + + Handles both the current canonical ``[offset_float, power]`` format and the + legacy ``(iso_str, power)`` format transparently. Returns an empty list if + data is missing or malformed. + """ + raw = cycle.get("power_data", []) + if not isinstance(raw, list) or not raw: + return [] + + start_time_raw = cycle.get("start_time") + start_time_iso: str | None = start_time_raw if isinstance(start_time_raw, str) and start_time_raw else None + if isinstance(start_time_raw, datetime): + start_time_iso = start_time_raw.isoformat() + offsets = power_data_to_offsets(cast(list[list[Any] | tuple[Any, ...]], raw), start_time_iso) + return [(float(o), float(p)) for o, p in offsets] + + +def compress_power_data(cycle: CycleDict) -> list[Any] | None: + """Compress cycle power data to [offset, power] format (Module-level helper). + + Returns the compressed list structure or None if compression failed/not needed. + """ + raw_data_raw = cycle.get("power_data") + if not isinstance(raw_data_raw, list) or not raw_data_raw: + return None + raw_data = cast(list[Any], raw_data_raw) + + # Check if already compressed (first element is number or mixed format) + first = raw_data[0] + if isinstance(first, (int, float)): + # Already flat list (very old format?) or specific compression + return None + if isinstance(first, (list, tuple)): + first_seq = cast(list[Any] | tuple[Any, ...], first) + if len(first_seq) == 2 and isinstance(first_seq[0], (int, float)): + # Already compressed [offset, power] + return None + + # Proceed with compression from [iso_string, power] + if "start_time" not in cycle: + return None + + try: + start_ts = _value_to_timestamp(cycle.get("start_time")) + if start_ts is None: + return None + compressed: list[list[float]] = [] + + last_saved_p = -999.0 + last_saved_t = -999.0 + + for i, entry in enumerate(raw_data): + if isinstance(entry, (list, tuple)): + entry_seq = cast(list[Any] | tuple[Any, ...], entry) + if len(entry_seq) != 2: + continue + t_str, p_val_raw = entry_seq + try: + # Handle both ISO string and potential timestamp float + t = _value_to_timestamp(t_str) + if t is None: + continue + + p_val = float(p_val_raw) + offset = round(t - start_ts, 1) + if offset < 0: + offset = 0.0 + + # Save first and last + is_endpoint = i == 0 or i == len(raw_data) - 1 + + # Downsample: change > 1W or gap > 60s + if ( + is_endpoint + or abs(p_val - last_saved_p) > 1.0 + or (offset - last_saved_t) > 60 + ): + compressed.append([offset, round(p_val, 1)]) + last_saved_p = p_val + last_saved_t = offset + + except (ValueError, TypeError): + continue + return compressed + except Exception: + return None + + +class WashDataStore(Store[JSONDict]): + """Store implementation with migration support.""" + + async def _async_migrate_func( + self, + old_major_version: int, + old_minor_version: int, # pylint: disable=unused-argument + old_data: JSONDict, + ) -> JSONDict: + """Migrate data to the new version.""" + if old_major_version < 2: + _LOGGER.info("Migrating storage from v%s to v2", old_major_version) + # Logic moved from ProfileStore._migrate_v1_to_v2 + cycles_raw = old_data.get("past_cycles", []) + cycles = cast(list[dict[str, Any]], cycles_raw) if isinstance(cycles_raw, list) else [] + migrated_cycles = 0 + for cycle in cycles: + if "signature" not in cycle and cycle.get("power_data"): + try: + # decompress_power_data now returns [(offset_seconds, power), ...] + tuples = decompress_power_data(cycle) + if tuples and len(tuples) > 10: + ts_arr = np.array([t for t, _ in tuples]) + p_arr = np.array([p for _, p in tuples]) + sig = compute_signature(ts_arr, p_arr) + cycle["signature"] = dataclasses.asdict(sig) + migrated_cycles += 1 + except Exception as e: # pylint: disable=broad-exception-caught + _LOGGER.warning( + "Failed to migrate signature for cycle %s: %s", cycle.get("id"), e + ) + + _LOGGER.info( + "Migration v1->v2: Computed signatures for %s cycles", migrated_cycles + ) + + if old_major_version < 3: + _LOGGER.info("Migrating storage from v%s to v3", old_major_version) + cycles_raw = old_data.get("past_cycles", []) + profiles_raw = old_data.get("profiles", {}) + cycles = cast(list[dict[str, Any]], cycles_raw) if isinstance(cycles_raw, list) else [] + profiles = cast(dict[str, dict[str, Any]], profiles_raw) if isinstance(profiles_raw, dict) else {} + migrated_count = 0 + + # 1. Migrate Power Data to canonical offset format & ensure status + for cycle in cycles: + if "status" not in cycle: + cycle["status"] = "completed" + + if cycle.get("power_data") and isinstance(cycle["power_data"], list): + try: + if migrate_power_data_to_offsets(cycle): + migrated_count += 1 + except Exception as e: + _LOGGER.warning( + "Failed to migrate power data for cycle %s: %s", + cycle.get("id"), + e, + ) + + # 2. Ensure Device Type in Profiles + for profile in profiles.values(): + if "device_type" not in profile: + profile["device_type"] = "washing_machine" + + _LOGGER.info( + "Migration v2->v3: Migrated power data for %s cycles", migrated_count + ) + + if old_major_version < 4: + _LOGGER.info("Migrating storage from v%s to v4", old_major_version) + profiles = old_data.get("profiles", {}) + if isinstance(profiles, dict): + for profile in cast(dict[str, dict[str, Any]], profiles).values(): + phases = profile.get("phases") + if not isinstance(phases, list): + profile["phases"] = [] + + custom = old_data.get("custom_phases") + if not isinstance(custom, dict): + old_data["custom_phases"] = {} + + if old_major_version < 5: + _LOGGER.info("Migrating storage from v%s to v5", old_major_version) + custom = old_data.get("custom_phases") + if isinstance(custom, list): + normalized: list[dict[str, Any]] = [] + seen: set[tuple[str, str]] = set() + for item in cast(list[Any], custom): + if not isinstance(item, dict): + continue + item_dict = cast(dict[str, Any], item) + name = str(item_dict.get("name", "")).strip() + if not name: + continue + device_type = str(item_dict.get("device_type", "")).strip() + key = (name.casefold(), device_type.casefold()) + if key in seen: + continue + seen.add(key) + normalized.append( + { + "name": name, + "description": str(item_dict.get("description", "")).strip(), + "device_type": device_type, + "created_at": item_dict.get("created_at") or dt_util.now().isoformat(), + } + ) + old_data["custom_phases"] = normalized + elif isinstance(custom, dict): + normalized: list[dict[str, Any]] = [] + seen: set[tuple[str, str]] = set() + custom_dict = cast(dict[str, Any], custom) + for legacy_device_type, phase_list in custom_dict.items(): + if not isinstance(phase_list, list): + continue + for item in cast(list[Any], phase_list): + if not isinstance(item, dict): + continue + item_dict = cast(dict[str, Any], item) + name = str(item_dict.get("name", "")).strip() + if not name: + continue + device_type = str(legacy_device_type or "").strip() + key = (name.casefold(), device_type.casefold()) + if key in seen: + continue + seen.add(key) + normalized.append( + { + "name": name, + "description": str(item_dict.get("description", "")).strip(), + "device_type": device_type, + "created_at": item_dict.get("created_at") or dt_util.now().isoformat(), + } + ) + old_data["custom_phases"] = normalized + else: + old_data["custom_phases"] = [] + + return old_data + + async def get_storage_stats(self) -> dict[str, Any]: + """Get storage usage statistics.""" + data = self._data # pylint: disable=protected-access + if not data: + data = await self.async_load() or {} + + # Rough file size estimation if possible, else 0 + file_size_kb = 0 + try: + path = self.path # pylint: disable=no-member + if os.path.exists(path): + file_size_kb = os.path.getsize(path) / 1024 + except Exception: # pylint: disable=broad-exception-caught + pass + + cycles = data.get("past_cycles", []) + profiles = data.get("profiles", {}) + + debug_traces_count = sum(1 for c in cycles if c.get("debug_data")) + + return { + "file_size_kb": round(file_size_kb, 1), + "total_cycles": len(cycles), + "total_profiles": len(profiles), + "debug_traces_count": debug_traces_count, + } + + async def async_clear_debug_data(self) -> int: + """Clear granular debug data from all cycles to free space.""" + if not self._data: + await self.async_load() + + if self._data is None: + return 0 + + cycles = self._data.get("past_cycles", []) + count = 0 + for cycle in cycles: + if "debug_data" in cycle: + del cycle["debug_data"] + count += 1 + + if count > 0: + await self.async_save(self._data) + _LOGGER.info("Cleared debug data from %s cycles", count) + + return count + + +class ProfileStore: + """Manages storage of washer profiles and past cycles.""" + + def __init__( + self, + hass: HomeAssistant, + entry_id: str, + min_duration_ratio: float = 0.50, + max_duration_ratio: float = 1.50, + save_debug_traces: bool = False, + match_threshold: float = 0.4, + unmatch_threshold: float = 0.35, + device_name: str = "", + ) -> None: + """Initialize the profile store.""" + self.hass = hass + self.entry_id = entry_id + self._logger = DeviceLoggerAdapter(_LOGGER, device_name) + self._min_duration_ratio = min_duration_ratio + self._max_duration_ratio = max_duration_ratio + self._match_threshold = match_threshold + self._unmatch_threshold = unmatch_threshold + self.dtw_bandwidth: float = DEFAULT_DTW_BANDWIDTH + self._save_debug_traces = save_debug_traces + + # Cache for resampled sample segments: key=(cycle_id, dt) + self._cached_sample_segments: dict[tuple[str, float], Segment] = {} + # Profile duration tolerance (set by manager; reserved for duration-based heuristics) + self._duration_tolerance: float = 0.25 + # Retention policy: cap total cycles and number of full-resolution traces per profile + self._max_past_cycles = DEFAULT_MAX_PAST_CYCLES + self._max_full_traces_per_profile = DEFAULT_MAX_FULL_TRACES_PER_PROFILE + self._max_full_traces_unlabeled = DEFAULT_MAX_FULL_TRACES_UNLABELED + # Separate store for each entry to avoid giant files + # Use WashDataStore to handle migration + self._store: Store[JSONDict] = WashDataStore( + hass, STORAGE_VERSION, f"{STORAGE_KEY}.{entry_id}" + ) + self._data: JSONDict = { + "profiles": {}, + "past_cycles": [], + "envelopes": {}, # Cached statistical envelopes per profile + "auto_adjustments": [], # Log of automatic setting changes + "suggestions": {}, # Suggested settings (do NOT change user options) + "feedback_history": {}, # Persisted user feedback (cycle_id -> record) + "pending_feedback": {}, # Persisted pending feedback requests + "custom_phases": [], # Shared custom phase catalog + } + + + + + def set_suggestion(self, key: str, value: Any, reason: str | None = None) -> None: + """Store a suggested setting value without changing config entry options.""" + suggestions: JSONDict = self._data.setdefault("suggestions", {}) + suggestions[key] = { + "value": value, + "reason": reason, + "updated": dt_util.now().isoformat(), + } + + def get_suggestions(self) -> dict[str, Any]: + """Return current suggestion map.""" + raw = self._data.get("suggestions") + if isinstance(raw, dict): + suggestions = cast(JSONDict, raw) + return suggestions.copy() + return {} + + def delete_suggestion(self, key: str) -> None: + """Remove a single suggestion entry by key.""" + suggestions: JSONDict = self._data.setdefault("suggestions", {}) + suggestions.pop(key, None) + + async def clear_suggestions(self) -> None: + """Clear all pending suggestions and persist.""" + self._data["suggestions"] = {} + await self.async_save() + + def get_feedback_history(self) -> dict[str, dict[str, Any]]: + """Return mutable feedback history mapping (cycle_id -> record).""" + raw = self._data.setdefault("feedback_history", {}) + if isinstance(raw, dict): + return cast(dict[str, dict[str, Any]], raw) + return {} + + def get_pending_feedback(self) -> dict[str, dict[str, Any]]: + """Return mutable pending feedback mapping (cycle_id -> request).""" + raw = self._data.setdefault("pending_feedback", {}) + if isinstance(raw, dict): + return cast(dict[str, dict[str, Any]], raw) + return {} + + def add_pending_feedback(self, cycle_id: str, request_data: dict[str, Any]) -> None: + """Add a pending feedback request (sync wrapper, does not save immediately).""" + feedbacks = self.get_pending_feedback() + feedbacks[cycle_id] = request_data + # Caller must ensure save is called eventually + + def remove_pending_feedback(self, cycle_id: str) -> None: + """Remove a pending feedback request.""" + feedbacks = self.get_pending_feedback() + if cycle_id in feedbacks: + del feedbacks[cycle_id] + + + def get_profile(self, name: str) -> JSONDict | None: + """Return a single profile by name with calculated stats (via list_profiles).""" + # Reuse list_profiles logic to ensure consistency and avoid duplication + all_profiles = self.list_profiles() + return next((p for p in all_profiles if p["name"] == name), None) + + def get_profiles(self) -> dict[str, JSONDict]: + """Return mutable profiles mapping (profile_name -> profile data).""" + raw = self._data.setdefault("profiles", {}) + if isinstance(raw, dict): + return cast(dict[str, JSONDict], raw) + return {} + + def get_past_cycles(self) -> list[CycleDict]: + """Return mutable list of stored cycles.""" + raw = self._data.setdefault("past_cycles", []) + if isinstance(raw, list): + return cast(list[CycleDict], raw) + return [] + + def _get_shared_custom_phases(self) -> list[dict[str, Any]]: + """Return mutable shared custom phase list with legacy flattening.""" + raw = self._data.setdefault("custom_phases", []) + if isinstance(raw, list): + return cast(list[dict[str, Any]], raw) + + # Legacy format: {device_type: [phase, ...]}. Flatten to shared list. + flattened: list[dict[str, Any]] = [] + seen: set[tuple[str, str]] = set() + if isinstance(raw, dict): + raw_dict = cast(dict[str, Any], raw) + for legacy_device_type, phase_list in raw_dict.items(): + if not isinstance(phase_list, list): + continue + for item in cast(list[Any], phase_list): + if not isinstance(item, dict): + continue + item_dict = cast(dict[str, Any], item) + name = str(item_dict.get("name", "")).strip() + if not name: + continue + device_type = str(legacy_device_type or "").strip() + key = (name.casefold(), device_type.casefold()) + if key in seen: + continue + seen.add(key) + flattened.append( + { + "name": name, + "description": str(item_dict.get("description", "")).strip(), + "device_type": device_type, + "created_at": item_dict.get("created_at") or dt_util.now().isoformat(), + } + ) + + self._data["custom_phases"] = flattened + return self._data["custom_phases"] + + def list_custom_phases(self, device_type: str) -> list[dict[str, Any]]: + """Return shared custom phases relevant to the requested device type.""" + + def applies_to_device(item_device_type: str, target_device_type: str) -> bool: + if not item_device_type: + return True + return item_device_type == target_device_type + + target = str(device_type or "").strip() + phases = self._get_shared_custom_phases() + return [ + { + "id": str(p.get("id", "")), + "name": str(p.get("name", "")).strip(), + "description": str(p.get("description", "")).strip(), + "device_type": str(p.get("device_type", "")).strip(), + "is_default": False, + } + for p in phases + if p.get("name") + and applies_to_device(str(p.get("device_type", "")).strip(), target) + ] + + def list_phase_catalog(self, device_type: str) -> list[dict[str, Any]]: + """Return merged shared default + custom phase catalog.""" + return merge_phase_catalog(device_type, self.list_custom_phases(device_type)) + + async def async_create_custom_phase( + self, + device_type: str, + phase_name: str, + description: str = "", + ) -> None: + """Create a custom phase in the shared catalog.""" + target_device_type = str(device_type or "").strip() + name = normalize_phase_name(phase_name) + desc = str(description or "").strip() + catalog = self.list_phase_catalog(target_device_type) + if any(str(p.get("name", "")).casefold() == name.casefold() for p in catalog): + raise ValueError("duplicate_phase") + + self._get_shared_custom_phases().append( + { + "id": str(uuid.uuid4()), + "name": name, + "description": desc, + "device_type": target_device_type, + "created_at": dt_util.now().isoformat(), + } + ) + await self.async_save() + + async def async_update_custom_phase( + self, + phase_id: str, + new_name: str, + description: str = "", + ) -> None: + """Update a phase by id, propagating rename to profile assignments. + + If phase_id matches a built-in, a custom override is created using the + built-in's id so the merge can replace it in-place. + """ + target_name = normalize_phase_name(new_name) + desc = str(description or "").strip() + phases = self._get_shared_custom_phases() + + # Look for an existing custom entry with this id. + found: dict[str, Any] | None = next( + (p for p in phases if str(p.get("id", "")) == phase_id), None + ) + creating_new = found is None + + if creating_new: + builtin = get_builtin_phase_by_id(phase_id) + if builtin is None: + raise ValueError("phase_not_found") + candidate: dict[str, Any] = { + "id": phase_id, + "name": str(builtin.get("name", "")), + "description": "", + "device_type": str(builtin.get("device_type", "")), + "created_at": dt_util.now().isoformat(), + } + else: + candidate = found # type: ignore[assignment] + + old_name = str(candidate.get("name", "")) + target_device_type = str(candidate.get("device_type", "")).strip() + + # Duplicate-name check before any mutation. + for p in self.list_phase_catalog(target_device_type): + pname = str(p.get("name", "")) + if pname.casefold() == target_name.casefold() and pname.casefold() != old_name.casefold(): + raise ValueError("duplicate_phase") + + if creating_new: + phases.append(candidate) + found = candidate + + found["name"] = target_name # type: ignore[index] + found["description"] = desc # type: ignore[index] + + # Propagate rename to profile assignments. + for profile in self.get_profiles().values(): + profile_device_type = str(profile.get("device_type", "")).strip() + if target_device_type and profile_device_type != target_device_type: + continue + phases_assigned = profile.get("phases", []) + if not isinstance(phases_assigned, list): + continue + for assigned in cast(list[dict[str, Any]], phases_assigned): + if str(assigned.get("name", "")).casefold() == old_name.casefold(): + assigned["name"] = target_name + + await self.async_save() + + def count_phase_usage(self, phase_name: str) -> int: + """Count how many profile assignments use a phase name.""" + used = 0 + for profile in self.get_profiles().values(): + phases_assigned = profile.get("phases", []) + if not isinstance(phases_assigned, list): + continue + assigned_list = cast(list[dict[str, Any]], phases_assigned) + used += sum( + 1 + for phase in assigned_list + if str(phase.get("name", "")).casefold() == phase_name.casefold() + ) + return used + + async def async_delete_custom_phase(self, phase_id: str) -> int: + """Delete a custom phase by id and remove matching profile assignments. + + Returns number of removed assignments. + Raises ValueError('phase_not_found') if no custom phase has this id. + Raises ValueError('cannot_delete_builtin') if the id is a built-in phase. + """ + phases = self._get_shared_custom_phases() + found = next((p for p in phases if str(p.get("id", "")) == phase_id), None) + if found is None: + raise ValueError("phase_not_found") + if get_builtin_phase_by_id(phase_id) is not None: + raise ValueError("cannot_delete_builtin") + + phase_name = str(found.get("name", "")) + phase_scope = str(found.get("device_type", "")).strip() + self._data["custom_phases"] = [p for p in phases if str(p.get("id", "")) != phase_id] + + removed_assignments = 0 + for profile in self.get_profiles().values(): + profile_device_type = str(profile.get("device_type", "")).strip() + if phase_scope and profile_device_type != phase_scope: + continue + assigned = profile.get("phases", []) + if not isinstance(assigned, list): + continue + assigned_list = cast(list[dict[str, Any]], assigned) + before = len(assigned_list) + profile["phases"] = [ + p for p in assigned_list + if str(p.get("name", "")).casefold() != phase_name.casefold() + ] + removed_assignments += before - len(profile["phases"]) + + await self.async_save() + return removed_assignments + + def get_profile_phase_ranges(self, profile_name: str) -> list[dict[str, Any]]: + """Return assigned phase ranges for a profile.""" + profiles = self._data.get("profiles", {}) + if not isinstance(profiles, dict): + return [] + profile_raw = cast(dict[str, Any], profiles).get(profile_name) + profile = cast(dict[str, Any], profile_raw) if isinstance(profile_raw, dict) else None + if not isinstance(profile, dict): + return [] + phases = profile.get("phases", []) + if not isinstance(phases, list): + return [] + phases_list = cast(list[dict[str, Any]], phases) + cleaned: list[dict[str, Any]] = [] + for phase in phases_list: + try: + start = float(phase.get("start", 0.0)) + end = float(phase.get("end", 0.0)) + except (TypeError, ValueError): + continue + if end <= start: + continue + cleaned.append( + { + "name": str(phase.get("name", "")).strip(), + "start": start, + "end": end, + "description": str(phase.get("description", "")).strip(), + } + ) + return sorted(cleaned, key=lambda x: (x["start"], x["end"], x["name"])) + + def get_profile_phase_ranges_for_device( + self, profile_name: str, device_type: str + ) -> list[dict[str, Any]]: + """Return assigned ranges enriched with catalog descriptions.""" + ranges = self.get_profile_phase_ranges(profile_name) + catalog = self.list_phase_catalog(device_type) + desc_map = { + str(p.get("name", "")).casefold(): str(p.get("description", "")).strip() + for p in catalog + } + enriched: list[dict[str, Any]] = [] + for row in ranges: + name = str(row.get("name", "")).strip() + enriched.append( + { + "name": name, + "start": float(row.get("start", 0.0)), + "end": float(row.get("end", 0.0)), + "description": desc_map.get(name.casefold(), ""), + } + ) + return enriched + + async def async_set_profile_phase_ranges( + self, profile_name: str, ranges: list[dict[str, Any]] + ) -> None: + """Replace assigned phase ranges for a profile.""" + profile = self._data.get("profiles", {}).get(profile_name) + if not isinstance(profile, dict): + raise ValueError("profile_not_found") + + normalized: list[dict[str, Any]] = [] + for item in ranges: + name = normalize_phase_name(str(item.get("name", ""))) + try: + start = float(item.get("start", 0.0)) + end = float(item.get("end", 0.0)) + except (TypeError, ValueError) as e: + raise ValueError("invalid_phase_range") from e + if end <= start: + raise ValueError("invalid_phase_range") + normalized.append({"name": name, "start": start, "end": end}) + + normalized.sort(key=lambda x: (x["start"], x["end"])) + prev_end = None + for row in normalized: + if prev_end is not None and row["start"] < prev_end: + raise ValueError("overlapping_phase_ranges") + prev_end = row["end"] + + profile["phases"] = normalized + await self.async_save() + + def set_duration_tolerance(self, tolerance: float) -> None: + """Set the profile duration tolerance used by matching heuristics.""" + try: + self._duration_tolerance = float(tolerance) + except (TypeError, ValueError): + pass + + def set_retention_limits( + self, + *, + max_past_cycles: int, + max_full_traces_per_profile: int, + max_full_traces_unlabeled: int, + ) -> None: + """Set retention caps for stored cycles and full-resolution traces.""" + try: + self._max_past_cycles = int(max_past_cycles) + self._max_full_traces_per_profile = int(max_full_traces_per_profile) + self._max_full_traces_unlabeled = int(max_full_traces_unlabeled) + except (TypeError, ValueError): + pass + + def get_duration_ratio_limits(self) -> tuple[float, float]: + """Return (min_duration_ratio, max_duration_ratio) used for duration matching.""" + return (float(self._min_duration_ratio), float(self._max_duration_ratio)) + + def set_duration_ratio_limits(self, *, min_ratio: float, max_ratio: float) -> None: + """Update duration ratio bounds used for duration matching.""" + try: + self._min_duration_ratio = float(min_ratio) + self._max_duration_ratio = float(max_ratio) + except (TypeError, ValueError): + pass + + def _migrate_phase_ids(self) -> bool: + """Assign ids to any custom phase missing one. Returns True if anything changed.""" + phases = self._data.get("custom_phases", []) + if not isinstance(phases, list): + return False + changed = False + for phase in cast(list[dict[str, Any]], phases): + if phase.get("id"): + continue + dt = str(phase.get("device_type", "")).strip() + name = str(phase.get("name", "")).strip() + matched_id: str | None = None + for bdt, bphases in DEFAULT_PHASES_BY_DEVICE.items(): + if dt and bdt != dt: + continue + for bp in bphases: + if str(bp.get("name", "")).strip().casefold() == name.casefold(): + matched_id = _builtin_phase_id(bdt, str(bp.get("name", ""))) + break + if matched_id: + break + phase["id"] = matched_id if matched_id else str(uuid.uuid4()) + changed = True + return changed + + async def async_load(self) -> None: + """Load data from storage with migration.""" + # WashDataStore handles migration internally via _async_migrate_func + data = await self._store.async_load() + if data: + self._data = data + # Ensure legacy custom phase formats are normalized in-memory. + self._get_shared_custom_phases() + # Assign ids to any custom phase missing one. + if self._migrate_phase_ids(): + await self.async_save() + # Repair cycles whose power_data was corrupted by the double-subtract bug. + if self.repair_corrupted_power_data(): + await self.async_save() + await self.async_rebuild_all_envelopes() + await self.async_save() + + # _migrate_v1_to_v2 and _decompress_power_from_raw removed; logic moved to WashDataStore + + def _decompress_power_from_raw( + self, cycle: CycleDict + ) -> list[tuple[float, float, float]] | None: + # Helper not needed if we use _decompress_power_data + pass + + async def async_repair_profile_samples(self) -> dict[str, int]: + """Repair profile sample references after retention or migrations. + + Ensures each profile's sample_cycle_id points to an existing cycle that still + has full-resolution power_data. If missing, picks the newest available cycle + with power_data and assigns it as the sample (and labels that cycle to the + profile if it was unlabeled). + + Returns stats dict. + """ + stats = { + "profiles_checked": 0, + "profiles_repaired": 0, + "cycles_labeled_as_sample": 0, + } + + profiles: dict[str, dict[str, Any]] = self._data.get("profiles", {}) or {} + cycles: list[dict[str, Any]] = self._data.get("past_cycles", []) or [] + if not profiles or not cycles: + return stats + + by_id: dict[str, dict[str, Any]] = {c["id"]: c for c in cycles if c.get("id")} + + def newest_unlabeled_with_power_data() -> dict[str, Any] | None: + candidates: list[dict[str, Any]] = [ + c for c in cycles if c.get("power_data") and not c.get("profile_name") + ] + if not candidates: + return None + try: + return max(candidates, key=lambda c: c.get("start_time", "")) + except Exception: # pylint: disable=broad-exception-caught + return candidates[-1] + + for profile_name, profile in profiles.items(): + stats["profiles_checked"] += 1 + sample_id = profile.get("sample_cycle_id") + sample = by_id.get(sample_id) if sample_id else None + + # Sample is valid only if it exists and still has power_data + if sample and sample.get("power_data"): + continue + + # Prefer newest already-labeled cycle for this profile that still has power_data + labeled_candidates = [ + c + for c in cycles + if c.get("profile_name") == profile_name and c.get("power_data") + ] + if labeled_candidates: + try: + chosen = max( + labeled_candidates, key=lambda c: c.get("start_time", "") + ) + except Exception: # pylint: disable=broad-exception-caught + chosen = labeled_candidates[-1] + else: + # Fallback: pick newest UNLABELED cycle with power_data + chosen = newest_unlabeled_with_power_data() + + if not chosen: + continue + + profile["sample_cycle_id"] = chosen.get("id") + if chosen.get("duration"): + profile["avg_duration"] = chosen["duration"] + + # If chosen cycle is unlabeled, label it to this profile to bootstrap matching + if not chosen.get("profile_name"): + chosen["profile_name"] = profile_name + stats["cycles_labeled_as_sample"] += 1 + + stats["profiles_repaired"] += 1 + try: + await self.async_rebuild_envelope(profile_name) + except Exception: # pylint: disable=broad-exception-caught + pass + + return stats + + async def async_save(self) -> None: + """Save data to storage.""" + await self._store.async_save(self._data) + + async def async_save_active_cycle(self, detector_snapshot: JSONDict) -> None: + """Save the active cycle state to storage (throttled by Manager).""" + self._data["active_cycle"] = detector_snapshot + self._data["last_active_save"] = dt_util.now().isoformat() + await self._store.async_save(self._data) + + def get_active_cycle(self) -> JSONDict | None: + """Get the saved active cycle.""" + raw = self._data.get("active_cycle") + if isinstance(raw, dict): + return cast(JSONDict, raw) + return None + + def get_last_active_save(self) -> datetime | None: + """Return the last time the active cycle snapshot was persisted.""" + raw = self._data.get("last_active_save") + if not isinstance(raw, str) or not raw: + return None + try: + return dt_util.parse_datetime(raw) + except ValueError: + return None + + async def async_clear_active_cycle(self) -> None: + """Clear the active cycle snapshot from storage.""" + if "active_cycle" in self._data: + del self._data["active_cycle"] + await self._store.async_save(self._data) + + def add_cycle(self, cycle_data: CycleDict) -> None: + """Add a completed cycle to history (sync wrapper, schedules async tasks).""" + self._add_cycle_data(cycle_data) + self.hass.async_create_task(self.async_enforce_retention()) + + async def async_add_cycle(self, cycle_data: CycleDict) -> None: + """Add a completed cycle to history asynchronously.""" + self._add_cycle_data(cycle_data) + await self.async_enforce_retention() + + def _add_cycle_data(self, cycle_data: CycleDict) -> None: + """Internal logic to add cycle data to storage.""" + # Generate SHA256 ID + unique_str = f"{cycle_data['start_time']}_{cycle_data['duration']}" + cycle_data["id"] = hashlib.sha256(unique_str.encode()).hexdigest()[:12] + + # Preserve profile_name if already set by manager; default to None otherwise + if "profile_name" not in cycle_data: + cycle_data["profile_name"] = None # Initially unknown + + # Store power data at native sampling resolution + # Format: [seconds_offset, power] preserves actual sample rate from device + # (e.g., 3s intervals from test socket, 60s intervals from real socket) + raw_data: list[Any] = cycle_data.get("power_data", []) or [] + self._logger.debug("add_cycle: raw_data has %s points", len(raw_data)) + + if raw_data: + start_time_raw = cycle_data.get("start_time") + start_time_iso: str | None = None + if start_time_raw is not None: + parsed_dt = _parse_start_dt(start_time_raw) + if parsed_dt is not None: + start_time_iso = parsed_dt.isoformat() + # Keep original ISO string as-is if it was already a valid ISO string + if isinstance(start_time_raw, str) and dt_util.parse_datetime(start_time_raw) is not None: + start_time_iso = start_time_raw + else: + try: + ts = float(start_time_raw) + start_time_iso = dt_util.utc_from_timestamp(ts).isoformat() + except (ValueError, OSError): + self._logger.debug( + "add_cycle: unparseable string start_time %r, falling back", + start_time_raw, + ) + elif isinstance(start_time_raw, datetime): + start_time_iso = start_time_raw.isoformat() + elif isinstance(start_time_raw, (int, float)): + try: + start_time_iso = dt_util.utc_from_timestamp(float(start_time_raw)).isoformat() + except (ValueError, OSError): + pass + if start_time_iso is not None: + cycle_data["start_time"] = start_time_iso + if start_time_iso is None and _value_to_timestamp(start_time_raw) is None: + self._logger.debug("add_cycle: invalid start_time %r, skipping power_data normalization", start_time_raw) + if hasattr(self, "_save_debug_traces") and not self._save_debug_traces: + cycle_data.pop("debug_data", None) + self._data["past_cycles"].append(cycle_data) + return + + # Use unified normalizer: handles offset, ISO-string, and datetime formats + pairs = power_data_to_offsets( + cast(list[list[Any] | tuple[Any, ...]], raw_data), start_time_iso + ) + stored: list[list[float]] = [[round(p[0], 1), round(p[1], 1)] for p in pairs] + offsets: list[float] = [p[0] for p in stored] + + # Calculate average sampling interval (in seconds) + if len(offsets) > 1: + intervals = np.diff(offsets) + positive_intervals = intervals[intervals > 0] + sampling_interval = float(np.median(positive_intervals)) if positive_intervals.size > 0 else 0.0 + else: + sampling_interval = 1.0 # Default fallback + + # Trim leading/trailing zero readings for cleaner data + # SKIP for completed cycles to preserve end spike data + if cycle_data.get("status") in ("completed", "force_stopped"): + # Only trim leading zeros for completed cycles, keep trailing data + start_idx = 0 + for i, point in enumerate(stored): + if point[1] > 1.0: + start_idx = i + break + stored = stored[start_idx:] + self._logger.debug("add_cycle: Skipping trailing trim for completed cycle") + else: + stored = trim_zero_power_data(stored, threshold=1.0) + + cycle_data["power_data"] = stored + cycle_data["sampling_interval"] = round(sampling_interval, 1) + + # Helper to get arrays for signature (use stored data for consistency) + ts_arr = np.array([t for t, _ in stored]) + p_arr = np.array([p for _, p in stored]) + + # Compute and store signature + if len(ts_arr) > 1 and len(ts_arr) == len(p_arr): + sig = compute_signature(ts_arr, p_arr) + cycle_data["signature"] = dataclasses.asdict(sig) + + # Compute and store energy (Wh) if not already set (e.g. by manager) + if "energy_wh" not in cycle_data and len(ts_arr) > 1: + sort_idx = np.argsort(ts_arr) + ts_s = ts_arr[sort_idx] + p_s = p_arr[sort_idx] + dt_h = np.diff(ts_s) / 3600.0 + # Use a data-driven gap threshold: 10x the median sampling interval, + # clamped to at least 60 s and at most 1 h, to skip sensor outages + # without masking valid slow-sampling configurations. + _gap_s = float(np.clip(10.0 * sampling_interval, 60.0, 3600.0)) + _MAX_GAP_H = _gap_s / 3600.0 + mask = (dt_h > 0) & (dt_h <= _MAX_GAP_H) + avg_p = (p_s[:-1] + p_s[1:]) / 2 + cycle_data["energy_wh"] = round(float(np.sum(avg_p[mask] * dt_h[mask])), 3) + + self._logger.debug( + "add_cycle: stored %s samples at %.1fs intervals", + len(stored), + sampling_interval, + ) + + # 4. Handle Debug Data (Strip if not enabled) + if hasattr(self, "_save_debug_traces") and not self._save_debug_traces: + if "debug_data" in cycle_data: + del cycle_data["debug_data"] + + self._data["past_cycles"].append(cycle_data) + # Apply retention after adding + + + async def async_enforce_retention(self) -> None: + """Apply retention policy asynchronously.""" + affected = self._enforce_retention_data() + for p in affected: + try: + # Use async rebuild task + self.hass.async_create_task(self.async_rebuild_envelope(p)) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to schedule envelope rebuild for %s: %s", p, e) + + def _enforce_retention_data(self) -> set[str]: + """Internal retention logic (data operations only). + Returns set of affected profile names.""" + raw_cycles = self._data.get("past_cycles", []) + cycles: list[CycleDict] = ( + cast(list[CycleDict], raw_cycles) if isinstance(raw_cycles, list) else [] + ) + if not cycles: + return set() + + def _start_time(cycle: CycleDict) -> str: + return str(cycle.get("start_time", "")) + + affected_profiles: set[str] = set() + + # 1) Cap total cycles + if len(cycles) > self._max_past_cycles: + # Sort by start_time and drop oldest beyond cap + try: + cycles.sort(key=_start_time) + except Exception: # pylint: disable=broad-exception-caught + pass + drop_count = len(cycles) - self._max_past_cycles + to_drop = cycles[:drop_count] + + # Maintain profile sample references when dropping + sample_refs = { + name: p.get("sample_cycle_id") + for name, p in self._data.get("profiles", {}).items() + } + for cy in to_drop: + # Track affected profile + p_name = cy.get("profile_name") + if p_name: + affected_profiles.add(p_name) + + cy_id = cy.get("id") + # If a profile sample points here, try to move to most recent cycle of that profile + for name, ref_id in list(sample_refs.items()): + if ref_id == cy_id: + # find newest cycle for that profile + newest = next( + ( + c + for c in reversed(cycles) + if c.get("profile_name") == name and c not in to_drop + ), + None, + ) + if newest: + self._data["profiles"][name]["sample_cycle_id"] = ( + newest.get("id") + ) + else: + # No replacement available + self._data["profiles"][name].pop("sample_cycle_id", None) + # Actually drop + del cycles[:drop_count] + + # 2) Strip older full traces per profile + by_profile: dict[str | None, list[CycleDict]] = {} + for cy in cycles: + key_any = cy.get("profile_name") # None for unlabeled + key: str | None = key_any if isinstance(key_any, str) and key_any else None + by_profile.setdefault(key, []).append(cy) + + # Collect cycle IDs that have pending feedback - never strip their power_data + pending_feedback_ids: set[str] = set(self._data.get("pending_feedback", {}).keys()) + + for key, group in by_profile.items(): + # newest first based on start_time + try: + group.sort(key=_start_time) + except Exception: # pylint: disable=broad-exception-caught + pass + # determine cap + cap = ( + self._max_full_traces_unlabeled + if key + in ( + None, + "", + ) + else self._max_full_traces_per_profile + ) + # count existing full traces + full_indices = [i for i, c in enumerate(group) if c.get("power_data")] + if len(full_indices) > cap: + # preserve last 'cap' full traces (newest at end after sort), strip older ones + keep_set = set(full_indices[-cap:]) + + # Get sample cycle ID for this profile + sample_id: str | None = None + if key and key in self._data.get("profiles", {}): + sample_id = self._data["profiles"][key].get("sample_cycle_id") + + for i, c in enumerate(group): + if i in keep_set: + continue + + # EXEMPTION: Never strip power data from the profile's sample cycle! + if sample_id and c.get("id") == sample_id: + continue + + # EXEMPTION: Never strip power data from cycles awaiting feedback review + if c.get("id") in pending_feedback_ids: + continue + + if c.get("power_data"): + c.pop("power_data", None) + c.pop("sampling_interval", None) + if key: + affected_profiles.add(key) + + return affected_profiles + + + + def cleanup_orphaned_profiles(self) -> int: + """Remove profiles that reference non-existent cycles. + Returns number of profiles removed.""" + cycle_ids = {c["id"] for c in self._data.get("past_cycles", [])} + orphaned: list[str] = [] + for name, profile in self._data["profiles"].items(): + ref = profile.get("sample_cycle_id") + # Only delete if it references a non-existent cycle ID (Broken Link) + # Creating a profile without a sample (None) is allowed (Pending State) + if ref and ref not in cycle_ids: + orphaned.append(name) + + for name in orphaned: + del self._data["profiles"][name] + self._logger.info( + "Cleaned up orphaned profile '%s' (cycle no longer exists)", name + ) + + return len(orphaned) + + async def async_run_maintenance(self) -> dict[str, int]: + """Run full maintenance: cleanup orphans, merge fragments, trim old cycles. + + Also rebuilds envelopes. Returns stats dict with counts of actions taken. + """ + stats = { + "orphaned_profiles": 0, + "merged_cycles": 0, + "split_cycles": 0, + "rebuilt_envelopes": 0, + } + + # 1. Clean up orphaned profiles + stats["orphaned_profiles"] = self.cleanup_orphaned_profiles() + + # 2. Auto-Label missed cycles (retroactive matching) + # Use overwrite=False to respect existing manual/confident labels + label_stats = await self.auto_label_cycles(confidence_threshold=0.75, overwrite=False) + stats["labeled_cycles"] = label_stats.get("labeled", 0) + + # 2. Smart Process History (Merge/Split/Rebuild) + proc_stats = await self.async_smart_process_history() + stats["merged_cycles"] = proc_stats.get("merged", 0) + stats["split_cycles"] = proc_stats.get("split", 0) + stats["rebuilt_envelopes"] = len(self._data.get("profiles", {})) # Approximation of rebuilt count + + # 4. Save if any changes made (smart process saves internally if needed, but explicit save safe) + if any(stats.values()): + await self.async_save() + self._logger.info("Maintenance completed: %s", stats) + + return stats + + def _reprocess_all_data_sync(self) -> int: + """Synchronous implementation of reprocessing logic (run in executor).""" + cycles_raw = self._data.get("past_cycles", []) + cycles = cast(list[CycleDict], cycles_raw) if isinstance(cycles_raw, list) else [] + if not cycles: + return 0 + + processed_count = 0 + + # 1. Update Signatures & Optimize Data + for cycle in cycles: + # Data Optimization: Trim leading/trailing zeros (0W) + # Only apply to compressed data to avoid breaking legacy format + p_data = cycle.get("power_data") + if ( + p_data + and isinstance(p_data, list) + and p_data + and isinstance(p_data[0], (list, tuple)) + ): + first_point = cast(list[Any] | tuple[Any, ...], p_data[0]) + # Only trim offset-format data (numeric offsets). Legacy ISO-format + # cycles skip trimming but still reach the signature block below. + if len(first_point) == 2 and isinstance(first_point[0], (int, float)): + p_data_list = cast(list[list[float]], p_data) + # Apply trim helper + original_len = len(p_data_list) + + # Logic: For completed cycles, only trim leading zeros. + # For others, trim both ends. + if cycle.get("status") in ("completed", "force_stopped"): + # Only trim leading + start_idx = 0 + for i, point in enumerate(p_data_list): + if point[1] > 1.0: # Match threshold below + start_idx = i + break + trimmed: list[list[float]] = p_data_list[start_idx:] + else: + trimmed = trim_zero_power_data(p_data_list, threshold=1.0) # Conservative 1W threshold + + if trimmed and len(trimmed) < original_len: + # Data was trimmed - check for start time shift + first_offset = trimmed[0][0] + + if first_offset > 0: + # Leading zeros removed - Must shift start_time forward + try: + start_dt = datetime.fromisoformat(cycle["start_time"]) + new_start = start_dt + timedelta(seconds=first_offset) + cycle["start_time"] = new_start.isoformat() + + # Re-normalize offsets to 0 + shifted_data: list[list[float]] = [] + for row in trimmed: + # row is [offset, power] + shifted_data.append([round(row[0] - first_offset, 1), row[1]]) + cycle["power_data"] = shifted_data + processed_count += 1 + except (ValueError, TypeError) as e: + self._logger.warning("Failed to shift start_time for trimmed cycle: %s", e) + else: + # Only trailing trimmed or no shift needed + cycle["power_data"] = trimmed + processed_count += 1 + + # Update duration to match new data length + # If we only trimmed the head, the new duration is old_duration - first_offset + # This preserves trailing silence. + if cycle.get("power_data"): + old_dur = float(cycle.get("duration", 0.0) or 0.0) + # If we shifted (first_offset > 0), new duration is old_dur - first_offset + # Otherwise if we only trimmed tail, we might want to snap, + # but for completed cycles we don't trim tail in this loop. + if first_offset > 0: + cycle["duration"] = max(0.0, old_dur - first_offset) + else: + # Only trailing was trimmed (not expected for completed cycles here) + # or no trim happened. + # If trailing was trimmed, we SHOULD snap. + if len(trimmed) < original_len: + cycle["duration"] = cycle["power_data"][-1][0] + + if cycle.get("power_data"): + try: + tuples = decompress_power_data(cycle) + if tuples and len(tuples) > 10: + ts_arr: list[float] = [] + p_arr: list[float] = [] + for offset_sec, p in tuples: + ts_arr.append(float(offset_sec)) + p_arr.append(float(p)) + + sig = compute_signature(np.array(ts_arr, dtype=float), np.array(p_arr, dtype=float)) + cycle["signature"] = dataclasses.asdict(sig) + processed_count += 1 + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to reprocess signature: %s", e) + + # 2. Rebuild Envelopes + + + return processed_count + + async def async_reprocess_all_data(self) -> int: + """Reprocess all historical data to update signatures and rebuild envelopes. + + This is a non-destructive operation for raw cycle data. It: + 1. Recalculates signatures for ALL past cycles using current logic. + 2. Rebuilds all profile envelopes from scratch. + 3. Updates global stats. + + Returns total number of cycles processed. + """ + self._logger.info("Starting reprocessing (offloaded)...") + + # Offload heavy synchronous work + processed_count = await self.hass.async_add_executor_job( + self._reprocess_all_data_sync + ) + + # 2. Rebuild Envelopes (Using new async infrastructure) + await self.async_rebuild_all_envelopes() + + await self.async_save() + + return processed_count + + async def get_storage_stats(self) -> dict[str, Any]: + """Get storage usage stats.""" + cycles = self._data.get("past_cycles", []) + profiles = self._data.get("profiles", {}) + debug_traces_count = sum(1 for c in cycles if c.get("debug_data")) + + file_size_kb = 0 + try: + # Attempt to get real file size from store + if hasattr(self._store, "path") and os.path.exists(self._store.path): + file_size_kb = os.path.getsize(self._store.path) / 1024 + else: + # Fallback: estimate + file_size_kb = len(json.dumps(self._data, default=str)) / 1024 + except Exception: # pylint: disable=broad-exception-caught + pass + + return { + "file_size_kb": round(file_size_kb, 1), + "total_cycles": len(cycles), + "total_profiles": len(profiles), + "debug_traces_count": debug_traces_count, + } + + async def async_clear_debug_data(self) -> int: + """Clear debug data from all cycles.""" + cycles = self._data.get("past_cycles", []) + count = 0 + for cycle in cycles: + if "debug_data" in cycle: + del cycle["debug_data"] + count += 1 + + if count > 0: + await self.async_save() + self._logger.info("Cleared debug data from %s cycles", count) + + return count + + + + def _rebuild_envelope_sync( + self, labeled_cycles: list[CycleDict] + ) -> tuple[Any, list[float]] | None: + """Sync worker to parse data and build envelope (run in executor).""" + raw_cycles_data: list[tuple[list[float], list[float], float]] = [] + durations: list[float] = [] + + for cycle in labeled_cycles: + # Use the shared decompressor so both legacy ISO-timestamp format + # and the current offset-float format are handled transparently. + pairs = self._decompress_power_data(cycle) + + if len(pairs) < 3: + continue + + offsets: list[float] = [p[0] for p in pairs] + values: list[float] = [p[1] for p in pairs] + + stored_dur = float(cycle.get("duration", 0.0) or 0.0) + authoritative_dur = float(max(offsets[-1], stored_dur)) + + # Use manual duration if available (e.g. from feedback correction) + man_dur = cycle.get("manual_duration") + if man_dur: + final_dur = float(man_dur) + else: + final_dur = authoritative_dur + + raw_cycles_data.append((offsets, values, final_dur)) + durations.append(final_dur) + + if not raw_cycles_data: + return None + + # Run Heavy Computation + result = analysis.compute_envelope_worker( + cast(Any, raw_cycles_data), + self.dtw_bandwidth + ) + + if not result: + return None + + return result, durations + + async def async_rebuild_all_envelopes(self) -> int: + """Rebuild envelopes for all profiles. Returns count of envelopes rebuilt.""" + count = 0 + for profile_name in list(self._data["profiles"].keys()): + if await self.async_rebuild_envelope(profile_name): + count += 1 + return count + + def repair_corrupted_power_data(self) -> int: + """Fix cycles whose power_data offsets were corrupted by the double-subtract bug. + + The bug caused ``offset = small_float - unix_timestamp`` to be stored instead of + just ``small_float``. Corrupted cycles have a first-offset < -1e8 (a value that + can never occur for a real appliance cycle offset). Recovery: add ``start_ts`` + back to every offset in the affected cycle. + + Returns the number of cycles repaired. + """ + repaired = 0 + for cycle in self._data.get("past_cycles", []): + power_data = cycle.get("power_data") + if not isinstance(power_data, list) or not power_data: + continue + first = power_data[0] + if not isinstance(first, (list, tuple)) or len(first) < 2: + continue + first_offset = first[0] + if not isinstance(first_offset, (int, float)) or first_offset > -1e8: + continue # Not corrupted + + start_ts = _value_to_timestamp(cycle.get("start_time")) + if start_ts is None: + continue + + repaired_rows: list[list[float]] = [] + for pt in power_data: + if not isinstance(pt, (list, tuple)) or len(pt) < 2: + continue + try: + repaired_rows.append([round(float(pt[0]) + start_ts, 1), round(float(pt[1]), 1)]) + except (TypeError, ValueError): + continue + if not repaired_rows: + continue # all rows malformed - leave original trace untouched + cycle["power_data"] = repaired_rows + repaired += 1 + repaired_data = cycle["power_data"] + if len(repaired_data) > 1: + r_offsets = [pt[0] for pt in repaired_data] + r_intervals = np.diff(r_offsets) + r_pos = r_intervals[r_intervals > 0] + r_si = float(np.median(r_pos)) if len(r_pos) > 0 else 1.0 + cycle["sampling_interval"] = round(r_si, 1) + # duration = last sample offset from cycle start (not span between + # first and last sample, which would be wrong when leading zeros + # were trimmed before storage) + r_duration = round(r_offsets[-1], 1) + cycle["duration"] = r_duration + cycle["end_time"] = dt_util.utc_from_timestamp( + start_ts + r_duration + ).isoformat() + r_ts = np.array(r_offsets, dtype=float) + r_p = np.array([pt[1] for pt in repaired_data], dtype=float) + r_sig = compute_signature(r_ts, r_p) + cycle["signature"] = dataclasses.asdict(r_sig) + elif len(repaired_data) == 1: + cycle["sampling_interval"] = 1.0 + cycle["duration"] = 0.0 + cycle["end_time"] = dt_util.utc_from_timestamp(start_ts).isoformat() + cycle["signature"] = None + + if repaired: + self._logger.warning( + "Repaired corrupted power_data offsets in %d cycle(s)", repaired + ) + return repaired + + async def async_rebuild_envelope(self, profile_name: str) -> bool: + """ + Build/rebuild statistical envelope for a profile asynchronously. + Offloads heavy DTW/normalization to executor. + """ + # 1. Gather Data (Main Thread) + labeled_cycles = [ + c + for c in self._data["past_cycles"] + if c.get("profile_name") == profile_name + and c.get("status") in ("completed", "force_stopped") + and c.get("duration", 0) > 60 + ] + + if not labeled_cycles: + if profile_name in self._data.get("envelopes", {}): + del self._data["envelopes"][profile_name] + return False + + # 2. Run Heavy Computation in Executor (Parsing + DTW) + result_pkg = await self.hass.async_add_executor_job( + self._rebuild_envelope_sync, + labeled_cycles + ) + + if not result_pkg: + # Envelope shape couldn't be built (no power data / too few points). + # Still update profile min/max/avg from raw cycle durations so that + # a duration correction via feedback is immediately reflected in stats. + if labeled_cycles and profile_name in self._data.get("profiles", {}): + raw_durs = [ + float(c.get("manual_duration") or c.get("duration", 0)) + for c in labeled_cycles + ] + raw_durs = [d for d in raw_durs if d > 60] + if raw_durs: + raw_arr_fallback = np.array(raw_durs, dtype=float) + self._data["profiles"][profile_name]["min_duration"] = float(np.min(raw_arr_fallback)) + self._data["profiles"][profile_name]["max_duration"] = float(np.max(raw_arr_fallback)) + self._data["profiles"][profile_name]["avg_duration"] = float(np.mean(raw_arr_fallback)) + if profile_name in self._data.get("envelopes", {}): + del self._data["envelopes"][profile_name] + return False + + result, durations = result_pkg + + # Update profile stats in storage (Fast metadata update) + if durations and profile_name in self._data.get("profiles", {}): + stats_durations = filter_duration_outliers(durations) + raw_arr = np.array(durations, dtype=float) + # min/max reflect the actual observed range (including outliers) + # avg uses the outlier-filtered set for a robust representative value + min_duration = float(np.min(raw_arr)) + max_duration = float(np.max(raw_arr)) + avg_duration = float(np.mean(stats_durations)) + self._data["profiles"][profile_name]["min_duration"] = min_duration + self._data["profiles"][profile_name]["max_duration"] = max_duration + self._data["profiles"][profile_name]["avg_duration"] = avg_duration + + if not result: + if profile_name in self._data.get("envelopes", {}): + del self._data["envelopes"][profile_name] + return False + + time_grid, min_curve, max_curve, avg_curve, std_curve, target_duration = result + + # 3. Update Storage + # Convert to list of points [[x, y], ...] + def to_points(y_vals: list[float]) -> list[list[float]]: + return [[round(t, 1), round(y, 1)] for t, y in zip(time_grid, y_vals)] + + # Calculate scalar stats + duration_std_dev = float(np.std(durations)) if durations else 0.0 + + # Calculate Energy from Average Curve (Trapezoidal Integration) + avg_energy = 0.0 + if len(time_grid) > 1: + # P(W) * dt(h) = Wh + # avg_curve is in Watts, time_grid is in Seconds + dt_h = np.diff(time_grid) / 3600.0 + avg_p = (np.array(avg_curve[:-1]) + np.array(avg_curve[1:])) / 2.0 + avg_energy = float(np.sum(avg_p * dt_h)) / 1000.0 # Convert to kWh for display? No, config flow expects kWh? + # Config flow line 1552: f"{envelope.get('avg_energy', 0):.2f}" + # If line 1552 says "kwh", then we should store as kWh or Wh? + # Config flow label says "Energy ... kWh" in table row (line 1587). + # Let's check config flow usage again. + # line 1552: kwh = f"{envelope.get('avg_energy', 0):.2f}" + # line 1587: ... | {kwh} kWh | ... + # So if we store 1.5, it displays "1.50 kWh". + # My calculation above gives Wh. So divide by 1000. + # avg_energy is already in kWh from line above. + + envelope_data: dict[str, Any] = { + "time_grid": time_grid, # Time grid used by manager for phase estimation + "target_duration": target_duration, # Target duration for phase estimation + "min": to_points(min_curve), + "max": to_points(max_curve), + "avg": to_points(avg_curve), + "std": to_points(std_curve), + "cycle_count": len(durations), + "avg_energy": avg_energy, + "duration_std_dev": duration_std_dev, + "updated": dt_util.now().isoformat(), + } + + if "envelopes" not in self._data: + self._data["envelopes"] = {} + self._data["envelopes"][profile_name] = envelope_data + + return True + + + + + def generate_profile_svg(self, profile_name: str) -> str | None: + """Generate an SVG string for the profile's power envelope.""" + envelope = self.get_envelope(profile_name) + if not envelope or not envelope.get("time_grid"): + return None + + try: + time_grid = cast(list[float], envelope["time_grid"]) + # Envelope curves are stored as list of [t, y] points. + # Extract Y values for SVG generation logic. + avg_curve = [float(p[1]) for p in cast(list[list[Any] | tuple[Any, ...]], envelope["avg"])] + min_curve = [float(p[1]) for p in cast(list[list[Any] | tuple[Any, ...]], envelope["min"])] + max_curve = [float(p[1]) for p in cast(list[list[Any] | tuple[Any, ...]], envelope["max"])] + + # Canvas configuration (Scaled up 50% for High DPI) + width, height = 1200, 450 + padding_x, padding_y = 60, 45 + graph_w = width - 2 * padding_x + graph_h = height - 2 * padding_y + + max_time = time_grid[-1] + # Add 5% headroom for power + max_power = max(*max_curve, 10.0) * 1.05 + + def to_x(t: float) -> float: + return padding_x + (t / max_time) * graph_w + + def to_y(p: float) -> float: + return height - padding_y - (p / max_power) * graph_h + + # Generate polygon points for min/max band + # Top edge (max) forward, Bottom edge (min) backward + points_max: list[str] = [] + points_min: list[str] = [] + points_avg: list[str] = [] + + for i, t in enumerate(time_grid): + x = to_x(t) + points_max.append(f"{x},{to_y(max_curve[i])}") + points_min.append(f"{x},{to_y(min_curve[i])}") + points_avg.append(f"{x},{to_y(avg_curve[i])}") + + # Band path: Max curve -> Reverse Min curve -> Close + band_path = " ".join(points_max + list(reversed(points_min))) + avg_path = " ".join(points_avg) + + # Metadata text + avg_energy = envelope.get("avg_energy", 0) + avg_duration = envelope.get("target_duration", 0) / 60.0 + title = f"{profile_name} ({avg_duration:.0f} min, ~{avg_energy:.2f} kWh)" + + svg = f""" + + + + + + + {int(max_power)}W + {int(max_time / 60)}m + {title} + + + + + + + """ + + return svg + + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Error generating SVG for %s: %s", profile_name, e) + return None + + + + def generate_profile_spaghetti_svg( + self, profile_name: str, overview_suffix: str = "Overview" + ) -> tuple[str | None, dict[str, str]]: + """ + Generate a 'Spaghetti Plot' SVG showing ALL individual cycles for a profile. + Returns (svg_string, cycle_metadata_map). + """ + # Get ALL completed cycles labeled with this profile + labeled_cycles = [ + c + for c in self._data["past_cycles"] + if c.get("profile_name") == profile_name + and c.get("status") in ("completed", "force_stopped") + ] + + if not labeled_cycles: + return None, {} + + # Sort by date + labeled_cycles.sort(key=lambda x: x["start_time"]) + + palette = [ + "#e6194b", "#3cb44b", "#ffe119", "#4363d8", "#f58231", + "#911eb4", "#42d4f4", "#f032e6", "#bfef45", "#fabed4", + "#469990", "#dcbeff", "#9A6324", "#fffac8", "#800000", + "#aaffc3", "#808000", "#ffd8b1", "#000075", "#a9a9a9", + ] + + cycle_metadata: dict[str, str] = {} + svg_curves: list[SVGCurve] = [] + + for i, cycle in enumerate(labeled_cycles): + power_data_raw = cycle.get("power_data", []) + cid = cycle["id"] + + # Decompress + pairs: list[tuple[float, float]] = [] + if isinstance(power_data_raw, list): + for item in cast(list[Any], power_data_raw): + if isinstance(item, (list, tuple)): + item_seq = cast(list[Any] | tuple[Any, ...], item) + if len(item_seq) < 2: + continue + try: + pairs.append((float(item_seq[0]), float(item_seq[1]))) + except (ValueError, TypeError): + continue + + if len(pairs) < 3: + continue + + offsets = [p[0] for p in pairs] + values = [p[1] for p in pairs] + + if not offsets: + continue + + # Assign color + color = palette[i % len(palette)] + cycle_metadata[cid] = color + + # Subsample for rendering performance + step = max(1, len(pairs) // 500) + subsampled_points = [(offsets[j], values[j]) for j in range(0, len(pairs), step)] + + svg_curves.append(SVGCurve( + points=subsampled_points, + color=color, + opacity=0.8, + stroke_width=2 + )) + + if not svg_curves: + return None, {} + + svg_content = _generate_generic_svg( + title=f"{profile_name} ({overview_suffix})", + curves=svg_curves, + width=1000, + height=400 + ) + + return svg_content, cycle_metadata + + def generate_preview_svg( + self, + power_data: list[tuple[str, float]], + head_trim: float, + tail_trim: float, + title: str = "Recording Preview", + trim_start_label: str = "Trim Start", + trim_end_label: str = "Trim End", + ) -> str: + """ + Generate a preview SVG for a recorded cycle, highlighting trimmed areas. + Blue = Keep, Red = Trim. + """ + if not power_data: + return "" + + # Parse data + points: list[tuple[float, float]] = [] + try: + start_dt = dt_util.parse_datetime(power_data[0][0]) + if start_dt is None: + return "" + start_ts = start_dt.timestamp() + for t_str, p in power_data: + parsed = dt_util.parse_datetime(t_str) + if parsed is None: + continue + t = parsed.timestamp() - start_ts + points.append((t, float(p))) + except (ValueError, TypeError, IndexError): + return "" + + if not points: + return "" + + total_duration = points[-1][0] + + keep_start = head_trim + keep_end = max(keep_start, total_duration - tail_trim) + + # Prepare curves + curves: list[SVGCurve] = [] + + # 1. Background (All Red) + curves.append(SVGCurve( + points=points, + color="#e6194b", + opacity=0.5, + stroke_width=2 + )) + + # 2. Keep (Blue) + keep_points = [pt for pt in points if keep_start <= pt[0] <= keep_end] + if keep_points: + curves.append(SVGCurve( + points=keep_points, + color="#4363d8", + opacity=1.0, + stroke_width=2 + )) + + # Markers + markers: list[dict[str, Any]] = [ + {"x": keep_start, "label": trim_start_label, "color": "#e6194b"}, + {"x": keep_end, "label": trim_end_label, "color": "#e6194b"}, + ] + + return _generate_generic_svg( + title=title, + curves=curves, + width=800, + height=400, + markers=markers + ) + + def get_envelope(self, profile_name: str) -> JSONDict | None: + """Get cached envelope for a profile, or None if not available.""" + envelopes = self._data.get("envelopes", {}) + if isinstance(envelopes, dict): + envelopes_map = cast(dict[str, Any], envelopes) + env = envelopes_map.get(profile_name) + return cast(JSONDict, env) if isinstance(env, dict) else None + return None + + def generate_feedback_comparison_svg( + self, profile_name: str, actual_cycle: CycleDict + ) -> str | None: + """Generate SVG comparing expected profile envelope with actual recorded cycle. + + Displays: + - Light blue band: min/max envelope from all labeled cycles + - Darker blue line: average expected profile + - Orange line: actual recorded power data from the cycle + + Args: + profile_name: Name of the detected/expected profile + actual_cycle: CycleDict with power_data and duration + + Returns: + SVG string or None if data unavailable + """ + try: + # Get envelope for the profile + envelope = self.get_envelope(profile_name) + if not envelope or not envelope.get("time_grid"): + return None + + # Decompress actual cycle power data (handles both ISO-timestamp and offset formats) + actual_pairs = decompress_power_data(actual_cycle) + + if len(actual_pairs) < 3: + return None + + # Extract envelope curves (already have [t, y] format) + time_grid = envelope["time_grid"] + avg_curve = envelope.get("avg", []) + min_curve = envelope.get("min", []) + max_curve = envelope.get("max", []) + + if not avg_curve or not min_curve or not max_curve: + return None + + # Build envelope curves for SVG + avg_points = [(p[0], p[1]) for p in avg_curve] + min_points = [(p[0], p[1]) for p in min_curve] + max_points = [(p[0], p[1]) for p in max_curve] + + # For the expected envelope band, we'll create a special visualization + # Canvas configuration (same as profile stats) + width, height = 1200, 450 + + # Use max time from actual data or envelope, whichever is larger + max_time_envelope = time_grid[-1] if time_grid else 1.0 + max_time_actual = actual_pairs[-1][0] if actual_pairs else 1.0 + max_time = max(max_time_envelope, max_time_actual) + + # Determine max power for scaling + all_power = ( + [p[1] for p in min_curve] + + [p[1] for p in avg_curve] + + [p[1] for p in max_curve] + + [p[1] for p in actual_pairs] + ) + max_power = max(all_power, default=1.0) * 1.05 + + # Build SVG curves + svg_curves: list[SVGCurve] = [] + + # 1. Envelope band (min/max as polygon fill) + envelope_band_points = ( + max_points + + list(reversed(min_points)) + ) + svg_curves.append(SVGCurve( + points=envelope_band_points, + color="#3498db", + opacity=0.3, + stroke_width=0, + is_polygon=True, + )) + + # 2. Average curve (darker blue line) + svg_curves.append(SVGCurve( + points=avg_points, + color="#3498db", + opacity=1.0, + stroke_width=4 + )) + + # 3. Actual cycle (orange line) + svg_curves.append(SVGCurve( + points=actual_pairs, + color="#f39c12", + opacity=0.95, + stroke_width=3 + )) + + # Get profile info for title + profile = self.get_profile(profile_name) + avg_duration = ( + profile.get("avg_duration", 0) / 60.0 + if profile + else max_time / 60.0 + ) + avg_energy = ( + profile.get("avg_energy") + if profile + else envelope.get("avg_energy", 0) + ) + + title = ( + f"Power Profile Comparison: {profile_name} " + f"({avg_duration:.0f}m, ~{avg_energy:.2f}kWh)" + ) + + # Create SVG using generic generator + svg = _generate_generic_svg( + title=title, + curves=svg_curves, + width=width, + height=height, + max_x_override=max_time, + max_y_override=max_power + ) + + # Add a single-row legend below the chart + if svg: + legend_height = 34 + total_height = height + legend_height + svg = svg.replace( + f'viewBox="0 0 {width} {height}"', + f'viewBox="0 0 {width} {total_height}"', + 1 + ) + ly = height + 22 # Vertical mid-line for all legend items + legend = ( + f'\n' + f'\n' + # Item 1: band swatch + f' \n' + f' ' + f'Expected range\n' + # Item 2: avg line + f' \n' + f' ' + f'Average profile\n' + # Item 3: actual line + f' \n' + f' ' + f'This cycle (actual)\n' + f'\n' + ) + return svg.replace("", legend + "", 1) + + return svg + + except Exception: # pylint: disable=broad-exception-caught + self._logger.exception("Error generating feedback comparison SVG") + return None + + def generate_feedback_multi_profile_svg( + self, + profile_names: list[str], + detected_profile: str, + actual_cycle: CycleDict, + chart_title_prefix: str = "Profile Comparison", + actual_cycle_label: str = "This cycle (actual)", + ) -> str | None: + """Generate a single SVG overlaying all profiles' avg curves with the actual cycle. + + The detected profile also shows a min/max envelope band. + Each profile gets a distinct colour; the actual cycle is orange. + A compact multi-column legend is appended below the chart. + """ + try: + # Colours: orange (#f39c12) is reserved for the actual cycle + palette = [ + "#3498db", # blue – detected profile (matches envelope tint) + "#2ecc71", # green + "#9b59b6", # purple + "#e74c3c", # red + "#1abc9c", # teal + "#f1c40f", # yellow + "#36a2eb", # sky-blue + "#8e44ad", # dark purple + "#16a085", # dark teal + "#c0392b", # dark red + ] + + # Load envelope data for every profile that has one + profile_envs: dict[str, JSONDict] = {} + for pname in profile_names: + env = self.get_envelope(pname) + if env and env.get("time_grid") and env.get("avg"): + profile_envs[pname] = env + + if not profile_envs: + return None + + # Decompress actual cycle power data (handles both ISO-timestamp and offset formats) + actual_pairs = decompress_power_data(actual_cycle) + + if len(actual_pairs) < 3: + return None + + # Global bounds + max_time = actual_pairs[-1][0] + for env in profile_envs.values(): + tg = env.get("time_grid", []) + if tg: + max_time = max(max_time, tg[-1]) + + all_power: list[float] = [p[1] for p in actual_pairs] + for env in profile_envs.values(): + all_power += [p[1] for p in env.get("max", [])] + all_power += [p[1] for p in env.get("avg", [])] + max_power = max(all_power, default=1.0) * 1.05 + + # Canvas + width, height = 1200, 450 + padding_x, padding_y = 60, 45 + graph_w = width - 2 * padding_x + graph_h = height - 2 * padding_y + + def _x(t: float) -> str: + return f"{padding_x + (t / max_time) * graph_w:.1f}" if max_time > 0 else str(padding_x) + + def _y(p: float) -> str: + return f"{height - padding_y - (p / max_power) * graph_h:.1f}" if max_power > 0 else str(height - padding_y) + + # Assign colours; detected profile always gets palette[0] + colors: dict[str, str] = {} + color_idx = 1 + if detected_profile in profile_envs: + colors[detected_profile] = palette[0] + for pname in profile_names: + if pname in profile_envs and pname != detected_profile: + colors[pname] = palette[color_idx % len(palette)] + color_idx += 1 + + elems: list[str] = [] + + # Background + axes + elems.append( + f'' + ) + elems.append( + f'' + ) + elems.append( + f'' + ) + elems.append( + f'{int(max_power)}W' + ) + elems.append( + f'{int(max_time / 60)}m' + ) + elems.append( + f'{chart_title_prefix}: {detected_profile}' + ) + + # Detected-profile envelope band (drawn first, behind all lines) + if detected_profile in profile_envs: + env = profile_envs[detected_profile] + max_c = env.get("max", []) + min_c = env.get("min", []) + if max_c and min_c: + fwd = " ".join(f"{_x(p[0])},{_y(p[1])}" for p in max_c) + rev = " ".join(f"{_x(p[0])},{_y(p[1])}" for p in reversed(min_c)) + band_color = colors.get(detected_profile, palette[0]) + elems.append( + f'' + ) + + # Average lines for every profile + for pname in profile_names: + if pname not in profile_envs: + continue + avg_c = profile_envs[pname].get("avg", []) + if not avg_c: + continue + color = colors.get(pname, "#aaa") + pts = " ".join(f"{_x(p[0])},{_y(p[1])}" for p in avg_c) + sw = 4 if pname == detected_profile else 2 + elems.append( + f'' + ) + + # Actual cycle on top + actual_pts = " ".join(f"{_x(p[0])},{_y(p[1])}" for p in actual_pairs) + elems.append( + f'' + ) + + # Legend (compact multi-column below the chart) + legend_items: list[tuple[str, str, int]] = [] # (color, label, stroke_width) + for pname in profile_names: + if pname not in profile_envs: + continue + color = colors.get(pname, "#aaa") + label = f"\u2605 {pname}" if pname == detected_profile else pname + legend_items.append((color, label, 4 if pname == detected_profile else 2)) + legend_items.append(("#f39c12", actual_cycle_label, 3)) + + items_per_row = 3 + col_w = (width - 2 * padding_x) // items_per_row + row_h = 34 + n_rows = (len(legend_items) + items_per_row - 1) // items_per_row + legend_h = n_rows * row_h + 22 + total_height = height + legend_h + + leg_elems: list[str] = [] + for i, (color, label, sw) in enumerate(legend_items): + col = i % items_per_row + row = i // items_per_row + lx = padding_x + col * col_w + ly = height + 26 + row * row_h + leg_elems.append( + f'' + ) + max_chars = 22 + display = label[:max_chars] + "\u2026" if len(label) > max_chars else label + leg_elems.append( + f'{display}' + ) + + return ( + f'\n' + + "\n".join(elems) + + "\n\n" + + "\n".join(leg_elems) + + "\n" + ) + + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Error generating multi-profile comparison SVG: %s", e) + return None + + def get_match_candidates_summary( + self, match_result: MatchResult, limit: int = 3 + ) -> list[dict[str, Any]]: + """Extract top candidates from query result for UI display. + + Args: + match_result: MatchResult from profile matching + limit: Number of top candidates to return + + Returns: + List of dicts with keys: profile_name, confidence_pct, mae, correlation, duration_ratio + """ + candidates: list[dict[str, Any]] = [] + + for candidate in match_result.ranking[:limit]: + try: + confidence_pct = round(candidate.get("score", 0.0) * 100, 1) + metrics = candidate.get("metrics", {}) + mae = round(metrics.get("mae", 0.0), 2) + corr = round(metrics.get("corr", 0.0), 3) + + profile_duration = candidate.get("profile_duration", 0.0) + actual_duration = match_result.expected_duration + duration_ratio = ( + round((actual_duration / profile_duration - 1.0) * 100, 1) + if profile_duration > 0 + else 0.0 + ) + + candidates.append({ + "profile_name": candidate.get("name", "Unknown"), + "confidence_pct": confidence_pct, + "mae": mae, + "correlation": corr, + "duration_ratio": duration_ratio, # ±% from expected + }) + except (TypeError, ValueError, KeyError): + continue + + return candidates + + def _get_cached_sample_segment( + self, sample_cycle: dict[str, Any], dt: float + ) -> Segment | None: + """Get or compute resampled segment for a sample cycle, using cache.""" + cycle_id = sample_cycle.get("id") + if not cycle_id: + return None + + # Round dt to avoid float cache misses + dt_key = float(round(dt, 2)) + key = (cycle_id, dt_key) + + if key in self._cached_sample_segments: + return self._cached_sample_segments[key] + + # Miss: Compute + sample_data = sample_cycle.get("power_data") + if not sample_data: + return None + + try: + if len(sample_data) > 0 and isinstance(sample_data[0], (list, tuple)): + s_ts = np.array([x[0] for x in sample_data]) + s_p = np.array([x[1] for x in sample_data]) + else: + return None + + s_segments = resample_uniform(s_ts, s_p, dt_s=dt, gap_s=21600.0) + if not s_segments: + return None + + sample_seg = max(s_segments, key=lambda s: len(s.power)) + + # Store + self._cached_sample_segments[key] = sample_seg + return sample_seg + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.warning("Error caching sample segment %s: %s", cycle_id, e) + return None + + async def async_match_profile( + self, + current_power_data: list[tuple[str, float]] | list[tuple[datetime, float]] | list[tuple[float, float]] | list[list[float]], + current_duration: float, + ) -> MatchResult: + """Run profile matching asynchronously in executor.""" + # 1. Prepare data in main thread (Access ProfileStore state safely) + + # Convert to list of floats for current power (uniform resampling) + if not current_power_data: + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0) + + # Pre-process current data + try: + # Normalize input format + first_elem = current_power_data[0][0] + if isinstance(first_elem, datetime): + # datetime objects: compute relative timestamps + t_start = first_elem.timestamp() + ts_arr = np.array([(x[0].timestamp() - t_start) for x in cast(list[tuple[datetime, float]], current_power_data)]) + elif isinstance(first_elem, (int, float)): + # Already offset timestamps (from compressed format) + ts_arr = np.array([float(x[0]) for x in cast(list[tuple[float, float]], current_power_data)]) + else: + # ISO format strings + t_start = datetime.fromisoformat(first_elem).timestamp() + ts_arr = np.array( + [ + (datetime.fromisoformat(x[0]).timestamp() - t_start) + for x in cast(list[tuple[str, float]], current_power_data) + ] + ) + + p_arr = np.array([float(x[1]) for x in current_power_data]) + + # Resample current + segments, used_dt = resample_adaptive(ts_arr, p_arr, min_dt=5.0, gap_s=21600.0) + if not segments: + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0) + current_seg = max(segments, key=lambda s: len(s.power)) + if len(current_seg.power) < 12: + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0) + + current_power_list = current_seg.power.tolist() + + # Prepare Snapshots + snapshots: list[dict[str, Any]] = [] + skipped_profiles: list[str] = [] + for name, profile in self._data["profiles"].items(): + # Try sample_cycle_id first, fall back to any labeled cycle + sample_id = profile.get("sample_cycle_id") + sample_cycle = None + if sample_id: + sample_cycle = next( + (c for c in self._data["past_cycles"] if c["id"] == sample_id), + None + ) + # Fallback: find ANY completed cycle labeled with this profile + if not sample_cycle: + sample_cycle = next( + (c for c in self._data["past_cycles"] + if c.get("profile_name") == name + and c.get("status") in ("completed", "force_stopped") + and c.get("power_data")), + None + ) + # Prefer envelope avg curve when ≥2 labeled cycles have been + # confirmed - it gives a more representative reference signal + # than the original sample alone, so confidence improves over + # time as the user keeps confirming correct detections. + envelope = self._data.get("envelopes", {}).get(name) + _env_avg = envelope.get("avg") if envelope else None + if ( + envelope + and envelope.get("cycle_count", 0) >= 2 + and _env_avg + and isinstance(_env_avg[0], (list, tuple)) + and len(_env_avg[0]) >= 2 + ): + avg_y = [float(p[1]) for p in _env_avg] + _env_ts_duration = ( + float(_env_avg[-1][0]) - float(_env_avg[0][0]) + if len(_env_avg) > 1 else 0.0 + ) + avg_duration = ( + envelope.get("target_duration") or + profile.get("avg_duration") or + _env_ts_duration or + None + ) + if not avg_duration: + skipped_profiles.append( + f"{name}: no valid duration (envelope has no target_duration, avg_duration, or timestamp span)" + ) + continue + snapshots.append({ + "name": name, + "avg_duration": float(avg_duration), + "sample_power": avg_y, + }) + continue + + if not sample_cycle: + skipped_profiles.append( + f"{name}: no sample cycle (sample_id={sample_id})" + ) + continue + + # Prepare sample segment (using cache) + sample_seg = self._get_cached_sample_segment(sample_cycle, used_dt) + if not sample_seg: + skipped_profiles.append( + f"{name}: failed to resample cycle {sample_cycle.get('id')}" + ) + continue + # avg_duration preference order: + # 1. profile["avg_duration"] (rolling average, most accurate) + # 2. sample_cycle["duration"] (raw cycle field) + # 3. timestamp span of sample_seg (estimate from the resampled data) + # Profiles created before avg_duration tracking was added may have + # 0 or a missing value; falling back to the segment estimate prevents + # update_match() from always seeing expected_duration=0, which + # silences time-remaining estimates and logs a misleading warning. + _seg_ts_duration = ( + float(sample_seg.timestamps[-1]) - float(sample_seg.timestamps[0]) + if len(sample_seg.timestamps) > 1 else 0.0 + ) + avg_dur = ( + profile.get("avg_duration") or + sample_cycle.get("duration") or + _seg_ts_duration + ) + if not avg_dur: + skipped_profiles.append( + f"{name}: no valid duration (avg_duration, cycle duration, and timestamp span all zero/missing)" + ) + continue + snapshots.append({ + "name": name, + "avg_duration": float(avg_dur), + "sample_power": sample_seg.power.tolist(), + "sample_dt": used_dt + }) + + if skipped_profiles: + self._logger.debug( + "Profile matching skipped %d profiles: %s", + len(skipped_profiles), + "; ".join(skipped_profiles) + ) + + config = { + "min_duration_ratio": self._min_duration_ratio, + "max_duration_ratio": self._max_duration_ratio, + "dtw_bandwidth": self.dtw_bandwidth + } + + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.error("Preparation for async match failed: %s", e) + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0) + + # 2. Run Heavy Logic in Executor + candidates = await self.hass.async_add_executor_job( + analysis.compute_matches_worker, + current_power_list, + current_duration, + cast(Any, snapshots), + config + ) + + # 3. Process Result (Main Thread) + if not candidates: + profiles_count = len(self._data.get("profiles", {})) + snapshots_count = len(snapshots) if 'snapshots' in dir() else 0 + self._logger.debug( + "No profile match candidates: profiles=%d, snapshots=%d, " + "duration=%.0fs. Possible reasons: duration ratio filter, " + "no labeled cycles, or no profiles defined.", + profiles_count, + snapshots_count, + current_duration + ) + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0, [], {}, is_confident_mismatch=True, mismatch_reason="all_rejected") + + best = candidates[0] + + # Reconstruct MatchResult + # Need to handle margin/ambiguity + margin = 1.0 + if len(candidates) > 1: + margin = best["score"] - candidates[1]["score"] + + is_ambiguous = margin < 0.05 + + # Phase Detection (Sync on main thread, fast enough? Phase check is O(N) but simple bounds check) + # We can run check_phase_match logic here or defer it. + # Let's run it here since we have the data. + # But check_phase_match uses wrappers. + matched_phase = None + if best.get("name"): + # Always resolve phase for the matched profile so phase sensors can + # show user-assigned phase names even when confidence is moderate. + matched_phase = self.check_phase_match(best["name"], current_duration) + + return MatchResult( + best["name"], + best["score"], + best["profile_duration"], + matched_phase, + candidates[:5], # Ranking + is_ambiguous, + margin, + # Extra fields... + ) + + def match_profile( + self, power_data: list[tuple[str, float]], duration: float + ) -> MatchResult: + """Synchronous wrapper for matching (for use in executor tasks).""" + # Convert to list for worker + p_list = [p[1] for p in power_data] + + # Prepare snapshots safely + snapshots: list[dict[str, Any]] = [] + # Accessing self._data in thread is generally safe for reads if not modifying + for name, profile in self._data["profiles"].items(): + sample_id = profile.get("sample_cycle_id") + sample_cycle = next((c for c in self._data["past_cycles"] if c["id"] == sample_id), None) + if not sample_cycle: + continue + + # Decompress sample data + sample_p_data = self._decompress_power_data(sample_cycle) + if not sample_p_data: + continue + + snapshots.append({ + "name": name, + "avg_duration": profile.get("avg_duration", sample_cycle.get("duration", 0)), + "sample_power": [x[1] for x in sample_p_data], + }) + + config = { + "min_duration_ratio": self._min_duration_ratio, + "max_duration_ratio": self._max_duration_ratio, + "dtw_bandwidth": self.dtw_bandwidth + } + + candidates = analysis.compute_matches_worker( + p_list, duration, cast(Any, snapshots), config + ) + + if not candidates: + return MatchResult(None, 0.0, 0.0, None, [], False, 0.0) + + best = candidates[0] + + # Calculate ambiguity + margin = 1.0 + if len(candidates) > 1: + margin = best["score"] - candidates[1]["score"] + + is_ambiguous = margin < 0.05 + + return MatchResult( + best["name"], + best["score"], + best["profile_duration"], + None, + candidates, + is_ambiguous, + margin, + ranking=candidates, + ) + + async def async_verify_alignment( + self, + profile_name: str, + current_power_data: list[list[float]] | list[tuple[Any, ...]], + ) -> tuple[bool, float, float]: + """ + Verify if the current power trace aligns with an expected low-power region in the envelope. + Returns: (is_confirmed_low_power, mapped_envelope_time, mapped_envelope_power) + """ + envelope = self.get_envelope(profile_name) + if not envelope or not envelope.get("avg") or not current_power_data: + return False, 0.0, 9999.0 + + # Extract envelope curves + # "avg" can be list of [t, p] (new) or [p, ...] (legacy) + env_avg_raw = envelope.get("avg", []) + if not env_avg_raw: + return False, 0.0, 9999.0 + + try: + # Handle both formats: [[t, y], ...] (new) or [y, ...] (legacy) + if isinstance(env_avg_raw[0], (list, tuple)) and len(env_avg_raw[0]) >= 2: + # New format: [[t, y], ...] + env_points = cast(list[list[Any] | tuple[Any, ...]], env_avg_raw) + env_time = [float(p[0]) for p in env_points] + env_power = [float(p[1]) for p in env_points] + else: + # Legacy format: [y, ...] + env_values = cast(list[float | int], env_avg_raw) + env_power = [float(p) for p in env_values] + # Reconstruct time grid from envelope if available, or assume 60s intervals + env_time_raw = envelope.get("time_grid") + env_time = cast(list[float], env_time_raw) if isinstance(env_time_raw, list) else None + if not env_time or len(env_time) != len(env_power): + target_dur = float(envelope.get("target_duration", 0.0) or 0.0) + if target_dur > 0: + env_time = cast(list[float], np.linspace(0, target_dur, len(env_power)).tolist()) + else: + env_time = [float(i * 60) for i in range(len(env_power))] + except (TypeError, ValueError, IndexError) as e: + first_type_name = type(env_avg_raw[0]).__name__ if env_avg_raw else "None" + self._logger.error( + "Malformed envelope 'avg' data for %s. Type: %s, Length: %d, Error: %s", + profile_name, first_type_name, len(env_avg_raw), e + ) + return False, 0.0, 9999.0 + + try: + current_power_list = [float(x[1]) for x in current_power_data] + except Exception: # pylint: disable=broad-exception-caught + return False, 0.0, 9999.0 + + # Offload to worker + mapped_time, mapped_power, score = await self.hass.async_add_executor_job( + analysis.verify_profile_alignment_worker, + current_power_list, + env_power, + env_time, + self.dtw_bandwidth + ) + + # Verify if mapped power and alignment score indicate an expected low-power region. + # Thresholds: Expected power < 15W, Alignment score > 0.4 + is_confirmed = (mapped_power < 15.0) and (score > 0.4) + + return is_confirmed, mapped_time, mapped_power + + + # match_profile (sync) removed in favor of async_match_profile + + def check_phase_match(self, profile_name: str, duration: float) -> str | None: + """ + Check if the current duration aligns with a known phase in the profile. + Returns the phase name (e.g., 'Rinse', 'Spin') or None. + """ + profile = self._data["profiles"].get(profile_name) + if not profile: + return None + + phases = profile.get("phases", []) + if not phases: + return None + + phases_sorted = sorted( + phases, + key=lambda p: float(p.get("start", 0)), + ) + + for phase in phases_sorted: + p_start = phase.get("start", 0) + p_end = phase.get("end", 0) + if p_start <= duration <= p_end: + return str(phase.get("name", "Unknown")) + + # If duration is outside explicit bounds, keep a phase label anyway so + # entities avoid falling back to generic running/starting states. + if phases_sorted: + if duration < float(phases_sorted[0].get("start", 0)): + return str(phases_sorted[0].get("name", "Unknown")) + return str(phases_sorted[-1].get("name", "Unknown")) + + return None + + + + async def create_profile(self, name: str, source_cycle_id: str) -> None: + """Create a new profile from a past cycle.""" + cycle = next( + (c for c in self._data["past_cycles"] if c["id"] == source_cycle_id), None + ) + if not cycle: + raise ValueError("Cycle not found") + + cycle["profile_name"] = name + + self._data.setdefault("profiles", {})[name] = { + "avg_duration": cycle["duration"], + "sample_cycle_id": source_cycle_id, + } + + # Save to persist the label + await self.async_save() + + def list_profiles(self) -> list[dict[str, Any]]: + """List all profiles with metadata.""" + profiles: list[JSONDict] = [] + raw_profiles = self._data.get("profiles", {}) + profiles_map = ( + cast(dict[str, Any], raw_profiles) if isinstance(raw_profiles, dict) else {} + ) + for name, data in profiles_map.items(): + profile_meta = cast(JSONDict, data) if isinstance(data, dict) else {} + + # Calculate count and last_run + p_cycles = [ + c for c in self._data.get("past_cycles", []) + if c.get("profile_name") == name + ] + cycle_count = len(p_cycles) + + last_run = None + if p_cycles: + last_c = max(p_cycles, key=lambda x: x.get("start_time", "")) + last_run = last_c.get("start_time") + + # Fetch envelope stats + envelope = self.get_envelope(name) + avg_energy = envelope.get("avg_energy") if envelope else None + duration_std_dev = envelope.get("duration_std_dev") if envelope else None + + profiles.append( + { + "name": name, + "avg_duration": profile_meta.get("avg_duration", 0), + "min_duration": profile_meta.get("min_duration", 0), + "max_duration": profile_meta.get("max_duration", 0), + "sample_cycle_id": profile_meta.get("sample_cycle_id"), + "cycle_count": cycle_count, + "last_run": last_run, + "avg_energy": avg_energy, + "duration_std_dev": duration_std_dev, + } + ) + return sorted(profiles, key=lambda p: profile_sort_key(p.get("name", ""))) + + async def create_profile_standalone( + self, + name: str, + reference_cycle_id: str | None = None, + avg_duration: float | None = None, + ) -> None: + """Create a profile without immediately labeling a cycle. + If reference_cycle_id is provided, use that cycle's characteristics. + If avg_duration is provided (and no reference cycle), use it as baseline.""" + if name in self._data.get("profiles", {}): + raise ValueError(f"Profile '{name}' already exists") + + profile_data: JSONDict = {} + if reference_cycle_id: + cycle = next( + (c for c in self._data["past_cycles"] if c["id"] == reference_cycle_id), + None, + ) + if cycle: + profile_data = { + "avg_duration": cycle["duration"], + "sample_cycle_id": reference_cycle_id, + } + # Label the reference cycle with the new profile so that + # statistics are immediately populated after creation. + if not cycle.get("profile_name"): + cycle["profile_name"] = name + elif avg_duration is not None and avg_duration > 0: + profile_data = { + "avg_duration": float(avg_duration), + } + + # Create profile with minimal data (will be updated when cycles are labeled) + profile_data.setdefault("phases", []) + self._data.setdefault("profiles", {})[name] = profile_data + + # Build the envelope from any already-labeled cycles (e.g. reference cycle above) + await self.async_rebuild_envelope(name) + + await self.async_save() + self._logger.info("Created standalone profile '%s'", name) + + async def update_profile( + self, old_name: str, new_name: str, avg_duration: float | None = None + ) -> int: + """Update a profile's name and/or average duration. + Returns number of cycles updated (if renamed).""" + profiles = self._data.get("profiles", {}) + if old_name not in profiles: + raise ValueError(f"Profile '{old_name}' not found") + + # Handle Rename + renamed = False + if new_name != old_name: + if new_name in profiles: + raise ValueError(f"Profile '{new_name}' already exists") + + # Rename in profiles dict + profiles[new_name] = profiles.pop(old_name) + + # Rename in envelopes + if "envelopes" in self._data and old_name in self._data["envelopes"]: + self._data["envelopes"][new_name] = self._data["envelopes"].pop( + old_name + ) + + renamed = True + + target_name = new_name if renamed else old_name + + # Handle Duration Update + if avg_duration is not None and avg_duration > 0: + profiles[target_name]["avg_duration"] = float(avg_duration) + # If there's an envelope, we ideally update its target_duration too, + # but envelope is usually rebuilt from data. + # However, for manual profiles, envelope might be empty or theoretical. + # Let's log it. + self._logger.info( + "Updated baseline duration for '%s' to %ss", + target_name, + avg_duration, + ) + + # Update cycles and feedback if renamed + count = 0 + if renamed: + # 1. Update past cycles + for cycle in self._data.get("past_cycles", []): + if cycle.get("profile_name") == old_name: + cycle["profile_name"] = new_name + count += 1 + + # 2. Update pending feedback + pending = self.get_pending_feedback() + for req in pending.values(): + if req.get("detected_profile") == old_name: + req["detected_profile"] = new_name + + # 3. Update feedback history + history = self.get_feedback_history() + for record in history.values(): + if record.get("original_detected_profile") == old_name: + record["original_detected_profile"] = new_name + if record.get("corrected_profile") == old_name: + record["corrected_profile"] = new_name + + self._logger.info( + "Renamed profile '%s' to '%s', updated %s cycles and associated feedback", + old_name, + new_name, + count, + ) + + await self.async_save() + return count + + async def delete_profile(self, name: str, unlabel_cycles: bool = True) -> int: + """Delete a profile. + If unlabel_cycles=True, removes profile label from cycles. + If unlabel_cycles=False, cycles keep the label (orphaned). + Returns number of cycles affected.""" + if name not in self._data.get("profiles", {}): + raise ValueError(f"Profile '{name}' not found") + + # Delete profile + del self._data["profiles"][name] + + # Handle cycles + count = 0 + for cycle in self._data.get("past_cycles", []): + if cycle.get("profile_name") == name: + if unlabel_cycles: + cycle["profile_name"] = None + count += 1 + + await self.async_save() + action = "unlabeled" if unlabel_cycles else "orphaned" + self._logger.info("Deleted profile '%s', %s %s cycles", name, action, count) + return count + + async def clear_all_data(self) -> None: + """Clear all profiles, cycle data, and derived state.""" + self._data["past_cycles"] = [] + self._data["profiles"] = {} + self._data["envelopes"] = {} + self._data["suggestions"] = {} + self._data["feedback_history"] = {} + self._data["pending_feedback"] = {} + self._data["auto_adjustments"] = [] + self._data["active_cycle"] = None + self._data["last_active_save"] = None + self._cached_sample_segments = {} + await self.async_save() + self._logger.info("Cleared all WashData storage") + + async def assign_profile_to_cycle( + self, cycle_id: str, profile_name: str | None + ) -> None: + """Assign an existing profile to a cycle. Rebuilds envelope.""" + old_profile = None + cycle = next( + (c for c in self._data["past_cycles"] if c["id"] == cycle_id), None + ) + if not cycle: + raise ValueError(f"Cycle {cycle_id} not found") + + # Track old profile for envelope rebuild + old_profile = cycle.get("profile_name") + + if profile_name and profile_name not in self._data.get("profiles", {}): + raise ValueError(f"Profile '{profile_name}' not found. Create it first.") + + # Update cycle + cycle["profile_name"] = profile_name if profile_name else None + + # Update profile metadata if this is the first cycle + if profile_name: + profile = self._data["profiles"][profile_name] + if not profile.get("sample_cycle_id"): + profile["sample_cycle_id"] = cycle_id + profile["avg_duration"] = cycle["duration"] + + # Rebuild envelopes for affected profiles + if old_profile and old_profile != profile_name: + await self.async_rebuild_envelope(old_profile) # Old profile lost a cycle + if profile_name: + await self.async_rebuild_envelope(profile_name) # New profile gained a cycle + # Apply retention after labeling, in case profile now exceeds cap + await self.async_enforce_retention() + + await self.async_save() + self._logger.info("Assigned profile '%s' to cycle %s", profile_name, cycle_id) + # Trigger smart processing to potentially merge now-labeled cycle + await self.async_smart_process_history() + + async def auto_label_cycles( + self, confidence_threshold: float = 0.75, overwrite: bool = False + ) -> dict[str, int]: + """Auto-label cycles retroactively using profile matching. + + Args: + confidence_threshold: Min confidence to apply a label. + overwrite: If True, re-evaluates already labeled cycles. + + Returns stats: {labeled: int, relabeled: int, skipped: int, total: int} + """ + stats = {"labeled": 0, "relabeled": 0, "skipped": 0, "total": 0} + + cycles = self._data.get("past_cycles", []) + + # Filter down if not overwriting + if not overwrite: + target_cycles = [c for c in cycles if not c.get("profile_name")] + else: + target_cycles = cycles + + stats["total"] = len(target_cycles) + + for cycle in target_cycles: + # Reconstruct power data for matching + power_data = self._decompress_power_data(cycle) + if not power_data or len(power_data) < 10: + stats["skipped"] += 1 + continue + + # Try to match + result = await self.async_match_profile(power_data, cycle["duration"]) + + if result.best_profile and result.confidence >= confidence_threshold: + current_label = cycle.get("profile_name") + + # If overwriting, check if new match is different and better/valid + if current_label: + if current_label != result.best_profile: + cycle["profile_name"] = result.best_profile + cycle["match_confidence"] = float(result.confidence) + stats["relabeled"] += 1 + self._logger.info( + "Relabeled cycle %s: '%s' -> '%s' (confidence: %.2f)", + cycle["id"], + current_label, + result.best_profile, + result.confidence, + ) + else: + cycle["profile_name"] = result.best_profile + cycle["match_confidence"] = float(result.confidence) + stats["labeled"] += 1 + self._logger.info( + "Auto-labeled cycle %s as '%s' (confidence: %.2f)", + cycle["id"], + result.best_profile, + result.confidence, + ) + else: + stats["skipped"] += 1 + + if stats["labeled"] > 0 or stats["relabeled"] > 0: + await self.async_save() + # Trigger smart processing after bulk labeling + await self.async_smart_process_history() + + self._logger.info( + "Auto-labeling complete: %s labeled, %s relabeled, %s skipped", + stats["labeled"], + stats["relabeled"], + stats["skipped"], + ) + return stats + + async def async_backfill_match_confidence(self) -> int: + """Populate match_confidence for labeled cycles that predate the field. + + Runs the matcher once per cycle with profile_name set but no + match_confidence, and persists the resulting confidence if the same + profile is returned. Returns the number of cycles updated. Safe to + call repeatedly — already-backfilled cycles are skipped. + """ + cycles = self._data.get("past_cycles", []) or [] + updated = 0 + for cycle in cycles: + if cycle.get("match_confidence") is not None: + continue + profile_name = cycle.get("profile_name") + if not profile_name: + continue + power_data = self._decompress_power_data(cycle) + if not power_data or len(power_data) < 10: + continue + try: + result = await self.async_match_profile( + power_data, cycle.get("duration", 0) + ) + except Exception: # pylint: disable=broad-exception-caught + continue + if result.best_profile == profile_name and result.confidence > 0: + cycle["match_confidence"] = float(result.confidence) + updated += 1 + if updated: + await self.async_save() + self._logger.info("Backfilled match_confidence on %d cycles", updated) + return updated + + def _decompress_power_data(self, cycle: CycleDict) -> list[tuple[float, float]]: + """Decompress cycle power data for matching (wrapper).""" + return [(float(offset), float(power)) for offset, power in decompress_power_data(cycle)] + + async def async_save_cycle(self, cycle_data: dict[str, Any]) -> None: + """Add and save a cycle. Rebuilds envelope if cycle is labeled.""" + self.add_cycle(cycle_data) + + # If cycle has a profile, rebuild that profile's envelope + profile_name = cycle_data.get("profile_name") + if profile_name: + await self.async_rebuild_envelope(profile_name) + + await self.async_save() + # Trigger smart processing on new cycle + await self.async_smart_process_history() + + async def async_migrate_cycles_to_compressed(self) -> int: + """ + Migrate all cycles to the compressed format. + Ensures all cycles use [offset_seconds, power] format. + Returns number of cycles migrated. + """ + raw_cycles = self._data.get("past_cycles", []) + cycles: list[CycleDict] = ( + cast(list[CycleDict], raw_cycles) if isinstance(raw_cycles, list) else [] + ) + migrated = 0 + + for cycle in cycles: + raw_data: list[Any] = cycle.get("power_data", []) or [] + if not raw_data: + continue + + # Check if already compressed (first element is number or mixed format) + first_elem = raw_data[0][0] + if isinstance(first_elem, (int, float)): + # Already in offset format + continue + + # Old format: ISO timestamp strings. Convert to compressed offsets. + try: + compressed = compress_power_data(cycle) + if compressed: + cycle["power_data"] = compressed + migrated += 1 + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to migrate cycle %s: %s", cycle.get("id"), e) + continue + + if migrated > 0: + self._logger.info("Migrated %s cycles to compressed format", migrated) + await self.async_save() + + return migrated + + + + async def async_split_cycles_smart( + self, cycle_id: str, min_gap_s: int = 900, idle_power: float = 2.0 + ) -> list[str]: + """Scan a cycle for significant idle gaps and split if parts match better (offloaded).""" + cycles = cast(list[CycleDict], self._data.get("past_cycles", [])) + idx = next((i for i, c in enumerate(cycles) if c.get("id") == cycle_id), -1) + + if idx == -1: + return [] + + cycle = cycles[idx] + + # Offload analysis + seg_ranges = await self.hass.async_add_executor_job( + self.analyze_split_sync, cycle, min_gap_s, idle_power + ) + + if not seg_ranges: + return [cycle_id] + + # Apply Split (Main Thread) + cycles.pop(idx) + new_ids: list[str] = [] + original_profile = cycle.get("profile_name") + start_dt_base_parsed = _parse_start_dt(cycle["start_time"]) + if not start_dt_base_parsed: + # Should not happen as analyze checked it, but safety + self._logger.warning("Failed to parse start time during split apply for %s", cycle_id) + return [cycle_id] + + start_dt_base: datetime = start_dt_base_parsed + # Use decompress_power_data which handles all format variations + p_data_tuples = self._decompress_power_data(cycle) + + if not p_data_tuples: + self._logger.warning("Failed to decompress data during split for %s", cycle_id) + return [cycle_id] + + # Convert to relative seconds for array logic. + # _decompress_power_data returns (offset_seconds, power). + + points: list[tuple[float, float]] = [] + for offset_seconds, val in p_data_tuples: + points.append((float(offset_seconds), float(val))) + + for seg_start, seg_end in seg_ranges: + # Construct new cycle logic + seg_dur = seg_end - seg_start + new_cycle_start = start_dt_base + timedelta(seconds=seg_start) + new_cycle_start_ts = new_cycle_start.timestamp() + + # Extract points + p_data_abs: list[list[float]] = [] + state_val = 0.0 + for t, p in points: + if t <= seg_start: + state_val = p + else: + break + p_data_abs.append([round(new_cycle_start_ts, 1), state_val]) + + for t, p in points: + if seg_start < t <= seg_end: + if start_dt_base: + p_data_abs.append([round(start_dt_base.timestamp() + t, 1), p]) + + new_cycle: dict[str, Any] = { + "start_time": new_cycle_start.isoformat(), + "end_time": (new_cycle_start + timedelta(seconds=seg_dur)).isoformat(), + "duration": round(seg_dur, 1), + "status": "completed", + "power_data": p_data_abs, + "profile_name": None + } + self.add_cycle(new_cycle) + new_ids.append(new_cycle["id"]) + + # Fix profile refs (same as original logic) + original_sample_id = cycle.get("id") + best_replacement_id = None + longest_dur = 0 + new_cycles_objs = [c for c in cycles if c["id"] in new_ids] + + for c in new_cycles_objs: + d = c.get("duration", 0) + if d > longest_dur: + longest_dur = d + best_replacement_id = c["id"] + + if best_replacement_id and original_profile: + p_data = self._data["profiles"].get(original_profile) + if p_data and p_data.get("sample_cycle_id") == original_sample_id: + p_data["sample_cycle_id"] = best_replacement_id + + # Rebuild envelope because dataset changed + await self.async_rebuild_envelope(original_profile) + + await self.async_save() + return new_ids + + async def async_smart_process_history( + self + ) -> dict[str, int]: + # Orchestrate smart history processing: Cleanup, Retention. + # Split/Merge is now manual via Interactive Editor. + stats = {"cleaned_profiles": 0} + + # 1. Cleanup + self._logger.debug("Running maintenance: cleanup_orphaned_profiles") + stats["cleaned_profiles"] = self.cleanup_orphaned_profiles() + + # 2. Retention + self._logger.debug("Running maintenance: async_enforce_retention") + await self.async_enforce_retention() + + # 3. Save + self._logger.debug("Maintenance complete, saving") + await self.async_save() + + return stats + + + + def log_adjustment( + self, setting_name: str, old_value: Any, new_value: Any, reason: str + ) -> None: + # Log an automatic adjustment to a setting. + if old_value == new_value: + return + adjustment: JSONDict = { + "timestamp": dt_util.now().isoformat(), + "setting": setting_name, + "old_value": old_value, + "new_value": new_value, + "reason": reason, + } + self._data.setdefault("auto_adjustments", []).append(adjustment) + # Keep last 50 adjustments + if len(self._data["auto_adjustments"]) > 50: + self._data["auto_adjustments"] = self._data["auto_adjustments"][-50:] + self._logger.info( + "Auto-adjustment: %s changed from %s to %s (%s)", + setting_name, + old_value, + new_value, + reason, + ) + + def export_data( + self, entry_data: JSONDict | None = None, entry_options: JSONDict | None = None + ) -> JSONDict: + # Return a serializable snapshot of the store for backup/export. + # Includes config entry data/options so users can transfer fine-tuned settings. + return { + "version": STORAGE_VERSION, + "entry_id": self.entry_id, + "exported_at": dt_util.now().isoformat(), + "data": self._data, + "entry_data": entry_data or {}, + "entry_options": entry_options or {}, + } + + async def async_import_data(self, payload: dict[str, Any]) -> dict[str, Any]: + # Import data from JSON payload (migration aware). + # Unwrap HA diagnostics download file (outer HA wrapper: {home_assistant, data, ...}) + if "home_assistant" in payload and "data" in payload: + payload = payload["data"] + self._logger.info("Detected HA diagnostics file wrapper, unwrapping 'data'") + + # Unwrap our integration's diagnostics format ({entry, manager_state, store_export, ...}) + if "store_export" in payload: + payload = payload["store_export"] + self._logger.info("Detected diagnostics store_export format, unwrapping") + + version = payload.get("version", 1) + + # Handle v1 format (flat structure) - convert to v2 + if version == 1 or "data" not in payload: + # V1 format had profiles/past_cycles at top level + data_dict = { + "profiles": payload.get("profiles", {}), + "past_cycles": payload.get("past_cycles", []), + "envelopes": payload.get("envelopes", {}), + } + self._logger.info( + "Importing v1 format: %s cycles", len(data_dict.get("past_cycles", [])) + ) + else: + # V2 format with nested "data" key + data = payload.get("data") + if not isinstance(data, dict): + raise ValueError( + "Invalid export payload (missing or invalid 'data' key)" + ) + data_dict = cast(JSONDict, data) + self._logger.info( + "Importing v2 format: %s cycles", len(data_dict.get("past_cycles", [])) + ) + + # Validate and repair structure + if not isinstance(data_dict.get("profiles"), dict): + data_dict["profiles"] = {} + if not isinstance(data_dict.get("past_cycles"), list): + data_dict["past_cycles"] = [] + data_dict.setdefault("envelopes", {}) + + self._data = data_dict + self._cached_sample_segments = {} + await self.async_save() + + # Strip diagnostic redaction sentinels so they don't overwrite real settings + def _strip_redacted(d: dict) -> dict: + if not isinstance(d, dict): + return {} + return {k: v for k, v in d.items() if v != "**REDACTED**"} + + return { + "entry_data": _strip_redacted(payload.get("entry_data", {})), + "entry_options": _strip_redacted(payload.get("entry_options", {})), + } + + + async def delete_cycle(self, cycle_id: str) -> bool: + """Delete a cycle by ID.""" + cycles = cast(list[CycleDict], self._data.get("past_cycles", [])) + initial_len = len(cycles) + cycle_to_delete = next((c for c in cycles if c.get("id") == cycle_id), None) + if not cycle_to_delete: + return False + + profile_name = cycle_to_delete.get("profile_name") + self._data["past_cycles"] = [c for c in cycles if c.get("id") != cycle_id] + + if len(self._data["past_cycles"]) < initial_len: + # Check profile references + for _p_name, p_data in self.get_profiles().items(): + if p_data.get("sample_cycle_id") == cycle_id: + p_data["sample_cycle_id"] = None + + # Rebuild envelope if cycle belonged to a profile + if profile_name: + await self.async_rebuild_envelope(profile_name) + + await self.async_save() + return True + return False + + def get_cycle_power_data(self, cycle_id: str) -> list[tuple[float, float]]: + """Return decompressed power data for a cycle as [(offset_s, watts), ...]. + + Returns an empty list if the cycle is not found or has no power data. + """ + cycle = next( + (c for c in self.get_past_cycles() if c.get("id") == cycle_id), None + ) + if cycle is None: + return [] + return self._decompress_power_data(cycle) + + async def trim_cycle_power_data( + self, + cycle_id: str, + new_start_s: float, + new_end_s: float, + ) -> bool: + """Trim a cycle's power_data to the window [new_start_s, new_end_s]. + + Offsets are renormalized so the kept segment starts at 0.0. + The cycle's ``duration``, ``signature``, and ``sampling_interval`` are + recomputed from the trimmed data. + + Returns True if successful, False if the cycle was not found or the + resulting data is empty. + """ + cycles = cast(list[CycleDict], self._data.get("past_cycles", [])) + cycle = next((c for c in cycles if c.get("id") == cycle_id), None) + if cycle is None: + return False + + p_data = self._decompress_power_data(cycle) + if not p_data: + return False + + new_start_s = max(0.0, float(new_start_s)) + new_end_s = float(new_end_s) + + kept = sorted( + ( + (offset, power) + for offset, power in p_data + if new_start_s <= offset <= new_end_s + ), + key=lambda x: x[0], + ) + if not kept: + return False + + # Re-normalize offsets so the trimmed segment starts at 0.0 + base = kept[0][0] + renorm: list[list[float]] = [ + [round(offset - base, 2), power] for offset, power in kept + ] + + # Advance start_time when trimming from the front + if base > 0: + start_ts = _value_to_timestamp(cycle.get("start_time")) + if start_ts is not None: + cycle["start_time"] = dt_util.utc_from_timestamp( + start_ts + base + ).isoformat() + + # Recompute sampling interval + if len(renorm) > 1: + offsets_arr = np.array([r[0] for r in renorm], dtype=float) + intervals = np.diff(offsets_arr) + pos = intervals[intervals > 0] + sampling_interval = float(np.median(pos)) if len(pos) > 0 else 1.0 + else: + sampling_interval = 1.0 + + # Recompute signature + ts_arr = np.array([r[0] for r in renorm], dtype=float) + p_arr = np.array([r[1] for r in renorm], dtype=float) + if len(ts_arr) > 1: + sig = compute_signature(ts_arr, p_arr) + cycle["signature"] = dataclasses.asdict(sig) + else: + cycle["signature"] = None + + new_duration = round(renorm[-1][0], 1) if renorm else 0.0 + cycle["power_data"] = renorm + cycle["sampling_interval"] = round(sampling_interval, 1) + cycle["duration"] = new_duration + + # Keep end_time consistent with the updated start_time and duration + new_start_ts = _value_to_timestamp(cycle.get("start_time")) + if new_start_ts is not None: + cycle["end_time"] = dt_util.utc_from_timestamp( + new_start_ts + new_duration + ).isoformat() + + # Clear manual_duration override - trimmed duration is now authoritative + cycle.pop("manual_duration", None) + + # Invalidate cached sample segments for this cycle so future lookups + # are recomputed from the trimmed data + stale_keys = [k for k in self._cached_sample_segments if k[0] == cycle_id] + for k in stale_keys: + del self._cached_sample_segments[k] + + # Rebuild envelope for the associated profile + profile_name = cycle.get("profile_name") + if profile_name: + await self.async_rebuild_envelope(profile_name) + + await self.async_save() + return True + + def analyze_split_sync( + self, cycle: CycleDict, min_gap_s: int = 900, idle_power: float = 2.0 + ) -> list[tuple[float, float]]: + """Analyze cycle for potential splits (sync for executor).""" + p_data = self._decompress_power_data(cycle) + if not p_data: + return [] + + # Parse all points to (rel_t, power) + points: list[tuple[float, float]] = [] + for offset_seconds, val in p_data: + points.append((float(offset_seconds), float(val))) + + if not points: + return [] + + valid_segments: list[tuple[float, float]] = [] + seg_start = 0.0 + + for i in range(1, len(points)): + t, _ = points[i] + prev_t, prev_p = points[i-1] + + # Detect idle gap + gap = t - prev_t + if prev_p < idle_power and gap > min_gap_s: + # Segment ending at prev_t + if (prev_t - seg_start) > 60: + valid_segments.append((seg_start, prev_t)) + seg_start = t + + # Last segment + last_t = points[-1][0] + if (last_t - seg_start) > 60: + valid_segments.append((seg_start, last_t)) + + if len(valid_segments) < 2: + return [] + + self._logger.debug( + "Analyzed split for %s: found %d segments", + cycle.get("id"), + len(valid_segments) + ) + return valid_segments + + def build_split_segments_from_offsets( + self, + cycle: CycleDict, + split_offsets_s: list[float], + min_segment_s: float = 60.0, + ) -> list[tuple[float, float]]: + """Build segments for a manual split from explicit offsets (seconds from cycle start). + + Returns adjacent [(start, end)] segments covering the cycle window, split at the + given offsets. Offsets are sorted and deduplicated; offsets outside the cycle window + or producing a sub-`min_segment_s` slice are dropped. Returns [] if fewer than two + segments would result. + """ + p_data = self._decompress_power_data(cycle) + if not p_data: + return [] + + last_t = float(p_data[-1][0]) + if last_t <= 0: + return [] + + unique_offsets = sorted( + {round(float(o), 3) for o in split_offsets_s if 0.0 < float(o) < last_t} + ) + if not unique_offsets: + return [] + + filtered_offsets: list[float] = [] + for offset in unique_offsets: + if offset <= min_segment_s: + continue + if offset >= (last_t - min_segment_s): + continue + if filtered_offsets and (offset - filtered_offsets[-1]) < min_segment_s: + continue + filtered_offsets.append(offset) + + if not filtered_offsets: + return [] + + boundaries = [0.0, *filtered_offsets, last_t] + segments: list[tuple[float, float]] = [] + for i in range(len(boundaries) - 1): + seg_start = boundaries[i] + seg_end = boundaries[i + 1] + if (seg_end - seg_start) >= min_segment_s: + segments.append((seg_start, seg_end)) + + if len(segments) < 2: + return [] + + self._logger.debug( + "Built manual split for %s: %d segments at offsets %s", + cycle.get("id"), + len(segments), + filtered_offsets, + ) + return segments + + async def apply_split_interactive( + self, cycle_id: str, segments: list[dict[str, Any]] + ) -> list[str]: + """Apply a manual split config. + + segments format: [{"start": float, "end": float, "profile": str|None}] + Returns list of new cycle IDs. + """ + cycles = cast(list[CycleDict], self._data.get("past_cycles", [])) + idx = next((i for i, c in enumerate(cycles) if c.get("id") == cycle_id), -1) + + if idx == -1: + return [] + + cycle = cycles[idx] + cycles.pop(idx) # Remove original + + new_ids: list[str] = [] + original_profile = cycle.get("profile_name") + start_dt_base_parsed = _parse_start_dt(cycle["start_time"]) + if not start_dt_base_parsed: + return [] + + start_ts = start_dt_base_parsed.timestamp() + + # Decompress original data + p_data_tuples = self._decompress_power_data(cycle) + if not p_data_tuples: + return [] + + # Prepare points (relative seconds) + points: list[tuple[float, float]] = [] + for offset_seconds, val in p_data_tuples: + points.append((float(offset_seconds), float(val))) + + # Create new cycles + for seg in segments: + if isinstance(seg, (list, tuple)): + seg_tuple = cast(tuple[Any, ...] | list[Any], seg) + seg_start = float(seg_tuple[0]) + seg_end = float(seg_tuple[1]) + seg_profile = None + else: + seg_start = float(seg["start"]) + seg_end = float(seg["end"]) + seg_profile = seg.get("profile") + + seg_dur = seg_end - seg_start + new_cycle_start = start_dt_base_parsed + timedelta(seconds=seg_start) + new_cycle_start_ts = new_cycle_start.timestamp() + + # Extract points for this segment + p_data_abs: list[list[float]] = [] + + # Find closest state before/at start to ensure continuity? + # Or just take points strictly inside? + # Generally better to capture the state at start 0. + state_val = 0.0 + for t, p in points: + if t <= seg_start: + state_val = p + else: + break + + # Start point (t=0 relative to new cycle) + p_data_abs.append([round(new_cycle_start_ts, 1), state_val]) + + for t, p in points: + if seg_start < t <= seg_end: + p_data_abs.append([round(start_ts + t, 1), p]) + + # Create Cycle Record + new_cycle: dict[str, Any] = { + "start_time": new_cycle_start.isoformat(), + "end_time": (new_cycle_start + timedelta(seconds=seg_dur)).isoformat(), + "duration": round(seg_dur, 1), + "status": "completed", + "power_data": p_data_abs, + "profile_name": seg_profile + } + self.add_cycle(new_cycle) + new_ids.append(new_cycle["id"]) + + # Fix profile refs (handle original sample cycle logic) + original_sample_id = cycle.get("id") + best_replacement_id = None + longest_dur = 0 + new_cycles_objs = [c for c in cycles if c["id"] in new_ids] # 'cycles' is mutated by add_cycle + + for c in new_cycles_objs: + d = c.get("duration", 0) + if d > longest_dur: + longest_dur = d + best_replacement_id = c["id"] + + if best_replacement_id and original_profile: + p_data = self._data["profiles"].get(original_profile) + if p_data and p_data.get("sample_cycle_id") == original_sample_id: + p_data["sample_cycle_id"] = best_replacement_id + + # Rebuild envelope because dataset changed + await self.async_rebuild_envelope(original_profile) + + await self.async_save() + self._logger.info("Interactive Split Applied to %s -> %s", cycle_id, new_ids) + return new_ids + + async def apply_merge_interactive( + self, cycle_ids: list[str], target_profile: str | None + ) -> str | None: + """ + Merge multiple past cycles into a single cycle record, filling gaps between traces with short zero-power segments. + + Parameters: + cycle_ids (list[str]): Unordered set of past-cycle IDs to merge; at least two IDs are required. + The function internally sorts cycles by start_time and mutates the chronologically earliest cycle. + target_profile (str | None): Profile name to assign to the merged cycle, or `None` to leave unlabeled. + + Description: + When successful, this sorts the provided cycles by start_time and replaces the earliest cycle with the merged cycle, + removing the other consumed cycles and updating related metadata. + + Side effects: + - Updates the store's past_cycles (removes consumed cycles and replaces the first cycle with the merged record). + - Clears any `manual_duration` override on the resulting cycle. + - Updates `sample_cycle_id` references in profiles that pointed to removed cycles. + - Attempts to recompute and store the merged cycle's signature. + - Persists changes to storage and triggers envelope rebuilds for affected profiles. + + Returns: + merged_id (str | None): The new merged cycle's ID if the merge was applied, `None` if the merge could not be performed. + """ + if len(cycle_ids) < 2: + return None + + cycles = self.get_past_cycles() + target_cycles = [c for c in cycles if c.get("id") in cycle_ids] + + if len(target_cycles) != len(cycle_ids): + return None + + # Sort by time - use timestamp comparison to handle mixed timezone offsets correctly + def _cycle_start_ts(c: CycleDict) -> float: + ts = _value_to_timestamp(c.get("start_time")) + return ts if ts is not None else float("inf") + + target_cycles.sort(key=_cycle_start_ts) + + # Collect affected profiles for envelope rebuild + affected_profiles: set[str] = set() + for c in target_cycles: + if c.get("profile_name"): + affected_profiles.add(c["profile_name"]) + if target_profile: + affected_profiles.add(target_profile) + + # We modify the first cycle (c1) to become the merged one + c1 = target_cycles[0] + ids_to_remove: list[str] = [] + + # Base setup + c1_start_dt = _parse_start_dt(c1["start_time"]) + if not c1_start_dt: + return None + + # Helper to get parsed points from a cycle + def get_points(cy: CycleDict) -> list[tuple[float, float, float]]: + # content: [(timestamp, offset, power)] + raw = self._decompress_power_data(cy) + res: list[tuple[float, float, float]] = [] + if not raw: + return [] + base_dt = _parse_start_dt(cy["start_time"]) + if base_dt is None: + return [] + base_t = base_dt.timestamp() + for offset_seconds, val in raw: + t_abs = base_t + float(offset_seconds) + res.append((t_abs, float(offset_seconds), float(val))) + return res + + # Start with C1 points + merged_points_abs: list[list[float]] = [] # [timestamp, power] + + # Add C1 points + c1_pts = get_points(c1) + for t_abs, _, p in c1_pts: + merged_points_abs.append([t_abs, p]) + + # Use the maximum t_abs seen so far (guards against out-of-order or corrupted points) + last_t_abs = max((pt[0] for pt in c1_pts), default=c1_start_dt.timestamp()) + + # Iterate others + max_power = c1.get("max_power", 0) + + for next_c in target_cycles[1:]: + c_start_dt = _parse_start_dt(next_c.get("start_time")) + if not c_start_dt: + continue + + c_pts = get_points(next_c) + if not c_pts: + continue + + current_start_ts = c_pts[0][0] + + # --- GAP FILLING --- + gap = current_start_ts - last_t_abs + # If gap > 1s, inject 0W points to ensure graph drops to 0 + if gap > 1.0: + merged_points_abs.append([last_t_abs + 0.1, 0.0]) + merged_points_abs.append([current_start_ts - 0.1, 0.0]) + + # Append points; track the running maximum to guard against reversed/corrupt data + for t_abs, _, p in c_pts: + merged_points_abs.append([t_abs, p]) + if t_abs > last_t_abs: + last_t_abs = t_abs + + max_power = max(max_power, next_c.get("max_power", 0)) + ids_to_remove.append(next_c["id"]) + + # Derive merged end time from power data when available; otherwise fall back to + # the end_time field of the last cycle (handles cycles without recorded power data). + if merged_points_abs: + # Use the maximum absolute timestamp from all collected data points + last_t_abs = max(pt[0] for pt in merged_points_abs) + final_end_dt = dt_util.utc_from_timestamp(last_t_abs) + else: + last_cycle = target_cycles[-1] + fallback_end_dt = _parse_start_dt(last_cycle.get("end_time")) + if fallback_end_dt is not None: + final_end_dt = fallback_end_dt + else: + final_end_dt = c1_start_dt + + new_dur = (final_end_dt - c1_start_dt).total_seconds() + + c1["end_time"] = final_end_dt.isoformat() + c1["duration"] = round(new_dur, 1) + c1["max_power"] = max_power + c1["profile_name"] = target_profile + # Remove manual_duration override so the freshly computed duration is shown + c1.pop("manual_duration", None) + + # Generate new compressed power_data [offset, power] + new_power_data: list[list[float]] = [] + c1_start_ts = c1_start_dt.timestamp() + + for t_abs, p in merged_points_abs: + offset = round(t_abs - c1_start_ts, 1) + new_power_data.append([offset, float(p)]) + + c1["power_data"] = new_power_data + + # New Hash ID + new_id = hashlib.sha256(f"{c1['start_time']}_{c1['duration']}".encode()).hexdigest()[:12] + old_c1_id = c1["id"] + c1["id"] = new_id + + # Update references in profiles + all_removed_ids = ids_to_remove + [old_c1_id] + for p_data in self.get_profiles().values(): + if p_data.get("sample_cycle_id") in all_removed_ids: + p_data["sample_cycle_id"] = new_id + + # Remove consumed cycles + self._data["past_cycles"] = [ + c for c in cycles if c.get("id") not in ids_to_remove + ] + + # Update signature + try: + ts_arr = np.array([pt[0] for pt in new_power_data], dtype=float) + p_arr = np.array([pt[1] for pt in new_power_data], dtype=float) + if len(ts_arr) > 1: + sig = compute_signature(ts_arr, p_arr) + c1["signature"] = dataclasses.asdict(sig) + except Exception as e: # pylint: disable=broad-exception-caught + self._logger.warning("Failed to update signature for merged cycle %s: %s", new_id, e) + + await self.async_save() + self._logger.info("Interactive Merge Applied: %s -> %s", cycle_ids, new_id) + + # Rebuild envelopes for all affected profiles + for p_name in affected_profiles: + await self.async_rebuild_envelope(p_name) + + return new_id + + def generate_interactive_split_svg( + self, + cycle_id: str, + segments: list[tuple[float, float]], + width: int = 600, + height: int = 300, + title_prefix: str = "Split Preview", + unlabeled_text: str = "Unlabeled", + ) -> str: + """Generate SVG for split preview.""" + cycle = next((c for c in self.get_past_cycles() if c["id"] == cycle_id), None) + if not cycle: + return "" + + p_data = self._decompress_power_data(cycle) + if not p_data: + return "" + + start_dt = _parse_start_dt(cycle["start_time"]) + if start_dt is None: + return "" + points: list[tuple[float, float]] = [] + for offset_seconds, val in p_data: + points.append((float(offset_seconds), float(val))) + + curves: list[SVGCurve] = [SVGCurve(points=points, color="#9E9E9E", opacity=0.5)] # Base ghost + markers: list[dict[str, Any]] = [] + + # Highlight Segments + colors = ["#2196F3", "#4CAF50", "#FF9800", "#9C27B0"] + for i, (seg_start, seg_end) in enumerate(segments): + seg_pts = [(t, p) for t, p in points if seg_start <= t <= seg_end] + if seg_pts: + color = colors[i % len(colors)] + curves.append(SVGCurve(points=seg_pts, color=color, stroke_width=2)) + markers.append({"x": seg_start, "label": f"S{i+1}", "color": color}) + + return _generate_generic_svg( + f"{title_prefix}: {cycle.get('profile_name') or unlabeled_text}", + curves, + width, + height, + markers=markers, + ) + + def generate_interactive_merge_svg( + self, + cycle_ids: list[str], + width: int = 600, + height: int = 300, + title: str = "Merge Preview", + no_data_label: str | None = None, + ) -> str: + """ + Generate an SVG preview that overlays power traces from the specified past cycles to illustrate the result of merging them. + + Cycles are ordered by their parsed start_time and each cycle's power data is aligned to the earliest cycle start to form overlaid curves. + + Parameters: + cycle_ids (list[str]): IDs of past cycles to include in the preview. + width (int): Width of the generated SVG in pixels. + height (int): Height of the generated SVG in pixels. + title (str): Title text shown in the SVG header. + no_data_label (str | None): Message rendered in the placeholder SVG when cycles are + present but contain no recorded power data. Defaults to None (empty message). + + Returns: + str: SVG markup for the merge preview. Returns an empty string if no valid cycles or + if the first cycle's start_time cannot be parsed. If cycles are present but none + contain power data, returns a placeholder SVG using no_data_label as the descriptive + message instead of a fixed string. + """ + cycles = [c for c in self.get_past_cycles() if c["id"] in cycle_ids] + + def _sort_ts(c: CycleDict) -> float: + """ + Provide a numeric sort key for a cycle by converting its `start_time` to a UNIX timestamp. + + Parameters: + c (CycleDict): Cycle mapping that may contain a `start_time` value in any parseable datetime form. + + Returns: + float: UNIX timestamp in seconds parsed from `start_time`, or `float('inf')` when `start_time` is missing or cannot be parsed so the cycle sorts after valid-dated cycles. + """ + dt = _parse_start_dt(c.get("start_time")) + return dt.timestamp() if dt is not None else float("inf") + + cycles.sort(key=_sort_ts) + + if not cycles: + return "" + + first_start_dt = _parse_start_dt(cycles[0].get("start_time")) + if first_start_dt is None: + return "" + first_start = first_start_dt.timestamp() + curves: list[SVGCurve] = [] + + colors = ["#2196F3", "#FF9800", "#4CAF50", "#9C27B0"] + + for i, c in enumerate(cycles): + p_data = self._decompress_power_data(c) + if not p_data: + continue + points: list[tuple[float, float]] = [] + cycle_start_raw = c.get("start_time") + cycle_start_dt = _parse_start_dt(cycle_start_raw) + if cycle_start_dt is None: + continue + cycle_start = cycle_start_dt.timestamp() + for offset_seconds, val in p_data: + rel_t = (cycle_start + float(offset_seconds)) - first_start + points.append((rel_t, float(val))) + + if points: + curves.append(SVGCurve(points=points, color=colors[i % len(colors)], stroke_width=2)) + + if not curves: + # No power data available - return a placeholder SVG with a message + safe_title = html.escape(title) + safe_label = html.escape(no_data_label or "") + return ( + f'' + f'' + f'{safe_title}' + f'{safe_label}' + f'' + ) + + return _generate_generic_svg(html.escape(title), curves, width, height) diff --git a/custom_components/ha_washdata/recorder.py b/custom_components/ha_washdata/recorder.py new file mode 100644 index 0000000..17b9bd1 --- /dev/null +++ b/custom_components/ha_washdata/recorder.py @@ -0,0 +1,317 @@ +"""Recorder for raw cycle data in WashData.""" +from __future__ import annotations + +import copy +import logging +from datetime import datetime +from typing import Any, cast + +from homeassistant.core import HomeAssistant +from homeassistant.helpers.storage import Store +from homeassistant.util import dt as dt_util + +from .const import ( + STORAGE_VERSION, + STORAGE_KEY, + SHORT_SILENCE_THRESHOLD_S, + TRIM_BUFFER_S, +) +from .log_utils import DeviceLoggerAdapter + +_LOGGER = logging.getLogger(__name__) + +STORAGE_KEY_RECORDER = f"{STORAGE_KEY}.recorder" + + +class RecorderStore(Store[dict[str, Any]]): + """Store for recorder data with migration support.""" + + async def _async_migrate_func( + self, + old_major_version: int, + old_minor_version: int, + old_data: dict[str, Any], + ) -> dict[str, Any]: + """Migrate data to the new version.""" + _LOGGER.info( + "Migrating recorder storage from v%s to v%s", + old_major_version, + STORAGE_VERSION, + ) + # Recorder data schema hasn't changed, simple pass-through is safe + return old_data + + +class CycleRecorder: + """Records raw power data without interference from detection logic.""" + + def __init__(self, hass: HomeAssistant, entry_id: str, device_name: str = "") -> None: + """Initialize the recorder.""" + self._logger = DeviceLoggerAdapter(_LOGGER, device_name) + self.hass = hass + self.entry_id = entry_id + self._store = RecorderStore(hass, STORAGE_VERSION, f"{STORAGE_KEY_RECORDER}.{entry_id}") + + # State + self._is_recording = False + self._start_time: datetime | None = None + self._buffer: list[tuple[str, float]] = [] # stored as (iso_str, power) for easy json + self._last_save: datetime | None = None + self._last_run: dict[str, Any] | None = None + + @property + def is_recording(self) -> bool: + """Return True if recording is active.""" + return self._is_recording + + @property + def start_time(self) -> datetime | None: + """Return recording start time.""" + return self._start_time + + @property + def current_duration(self) -> float: + """Return current recording duration in seconds.""" + if self._start_time: + return (dt_util.now() - self._start_time).total_seconds() + return 0.0 + + async def async_load(self) -> None: + """Load state from storage.""" + data_raw = await self._store.async_load() + data = data_raw if isinstance(data_raw, dict) else {} + # Reset to safe defaults before applying loaded values so stale state + # is never left in place when loaded data omits keys. + self._is_recording = False + self._start_time = None + self._buffer = [] + self._last_run = None + if data: + value = data.get("is_recording", False) + self._is_recording = value if isinstance(value, bool) else False + start_iso = data.get("start_time") + if isinstance(start_iso, str) and start_iso: + parsed_time = dt_util.parse_datetime(start_iso) + if parsed_time is not None and getattr(parsed_time, "tzinfo", None) is None: + self._logger.warning( + "Recorder state loaded naive start_time (%s); treating as invalid", start_iso + ) + self._start_time = None + else: + self._start_time = parsed_time + if self._is_recording and self._start_time is None: + self._logger.warning( + "Recorder state had is_recording=True with invalid start_time; restoring as not recording" + ) + self._is_recording = False + buffer_raw = data.get("buffer", []) + sanitized: list[tuple[str, float]] = [] + if isinstance(buffer_raw, list): + for item in buffer_raw: + if not isinstance(item, (list, tuple)) or len(item) != 2: + continue + key, ts = item[0], item[1] + if not isinstance(key, str) or not key: + continue + if not isinstance(ts, (int, float)): + continue + sanitized.append((key, float(ts))) + self._buffer = sanitized + last_run_raw = data.get("last_run") + self._last_run = ( + copy.deepcopy(cast(dict[str, Any], last_run_raw)) + if isinstance(last_run_raw, dict) + else None + ) + self._logger.info( + "Loaded recorder state: recording=%s, samples=%d, has_last_run=%s", + self._is_recording, + len(self._buffer), + self._last_run is not None, + ) + + async def stop_recording(self) -> dict[str, Any]: + """Stop recording and save data for processing.""" + if not self._is_recording: + return {} + + self._logger.info("Stopping cycle recording. Total samples: %d", len(self._buffer)) + self._is_recording = False + + # Create output packet + result: dict[str, Any] = { + "start_time": self._start_time.isoformat() if self._start_time else None, + "end_time": dt_util.now().isoformat(), + "data": copy.deepcopy(self._buffer), + } + + # Save as last run (persisted) + self._last_run = copy.deepcopy(result) + + # Clear active state + self._start_time = None + self._buffer = [] + await self._async_save() + + return result + + @property + def last_run(self) -> dict[str, Any] | None: + """Return the last recorded cycle data.""" + return copy.deepcopy(self._last_run) + + async def clear_last_run(self) -> None: + """Clear the last recorded run.""" + self._last_run = None + await self._async_save() + + async def _async_save(self) -> None: + """Save state to storage.""" + data: dict[str, Any] = { + "is_recording": self._is_recording, + "start_time": self._start_time.isoformat() if self._start_time else None, + "buffer": self._buffer, + "last_run": self._last_run, + } + await self._store.async_save(data) + self._last_save = dt_util.now() + + async def start_recording(self) -> None: + """Start a new recording.""" + if self._is_recording: + self._logger.warning("Recording already in progress") + return + + self._logger.info("Starting new cycle recording") + # Previous recordings are kept until explicitly cleared or overwritten + + self._is_recording = True + self._start_time = dt_util.now() + self._buffer = [] + await self._async_save() + + def process_reading(self, power: float) -> None: + """Process a power reading (synchronous to avoid blocking loop).""" + if not self._is_recording: + return + + now = dt_util.now() + # Append to buffer + self._buffer.append((now.isoformat(), float(power))) + + # Periodic save every 60s to ensure data persistence + # Better safe than sorry: save if last save was > 1 minute ago + if self._last_save and (now - self._last_save).total_seconds() > 60: + self.hass.add_job(self._async_save) + elif not self._last_save: + self.hass.add_job(self._async_save) + + def get_trim_suggestions( + self, + data: list[tuple[str, float]], + recording_start: datetime | None = None, + recording_end: datetime | None = None, + ) -> tuple[float, float, float]: + """Analyze data to propose trims. + + Args: + data: List of (iso_timestamp, power) + recording_start: Actual start time of recording (for head trim relative to start) + recording_end: Actual end time of recording (for tail trim relative to end) + + Returns: (head_trim_seconds, tail_trim_seconds, median_dt) + """ + if not data: + # No data found - return full recording duration as trim + if recording_start and recording_end: + dur = (recording_end - recording_start).total_seconds() + return 0.0, dur, 0.0 + return 0.0, 0.0, 0.0 + + # Parse timestamps and powers + parsed: list[tuple[float, float]] = [] + for t_str, p in data: + t = dt_util.parse_datetime(t_str) + if t: + parsed.append((t.timestamp(), p)) + + if not parsed: + return 0.0, 0.0, 0.0 + + data_start_ts = parsed[0][0] + data_end_ts = parsed[-1][0] + + # Use provided bounds or fallback to data bounds + rec_start_ts = recording_start.timestamp() if recording_start else data_start_ts + rec_end_ts = recording_end.timestamp() if recording_end else data_end_ts + + # Ensure bounds cover data + rec_start_ts = min(rec_start_ts, data_start_ts) + rec_end_ts = max(rec_end_ts, data_end_ts) + + threshold = 1.0 # W + + first_active_idx = -1 + last_active_idx = -1 + + for i, (_, p) in enumerate(parsed): + if p > threshold: + if first_active_idx == -1: + first_active_idx = i + last_active_idx = i + + if first_active_idx == -1: + # No activity found + total_dur = rec_end_ts - rec_start_ts + return 0.0, round(total_dur, 1), 0.0 + + head_ts = parsed[first_active_idx][0] + tail_ts = parsed[last_active_idx][0] + + if len(parsed) > 1: + dts = [t - s for (t, _), (s, _) in zip(parsed[1:], parsed[:-1])] + # Median calculation without numpy + dts.sort() + mid = len(dts) // 2 + if len(dts) % 2 == 0: + median_dt = (dts[mid - 1] + dts[mid]) / 2.0 + else: + median_dt = dts[mid] + if median_dt <= 0: + median_dt = 1.0 # Fallback + else: + median_dt = 1.0 + + # 1. Head Trim + # Time from recording start to first active sample + raw_head_trim = max(0.0, head_ts - rec_start_ts) + + # Align to sampling rate (floor to keep buffer) + # Example: raw=19s, dt=10s -> trim 10s. Buffer=9s. + # Example: raw=21s, dt=10s -> trim 20s. Buffer=1s. + # To ensure we don't cut active sample if jitter: + # We start at rec_start_ts. We want start_time + trim <= head_ts + # floor ensures this. + + steps_head = int(raw_head_trim / median_dt) + # Align trim to sampling rate (floor to keep buffer before active sample) + + # However, if using the "floor" logic makes it 0, that's fine. + head_trim = steps_head * median_dt + + # 2. Tail Trim + # Time from last active sample to recording end + # For manual recordings, we want to be conservative because of drying phases. + raw_tail_trim = max(0.0, rec_end_ts - tail_ts) + + # If tail silence is less than SHORT_SILENCE_THRESHOLD_S, suggest 0 trim to be safe. + # Dishwashers often have 5-10 min silent periods that are NOT the end. + if raw_tail_trim < SHORT_SILENCE_THRESHOLD_S: + tail_trim = 0.0 + else: + # If it's very long, suggest trimming but keep a TRIM_BUFFER_S buffer + tail_trim = max(0.0, raw_tail_trim - TRIM_BUFFER_S) + steps_tail = int(tail_trim / median_dt) + tail_trim = steps_tail * median_dt + + return round(head_trim, 1), round(tail_trim, 1), round(median_dt, 1) diff --git a/custom_components/ha_washdata/select.py b/custom_components/ha_washdata/select.py new file mode 100644 index 0000000..077cd65 --- /dev/null +++ b/custom_components/ha_washdata/select.py @@ -0,0 +1,126 @@ +"""Select entity for WashData.""" + +from __future__ import annotations + +import logging + +from homeassistant.components.select import SelectEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.dispatcher import async_dispatcher_connect + +from .const import DOMAIN, SIGNAL_WASHER_UPDATE +from .manager import WashDataManager +from .profile_store import profile_sort_key + +_LOGGER = logging.getLogger(__name__) + +OPTION_AUTO = "auto_detect" + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the select entity.""" + manager: WashDataManager = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities([WashDataProgramSelect(manager, config_entry)]) + + +class WashDataProgramSelect(SelectEntity): + """Select entity to manually choose the running program.""" + + _attr_has_entity_name = True + + _attr_translation_key = "program_select" + + def __init__(self, manager: WashDataManager, config_entry: ConfigEntry) -> None: + """Initialize the select entity.""" + self._manager = manager + self._config_entry = config_entry + self._attr_unique_id = f"{config_entry.entry_id}_program_select" + self._attr_device_info = { + "identifiers": {(DOMAIN, config_entry.entry_id)}, + "name": config_entry.title, + "manufacturer": "WashData", + } + + # Determine icon based on device type + dtype = getattr(manager, "device_type", "washing_machine") + if dtype == "dryer": + self._attr_icon = "mdi:tumble-dryer" + elif dtype == "dishwasher": + self._attr_icon = "mdi:dishwasher" + elif dtype == "ev": + self._attr_icon = "mdi:car-electric" + elif dtype == "coffee_machine": + self._attr_icon = "mdi:coffee" + elif dtype == "air_fryer": + self._attr_icon = "mdi:pot-steam" + elif dtype == "heat_pump": + self._attr_icon = "mdi:heat-pump" + elif dtype == "oven": + self._attr_icon = "mdi:stove" + else: + self._attr_icon = "mdi:washing-machine" # Default and washing_machine + + self._update_options() + + @callback + def _update_options(self) -> None: + """Update the list of available options from profiles.""" + profiles = self._manager.profile_store.list_profiles() + # Sort profiles by name + profile_names = sorted([p["name"] for p in profiles], key=profile_sort_key) + self._attr_options = [OPTION_AUTO] + profile_names + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_WASHER_UPDATE.format(self._manager.entry_id), + self._update_state, + ) + ) + self._update_state() + + @callback + def _update_state(self) -> None: + """Update state from manager.""" + # Refresh options in case new profiles were created + self._update_options() + + current = self._manager.current_program + manual_active = getattr(self._manager, "manual_program_active", False) + + if manual_active and current: + self._attr_current_option = current + elif current in ("detecting...", "off", "restored..."): + self._attr_current_option = OPTION_AUTO + elif current: + # It detected a program, but it's not "manual override" mode. + # Should we show the detected program or "Auto"? + # Showing "Auto" implies "I am in auto mode". + # But user might want to see what is detected here too? + # Standard pattern: Select shows target/mode. + # If we are in auto mode, show Auto. The sensor shows the detected program. + self._attr_current_option = OPTION_AUTO + else: + self._attr_current_option = OPTION_AUTO + + self.async_write_ha_state() + + def select_option(self, option: str) -> None: + """Handle the option selection (sync wrapper).""" + raise NotImplementedError("Use async_select_option instead") + + async def async_select_option(self, option: str) -> None: + """Change the selected option.""" + if option == OPTION_AUTO: + self._manager.clear_manual_program() + else: + self._manager.set_manual_program(option) diff --git a/custom_components/ha_washdata/sensor.py b/custom_components/ha_washdata/sensor.py new file mode 100644 index 0000000..583bedb --- /dev/null +++ b/custom_components/ha_washdata/sensor.py @@ -0,0 +1,864 @@ +"""Sensors for WashData.""" + +from __future__ import annotations + +from asyncio import Task +import hashlib +import logging +from typing import Any + +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription, SensorDeviceClass, SensorStateClass +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import HomeAssistantError +from homeassistant.const import EntityCategory +from homeassistant.helpers import entity_registry +from homeassistant.helpers.dispatcher import async_dispatcher_connect +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import dt as dt_util + +from .const import ( + CONF_AUTO_LABEL_CONFIDENCE, + CONF_DURATION_TOLERANCE, + DOMAIN, + CONF_END_ENERGY_THRESHOLD, + CONF_MIN_OFF_GAP, + CONF_MIN_POWER, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_OFF_DELAY, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_RUNNING_DEAD_ZONE, + CONF_SAMPLING_INTERVAL, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + SIGNAL_WASHER_UPDATE, + CONF_WATCHDOG_INTERVAL, + CONF_EXPOSE_DEBUG_ENTITIES, + DEVICE_TYPE_PUMP, + STATE_OFF, + STATE_IDLE, + STATE_STARTING, + STATE_RUNNING, + STATE_PAUSED, + STATE_USER_PAUSED, + STATE_ENDING, + STATE_FINISHED, + STATE_ANTI_WRINKLE, + STATE_DELAY_WAIT, + STATE_INTERRUPTED, + STATE_FORCE_STOPPED, + STATE_RINSE, + STATE_UNKNOWN, + STATE_CLEAN, +) +from .manager import WashDataManager + +_LOGGER = logging.getLogger(__name__) + + +_STATIC_DIAGNOSTIC_SUFFIXES = { + "debug_info", + "suggestions", + "match_confidence", + "top_candidates", + "ambiguity", +} + + +def _profile_count_unique_id(entry_id: str, profile_name: str) -> str: + """Build deterministic unique_id for a profile count diagnostic sensor.""" + profile_token = hashlib.sha256(profile_name.encode("utf-8")).hexdigest()[:8] + return f"{entry_id}_profile_count_{profile_token}" + + +def _expected_diagnostic_unique_ids(manager: WashDataManager, entry: ConfigEntry) -> set[str]: + """Return expected diagnostic unique_ids for this config entry.""" + expected = { + f"{entry.entry_id}_debug_info", + f"{entry.entry_id}_suggestions", + } + + if entry.options.get(CONF_EXPOSE_DEBUG_ENTITIES): + expected.update( + { + f"{entry.entry_id}_match_confidence", + f"{entry.entry_id}_top_candidates", + f"{entry.entry_id}_ambiguity", + } + ) + + for profile in manager.profile_store.list_profiles(): + profile_name = profile.get("name") + if isinstance(profile_name, str) and profile_name: + expected.add(_profile_count_unique_id(entry.entry_id, profile_name)) + + return expected + + +def cleanup_orphaned_diagnostic_entities( + hass: HomeAssistant, manager: WashDataManager, entry: ConfigEntry +) -> int: + """Remove stale diagnostic entities for this config entry from entity registry.""" + ent_reg = entity_registry.async_get(hass) + expected_unique_ids = _expected_diagnostic_unique_ids(manager, entry) + + removed = 0 + entry_prefix = f"{entry.entry_id}_" + for reg_entry in entity_registry.async_entries_for_config_entry(ent_reg, entry.entry_id): + unique_id = reg_entry.unique_id or "" + if not unique_id.startswith(entry_prefix): + continue + + suffix = unique_id[len(entry_prefix) :] + + # Remove stale pump_runs_today when device type has changed away from pump. + if suffix == "pump_runs_today" and manager.device_type != DEVICE_TYPE_PUMP: + ent_reg.async_remove(reg_entry.entity_id) + removed += 1 + continue + + is_diagnostic_family = ( + suffix in _STATIC_DIAGNOSTIC_SUFFIXES + or suffix.startswith("profile_count_") + or suffix == "wash_phase" + ) + if not is_diagnostic_family: + continue + + if unique_id not in expected_unique_ids: + ent_reg.async_remove(reg_entry.entity_id) + removed += 1 + + if removed: + _LOGGER.info( + "Removed %s orphaned diagnostic entities for entry %s", + removed, + entry.entry_id, + ) + return removed + + +async def async_setup_entry( + hass: HomeAssistant, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up the sensors.""" + manager: WashDataManager = hass.data[DOMAIN][entry.entry_id] + + entities: list[SensorEntity] = [ + WasherStateSensor(manager, entry), + WasherProgramSensor(manager, entry), + WasherCurrentPhaseSensor(manager, entry), + WasherTimeRemainingSensor(manager, entry), + WasherTotalDurationSensor(manager, entry), + WasherProgressSensor(manager, entry), + WasherPowerSensor(manager, entry), + WasherElapsedTimeSensor(manager, entry), + WasherDebugSensor(manager, entry), + WasherSuggestionsSensor(manager, entry), + WasherCycleCountSensor(manager, entry), + ] + + # Add pump-specific sensors + if manager.device_type == DEVICE_TYPE_PUMP: + entities.append(PumpRunsTodaySensor(manager, entry)) + + # Add debug entities if enabled + if entry.options.get(CONF_EXPOSE_DEBUG_ENTITIES): + entities.extend( + [ + WasherMatchConfidenceSensor(manager, entry), + WasherTopCandidatesSensor(manager, entry), + ] + ) + + async_add_entities(entities) + + # Reconcile diagnostics at startup so stale unavailable entries are auto-removed. + cleanup_orphaned_diagnostic_entities(hass, manager, entry) + + # Initialize dynamic profile sensor manager + profile_sensor_manager = WasherProfileSensorManager(manager, entry, async_add_entities) + await profile_sensor_manager.async_update() + entry.async_on_unload(profile_sensor_manager.unsubscribe) + + +class WasherBaseSensor(SensorEntity): + """Base sensor for ha_washdata.""" + + _attr_has_entity_name = True + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize.""" + self._manager = manager + self._entry = entry + self._attr_device_info = { + "identifiers": {(DOMAIN, entry.entry_id)}, + "name": entry.title, + "manufacturer": "WashData", + } + self._attr_unique_id = f"{entry.entry_id}_{self.entity_description.key}" + + async def async_added_to_hass(self) -> None: + """Register callbacks.""" + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SIGNAL_WASHER_UPDATE.format(self._entry.entry_id), + self._update_callback, + ) + ) + + @callback + def _update_callback(self) -> None: + """Update the sensor.""" + self.async_write_ha_state() + + +class WasherStateSensor(WasherBaseSensor): + """Sensor for the washing machine state.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the state sensor.""" + self.entity_description = SensorEntityDescription( + key="washer_state", + translation_key="washer_state", + device_class=SensorDeviceClass.ENUM, + options=[ + STATE_OFF, + STATE_IDLE, + STATE_STARTING, + STATE_RUNNING, + STATE_PAUSED, + STATE_USER_PAUSED, + STATE_ENDING, + STATE_FINISHED, + STATE_ANTI_WRINKLE, + STATE_DELAY_WAIT, + STATE_INTERRUPTED, + STATE_FORCE_STOPPED, + STATE_RINSE, + STATE_UNKNOWN, + STATE_CLEAN, + ], + ) + super().__init__(manager, entry) + + @property + def icon(self) -> str | None: # type: ignore[override] + """Return the icon.""" + dtype = self._manager.device_type + if dtype == "dryer": + return "mdi:tumble-dryer" + if dtype == "dishwasher": + return "mdi:dishwasher" + if dtype == "ev": + return "mdi:car-electric" + if dtype == "coffee_machine": + return "mdi:coffee-maker" + if dtype == "air_fryer": + return "mdi:pot-steam" + if dtype == "heat_pump": + return "mdi:heat-pump" + if dtype == "pump": + return "mdi:water-pump" + if dtype == "oven": + return "mdi:stove" + return "mdi:washing-machine" + + @property + def native_value(self): # type: ignore[override] + return self._manager.check_state() + + @property + def extra_state_attributes(self): # type: ignore[override] + attrs: dict[str, Any] = { + "samples_recorded": self._manager.samples_recorded, + "current_program_guess": self._manager.current_program, + "sub_state": self._manager.sub_state, + } + if self._manager.device_type == DEVICE_TYPE_PUMP: + attrs["pump_stuck"] = self._manager.pump_stuck + return attrs + + +class WasherProgramSensor(WasherBaseSensor): + """Sensor for the current program.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the program sensor.""" + self.entity_description = SensorEntityDescription( + key="washer_program", + translation_key="washer_program", + icon="mdi:file-document-outline", + device_class=SensorDeviceClass.ENUM, + ) + super().__init__(manager, entry) + + @property + def options(self) -> list[str] | None: # type: ignore[override] + """Return a list of possible options.""" + profiles = self._manager.profile_store.list_profiles() + # Include current program if not in profiles (e.g. unknown or special states) + options = [p["name"] for p in profiles] + curr = self._manager.current_program + if curr and curr not in options: + options.append(curr) + if "none" not in options: + options.append("none") + if "unknown" not in options: + options.append("unknown") + return options + + @property + def native_value(self): # type: ignore[override] + return self._manager.current_program + + @property + def extra_state_attributes(self): # type: ignore[override] + profile_name = self._manager.current_program + if not profile_name or profile_name in ("off", "detecting...", "starting", "unknown"): + return None + + device_type = self._manager.device_type + if device_type: + catalog = self._manager.list_phase_catalog(device_type) + assigned = self._manager.get_profile_phase_ranges_for_device( + profile_name, + device_type, + ) + else: + catalog = [] + assigned = [] + catalog_view: list[dict[str, Any]] = [ + { + "name": p.get("name"), + "description": p.get("description", ""), + "is_default": bool(p.get("is_default", False)), + } + for p in catalog + ] + + attrs: dict[str, Any] = { + "active_phase": self._manager.phase_description, + "phase_catalog": catalog_view, + "phase_ranges": assigned, + } + return attrs + + +class WasherTimeRemainingSensor(WasherBaseSensor): + """Sensor for estimated time remaining.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the time remaining sensor.""" + self.entity_description = SensorEntityDescription( + key="time_remaining", + translation_key="time_remaining", + device_class=SensorDeviceClass.DURATION, + # Declare the unit statically (not as a state-dependent property) so + # Home Assistant always sees a duration entity and offers the + # duration display-format options, even while the appliance is idle + # and the value is unknown (see issue #261). + native_unit_of_measurement="min", + icon="mdi:timer-sand", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + if self._manager.check_state() in (STATE_OFF, STATE_ANTI_WRINKLE, STATE_DELAY_WAIT): + return None + if self._manager.time_remaining is not None: + return int(self._manager.time_remaining / 60) + return None + + +class WasherTotalDurationSensor(WasherBaseSensor): + """Sensor for total predicted duration.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the total duration sensor.""" + self.entity_description = SensorEntityDescription( + key="total_duration", + translation_key="total_duration", + device_class=SensorDeviceClass.DURATION, + # See WasherTimeRemainingSensor / issue #261: keep the unit static so + # the duration display-format options are available even while idle. + native_unit_of_measurement="min", + icon="mdi:timer-check-outline", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + if self._manager.check_state() == STATE_OFF: + return None + if self._manager.total_duration: + return int(self._manager.total_duration / 60) + return None + + @property + def extra_state_attributes(self): # type: ignore[override] + """Return extra state attributes.""" + return { + "last_updated": self._manager.last_total_duration_update, + } + + +class WasherProgressSensor(WasherBaseSensor): + """Sensor for cycle progress percentage.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the progress sensor.""" + self.entity_description = SensorEntityDescription( + key="cycle_progress", + translation_key="cycle_progress", + native_unit_of_measurement="%", + suggested_display_precision=1, + icon="mdi:progress-clock", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + return self._manager.cycle_progress + + +class WasherPowerSensor(WasherBaseSensor): + """Sensor for current power usage.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the power sensor.""" + self.entity_description = SensorEntityDescription( + key="current_power", + translation_key="current_power", + native_unit_of_measurement="W", + device_class=SensorDeviceClass.POWER, + icon="mdi:flash", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + return self._manager.current_power + + +class WasherElapsedTimeSensor(WasherBaseSensor): + """Sensor for elapsed cycle time.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the elapsed time sensor.""" + self.entity_description = SensorEntityDescription( + key="elapsed_time", + translation_key="elapsed_time", + native_unit_of_measurement="s", + device_class=SensorDeviceClass.DURATION, + icon="mdi:timer-outline", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + if self._manager.check_state() == STATE_OFF: + return 0 + start = self._manager.cycle_start_time + if start: + delta = dt_util.now() - start + return int(delta.total_seconds()) + return 0 + + +class WasherDebugSensor(WasherBaseSensor): + """Sensor for internal debug information.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + """Initialize the debug sensor.""" + self.entity_description = SensorEntityDescription( + key="debug_info", + translation_key="debug_info", + icon="mdi:bug", + entity_registry_enabled_default=False, # Hidden by default + entity_category=EntityCategory.DIAGNOSTIC, + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + return self._manager.check_state() + + @property + def extra_state_attributes(self): # type: ignore[override] + """Return various internal states for debugging.""" + detector = self._manager.detector + stats = self._manager.sample_interval_stats + # pylint: disable=protected-access + attrs: dict[str, Any] = { + "sub_state": detector.sub_state, + "match_confidence": getattr(self._manager, "_last_match_confidence", 0.0), + "cycle_id": getattr(detector, "_current_cycle_start", None), + "samples": detector.samples_recorded, + "energy_accum": getattr(detector, "_energy_since_idle_wh", 0.0), + "time_below": getattr(detector, "_time_below_threshold", 0.0), + "sampling_p95": stats.get("p95"), + "noise_events": len(getattr(self._manager, "_noise_events", [])), + "top_candidates": self._manager.top_candidates, + "last_match_details": self._manager.last_match_details, + } + return attrs + + +class WasherMatchConfidenceSensor(WasherBaseSensor): + """Sensor for profile match confidence.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="match_confidence", + translation_key="match_confidence", + icon="mdi:chart-bar", + state_class="measurement", + native_unit_of_measurement="%", + entity_category=EntityCategory.DIAGNOSTIC, + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + conf = getattr(self._manager, "_last_match_confidence", 0.0) + return int(conf * 100) + + +class WasherTopCandidatesSensor(WasherBaseSensor): + """Sensor showing top matching candidates.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="top_candidates", + translation_key="top_candidates", + icon="mdi:format-list-numbered", + entity_category=EntityCategory.DIAGNOSTIC, + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + candidates = self._manager.top_candidates + if not candidates: + return "none" + # Return simplified string: "Name (Score), Name (Score)" + return ", ".join([f"{c['name']} ({c['score']:.2f})" for c in candidates[:3]]) + + @property + def extra_state_attributes(self): # type: ignore[override] + return {"candidates": self._manager.top_candidates} + + +class WasherCurrentPhaseSensor(WasherBaseSensor): + """Sensor for the current detected phase.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="current_phase", + translation_key="current_phase", + icon="mdi:water-sync", + ) + super().__init__(manager, entry) + + @property + def native_value(self): # type: ignore[override] + return self._manager.phase_description + + +class WasherProfileCountSensor(WasherBaseSensor): + """Diagnostic sensor showing cycle count for a specific profile.""" + + def __init__( + self, manager: WashDataManager, entry: ConfigEntry, profile_name: str, count: int + ) -> None: + """Initialize.""" + self._profile_name = profile_name + self._profile_token = hashlib.sha256( + profile_name.encode("utf-8") + ).hexdigest()[:8] + # We store initial count, but update callback will refresh it + self._count = count + + self.entity_description = SensorEntityDescription( + key=f"profile_count_{self._profile_token}", + translation_key="profile_cycle_count", + icon="mdi:counter", + state_class="total", + entity_category=EntityCategory.DIAGNOSTIC, + ) + self._attr_translation_placeholders = {"profile_name": profile_name} + super().__init__(manager, entry) + # Override unique ID to be profile specific + self._attr_unique_id = f"{entry.entry_id}_profile_count_{self._profile_token}" + + @property + def native_value(self) -> int: # type: ignore[override] + """Return the cycle count.""" + # Fetch fresh count from store if available + profile = self._manager.profile_store.get_profile(self._profile_name) + if profile: + return profile.get("cycle_count", 0) + return 0 + + @property + def available(self) -> bool: # type: ignore[override] + """Return True if profile still exists.""" + return self._manager.profile_store.get_profile(self._profile_name) is not None + + @property + def extra_state_attributes(self) -> dict[str, Any] | None: # type: ignore[override] + """Return profile statistics.""" + profile = self._manager.profile_store.get_profile(self._profile_name) + if not profile: + return None + + avg_energy = profile.get("avg_energy") + count = profile.get("cycle_count", 0) + total_energy = (avg_energy * count) if avg_energy is not None else None + duration_std_dev = profile.get("duration_std_dev") + consistency_min = ( + float(duration_std_dev) / 60.0 + if isinstance(duration_std_dev, (int, float)) + else None + ) + + # Helper to format duration + def _to_min(sec: float) -> int: + return int(sec / 60) if sec else 0 + + return { + "average_consumption_kwh": avg_energy, + "total_consumption_kwh": total_energy, + "last_run": profile.get("last_run"), + "average_length_min": _to_min(profile.get("avg_duration", 0)), + "min_length_min": _to_min(profile.get("min_duration", 0)), + "max_length_min": _to_min(profile.get("max_duration", 0)), + "consistency_min": consistency_min, + } + + +class WasherProfileSensorManager: + """Manages dynamic profile sensors.""" + + def __init__( + self, + manager: WashDataManager, + entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, + ) -> None: + """Initialize.""" + self._manager = manager + self._entry = entry + self._async_add_entities = async_add_entities + self._sensors: dict[str, WasherProfileCountSensor] = {} + self._diagnostics_cleanup_done: bool = False + + # Determine the signal string. It must match SIGNAL_WASHER_UPDATE from const.py + # which is "washdata_update_{}" + self._signal = SIGNAL_WASHER_UPDATE.format(entry.entry_id) + self._update_task: Task[None] | None = None + self._pending_update: bool = False + + # Register callback for ALL updates (simplest hook we have) + # Ideally we'd have a specific profile update signal, but general update is fine + # as long as we debounce or check efficiently. + self._unsub_dispatcher = async_dispatcher_connect( + manager.hass, + self._signal, + self._update_callback, + ) + + # Handle stale diagnostics that were left in registry by previous naming schemes + # or profile renames. Run once at initialization instead of on every update. + cleanup_orphaned_diagnostic_entities( + self._manager.hass, self._manager, self._entry + ) + self._diagnostics_cleanup_done = True + + def unsubscribe(self) -> None: + """Remove the dispatcher subscription.""" + if self._unsub_dispatcher: + self._unsub_dispatcher() + self._unsub_dispatcher = None + + # Prevent queued follow-up refreshes after teardown. + self._pending_update = False + if self._update_task and not self._update_task.done(): + self._update_task.cancel() + self._update_task = None + + @callback + def _update_callback(self) -> None: + """Handle updates.""" + if self._update_task and not self._update_task.done(): + self._pending_update = True + return + + task = self._manager.hass.async_create_task(self.async_update()) + self._update_task = task + + def _clear_update_task(done_task: Task[Any]) -> None: + if self._update_task is done_task: + self._update_task = None + if self._unsub_dispatcher is not None and self._pending_update: + self._pending_update = False + follow = self._manager.hass.async_create_task(self.async_update()) + self._update_task = follow + follow.add_done_callback(_clear_update_task) + + task.add_done_callback(_clear_update_task) + + async def async_update(self) -> None: + """Reflect profile changes in sensors.""" + profiles = self._manager.profile_store.list_profiles() + current_names = {p["name"] for p in profiles} + existing_names = set(self._sensors.keys()) + + # Add new + new_names = current_names - existing_names + new_entities: list[SensorEntity] = [] + for name in new_names: + p_data = self._manager.profile_store.get_profile(name) + count = p_data.get("cycle_count", 0) if p_data else 0 + sensor = WasherProfileCountSensor(self._manager, self._entry, name, count) + self._sensors[name] = sensor + new_entities.append(sensor) + + if new_entities: + self._async_add_entities(new_entities) + + # Remove old (if profile deleted) + removed_names = existing_names - current_names + + if removed_names: + ent_reg = entity_registry.async_get(self._manager.hass) + for name in removed_names: + sensor = self._sensors.pop(name) + # Remove from Entity Registry if registered + if sensor.entity_id: + if ent_reg.async_get(sensor.entity_id): + ent_reg.async_remove(sensor.entity_id) + else: + # Fallback for non-registered entities that were attached. + if sensor.hass: + try: + await sensor.async_remove() + except Exception as err: # pylint: disable=broad-exception-caught + _LOGGER.debug( + "Failed to remove sensor '%s' via fallback path: %s", + name, + err, + ) + + +class WasherSuggestionsSensor(WasherBaseSensor): + """Sensor for learned settings suggestions.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="suggestions", + translation_key="suggestions", + icon="mdi:lightbulb-on-outline", + entity_category=EntityCategory.DIAGNOSTIC, + ) + super().__init__(manager, entry) + + @staticmethod + def _applicable_suggestion_keys() -> tuple[str, ...]: + """Return suggestion keys that can be applied in options flow.""" + return ( + CONF_MIN_POWER, + CONF_OFF_DELAY, + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_SAMPLING_INTERVAL, + CONF_PROFILE_MATCH_INTERVAL, + CONF_AUTO_LABEL_CONFIDENCE, + CONF_DURATION_TOLERANCE, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_MIN_OFF_GAP, + CONF_STOP_THRESHOLD_W, + CONF_START_THRESHOLD_W, + CONF_END_ENERGY_THRESHOLD, + CONF_RUNNING_DEAD_ZONE, + ) + + def _count_applicable_suggestions(self, suggestions: dict[str, Any]) -> int: + """Count only suggestions with values that can be applied from options flow.""" + count = 0 + for key in self._applicable_suggestion_keys(): + entry = suggestions.get(key) + if isinstance(entry, dict) and entry.get("value") is not None: + count += 1 + return count + + @property + def native_value(self): # type: ignore[override] + suggestions = self._manager.suggestions + if not suggestions: + return 0 + return self._count_applicable_suggestions(suggestions) + + @property + def extra_state_attributes(self): # type: ignore[override] + suggestions: dict[str, Any] = self._manager.suggestions or {} + count = self._count_applicable_suggestions(suggestions) + applicable_keys = sorted( + k for k in self._applicable_suggestion_keys() + if isinstance(suggestions.get(k), dict) and suggestions[k].get("value") is not None + ) + + attrs: dict[str, Any] = { + "has_actionable_suggestions": count > 0, + "suggestions_count": count, + "suggested_option_keys": applicable_keys, + "suggestions": suggestions, + } + return attrs + + +class PumpRunsTodaySensor(WasherBaseSensor): + """Sensor reporting how many pump cycles occurred in the last 24 hours. + + Only created when device type is ``pump``. + """ + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="pump_runs_today", + translation_key="pump_runs_today", + icon="mdi:counter", + native_unit_of_measurement="cycles", + ) + super().__init__(manager, entry) + + @property + def native_value(self) -> int: # type: ignore[override] + return self._manager.pump_runs_today + + +class WasherCycleCountSensor(WasherBaseSensor): + """Sensor reporting the total number of completed cycles stored for this device.""" + + def __init__(self, manager: WashDataManager, entry: ConfigEntry) -> None: + self.entity_description = SensorEntityDescription( + key="cycle_count", + translation_key="cycle_count", + icon="mdi:counter", + native_unit_of_measurement="cycles", + ) + super().__init__(manager, entry) + + @property + def native_value(self) -> int: # type: ignore[override] + return self._manager.cycle_count \ No newline at end of file diff --git a/custom_components/ha_washdata/services.yaml b/custom_components/ha_washdata/services.yaml new file mode 100644 index 0000000..cba747c --- /dev/null +++ b/custom_components/ha_washdata/services.yaml @@ -0,0 +1,249 @@ +label_cycle: + name: Label Cycle + description: Assign an existing profile to a past cycle, or remove the label. + fields: + device_id: + name: Device + description: The WashData device to label. + required: true + selector: + device: + integration: ha_washdata + cycle_id: + name: Cycle ID + description: The ID of the cycle to label. + required: true + selector: + text: + profile_name: + name: Profile Name + description: The name of an existing profile (create profiles in Manage Profiles menu). Leave blank to remove label. + required: false + selector: + text: + +create_profile: + name: Create Profile + description: Create a new profile (standalone or based on a reference cycle). + fields: + device_id: + name: Device + description: The WashData device. + required: true + selector: + device: + integration: ha_washdata + profile_name: + name: Profile Name + description: Name for the new profile (e.g. "Heavy Duty", "Delicates"). + required: true + selector: + text: + reference_cycle_id: + name: Reference Cycle ID + description: Optional cycle ID to base this profile on. + required: false + selector: + text: + +delete_profile: + name: Delete Profile + description: Delete a profile and optionally unlabel cycles using it. + fields: + device_id: + name: Device + description: The WashData device. + required: true + selector: + device: + integration: ha_washdata + profile_name: + name: Profile Name + description: The profile to delete. + required: true + selector: + text: + unlabel_cycles: + name: Unlabel Cycles + description: Remove profile label from cycles using this profile. + required: false + default: true + selector: + boolean: + +auto_label_cycles: + name: Auto-Label Old Cycles + description: Retroactively label unlabeled cycles using profile matching. + fields: + device_id: + name: Device + description: The WashData device. + required: true + selector: + device: + integration: ha_washdata + confidence_threshold: + name: Confidence Threshold + description: Minimum match confidence (0.50-0.95) to apply labels. + required: false + default: 0.70 + selector: + number: + min: 0.50 + max: 0.95 + step: 0.05 + +export_config: + name: Export Config + description: Export this washer's profiles and cycles to a JSON file (per device). + fields: + device_id: + name: Device + description: The WashData device to export. + required: true + selector: + device: + integration: ha_washdata + path: + name: Path + description: Optional absolute file path to write (defaults to /config/ha_washdata_export_.json). + required: false + selector: + text: + +import_config: + name: Import Config + description: Import profiles and cycles for this washer from a JSON export file. + fields: + device_id: + name: Device + description: The WashData device to import into. + required: true + selector: + device: + integration: ha_washdata + path: + name: Path + description: Absolute path to the export JSON file to import. + required: true + selector: + text: + +submit_cycle_feedback: + name: Submit Cycle Feedback + description: > + Confirm or correct an auto-detected program after a completed cycle. + Provide either `entry_id` (advanced) or `device_id` (recommended). + fields: + device_id: + name: Device + description: The WashData device (recommended instead of entry_id). + required: false + selector: + device: + integration: ha_washdata + entry_id: + name: Entry ID + description: The config entry id for the device (alternative to device_id). + required: false + selector: + text: + cycle_id: + name: Cycle ID + description: The cycle id shown in the feedback notification / logs. + required: true + selector: + text: + user_confirmed: + name: Confirm Detected Program + description: Set true if the detected program is correct. + required: true + selector: + boolean: + corrected_profile: + name: Corrected Profile + description: If not confirmed, provide the correct profile/program name. + required: false + selector: + text: + corrected_duration: + name: Corrected Duration (seconds) + description: Optional corrected duration in seconds. + required: false + selector: + number: + min: 0 + max: 86400 + step: 1 + mode: box + notes: + name: Notes + description: Optional notes about this cycle. + required: false + selector: + text: + +record_start: + name: Record Cycle Start + description: Start manually recording a clean cycle (bypasses all matching logic). + fields: + device_id: + name: Device + description: The WashData device to record. + required: true + selector: + device: + integration: ha_washdata + +record_stop: + name: Record Cycle Stop + description: Stop manual recording. + fields: + device_id: + name: Device + description: The WashData device to stop recording. + required: true + selector: + device: + integration: ha_washdata + +trim_cycle: + name: Trim Cycle + description: > + Trim the power data of a past cycle to a specific time window. + Offsets are renormalized to start from 0 and the cycle duration is updated. + fields: + device_id: + name: Device + description: The WashData device. + required: true + selector: + device: + integration: ha_washdata + cycle_id: + name: Cycle ID + description: The ID of the cycle to trim. + required: true + selector: + text: + trim_start_s: + name: Trim Start (seconds) + description: Keep data from this offset (seconds from cycle start). Default 0. + required: false + default: 0 + selector: + number: + min: 0 + max: 86400 + step: 1 + mode: box + trim_end_s: + name: Trim End (seconds) + description: Keep data up to this offset (seconds from cycle start). Default = full duration. + required: false + selector: + number: + min: 1 + max: 86400 + step: 1 + mode: box diff --git a/custom_components/ha_washdata/signal_processing.py b/custom_components/ha_washdata/signal_processing.py new file mode 100644 index 0000000..0df7b17 --- /dev/null +++ b/custom_components/ha_washdata/signal_processing.py @@ -0,0 +1,266 @@ +"""Signal processing primitives for WashData. + +Constraint: NumPy only. +Constraint: All computations must be dt-aware (robust to irregular cadence). +Constraint: Resampling must be segment-based (no interpolation across gaps). +""" + +from dataclasses import dataclass +from typing import List, Tuple + +import numpy as np + + +@dataclass +class Segment: + """A continuous metrics segment suitable for matching. + + Attributes: + timestamps: Uniformly spaced timestamps (seconds) + power: Interpolated power values (Watts) + mask: Boolean mask (True = valid, False = gap/invalid). + In strict segmentation, typically all True, but support mask for partial validity. + """ + + timestamps: np.ndarray + power: np.ndarray + mask: np.ndarray + # Future extensibility: might add other channels here + + +def integrate_wh(timestamps: np.ndarray, power: np.ndarray) -> float: + """Compute energy in Wh using trapezoidal integration. + + Args: + timestamps: Array of timestamps in seconds. + power: Array of power values in Watts. + + Returns: + Energy in Watt-hours. + """ + if len(timestamps) < 2: + return 0.0 + + # Calculate dt in hours + # np.diff(timestamps) is in seconds, divide by 3600 for hours + dt_hours = np.diff(timestamps) / 3600.0 + + # Trapezoidal rule: (p[i] + p[i+1]) / 2 * dt + avg_power = (power[:-1] + power[1:]) * 0.5 + + return float(np.sum(avg_power * dt_hours)) + + +def robust_smooth( + power: np.ndarray, timestamps: np.ndarray, time_constant_s: float = 30.0 +) -> np.ndarray: + """Apply robust smoothing to power data. + + Combines a median filter (spike rejection) with an Exponential Moving Average (EMA). + EMA is calculated using time-weighted alpha to handle irregular jitter. + + Args: + power: Array of power values. + timestamps: Array of timestamps in seconds. + time_constant_s: EMA time constant in seconds. + alpha = 1 - exp(-dt / time_constant) + + Returns: + Smoothed power array. + """ + if len(power) == 0: + return np.array([]) + if len(power) < 3: + return power.copy() + + # 1. Median filter (3-point) using pure NumPy + p_med = power.copy() + + # Vectorized 3-point median: y[i] = median(x[i-1], x[i], x[i+1]) + # Edge handling: repeat values (first and last) + if len(power) >= 3: + # Pad with edge values + p_padded = np.empty(len(power) + 2) + p_padded[0] = power[0] + p_padded[-1] = power[-1] + p_padded[1:-1] = power + + # Stack shifted views + # Left neighbor: p_padded[0:-2] -> indices 0..N + # Center: p_padded[1:-1] -> indices 1..N+1 (original) + # Right neighbor: p_padded[2:] -> indices 2..N+2 + stack = np.vstack( + [p_padded[0 : len(power)], p_padded[1 : len(power) + 1], p_padded[2:]] + ) + + # Compute median down columns + p_med = np.median(stack, axis=0) + + # 2. Time-aware EMA + # y[i] = alpha * x[i] + (1-alpha) * y[i-1] + # alpha = 1 - exp(-dt / tau) + + smoothed = np.zeros_like(p_med, dtype=float) + smoothed[0] = p_med[0] + + # We Iterate because alpha changes with dt. + # Vectorization is possible but complex for IIR filter with variable coefs. + # Python loop is fine for typical cycle lengths (points < 10k). + + prev_y = p_med[0] + prev_t = timestamps[0] + + for i in range(1, len(p_med)): + dt = timestamps[i] - prev_t + if dt <= 0: + # Duplicate or disorderly timestamp, just carry forward + smoothed[i] = prev_y + continue + + current_val = p_med[i] + + # Adaptive alpha based on dt + alpha = 1.0 - np.exp(-dt / time_constant_s) + + # Apply EMA + y = alpha * current_val + (1.0 - alpha) * prev_y + + smoothed[i] = y + prev_y = y + prev_t = timestamps[i] + + return smoothed + + +def resample_uniform( + timestamps: np.ndarray, power: np.ndarray, dt_s: float = 5.0, gap_s: float = 60.0 +) -> List[Segment]: + """Resample irregularly sampled data onto a uniform grid, respecting gaps. + + Returns a LIST of Segments. Does NOT interpolate across gaps > gap_s. + + Args: + timestamps: Raw timestamps (seconds). + power: Raw power values. + dt_s: Target uniform step size (seconds). + gap_s: Max gap to interpolate across (seconds). + + Returns: + List of Segment objects. + """ + if len(timestamps) < 2: + return [] + + segments: List[Segment] = [] + + # Find indices where dt > gap_s + diffs = np.diff(timestamps) + break_indices = np.where(diffs > gap_s)[0] + 1 + + # Add start and end indices + start_indices = np.concatenate(([0], break_indices)) + end_indices = np.concatenate((break_indices, [len(timestamps)])) + + for start_idx, end_idx in zip(start_indices, end_indices): + chunk_ts = timestamps[start_idx:end_idx] + chunk_p = power[start_idx:end_idx] + + if len(chunk_ts) < 2: + continue + + # Define uniform grid for this chunk + # Define uniform grid for this chunk (start at first timestamp) + # Simple approach: start at t[0], go to t[-1] stepping by dt_s + + grid_start = chunk_ts[0] + grid_end = chunk_ts[-1] + + # Ensure at least two points + if grid_end - grid_start < dt_s: + continue + + # arange(start, end + epsilon, dt) + target_ts = np.arange(grid_start, grid_end + 0.001, dt_s) + + # Use numpy interp (linear interpolation) + # It's safe here because we know max gap < gap_s within this chunk + interpolated_p = np.interp(target_ts, chunk_ts, chunk_p) + + segments.append( + Segment( + timestamps=target_ts, + power=interpolated_p, + mask=np.ones_like(target_ts, dtype=bool), + ) + ) + + return segments + + +def resample_adaptive( + timestamps: np.ndarray, + power: np.ndarray, + min_dt: float = 5.0, + gap_s: float = 300.0, +) -> Tuple[List[Segment], float]: + """Resample data using an adaptive time step based on input cadence. + + Target dt is based on observed cadence with a lower bound: + ``target_dt = max(min_dt, median_interval)``. + - If data is dense (for example 1s), it is downsampled to ``min_dt``. + - If data is sparse (for example 30s), cadence is preserved. + + Args: + timestamps: Raw timestamps (seconds). + power: Raw power values. + min_dt: Minimum allowed dt (seconds). + gap_s: Max gap to interpolate across. + + Returns: + Tuple of ``(segments, used_dt_s)`` where ``segments`` are gap-aware, + uniformly sampled chunks and ``used_dt_s`` is the chosen target step. + """ + if len(timestamps) < 2: + return [], min_dt + + # Determine cadence + diffs = np.diff(timestamps) + # Filter strictly zero diffs (duplicates) + valid_diffs = diffs[diffs > 0.001] + + if len(valid_diffs) == 0: + median_dt = min_dt + else: + median_dt = float(np.median(valid_diffs)) + + # Logic: Never resample finer than sensor (median_dt). + # Also enforce min_dt (don't go finer than 5s). + # We ignore max_dt for clamping down, to respect "never finer" rule. + min_dt = max(min_dt, 1e-3) # Guard against non-positive step + target_dt = max(min_dt, median_dt) + gap_s = max(gap_s, target_dt * 1.5, 1e-3) # Guard against non-positive gap + + # Delegate to uniform resampler with chosen dt + segments = resample_uniform(timestamps, power, dt_s=target_dt, gap_s=gap_s) + + return segments, target_dt + + +def estimate_idle_baseline(power: np.ndarray) -> Tuple[float, float]: + """Estimate idle baseline level using robust statistics. + + Args: + power: Power samples (ideally from a period known or suspected to be IDLE/lower). + If mixed data is passed, the median might be biased if active time > idle time. + + Returns: + (baseline_median, baseline_mad) + """ + if len(power) == 0: + return 0.0, 0.0 + + median = float(np.median(power)) + # Median Absolute Deviation + mad = float(np.median(np.abs(power - median))) + + return median, mad diff --git a/custom_components/ha_washdata/strings.json b/custom_components/ha_washdata/strings.json new file mode 100644 index 0000000..9b3650e --- /dev/null +++ b/custom_components/ha_washdata/strings.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Setup", + "description": "Configure your washing machine or other appliance.\n\nPower sensor is required.\n\n**Next, you will be asked if you want to create your first profile.**", + "data": { + "name": "Device Name", + "device_type": "Device Type", + "power_sensor": "Power Sensor", + "min_power": "Minimum Power Threshold (W)" + }, + "data_description": { + "name": "A friendly name for this device (e.g., 'Washing Machine', 'Dishwasher').", + "device_type": "What type of appliance is this? Helps tailor detection and labeling.", + "power_sensor": "The sensor entity that reports real-time power consumption (in watts) from your smart plug.", + "min_power": "Power readings above this threshold (in watts) indicate the appliance is running. Start with 2W for most devices." + } + }, + "first_profile": { + "title": "Create First Profile", + "description": "A **Cycle** is a complete run of your appliance (e.g., a load of laundry). A **Profile** groups these cycles by type (e.g., 'Cotton', 'Quick Wash'). Creating a profile now helps the integration estimate duration immediately.\n\nYou can manually select this profile in the device controls if it's not detected automatically.\n\n**Leave 'Profile Name' empty to skip this step.**", + "data": { + "profile_name": "Profile Name (Optional)", + "manual_duration": "Estimated Duration (minutes)" + } + } + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Review Learning Feedbacks", + "settings": "Settings", + "notifications": "Notifications", + "manage_cycles": "Manage Cycles", + "manage_profiles": "Manage Profiles", + "manage_phase_catalog": "Manage Phase Catalog", + "record_cycle": "Record Cycle (Manual)", + "diagnostics": "Diagnostics & Maintenance" + } + }, + "apply_suggestions": { + "title": "Copy Suggested Values", + "description": "This will copy the current suggested values into your saved Settings. This is a one-time action and does not enable auto-overwrites.\n\nSuggested values to copy:\n{suggested}", + "data": { + "confirm": "Confirm copy action" + } + }, + "apply_suggestions_confirm": { + "title": "Review Suggested Changes", + "description": "WashData found **{pending_count}** suggested value(s) to apply.\n\nChanges:\n{changes}\n\n✅ Check the box below and click **Submit** to apply and save these changes immediately.\n❌ Leave it unchecked and click **Submit** to cancel and go back.", + "data": { + "confirm_apply_suggestions": "Apply and save these changes" + } + }, + "settings": { + "title": "Settings", + "description": "{deprecation_warning}Tune detection thresholds, learning, and advanced behavior. Defaults work well for most setups.\n\nSuggested Settings currently available: {suggestions_count}.\nIf this is above 0, open Advanced Settings and use Apply Suggested Values to review recommendations.", + "data": { + "apply_suggestions": "Apply Suggested Values", + "device_type": "Device Type", + "power_sensor": "Power Sensor Entity", + "min_power": "Minimum Power (W)", + "off_delay": "Cycle End Delay (s)", + "notify_actions": "Notification Actions", + "notify_people": "Delay Delivery Until These People Are Home", + "notify_only_when_home": "Delay Notifications Until Selected Person Is Home", + "notify_fire_events": "Fire Automation Events", + "notify_start_services": "Cycle Start - Notification Targets", + "notify_finish_services": "Cycle Finish - Notification Targets", + "notify_live_services": "Live Progress - Notification Targets", + "notify_before_end_minutes": "Pre-completion Notification (minutes before end)", + "notify_title": "Notification Title", + "notify_icon": "Notification Icon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Finish Message Format", + "notify_pre_complete_message": "Live Update Message Format", + "show_advanced": "Edit Advanced Settings (Next Step)" + }, + "data_description": { + "apply_suggestions": "Check this box to copy values suggested by the learning algorithm into the form. Review before saving.\n\nSuggested (from learning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Select the type of appliance (Washing Machine, Dryer, Dishwasher, Coffee Machine, EV, Bread Maker, Pump). This tag is stored with each cycle for better organization.", + "power_sensor": "The sensor entity reporting real-time power (in watts). You can change this anytime without losing historical data — useful if you replace a smart plug.", + "min_power": "Power readings above this threshold (in watts) indicate the appliance is running. Start with 2W for most devices.", + "off_delay": "Seconds the smoothed power must stay below the stop threshold to mark completion. Default: 120s.", + "notify_actions": "Optional Home Assistant action sequence to run for notifications (scripts, notify, TTS, etc.). If set, actions run before per-event service targets.", + "notify_people": "People entities used for presence gating. When enabled, notification delivery is delayed until at least one selected person is home.", + "notify_only_when_home": "If enabled, delay notifications until at least one selected person is home.", + "notify_fire_events": "If enabled, fire Home Assistant events for cycle start and end (ha_washdata_cycle_started and ha_washdata_cycle_ended) to drive automations.", + "notify_start_services": "One or more notify services to alert when a cycle begins. Leave empty to skip start notifications.", + "notify_finish_services": "One or more notify services to alert when a cycle finishes or nears completion. Leave empty to skip finish notifications.", + "notify_live_services": "One or more notify services to receive live progress updates while the cycle runs (mobile companion app only). Leave empty to skip live updates.", + "notify_before_end_minutes": "Minutes before the estimated end of the cycle to trigger a notification. Set to 0 to disable. Default: 0.", + "notify_title": "Custom title for notifications. Supports {device} placeholder.", + "notify_icon": "Icon to use for notifications (e.g. mdi:washing-machine). Leave empty to send no icon.", + "notify_start_message": "Message sent when a cycle starts. Supports {device} placeholder.", + "notify_finish_message": "Message sent when a cycle finishes. Supports {device}, {duration}, {program}, {energy_kwh}, {cost} placeholders.", + "notify_pre_complete_message": "Text of the recurring live progress updates while the cycle runs. Supports {device}, {minutes}, {program} placeholders." + } + }, + "notifications": { + "title": "Notifications", + "description": "Configure notification targets per event type and optional live mobile progress updates. Each event type can be sent to a different set of recipients.", + "data": { + "notify_actions": "Notification Actions", + "notify_people": "Delay Delivery Until These People Are Home", + "notify_only_when_home": "Delay Notifications Until Selected Person Is Home", + "notify_fire_events": "Fire Automation Events", + "notify_start_services": "Cycle Start - Notification Targets", + "notify_finish_services": "Cycle Finish - Notification Targets", + "notify_live_services": "Live Progress - Notification Targets", + "notify_before_end_minutes": "Pre-completion Notification (minutes before end)", + "notify_live_interval_seconds": "Live Update Interval (seconds)", + "notify_live_overrun_percent": "Live Update Overrun Allowance (%)", + "notify_live_chronometer": "Chronometer Countdown Timer", + "notify_title": "Notification Title", + "notify_icon": "Notification Icon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Finish Message Format", + "notify_pre_complete_message": "Live Update Message Format", + "notify_reminder_message": "Reminder Message Format", + "notify_timeout_seconds": "Auto-dismiss After (seconds)", + "notify_channel": "Notification Channel (status/live/reminder)", + "notify_finish_channel": "Finished Notification Channel", + "energy_price_entity": "Energy Price - Entity (optional)", + "energy_price_static": "Energy Price - Static value (optional)", + "go_back": "Go back without saving" + }, + "data_description": { + "go_back": "Tick this and click Submit to discard any changes above and return to the main menu.", + "notify_actions": "Optional Home Assistant action sequence to run for every notification event (scripts, notify, TTS, etc.). Actions fire before the per-event service targets.", + "notify_people": "People entities used for presence gating. When enabled, notification delivery is delayed until at least one selected person is home.", + "notify_only_when_home": "If enabled, delay notifications until at least one selected person is home.", + "notify_fire_events": "If enabled, fire Home Assistant events for cycle start and end (ha_washdata_cycle_started and ha_washdata_cycle_ended) to drive automations.", + "notify_start_services": "One or more notify services to alert when a cycle begins (e.g. notify.mobile_app_my_phone). Leave empty to skip start notifications.", + "notify_finish_services": "One or more notify services to alert when a cycle finishes or nears completion. Leave empty to skip finish notifications.", + "notify_live_services": "One or more notify services to receive live progress updates while the cycle runs (mobile companion app only). Leave empty to skip live updates.", + "notify_before_end_minutes": "Minutes before the estimated end of the cycle to trigger a notification. Set to 0 to disable. Default: 0.", + "notify_live_interval_seconds": "How often progress updates are sent while running. Lower values are more responsive but consume more notifications. A minimum of 30 seconds is enforced - values below 30 s are automatically rounded up to 30 s.", + "notify_live_overrun_percent": "Safety margin above the estimated update count for long/overrunning cycles (for example 20% allows 1.2x estimated updates).", + "notify_live_chronometer": "When enabled, each live update includes a chronometer countdown to the estimated finish time. The notification timer ticks down automatically on the device between updates (Android companion app only).", + "notify_title": "Custom title for notifications. Supports {device} placeholder.", + "notify_icon": "Icon to use for notifications (e.g. mdi:washing-machine). Leave empty to send no icon.", + "notify_start_message": "Message sent when a cycle starts. Supports {device} placeholder.", + "notify_finish_message": "Message sent when a cycle finishes. Supports {device}, {duration}, {program}, {energy_kwh}, {cost} placeholders.", + "notify_pre_complete_message": "Text of the recurring live progress updates while the cycle runs. Supports {device}, {minutes}, {program} placeholders.", + "notify_reminder_message": "Text of the one-time reminder sent the configured number of minutes before the estimated end. Distinct from the recurring live updates above. Supports {device}, {minutes}, {program} placeholders.", + "notify_timeout_seconds": "Automatically dismiss notifications after this many seconds (forwarded as the companion app 'timeout'). Set to 0 to keep them until dismissed. Default: 0.", + "notify_channel": "Android companion app notification channel for status, live progress, and reminder notifications. The sound and importance of a channel are configured in the companion app the first time the channel name is used - WashData only sets the channel name. Leave empty for the app default.", + "notify_finish_channel": "Separate Android channel for the finished alert (also used by the reminder and the laundry-waiting nag) so it can have its own sound. Leave empty to reuse the channel above.", + "energy_price_entity": "Select a numeric entity that holds the current electricity price (e.g. sensor.electricity_price, input_number.energy_rate). When set, enables the {cost} placeholder. Takes precedence over the static value if both are configured.", + "energy_price_static": "Fixed electricity price per kWh. When set, enables the {cost} placeholder. Ignored if an entity is configured above. Add your currency symbol in the message template, e.g. {cost} €." + } + }, + "advanced_settings": { + "title": "Advanced Settings", + "description": "Tune detection thresholds, learning, and advanced behavior. Defaults work well for most setups.", + "sections": { + "suggestions_section": { + "name": "Suggested Settings", + "description": "{suggestions_count} learned suggestions available. Tick Apply Suggested Values to review proposed changes before saving.", + "data": { + "apply_suggestions": "Apply Suggested Values" + }, + "data_description": { + "apply_suggestions": "Check this box to open a review step for suggested values from the learning algorithm. Nothing is saved automatically.\n\nSuggested (from learning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detection & Power Thresholds", + "description": "When the appliance is considered ON or OFF, energy gates, and timing for state transitions.", + "data": { + "start_duration_threshold": "Start Debounce Duration (s)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Completion Minimum Runtime (seconds)", + "start_threshold_w": "Start Threshold (W)", + "stop_threshold_w": "Stop Threshold (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Running Dead Zone (seconds)", + "end_repeat_count": "End Repeat Count", + "min_off_gap": "Min Gap Between Cycles (s)", + "sampling_interval": "Sampling Interval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignore brief power spikes shorter than this duration (seconds). Filters out boot spikes (e.g., 60W for 2s) before real cycle starts. Default: 5s.", + "start_energy_threshold": "Cycle must accumulate at least this much energy (Wh) during the START phase to be confirmed. Default: 0.2 Wh.", + "completion_min_seconds": "Cycles shorter than this will be marked as 'interrupted' even if they finish naturally. Default: 600s.", + "start_threshold_w": "Power must rise ABOVE this threshold to START a cycle. Set higher than your standby power. Default: min_power + 1 W.", + "stop_threshold_w": "Power must fall BELOW this threshold to END a cycle. Set just above zero to avoid noise triggering false ends. Default: 0.6 * min_power.", + "end_energy_threshold": "Cycle ends only if energy in the last Off-Delay window is below this value (Wh). Prevents false ends if power fluctuates near zero. Default: 0.05 Wh.", + "running_dead_zone": "After cycle starts, ignore power dips for this many seconds to prevent false end detection during initial spin-up phase. Set to 0 to disable. Default: 3s.", + "end_repeat_count": "Number of times the end condition (power below threshold for off_delay) must be met consecutively before ending the cycle. Higher values prevent false ends during pauses. Default: 1.", + "min_off_gap": "If a cycle starts within this many seconds of the previous one ending, they will be MERGED into a single cycle.", + "sampling_interval": "Minimum sampling interval in seconds. Updates faster than this rate will be ignored. Default: 30s (2s for washing machines, washer-dryers, and dishwashers)." + } + }, + "matching_section": { + "name": "Profile Matching & Learning", + "description": "How aggressively WashData matches running cycles against learned profiles and when to ask for feedback.", + "data": { + "profile_match_min_duration_ratio": "Profile Match Min Duration Ratio (0.1-1.0)", + "profile_match_interval": "Profile Match Interval (seconds)", + "profile_match_threshold": "Profile Match Threshold", + "profile_unmatch_threshold": "Profile Unmatch Threshold", + "auto_label_confidence": "Auto-Label Confidence (0-1)", + "learning_confidence": "Feedback Request Confidence (0-1)", + "suppress_feedback_notifications": "Suppress Feedback Notifications", + "duration_tolerance": "Duration Tolerance (0-0.5)", + "smoothing_window": "Smoothing Window (samples)", + "profile_duration_tolerance": "Profile Match Duration Tolerance (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum duration ratio for profile matching. Running cycle must be at least this fraction of profile duration to match. Lower = earlier matching. Default: 0.10 (10%).", + "profile_match_interval": "How often (in seconds) to attempt profile matching during a cycle. Lower values provide faster detection but use more CPU. Default: 300s (5 minutes).", + "profile_match_threshold": "Minimum DTW similarity score (0.0–1.0) required to consider a profile as a match. Higher = stricter matching, fewer false positives. Default: 0.4.", + "profile_unmatch_threshold": "DTW similarity score (0.0–1.0) below which a previously matched profile is rejected. Should be lower than match threshold to prevent flickering. Default: 0.35.", + "auto_label_confidence": "Automatically label cycles at or above this confidence on completion (0.0–1.0).", + "learning_confidence": "Minimum confidence to request user verification via an event (0.0–1.0).", + "suppress_feedback_notifications": "When enabled, WashData will still track and request feedback internally but will not show persistent notifications asking you to confirm cycles. Useful when cycles are always detected correctly and you find the prompts distracting.", + "duration_tolerance": "Tolerance for time-remaining estimates during a run (0.0–0.5 corresponds to 0–50%).", + "smoothing_window": "Number of recent power readings used for moving-average smoothing.", + "profile_duration_tolerance": "Tolerance used by profile matching for total duration variance (0.0–0.5). Default behavior: ±25%." + } + }, + "timing_section": { + "name": "Timing, Maintenance & Debug", + "description": "Watchdog, progress reset, auto-maintenance, and debug entity exposure.", + "data": { + "watchdog_interval": "Watchdog Interval (seconds)", + "no_update_active_timeout": "No-Update Timeout (s)", + "progress_reset_delay": "Progress Reset Delay (seconds)", + "auto_maintenance": "Enable Auto-Maintenance", + "expose_debug_entities": "Expose Debug Entities", + "save_debug_traces": "Save Debug Traces" + }, + "data_description": { + "watchdog_interval": "Seconds between watchdog checks while running. Default: 30s. WARNING: Ensure this is HIGHER than your sensor's update interval to avoid false stops.", + "no_update_active_timeout": "For publish-on-change sensors: only force-end an ACTIVE cycle if no updates arrive for this many seconds. Low-power completion still uses the Off Delay.", + "progress_reset_delay": "After a cycle completes (100%), wait this many seconds of idle before resetting progress to 0%. Default: 1800s.", + "auto_maintenance": "Enable auto-maintenance (repair samples and perform routine cleanup).", + "expose_debug_entities": "Show advanced sensors (Confidence, Phase, Ambiguity) for debugging.", + "save_debug_traces": "Store detailed ranking and power trace data in history (Increases storage usage)." + } + }, + "anti_wrinkle_section": { + "name": "Anti-Wrinkle Shield (Dryers)", + "description": "Ignore post-cycle drum rotations to prevent ghost cycles. Dryer / washer-dryer only.", + "data": { + "anti_wrinkle_enabled": "Anti-Wrinkle Shield (Dryer/Washer-Dryer Only)", + "anti_wrinkle_max_power": "Anti-Wrinkle Max Power (W)", + "anti_wrinkle_max_duration": "Anti-Wrinkle Max Duration (s)", + "anti_wrinkle_exit_power": "Anti-Wrinkle Exit Power (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignore short low-power drum rotations after a cycle ends to prevent ghost cycles (dryer/washer-dryer only).", + "anti_wrinkle_max_power": "Bursts at or below this power are treated as anti-wrinkle rotations instead of new cycles. Only applies when Anti-Wrinkle Shield is enabled.", + "anti_wrinkle_max_duration": "Maximum burst length to treat as anti-wrinkle rotation. Only applies when Anti-Wrinkle Shield is enabled.", + "anti_wrinkle_exit_power": "If power drops below this level, exit anti-wrinkle immediately (true off). Only applies when Anti-Wrinkle Shield is enabled." + } + }, + "delay_start_section": { + "name": "Delayed Start Detection", + "description": "Treat sustained low-power standby as 'Waiting to Start' until the cycle actually begins.", + "data": { + "delay_start_detect_enabled": "Enable Delayed Start Detection", + "delay_confirm_seconds": "Delayed Start: Standby Confirm Time (s)", + "delay_timeout_hours": "Delayed Start: Max Wait Time (h)" + }, + "data_description": { + "delay_start_detect_enabled": "When enabled, sustained low power between Stop Threshold and Start Threshold (W) is treated as a delayed start. The state sensor shows 'Waiting to Start' until power crosses Start Threshold (W) for long enough to count as a real cycle. Tune Start Threshold (W) above any background tumble/anti-damp power your appliance draws while waiting.", + "delay_confirm_seconds": "Power must stay between Stop Threshold (W) and Start Threshold (W) for this many seconds before WashData enters 'Waiting to Start'. Filters out brief menu-navigation peaks. Default: 60 s.", + "delay_timeout_hours": "Safety timeout: if the machine is still in 'Waiting to Start' after this many hours, WashData resets to Off. Default: 8 h." + } + }, + "external_triggers_section": { + "name": "External Triggers, Door & Pause", + "description": "Optional external end-trigger sensor, door sensor for pause/clean detection, and pause-power-cut switch.", + "data": { + "external_end_trigger_enabled": "Enable External Cycle End Trigger", + "external_end_trigger": "External Cycle End Sensor", + "external_end_trigger_inverted": "Invert Trigger Logic (Trigger on OFF)", + "door_sensor_entity": "Door Sensor", + "pause_cuts_power": "Cut Power When Pausing", + "switch_entity": "Switch Entity (for Pause Power Cut)", + "notify_unload_delay_minutes": "Laundry Waiting Notification Delay (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Enable monitoring of an external binary sensor to trigger cycle completion.", + "external_end_trigger": "Select a binary sensor entity. When this sensor triggers, the current cycle will end with status 'completed'.", + "external_end_trigger_inverted": "By default, the trigger fires when the sensor turns ON. Check this box to fire when the sensor turns OFF instead.", + "door_sensor_entity": "Optional binary sensor for the machine door (on = open, off = closed). When the door opens during an active cycle, WashData will confirm the cycle as intentionally paused. After the cycle ends, if the door is still closed, the state changes to 'Clean' until the door is opened. Note: closing the door does NOT auto-resume a paused cycle - resume must be triggered manually via the Resume Cycle button or service.", + "pause_cuts_power": "When pausing via the Pause Cycle button or service, also turn off the configured switch entity. Only takes effect if a switch entity is configured for this device.", + "switch_entity": "The switch entity to toggle when pausing or resuming. Required when 'Cut Power When Pausing' is enabled.", + "notify_unload_delay_minutes": "Send a notification via the finish notification channel after the cycle ends and the door has not been opened for this many minutes (requires Door Sensor). Set to 0 to disable. Default: 60 min." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Optionally link this WashData device to an existing Home Assistant device (for example the smart plug or the appliance itself). The WashData device is then shown as \"Connected via\" that device. Leave empty to keep WashData as a standalone device.", + "data": { + "linked_device": "Linked Device" + }, + "data_description": { + "linked_device": "Select the device to connect WashData to. This adds a 'Connected via' reference in the device registry; WashData keeps its own device card and entities. Clear the selection to remove the link." + } + }, + "pump_section": { + "name": "Pump Monitor", + "description": "Pump device type only. Fires an event if a pump cycle runs past this duration.", + "data": { + "pump_stuck_duration": "Pump Stuck Alert Threshold (s) (Pump Only)" + }, + "data_description": { + "pump_stuck_duration": "If a pump cycle is still running after this many seconds, a ha_washdata_pump_stuck event is fired once. Use this to detect a jammed motor (typical sump pump run is under 60 s). Default: 1800 s (30 min). Pump device type only." + } + } + } + }, + "diagnostics": { + "title": "Diagnostics & Maintenance", + "description": "Run maintenance actions like merging fragmented cycles or migrating stored data.\n\n**Storage Usage**\n\n- File Size: {file_size_kb} KB\n- Cycles: {cycle_count}\n- Profiles: {profile_count}\n- Debug Traces: {debug_count}", + "menu_options": { + "reprocess_history": "Maintenance: Reprocess & Optimize Data", + "clear_debug_data": "Clear Debug Data (Free up space)", + "wipe_history": "Wipe ALL data for this device (irreversible)", + "export_import": "Export/Import JSON with settings (copy/paste)", + "menu_back": "← Back" + } + }, + "clear_debug_data": { + "title": "Clear Debug Data", + "description": "Are you sure you want to delete all stored debug traces? This will free up space but remove detailed ranking info from past cycles." + }, + "manage_cycles": { + "title": "Manage Cycles", + "description": "### Recent cycles\n\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "select_cycle_to_label": "Label Specific Cycle", + "select_cycle_to_delete": "Delete Cycle", + "interactive_editor": "Merge/Split Interactive Editor", + "trim_cycle_select": "Trim Cycle Data", + "menu_back": "← Back" + } + }, + "manage_cycles_empty": { + "title": "Manage Cycles", + "description": "No cycles recorded yet.", + "menu_options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "menu_back": "← Back" + } + }, + "manage_profiles": { + "title": "Manage Profiles", + "description": "Profile Summary:\n{profile_summary}", + "menu_options": { + "create_profile": "Create New Profile", + "edit_profile": "Edit/Rename Profile", + "delete_profile_select": "Delete Profile", + "profile_stats": "Profile Statistics", + "cleanup_profile": "Clean Up History - Graph & Delete", + "assign_profile_phases_select": "Assign Phase Ranges", + "menu_back": "← Back" + } + }, + "manage_profiles_empty": { + "title": "Manage Profiles", + "description": "No profiles created yet.", + "menu_options": { + "create_profile": "Create New Profile", + "menu_back": "← Back" + } + }, + "manage_phase_catalog": { + "title": "Manage Phase Catalog", + "description": "Phase catalog (defaults for this device + custom phases):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Create New Phase", + "phase_catalog_edit_select": "Edit Phase", + "phase_catalog_delete": "Delete Phase", + "menu_back": "← Back" + } + }, + "phase_catalog_create": { + "title": "Create Custom Phase", + "description": "Create a custom phase name and description, and choose which device type it applies to.", + "data": { + "target_device_type": "Target Device Type", + "phase_name": "Phase Name", + "phase_description": "Phase Description" + } + }, + "phase_catalog_edit_select": { + "title": "Edit Custom Phase", + "description": "Select a custom phase to edit.", + "data": { + "phase_name": "Custom Phase" + } + }, + "phase_catalog_edit": { + "title": "Edit Custom Phase", + "description": "Editing phase: {phase_name}", + "data": { + "phase_name": "Phase Name", + "phase_description": "Phase Description" + } + }, + "phase_catalog_delete": { + "title": "Delete Custom Phase", + "description": "Select a custom phase to delete. Any assigned ranges using this phase will be removed.", + "data": { + "phase_name": "Custom Phase" + } + }, + "assign_profile_phases": { + "title": "Assign Phase Ranges", + "description": "Phase preview for profile: **{profile_name}**\n\n{timeline_svg}\n\n**Current ranges:**\n{current_ranges}\n\nUse actions below to add, edit, delete, or save ranges.", + "menu_options": { + "assign_profile_phases_add": "Add Phase Range", + "assign_profile_phases_edit_select": "Edit Phase Range", + "assign_profile_phases_delete": "Delete Phase Range", + "phase_ranges_clear": "Clear All Ranges", + "assign_profile_phases_auto_detect": "Auto-detect Phases", + "phase_ranges_save": "Save and Return", + "menu_back": "← Back" + } + }, + "assign_profile_phases_add": { + "title": "Add Phase Range", + "description": "Add one phase range to the current draft.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minute", + "end_min": "End Minute" + } + }, + "assign_profile_phases_edit_select": { + "title": "Edit Phase Range", + "description": "Select a range to edit.", + "data": { + "range_index": "Phase Range" + } + }, + "assign_profile_phases_edit": { + "title": "Edit Phase Range", + "description": "Update the selected phase range.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minute", + "end_min": "End Minute" + } + }, + "assign_profile_phases_delete": { + "title": "Delete Phase Range", + "description": "Select a range to remove from the draft.", + "data": { + "range_index": "Phase Range" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detected Phases", + "description": "Profile: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} phase(s) detected automatically.** Choose an action below.", + "data": { + "action": "Action" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Name Detected Phases", + "description": "Profile: **{profile_name}**\n\n**{detected_count} phase(s) detected:**\n{ranges_summary}\n\nAssign a name to each phase." + }, + "assign_profile_phases_select": { + "title": "Assign Phase Ranges", + "description": "Select a profile to configure phase ranges.", + "data": { + "profile": "Profile" + } + }, + "profile_stats": { + "title": "Profile Statistics", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Create New Profile", + "description": "Create a new profile manually or from a past cycle.\n\nProfile name examples: 'Cotton 40°C', 'Eco', 'Quick Wash', 'Cotton + Cupboard Dry'.\n\n**What the matcher uses:** power-consumption shape, total cycle duration, and total energy. It does not read temperature or spin settings directly.\n\n**Profiles that will be reliably auto-detected:** programs with clearly different durations or power patterns - e.g. a 20-minute Quick Wash vs a 3-hour Cotton cycle, or a wash-only cycle vs a wash+dry combo (which typically runs 2-3× longer).\n\n**Profiles that may need manual selection:** variants of the same program that differ only in temperature or spin speed (e.g. Cotton 40°C vs Cotton 60°C) often produce similar power shapes. The system will still attempt to distinguish them and learn from your corrections over time, but expect to confirm matches manually at first.\n\n**Washer-dryer tip:** create separate profiles for wash-only and wash+dry runs - the duration and energy difference is large enough for reliable auto-detection.", + "data": { + "profile_name": "Profile Name", + "reference_cycle": "Reference Cycle (optional)", + "manual_duration": "Manual Duration (minutes)" + } + }, + "edit_profile": { + "title": "Edit Profile", + "description": "Select a profile to rename", + "data": { + "profile": "Profile" + } + }, + "rename_profile": { + "title": "Edit Profile Details", + "description": "Current profile: {current_name}", + "data": { + "new_name": "Profile Name", + "manual_duration": "Manual Duration (minutes)" + } + }, + "delete_profile_select": { + "title": "Delete Profile", + "description": "Select a profile to delete", + "data": { + "profile": "Profile" + } + }, + "delete_profile_confirm": { + "title": "Confirm Delete Profile", + "description": "⚠️ This will permanently delete profile: {profile_name}", + "data": { + "unlabel_cycles": "Remove label from cycles using this profile" + } + }, + "auto_label_cycles": { + "title": "Auto-Label Old Cycles", + "description": "Found {total_count} total cycles. Profiles: {profiles}", + "data": { + "confidence_threshold": "Minimum Confidence (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Select Cycle to Label", + "description": "✓ = completed, ⚠ = resumed, ✗ = interrupted", + "data": { + "cycle_id": "Cycle" + } + }, + "select_cycle_to_delete": { + "title": "Delete Cycle", + "description": "⚠️ This will permanently delete the selected cycle", + "data": { + "cycle_id": "Cycle to Delete" + } + }, + "label_cycle": { + "title": "Label Cycle", + "description": "{cycle_info}", + "data": { + "profile_name": "Profile", + "new_profile_name": "New Profile Name (if creating)" + } + }, + "post_process": { + "title": "Process History (Merge/Split)", + "description": "Clean up cycle history by merging fragmented runs and splitting incorrectly merged cycles within a time window. Enter number of hours to look back (use 999999 for all).", + "data": { + "time_range": "Lookback Window (hours)", + "gap_seconds": "Merge/Split Gap (seconds)" + }, + "data_description": { + "time_range": "Number of hours to scan for fragmented cycles to merge. Use 999999 to process all historical data.", + "gap_seconds": "Threshold to decide split vs merge. Gaps SHORTER than this are merged. Gaps LONGER than this are split. Lower this to force a split on a cycle that was merged incorrectly." + } + }, + "cleanup_profile": { + "title": "Clean Up History - Select Profile", + "description": "Select a profile to visualize. This will generate a graph showing all past cycles for this profile to help identify outliers.", + "data": { + "profile": "Profile" + } + }, + "cleanup_select": { + "title": "Clean Up History - Graph & Delete", + "description": "![Graph]({graph_url})\n\n**Visualizing {profile_name}**\n\nThe graph shows individual cycles as colored lines. Identify outliers (lines far from the group) and matching their color in the list below to delete them.\n\n**Select cycles to PERMANENTLY delete:**", + "data": { + "cycles_to_delete": "Cycles to Delete (check matching colors)" + } + }, + "interactive_editor": { + "title": "Merge/Split Interactive Editor", + "description": "Select a manual action to perform on your cycle history.", + "menu_options": { + "editor_split": "Split a Cycle (Find gaps)", + "editor_merge": "Merge Cycles (Join fragments)", + "editor_delete": "Delete Cycle(s)", + "menu_back": "← Back" + } + }, + "editor_select": { + "title": "Select Cycles", + "description": "Select the cycle(s) to process.\n\n{info_text}", + "data": { + "selected_cycles": "Cycles" + } + }, + "editor_configure": { + "title": "Configure & Apply", + "description": "{preview_md}", + "data": { + "confirm_action": "Action", + "merged_profile": "Resulting Profile", + "new_profile_name": "New Profile Name", + "confirm_commit": "Yes, I confirm this action", + "segment_0_profile": "Segment 1 Profile", + "segment_1_profile": "Segment 2 Profile", + "segment_2_profile": "Segment 3 Profile", + "segment_3_profile": "Segment 4 Profile", + "segment_4_profile": "Segment 5 Profile", + "segment_5_profile": "Segment 6 Profile", + "segment_6_profile": "Segment 7 Profile", + "segment_7_profile": "Segment 8 Profile", + "segment_8_profile": "Segment 9 Profile", + "segment_9_profile": "Segment 10 Profile" + } + }, + "editor_split_params": { + "title": "Split Method", + "description": "Choose how to split this cycle. **Auto-detect** finds idle gaps in the power data; **Manual timestamp** lets you set explicit split points (useful when one cycle ran straight into the next with no idle gap between them).", + "data": { + "split_mode": "Split Method" + } + }, + "editor_split_auto_params": { + "title": "Auto-Split Configuration", + "description": "Adjust the sensitivity for splitting this cycle. Any idle gap longer than this will cause a split.", + "data": { + "min_gap_seconds": "Split Gap Threshold (seconds)" + } + }, + "editor_split_manual_params": { + "title": "Manual Split Timestamps", + "description": "{preview_md}Cycle window: **{cycle_start_wallclock} → {cycle_end_wallclock}** on {cycle_date}.\n\nEnter one or more split timestamps (`HH:MM` or `HH:MM:SS`), one per line. The cycle will be cut at each timestamp — N timestamps produce N+1 segments.", + "data": { + "split_timestamps": "Split Timestamps" + } + }, + "reprocess_history": { + "title": "Maintenance: Reprocess & Optimize Data", + "description": "Performs deep cleaning of historical data:\n1. Trims zero-power readings (silence).\n2. Fixes cycle timing/duration.\n3. Recalculates all statistical models (envelopes).\n\n**Run this to fix data issues or apply new algorithms.**" + }, + "wipe_history": { + "title": "Wipe History (Testing Only)", + "description": "⚠️ This will permanently delete ALL stored cycles and profiles for this device. This cannot be undone!" + }, + "export_import": { + "title": "Export/Import JSON", + "description": "Copy/paste cycles, profiles, AND settings for this device. Export below or paste JSON to import.", + "data": { + "mode": "Action", + "json_payload": "Complete Configuration JSON" + }, + "data_description": { + "mode": "Choose Export to copy current data and settings, or Import to paste exported data from another WashData device.", + "json_payload": "For Export: copy this JSON (includes cycles, profiles, and all fine-tuned settings). For Import: paste JSON exported from WashData." + } + }, + "record_cycle": { + "title": "Record Cycle", + "description": "Manually record a cycle to create a clean profile without interference.\n\nStatus: **{status}**\nDuration: {duration}s\nSamples: {samples}", + "menu_options": { + "record_refresh": "Refresh Status", + "record_stop": "Stop Recording (Save & Process)", + "record_start": "Start New Recording", + "record_process": "Process Last Recording (Trim & Save)", + "record_discard": "Discard Last Recording", + "menu_back": "← Back" + } + }, + "record_process": { + "title": "Process Recording", + "description": "![Graph]({graph_url})\n\n**Graph shows raw recording.** Blue = Keep, Red = Proposed Trim.\n*Graph is static and does not update with form changes.*\n\nRecording stopped. Trims are aligned to the detected sampling rate (~{sampling_rate}s).\n\nRaw Duration: {duration}s\nSamples: {samples}", + "data": { + "head_trim": "Trim Start (seconds)", + "tail_trim": "Trim End (seconds)", + "save_mode": "Save Destination", + "profile_name": "Profile Name" + } + }, + "learning_feedbacks": { + "title": "Learning Feedbacks", + "description": "{count} pending review request(s).\n\n{pending_table}", + "menu_options": { + "learning_feedbacks_pick": "Review a pending feedback", + "learning_feedbacks_dismiss_all": "Dismiss all pending feedback", + "menu_back": "← Back" + } + }, + "learning_feedbacks_pick": { + "title": "Pick Feedback to Review", + "description": "Select a cycle review request to open.", + "data": { + "selected_feedback": "Select Cycle to Review" + } + }, + "learning_feedbacks_empty": { + "title": "Learning Feedbacks", + "description": "No pending feedback requests found.", + "menu_options": { + "menu_back": "← Back" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Dismiss All Pending Feedbacks", + "description": "You are about to dismiss **{count}** pending feedback request(s). Dismissed cycles remain in your history but will not contribute new learning signal. This cannot be undone.\n\n✅ Check the box below and click **Submit** to dismiss all.\n❌ Leave it unchecked and click **Submit** to cancel and go back.", + "data": { + "confirm_dismiss_all": "Confirm: dismiss all pending feedback requests" + } + }, + "resolve_feedback": { + "title": "Resolve Feedback", + "description": "{comparison_img}{candidates_table}\n**Detected Profile**: {detected_profile} ({confidence_pct}%)\n**Estimated Duration**: {est_duration_min} min\n**Actual Duration**: {act_duration_min} min\n\n__Choose an action below:__", + "data": { + "action": "What would you like to do?", + "corrected_profile": "Correct Program (if correcting)", + "corrected_duration": "Correct Duration in minutes (if correcting)" + } + }, + "trim_cycle_select": { + "title": "Trim Cycle - Select Cycle", + "description": "Select a cycle to trim. ✓ = completed, ⚠ = resumed, ✗ = interrupted", + "data": { + "cycle_id": "Cycle" + } + }, + "trim_cycle": { + "title": "Trim Cycle", + "description": "Current window: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Action" + } + }, + "trim_cycle_start": { + "title": "Set Trim Start", + "description": "Cycle window: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nCurrent start: **{current_wallclock}** (+{current_offset_min} min from cycle start)\n\nPick a new start time using the clock picker below.", + "data": { + "trim_start_time": "New start time", + "trim_start_min": "New start (minutes from cycle start)" + } + }, + "trim_cycle_end": { + "title": "Set Trim End", + "description": "Cycle window: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nCurrent end: **{current_wallclock}** (+{current_offset_min} min from cycle start)\n\nPick a new end time using the clock picker below.", + "data": { + "trim_end_time": "New end time", + "trim_end_min": "New end (minutes from cycle start)" + } + } + }, + "error": { + "import_failed": "Import failed. Please paste a valid WashData export JSON.", + "select_exactly_one": "Select exactly one cycle for this action.", + "select_at_least_one": "Select at least one cycle for this action.", + "select_at_least_two": "Select at least two cycles for this action.", + "profile_exists": "A profile with this name already exists", + "rename_failed": "Failed to rename profile. Profile may not exist or new name is already taken.", + "assignment_failed": "Failed to assign profile to cycle", + "duplicate_phase": "A phase with this name already exists.", + "phase_not_found": "Selected phase was not found.", + "invalid_phase_name": "Phase name cannot be empty.", + "phase_name_too_long": "Phase name is too long.", + "invalid_phase_range": "Phase range is invalid. End must be greater than start.", + "invalid_phase_timestamp": "Timestamp is invalid. Use YYYY-MM-DD HH:MM or HH:MM format.", + "overlapping_phase_ranges": "Phase ranges overlap. Adjust the ranges and try again.", + "incomplete_phase_row": "Each phase row must include a phase plus complete start/end values for the selected mode.", + "timestamp_mode_cycle_required": "Please select a source cycle when using timestamp mode.", + "no_phase_ranges": "No phase ranges are available for that action yet.", + "feedback_notification_title": "WashData: Verify Cycle ({device})", + "feedback_notification_message": "**Device**: {device}\n**Program**: {program} ({confidence}% confidence)\n**Time**: {time}\n\nWashData needs your help to verify this detected cycle.\n\nPlease go to **Settings > Devices & Services > WashData > Configure > Learning Feedbacks** to confirm or correct this result.", + "suggestions_ready_notification_title": "WashData: Suggested Settings Ready ({device})", + "suggestions_ready_notification_message": "The **Suggested Settings** sensor now reports **{count}** actionable recommendations.\n\nTo review and apply them: **Settings > Devices & Services > WashData > Configure > Advanced Settings > Apply Suggested Values**.\n\nSuggestions are optional and shown for review before you save.", + "trim_range_invalid": "Start must be before end. Please adjust your trim points.", + "trim_failed": "Trim failed. The cycle may no longer exist or the window is empty.", + "invalid_split_timestamp": "Timestamp is invalid or outside the cycle window. Use HH:MM or HH:MM:SS within the cycle's window.", + "no_split_segments_found": "No valid segments could be produced from these timestamps. Make sure each timestamp is inside the cycle window and segments are at least 1 minute long.", + "auto_tune_suggestion": "{device_type} {device_title} detected ghost cycles. Suggested min_power change: {current_min}W -> {new_min}W (not applied automatically).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} detected ghost cycles. Suggested min_power change: {current_min}W -> {new_min}W (not applied automatically)." + }, + "abort": { + "no_cycles_found": "No cycles found", + "no_split_segments_found": "No splittable segments were found in the selected cycle.", + "cycle_not_found": "The selected cycle could not be loaded.", + "no_power_data": "No power trace data available for the selected cycle.", + "no_profiles_found": "No profiles found. Create a profile first.", + "no_profiles_for_matching": "No profiles available for matching. Create profiles first.", + "no_unlabeled_cycles": "All cycles are already labeled", + "no_suggestions": "No suggested values available yet. Run a few cycles and try again.", + "no_predictions": "No predictions", + "no_custom_phases": "No custom phases found.", + "reprocess_success": "Successfully reprocessed {count} cycles.", + "debug_data_cleared": "Successfully cleared debug data from {count} cycles." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cycle Start", + "cycle_finish": "Cycle Finish", + "cycle_live": "Live Progress" + } + }, + "device_type": { + "options": { + "washing_machine": "Washing Machine", + "dryer": "Dryer", + "washer_dryer": "Washer-Dryer Combo", + "dishwasher": "Dishwasher", + "coffee_machine": "Coffee Machine (deprecated)", + "ev": "Electric Vehicle (deprecated)", + "air_fryer": "Air Fryer", + "heat_pump": "Heat Pump (deprecated)", + "bread_maker": "Bread Maker", + "pump": "Pump / Sump Pump", + "oven": "Oven (deprecated)", + "other": "Other (Advanced)" + } + }, + "common_text": { + "options": { + "unlabeled": "(Unlabeled)", + "create_new_profile": "Create New Profile", + "remove_label": "Remove Label", + "no_reference_cycle": "(No reference cycle)", + "all_device_types": "All Device Types", + "current_suffix": "(current)", + "phase_builtin_suffix": "(Built-in)", + "phase_none_available": "No phases available.", + "phase_other_device_types": "Other device types:", + "settings_deprecation_warning": "⚠️ **Deprecated device type:** {device_type} is scheduled for removal in a future release. WashData's matching pipeline does not produce reliable results for this appliance class. Your integration keeps working through the deprecation period; to silence this warning, switch **Device Type** below to one of the supported types (Washing Machine, Dryer, Washer-Dryer Combo, Dishwasher, Air Fryer, Bread Maker, or Pump), or to **Other (Advanced)** if your appliance does not match any of the supported types. **Other (Advanced)** ships intentionally generic defaults that are not tuned for any specific appliance, so you will need to configure thresholds, timeouts, and matching parameters yourself; all your existing settings are preserved when you switch.", + "editor_select_info": "Select 1 cycle to split, or 2+ cycles to merge.", + "editor_select_info_split": "Select 1 cycle to split.", + "editor_select_info_merge": "Select 2+ cycles to merge.", + "editor_select_info_delete": "Select 1+ cycle(s) to delete.", + "no_cycles_recorded": "No cycles recorded yet.", + "profile_summary_line": "- **{name}**: {count} cycles, {avg}m avg", + "no_profiles_created": "No profiles created yet.", + "phase_preview": "Phase Preview", + "phase_preview_no_curve": "Average profile curve is not available yet. Run/label more cycles for this profile.", + "average_power_curve": "Average Power Curve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cycles, ~{duration}m avg)", + "profile_option_short_fmt": "{name} ({count} cycles, ~{duration}m)", + "deleted_cycles_from_profile": "Deleted {count} cycles from {profile}.", + "not_enough_data_graph": "Not enough data to generate graph.", + "no_profiles_found": "No profiles found.", + "cycle_info_fmt": "Cycle: {start}, {duration}m, Current: {label}", + "top_candidates_header": "### Top Candidates", + "tbl_profile": "Profile", + "tbl_confidence": "Confidence", + "tbl_correlation": "Correlation", + "tbl_duration_match": "Duration Match", + "detected_profile": "Detected Profile", + "estimated_duration": "Estimated Duration", + "actual_duration": "Actual Duration", + "choose_action": "__Choose an action below:__", + "tbl_count": "Count", + "tbl_avg": "Avg", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energy (Avg)", + "tbl_energy_total": "Energy (Total)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Last Run", + "graph_legend_title": "Graph Legend", + "graph_legend_body": "The blue band represents the minimum and maximum power draw range observed. The line shows the average power curve.", + "recording_preview": "Recording Preview", + "trim_start": "Trim Start", + "trim_end": "Trim End", + "split_preview_title": "Split Preview", + "split_preview_found_fmt": "Found {count} segments.", + "split_preview_confirm_fmt": "Click Confirm to split this cycle into {count} separate cycles.", + "merge_preview_title": "Merge Preview", + "merge_preview_joining_fmt": "Joining {count} cycles. Gaps will be filled with 0W readings.", + "editor_delete_preview_title": "Delete Preview", + "editor_delete_preview_intro": "The selected cycles will be permanently deleted:", + "editor_delete_preview_confirm": "Click Confirm to permanently delete these cycle records.", + "no_power_preview": "*No power data available for preview.*", + "profile_comparison": "Profile Comparison", + "actual_cycle_label": "This cycle (actual)", + "storage_usage_header": "Storage Usage", + "storage_file_size": "File Size", + "storage_cycles": "Cycles", + "storage_profiles": "Profiles", + "storage_debug_traces": "Debug Traces", + "overview_suffix": "Overview", + "phase_preview_for_profile": "Phase preview for profile", + "current_ranges_header": "Current ranges", + "assign_phases_help": "Use actions below to add, edit, delete, or save ranges.", + "profile_summary_header": "Profile Summary", + "recent_cycles_header": "Recent cycles", + "trim_cycle_preview_title": "Cycle Trim Preview", + "trim_cycle_preview_no_data": "No power data available for this cycle.", + "trim_cycle_preview_kept_suffix": "kept", + "table_program": "Program", + "table_when": "When", + "table_length": "Length", + "table_match": "Match", + "table_profile": "Profile", + "table_cycles": "Cycles", + "table_avg_length": "Avg Length", + "table_last_run": "Last Run", + "table_avg_energy": "Avg Energy", + "table_detected_program": "Detected Program", + "table_confidence": "Confidence", + "table_reported": "Reported" + } + }, + "interactive_editor_action": { + "options": { + "split": "Split a Cycle (Find gaps)", + "merge": "Merge Cycles (Join fragments)", + "delete": "Delete Cycle(s)" + } + }, + "split_mode": { + "options": { + "auto": "Auto-detect idle gaps", + "manual": "Manual timestamp(s)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Maintenance: Reprocess & Optimize Data", + "clear_debug_data": "Clear Debug Data (Free up space)", + "wipe_history": "Wipe ALL data for this device (irreversible)", + "export_import": "Export/Import JSON with settings (copy/paste)" + } + }, + "export_import_mode": { + "options": { + "export": "Export All Data", + "import": "Import/Merge Data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "select_cycle_to_label": "Label Specific Cycle", + "select_cycle_to_delete": "Delete Cycle", + "interactive_editor": "Merge/Split Interactive Editor", + "trim_cycle": "Trim Cycle Data" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Create New Profile", + "edit_profile": "Edit/Rename Profile", + "delete_profile": "Delete Profile", + "profile_stats": "Profile Statistics", + "cleanup_profile": "Clean Up History - Graph & Delete", + "assign_phases": "Assign Phase Ranges" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Create New Phase", + "edit_custom_phase": "Edit Phase", + "delete_custom_phase": "Delete Phase" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Name & apply phases", + "cancel": "Go back without changes" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Add Phase Range", + "edit_range": "Edit Phase Range", + "delete_range": "Delete Phase Range", + "clear_ranges": "Clear All Ranges", + "auto_detect_ranges": "Auto-detect Phases", + "save_ranges": "Save and Return" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Refresh Status", + "stop_recording": "Stop Recording (Save & Process)", + "start_recording": "Start New Recording", + "process_recording": "Process Last Recording (Trim & Save)", + "discard_recording": "Discard Last Recording" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Create New Profile", + "existing_profile": "Add to Existing Profile", + "discard": "Discard Recording" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirm - Correct Detection", + "correct": "Correct - Choose Program/Duration", + "ignore": "Ignore - False Positive/Noisy Cycle", + "delete": "Delete - Remove from History" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Set Start Point", + "set_end": "Set End Point", + "reset": "Reset to Full Duration", + "apply": "Apply Trim", + "cancel": "Cancel" + } + } + }, + "services": { + "label_cycle": { + "name": "Label Cycle", + "description": "Assign an existing profile to a past cycle, or remove the label.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to label." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The ID of the cycle to label." + }, + "profile_name": { + "name": "Profile Name", + "description": "The name of an existing profile (create profiles in Manage Profiles menu). Leave blank to remove label." + } + } + }, + "create_profile": { + "name": "Create Profile", + "description": "Create a new profile (standalone or based on a reference cycle).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "profile_name": { + "name": "Profile Name", + "description": "Name for the new profile (e.g. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Reference Cycle ID", + "description": "Optional cycle ID to base this profile on." + } + } + }, + "delete_profile": { + "name": "Delete Profile", + "description": "Delete a profile and optionally unlabel cycles using it.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "profile_name": { + "name": "Profile Name", + "description": "The profile to delete." + }, + "unlabel_cycles": { + "name": "Unlabel Cycles", + "description": "Remove profile label from cycles using this profile." + } + } + }, + "auto_label_cycles": { + "name": "Auto-Label Old Cycles", + "description": "Retroactively label unlabeled cycles using profile matching.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "confidence_threshold": { + "name": "Confidence Threshold", + "description": "Minimum match confidence (0.50-0.95) to apply labels." + } + } + }, + "export_config": { + "name": "Export Config", + "description": "Export this device's profiles, cycles, and settings to a JSON file (per device).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to export." + }, + "path": { + "name": "Path", + "description": "Optional absolute file path to write (defaults to /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Import Config", + "description": "Import profiles, cycles, and settings for this device from a JSON export file (settings may be overwritten).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to import into." + }, + "path": { + "name": "Path", + "description": "Absolute path to the export JSON file to import." + } + } + }, + "submit_cycle_feedback": { + "name": "Submit Cycle Feedback", + "description": "Confirm or correct an auto-detected program after a completed cycle. Provide either `entry_id` (advanced) or `device_id` (recommended).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device (recommended instead of entry_id)." + }, + "entry_id": { + "name": "Entry ID", + "description": "The config entry id for the device (alternative to device_id)." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The cycle id shown in the feedback notification / logs." + }, + "user_confirmed": { + "name": "Confirm Detected Program", + "description": "Set true if the detected program is correct." + }, + "corrected_profile": { + "name": "Corrected Profile", + "description": "If not confirmed, provide the correct profile/program name." + }, + "corrected_duration": { + "name": "Corrected Duration (seconds)", + "description": "Optional corrected duration in seconds." + }, + "notes": { + "name": "Notes", + "description": "Optional notes about this cycle." + } + } + }, + "record_start": { + "name": "Record Cycle Start", + "description": "Start manually recording a clean cycle (bypasses all matching logic).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to record." + } + } + }, + "record_stop": { + "name": "Record Cycle Stop", + "description": "Stop manual recording.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to stop recording." + } + } + }, + "trim_cycle": { + "name": "Trim Cycle", + "description": "Trim the power data of a past cycle to a specific time window.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The ID of the cycle to trim." + }, + "trim_start_s": { + "name": "Trim Start (seconds)", + "description": "Keep data from this offset in seconds. Default 0." + }, + "trim_end_s": { + "name": "Trim End (seconds)", + "description": "Keep data up to this offset in seconds. Default = full duration." + } + } + }, + "pause_cycle": { + "name": "Pause Cycle", + "description": "Pause the active cycle for a WashData device.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to pause." + } + } + }, + "resume_cycle": { + "name": "Resume Cycle", + "description": "Resume a paused cycle for a WashData device.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to resume." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Running" + }, + "match_ambiguity": { + "name": "Match Ambiguity" + } + }, + "sensor": { + "washer_state": { + "name": "State", + "state": { + "off": "Off", + "idle": "Idle", + "starting": "Starting", + "running": "Running", + "paused": "Paused", + "user_paused": "Paused by user", + "ending": "Ending", + "finished": "Finished", + "anti_wrinkle": "Anti-Wrinkle", + "interrupted": "Interrupted", + "force_stopped": "Force Stopped", + "rinse": "Rinse", + "unknown": "Unknown", + "clean": "Clean", + "delay_wait": "Delay Start" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "total_duration": { + "name": "Total Duration" + }, + "cycle_progress": { + "name": "Progress" + }, + "current_power": { + "name": "Current Power" + }, + "elapsed_time": { + "name": "Elapsed Time" + }, + "debug_info": { + "name": "Debug Info" + }, + "match_confidence": { + "name": "Match Confidence" + }, + "top_candidates": { + "name": "Top Candidates", + "state": { + "none": "None" + } + }, + "current_phase": { + "name": "Current Phase" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Profile {profile_name} Count", + "unit_of_measurement": "cycles" + }, + "suggestions": { + "name": "Suggested Settings Available" + }, + "pump_runs_today": { + "name": "Pump Runs (Last 24 h)" + }, + "cycle_count": { + "name": "Cycle Count" + } + }, + "select": { + "program_select": { + "name": "Cycle Program", + "state": { + "auto_detect": "Auto-Detect" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Force End Cycle" + }, + "pause_cycle": { + "name": "Pause Cycle" + }, + "resume_cycle": { + "name": "Resume Cycle" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "A valid device_id is required." + }, + "cycle_id_required": { + "message": "A valid cycle_id is required." + }, + "profile_name_required": { + "message": "A valid profile_name is required." + }, + "device_not_found": { + "message": "Device not found." + }, + "no_config_entry": { + "message": "No config entry found for device." + }, + "integration_not_loaded": { + "message": "Integration not loaded for this device." + }, + "cycle_not_found_or_no_power": { + "message": "Cycle not found or has no power data." + }, + "trim_failed_empty_window": { + "message": "Trim failed - cycle not found, no power data, or resulting window is empty." + }, + "trim_invalid_range": { + "message": "trim_end_s must be greater than trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/suggestion_engine.py b/custom_components/ha_washdata/suggestion_engine.py new file mode 100644 index 0000000..f0eb84e --- /dev/null +++ b/custom_components/ha_washdata/suggestion_engine.py @@ -0,0 +1,455 @@ +"""Suggestion engine for WashData.""" + +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any, TYPE_CHECKING, cast + +import numpy as np +from homeassistant.core import HomeAssistant + +from .const import ( + CONF_WATCHDOG_INTERVAL, + CONF_NO_UPDATE_ACTIVE_TIMEOUT, + CONF_OFF_DELAY, + CONF_PROFILE_MATCH_INTERVAL, + CONF_PROFILE_MATCH_MAX_DURATION_RATIO, + CONF_PROFILE_MATCH_MIN_DURATION_RATIO, + CONF_DURATION_TOLERANCE, + CONF_PROFILE_DURATION_TOLERANCE, + CONF_START_THRESHOLD_W, + CONF_STOP_THRESHOLD_W, + CONF_END_ENERGY_THRESHOLD, + CONF_RUNNING_DEAD_ZONE, + CONF_MIN_OFF_GAP, + DEFAULT_OFF_DELAY_BY_DEVICE, + DEFAULT_OFF_DELAY, + DEFAULT_MIN_OFF_GAP_BY_DEVICE, + DEFAULT_MIN_OFF_GAP, +) +from .time_utils import power_data_to_offsets + +if TYPE_CHECKING: + from .profile_store import ProfileStore + +_LOGGER = logging.getLogger(__name__) + + +def _parse_ts(v: Any) -> float | None: + """Parse a value into a unix timestamp float, supporting ISO strings.""" + if isinstance(v, str): + try: + return datetime.fromisoformat(v.replace("Z", "+00:00")).timestamp() + except ValueError: + return None + return None + + +class SuggestionEngine: + """Refined engine for generating data-driven parameter suggestions.""" + + def __init__( + self, + hass: HomeAssistant, + entry_id: str, + profile_store: "ProfileStore", + device_type: str | None = None, + ) -> None: + """Initialize the suggestion engine.""" + self.hass = hass + self.entry_id = entry_id + self.profile_store = profile_store + self.device_type = device_type + + def generate_operational_suggestions(self, p95_dt: float, median_dt: float) -> dict[str, Any]: + """Generate suggestions for operational parameters based on cadence.""" + suggestions: dict[str, dict[str, Any]] = {} + + # 1. Watchdog Interval + suggested_watchdog = int(max(30, p95_dt * 10)) + suggestions[CONF_WATCHDOG_INTERVAL] = { + "value": suggested_watchdog, + "reason": f"Based on observed update cadence (p95={p95_dt:.1f}s) * 10 (min 30s buffer)." + } + + # 2. No Update Timeout + suggested_timeout = int(max(60, p95_dt * 20)) + suggestions[CONF_NO_UPDATE_ACTIVE_TIMEOUT] = { + "value": suggested_timeout, + "reason": f"Based on observed update cadence (p95={p95_dt:.1f}s) * 20 (min 60s)." + } + + # 3. Off Delay + # Use device-specific default as floor to prevent splitting cycles with long pauses + device_floor = ( + DEFAULT_OFF_DELAY_BY_DEVICE.get(self.device_type, DEFAULT_OFF_DELAY) + if self.device_type is not None + else DEFAULT_OFF_DELAY + ) + suggested_off_delay = int(max(device_floor, p95_dt * 5)) + + reason_off = f"Based on observed update cadence (p95={p95_dt:.1f}s) * 5" + if suggested_off_delay == device_floor: + if self.device_type and self.device_type in DEFAULT_OFF_DELAY_BY_DEVICE: + reason_off = ( + f"Used device-specific safe minimum for {self.device_type} ({device_floor}s)." + ) + else: + reason_off = f"Used generic safe minimum ({DEFAULT_OFF_DELAY}s)." + + suggestions[CONF_OFF_DELAY] = { + "value": suggested_off_delay, + "reason": reason_off + } + + # 4. Profile Match Interval + suggested_match = int(max(10, median_dt * 10)) + suggestions[CONF_PROFILE_MATCH_INTERVAL] = { + "value": suggested_match, + "reason": f"Based on observed update cadence (median={median_dt:.1f}s) * 10." + } + + return suggestions + + def generate_model_suggestions(self) -> dict[str, Any]: + """Generate suggestions for model parameters based on past cycles.""" + suggestions: dict[str, dict[str, Any]] = {} + + cycles = self.profile_store.get_past_cycles()[-100:] + profiles = self.profile_store.get_profiles() + + ratios: list[float] = [] + for c in cycles: + if not isinstance(c, dict): + continue + profile_name = c.get("profile_name") + if not isinstance(profile_name, str) or c.get("status") == "interrupted": + continue + prof = profiles.get(profile_name) + if not isinstance(prof, dict): + continue + try: + avg = float(prof.get("avg_duration") or 0.0) + dur = float(c.get("duration") or 0.0) + except (TypeError, ValueError): + continue + if avg > 60 and dur > 60: + ratios.append(dur / avg) + + if len(ratios) >= 10: + arr: np.ndarray[Any, np.dtype[np.float64]] = np.array(ratios, dtype=float) + deviations = np.abs(arr - 1.0) + p95_dev = float(np.percentile(deviations, 95)) + + suggested_tol = min(0.50, max(0.10, round(p95_dev + 0.05, 2))) + reason_tol = f"Based on duration variance of {len(ratios)} recent labeled cycles (p95 dev={p95_dev:.2f})." + + suggestions[CONF_DURATION_TOLERANCE] = {"value": suggested_tol, "reason": reason_tol} + suggestions[CONF_PROFILE_DURATION_TOLERANCE] = {"value": suggested_tol, "reason": reason_tol} + + p05_ratio = float(np.percentile(arr, 5)) + p95_ratio = float(np.percentile(arr, 95)) + + min_r = max(0.1, round(p05_ratio - 0.1, 2)) + max_r = min(3.0, round(p95_ratio + 0.1, 2)) + + if min_r < max_r - 0.2: + suggestions[CONF_PROFILE_MATCH_MIN_DURATION_RATIO] = { + "value": min_r, + "reason": f"Based on labeled cycle durations (p05={p05_ratio:.2f})." + } + suggestions[CONF_PROFILE_MATCH_MAX_DURATION_RATIO] = { + "value": max_r, + "reason": f"Based on labeled cycle durations (p95={p95_ratio:.2f})." + } + + # Min-off-gap: derived from observed inter-cycle gaps + min_off_gap = self._suggest_min_off_gap(cycles) + if min_off_gap is not None: + suggestions[CONF_MIN_OFF_GAP] = min_off_gap + + return suggestions + + def _suggest_min_off_gap( + self, cycles: list[dict[str, Any]] + ) -> dict[str, Any] | None: + """Derive a min_off_gap suggestion from observed inter-cycle gaps.""" + # Only consider completed, labeled cycles with valid timestamps + timed_cycles: list[tuple[float, float]] = [] + for c in cycles: + if not isinstance(c, dict): + continue + if c.get("status") not in ("completed", "force_stopped"): + continue + label = c.get("profile_name") or c.get("label") + if not label or label == "noise": + continue + try: + start = float(c["start_time"]) if isinstance(c.get("start_time"), (int, float)) and not isinstance(c.get("start_time"), bool) else None + end = float(c["end_time"]) if isinstance(c.get("end_time"), (int, float)) and not isinstance(c.get("end_time"), bool) else None + if start is None or end is None: + # Try ISO string parsing + start = _parse_ts(c.get("start_time")) + end = _parse_ts(c.get("end_time")) + if start is None or end is None or end <= start: + continue + timed_cycles.append((start, end)) + except (TypeError, ValueError, KeyError): + continue + + if len(timed_cycles) < 3: + return None + + timed_cycles.sort(key=lambda x: x[0]) + gaps: list[float] = [] + for i in range(1, len(timed_cycles)): + gap = timed_cycles[i][0] - timed_cycles[i - 1][1] + if 30 <= gap <= 86400: # Only gaps between 30s and 1 day + gaps.append(gap) + + if len(gaps) < 3: + return None + + gaps_arr = np.array(gaps) + # Use the 5th-percentile gap as the safe minimum, with device-type floor + p05_gap = float(np.percentile(gaps_arr, 5)) + device_floor = ( + DEFAULT_MIN_OFF_GAP_BY_DEVICE.get(self.device_type, DEFAULT_MIN_OFF_GAP) + if self.device_type is not None + else DEFAULT_MIN_OFF_GAP + ) + # Add a 20% safety margin so we never split a real gap into two cycles + suggested = int(max(device_floor, min(p05_gap * 0.8, 3600))) + # When the data-derived value is equal to the device floor, we have no + # useful signal to surface — return None to suppress a misleading suggestion. + if suggested == device_floor: + return None + reason = ( + f"Based on {len(gaps)} observed inter-cycle gaps " + f"(p05={p05_gap:.0f}s). Device floor: {device_floor}s." + ) + return {"value": suggested, "reason": reason} + + def run_simulation(self, cycle_data: dict[str, Any]) -> dict[str, Any]: + """Replay a single cycle with varied parameters to find optimal settings. + + For richer, multi-cycle suggestions use :meth:`run_batch_simulation`. + """ + power_data_raw: Any = cycle_data.get("power_data", []) + if not isinstance(power_data_raw, list): + return {} + power_data = cast(list[list[float] | tuple[Any, float]], power_data_raw) + if len(power_data) < 10: + return {} + + start_time_raw = cycle_data.get("start_time") + start_time_iso = ( + start_time_raw if isinstance(start_time_raw, str) and start_time_raw else None + ) + + # Normalise power_data to [[offset_sec, power], ...] regardless of source format. + readings_list = power_data_to_offsets(power_data, start_time_iso) + + readings: list[tuple[float, float]] = [ + (float(offset), float(power)) for offset, power in readings_list + ] + + if not readings: + return {} + + powers = np.array([p[1] for p in readings]) + active_powers = powers[powers > 0.5] + + if len(active_powers) < 5: + return {} + + min_active = float(np.min(active_powers)) + + suggested_stop = round(min_active * 0.8, 2) + suggested_start = round(min_active * 1.2, 2) + + # Energy suggestions + suggested_end_energy = 0.05 + + # Dead zone: look for early dips in the first 5 minutes + dead_zone = 0 + for ts_offset, p in readings: + elapsed = ts_offset + if elapsed > 300: + break + if p < 5.0 and elapsed > 5.0: + dead_zone = int(elapsed) + + suggested_dead_zone = min(300, dead_zone) if dead_zone > 0 else 60 + + return { + CONF_STOP_THRESHOLD_W: { + "value": suggested_stop, + "reason": f"Based on minimum active power ({min_active:.1f}W) observed in last cycle." + }, + CONF_START_THRESHOLD_W: { + "value": suggested_start, + "reason": f"Based on minimum active power ({min_active:.1f}W) observed in last cycle." + }, + CONF_END_ENERGY_THRESHOLD: { + "value": suggested_end_energy, + "reason": "Default recommended baseline for end-of-cycle noise gate." + }, + CONF_RUNNING_DEAD_ZONE: { + "value": suggested_dead_zone, + "reason": f"Based on early power dip detected at {suggested_dead_zone}s." + }, + } + + def run_batch_simulation(self, cycles: list[dict[str, Any]]) -> dict[str, Any]: + """Derive parameter suggestions from a collection of labeled cycles. + + Unlike :meth:`run_simulation` (single-cycle heuristics), this method + aggregates statistics across *multiple* cycles for robustness: + + - Power thresholds from the 5th-percentile minimum active power. + - Dead zone from the 75th-percentile of early dips across cycles. + - End-energy threshold from the maximum false-end energy seen. + - Min-off-gap from the 5th-percentile inter-cycle gap. + + Returns an empty dict when fewer than ``_BATCH_MIN_CYCLES`` valid + cycles are provided. + """ + _BATCH_MIN_CYCLES = 5 + + valid_cycles: list[list[tuple[float, float]]] = [] + for c in cycles: + if not isinstance(c, dict): + continue + label = c.get("label") or c.get("profile_name") + if not isinstance(label, str) or not label: + continue + if label.lower() == "noise": + continue + if not ( + c.get("state") == "completed" + or c.get("status") in ("completed", "force_stopped") + ): + continue + raw = c.get("power_data") + if not isinstance(raw, list) or len(raw) < 5: + continue + start_iso = c.get("start_time") if isinstance(c.get("start_time"), str) else None + readings_list = power_data_to_offsets( + cast(list[list[float] | tuple[Any, float]], raw), start_iso + ) + readings = [(float(o), float(p)) for o, p in readings_list] + if len(readings) >= 5: + valid_cycles.append(readings) + + if len(valid_cycles) < _BATCH_MIN_CYCLES: + return {} + + # --- Power thresholds --- + lowest_active: list[float] = [] + false_end_energies: list[float] = [] + dead_zone_candidates: list[int] = [] + + _MAX_PAUSE_GAP_H = 1.0 + max_gap_s = _MAX_PAUSE_GAP_H * 3600 + for readings in valid_cycles: + powers = np.array([p for _, p in readings]) + active = powers[powers > 0.5] + if len(active) > 0: + lowest_active.append(float(np.min(active))) + + # Dead zone: first dip below 5 W within the first 5 minutes + for ts_offset, p in readings: + if ts_offset > 300: + break + if p < 5.0 and ts_offset > 5.0: + dead_zone_candidates.append(int(ts_offset)) + break + + # False-end energies: low-power segments that resumed + in_pause = False + pause_energy = 0.0 + stop_w = 2.0 + for i in range(1, len(readings)): + t0, p0 = readings[i - 1] + t1, p1 = readings[i] + dt_s = t1 - t0 + # Guard against non-positive or excessively large time gaps + if dt_s <= 0 or dt_s > max_gap_s: + # Skip this interval and reset pause state + in_pause = False + pause_energy = 0.0 + continue + avg_p = (p0 + p1) / 2.0 + dt_h = dt_s / 3600.0 + if avg_p < stop_w: + if not in_pause: + in_pause = True + pause_energy = 0.0 + pause_energy += avg_p * dt_h + elif in_pause: + false_end_energies.append(pause_energy) + in_pause = False + + suggestions: dict[str, dict[str, Any]] = {} + + if lowest_active: + p05_min = float(np.percentile(lowest_active, 5)) + suggested_stop = round(p05_min * 0.8, 2) + suggested_start = round(max(suggested_stop + 0.1, p05_min * 1.2), 2) + n = len(lowest_active) + suggestions[CONF_STOP_THRESHOLD_W] = { + "value": suggested_stop, + "reason": ( + f"Based on p05 of minimum active power across {n} cycles " + f"({p05_min:.1f}W)." + ), + } + suggestions[CONF_START_THRESHOLD_W] = { + "value": suggested_start, + "reason": ( + f"Based on p05 of minimum active power across {n} cycles " + f"({p05_min:.1f}W)." + ), + } + + if false_end_energies: + max_false = float(np.max(false_end_energies)) + suggested_end = round(max(0.05, max_false * 1.2), 4) + else: + suggested_end = 0.05 + suggestions[CONF_END_ENERGY_THRESHOLD] = { + "value": suggested_end, + "reason": ( + f"Based on maximum false-end energy " + f"({float(np.max(false_end_energies)) if false_end_energies else 0:.4f}Wh) " + f"across {len(valid_cycles)} cycles." + ), + } + + if dead_zone_candidates: + # Use the 75th percentile to cover most cycles without being overly generous + p75_dz = int(np.percentile(dead_zone_candidates, 75)) + suggested_dz = min(300, p75_dz) + suggestions[CONF_RUNNING_DEAD_ZONE] = { + "value": suggested_dz, + "reason": ( + f"Based on p75 of early power dips across " + f"{len(dead_zone_candidates)} cycles ({suggested_dz}s)." + ), + } + + min_off_gap = self._suggest_min_off_gap(cycles) + if min_off_gap is not None: + suggestions[CONF_MIN_OFF_GAP] = min_off_gap + + return suggestions + + def apply_suggestions(self, suggestions: dict[str, Any]) -> None: + """Persist suggestions to the profile store.""" + for key, data in suggestions.items(): + self.profile_store.set_suggestion(key, data["value"], reason=data["reason"]) + + if self.hass and suggestions: + self.hass.async_create_task(self.profile_store.async_save()) \ No newline at end of file diff --git a/custom_components/ha_washdata/time_utils.py b/custom_components/ha_washdata/time_utils.py new file mode 100644 index 0000000..d76e83e --- /dev/null +++ b/custom_components/ha_washdata/time_utils.py @@ -0,0 +1,275 @@ +"""Unified time/power-data utilities for WashData. + +Canonical storage format for power_data: ``[[offset_seconds, power], ...]`` +where ``offset_seconds`` is a float relative to the cycle's ``start_time``. + +All helpers in this module accept *any* of the three in-flight formats and +normalise them to the canonical form so consumers never need to guess. + +Formats recognised: + - ``(datetime, float)`` – live trace from CycleDetector internals + - ``(iso_str, float)`` – legacy on-disk format (pre-offset era) + - ``[offset_float, float]`` – current canonical on-disk format +""" + +from __future__ import annotations + +import logging +from datetime import datetime +from typing import Any, Literal, cast + +import homeassistant.util.dt as dt_util + +_LOGGER = logging.getLogger(__name__) + +# Type aliases +PowerPoint = list[Any] | tuple[Any, ...] +PowerData = list[PowerPoint] + + +def detect_power_data_format( + power_data: PowerData, +) -> Literal["offset", "iso", "datetime", "empty", "unknown", "unix_timestamp"]: + """Identify which format a power_data list is in. + + Returns one of: ``"offset"``, ``"iso"``, ``"datetime"``, ``"empty"``, + ``"unknown"``, ``"unix_timestamp"``. + """ + if not power_data: + return "empty" + ts = None + for sample in power_data: + if isinstance(sample, (list, tuple)) and len(sample) >= 2 and sample[0] is not None: + ts = sample[0] + break + if ts is None: + return "unknown" + if isinstance(ts, datetime): + return "datetime" + if isinstance(ts, str): + return "iso" + if isinstance(ts, (int, float)): + # Values > 1e8 (≈ 3+ years of seconds) are absolute Unix epoch timestamps, + # not relative offsets. Treat them differently so we can subtract start_time. + if float(ts) > 1e8: + return "unix_timestamp" + return "offset" + return "unknown" + + +def power_data_to_offsets( + power_data: PowerData, + start_time_iso: str | None = None, +) -> list[list[float]]: + """Normalise *any* power_data format to ``[[offset_sec, power], ...]``. + + Args: + power_data: Input list in any recognised format. + start_time_iso: ISO-8601 cycle start time string. Required when + converting from the ISO-string format so that offsets can be + computed. When converting from datetime format, used as the anchor + if provided; falls back to the first sample's timestamp otherwise. + Ignored for offset format. + + Returns: + List of ``[offset_seconds, power]`` pairs. Empty list on failure. + """ + if not power_data: + return [] + + fmt = detect_power_data_format(power_data) + + if fmt == "unix_timestamp": + # Absolute Unix epoch floats - subtract cycle start to get relative offsets. + base_ts: float | None = None + if start_time_iso: + try: + parsed_start = dt_util.parse_datetime(start_time_iso) + if parsed_start is not None: + base_ts = parsed_start.timestamp() + except (ValueError, OSError) as e: + _LOGGER.debug("Failed to parse start_time_iso %s: %s", start_time_iso, e) + result: list[list[float]] = [] + for item in power_data: + try: + ts_abs = float(item[0]) + p = float(item[1]) + if base_ts is None: + base_ts = ts_abs # use first reading as anchor + offset = round(ts_abs - base_ts, 1) + result.append([max(0.0, offset), p]) + except (TypeError, ValueError, IndexError): + continue + return result + + if fmt == "offset": + # Already canonical – return a clean list of [float, float] + result: list[list[float]] = [] + for item in power_data: + try: + result.append([float(item[0]), float(item[1])]) + except (TypeError, ValueError, IndexError): + continue + return result + + if fmt == "datetime": + start_ts: float | None = None + if start_time_iso: + try: + parsed_start = dt_util.parse_datetime(start_time_iso) + if parsed_start is not None: + start_ts = parsed_start.timestamp() + except (ValueError, OSError) as e: + _LOGGER.debug("Failed to parse datetime %s: %s", start_time_iso, e) + result: list[list[float]] = [] + for item in power_data: + try: + ts_raw = item[0] + if not isinstance(ts_raw, datetime): + continue + p = float(item[1]) + ts = ts_raw + if start_ts is None: + start_ts = ts.timestamp() + result.append([round(ts.timestamp() - start_ts, 1), p]) + except (TypeError, ValueError, AttributeError, IndexError): + continue + return result + + if fmt == "iso": + # We need start_time to compute offsets + base_ts: float | None = None + if start_time_iso: + try: + parsed = dt_util.parse_datetime(start_time_iso) + if parsed is None: + return [] + base_ts = parsed.timestamp() + except (ValueError, OSError) as e: + _LOGGER.debug("Failed to parse datetime %s: %s", start_time_iso, e) + return [] + + result: list[list[float]] = [] + first_ts: float | None = None + for item in power_data: + try: + ts_raw = item[0] + if not isinstance(ts_raw, str): + continue + p = float(item[1]) + parsed_ts = dt_util.parse_datetime(ts_raw) + if parsed_ts is None: + continue + t_val = parsed_ts.timestamp() + if base_ts is not None: + offset = round(t_val - base_ts, 1) + else: + # Fallback: use first reading as zero reference + if first_ts is None: + first_ts = t_val + _LOGGER.warning( + "power_data_to_offsets: start_time_iso missing/invalid; " + "shifting timestamps to first sample as zero reference " + "(first sample: %s, total samples: %d)", + ts_raw, + len(power_data), + ) + offset = round(t_val - first_ts, 1) + if offset < 0: + _LOGGER.debug( + "power_data_to_offsets: clamping negative offset %.1f to 0 " + "(power=%.1f, index=%d)", + offset, p, len(result), + ) + result.append([max(0.0, offset), p]) + except (TypeError, ValueError, AttributeError, IndexError): + continue + return result + + _LOGGER.debug("power_data_to_offsets: unrecognised format, returning empty") + return [] + + +def power_data_offsets_to_datetimes( + power_data: PowerData, + start_time_iso: str, +) -> list[tuple[datetime, float]]: + """Convert stored ``[[offset_sec, power], ...]`` to ``[(datetime, power), ...]``. + + Args: + power_data: Offset-format power data. + start_time_iso: ISO-8601 cycle start time. + + Returns: + List of ``(datetime, power)`` tuples. Empty list on failure. + """ + try: + start_dt = dt_util.parse_datetime(start_time_iso) + if start_dt is None: + return [] + start_ts = start_dt.timestamp() + except Exception: # pylint: disable=broad-exception-caught + return [] + + result: list[tuple[datetime, float]] = [] + for item in power_data: + try: + offset = float(item[0]) + p = float(item[1]) + ts = datetime.fromtimestamp(start_ts + offset, tz=start_dt.tzinfo) + result.append((ts, p)) + except (TypeError, ValueError, IndexError): + continue + return result + + +def migrate_power_data_to_offsets(cycle: dict[str, Any]) -> bool: + """Migrate a single cycle's power_data to offset format in-place. + + Detects if ``power_data`` is still in legacy ISO-string format and converts + it. Safe to call on already-converted cycles. + + Returns: + ``True`` if the cycle was modified, ``False`` if no change was needed. + """ + raw = cycle.get("power_data") + if not isinstance(raw, list) or not raw: + return False + raw_power_data = cast(PowerData, raw) + + fmt = detect_power_data_format(raw_power_data) + if fmt in ("offset", "empty"): + return False # Already canonical + + if fmt not in ("iso", "datetime", "unix_timestamp"): + _LOGGER.warning( + "migrate_power_data_to_offsets: unknown format '%s', skipping", fmt + ) + return False + + start_time_raw = cycle.get("start_time") + start_time_iso: str | None = ( + str(start_time_raw) if isinstance(start_time_raw, str) and start_time_raw else None + ) + if fmt == "iso": + if not start_time_iso: + _LOGGER.warning( + "migrate_power_data_to_offsets: missing start_time, skipping" + ) + return False + if dt_util.parse_datetime(start_time_iso) is None: + _LOGGER.warning( + "migrate_power_data_to_offsets: unparsable start_time '%s', skipping", + start_time_iso, + ) + return False + + converted = power_data_to_offsets(raw_power_data, start_time_iso) + if not converted: + _LOGGER.warning( + "migrate_power_data_to_offsets: conversion produced empty result, skipping" + ) + return False + + cycle["power_data"] = converted + return True diff --git a/custom_components/ha_washdata/translations/.translation-meta.json b/custom_components/ha_washdata/translations/.translation-meta.json new file mode 100644 index 0000000..5dbff51 --- /dev/null +++ b/custom_components/ha_washdata/translations/.translation-meta.json @@ -0,0 +1,5037 @@ +{ + "version": 1, + "entries": { + "af": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "aeda4ed76fb1c6c2", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "f43e9fb7f828dcb2", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "26b0d4f876a6b0fc", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "661ee6dd95ac4e14", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "1fd95215a7d0d60e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "cf903a5d72c8a42b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "3c5286eb73869201", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "9842b2e0f929b5f0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "b7052e3ceb0403b2", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "b2b9e66d007c4d75", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "3c32054882767c1b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "f175dfd0088b0396", + "origin": "machine" + } + }, + "ar": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "068b140a03f34c19", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "280798a26d0b1124", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "2d2fa61731acaf71", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "e97eaa3f05a45cd1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "ffebae153bf61910", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "a96fc31621dc7524", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "2c2e1bd0c812809b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "f1219eef9e364753", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "8312fcdbe8a5e3a3", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "889dd728a0cebf5d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "45cddffb2e43e8d1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "19eecfb4b9d73a34", + "origin": "machine" + } + }, + "bg": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "1a7b691d5cc6da03", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "1efa657992df7f23", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "cb3e6f9254127165", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "15520b80412223fc", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "c20a41264bf1592a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "4eb595060f8e5e51", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "709c3397af139e65", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "3181bffa5b5e996c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "c60830f7208811bf", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "923d07f1cbcb7294", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "b59aa07418b7c827", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "09f34acb6ce058f1", + "origin": "machine" + } + }, + "bn": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "5613951a5cf48e61", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "27707e4d343de728", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "c2490f913765820e", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "87292166f83efc78", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "f421fec20f2cc125", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "3d6707af1c1d83dd", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "a5c91f5d63eb63a4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "06ed2418aab23a66", + "origin": "machine" + }, + "options.step.advanced_settings.sections.external_triggers_section.data_description.switch_entity": { + "src": "288d1e8c3ad66527", + "val": "32ef5176cce02482", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "f13d3bd5d1db1184", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "355a89bff66f864d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "785f712afc4841c8", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "07f07645858b61cd", + "origin": "machine" + } + }, + "bs": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "c72d55dc3d4620d2", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "0d0690c8a7f7fb58", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "db3da0d4ca3a69c9", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "83951ddcfe283df9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "de425b816aecbac6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "c9331794647d673e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "4d4aaa30a11020d6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "205f6485d9f96daf", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e738ae6749d9e924", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "e3cf0ab8c118c857", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "65f2bcc6a7a499a0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "f1d57d8d8bb28027", + "origin": "machine" + } + }, + "ca": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "4b2d96f86b21d1f0", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "818faf901c2a4a81", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "17524a591295ea46", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "0e24c9c83c739ff4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "8155c53f8b6ec0d0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "6f74a69073731a03", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "684148cc3d2b76d1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "05e6c4aee1fb8b27", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "ef3a293a6f7319b4", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "fc78b7f62823f4e8", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "da0a95609a820a93", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "cfaff97339959055", + "origin": "machine" + } + }, + "cs": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "709a23220f2c3d64", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "7b0630acb775eff3", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "543218ec28f96d55", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "8d225ec26bcfc3d6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b93deaf97c3dab4c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "4ad04b35774effa6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "306c7c232e1e9193", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "584ba889722b7b3b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "243e6cecda4c9ebe", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "67768a0dc167ac68", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "787be3f54f88d928", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "276479a45b1eddb8", + "origin": "machine" + } + }, + "cy": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "54411b32dcbc20dd", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "325f01c6cea85f77", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "193b91b813e240e6", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "fe2563b8c0a33cb1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b2fbd030d6172613", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "51cf0e8c27f2685a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "03bbadb620818f28", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "5ffd5150d7c027cf", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "2b3f318497657727", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "36ad67b23f3d4418", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "a1609eaa0d49442c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "ff4e612c9f7ad26d", + "origin": "machine" + } + }, + "da": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "5a88c5cbd594ea71", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "d8a8bf486a82901a", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "6fb67dc024537b46", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "7fcb6b5375119bbb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6f2e6401dd355387", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "615a083112b49e30", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "543bbea361940af3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "9ca0119befe664fd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "cdecc8d091a4b8b2", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "0d03cb3a938e7f64", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "9c55d15f678a8717", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "9791d0e7d0daf8a2", + "origin": "machine" + } + }, + "de": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "a8e24ca66e0ac18d", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "2b7c90d6c8fafcd4", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "8525b0ded5aa2a8c", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "0629fc80dc0962b1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "86c0768044c57bd2", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "6ee62f30afd94b7c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "ac853b69e6eb443d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "388e6dfaaf5b749e", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "d6e553a8b930ada1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "cdaf320dad1da371", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "42c1e3751dd44747", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "85c42e776893885e", + "origin": "machine" + } + }, + "el": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "90f6776137b6b284", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "6ead8dfb8d53a344", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "0f2c893e3ede7eae", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "4596f265488847d3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "e88b08c80473b3d3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "a0edab890adbe11e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "695c00a1a68cf293", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "20a895eac4d79128", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "ec0fac3cbbc76702", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "38ce640fd2c807bd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "60931ef96710ec8b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "bf5cb569a86c6b2d", + "origin": "machine" + } + }, + "eo": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "c3cad8d892753d76", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "c9e437e364d76f90", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "5f3a4494b10a1575", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "a4b6e8da489ff09c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "19142940dad16a76", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "60051d9592b4561e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "f41d34e3a89fb0dd", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "1f7c793bb7c3ce87", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "4b629df97c74094f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "d5e9210cb4279043", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "23d584def431f0b9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "f35595bfc7cbcdf7", + "origin": "machine" + } + }, + "es": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "3d53215edda2f118", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "a9e203871492202b", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "441cbeb0af86b507", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "7add672c6f1888d9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b1e446cdba5eaa1d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "232b138a50bd3c46", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "78969dd3ec943162", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "32823dbe17fe1111", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "b2a49fca06828ae3", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "d9a8be875ae7c35a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "cfd0c46b08a77380", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "32d5d18cd5d4c891", + "origin": "machine" + } + }, + "es-419": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "3d53215edda2f118", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "a9e203871492202b", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "441cbeb0af86b507", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "7add672c6f1888d9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b1e446cdba5eaa1d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "232b138a50bd3c46", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "78969dd3ec943162", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "32823dbe17fe1111", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "b2a49fca06828ae3", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "d9a8be875ae7c35a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "cfd0c46b08a77380", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "32d5d18cd5d4c891", + "origin": "machine" + } + }, + "et": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "ada6395824f29461", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "9504d9be84928ad8", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "6bc23ee7dc021835", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "a6d400b2ae7dea7a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6672ea296bc660f5", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "7ccaa918502d2c97", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "b68e3c1723d50eeb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "27777466c7f436fa", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e837e4a0a2334726", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "69f40bf602105b71", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "f9acfcedc7efce0f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "0b33835d5a0600d9", + "origin": "machine" + } + }, + "eu": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "0e9d2271177003d7", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "9debf33a8a568d74", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d260d8cce6dd887b", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "77a86832673ee94f", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "e634f221918f17ca", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "fb9e0494e3d745aa", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "354a8c619b965eea", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "8293b74fc1e07383", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "d0c16fd12e180b1f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "35f61971f6149ddb", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "86b32cc70780e155", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "c34efe86eff39e2c", + "origin": "machine" + } + }, + "fa": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "cfb4e68ae905f0b6", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "eb5d4ec2cb514d23", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "487b3c46f5dca832", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "2adf10f55c6bf89a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "d474df3b25e1795f", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "3226e819c0c708f3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "94c09173628c711b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "879b97ee764a360e", + "origin": "machine" + }, + "options.step.advanced_settings.sections.external_triggers_section.data.external_end_trigger_enabled": { + "src": "727b935c5d66c507", + "val": "f084c8737ce307e5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e43e8f980c53e8b4", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "a0e8e32426a11a86", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "dd3ca6945f0ed5fc", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "3bcff11b77db61aa", + "origin": "machine" + } + }, + "fi": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "6c00fe27694af9db", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "cc16bee99f9b9655", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "4bbb3a65ac5e6ce4", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "ad0ab9934e7b5b59", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "a863ea6603526aed", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "89a358c67d6beb72", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "d9852df15efbaba9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "92d92cbd4c259b6a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "0936ca021a7e9528", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "698f1d84a7ed5eae", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "790a46a9f245c165", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "63f93d4c23608e00", + "origin": "machine" + } + }, + "fr": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "1b525710704727c7", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "aaf89f3d7250ac43", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "0b843acac1473512", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "34cbd7876d40ff69", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "098460e6eeb292bb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "0204eb08a58619d5", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "2db0b6545f27c321", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "ca8adc387c950e87", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "ea92b32450641081", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "162b7f2e35a74d0d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "9ea92bfab0c07101", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "c8440d2a55c38f9a", + "origin": "machine" + } + }, + "fy": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "1397c0cc337eefe7", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "3d867959097a1da0", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "1d878f7f6e367501", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "cbbf57e589ca5c10", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "92bf1deb37eef26e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "4f5014467004a4f1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "4bde439bfd3afe4b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "f8051599620fa743", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e738ae6749d9e924", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "34a3465a0aea1869", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "9f94069298f92a1b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "979a80042451d552", + "origin": "machine" + } + }, + "ga": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "5fdd88c1e4e3a290", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "1349c692e99aeecc", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "fbbc6d55d95a8049", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "74d8516acf87e173", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "e1ce4449468ee632", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "2787bb9ad67607da", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "0248c6b4e823c11a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "08884a707481b83c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "1f854530b29b6134", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "d536f7cc1399c52d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "d7c48af4ef8776b5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "988c41f60995730b", + "origin": "machine" + } + }, + "gl": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "8eec80a57926c590", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "9b01778edf50523e", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "aae672011ddf67a4", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "1bf0e79aabd67c0b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "22c71e3a520518a9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "ae4c6db6ed075812", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "64931882af082a40", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "d683010ab53073af", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "5f1fd3ffd7f02179", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "57164eb20cb45082", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "870755afc313a56a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "fb2886d8d1a214f9", + "origin": "machine" + } + }, + "gsw": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "cc633d68016e513e", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "001f8a3ef8725298", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "8525b0ded5aa2a8c", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "e0bb42118aa21f21", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "e162142329821fd7", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "8e7534d908d5b594", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "76e63021d3a2bdd3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "d2a1433ba0a6ef18", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "9308a4306b12d967", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "dffe1c697afa6c2a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "42c1e3751dd44747", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "c65dd5aff32661de", + "origin": "machine" + } + }, + "he": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "5d2a28d211bbceda", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "98c5ded3cf85fd90", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "df642450e237f416", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "3d22cb9e8ea1343f", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "3c86feea99d90388", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "aea8f1aa488a4346", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "c4ceb35d396408b2", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "f1a36f23f175138f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "ae483d7d46821e41", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "92b5d2f32a0ca25a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "1dbeb0b31128f131", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "1571112ee229a5fe", + "origin": "machine" + } + }, + "hi": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "eb98a2d8ce2fdfd5", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "8d939cfc3ff74c9a", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "75ac0a595b6e4f45", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "c23cc85673a8eee0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "3fe3cf876bfa4095", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "aeb02d5c0271d96e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "6993d0f855b20f47", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "c2e0b72ac3432848", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "d64f986ccbea2c0b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "8aa4a734ba534ffe", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "928b6222389754a0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "cf7f247ffea5dc8e", + "origin": "machine" + } + }, + "hr": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "c72d55dc3d4620d2", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "f3eda7e57e554365", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "525eb170a6ecf147", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "83951ddcfe283df9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "be9c739d5f462dab", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "fa336aa5374cfc3e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "c41c12f9bca2c195", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "29b02a409f2f7c1d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "acba7a0b2ff63c1a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "701cce476870caaa", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "65f2bcc6a7a499a0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "e9b80a0cf4eec043", + "origin": "machine" + } + }, + "hu": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "1fa7061278d07abb", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "274785c2ec717864", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "a3c5edee7dc841aa", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "68e91e1e989964a4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "dafa2f6aac56cbbc", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "56e0b533b61755cd", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "4da285c2b40f4e38", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "719ab94023d4005c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "13373c0a1f9fa726", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "2570c9c9d6c9115d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "672ce41ad08d8538", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "6ca2d3e86eff8316", + "origin": "machine" + } + }, + "hy": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "ed5158adc454e593", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "e7bdb4774150f847", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "c07dee48431d5446", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "661134e9f78c43d2", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "86eb7cb160d61fb9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "208d5ae329339707", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "14a35db59f4fc819", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "a70a3eb89fd5a829", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "0320e86816970095", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "63d3892f03c6a339", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "4a1d6315884687cd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "2f6207ce95aa98d4", + "origin": "machine" + } + }, + "id": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "ec276a5e1045147d", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "5e2e8bcef7a1a016", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "ec3b3948c5ed23e7", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "cca45bb630e2d4d9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "abb2c0a46fe4f943", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "b18be7d607e329a1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "d74cfed4aa6ce095", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "26b3947cb3173552", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "c1dc7bc6698616cd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "0b125a353820fcbf", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "ca7ce9aca65d6cfd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "f052c4780836b414", + "origin": "machine" + } + }, + "is": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "95372dd41c800d51", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "7d690f5f038917ce", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "49263e55fbdd38f7", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "c9967d0795b85a5e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "f079091cbc883ea4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "1ecfc49c7f6d0a81", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "b6217f2af7fcd761", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "d0ec75af3c668b2d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "c2891869234a197d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "2ed5d78517c8dfda", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "11b154c880e916e5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "8f8d84a766dc9a60", + "origin": "machine" + } + }, + "it": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "60763a5a5ef883a4", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "647bd893d48db4b0", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "03e09e30734b5da3", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "4852e3a956520658", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "1e5a6650fd135c98", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "e1b5ff765d9fdf4a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "25752888ac5208d9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "7bb9955fbc3c95ae", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "6e4f71a0a68eccf5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "75858708328f6d46", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "8d29c5a3f846bfc5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "ec9d45a10c09e5d7", + "origin": "machine" + } + }, + "ja": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "11fbc4c35f0ae0fb", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "4976435c3a8e5bcb", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "6796709eb2c2af0f", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "d3b63b2863b4f0eb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "88689c36d79b2d5a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "11f102d1d51ebaec", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "80bc4887e51b5ce6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "a9c52fec8c17b885", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "af1fc2f6eedc7c7e", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "50dff903a21a15f0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "282a5df8d38aa693", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "8e4a200a9d5da36c", + "origin": "machine" + } + }, + "ka": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "579245e6d6fb51ea", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "b848ad817ddbd509", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "1c9197a35ee406a8", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "9644c07c4a60fa3c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "608eae55e3232651", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "ea9750991f84d06b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "f239d611da4708d0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "e43f8192c64c02ef", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "ae01c0ab74e907de", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "58c8aa0756456c26", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "b268eea662515676", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "cb84b4467ab60c91", + "origin": "machine" + } + }, + "ko": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "f8db37682322d50b", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "bdd3525a56f852f3", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "3f3b43acc4df2418", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "c48593a514cc3186", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "33ebbcafbf15f0c1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "47db875f6e7f2293", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "09ea73e64c5151f4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "b9453d5260c5423c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "9c996e831884c3c0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "d1226af825bfd458", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "0c1cdaa9a4f5f389", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "fb50c4a6fb103c9d", + "origin": "machine" + } + }, + "lb": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "d9abbaa9f6351f26", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "7e47a98db17778af", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d2259f098d71565c", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "7c01cfda28d6ee7f", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "990662765d2646e3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "265161dfb0c3afa5", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "e30015d78f9a5e1d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "76e9c355bab0d7ed", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "d2eef7abd415e002", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "a9f71cb8066a4d18", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "e641ff390d14d134", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "01bf8ef64ff21adf", + "origin": "machine" + } + }, + "lt": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "8f7a0a5707ce688f", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "d03550a6c6624d40", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d7909ccbac34ba01", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "727ce1b97bd6e6c8", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "59b3d94dba36cb95", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "383a8185c20974fa", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "09850e8c885e4481", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "248f51aa4f6b51d0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "8991b661b190c53a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "80206c83e2481cc1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "66afbf42b605da19", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "2665bafd08ff7cea", + "origin": "machine" + } + }, + "lv": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "e0787fa9c984649f", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "e0f57d23ecdde2ed", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d8083aabe7ba4c93", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "63b16965a0ccd53a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b59228b7c33b720e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "b05639d267be3fd1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "5d7c638bee790c04", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "f0b7acacd15704ae", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "06cf9abcac54ed93", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "0f2126cee764ad47", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "f7dc9bdb2ad50edd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "9e933e7dadbb6ddd", + "origin": "machine" + } + }, + "mk": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "8ec3d4d223a58e2f", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "dc44e2b634a7930f", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "440816be58a9c7b7", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "031a61b25357f8ef", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6560df8fe0f61acd", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "c971f6becb857b10", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "ee4cf409d0fe07d1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "c873e26e8ac7ba3b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "abc88a824ee917b8", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "e40d022f7e9f3ab7", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "92916654c5a482fd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "96b54279a0150b6c", + "origin": "machine" + } + }, + "ml": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "d7433b5057cd1d4d", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "eab5b77579df106d", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "01c0ddea656e3013", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "67b396590341f62c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "81e0d9d9f38961a4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "3268e1d07867ed0d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "a16aa729348b16b0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "574e471040a8b754", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e904605fcffd701b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "ea277d783d741309", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "9694e1a530a3c06e", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "bfdec13bb09d18d5", + "origin": "machine" + } + }, + "nb": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "b5bd70631118c2a5", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "5e61b6556b418ee0", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "5b52ae36509a0998", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "c1959cc12e7301a7", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "d5c03ad4a7ca3769", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "54799a7f004a823b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "858f9ba645a547a7", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "b3e7e66b1a1e2371", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "42090bb09a885842", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "a102ec3a75f62e86", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "cf8d828d5ffd4bbb", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "d7c38f2da1a1da44", + "origin": "machine" + } + }, + "nl": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "1de5d68e6e12fb65", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "93eb6da9af3e4ca8", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "494a04180254c436", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "3a4749f22a80fd9c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "92cd87c4b928312c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "2a564ca1ced90896", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "e007e758e06ec568", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "7504cdd20a687ece", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "530484bc8450d50c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "2ab5ebf35b01dae5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "f3e08ac75f20fe1f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "fc835cbf19599598", + "origin": "machine" + } + }, + "pl": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "3d8addb7c59886ca", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "29a875a50c664422", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "7b3545a6265847bd", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "d1f005e886da2c97", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "4f5b05adea4140c9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "54671ca756379f3a", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "78c43db458d668c3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "0bfa3afe7980073f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "a0e961e438aef5fa", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "07bc5071a14cad68", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "4dc7e2aaf478b667", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "e856560509dc5f73", + "origin": "machine" + } + }, + "pt": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "b4308bbba6b55145", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "da94eef19623a8b9", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "0174b325b2c7361e", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "1c7d853ff07948a4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "d60fbe539d8f0a06", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "d66677a0a6cb4982", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "9d9be2476da4a9ec", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "41206b14c16a41ec", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "8095ea07d16cf8e5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "f7e5213fe92ddb73", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "cfd0c46b08a77380", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "7e1ddeed07b60947", + "origin": "machine" + } + }, + "pt-BR": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "b4308bbba6b55145", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "da94eef19623a8b9", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "0174b325b2c7361e", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "1c7d853ff07948a4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "d60fbe539d8f0a06", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "d66677a0a6cb4982", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "9d9be2476da4a9ec", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "41206b14c16a41ec", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "8095ea07d16cf8e5", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "f7e5213fe92ddb73", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "cfd0c46b08a77380", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "7e1ddeed07b60947", + "origin": "machine" + } + }, + "ro": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "ced0c2df4afa24c0", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "5fd85c589a946f83", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "9cfb234008e4c3c6", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "9cdcd49cd65c08e9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "64031881a27d0a93", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "885c308eba4377f3", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "e34b8f0895b70e22", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "9e3e6e8eded536f6", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "e738ae6749d9e924", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "caced20cfec82d91", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "1c4661dad1c2be23", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "8c1a4bef9d3345ba", + "origin": "machine" + } + }, + "ru": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "5a96859e8b145c41", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "544fc0d41fce45cd", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "7e8e5db2e44e0ebd", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "a84bfdda157d6709", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "1f4ca450e7792569", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "45e43119017274d5", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "3c782260af6f6138", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "aea022b4f9b1fbe9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "6e35b1c1b9e1bc8a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "28dda4a0c89cc0dc", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "19718a839d01c580", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "291be7cdb242a632", + "origin": "machine" + } + }, + "sk": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "d73bb26fee50a43d", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "060aec4d39f56a84", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "ab269e1bc8a159d3", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "e250adc7a152a32c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "b38a36fee3c6c7f0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "4623dd9015c7ce85", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "039cf96af4da2deb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "1ecc72dc8f33ed54", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "f2b56120b349ad81", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "f06b02a83f903783", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "4d1b83232f4ea998", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "3c970d9b45ae71a9", + "origin": "machine" + } + }, + "sl": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "0b9c32e37d948730", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "fe7d9db4739125ae", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "178aa28f7cb45d03", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "b06e4777db1a64be", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "431086f405cce185", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "c3e53e06beeabfc0", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "b953877f0457558d", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "216910660258830a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "19f7b9a84d8d3a35", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "35618d4e5519ac1f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "24671b6da32184b1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "4006149f9ef8098c", + "origin": "machine" + } + }, + "sq": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "33803a004cff27fb", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "f99b5af3fb4bb03d", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "fc2502cf5381f708", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "dd0b16db77162faa", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6c1ec2ad70acc6f6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "9895616c2590bb1e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "ab3aa306e5942ed1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "e972c5dce005e364", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "a2e6de85aebee008", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "11756f5549817561", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "a3a8794990ceb830", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "59a8b7e62bb232ac", + "origin": "machine" + } + }, + "sr-Latn": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "f0bb8c1d1bf50c15", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "dd1920ebec5ebfd9", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "5dda7e44fc245ebd", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "3816b8f1bb6bf7c4", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "c038c3e25ee7f8fb", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "167c2ffa6af944de", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "c5a8ede3699b1b90", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "575b96deefc356a9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "68b2db8aa549913b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "7b7486a6d1a3f5cb", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "d208a38ec3a6b8b1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "2f587880b45bf334", + "origin": "machine" + } + }, + "sv": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "faf0db800093e712", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "384117670e9169d1", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "e782a3b055d62b88", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "e417e114f58b028c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "5e49b23ea03c4350", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "2c94a1e2a075a926", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "9a0947d9db723f1b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "e3e45782bbb673f3", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "eae64fe27eb60de7", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "aded8c0188962aa0", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "a16781fa40742990", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "e77dacd5ee2bd856", + "origin": "machine" + } + }, + "ta": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "235d131ec03425f9", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "235bbbfb8af6edb8", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "dcf7bbec640eb6b6", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "1be075440d544d11", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "397b284c8772ac69", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "c23d1795e1e7d1ca", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "e2a06c05118cfc1e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "8fde341af64b3371", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "73cd51f66f961247", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "e70d771e1cd45477", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "d17cc21e4cfed138", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "47e15d4c87777a62", + "origin": "machine" + } + }, + "te": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "9c3262c171e6feaa", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "f1c98fa019eed394", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "88d85962a96d7006", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "46f3a3607e56ac71", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6b7c424e0d46dda9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "625aad9584be6936", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "577048a433d1358b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "7715ff9c470aefe4", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "03017abf6d3d3ee9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "6ccd8ca5ef337820", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "1c0702625be396d1", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "7ca3a95439227d1a", + "origin": "machine" + } + }, + "th": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "36cd1f176591cbc5", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "b7877ec0568482af", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "f5a9bb65a7cdbcb1", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "ef6968f9e2b45030", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "c310fa35fedd1062", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "ebe382d747c6e317", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "f44866bc29d3e322", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "30642b631dc425a6", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "b6f03a1dc5e513b6", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "5c34793738199920", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "979f341940798dee", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "7d7890d5a71c910e", + "origin": "machine" + }, + "options.step.create_profile.description": { + "src": "2b87fddc6836034e", + "val": "0b57fe19fdc03ab0", + "origin": "machine" + } + }, + "tr": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "f57b0df0f71e76e9", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "917f034abd9d6754", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "65dfe3bb1ddbc03c", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "54735f9e22114b38", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "980fac23fa717fe9", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "3d07c18a18bc5b64", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "fcd1325c639364d7", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "c3865409838f3f2c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "790977928777f136", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "8111da203079e9ba", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "9951e5a27be87ee3", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "06b1255a301f7b24", + "origin": "machine" + } + }, + "uk": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "08d84b0fcedfba13", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "af14c71095bf4fbe", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d84f4c12ab1adf0b", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "4a41d45903047f32", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "9eec60e99df3c66f", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "d7bab7f79642ff56", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "5fa790fff3926f71", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "71f272ec21ce31c9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "1308feac907132ae", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "ecd7e1ddee945424", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "54a9c21df9a8a7ae", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "3b66847a5e551381", + "origin": "machine" + }, + "options.step.create_profile.description": { + "src": "2b87fddc6836034e", + "val": "713cf4aed87a4ac0", + "origin": "machine" + } + }, + "ur": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "05e060e0b40d4db9", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "416d06b5f4ca5132", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "d3219601d7284452", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "13db05f9c7d03b0e", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "0f9d3688a3cefe6c", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "55239dbe1c730111", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "2e44dba2b697a77b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "97c96bff0d43049a", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "31fca5a0c8072dc4", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "6603f197a4cbe155", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "f0edf31f70bc8539", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "d896231fa0ed2059", + "origin": "machine" + } + }, + "vi": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "0550449ecb8d61c8", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "c11a56b4e2058102", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "1ad46afc159e161f", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "5b304efc602aa760", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "04d0cc0542d07ce5", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "290220fbb84abfdc", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "a4559797c6654adf", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "01fdc065fb94216f", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "b8b3cc8ea446e4bd", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "c78757b98db28ad9", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "a240b8e3dae5df1d", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "bfb643bc1fa249a4", + "origin": "machine" + } + }, + "zh-Hans": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "10620488b14ba498", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "1ab1bfb7cc2b3173", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "b050169d2a2f7eee", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "458f56d71a9eac3b", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "3b194db0b53678a6", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "7de24bb888fa84c1", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "80441562c4ad4183", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "0449873c474945c7", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "d5677dfaa0590ada", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "cef190e4b7f13543", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "ada2e1f433d31563", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "59d95a604507c993", + "origin": "machine" + } + }, + "zh-Hant": { + "options.step.notifications.data.notify_reminder_message": { + "src": "4dd697a62ae06c96", + "val": "14047ab89a742a1e", + "origin": "machine" + }, + "options.step.notifications.data.notify_timeout_seconds": { + "src": "5fa8a67171364696", + "val": "5c1eb1281582d3a5", + "origin": "machine" + }, + "options.step.notifications.data.notify_channel": { + "src": "861058249490fef9", + "val": "b7fdd1d59a37eefd", + "origin": "machine" + }, + "options.step.notifications.data.notify_finish_channel": { + "src": "e38d0bb3e7778049", + "val": "77d0cb4a467a8789", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_reminder_message": { + "src": "b425eeaccab51f6c", + "val": "6d12fe446f81a6cf", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_timeout_seconds": { + "src": "dcfd8289358c3b06", + "val": "246bf424ae99bc28", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_channel": { + "src": "53bdc31734418a72", + "val": "e42a58426cf41683", + "origin": "machine" + }, + "options.step.notifications.data_description.notify_finish_channel": { + "src": "d24f9cd1b6735537", + "val": "63f581e3e9a9ba06", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.name": { + "src": "e738ae6749d9e924", + "val": "1a4641271ece722b", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.description": { + "src": "453435c11f9136e2", + "val": "7391541545dfd654", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data.linked_device": { + "src": "9afbd3ed99681e00", + "val": "633904510d74a86c", + "origin": "machine" + }, + "options.step.advanced_settings.sections.device_link_section.data_description.linked_device": { + "src": "b874e865d695ea8c", + "val": "080e3a73ba4b262a", + "origin": "machine" + } + } + }, + "card": { + "af": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3b99971ede0aaf7a", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "09d969ca84d54547", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "61ee7895dd6231cd", + "origin": "machine" + } + }, + "bs": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "show_program": { + "src": "08303382bc087c9b", + "val": "08303382bc087c9b", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "2941c1c04cb85f3a", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "76f6c5352d86854a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "0124325aff09b12b", + "origin": "machine" + } + }, + "ca": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "6380bc33fbdad02d", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "c678b645fbf5641b", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "08dedd5ae04960af", + "origin": "machine" + } + }, + "cs": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "b3b91dcc446182c0", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "5d9a6c50610c196e", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "96ff8993b8ff8ee1", + "origin": "machine" + } + }, + "cy": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "ce3448d60f86755f", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "24197c881779c2a3", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "6a6d60370300a220", + "origin": "machine" + } + }, + "da": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "f99aaa64fc477e91", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "0455f3fe3b4d9f3a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "351c562dd2bcba40", + "origin": "machine" + } + }, + "de": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "1a888f718f10a349", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "75ccc2e21a09a559", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "3ef465fbe1b282c4", + "origin": "machine" + } + }, + "et": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "ed77074a2358743a", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "cad687216715da8a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "35ae1d09c80d8398", + "origin": "machine" + } + }, + "eu": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "4c0d27b1298f3ce9", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "873a260ba4108788", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "728bb0f481c8ad60", + "origin": "machine" + } + }, + "fi": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "affe58c310117614", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "d9aabe6164c2dabc", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "e2f0bd78ca1d688c", + "origin": "machine" + } + }, + "fr": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "9ea21d5bb5fe3a76", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "8c2404ebc81f234d", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "0f4730dc13bb7531", + "origin": "machine" + } + }, + "fy": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "show_details": { + "src": "02c57c62390693ad", + "val": "02c57c62390693ad", + "origin": "machine" + }, + "program_entity": { + "src": "e17c37a5d72d056b", + "val": "e17c37a5d72d056b", + "origin": "machine" + }, + "display_mode": { + "src": "ab106677edc710cd", + "val": "ab106677edc710cd", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "d3420cca05c5d5a8", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "97f624701ac2cab5", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "77d4b05855119528", + "origin": "machine" + } + }, + "gl": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "d78b060fc4df7057", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "628988efb8ace444", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "bd8a7d6ed7dfd480", + "origin": "machine" + } + }, + "gsw": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "a7585383b1eb561b", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "f2a3968964cd313d", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "3ef465fbe1b282c4", + "origin": "machine" + } + }, + "hr": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "show_program": { + "src": "08303382bc087c9b", + "val": "08303382bc087c9b", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "2941c1c04cb85f3a", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "79cba4ccb10ba9cd", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "eddc48b6412247e4", + "origin": "machine" + } + }, + "hu": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "46b7242b6d6800c4", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "95b6e9d77e4730dd", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "d1bb3b30c0698618", + "origin": "machine" + } + }, + "id": { + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3d2926e8e3f97268", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "0872c43738981561", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "11927a706936aef6", + "origin": "machine" + } + }, + "it": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "87e07713a0bfe969", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "0be536d0eaf873ec", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "9e0deaaef8334399", + "origin": "machine" + } + }, + "lb": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "28d068ee447184da", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "b92a73dc854f9206", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "4b1626b5887565c6", + "origin": "machine" + } + }, + "lt": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "0ad809a0ce24a5a7", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "14311d614c6accbf", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "a520f16a6faefe03", + "origin": "machine" + } + }, + "lv": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "progress": { + "src": "1b90271d66cf2d3a", + "val": "1b90271d66cf2d3a", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "bacc602df3c68fe4", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "1808f69fc08f9b41", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "d3d573e0dce7a857", + "origin": "machine" + } + }, + "nb": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "241bbe7a28a9aaa8", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "0455f3fe3b4d9f3a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "89ee3d7d28e72a22", + "origin": "machine" + } + }, + "nl": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "icon": { + "src": "716f63b96e0c2632", + "val": "716f63b96e0c2632", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "fce96f6c185f22c7", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "38ec5c4d4957fcfa", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "5e36e1e18937d38b", + "origin": "machine" + } + }, + "pl": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "75a67c8b1d24fc51", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "f0c966c3c2db84da", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "d538cbddd581bf15", + "origin": "machine" + } + }, + "pt": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "52da8974f4ec27fa", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "03fa4bb1c0c51cbb", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "268d3494ad95e7f3", + "origin": "machine" + } + }, + "pt-BR": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "52da8974f4ec27fa", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "03fa4bb1c0c51cbb", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "268d3494ad95e7f3", + "origin": "machine" + } + }, + "ro": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "1608872d3d460c09", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "6abe0ec15790090f", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "28861cf3acdf8813", + "origin": "machine" + } + }, + "sk": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "54925846857a7607", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "d94475f59310e685", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "58a9f74a305ffc56", + "origin": "machine" + } + }, + "sl": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "show_program": { + "src": "08303382bc087c9b", + "val": "08303382bc087c9b", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "c052d0a5a924dcce", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "07ba5b892881fa05", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "e9348c5c94b10c4a", + "origin": "machine" + } + }, + "sq": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3f98fe837cd69c53", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "bb26490f0b84c7cb", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "2b6889329cdabc8c", + "origin": "machine" + } + }, + "sv": { + "minutes": { + "src": "b6c935d4f3c7b220", + "val": "b6c935d4f3c7b220", + "origin": "machine" + }, + "status": { + "src": "bae7d5be70820ed5", + "val": "bae7d5be70820ed5", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "fd0f1bedb4f50003", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "de8e9080c6699c67", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "647b01e4e9326909", + "origin": "machine" + } + }, + "uk": { + "status_entity": { + "src": "bcbb6bff443af7df", + "val": "bcbb6bff443af7df", + "origin": "machine" + }, + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3e36ac738266908a", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "14736c8125314057", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "0b9d37f2f00f7a65", + "origin": "machine" + } + }, + "ar": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "483c816de3a7f039", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "bfc3318bde36322a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "5756826926efbec6", + "origin": "machine" + } + }, + "bg": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "1861f38b0d7f132c", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "b4860bf2bc3eb01a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "1778303a61ae5539", + "origin": "machine" + } + }, + "bn": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "66d772fd72ee9912", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "b89ea0b3109cbf12", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "fb92f3e1c9bc2a3b", + "origin": "machine" + } + }, + "el": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "afff6c43bf3d4a07", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "86183af6b20a880d", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "6280734b3047f1dd", + "origin": "machine" + } + }, + "eo": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "8c85d6505c46e5d0", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "cbf0bafe90030444", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "4e86138b87663479", + "origin": "machine" + } + }, + "es": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "9ab0be24e24e8e5c", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "40e08a8a42a0ad36", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "64a8d318d8789780", + "origin": "machine" + } + }, + "es-419": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "9ab0be24e24e8e5c", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "40e08a8a42a0ad36", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "64a8d318d8789780", + "origin": "machine" + } + }, + "fa": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "a0f7a59049113aa9", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "201d3738e41a5513", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "40a744f62acfbb85", + "origin": "machine" + } + }, + "ga": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3e3ce389b217d5a9", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "96a88e166397e23a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "ac076e3f0c2ca307", + "origin": "machine" + } + }, + "he": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "e4e09e67f68ffef7", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "1bef06724d2a5617", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "43d87535ae2a6169", + "origin": "machine" + } + }, + "hi": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "cc74133d3062ec76", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "ddf3c44215a9ca15", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "7aefcb1754dd220e", + "origin": "machine" + } + }, + "hy": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "25af3433f20f0e95", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "8f3461ba9910d4e3", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "41e2bd14f30ef305", + "origin": "machine" + } + }, + "is": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "26b8782d3fe6a6c2", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "9a0ff88c462d4889", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "ccf081a3662fded7", + "origin": "machine" + } + }, + "ja": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "afbe64ea83fcd871", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "13c3438c1ca245a7", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "8ac6a80f263546e4", + "origin": "machine" + } + }, + "ka": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "bb04125b317a4b03", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "7d3a3f66f34d079d", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "3f559391115f81c1", + "origin": "machine" + } + }, + "ko": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "ed9286f9752a6772", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "ea81eb4dc902ba87", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "3e433f70711e0fd0", + "origin": "machine" + } + }, + "mk": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "be479974eafb7db2", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "ff331763fcc100f3", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "b7a1713d796b344b", + "origin": "machine" + } + }, + "ml": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "b786260285b23a80", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "b358892b0a97b7f4", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "bf6e85756dd04eaf", + "origin": "machine" + } + }, + "ru": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "ff00595275943e14", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "a21df3c7f82a4ec4", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "1013eebbe24b0360", + "origin": "machine" + } + }, + "sr-Latn": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "b0a38dcc5c202c83", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "3eacd5f644caa9f1", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "d02325f63d89ddc4", + "origin": "machine" + } + }, + "ta": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "8d5949ce5cc05b18", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "5837f3a7f51e258a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "d863393d936a1828", + "origin": "machine" + } + }, + "te": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "c2306be687e5b3fa", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "1c79151437f3d86e", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "4e7ab747f4297971", + "origin": "machine" + } + }, + "th": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "60cc60803c6e8d15", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "e9c8536e511799b7", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "8aa7fb4c6a4340ef", + "origin": "machine" + } + }, + "tr": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "0a8bd01ef161a864", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "e81f890908bae598", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "2f78d7b0d6f21bc7", + "origin": "machine" + } + }, + "ur": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "94c50b3c89d486a2", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "f1d42d927c7fd3bd", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "72aaaa39d20c1512", + "origin": "machine" + } + }, + "vi": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "3d86a6e93f437add", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "cf7446c495ee982a", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "711d13aa776d1290", + "origin": "machine" + } + }, + "zh-Hans": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "a2ca808795878f67", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "e504c58b05c0f347", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "e8113090dcc52d5d", + "origin": "machine" + } + }, + "zh-Hant": { + "tap_action": { + "src": "d9914c3b2f14b3ac", + "val": "ed36ed31e03783de", + "origin": "machine" + }, + "hold_action": { + "src": "d94475f59310e685", + "val": "781be2b99e5ac1f9", + "origin": "machine" + }, + "double_tap_action": { + "src": "4b1626b5887565c6", + "val": "329d139677949670", + "origin": "machine" + } + } + } +} diff --git a/custom_components/ha_washdata/translations/af.json b/custom_components/ha_washdata/translations/af.json new file mode 100644 index 0000000..24e8674 --- /dev/null +++ b/custom_components/ha_washdata/translations/af.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-opstelling", + "description": "Stel jou wasmasjien of ander toestel op.\n\nKragsensor word benodig.\n\n**Volgende sal jy gevra word of jy jou eerste profiel wil skep.**", + "data": { + "name": "Toestelnaam", + "device_type": "Soort toestel", + "power_sensor": "Kragsensor", + "min_power": "Minimum kragdrempel (W)" + }, + "data_description": { + "name": "'n Vriendelike naam vir hierdie toestel (bv. 'Wasmasjien', 'skottelgoedwasser').", + "device_type": "Watter tipe toestel is dit? Help om opsporing en etikettering aan te pas.", + "power_sensor": "Die sensor-entiteit wat intydse kragverbruik (in watt) vanaf jou slimprop rapporteer.", + "min_power": "Kraglesings bo hierdie drempel (in watt) dui aan dat die toestel aan die gang is. Begin met 2W vir die meeste toestelle." + } + }, + "first_profile": { + "title": "Skep eerste profiel", + "description": "'n **Siklus** is 'n volledige loop van jou toestel (bv. 'n vrag wasgoed). 'n **Profiel** groepeer hierdie siklusse volgens tipe (bv. 'Katoen', 'Vinnigewas'). Die skep van 'n profiel help nou om die integrasietydsduur onmiddellik te skat.\n\nJy kan hierdie profiel handmatig in die toestelkontroles kies as dit nie outomaties bespeur word nie.\n\n**Laat 'Profielnaam' leeg om hierdie stap oor te slaan.**", + "data": { + "profile_name": "Profielnaam (opsioneel)", + "manual_duration": "Geskatte duur (minute)" + } + } + }, + "error": { + "cannot_connect": "Kon nie koppel nie", + "invalid_auth": "Ongeldige stawing", + "unknown": "Onverwagte fout" + }, + "abort": { + "already_configured": "Toestel is reeds opgestel" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Hersien leerterugvoere", + "settings": "Instellings", + "notifications": "Kennisgewings", + "manage_cycles": "Bestuur siklusse", + "manage_profiles": "Bestuur profiele", + "manage_phase_catalog": "Bestuur Fasekatalogus", + "record_cycle": "Rekordsiklus (Handmatig)", + "diagnostics": "Diagnostiek en instandhouding" + } + }, + "apply_suggestions": { + "title": "Kopieer voorgestelde waardes", + "description": "Dit sal die huidige voorgestelde waardes na jou gestoorde instellings kopieer. Dit is 'n eenmalige handeling en aktiveer nie outo-oorskryf nie.\n\nVoorgestelde waardes om te kopieer:\n{suggested}", + "data": { + "confirm": "Bevestig kopieeraksie" + } + }, + "apply_suggestions_confirm": { + "title": "Hersien voorgestelde veranderinge", + "description": "WashData gevind **{pending_count}** voorgestelde waarde(s) om toe te pas.\n\nVeranderinge:\n{changes}\n\n✅ Merk die blokkie hieronder en klik **Dien in** om hierdie veranderinge onmiddellik toe te pas en te stoor.\n❌ Laat dit ongemerk en klik **Dien in** om te kanselleer en terug te gaan.", + "data": { + "confirm_apply_suggestions": "Pas hierdie veranderinge toe en stoor dit" + } + }, + "settings": { + "title": "Instellings", + "description": "{deprecation_warning}Pas opsporingsdrempels, leer en gevorderde gedrag aan. Verstekstellings werk goed vir die meeste opstellings.\n\nVoorgestelde instellings tans beskikbaar: {suggestions_count}.\nAs dit bo 0 is, maak Gevorderde instellings oop en gebruik 'Pas voorgestelde waardes toe' om aanbevelings te hersien.", + "data": { + "apply_suggestions": "Pas voorgestelde waardes toe", + "device_type": "Soort toestel", + "power_sensor": "Kragsensor-entiteit", + "min_power": "Minimum krag (W)", + "off_delay": "Siklus eindvertraging (s)", + "notify_actions": "Kennisgewingsaksies", + "notify_people": "Vertraag aflewering totdat hierdie mense tuis is", + "notify_only_when_home": "Vertraag kennisgewings totdat gekose persoon tuis is", + "notify_fire_events": "Aktiveer outomatiseringsgebeure", + "notify_start_services": "Siklusbegin - Kennisgewingteikens", + "notify_finish_services": "Siklus Voltooi - Kennisgewing teikens", + "notify_live_services": "Regstreekse vordering - Kennisgewingteikens", + "notify_before_end_minutes": "Voorafhandelingkennisgewing (minute voor einde)", + "notify_title": "Kennisgewing titel", + "notify_icon": "Kennisgewing-ikoon", + "notify_start_message": "Begin Boodskapformaat", + "notify_finish_message": "Voltooi Boodskapformaat", + "notify_pre_complete_message": "Pre-voltooiing Boodskap Formaat", + "show_advanced": "Wysig Gevorderde Instellings (Volgende Stap)" + }, + "data_description": { + "apply_suggestions": "Merk hierdie blokkie om waardes voorgestel deur die leeralgoritme in die vorm te kopieer. Hersien voordat u dit stoor.\n\nVoorgestelde (vanaf leer):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Kies die tipe toestel (wasmasjien, droër, skottelgoedwasser, koffiemasjien, EV). Hierdie merker word saam met elke siklus gestoor vir beter organisasie.", + "power_sensor": "Die sensor-entiteit rapporteer intydse krag (in watt). Jy kan dit enige tyd verander sonder om historiese data te verloor-nuttig as jy 'n slimprop vervang.", + "min_power": "Kraglesings bo hierdie drempel (in watt) dui aan dat die toestel aan die gang is. Begin met 2W vir die meeste toestelle.", + "off_delay": "Sekondes moet die gladde krag onder die stopdrempel bly om voltooiing te merk. Verstek: 120s.", + "notify_actions": "Opsionele Home Assistant-aksievolgorde om vir kennisgewings uit te voer (skrifte, kennisgewing, TTS, ens.). Indien gestel, loop aksies voor terugvaldiens in kennis stel.", + "notify_people": "Mense-entiteite wat vir teenwoordigheidhek gebruik word. Wanneer dit geaktiveer is, word kennisgewingaflewering vertraag totdat ten minste een geselekteerde persoon tuis is.", + "notify_only_when_home": "Indien geaktiveer, stel kennisgewings uit totdat ten minste een geselekteerde persoon tuis is.", + "notify_fire_events": "Indien geaktiveer, vuur Home Assistant-gebeurtenisse vir siklusbegin en -einde (ha_washdata_cycle_started en ha_washdata_cycle_ended) af om outomatisering aan te dryf.", + "notify_start_services": "Een of meer stel dienste in kennis om te waarsku wanneer 'n siklus begin. Laat leeg om beginkennisgewings oor te slaan.", + "notify_finish_services": "Een of meer stel dienste in kennis om te waarsku wanneer 'n siklus eindig of naby voltooiing is. Laat leeg om afwerkingkennisgewings oor te slaan.", + "notify_live_services": "Een of meer stel dienste in kennis om regstreekse vorderingopdaterings te ontvang terwyl die siklus loop (slegs mobiele metgesel-toepassing). Laat leeg om regstreekse opdaterings oor te slaan.", + "notify_before_end_minutes": "Minute voor die geskatte einde van die siklus om 'n kennisgewing te aktiveer. Stel op 0 om te deaktiveer. Verstek: 0.", + "notify_title": "Gepasmaakte titel vir kennisgewings. Ondersteun {device} plekhouer.", + "notify_icon": "Ikoon om vir kennisgewings te gebruik (bv. mdi:wasmasjien). Laat leeg om sonder ikoon te stuur.", + "notify_start_message": "Boodskap gestuur wanneer 'n siklus begin. Ondersteun {device} plekhouer.", + "notify_finish_message": "Boodskap gestuur wanneer 'n siklus klaar is. Ondersteun {device}, {duration}, {program}, {energy_kwh}, {cost} plekhouers.", + "notify_pre_complete_message": "Teks van die herhalende regstreekse vordering word opgedateer terwyl die siklus loop. Ondersteun {device}, {minutes}, {program} plekhouers." + } + }, + "notifications": { + "title": "Kennisgewings", + "description": "Stel kennisgewingskanale, sjablone en opsionele regstreekse mobiele vorderingopdaterings op.", + "data": { + "notify_actions": "Kennisgewingsaksies", + "notify_people": "Vertraag aflewering totdat hierdie mense tuis is", + "notify_only_when_home": "Vertraag kennisgewings totdat gekose persoon tuis is", + "notify_fire_events": "Aktiveer outomatiseringsgebeure", + "notify_start_services": "Siklusbegin - Kennisgewingsteikens", + "notify_finish_services": "Siklus Voltooi - Kennisgewing teikens", + "notify_live_services": "Regstreekse vordering - Kennisgewingteikens", + "notify_before_end_minutes": "Voorafhandelingkennisgewing (minute voor einde)", + "notify_live_interval_seconds": "Regstreekse opdateringsinterval (sekondes)", + "notify_live_overrun_percent": "Regstreekse opdatering oorskry toelae (%)", + "notify_live_chronometer": "Chronometer aftellingstimer", + "notify_title": "Kennisgewing titel", + "notify_icon": "Kennisgewing-ikoon", + "notify_start_message": "Begin Boodskapformaat", + "notify_finish_message": "Voltooi Boodskapformaat", + "notify_pre_complete_message": "Pre-voltooiing Boodskap Formaat", + "energy_price_entity": "Energieprys – Entiteit (opsioneel)", + "energy_price_static": "Energieprys – Statiese waarde (opsioneel)", + "go_back": "Gaan terug sonder om te stoor", + "notify_reminder_message": "Herinnering Boodskap Formaat", + "notify_timeout_seconds": "Maak outomaties toe na (sekondes)", + "notify_channel": "Kennisgewingkanaal (status/regstreeks/herinnering)", + "notify_finish_channel": "Klaar kennisgewingkanaal" + }, + "data_description": { + "go_back": "Merk dit en klik Indien om enige bogenoemde veranderinge weg te gooi en na die hoofkieslys terug te keer.", + "notify_actions": "Opsionele Home Assistant-aksievolgorde om vir kennisgewings uit te voer (skrifte, kennisgewing, TTS, ens.). Indien gestel, loop aksies voor terugvaldiens in kennis stel.", + "notify_people": "Mense-entiteite wat vir teenwoordigheidhek gebruik word. Wanneer dit geaktiveer is, word kennisgewingaflewering vertraag totdat ten minste een geselekteerde persoon tuis is.", + "notify_only_when_home": "Indien geaktiveer, stel kennisgewings uit totdat ten minste een geselekteerde persoon tuis is.", + "notify_fire_events": "Indien geaktiveer, vuur Home Assistant-gebeurtenisse vir siklusbegin en -einde (ha_washdata_cycle_started en ha_washdata_cycle_ended) af om outomatisering aan te dryf.", + "notify_start_services": "Een of meer stel dienste in kennis om te waarsku wanneer 'n siklus begin (bv. notify.mobile_app_my_phone). Laat leeg om beginkennisgewings oor te slaan.", + "notify_finish_services": "Een of meer stel dienste in kennis om te waarsku wanneer 'n siklus eindig of naby voltooiing is. Laat leeg om afwerkingkennisgewings oor te slaan.", + "notify_live_services": "Een of meer stel dienste in kennis om regstreekse vorderingopdaterings te ontvang terwyl die siklus loop (slegs mobiele metgesel-toepassing). Laat leeg om regstreekse opdaterings oor te slaan.", + "notify_before_end_minutes": "Minute voor die geskatte einde van die siklus om 'n kennisgewing te aktiveer. Stel op 0 om te deaktiveer. Verstek: 0.", + "notify_live_interval_seconds": "Hoe gereeld vorderingsopdaterings gestuur word terwyl dit loop. Laer waardes reageer meer, maar verbruik meer kennisgewings. 'n Minimum van 30 sekondes word afgedwing - waardes onder 30 s word outomaties na 30 s afgerond.", + "notify_live_overrun_percent": "Veiligheidsmarge bo die beraamde opdateringtelling vir lang/oorlopende siklusse (byvoorbeeld 20% laat 1,2x beraamde opdaterings toe).", + "notify_live_chronometer": "Wanneer dit geaktiveer is, bevat elke regstreekse opdatering 'n chronometer-aftelling na die geskatte eindtyd. Die kennisgewingtydteller tik outomaties af op die toestel tussen opdaterings (slegs Android-metgeseltoepassing).", + "notify_title": "Gepasmaakte titel vir kennisgewings. Ondersteun {device} plekhouer.", + "notify_icon": "Ikoon om vir kennisgewings te gebruik (bv. mdi:wasmasjien). Laat leeg om sonder ikoon te stuur.", + "notify_start_message": "Boodskap gestuur wanneer 'n siklus begin. Ondersteun {device} plekhouer.", + "notify_finish_message": "Boodskap gestuur wanneer 'n siklus klaar is. Ondersteun {device}, {duration}, {program}, {energy_kwh}, {cost} plekhouers.", + "energy_price_entity": "Kies 'n numeriese entiteit wat die huidige elektrisiteitsprys hou (bv. sensor.electricity_price, input_number.energy_rate). As dit gestel is, aktiveer die {cost} plekhouer. Kry voorrang bo die statiese waarde as albei gekonfigureer is.", + "energy_price_static": "Vaste elektrisiteitsprys per kWh. As dit gestel is, aktiveer die {cost} plekhouer. Ignoreer as 'n entiteit hierbo gekonfigureer is. Voeg jou geldeenheid simbool in die boodskap sjabloon, bv. {cost} €.", + "notify_reminder_message": "Teks van die eenmalige herinnering het die gekonfigureerde aantal minute voor die geskatte einde gestuur. Onderskei van die herhalende regstreekse opdaterings hierbo. Ondersteun {device}, {minutes}, {program} plekhouers.", + "notify_timeout_seconds": "Verwerp kennisgewings outomaties na hierdie baie sekondes (aangestuur as die metgesel-toepassing 'time-out'). Stel op 0 om hulle te hou totdat dit verwerp word. Verstek: 0.", + "notify_channel": "Android metgeselprogramkennisgewingkanaal vir status, regstreekse vordering en herinneringkennisgewings. Die klank en belangrikheid van 'n kanaal word in die metgesel-toepassing gekonfigureer die eerste keer dat die kanaalnaam gebruik word - WashData stel net die kanaalnaam. Laat leeg vir die toepassing verstek.", + "notify_finish_channel": "Aparte Android-kanaal vir die voltooide waarskuwing (ook gebruik deur die aanmaning en die wasgoed-wag-knal) sodat dit sy eie klank kan hê. Laat leeg om die kanaal hierbo te hergebruik.", + "notify_pre_complete_message": "Teks van die herhalende regstreekse vordering word opgedateer terwyl die siklus loop. Ondersteun {device}, {minutes}, {program} plekhouers." + } + }, + "advanced_settings": { + "title": "Gevorderde instellings", + "description": "Pas opsporingsdrempels, leer en gevorderde gedrag aan. Verstekstellings werk goed vir die meeste opstellings.", + "sections": { + "suggestions_section": { + "name": "Voorgestelde instellings", + "description": "{suggestions_count} aangeleerde voorstelle beskikbaar. Merk Pas Voorgestelde Waardes Toe om voorgestelde veranderinge voor stoor te hersien.", + "data": { + "apply_suggestions": "Pas voorgestelde waardes toe" + }, + "data_description": { + "apply_suggestions": "Merk hierdie blokkie om 'n hersieningstap oop te maak vir voorgestelde waardes van die leeralgoritme. Niks word outomaties gestoor nie.\n\nVoorgestelde (vanaf leer):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Opsporing & kragdrempels", + "description": "Wanneer die toestel as AAN of AF beskou word, energiedrempels en tydsberekening vir toestandsoorgange.", + "data": { + "start_duration_threshold": "Begin debounce Tydsduur (s)", + "start_energy_threshold": "Begin Energiehek (Wh)", + "completion_min_seconds": "Voltooiing Minimum Looptyd (sekondes)", + "start_threshold_w": "Begindrempel (W)", + "stop_threshold_w": "Stopdrempel (W)", + "end_energy_threshold": "Eindenergiehek (Wh)", + "running_dead_zone": "Hardloop Dooie Sone (sekondes)", + "end_repeat_count": "Beëindig Herhaaltelling", + "min_off_gap": "Min gaping tussen siklusse (s)", + "sampling_interval": "Steekproefinterval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignoreer kort kragpunte korter as hierdie duur (sekondes). Filtreer selflaaispykers uit (bv. 60W vir 2s) voor die werklike siklus begin. Verstek: 5s.", + "start_energy_threshold": "Siklus moet ten minste soveel energie (Wh) ophoop tydens die BEGIN fase om bevestig te word. Verstek: 0,005 Wh.", + "completion_min_seconds": "Siklusse korter as dit sal as 'onderbreek' gemerk word, selfs al eindig hulle natuurlik. Verstek: 600s.", + "start_threshold_w": "Krag moet BO hierdie drempel styg om 'n siklus te BEGIN. Stel hoër as jou bystandkrag. Verstek: min_krag + 1 W.", + "stop_threshold_w": "Krag moet ONDER hierdie drempel val om 'n siklus te BEËINDIG. Stel net bo nul om geraas te vermy wat valse punte veroorsaak. Verstek: 0,6 * min_krag.", + "end_energy_threshold": "Siklus eindig slegs as energie in die laaste Af-vertragingsvenster onder hierdie waarde (Wh) is. Voorkom valse eindes as krag naby nul fluktueer. Verstek: 0,05 Wh.", + "running_dead_zone": "Nadat die siklus begin het, ignoreer kragdalings vir soveel sekondes om valse eindbespeuring tydens die aanvanklike spin-up fase te voorkom. Stel op 0 om te deaktiveer. Verstek: 0s.", + "end_repeat_count": "Aantal kere wat die eindvoorwaarde (krag onder die drempel vir af_vertraging) agtereenvolgens nagekom moet word voordat die siklus beëindig word. Hoër waardes verhoed vals eindes tydens pouses. Verstek: 1.", + "min_off_gap": "As 'n siklus begin binne soveel sekondes nadat die vorige een geëindig het, sal hulle in 'n enkele siklus saamgesmelt word.", + "sampling_interval": "Minimum steekproefinterval in sekondes. Opdaterings vinniger as hierdie koers sal geïgnoreer word. Verstek: 30s (2s vir wasmasjiene, wasdroërs en skottelgoedwassers)." + } + }, + "matching_section": { + "name": "Profielpassing & leer", + "description": "Hoe aggressief WashData lopende siklusse teen aangeleerde profiele pas en wanneer om vir terugvoer te vra.", + "data": { + "profile_match_min_duration_ratio": "Profielwedstryd minimum duurverhouding (0,1-1,0)", + "profile_match_interval": "Profielpassingsinterval (sekondes)", + "profile_match_threshold": "Profielpassingdrempel", + "profile_unmatch_threshold": "Profiel Onpas Drempel", + "auto_label_confidence": "Outo-etiket selfvertroue (0-1)", + "learning_confidence": "Terugvoer Versoek vertroue (0-1)", + "suppress_feedback_notifications": "Onderdruk terugvoerkennisgewings", + "duration_tolerance": "Tydsverdraagsaamheid (0-0,5)", + "smoothing_window": "Maak venster glad (monsters)", + "profile_duration_tolerance": "Profielwedstrydduurverdraagsaamheid (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum duurverhouding vir profielpassing. Loopsiklus moet ten minste hierdie fraksie van profielduur wees om te pas. Laer = vroeër passing. Verstek: 0,50 (50%).", + "profile_match_interval": "Hoe gereeld (in sekondes) om profielpassing tydens 'n siklus te probeer. Laer waardes bied vinniger opsporing, maar gebruik meer SVE. Verstek: 300s (5 minute).", + "profile_match_threshold": "Minimum DTW-ooreenkomstelling (0.0–1.0) word vereis om 'n profiel as 'n passing te beskou. Hoër = strenger passing, minder vals positiewe. Verstek: 0.4.", + "profile_unmatch_threshold": "DTW-ooreenkomstelling (0.0–1.0) waaronder 'n voorheen ooreenstemmende profiel verwerp word. Moet laer as die wedstryddrempel wees om flikkering te voorkom. Verstek: 0,35.", + "auto_label_confidence": "Merk siklusse outomaties by of bo hierdie vertroue by voltooiing (0.0–1.0).", + "learning_confidence": "Minimum vertroue om gebruikerverifikasie aan te vra via 'n gebeurtenis (0.0–1.0).", + "suppress_feedback_notifications": "Wanneer dit geaktiveer is, sal WashData steeds intern terugvoer naspoor en versoek, maar sal nie aanhoudende kennisgewings wys wat jou vra om siklusse te bevestig nie. Nuttig wanneer siklusse altyd korrek opgespoor word en jy vind die opdragte steurend.", + "duration_tolerance": "Toleransie vir tydoorblywende skattings tydens 'n lopie (0.0–0.5 stem ooreen met 0–50%).", + "smoothing_window": "Aantal onlangse kraglesings wat gebruik is vir bewegende gemiddelde gladmaak.", + "profile_duration_tolerance": "Toleransie gebruik deur profielpassing vir totale duurafwyking (0.0–0.5). Verstekgedrag: ±25%." + } + }, + "timing_section": { + "name": "Tydsberekening, onderhoud & foutsporing", + "description": "Waghond, vorderingterugstelling, outo-onderhoud en blootstelling van debug-entiteite.", + "data": { + "watchdog_interval": "Waghondinterval (sekondes)", + "no_update_active_timeout": "Geen-opdatering-tydverstreke (s)", + "progress_reset_delay": "Vordering-terugstelling vertraging (sekondes)", + "auto_maintenance": "Aktiveer outo-onderhoud", + "expose_debug_entities": "Stel ontfoutingsentiteite bloot", + "save_debug_traces": "Stoor ontfoutspore" + }, + "data_description": { + "watchdog_interval": "Sekondes tussen waghondkontroles terwyl jy hardloop. Verstek: 5s. WAARSKUWING: Maak seker dit is HOËR as jou sensor se opdateringsinterval om vals stops te vermy (bv., as die sensor elke 60s opdateer, gebruik 65s+).", + "no_update_active_timeout": "Vir publiseer-op-verandering-sensors: forseer slegs 'n AKTIEWE siklus af as geen opdaterings vir soveel sekondes opdaag nie. Laekrag-voltooiing gebruik steeds die Off Delay.", + "progress_reset_delay": "Nadat 'n siklus voltooi is (100%), wag soveel sekondes van lediging voordat die vordering na 0% teruggestel word. Verstek: 300s.", + "auto_maintenance": "Aktiveer outomatiese instandhouding (herstel monsters, voeg fragmente saam).", + "expose_debug_entities": "Wys gevorderde sensors (vertroue, fase, dubbelsinnigheid) vir ontfouting.", + "save_debug_traces": "Stoor gedetailleerde rangorde en kragspoordata in geskiedenis (Verhoog berginggebruik)." + } + }, + "anti_wrinkle_section": { + "name": "Anti-kreukel-skild (Droërs)", + "description": "Ignoreer trommelrotasies ná die siklus om spooksiklusse te voorkom. Slegs vir droër / was-droër.", + "data": { + "anti_wrinkle_enabled": "Anti-rimpel skild (slegs droër)", + "anti_wrinkle_max_power": "Anti-rimpel maksimum krag (W)", + "anti_wrinkle_max_duration": "Anti-rimpel maksimum duur (s)", + "anti_wrinkle_exit_power": "Anti-rimpeluitgangskrag (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignoreer kort laekrag-dromrotasies nadat 'n siklus geëindig het om spooksiklusse te voorkom (slegs droër/wasdroër).", + "anti_wrinkle_max_power": "Uitbarstings by of onder hierdie krag word behandel as anti-rimpel rotasies in plaas van nuwe siklusse. Slegs van toepassing wanneer Anti-rimpel Skild geaktiveer is.", + "anti_wrinkle_max_duration": "Maksimum barslengte om te behandel as anti-rimpel rotasie. Slegs van toepassing wanneer Anti-rimpel Skild geaktiveer is.", + "anti_wrinkle_exit_power": "As krag onder hierdie vlak daal, verlaat die anti-rimpel onmiddellik (waar af). Slegs van toepassing wanneer Anti-rimpel Skild geaktiveer is." + } + }, + "delay_start_section": { + "name": "Vertraagde-begin-opsporing", + "description": "Behandel volgehoue lae-krag bystand as 'Wag om te begin' totdat die siklus werklik begin.", + "data": { + "delay_start_detect_enabled": "Aktiveer vertraagde beginopsporing", + "delay_confirm_seconds": "Vertraagde begin: Bevestigingstyd vir bystand (s)", + "delay_timeout_hours": "Vertraagde begin: maksimum wagtyd (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Wanneer dit geaktiveer is, word 'n kort lae-krag drein piek gevolg deur volgehoue ​​bystand krag bespeur as 'n vertraagde begin. Die toestandsensor wys 'Wag om te begin' tydens die vertraging. Geen programtyd of vordering word opgespoor totdat die siklus werklik begin nie. Vereis dat start_threshold_w bo die dreineerpuntkrag gestel word.", + "delay_confirm_seconds": "Krag moet vir soveel sekondes tussen Stopdrempel (W) en Begindrempel (W) bly voordat WashData 'Wag om te begin' betree. Filter kort pieke tydens menu-navigasie uit. Verstek: 60 s.", + "delay_timeout_hours": "Veiligheidsonderbreking: as die masjien na soveel ure steeds in 'Wag om te begin' is, stel WashData terug na Af. Verstek: 8 uur." + } + }, + "external_triggers_section": { + "name": "Eksterne snellers, deur & pouse", + "description": "Opsionele eksterne eindsnellersensor, deursensor vir pouse/skoon-opsporing, en skakelaar vir krag-af tydens pouse.", + "data": { + "external_end_trigger_enabled": "Aktiveer eksterne siklus-eindsneller", + "external_end_trigger": "Eksterne siklus-eindsensor", + "external_end_trigger_inverted": "Keer snellerlogika om (sneller aan AF)", + "door_sensor_entity": "Deursensor", + "pause_cuts_power": "Sny krag tydens pouse", + "switch_entity": "Skakel entiteit (vir pouse kragonderbreking)", + "notify_unload_delay_minutes": "Wasgoedwagkennisgewingvertraging (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktiveer monitering van 'n eksterne binêre sensor om siklusvoltooiing te aktiveer.", + "external_end_trigger": "Kies 'n binêre sensor-entiteit. Wanneer hierdie sensor aktiveer, sal die huidige siklus eindig met status 'voltooid'.", + "external_end_trigger_inverted": "Die sneller brand by verstek wanneer die sensor AAN skakel. Merk hierdie blokkie om te brand wanneer die sensor eerder AF skakel.", + "door_sensor_entity": "Opsionele binêre sensor vir die masjiendeur (aan = oop, af = toe). Wanneer die deur oopmaak tydens 'n aktiewe siklus, sal WashData die siklus bevestig as opsetlik onderbreek. Nadat die siklus geëindig het, as die deur nog gesluit is, verander die toestand na 'Skoon' totdat die deur oopgemaak word. Let wel: die sluiting van die deur hervat NIE 'n onderbreekte siklus outomaties nie - hervat moet handmatig geaktiveer word via die Hervat siklus-knoppie of diens.", + "pause_cuts_power": "Skakel ook die gekonfigureerde skakelaar-entiteit af wanneer jy met die Pouse-siklus-knoppie of diens onderbreek. Tree slegs in werking as 'n skakelaar-entiteit vir hierdie toestel opgestel is.", + "switch_entity": "Die skakel-entiteit om te wissel wanneer onderbreek of hervat word. Vereis wanneer 'Sny krag tydens pouse' geaktiveer is.", + "notify_unload_delay_minutes": "Stuur 'n kennisgewing via die afwerkingkennisgewingkanaal nadat die siklus geëindig het en die deur vir soveel minute nie oopgemaak is nie (vereis deursensor). Stel op 0 om te deaktiveer. Verstek: 60 min." + } + }, + "pump_section": { + "name": "Pompmonitor", + "description": "Slegs vir pomptoesteltipe. Vuur 'n gebeurtenis as 'n pompsiklus langer as hierdie duur loop.", + "data": { + "pump_stuck_duration": "Pomp vas waarskuwingsdrempel(s) (slegs pomp)" + }, + "data_description": { + "pump_stuck_duration": "As 'n pompsiklus na soveel sekondes steeds aan die gang is, word 'n ha_washdata_pump_stuck-gebeurtenis een keer afgevuur. Gebruik dit om 'n motor wat vasgesteek is op te spoor (tipiese sumppomploop is minder as 60 s). Verstek: 1800 s (30 min.). Slegs tipe pomptoestel." + } + }, + "device_link_section": { + "name": "Toestel skakel", + "description": "Koppel hierdie WashData-toestel opsioneel aan 'n bestaande Home Assistant-toestel (byvoorbeeld die slimprop of die toestel self). Die WashData-toestel word dan gewys as \"Gekoppel via\" daardie toestel. Laat leeg om WashData as 'n selfstandige toestel te hou.", + "data": { + "linked_device": "Gekoppelde toestel" + }, + "data_description": { + "linked_device": "Kies die toestel om WashData aan te koppel. Dit voeg 'n 'Gekoppel via'-verwysing by in die toestelregister; WashData hou sy eie toestelkaart en entiteite. Vee die keuse uit om die skakel te verwyder." + } + } + } + }, + "diagnostics": { + "title": "Diagnostiek en instandhouding", + "description": "Voer instandhoudingsaksies uit, soos om gefragmenteerde siklusse saam te voeg of gestoorde data te migreer.\n\n**berginggebruik**\n\n- Lêergrootte: {file_size_kb} KB\n- Siklusse: {cycle_count}\n- Profiele: {profile_count}\n- Ontfoutspore: {debug_count}", + "menu_options": { + "reprocess_history": "Onderhoud: Herverwerk en optimaliseer data", + "clear_debug_data": "Vee ontfoutdata uit (maak spasie vry)", + "wipe_history": "Vee ALLE data vir hierdie toestel uit (onomkeerbaar)", + "export_import": "Voer JSON uit/voer in met instellings (kopieer/plak)", + "menu_back": "← Terug" + } + }, + "clear_debug_data": { + "title": "Vee ontfoutdata uit", + "description": "Is jy seker jy wil alle gestoorde ontfoutspore uitvee? Dit sal spasie vrymaak, maar gedetailleerde ranglysinligting uit vorige siklusse verwyder." + }, + "manage_cycles": { + "title": "Bestuur siklusse", + "description": "Onlangse siklusse:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Outo-etiketteer ou siklusse", + "select_cycle_to_label": "Benoem Spesifieke Siklus", + "select_cycle_to_delete": "Vee siklus uit", + "interactive_editor": "Voeg saam / verdeel interaktiewe redigeerder", + "trim_cycle_select": "Knip siklusdata", + "menu_back": "← Terug" + } + }, + "manage_cycles_empty": { + "title": "Bestuur siklusse", + "description": "Geen siklusse is nog aangeteken nie.", + "menu_options": { + "auto_label_cycles": "Outo-etiketteer ou siklusse", + "menu_back": "← Terug" + } + }, + "manage_profiles": { + "title": "Bestuur profiele", + "description": "Profielopsomming:\n{profile_summary}", + "menu_options": { + "create_profile": "Skep nuwe profiel", + "edit_profile": "Wysig/hernoem profiel", + "delete_profile_select": "Vee profiel uit", + "profile_stats": "Profielstatistieke", + "cleanup_profile": "Maak geskiedenis skoon - grafiek en vee uit", + "assign_profile_phases_select": "Ken fasereekse toe", + "menu_back": "← Terug" + } + }, + "manage_profiles_empty": { + "title": "Bestuur profiele", + "description": "Geen profiele is nog geskep nie.", + "menu_options": { + "create_profile": "Skep nuwe profiel", + "menu_back": "← Terug" + } + }, + "manage_phase_catalog": { + "title": "Bestuur Fasekatalogus", + "description": "Fasekatalogus (verstek vir hierdie toestel + pasgemaakte fases):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Skep nuwe fase", + "phase_catalog_edit_select": "Wysig fase", + "phase_catalog_delete": "Vee Fase uit", + "menu_back": "← Terug" + } + }, + "phase_catalog_create": { + "title": "Skep pasgemaakte fase", + "description": "Skep 'n pasgemaakte fasenaam en -beskrywing, en kies op watter toesteltipe dit van toepassing is.", + "data": { + "target_device_type": "Tipe teikentoestel", + "phase_name": "Fase Naam", + "phase_description": "Fasebeskrywing" + } + }, + "phase_catalog_edit_select": { + "title": "Wysig pasgemaakte fase", + "description": "Kies 'n pasgemaakte fase om te wysig.", + "data": { + "phase_name": "Pasgemaakte fase" + } + }, + "phase_catalog_edit": { + "title": "Wysig pasgemaakte fase", + "description": "Redigeringsfase: {phase_name}", + "data": { + "phase_name": "Fase Naam", + "phase_description": "Fasebeskrywing" + } + }, + "phase_catalog_delete": { + "title": "Vee pasgemaakte fase uit", + "description": "Kies 'n pasgemaakte fase om uit te vee. Enige toegewysde reekse wat hierdie fase gebruik, sal verwyder word.", + "data": { + "phase_name": "Pasgemaakte fase" + } + }, + "assign_profile_phases": { + "title": "Ken fasereekse toe", + "description": "Fasevoorskou vir profiel: **{profile_name}**\n\n{timeline_svg}\n\n**Huidige reekse:**\n{current_ranges}\n\nGebruik handelinge hieronder om reekse by te voeg, te wysig, uit te vee of te stoor.", + "menu_options": { + "assign_profile_phases_add": "Voeg fasereeks by", + "assign_profile_phases_edit_select": "Wysig fasereeks", + "assign_profile_phases_delete": "Vee fasereeks uit", + "phase_ranges_clear": "Vee alle reekse uit", + "assign_profile_phases_auto_detect": "Bespeur fases outomaties", + "phase_ranges_save": "Stoor en keer terug", + "menu_back": "← Terug" + } + }, + "assign_profile_phases_add": { + "title": "Voeg fasereeks by", + "description": "Voeg een fasereeks by die huidige konsep.", + "data": { + "phase_name": "Fase", + "start_min": "Begin minuut", + "end_min": "Eindminuut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Wysig fasereeks", + "description": "Kies 'n reeks om te wysig.", + "data": { + "range_index": "Fasereeks" + } + }, + "assign_profile_phases_edit": { + "title": "Wysig fasereeks", + "description": "Dateer die geselekteerde fasereeks op.", + "data": { + "phase_name": "Fase", + "start_min": "Begin minuut", + "end_min": "Eindminuut" + } + }, + "assign_profile_phases_delete": { + "title": "Vee fasereeks uit", + "description": "Kies 'n reeks om uit die konsep te verwyder.", + "data": { + "range_index": "Fasereeks" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases wat outomaties opgespoor is", + "description": "Profiel: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) word outomaties bespeur.** Kies 'n handeling hieronder.", + "data": { + "action": "Aksie" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Naam Bespeur Fases", + "description": "Profiel: **{profile_name}**\n\n**{detected_count} fase(s) bespeur:**\n{ranges_summary}\n\nKen 'n naam aan elke fase toe." + }, + "assign_profile_phases_select": { + "title": "Ken fasereekse toe", + "description": "Kies 'n profiel om fasereekse op te stel.", + "data": { + "profile": "Profiel" + } + }, + "profile_stats": { + "title": "Profielstatistieke", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Skep nuwe profiel", + "description": "Skep 'n nuwe profiel met die hand of uit 'n vorige siklus.\n\nVoorbeelde van profielnaam: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Profiel Naam", + "reference_cycle": "Verwysingsiklus (opsioneel)", + "manual_duration": "Handmatige duur (minute)" + } + }, + "edit_profile": { + "title": "Wysig profiel", + "description": "Kies 'n profiel om te hernoem", + "data": { + "profile": "Profiel" + } + }, + "rename_profile": { + "title": "Wysig profielbesonderhede", + "description": "Huidige profiel: {current_name}", + "data": { + "new_name": "Profiel Naam", + "manual_duration": "Handmatige duur (minute)" + } + }, + "delete_profile_select": { + "title": "Vee profiel uit", + "description": "Kies 'n profiel om uit te vee", + "data": { + "profile": "Profiel" + } + }, + "delete_profile_confirm": { + "title": "Bevestig verwyder profiel", + "description": "⚠️ Dit sal profiel permanent uitvee: {profile_name}", + "data": { + "unlabel_cycles": "Verwyder etiket uit siklusse deur hierdie profiel te gebruik" + } + }, + "auto_label_cycles": { + "title": "Outo-etiketteer ou siklusse", + "description": "Het {total_count} totale siklusse gevind. Profiele: {profiles}", + "data": { + "confidence_threshold": "Minimum vertroue (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Kies Siklus om te Etiketeer", + "description": "✓ = voltooi, ⚠ = hervat, ✗ = onderbreek", + "data": { + "cycle_id": "Siklus" + } + }, + "select_cycle_to_delete": { + "title": "Vee siklus uit", + "description": "⚠️ Dit sal die geselekteerde siklus permanent uitvee", + "data": { + "cycle_id": "Blaai om uit te vee" + } + }, + "label_cycle": { + "title": "Etiketsiklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profiel", + "new_profile_name": "Nuwe profielnaam (indien geskep)" + } + }, + "post_process": { + "title": "Prosesgeskiedenis (samevoeg/verdeel)", + "description": "Maak siklusgeskiedenis skoon deur gefragmenteerde lopies saam te voeg en verkeerd saamgevoegde siklusse binne 'n tydvenster te verdeel. Voer die aantal ure in om terug te kyk (gebruik 999999 vir almal).", + "data": { + "time_range": "Terugkykvenster (ure)", + "gap_seconds": "Voeg saam/Verdeel gaping (sekondes)" + }, + "data_description": { + "time_range": "Aantal ure om te skandeer vir gefragmenteerde siklusse om saam te smelt. Gebruik 999999 om alle historiese data te verwerk.", + "gap_seconds": "Drempel om te besluit split vs samesmelting. Gapings KORTER as dit word saamgevoeg. Gapings LANGER as dit word verdeel. Verlaag dit om 'n verdeling af te dwing op 'n siklus wat verkeerd saamgevoeg is." + } + }, + "cleanup_profile": { + "title": "Maak geskiedenis skoon - Kies profiel", + "description": "Kies 'n profiel om te visualiseer. Dit sal 'n grafiek genereer wat alle vorige siklusse vir hierdie profiel wys om uitskieters te help identifiseer.", + "data": { + "profile": "Profiel" + } + }, + "cleanup_select": { + "title": "Maak geskiedenis skoon - grafiek en vee uit", + "description": "![Grafiek]({graph_url})\n\n**Visualisering van {profile_name}**\n\nDie grafiek toon individuele siklusse as gekleurde lyne. Identifiseer uitskieters (lyne ver van die groep) en pas hul kleur in die lys hieronder om hulle uit te vee.\n\n**Kies siklusse om PERMANENT uit te vee:**", + "data": { + "cycles_to_delete": "Siklusse om uit te vee (kontroleer bypassende kleure)" + } + }, + "interactive_editor": { + "title": "Voeg saam / verdeel interaktiewe redigeerder", + "description": "Kies 'n handmatige aksie om op jou siklusgeskiedenis uit te voer.", + "menu_options": { + "editor_split": "Verdeel 'n siklus (Vind gapings)", + "editor_merge": "Voeg siklusse saam (Sluit fragmente aan)", + "editor_delete": "Vee siklus(s) uit", + "menu_back": "← Terug" + } + }, + "editor_select": { + "title": "Kies Siklusse", + "description": "Kies die siklus(se) om te verwerk.\n\n{info_text}", + "data": { + "selected_cycles": "Siklusse" + } + }, + "editor_configure": { + "title": "Konfigureer en pas toe", + "description": "{preview_md}", + "data": { + "confirm_action": "Aksie", + "merged_profile": "Resulterende profiel", + "new_profile_name": "Nuwe profielnaam", + "confirm_commit": "Ja, ek bevestig hierdie aksie", + "segment_0_profile": "Segment 1-profiel", + "segment_1_profile": "Segment 2-profiel", + "segment_2_profile": "Segment 3-profiel", + "segment_3_profile": "Segment 4 Profiel", + "segment_4_profile": "Segment 5-profiel", + "segment_5_profile": "Segment 6 Profiel", + "segment_6_profile": "Segment 7 Profiel", + "segment_7_profile": "Segment 8 Profiel", + "segment_8_profile": "Segment 9 Profiel", + "segment_9_profile": "Segment 10 Profiel" + } + }, + "editor_split_params": { + "title": "Verdeel konfigurasie", + "description": "Pas die sensitiwiteit aan om hierdie siklus te verdeel. Enige ledige gaping langer as dit sal 'n skeuring veroorsaak.", + "data": { + "split_mode": "Verdelingsmetode" + } + }, + "editor_split_auto_params": { + "title": "Outomatiese verdeel-opstelling", + "description": "Pas die sensitiwiteit aan vir die verdeling van hierdie siklus. Enige wagtyd langer as dit sal 'n verdeling veroorsaak.", + "data": { + "min_gap_seconds": "Verdeling-gapingdrempel (sekondes)" + } + }, + "editor_split_manual_params": { + "title": "Handmatige verdeel-tydstempels", + "description": "{preview_md}Siklusvenster: **{cycle_start_wallclock} → {cycle_end_wallclock}** op {cycle_date}.\n\nVoer een of meer verdeel-tydstempels in (`HH:MM` of `HH:MM:SS`), een per reël. Die siklus sal by elke tydstempel gesny word — N tydstempels lewer N+1 segmente.", + "data": { + "split_timestamps": "Verdeel-tydstempels" + } + }, + "reprocess_history": { + "title": "Onderhoud: Herverwerk en optimaliseer data", + "description": "Voer diep skoonmaak van historiese data uit:\n1. Sny nulkraglesings (stilte).\n2. Stel siklustydsberekening/-duur reg.\n3. Herbereken alle statistiese modelle (koeverte).\n\n**Laat dit hardloop om datakwessies reg te stel of nuwe algoritmes toe te pas.**" + }, + "wipe_history": { + "title": "Vee geskiedenis uit (slegs toets)", + "description": "⚠️ Dit sal ALLE gestoorde siklusse en profiele vir hierdie toestel permanent uitvee. Dit kan nie ongedaan gemaak word nie!" + }, + "export_import": { + "title": "Voer JSON uit/voer in", + "description": "Kopieer/plak siklusse, profiele EN instellings vir hierdie toestel. Voer hieronder uit of plak JSON om in te voer.", + "data": { + "mode": "Aksie", + "json_payload": "Voltooi konfigurasie JSON" + }, + "data_description": { + "mode": "Kies Uitvoer om huidige data en instellings te kopieer, of Invoer om uitgevoerde data vanaf 'n ander WashData-toestel te plak.", + "json_payload": "Vir uitvoer: kopieer hierdie JSON (sluit siklusse, profiele en alle verfynde instellings in). Vir invoer: plak JSON wat vanaf WashData uitgevoer is." + } + }, + "record_cycle": { + "title": "Rekordsiklus", + "description": "Teken 'n siklus handmatig op om 'n skoon profiel sonder inmenging te skep.\n\nStatus: **{status}**\nTydsduur: {duration}s\nVoorbeelde: {samples}", + "menu_options": { + "record_refresh": "Herlaai status", + "record_stop": "Stop opname (stoor en verwerk)", + "record_start": "Begin nuwe opname", + "record_process": "Verwerk laaste opname (sny en stoor)", + "record_discard": "Gooi laaste opname weg", + "menu_back": "← Terug" + } + }, + "record_process": { + "title": "Proses opname", + "description": "![Grafiek]({graph_url})\n\n**Grafiek wys rou opname.** Blou = Hou, Rooi = Voorgestelde snoei.\n*Grafiek is staties en werk nie op met vormveranderings nie.*\n\nOpname het gestop. Trims is in lyn met die bespeurde steekproeftempo (~{sampling_rate}s).\n\nOnbewerkte duur: {duration}s\nVoorbeelde: {samples}", + "data": { + "head_trim": "Trim Begin (sekondes)", + "tail_trim": "Knip einde (sekondes)", + "save_mode": "Stoor bestemming", + "profile_name": "Profiel Naam" + } + }, + "learning_feedbacks": { + "title": "Leer Terugvoer", + "description": "Hangende terugvoer: {count}\n\n{pending_table}\n\nKies 'n siklushersieningsversoek om te verwerk.", + "menu_options": { + "learning_feedbacks_pick": "Hersien hangende terugvoer", + "learning_feedbacks_dismiss_all": "Wys alle hangende terugvoer af", + "menu_back": "← Terug" + } + }, + "learning_feedbacks_pick": { + "title": "Kies terugvoer om te hersien", + "description": "Kies 'n siklus-hersieningsversoek om oop te maak.", + "data": { + "selected_feedback": "Kies siklus om te hersien" + } + }, + "learning_feedbacks_empty": { + "title": "Leer Terugvoer", + "description": "Geen hangende terugvoerversoeke gevind nie.", + "menu_options": { + "menu_back": "← Terug" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Wys alle hangende terugvoere af", + "description": "Jy is op die punt om **{count}** hangende terugvoerversoek(e) af te wys. Afgewysde siklusse bly in jou geskiedenis, maar sal nie nuwe leersignaal bydra nie. Dit kan nie ongedaan gemaak word nie.\n\n✅ Merk die blokkie hieronder en klik **Dien in** om alles af te wys.\n❌ Laat dit ongemerk en klik **Dien in** om te kanselleer en terug te gaan.", + "data": { + "confirm_dismiss_all": "Bevestig: wys alle hangende terugvoerversoeke af" + } + }, + "resolve_feedback": { + "title": "Los terugvoer op", + "description": "{comparison_img}{candidates_table}\n**Bespeurde profiel**: {detected_profile} ({confidence_pct}%)\n**Geskatte duur**: {est_duration_min} min\n**Werklike duur**: {act_duration_min} min\n\n__Kies 'n handeling hieronder:__", + "data": { + "action": "Wat sou jy graag wou doen?", + "corrected_profile": "Korrekte program (indien reggestel)", + "corrected_duration": "Korrekte duur in sekondes (indien reggestel)" + } + }, + "trim_cycle_select": { + "title": "Snoei Siklus - Kies Siklus", + "description": "Kies 'n siklus om te snoei. ✓ = voltooi, ⚠ = hervat, ✗ = onderbreek", + "data": { + "cycle_id": "Siklus" + } + }, + "trim_cycle": { + "title": "Snoei siklus", + "description": "Huidige venster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aksie" + } + }, + "trim_cycle_start": { + "title": "Stel Snoeibegin", + "description": "Siklusvenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nHuidige begin: **{current_wallclock}** (+{current_offset_min} min vanaf siklusbegin)\n\nKies 'n nuwe begintyd deur die horlosiekieser hieronder te gebruik.", + "data": { + "trim_start_time": "Nuwe begin tyd", + "trim_start_min": "Nuwe begin (minute vanaf siklus begin)" + } + }, + "trim_cycle_end": { + "title": "Stel Snoei-einde", + "description": "Siklusvenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nHuidige einde: **{current_wallclock}** (+{current_offset_min} min vanaf siklusbegin)\n\nKies 'n nuwe eindtyd deur die klokkieser hieronder te gebruik.", + "data": { + "trim_end_time": "Nuwe eindtyd", + "trim_end_min": "Nuwe einde (minute vanaf siklus begin)" + } + } + }, + "error": { + "import_failed": "Invoer het misluk. Plak asseblief 'n geldige WashData-uitvoer-JSON.", + "select_exactly_one": "Kies presies een siklus vir hierdie handeling.", + "select_at_least_one": "Kies ten minste een siklus vir hierdie handeling.", + "select_at_least_two": "Kies ten minste twee siklusse vir hierdie handeling.", + "profile_exists": "'n Profiel met hierdie naam bestaan ​​reeds", + "rename_failed": "Kon nie profiel hernoem nie. Profiel bestaan ​​dalk nie of nuwe naam is reeds geneem.", + "assignment_failed": "Kon nie profiel aan siklus toewys nie", + "duplicate_phase": "'n Fase met hierdie naam bestaan ​​reeds.", + "phase_not_found": "Geselekteerde fase is nie gevind nie.", + "invalid_phase_name": "Fasenaam kan nie leeg wees nie.", + "phase_name_too_long": "Fasenaam is te lank.", + "invalid_phase_range": "Fasereeks is ongeldig. Einde moet groter as begin wees.", + "invalid_phase_timestamp": "Tydstempel is ongeldig. Gebruik JJJJ-MM-DD UU:MM of UU:MM-formaat.", + "overlapping_phase_ranges": "Fasereekse oorvleuel. Pas die reekse aan en probeer weer.", + "incomplete_phase_row": "Elke fasery moet 'n fase plus volledige begin/eindwaardes vir die geselekteerde modus insluit.", + "timestamp_mode_cycle_required": "Kies asseblief 'n bronsiklus wanneer jy tydstempelmodus gebruik.", + "no_phase_ranges": "Geen fasereekse is nog vir daardie aksie beskikbaar nie.", + "feedback_notification_title": "Wasdata: Verifieer siklus ({device})", + "feedback_notification_message": "**Toestel**: {device}\n**Program**: {program} ({confidence}% vertroue)\n**Tyd**: {time}\n\nWashData het jou hulp nodig om hierdie bespeurde siklus te verifieer.\n\nGaan asseblief na **Instellings > Toestelle en Dienste > Wasdata > Konfigureer > Leerterugvoere** om hierdie resultaat te bevestig of reg te stel.", + "suggestions_ready_notification_title": "WashData: Voorgestelde instellings gereed ({device})", + "suggestions_ready_notification_message": "Die **Voorgestelde instellings**-sensor rapporteer nou **{count}** uitvoerbare aanbevelings.\n\nOm dit te hersien en toe te pas: **Instellings > Toestelle en Dienste > Wasdata > Konfigureer > Gevorderde instellings > Pas voorgestelde waardes toe**.\n\nVoorstelle is opsioneel en gewys vir hersiening voordat jy stoor.", + "trim_range_invalid": "Begin moet voor einde wees. Pas asseblief jou snoeipunte aan.", + "trim_failed": "Sny het misluk. Die siklus bestaan ​​dalk nie meer nie of die venster is leeg.", + "invalid_split_timestamp": "Tydstempel is ongeldig of buite die siklusvenster. Gebruik HH:MM of HH:MM:SS binne die siklusvenster.", + "no_split_segments_found": "Geen geldige segmente kon uit hierdie tydstempels geskep word nie. Maak seker elke tydstempel val binne die siklusvenster en segmente is minstens 1 minuut lank.", + "auto_tune_suggestion": "{device_type} {device_title} het spooksiklusse bespeur. Voorgestelde min_kragverandering: {current_min}W -> {new_min}W (nie outomaties toegepas nie).", + "auto_tune_title": "WashData Outo-Tune", + "auto_tune_fallback": "{device_type} {device_title} het spooksiklusse bespeur. Voorgestelde min_kragverandering: {current_min}W -> {new_min}W (nie outomaties toegepas nie)." + }, + "abort": { + "no_cycles_found": "Geen siklusse gevind nie", + "no_split_segments_found": "Geen verdeelbare segmente is in die gekose siklus gevind nie.", + "cycle_not_found": "Die gekose siklus kon nie gelaai word nie.", + "no_power_data": "Geen kragspoordata beskikbaar vir die geselekteerde siklus nie.", + "no_profiles_found": "Geen profiele gevind nie. Skep eers 'n profiel.", + "no_profiles_for_matching": "Geen profiele beskikbaar vir passing nie. Skep eers profiele.", + "no_unlabeled_cycles": "Alle siklusse is reeds gemerk", + "no_suggestions": "Nog geen voorgestelde waardes beskikbaar nie. Hardloop 'n paar siklusse en probeer weer.", + "no_predictions": "Geen voorspellings nie", + "no_custom_phases": "Geen pasgemaakte fases gevind nie.", + "reprocess_success": "Het {count} siklusse suksesvol herverwerk.", + "debug_data_cleared": "Het ontfoutdata van {count} siklusse suksesvol uitgevee." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Siklus begin", + "cycle_finish": "Siklus voltooi", + "cycle_live": "Regstreekse vordering" + } + }, + "common_text": { + "options": { + "unlabeled": "(Ongemerk)", + "create_new_profile": "Skep nuwe profiel", + "remove_label": "Verwyder etiket", + "no_reference_cycle": "(Geen verwysingsiklus nie)", + "all_device_types": "Alle toesteltipes", + "current_suffix": "(huidige)", + "editor_select_info": "Kies 1 siklus om te verdeel, of 2+ siklusse om saam te voeg.", + "editor_select_info_split": "Kies 1 siklus om te verdeel.", + "editor_select_info_merge": "Kies 2+ siklusse om saam te voeg.", + "editor_select_info_delete": "Kies 1+ siklus(se) om te skrap.", + "no_cycles_recorded": "Geen siklusse is nog aangeteken nie.", + "profile_summary_line": "- **{name}**: {count} siklusse, {avg}m gem", + "no_profiles_created": "Geen profiele is nog geskep nie.", + "phase_preview": "Fasevoorskou", + "phase_preview_no_curve": "Gemiddelde profielkurwe is nog nie beskikbaar nie. Laat loop/benoem meer siklusse vir hierdie profiel.", + "average_power_curve": "Gemiddelde kragkurwe", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} siklusse, ~{duration}m gemiddeld)", + "profile_option_short_fmt": "{name} ({count} siklusse, ~{duration}m)", + "deleted_cycles_from_profile": "Het {count} siklusse van {profile} uitgevee.", + "not_enough_data_graph": "Nie genoeg data om grafiek te genereer nie.", + "no_profiles_found": "Geen profiele gevind nie.", + "cycle_info_fmt": "Siklus: {start}, {duration}m, stroom: {label}", + "top_candidates_header": "### Topkandidate", + "tbl_profile": "Profiel", + "tbl_confidence": "Vertroue", + "tbl_correlation": "Korrelasie", + "tbl_duration_match": "Tydswedstryd", + "detected_profile": "Bespeur profiel", + "estimated_duration": "Geskatte duur", + "actual_duration": "Werklike Duur", + "choose_action": "__Kies 'n handeling hieronder:__", + "tbl_count": "Tel", + "tbl_avg": "Gem", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energie (Gem.)", + "tbl_energy_total": "Energie (totaal)", + "tbl_consistency": "Bestaan.", + "tbl_last_run": "Laaste hardloop", + "graph_legend_title": "Grafieklegende", + "graph_legend_body": "Die blou band verteenwoordig die minimum en maksimum krag trekking reeks waargeneem. Die lyn wys die gemiddelde drywingskromme.", + "recording_preview": "Opnamevoorskou", + "trim_start": "Trim Begin", + "trim_end": "Sny Einde", + "split_preview_title": "Verdeel voorskou", + "split_preview_found_fmt": "Het {count} segmente gevind.", + "split_preview_confirm_fmt": "Klik Bevestig om hierdie siklus in {count} afsonderlike siklusse te verdeel.", + "merge_preview_title": "Voeg voorskou saam", + "merge_preview_joining_fmt": "Sluit tans aan by {count} siklusse. Leemtes sal gevul word met 0W lesings.", + "editor_delete_preview_title": "Skrapvoorskou", + "editor_delete_preview_intro": "Die gekose siklusse sal permanent geskrap word:", + "editor_delete_preview_confirm": "Klik Bevestig om hierdie siklusrekords permanent te skrap.", + "no_power_preview": "*Geen kragdata beskikbaar vir voorskou nie.*", + "profile_comparison": "Profielvergelyking", + "actual_cycle_label": "Hierdie siklus (werklik)", + "storage_usage_header": "Berginggebruik", + "storage_file_size": "Lêergrootte", + "storage_cycles": "Siklusse", + "storage_profiles": "Profiele", + "storage_debug_traces": "Ontfout spore", + "overview_suffix": "Oorsig", + "phase_preview_for_profile": "Fasevoorskou vir profiel", + "current_ranges_header": "Huidige reekse", + "assign_phases_help": "Gebruik handelinge hieronder om reekse by te voeg, te wysig, uit te vee of te stoor.", + "profile_summary_header": "Profielopsomming", + "recent_cycles_header": "Onlangse siklusse", + "trim_cycle_preview_title": "Siklus Trim Voorskou", + "trim_cycle_preview_no_data": "Geen kragdata beskikbaar vir hierdie siklus nie.", + "trim_cycle_preview_kept_suffix": "gehou", + "table_program": "Program", + "table_when": "Wanneer", + "table_length": "Lengte", + "table_match": "Ooreenstemming", + "table_profile": "Profiel", + "table_cycles": "Siklusse", + "table_avg_length": "Gem. lengte", + "table_last_run": "Laaste hardloop", + "table_avg_energy": "Gem. energie", + "table_detected_program": "Bespeurde program", + "table_confidence": "Vertroue", + "table_reported": "Gerapporteer", + "phase_builtin_suffix": "(Ingeboude)", + "phase_none_available": "Geen fases beskikbaar nie.", + "settings_deprecation_warning": "⚠️ **Verouderde toesteltipe:** {device_type} is geskeduleer vir verwydering in 'n toekomstige vrystelling. WashData se bypassende pyplyn lewer nie betroubare resultate vir hierdie toestelklas nie. Jou integrasie bly deur die afskaffingstydperk werk; om hierdie waarskuwing stil te maak, skakel **Toesteltipe** hieronder na een van die ondersteunde tipes (wasmasjien, droër, wasmasjien-droër-kombinasie, skottelgoedwasser, lugbraaier, broodmaker of pomp), of na **Ander (Gevorderd)** as jou toestel nie by enige van die ondersteunde tipes pas nie. **Ander (Gevorderd)** stuur opsetlik generiese verstekwaardes wat nie vir enige spesifieke toestel ingestel is nie, so jy sal self drempels, uitteltyd en bypassende parameters moet opstel; al jou bestaande instellings word bewaar wanneer jy oorskakel.", + "phase_other_device_types": "Ander tipes toestel:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Verdeel 'n siklus (Vind gapings)", + "merge": "Voeg siklusse saam (Sluit fragmente aan)", + "delete": "Vee siklus(s) uit" + } + }, + "split_mode": { + "options": { + "auto": "Outo-bespeur ledige gapings", + "manual": "Handmatige tydstempel(s)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Onderhoud: Herverwerk en optimaliseer data", + "clear_debug_data": "Vee ontfoutdata uit (maak spasie vry)", + "wipe_history": "Vee ALLE data vir hierdie toestel uit (onomkeerbaar)", + "export_import": "Voer JSON uit/voer in met instellings (kopieer/plak)" + } + }, + "export_import_mode": { + "options": { + "export": "Voer alle data uit", + "import": "Voer data in/voeg saam" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Outo-etiketteer ou siklusse", + "select_cycle_to_label": "Benoem Spesifieke Siklus", + "select_cycle_to_delete": "Vee siklus uit", + "interactive_editor": "Voeg saam / verdeel interaktiewe redigeerder", + "trim_cycle": "Knip siklusdata" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Skep nuwe profiel", + "edit_profile": "Wysig/hernoem profiel", + "delete_profile": "Vee profiel uit", + "profile_stats": "Profielstatistieke", + "cleanup_profile": "Maak geskiedenis skoon - grafiek en vee uit", + "assign_phases": "Ken fasereekse toe" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Skep nuwe fase", + "edit_custom_phase": "Wysig fase", + "delete_custom_phase": "Vee Fase uit" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Voeg fasereeks by", + "edit_range": "Wysig fasereeks", + "delete_range": "Vee fasereeks uit", + "clear_ranges": "Vee alle reekse uit", + "auto_detect_ranges": "Bespeur fases outomaties", + "save_ranges": "Stoor en keer terug" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Herlaai status", + "stop_recording": "Stop opname (stoor en verwerk)", + "start_recording": "Begin nuwe opname", + "process_recording": "Verwerk laaste opname (sny en stoor)", + "discard_recording": "Gooi laaste opname weg" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Skep nuwe profiel", + "existing_profile": "Voeg by bestaande profiel", + "discard": "Gooi opname weg" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bevestig - Korrekte opsporing", + "correct": "Korrek - Kies program/duur", + "ignore": "Ignoreer - Vals Positief/Lawaaierige Siklus", + "delete": "Vee uit - Verwyder uit geskiedenis" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Noem en pas fases toe", + "cancel": "Gaan terug sonder veranderinge" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Stel Beginpunt", + "set_end": "Stel Eindpunt", + "reset": "Stel terug na volle duur", + "apply": "Pas Trim toe", + "cancel": "Kanselleer" + } + }, + "device_type": { + "options": { + "washing_machine": "Wasmasjien", + "dryer": "Droër", + "washer_dryer": "Wasmasjien-droër kombinasie", + "dishwasher": "Skottelgoedwasser", + "coffee_machine": "Koffiemasjien (verouderd)", + "ev": "Elektriese voertuig (opgeskort)", + "air_fryer": "Air Fryer", + "heat_pump": "Hittepomp (verwerp)", + "bread_maker": "Broodmaker", + "pump": "Pomp / Sump Pomp", + "oven": "Oond (verouderd)", + "other": "Ander (gevorderd)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiketsiklus", + "description": "Ken 'n bestaande profiel aan 'n vorige siklus toe, of verwyder die etiket.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel te etiketteer." + }, + "cycle_id": { + "name": "Siklus ID", + "description": "Die ID van die siklus om te etiketteer." + }, + "profile_name": { + "name": "Profiel Naam", + "description": "Die naam van 'n bestaande profiel (skep profiele in Bestuur profiele-kieslys). Laat leeg om etiket te verwyder." + } + } + }, + "create_profile": { + "name": "Skep profiel", + "description": "Skep 'n nuwe profiel (selfstandig of gebaseer op 'n verwysingsiklus).", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel." + }, + "profile_name": { + "name": "Profiel Naam", + "description": "Naam vir die nuwe profiel (bv. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Verwysingsiklus ID", + "description": "Opsionele siklus-ID om hierdie profiel op te baseer." + } + } + }, + "delete_profile": { + "name": "Vee profiel uit", + "description": "Vee 'n profiel uit en ontmerk siklusse opsioneel deur dit te gebruik.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel." + }, + "profile_name": { + "name": "Profiel Naam", + "description": "Die profiel om uit te vee." + }, + "unlabel_cycles": { + "name": "Ontmerk siklusse", + "description": "Verwyder profieletiket uit siklusse deur hierdie profiel te gebruik." + } + } + }, + "auto_label_cycles": { + "name": "Outo-etiketteer ou siklusse", + "description": "Benoem ongemerkte siklusse terugwerkend deur profielpassing te gebruik.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel." + }, + "confidence_threshold": { + "name": "Vertrouensdrempel", + "description": "Minimum wedstrydvertroue (0.50-0.95) om etikette toe te pas." + } + } + }, + "export_config": { + "name": "Uitvoer Konfigurasie", + "description": "Voer hierdie toestel se profiele, siklusse en instellings uit na 'n JSON-lêer (per toestel).", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel om uit te voer." + }, + "path": { + "name": "Pad", + "description": "Opsionele absolute lêerpad om te skryf (verstek na /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Voer konfigurasie in", + "description": "Voer profiele, siklusse en instellings vir hierdie toestel in vanaf 'n JSON-uitvoerlêer (instellings kan dalk oorgeskryf word).", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel om in te voer." + }, + "path": { + "name": "Pad", + "description": "Absolute pad na die uitvoer JSON-lêer om in te voer." + } + } + }, + "submit_cycle_feedback": { + "name": "Dien siklusterugvoer in", + "description": "Bevestig of korrigeer 'n outomaties bespeurde program na 'n voltooide siklus. Verskaf óf `entry_id` (gevorderd) of `device_id` (aanbeveel).", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die WashData-toestel (aanbeveel in plaas van entry_id)." + }, + "entry_id": { + "name": "Inskrywing ID", + "description": "Die config-invoer-ID vir die toestel (alternatief vir device_id)." + }, + "cycle_id": { + "name": "Siklus ID", + "description": "Die siklus-ID wat in die terugvoerkennisgewing / logs gewys word." + }, + "user_confirmed": { + "name": "Bevestig opgespoorde program", + "description": "Stel waar as die bespeurde program korrek is." + }, + "corrected_profile": { + "name": "Gekorrigeerde profiel", + "description": "Indien nie bevestig nie, verskaf die korrekte profiel/programnaam." + }, + "corrected_duration": { + "name": "Korrigeerde duur (sekondes)", + "description": "Opsionele gekorrigeerde duur in sekondes." + }, + "notes": { + "name": "Notas", + "description": "Opsionele notas oor hierdie siklus." + } + } + }, + "record_start": { + "name": "Rekordsiklus begin", + "description": "Begin om 'n skoon siklus met die hand op te neem (omseil alle ooreenstemmende logika).", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel om aan te teken." + } + } + }, + "record_stop": { + "name": "Rekordsiklus stop", + "description": "Stop handmatige opname.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die wasmasjien toestel om opname te stop." + } + } + }, + "trim_cycle": { + "name": "Trim siklus", + "description": "Sny die kragdata van 'n vorige siklus na 'n spesifieke tydvenster.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die WashData-toestel." + }, + "cycle_id": { + "name": "Siklus ID", + "description": "Die ID van die siklus om te snoei." + }, + "trim_start_s": { + "name": "Trim Begin (sekondes)", + "description": "Hou data van hierdie afwyking binne sekondes. Verstek 0." + }, + "trim_end_s": { + "name": "Knip einde (sekondes)", + "description": "Hou data binne sekondes tot by hierdie afwyking. Default = full duration." + } + } + }, + "pause_cycle": { + "name": "Onderbreek siklus", + "description": "Onderbreek die aktiewe siklus vir 'n WashData-toestel.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die WashData-toestel om te onderbreek." + } + } + }, + "resume_cycle": { + "name": "Hervat siklus", + "description": "Hervat 'n onderbreekte siklus vir 'n WashData-toestel.", + "fields": { + "device_id": { + "name": "Toestel", + "description": "Die WashData-toestel om te hervat." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Hardloop" + }, + "match_ambiguity": { + "name": "Pas dubbelsinnigheid by" + } + }, + "sensor": { + "washer_state": { + "name": "Staat", + "state": { + "off": "Af", + "idle": "Ledig", + "starting": "Begin", + "running": "Hardloop", + "paused": "Onderbreek", + "user_paused": "Gepauseer deur gebruiker", + "ending": "Einde", + "finished": "Klaar", + "anti_wrinkle": "Anti-rimpel", + "delay_wait": "Wag om te begin", + "interrupted": "Onderbreek", + "force_stopped": "Geforseer gestop", + "rinse": "Spoel af", + "unknown": "Onbekend", + "clean": "Skoon" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Tyd wat oorbly" + }, + "total_duration": { + "name": "Totale duur" + }, + "cycle_progress": { + "name": "Vordering" + }, + "current_power": { + "name": "Huidige krag" + }, + "elapsed_time": { + "name": "Verstreke Tyd" + }, + "debug_info": { + "name": "Ontfout inligting" + }, + "match_confidence": { + "name": "Pas Vertroue" + }, + "top_candidates": { + "name": "Top kandidate", + "state": { + "none": "Geen" + } + }, + "current_phase": { + "name": "Huidige fase" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Profiel {profile_name} Tel", + "unit_of_measurement": "siklusse" + }, + "suggestions": { + "name": "Voorgestelde instellings" + }, + "pump_runs_today": { + "name": "Pomp loop (laaste 24 uur)" + }, + "cycle_count": { + "name": "Siklus telling" + } + }, + "select": { + "program_select": { + "name": "Siklus Program", + "state": { + "auto_detect": "Outo-bespeur" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forseer eindsiklus" + }, + "pause_cycle": { + "name": "Onderbreek siklus" + }, + "resume_cycle": { + "name": "Hervat siklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "'n Geldige toestel-ID word vereis." + }, + "cycle_id_required": { + "message": "'n Geldige cycle_id word vereis." + }, + "profile_name_required": { + "message": "'n Geldige profile_name word vereis." + }, + "device_not_found": { + "message": "Toestel nie gevind nie." + }, + "no_config_entry": { + "message": "Geen konfigurasie-inskrywing vir toestel gevind nie." + }, + "integration_not_loaded": { + "message": "Integrasie nie vir hierdie toestel gelaai nie." + }, + "cycle_not_found_or_no_power": { + "message": "Siklus nie gevind nie of het geen kragdata nie." + }, + "trim_failed_empty_window": { + "message": "Snoei het misluk – siklus nie gevind nie, geen kragdata nie, of die gevolglike venster is leeg." + }, + "trim_invalid_range": { + "message": "trim_end_s moet groter as trim_start_s wees." + } + } +} diff --git a/custom_components/ha_washdata/translations/ar.json b/custom_components/ha_washdata/translations/ar.json new file mode 100644 index 0000000..7126569 --- /dev/null +++ b/custom_components/ha_washdata/translations/ar.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "إعداد بيانات الغسيل", + "description": "قم بتكوين غسالتك أو أي جهاز آخر.\n\nمطلوب جهاز استشعار الطاقة.\n\n**بعد ذلك، سيتم سؤالك عما إذا كنت تريد إنشاء ملفك الشخصي الأول.**", + "data": { + "name": "اسم الجهاز", + "device_type": "نوع الجهاز", + "power_sensor": "مستشعر الطاقة", + "min_power": "الحد الأدنى لعتبة الطاقة (W)" + }, + "data_description": { + "name": "اسم مألوف لهذا الجهاز (على سبيل المثال، \"غسالة\"، \"غسالة أطباق\").", + "device_type": "ما نوع هذا الجهاز؟ يساعد على تخصيص الكشف ووضع العلامات.", + "power_sensor": "كيان المستشعر الذي يقوم بالإبلاغ عن استهلاك الطاقة في الوقت الفعلي (بالواط) من القابس الذكي الخاص بك.", + "min_power": "تشير قراءات الطاقة فوق هذا الحد (بالواط) إلى أن الجهاز قيد التشغيل. ابدأ بـ 2 وات لمعظم الأجهزة." + } + }, + "first_profile": { + "title": "إنشاء الملف الشخصي الأول", + "description": "**الدورة** هي عملية تشغيل كاملة لجهازك (على سبيل المثال، كمية من الغسيل). يقوم **الملف الشخصي** بتجميع هذه الدورات حسب النوع (على سبيل المثال، \"القطن\"، \"الغسيل السريع\"). يساعد إنشاء ملف تعريف الآن على تقدير مدة التكامل على الفور.\n\nيمكنك تحديد ملف التعريف هذا يدويًا في عناصر التحكم بالجهاز إذا لم يتم اكتشافه تلقائيًا.\n\n**اترك \"اسم الملف الشخصي\" فارغًا لتخطي هذه الخطوة.**", + "data": { + "profile_name": "اسم الملف الشخصي (اختياري)", + "manual_duration": "المدة المقدرة (بالدقائق)" + } + } + }, + "error": { + "cannot_connect": "فشل الاتصال", + "invalid_auth": "مصادقة غير صالحة", + "unknown": "خطأ غير متوقع" + }, + "abort": { + "already_configured": "تم تكوين الجهاز بالفعل" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "مراجعة التغذية الراجعة للتعلم", + "settings": "إعدادات", + "notifications": "إشعارات", + "manage_cycles": "إدارة الدورات", + "manage_profiles": "إدارة الملفات الشخصية", + "manage_phase_catalog": "إدارة كتالوج المرحلة", + "record_cycle": "دورة التسجيل (يدوية)", + "diagnostics": "التشخيص والصيانة" + } + }, + "apply_suggestions": { + "title": "انسخ القيم المقترحة", + "description": "سيؤدي هذا إلى نسخ القيم المقترحة الحالية إلى إعداداتك المحفوظة. يعد هذا إجراءً لمرة واحدة ولا يتيح إمكانية الكتابة التلقائية.\n\nالقيم المقترحة للنسخ:\n{suggested}", + "data": { + "confirm": "تأكيد إجراء النسخ" + } + }, + "apply_suggestions_confirm": { + "title": "قم بمراجعة التغييرات المقترحة", + "description": "عثرت WashData على **{pending_count}** القيمة (القيم) المقترحة لتطبيقها.\n\nالتغييرات:\n{changes}\n\n✅ حدد المربع أدناه وانقر فوق **إرسال** لتطبيق هذه التغييرات وحفظها على الفور.\n❌ اتركه بدون تحديد وانقر على **إرسال** للإلغاء والعودة.", + "data": { + "confirm_apply_suggestions": "تطبيق وحفظ هذه التغييرات" + } + }, + "settings": { + "title": "إعدادات", + "description": "{deprecation_warning}ضبط عتبات الكشف والتعلم والسلوك المتقدم. تعمل الإعدادات الافتراضية بشكل جيد مع معظم الإعدادات.\n\nالإعدادات المقترحة المتاحة حاليًا: {suggestions_count}.\nإذا كان هذا أعلى من 0، افتح الإعدادات المتقدمة واستخدم 'تطبيق القيم المقترحة' لمراجعة التوصيات.", + "data": { + "apply_suggestions": "تطبيق القيم المقترحة", + "device_type": "نوع الجهاز", + "power_sensor": "كيان استشعار الطاقة", + "min_power": "الحد الأدنى من الطاقة (W)", + "off_delay": "تأخير نهاية الدورة (ثواني)", + "notify_actions": "إجراءات الإخطار", + "notify_people": "تأخير التسليم حتى يعود هؤلاء الأشخاص إلى المنزل", + "notify_only_when_home": "تأخير الإخطارات حتى يعود الشخص المحدد إلى المنزل", + "notify_fire_events": "أحداث تشغيل الأتمتة", + "notify_start_services": "بداية الدورة - أهداف الإخطار", + "notify_finish_services": "إنهاء الدورة - أهداف الإخطار", + "notify_live_services": "التقدم المباشر - أهداف الإخطار", + "notify_before_end_minutes": "إشعار ما قبل الإكمال (قبل دقائق من النهاية)", + "notify_title": "عنوان الإخطار", + "notify_icon": "أيقونة الإخطار", + "notify_start_message": "بدء تنسيق الرسالة", + "notify_finish_message": "إنهاء تنسيق الرسالة", + "notify_pre_complete_message": "تنسيق رسالة ما قبل الإكمال", + "show_advanced": "تحرير الإعدادات المتقدمة (الخطوة التالية)" + }, + "data_description": { + "apply_suggestions": "حدد هذا المربع لنسخ القيم التي تقترحها خوارزمية التعلم في النموذج. المراجعة قبل الحفظ.\n\nمقترح (من التعلم):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "اختر نوع الجهاز (غسالة، مجفف، غسالة أطباق، ماكينة صنع القهوة، EV). يتم تخزين هذه العلامة مع كل دورة لتنظيم أفضل.", + "power_sensor": "يقوم كيان المستشعر بالإبلاغ عن الطاقة في الوقت الفعلي (بالواط). يمكنك تغيير هذا في أي وقت دون فقدان البيانات التاريخية، وهو أمر مفيد إذا قمت باستبدال القابس الذكي.", + "min_power": "تشير قراءات الطاقة فوق هذا الحد (بالواط) إلى أن الجهاز قيد التشغيل. ابدأ بـ 2 وات لمعظم الأجهزة.", + "off_delay": "الثواني، يجب أن تظل القوة الممهدة أقل من حد التوقف لوضع علامة على الاكتمال. الافتراضي: 120 ثانية.", + "notify_actions": "تسلسل إجراءات Home Assistant الاختياري لتشغيل الإشعارات (البرامج النصية، والإشعارات، وتحويل النص إلى كلام، وما إلى ذلك). في حالة التعيين، سيتم تشغيل الإجراءات قبل خدمة الإشعارات الاحتياطية.", + "notify_people": "الكيانات البشرية المستخدمة لبوابة الحضور. عند التمكين، يتم تأخير تسليم الإشعارات حتى يعود شخص واحد محدد على الأقل إلى المنزل.", + "notify_only_when_home": "إذا تم تمكينه، قم بتأخير الإشعارات حتى يعود شخص واحد محدد على الأقل إلى المنزل.", + "notify_fire_events": "في حالة التمكين، قم بتشغيل أحداث Home Assistant لبدء الدورة ونهايتها (ha_washdata_cycle_started وha_washdata_cycle_ended) لتشغيل عمليات التشغيل الآلي.", + "notify_start_services": "خدمة إخطار واحدة أو أكثر للتنبيه عند بدء الدورة. اتركه فارغًا لتخطي إشعارات البدء.", + "notify_finish_services": "تقوم واحدة أو أكثر من خدمات الإخطار بالتنبيه عند انتهاء الدورة أو اقترابها من الانتهاء. اتركه فارغًا لتخطي إشعارات الانتهاء.", + "notify_live_services": "تقوم خدمة واحدة أو أكثر بإخطار الخدمات لتلقي تحديثات التقدم المباشرة أثناء تشغيل الدورة (تطبيق الهاتف المصاحب فقط). اتركه فارغًا لتخطي التحديثات المباشرة.", + "notify_before_end_minutes": "دقائق قبل النهاية المقدرة للدورة لإطلاق إشعار. اضبط على 0 للتعطيل. الافتراضي: 0.", + "notify_title": "عنوان مخصص للإخطارات. يدعم العنصر النائب {device}.", + "notify_icon": "الرمز المطلوب استخدامه للإشعارات (على سبيل المثال، mdi:washing-machine). اتركه فارغًا لاستخدام الافتراضي.", + "notify_start_message": "يتم إرسال الرسالة عند بدء الدورة. يدعم العنصر النائب {device}.", + "notify_finish_message": "يتم إرسال الرسالة عند انتهاء الدورة. يدعم العناصر النائبة {device} و{duration} و{program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "نص تحديثات التقدم المباشر المتكررة أثناء تشغيل الدورة. يدعم العناصر النائبة {device} و{minutes} و{program}." + } + }, + "notifications": { + "title": "إشعارات", + "description": "قم بتكوين قنوات الإشعارات والقوالب وتحديثات تقدم الجوال المباشرة الاختيارية.", + "data": { + "notify_actions": "إجراءات الإخطار", + "notify_people": "تأخير التسليم حتى يعود هؤلاء الأشخاص إلى المنزل", + "notify_only_when_home": "تأخير الإخطارات حتى يعود الشخص المحدد إلى المنزل", + "notify_fire_events": "أحداث تشغيل الأتمتة", + "notify_start_services": "بداية الدورة - أهداف الإخطار", + "notify_finish_services": "إنهاء الدورة - أهداف الإخطار", + "notify_live_services": "التقدم المباشر - أهداف الإخطار", + "notify_before_end_minutes": "إشعار ما قبل الإكمال (قبل دقائق من النهاية)", + "notify_live_interval_seconds": "الفاصل الزمني للتحديث المباشر (ثواني)", + "notify_live_overrun_percent": "بدل تجاوز التحديث المباشر (%)", + "notify_live_chronometer": "الكرونومتر للعد التنازلي", + "notify_title": "عنوان الإخطار", + "notify_icon": "أيقونة الإخطار", + "notify_start_message": "بدء تنسيق الرسالة", + "notify_finish_message": "إنهاء تنسيق الرسالة", + "notify_pre_complete_message": "تنسيق رسالة ما قبل الإكمال", + "energy_price_entity": "سعر الطاقة - الكيان (اختياري)", + "energy_price_static": "سعر الطاقة - القيمة الثابتة (اختياري)", + "go_back": "العودة بدون حفظ", + "notify_reminder_message": "رسالة تذكيرية", + "notify_timeout_seconds": "بعد )الثانيات(", + "notify_channel": "قناة الإخطار (الحالة/العمر/الذكورة)", + "notify_finish_channel": "قناة الإخطار المنتهي" + }, + "data_description": { + "go_back": "حدد هذا ثم انقر فوق إرسال لتجاهل أي تغييرات أعلاه والعودة إلى القائمة الرئيسية.", + "notify_actions": "تسلسل إجراءات Home Assistant الاختياري لتشغيل الإشعارات (البرامج النصية، والإشعارات، وتحويل النص إلى كلام، وما إلى ذلك). في حالة التعيين، سيتم تشغيل الإجراءات قبل خدمة الإشعارات الاحتياطية.", + "notify_people": "الكيانات البشرية المستخدمة لبوابة الحضور. عند التمكين، يتم تأخير تسليم الإشعارات حتى يعود شخص واحد محدد على الأقل إلى المنزل.", + "notify_only_when_home": "إذا تم تمكينه، قم بتأخير الإشعارات حتى يعود شخص واحد محدد على الأقل إلى المنزل.", + "notify_fire_events": "في حالة التمكين، قم بتشغيل أحداث Home Assistant لبدء الدورة ونهايتها (ha_washdata_cycle_started وha_washdata_cycle_ended) لتشغيل عمليات التشغيل الآلي.", + "notify_start_services": "خدمة إخطار واحدة أو أكثر للتنبيه عند بدء الدورة (على سبيل المثال، notify.mobile_app_my_phone). اتركه فارغًا لتخطي إشعارات البدء.", + "notify_finish_services": "تقوم واحدة أو أكثر من خدمات الإخطار بالتنبيه عند انتهاء الدورة أو اقترابها من الانتهاء. اتركه فارغًا لتخطي إشعارات الانتهاء.", + "notify_live_services": "تقوم خدمة واحدة أو أكثر بإخطار الخدمات لتلقي تحديثات التقدم المباشرة أثناء تشغيل الدورة (تطبيق الهاتف المصاحب فقط). اتركه فارغًا لتخطي التحديثات المباشرة.", + "notify_before_end_minutes": "دقائق قبل النهاية المقدرة للدورة لإطلاق إشعار. اضبط على 0 للتعطيل. الافتراضي: 0.", + "notify_live_interval_seconds": "عدد مرات إرسال تحديثات التقدم أثناء التشغيل. تكون القيم الأقل أكثر استجابة ولكنها تستهلك المزيد من الإشعارات. يتم فرض 30 ثانية على الأقل - يتم تقريب القيم الأقل من 30 ثانية تلقائيًا إلى 30 ثانية.", + "notify_live_overrun_percent": "هامش أمان أعلى من عدد التحديثات المقدر للدورات الطويلة/المتجاوزة (على سبيل المثال، 20% يسمح بتحديثات مقدرة بمقدار 1.2x).", + "notify_live_chronometer": "عند التمكين، يتضمن كل تحديث مباشر عدًا تنازليًا للكرونومتر حتى وقت الانتهاء المقدر. يستمر مؤقت الإشعارات في العد التنازلي تلقائيًا على الجهاز بين التحديثات (تطبيق Android المصاحب فقط).", + "notify_title": "عنوان مخصص للإخطارات. يدعم العنصر النائب {device}.", + "notify_icon": "الرمز المطلوب استخدامه للإشعارات (على سبيل المثال، mdi:washing-machine). اتركه فارغًا لاستخدام الافتراضي.", + "notify_start_message": "يتم إرسال الرسالة عند بدء الدورة. يدعم العنصر النائب {device}.", + "notify_finish_message": "يتم إرسال الرسالة عند انتهاء الدورة. يدعم العناصر النائبة {device} و{duration} و{program}, {energy_kwh}, {cost}.", + "energy_price_entity": "حدد كيانًا رقميًا يحمل سعر الكهرباء الحالي (على سبيل المثال، sensor.electricity_price، input_number.energy_rate). عند التعيين، قم بتمكين العنصر النائب {cost}. تكون له الأسبقية على القيمة الثابتة إذا تم تكوين كليهما.", + "energy_price_static": "سعر الكهرباء الثابت لكل كيلووات ساعة. عند التعيين، قم بتمكين العنصر النائب {cost}. يتم تجاهله إذا تم تكوين الكيان أعلاه. أضف رمز العملة الخاص بك في قالب الرسالة، على سبيل المثال. {cost} €.", + "notify_reminder_message": "وأرسل نص التذكير غير المكرر رقماً محدداً قبل الموعد المقدر بدقائق. متفرقة من التحديثات الحية المتكررة أعلاه. Supports {device}, {minutes}, {program} مَلْكِلَةِ.", + "notify_timeout_seconds": "إشعارات بالطرد الآلي بعد هذه الثواني الكثيرة (المقدمة كطلب مرافق) إستعدْ إلى صفر لإبقائهم حتى رُفضَ. الفشل: صفر.", + "notify_channel": "Androidقناة الإخطار بالطلب المصاحب للحصول على المركز، التقدم الحي، والإخطارات التذكيرية. إن صوت القناة وأهميتها مهيأة في تطبيق الرفيق في المرة الأولى التي يستخدم فيها اسم القناة - واشداتا لا تحدد سوى اسم القناة. إتركْ فارغاً لمقعدِ التأليفِ.", + "notify_finish_channel": "قناة Android منفصلة للتنبيه النهائي (يستخدم أيضًا بواسطة التذكير وتذمر انتظار الغسيل) حتى يكون له صوت خاص به. اتركه فارغًا لإعادة استخدام القناة أعلاه.", + "notify_pre_complete_message": "نص تحديثات التقدم المباشر المتكررة أثناء تشغيل الدورة. يدعم العناصر النائبة {device} و{minutes} و{program}." + } + }, + "advanced_settings": { + "title": "الإعدادات المتقدمة", + "description": "ضبط عتبات الكشف والتعلم والسلوك المتقدم. تعمل الإعدادات الافتراضية بشكل جيد مع معظم الإعدادات.", + "sections": { + "suggestions_section": { + "name": "الإعدادات المقترحة", + "description": "تتوفر {suggestions_count} اقتراحات متعلمة. حدد \"تطبيق القيم المقترحة\" لمراجعة التغييرات المقترحة قبل الحفظ.", + "data": { + "apply_suggestions": "تطبيق القيم المقترحة" + }, + "data_description": { + "apply_suggestions": "حدد هذا المربع لفتح خطوة مراجعة للقيم المقترحة من خوارزمية التعلم. لا يتم حفظ أي شيء تلقائيًا.\n\nمقترح (من التعلم):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "عتبات الاكتشاف والطاقة", + "description": "متى يُعتبر الجهاز في حالة تشغيل أو إيقاف، وبوابات الطاقة، وتوقيت انتقالات الحالة.", + "data": { + "start_duration_threshold": "بدء مدة (فترات) الارتداد", + "start_energy_threshold": "بداية بوابة الطاقة (Wh)", + "completion_min_seconds": "الحد الأدنى لوقت التشغيل (بالثواني)", + "start_threshold_w": "عتبة البداية (W)", + "stop_threshold_w": "عتبة الإيقاف (W)", + "end_energy_threshold": "بوابة الطاقة النهائية (Wh)", + "running_dead_zone": "تشغيل المنطقة الميتة (ثواني)", + "end_repeat_count": "نهاية عدد التكرار", + "min_off_gap": "الحد الأدنى للفجوة بين الدورات (الدورات)", + "sampling_interval": "الفاصل الزمني (الفترات) لأخذ العينات" + }, + "data_description": { + "start_duration_threshold": "تجاهل ارتفاعات الطاقة القصيرة التي تقل عن هذه المدة (بالثواني). يقوم بتصفية ارتفاعات التمهيد (على سبيل المثال، 60 واط لمدة ثانيتين) قبل بدء الدورة الحقيقية. الافتراضي: 5S.", + "start_energy_threshold": "يجب أن تتراكم الدورة على الأقل هذا القدر من الطاقة (Wh) خلال مرحلة START حتى يتم تأكيدها. الافتراضي: 0.005 واط ساعة.", + "completion_min_seconds": "سيتم وضع علامة على الدورات الأقصر من ذلك على أنها \"متقطعة\" حتى لو انتهت بشكل طبيعي. الافتراضي: 600 ثانية.", + "start_threshold_w": "يجب أن ترتفع الطاقة فوق هذا الحد لبدء الدورة. تعيين أعلى من الطاقة الاحتياطية الخاصة بك. الافتراضي: min_power + 1 وات.", + "stop_threshold_w": "يجب أن تنخفض الطاقة عن هذا الحد لإنهاء الدورة. اضبطه أعلى بقليل من الصفر لتجنب التسبب في حدوث نهايات زائفة. الافتراضي: 0.6 * min_power.", + "end_energy_threshold": "تنتهي الدورة فقط إذا كانت الطاقة في نافذة تأخير التوقف الأخيرة أقل من هذه القيمة (Wh). يمنع النهايات الزائفة إذا تقلبت الطاقة بالقرب من الصفر. الافتراضي: 0.05 واط ساعة.", + "running_dead_zone": "بعد بدء الدورة، تجاهل انخفاضات الطاقة لعدة ثوانٍ لمنع اكتشاف النهاية الخاطئة أثناء مرحلة الدوران الأولية. اضبط على 0 للتعطيل. الافتراضي: 0 ثانية.", + "end_repeat_count": "عدد المرات التي يجب فيها استيفاء شرط النهاية (الطاقة أقل من عتبة off_delay) على التوالي قبل إنهاء الدورة. تمنع القيم الأعلى النهايات الزائفة أثناء التوقف المؤقت. الافتراضي: 1.", + "min_off_gap": "إذا بدأت الدورة خلال هذه الثواني العديدة من انتهاء الدورة السابقة، فسيتم دمجها في دورة واحدة.", + "sampling_interval": "الحد الأدنى للفاصل الزمني لأخذ العينات بالثواني. سيتم تجاهل التحديثات الأسرع من هذا المعدل. الافتراضي: 30 ثانية (2 ثانية للغسالات والغسالات والمجففات وغسالات الأطباق)." + } + }, + "matching_section": { + "name": "مطابقة الملف الشخصي والتعلم", + "description": "مدى قوة مطابقة WashData للدورات قيد التشغيل مع الملفات الشخصية المتعلمة ومتى يطلب التعليقات.", + "data": { + "profile_match_min_duration_ratio": "الحد الأدنى لنسبة مدة مطابقة الملف الشخصي (0.1-1.0)", + "profile_match_interval": "الفاصل الزمني لمطابقة الملف الشخصي (بالثواني)", + "profile_match_threshold": "عتبة مطابقة الملف الشخصي", + "profile_unmatch_threshold": "عتبة عدم تطابق الملف الشخصي", + "auto_label_confidence": "الثقة ذات التصنيف التلقائي (0-1)", + "learning_confidence": "طلب تعليقات الثقة (0-1)", + "suppress_feedback_notifications": "قمع إشعارات التعليقات", + "duration_tolerance": "مدة التسامح (0-0.5)", + "smoothing_window": "تجانس النافذة (عينات)", + "profile_duration_tolerance": "التسامح لمدة مطابقة الملف الشخصي (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "الحد الأدنى لنسبة المدة لمطابقة الملف الشخصي. يجب أن تكون دورة التشغيل على الأقل هذا الجزء من مدة الملف الشخصي للمطابقة. أقل = مطابقة سابقة. الافتراضي: 0.50 (50%).", + "profile_match_interval": "عدد المرات (بالثواني) لمحاولة مطابقة الملف الشخصي أثناء الدورة. توفر القيم المنخفضة اكتشافًا أسرع ولكنها تستخدم المزيد من وحدة المعالجة المركزية. الافتراضي: 300 ثانية (5 دقائق).", + "profile_match_threshold": "الحد الأدنى لدرجة تشابه DTW (0.0–1.0) المطلوبة لاعتبار الملف الشخصي مطابقًا. أعلى = مطابقة أكثر صرامة، عدد أقل من النتائج الإيجابية الخاطئة. الافتراضي: 0.4.", + "profile_unmatch_threshold": "درجة تشابه DTW (0.0–1.0) والتي يتم تحتها رفض ملف التعريف المطابق مسبقًا. يجب أن يكون أقل من عتبة المطابقة لمنع الخفقان. الافتراضي: 0.35.", + "auto_label_confidence": "قم بتسمية الدورات تلقائيًا عند هذه الثقة أو أعلى منها عند الاكتمال (0.0-1.0).", + "learning_confidence": "الحد الأدنى من الثقة لطلب التحقق من المستخدم عبر حدث (0.0-1.0).", + "suppress_feedback_notifications": "عند التمكين، ستستمر WashData في تتبع وطلب التعليقات داخليًا ولكنها لن تعرض إشعارات مستمرة تطلب منك تأكيد الدورات. يكون هذا مفيدًا عندما يتم اكتشاف الدورات دائمًا بشكل صحيح وتجد أن المطالبات تشتت انتباهك.", + "duration_tolerance": "التسامح مع تقديرات الوقت المتبقي أثناء التشغيل (0.0-0.5 يتوافق مع 0-50%).", + "smoothing_window": "عدد قراءات الطاقة الحديثة المستخدمة لتجانس المتوسط ​​المتحرك.", + "profile_duration_tolerance": "التسامح المستخدم من خلال مطابقة الملف الشخصي لتباين المدة الإجمالية (0.0-0.5). السلوك الافتراضي: ±25%." + } + }, + "timing_section": { + "name": "التوقيت والصيانة وتصحيح الأخطاء", + "description": "المراقب، وإعادة تعيين التقدم، والصيانة التلقائية، وإظهار كيانات تصحيح الأخطاء.", + "data": { + "watchdog_interval": "الفاصل الزمني للمراقبة (ثواني)", + "no_update_active_timeout": "مهلة (مهلة) عدم التحديث", + "progress_reset_delay": "تأخير إعادة تعيين التقدم (ثواني)", + "auto_maintenance": "تمكين الصيانة التلقائية", + "expose_debug_entities": "فضح كيانات التصحيح", + "save_debug_traces": "حفظ آثار التصحيح" + }, + "data_description": { + "watchdog_interval": "ثواني بين الشيكات الرقابية أثناء التشغيل. الافتراضي: 5S. تحذير: تأكد من أن هذا أعلى من الفاصل الزمني لتحديث المستشعر الخاص بك لتجنب التوقفات الخاطئة (على سبيل المثال، إذا تم تحديث المستشعر كل 60 ثانية، فاستخدم 65 ثانية+).", + "no_update_active_timeout": "بالنسبة لأجهزة استشعار النشر عند التغيير: قم بفرض إنهاء الدورة النشطة فقط في حالة عدم وصول أي تحديثات لهذه الثواني العديدة. لا يزال إكمال الطاقة المنخفضة يستخدم تأخير إيقاف التشغيل.", + "progress_reset_delay": "بعد اكتمال الدورة (100%)، انتظر هذه الثواني العديدة من الخمول قبل إعادة تعيين التقدم إلى 0%. الافتراضي: 300 ثانية.", + "auto_maintenance": "تمكين الصيانة التلقائية (إصلاح العينات، ودمج الأجزاء).", + "expose_debug_entities": "إظهار أجهزة الاستشعار المتقدمة (الثقة، المرحلة، الغموض) لتصحيح الأخطاء.", + "save_debug_traces": "قم بتخزين بيانات التصنيف التفصيلية وتتبع الطاقة في السجل (يزيد من استخدام التخزين)." + } + }, + "anti_wrinkle_section": { + "name": "درع مضاد للتجاعيد (للمجففات)", + "description": "تجاهل دورانات الأسطوانة بعد انتهاء الدورة لمنع الدورات الشبحية. للمجفف / الغسالة-المجفف فقط.", + "data": { + "anti_wrinkle_enabled": "درع مضاد للتجاعيد (المجفف فقط)", + "anti_wrinkle_max_power": "القوة القصوى المضادة للتجاعيد (وات)", + "anti_wrinkle_max_duration": "الحد الأقصى للمدة (المدد) المضادة للتجاعيد", + "anti_wrinkle_exit_power": "قوة الخروج المضادة للتجاعيد (وات)" + }, + "data_description": { + "anti_wrinkle_enabled": "تجاهل دورات الأسطوانة القصيرة منخفضة الطاقة بعد انتهاء الدورة لمنع دورات التشويه (المجفف/الغسالة والمجفف فقط).", + "anti_wrinkle_max_power": "يتم التعامل مع الاندفاعات عند هذه القوة أو أقل منها على أنها دورات مضادة للتجاعيد بدلاً من الدورات الجديدة. ينطبق فقط عند تمكين Anti-Wrinkle Shield.", + "anti_wrinkle_max_duration": "الحد الأقصى لطول الانفجار لعلاجه كدوران مضاد للتجاعيد. ينطبق فقط عند تمكين Anti-Wrinkle Shield.", + "anti_wrinkle_exit_power": "إذا انخفضت الطاقة إلى ما دون هذا المستوى، فاخرج من مضاد التجاعيد على الفور (إيقاف صحيح). ينطبق فقط عند تمكين Anti-Wrinkle Shield." + } + }, + "delay_start_section": { + "name": "اكتشاف البدء المتأخر", + "description": "تعامل مع وضع الاستعداد منخفض الطاقة المستمر على أنه 'في انتظار البدء' حتى تبدأ الدورة فعليًا.", + "data": { + "delay_start_detect_enabled": "تمكين اكتشاف تأخر البدء", + "delay_confirm_seconds": "البدء المتأخر: وقت تأكيد وضع الاستعداد (ث)", + "delay_timeout_hours": "البدء المؤجل: الحد الأقصى لوقت الانتظار (ح)" + }, + "data_description": { + "delay_start_detect_enabled": "عند التمكين، يتم اكتشاف ارتفاع حاد في استنزاف الطاقة المنخفضة متبوعًا بالطاقة الاحتياطية المستمرة كبداية متأخرة. يعرض مستشعر الحالة \"في انتظار البدء\" أثناء التأخير. لا يتم تتبع أي وقت للبرنامج أو تقدمه حتى تبدأ الدورة فعليًا. يتطلب تعيين start_threshold_w فوق قوة ارتفاع التصريف.", + "delay_confirm_seconds": "يجب أن تبقى الطاقة بين عتبة الإيقاف (W) وعتبة البدء (W) لهذا العدد من الثواني قبل أن تدخل WashData حالة 'في انتظار البدء'. يؤدي ذلك إلى تصفية قمم التنقل القصيرة في القوائم. الافتراضي: 60 ث.", + "delay_timeout_hours": "مهلة السلامة: إذا كان الجهاز لا يزال في وضع \"انتظار البدء\" بعد هذه الساعات العديدة، فسيتم إعادة ضبط WashData على وضع إيقاف التشغيل. الافتراضي: 8 ساعات." + } + }, + "external_triggers_section": { + "name": "المشغلات الخارجية والباب والإيقاف المؤقت", + "description": "مستشعر اختياري لمشغل النهاية الخارجي، ومستشعر باب لاكتشاف الإيقاف المؤقت/النظيف، ومفتاح لقطع الطاقة عند الإيقاف المؤقت.", + "data": { + "external_end_trigger_enabled": "تمكين مشغل نهاية الدورة الخارجية", + "external_end_trigger": "مستشعر نهاية الدورة الخارجية", + "external_end_trigger_inverted": "عكس منطق الزناد (الزناد عند الإيقاف)", + "door_sensor_entity": "مستشعر الباب", + "pause_cuts_power": "قطع الطاقة عند التوقف", + "switch_entity": "تبديل الكيان (لإيقاف انقطاع التيار الكهربائي مؤقتًا)", + "notify_unload_delay_minutes": "تأخير إشعار انتظار الغسيل (دقائق)" + }, + "data_description": { + "external_end_trigger_enabled": "تمكين مراقبة جهاز استشعار ثنائي خارجي لتحفيز إكمال الدورة.", + "external_end_trigger": "حدد كيان استشعار ثنائي. عندما يتم تشغيل هذا المستشعر، ستنتهي الدورة الحالية بالحالة \"مكتملة\".", + "external_end_trigger_inverted": "افتراضيًا، يتم تشغيل المشغل عند تشغيل المستشعر. حدد هذا المربع لإطلاق النار عند إيقاف تشغيل المستشعر بدلاً من ذلك.", + "door_sensor_entity": "مستشعر ثنائي اختياري لباب الماكينة (تشغيل = مفتوح، إيقاف = مغلق). عندما يفتح الباب أثناء دورة نشطة، ستؤكد WashData أن الدورة متوقفة مؤقتًا عن عمد. بعد انتهاء الدورة، إذا كان الباب لا يزال مغلقًا، تتغير الحالة إلى \"نظيف\" حتى يتم فتح الباب. ملحوظة: إغلاق الباب لا يؤدي إلى استئناف الدورة المتوقفة مؤقتًا تلقائيًا - يجب تشغيل الاستئناف يدويًا عبر زر أو خدمة استئناف الدورة.", + "pause_cuts_power": "عند الإيقاف المؤقت عبر زر أو خدمة الإيقاف المؤقت للدورة، قم أيضًا بإيقاف تشغيل كيان التبديل الذي تم تكوينه. يسري مفعوله فقط إذا تم تكوين كيان التبديل لهذا الجهاز.", + "switch_entity": "كيان التبديل المطلوب تبديله عند الإيقاف المؤقت أو الاستئناف. مطلوب عند تمكين \"قطع الطاقة عند الإيقاف المؤقت\".", + "notify_unload_delay_minutes": "أرسل إشعارًا عبر قناة إشعار الانتهاء بعد انتهاء الدورة وعدم فتح الباب لعدة دقائق (يتطلب مستشعر الباب). اضبط على 0 للتعطيل. الافتراضي: 60 دقيقة." + } + }, + "pump_section": { + "name": "مراقب المضخة", + "description": "لنوع جهاز المضخة فقط. يطلق حدثًا إذا استمرت دورة المضخة بعد هذه المدة.", + "data": { + "pump_stuck_duration": "عتبة (عتبات) التنبيه بشأن توقف المضخة (المضخة فقط)" + }, + "data_description": { + "pump_stuck_duration": "إذا كانت دورة المضخة لا تزال تعمل بعد هذه الثواني العديدة، فسيتم إطلاق حدث ha_washdata_pump_stuck مرة واحدة. استخدم هذا للكشف عن محرك محشور (تشغيل المضخة الغاطسة النموذجية أقل من 60 ثانية). الافتراضي: 1800 ثانية (30 دقيقة). نوع جهاز المضخة فقط." + } + }, + "device_link_section": { + "name": "رابط الجهاز", + "description": "(ب) ربط جهاز واشداتا هذا على نحو اختياري بجهاز قائم Home Assistant (على سبيل المثال، البقعة الذكية أو التبعية نفسها). جهاز (واشداتا) يظهر بعد ذلك على أنه \"متواصل عبر\" ذلك الجهاز إتركْ فارغاً لإبْقاء واشداتا كa جهاز مستقل.", + "data": { + "linked_device": "الأجهزة المترابطة" + }, + "data_description": { + "linked_device": "(اختار الجهاز لربط (واشادا هذا يضيف إشارة \"متواصلة\" في سجل الأجهزة، ويشتداتا تحتفظ ببطاقة جهازها الخاص وكياناتها. نظفوا الإختيار لإزالة الرابط" + } + } + } + }, + "diagnostics": { + "title": "التشخيص والصيانة", + "description": "قم بتشغيل إجراءات الصيانة مثل دمج الدورات المجزأة أو ترحيل البيانات المخزنة.\n\n**استخدام التخزين**\n\n- حجم الملف: {file_size_kb} كيلو بايت\n- الدورات: {cycle_count}\n- الملفات الشخصية: {profile_count}\n- آثار التصحيح: {debug_count}", + "menu_options": { + "reprocess_history": "الصيانة: إعادة معالجة البيانات وتحسينها", + "clear_debug_data": "مسح بيانات التصحيح (تحرير مساحة)", + "wipe_history": "مسح كافة البيانات لهذا الجهاز (لا رجعة فيه)", + "export_import": "تصدير/استيراد JSON مع الإعدادات (نسخ/لصق)", + "menu_back": "← رجوع" + } + }, + "clear_debug_data": { + "title": "مسح بيانات التصحيح", + "description": "هل أنت متأكد أنك تريد حذف كافة آثار التصحيح المخزنة؟ سيؤدي هذا إلى توفير مساحة ولكن إزالة معلومات التصنيف التفصيلية من الدورات السابقة." + }, + "manage_cycles": { + "title": "إدارة الدورات", + "description": "الدورات الأخيرة:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "التسمية التلقائية للدورات القديمة", + "select_cycle_to_label": "تسمية دورة محددة", + "select_cycle_to_delete": "حذف دورة", + "interactive_editor": "دمج/تقسيم المحرر التفاعلي", + "trim_cycle_select": "تقليم بيانات الدورة", + "menu_back": "← رجوع" + } + }, + "manage_cycles_empty": { + "title": "إدارة الدورات", + "description": "لم يتم تسجيل أي دورات حتى الآن.", + "menu_options": { + "auto_label_cycles": "التسمية التلقائية للدورات القديمة", + "menu_back": "← رجوع" + } + }, + "manage_profiles": { + "title": "إدارة الملفات الشخصية", + "description": "ملخص الملف الشخصي:\n{profile_summary}", + "menu_options": { + "create_profile": "إنشاء ملف تعريف جديد", + "edit_profile": "تحرير/إعادة تسمية الملف الشخصي", + "delete_profile_select": "حذف الملف الشخصي", + "profile_stats": "إحصائيات الملف الشخصي", + "cleanup_profile": "تنظيف التاريخ - الرسم البياني والحذف", + "assign_profile_phases_select": "تعيين نطاقات المرحلة", + "menu_back": "← رجوع" + } + }, + "manage_profiles_empty": { + "title": "إدارة الملفات الشخصية", + "description": "لم يتم إنشاء أي ملفات شخصية حتى الآن.", + "menu_options": { + "create_profile": "إنشاء ملف تعريف جديد", + "menu_back": "← رجوع" + } + }, + "manage_phase_catalog": { + "title": "إدارة كتالوج المرحلة", + "description": "كتالوج المرحلة (الافتراضيات لهذا الجهاز + المراحل المخصصة):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "إنشاء مرحلة جديدة", + "phase_catalog_edit_select": "تحرير المرحلة", + "phase_catalog_delete": "حذف المرحلة", + "menu_back": "← رجوع" + } + }, + "phase_catalog_create": { + "title": "إنشاء مرحلة مخصصة", + "description": "أنشئ اسمًا ووصفًا مخصصًا للمرحلة، واختر نوع الجهاز الذي ينطبق عليه.", + "data": { + "target_device_type": "نوع الجهاز المستهدف", + "phase_name": "اسم المرحلة", + "phase_description": "وصف المرحلة" + } + }, + "phase_catalog_edit_select": { + "title": "تحرير المرحلة المخصصة", + "description": "حدد مرحلة مخصصة للتحرير.", + "data": { + "phase_name": "المرحلة المخصصة" + } + }, + "phase_catalog_edit": { + "title": "تحرير المرحلة المخصصة", + "description": "مرحلة التحرير: {phase_name}", + "data": { + "phase_name": "اسم المرحلة", + "phase_description": "وصف المرحلة" + } + }, + "phase_catalog_delete": { + "title": "حذف المرحلة المخصصة", + "description": "حدد مرحلة مخصصة لحذفها. ستتم إزالة أي نطاقات مخصصة تستخدم هذه المرحلة.", + "data": { + "phase_name": "المرحلة المخصصة" + } + }, + "assign_profile_phases": { + "title": "تعيين نطاقات المرحلة", + "description": "معاينة المرحلة للملف الشخصي: **{profile_name}**\n\n{timeline_svg}\n\n**النطاقات الحالية:**\n{current_ranges}\n\nاستخدم الإجراءات أدناه لإضافة النطاقات أو تعديلها أو حذفها أو حفظها.", + "menu_options": { + "assign_profile_phases_add": "إضافة نطاق المرحلة", + "assign_profile_phases_edit_select": "تحرير نطاق المرحلة", + "assign_profile_phases_delete": "حذف نطاق المرحلة", + "phase_ranges_clear": "مسح كافة النطاقات", + "assign_profile_phases_auto_detect": "مراحل الكشف التلقائي", + "phase_ranges_save": "حفظ والعودة", + "menu_back": "← رجوع" + } + }, + "assign_profile_phases_add": { + "title": "إضافة نطاق المرحلة", + "description": "أضف نطاق مرحلة واحدة إلى المسودة الحالية.", + "data": { + "phase_name": "مرحلة", + "start_min": "دقيقة البداية", + "end_min": "دقيقة النهاية" + } + }, + "assign_profile_phases_edit_select": { + "title": "تحرير نطاق المرحلة", + "description": "حدد نطاقًا لتحريره.", + "data": { + "range_index": "نطاق المرحلة" + } + }, + "assign_profile_phases_edit": { + "title": "تحرير نطاق المرحلة", + "description": "تحديث نطاق المرحلة المحددة.", + "data": { + "phase_name": "مرحلة", + "start_min": "دقيقة البداية", + "end_min": "دقيقة النهاية" + } + }, + "assign_profile_phases_delete": { + "title": "حذف نطاق المرحلة", + "description": "حدد نطاقًا لإزالته من المسودة.", + "data": { + "range_index": "نطاق المرحلة" + } + }, + "assign_profile_phases_auto_detect": { + "title": "المراحل المكتشفة تلقائيًا", + "description": "الملف الشخصي: **{profile_name}**\n\n{timeline_svg}\n\n**تم اكتشاف {detected_count} مرحلة (مراحل) تلقائيًا.** اختر إجراءً أدناه.", + "data": { + "action": "فعل" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "اسم المراحل المكتشفة", + "description": "الملف الشخصي: **{profile_name}**\n\n**{detected_count} المرحلة (المراحل) التي تم اكتشافها:**\n{ranges_summary}\n\nخصص اسمًا لكل مرحلة." + }, + "assign_profile_phases_select": { + "title": "تعيين نطاقات المرحلة", + "description": "حدد ملف تعريف لتكوين نطاقات المرحلة.", + "data": { + "profile": "حساب تعريفي" + } + }, + "profile_stats": { + "title": "إحصائيات الملف الشخصي", + "description": "{stats_table}" + }, + "create_profile": { + "title": "إنشاء ملف تعريف جديد", + "description": "قم بإنشاء ملف تعريف جديد يدويًا أو من دورة سابقة.\n\nأمثلة على اسم الملف الشخصي: \"الملابس الرقيقة\"، \"الخدمة الشاقة\"، \"الغسيل السريع\"", + "data": { + "profile_name": "اسم الملف الشخصي", + "reference_cycle": "الدورة المرجعية (اختياري)", + "manual_duration": "المدة اليدوية (بالدقائق)" + } + }, + "edit_profile": { + "title": "تحرير الملف الشخصي", + "description": "حدد ملف تعريف لإعادة تسميته", + "data": { + "profile": "حساب تعريفي" + } + }, + "rename_profile": { + "title": "تحرير تفاصيل الملف الشخصي", + "description": "الملف الشخصي الحالي: {current_name}", + "data": { + "new_name": "اسم الملف الشخصي", + "manual_duration": "المدة اليدوية (بالدقائق)" + } + }, + "delete_profile_select": { + "title": "حذف الملف الشخصي", + "description": "حدد ملفًا شخصيًا لحذفه", + "data": { + "profile": "حساب تعريفي" + } + }, + "delete_profile_confirm": { + "title": "تأكيد حذف الملف الشخصي", + "description": "⚠️ سيؤدي هذا إلى حذف الملف الشخصي نهائيًا: {profile_name}", + "data": { + "unlabel_cycles": "قم بإزالة الملصق من الدورات باستخدام ملف التعريف هذا" + } + }, + "auto_label_cycles": { + "title": "التسمية التلقائية للدورات القديمة", + "description": "تم العثور على {total_count} إجمالي الدورات. الملفات الشخصية: {profiles}", + "data": { + "confidence_threshold": "الحد الأدنى من الثقة (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "حدد دورة للتسمية", + "description": "✓ = اكتمل، ⚠ = مستأنف، ✗ = متقطع", + "data": { + "cycle_id": "دورة" + } + }, + "select_cycle_to_delete": { + "title": "حذف دورة", + "description": "⚠️ سيؤدي هذا إلى حذف الدورة المحددة نهائيًا", + "data": { + "cycle_id": "دورة للحذف" + } + }, + "label_cycle": { + "title": "دورة التسمية", + "description": "{cycle_info}", + "data": { + "profile_name": "حساب تعريفي", + "new_profile_name": "اسم ملف التعريف الجديد (في حالة الإنشاء)" + } + }, + "post_process": { + "title": "تاريخ العملية (دمج/تقسيم)", + "description": "قم بتنظيف سجل الدورة عن طريق دمج عمليات التشغيل المجزأة وتقسيم الدورات المدمجة بشكل غير صحيح ضمن نافذة زمنية. أدخل عدد الساعات للرجوع إلى الوراء (استخدم 999999 للجميع).", + "data": { + "time_range": "فترة المراجعة (ساعات)", + "gap_seconds": "دمج/تقسيم الفجوة (ثواني)" + }, + "data_description": { + "time_range": "عدد الساعات المطلوب فحصها بحثًا عن دورات مجزأة لدمجها. استخدم 999999 لمعالجة كافة البيانات التاريخية.", + "gap_seconds": "عتبة لتحديد الانقسام مقابل الدمج. يتم دمج الفجوات الأقصر من ذلك. يتم تقسيم الفجوات الأطول من هذا. قم بخفض هذا لفرض الانقسام على الدورة التي تم دمجها بشكل غير صحيح." + } + }, + "cleanup_profile": { + "title": "تنظيف السجل - حدد ملف التعريف", + "description": "حدد ملفًا شخصيًا لتصوره. سيؤدي هذا إلى إنشاء رسم بياني يوضح جميع الدورات السابقة لهذا الملف الشخصي للمساعدة في تحديد القيم المتطرفة.", + "data": { + "profile": "حساب تعريفي" + } + }, + "cleanup_select": { + "title": "تنظيف التاريخ - الرسم البياني والحذف", + "description": "![رسم بياني]({graph_url})\n\n**التصور {profile_name}**\n\nيوضح الرسم البياني الدورات الفردية كخطوط ملونة. حدد القيم المتطرفة (الخطوط البعيدة عن المجموعة) وطابق لونها في القائمة أدناه لحذفها.\n\n**حدد الدورات المراد حذفها نهائيًا:**", + "data": { + "cycles_to_delete": "الدورات المراد حذفها (تحقق من الألوان المطابقة)" + } + }, + "interactive_editor": { + "title": "دمج/تقسيم المحرر التفاعلي", + "description": "حدد إجراءً يدويًا لتنفيذه على سجل دورتك.", + "menu_options": { + "editor_split": "تقسيم الدورة (البحث عن الثغرات)", + "editor_merge": "دمج الدورات (ربط الأجزاء)", + "editor_delete": "حذف الدورة (الدورات)", + "menu_back": "← رجوع" + } + }, + "editor_select": { + "title": "حدد الدورات", + "description": "حدد الدورة (الدورات) المراد معالجتها.\n\n{info_text}", + "data": { + "selected_cycles": "دورات" + } + }, + "editor_configure": { + "title": "تكوين وتطبيق", + "description": "{preview_md}", + "data": { + "confirm_action": "فعل", + "merged_profile": "الملف الشخصي الناتج", + "new_profile_name": "اسم الملف الشخصي الجديد", + "confirm_commit": "نعم أؤكد هذا الإجراء", + "segment_0_profile": "الملف الشخصي للجزء الأول", + "segment_1_profile": "الملف الشخصي للجزء 2", + "segment_2_profile": "الجزء 3 الملف الشخصي", + "segment_3_profile": "الجزء 4 الملف الشخصي", + "segment_4_profile": "الجزء 5 الملف الشخصي", + "segment_5_profile": "الجزء 6 الملف الشخصي", + "segment_6_profile": "الجزء 7 الملف الشخصي", + "segment_7_profile": "الجزء 8 الملف الشخصي", + "segment_8_profile": "الجزء 9 الملف الشخصي", + "segment_9_profile": "الجزء 10 الملف الشخصي" + } + }, + "editor_split_params": { + "title": "تكوين الانقسام", + "description": "ضبط الحساسية لتقسيم هذه الدورة. أي فجوة خاملة أطول من ذلك ستؤدي إلى الانقسام.", + "data": { + "split_mode": "طريقة التقسيم" + } + }, + "editor_split_auto_params": { + "title": "إعداد التقسيم التلقائي", + "description": "اضبط حساسية تقسيم هذه الدورة. أي فجوة خمول أطول من هذا ستؤدي إلى تقسيم.", + "data": { + "min_gap_seconds": "عتبة فجوة التقسيم (بالثواني)" + } + }, + "editor_split_manual_params": { + "title": "طوابع زمنية للتقسيم اليدوي", + "description": "{preview_md}نافذة الدورة: **{cycle_start_wallclock} → {cycle_end_wallclock}** في {cycle_date}.\n\nأدخل طابعًا زمنيًا واحدًا أو أكثر للتقسيم (`HH:MM` أو `HH:MM:SS`)، واحدًا في كل سطر. سيتم قطع الدورة عند كل طابع زمني — ينتج عن N من الطوابع الزمنية N+1 من المقاطع.", + "data": { + "split_timestamps": "تقسيم الطوابع الزمنية" + } + }, + "reprocess_history": { + "title": "الصيانة: إعادة معالجة البيانات وتحسينها", + "description": "يقوم بالتنظيف العميق للبيانات التاريخية:\n1. يقوم بتشذيب قراءات الطاقة الصفرية (الصمت).\n2. إصلاح توقيت/مدة الدورة.\n3. إعادة حساب كافة النماذج الإحصائية (المظاريف).\n\n**قم بتشغيل هذا لإصلاح مشكلات البيانات أو تطبيق خوارزميات جديدة.**" + }, + "wipe_history": { + "title": "مسح السجل (اختبار فقط)", + "description": "⚠️ سيؤدي هذا إلى حذف جميع الدورات والملفات الشخصية المخزنة لهذا الجهاز بشكل دائم. لا يمكن التراجع عن هذا!" + }, + "export_import": { + "title": "تصدير/استيراد JSON", + "description": "نسخ/لصق الدورات والملفات الشخصية والإعدادات لهذا الجهاز. قم بالتصدير أدناه أو لصق JSON للاستيراد.", + "data": { + "mode": "فعل", + "json_payload": "استكمال التكوين JSON" + }, + "data_description": { + "mode": "اختر تصدير لنسخ البيانات والإعدادات الحالية، أو استيراد للصق البيانات المصدرة من جهاز WashData آخر.", + "json_payload": "للتصدير: انسخ ملف JSON هذا (يتضمن الدورات والملفات الشخصية وجميع الإعدادات المضبوطة بدقة). للاستيراد: الصق JSON الذي تم تصديره من WashData." + } + }, + "record_cycle": { + "title": "دورة التسجيل", + "description": "قم بتسجيل الدورة يدويًا لإنشاء ملف تعريف نظيف دون أي تدخل.\n\nالحالة: **{status}**\nالمدة: {duration}ث\nالعينات: {samples}", + "menu_options": { + "record_refresh": "تحديث الحالة", + "record_stop": "إيقاف التسجيل (الحفظ والمعالجة)", + "record_start": "ابدأ التسجيل الجديد", + "record_process": "معالجة التسجيل الأخير (القص والحفظ)", + "record_discard": "تجاهل التسجيل الأخير", + "menu_back": "← رجوع" + } + }, + "record_process": { + "title": "تسجيل العملية", + "description": "![رسم بياني]({graph_url})\n\n**يظهر الرسم البياني التسجيل الأولي.** الأزرق = الاحتفاظ، الأحمر = القطع المقترح.\n*الرسم البياني ثابت ولا يتم تحديثه بتغييرات النموذج.*\n\nتوقف التسجيل. تتم محاذاة عمليات القطع مع معدل أخذ العينات المكتشف (~{sampling_rate}s).\n\nالمدة الأولية: {duration}ث\nالعينات: {samples}", + "data": { + "head_trim": "بدء القطع (بالثواني)", + "tail_trim": "نهاية القطع (ثواني)", + "save_mode": "حفظ الوجهة", + "profile_name": "اسم الملف الشخصي" + } + }, + "learning_feedbacks": { + "title": "التغذية الراجعة للتعلم", + "description": "التغذية الراجعة المعلقة: {count}\n\n{pending_table}\n\nحدد طلب مراجعة دورة للمعالجة.", + "menu_options": { + "learning_feedbacks_pick": "مراجعة تعليق معلق", + "learning_feedbacks_dismiss_all": "رفض جميع التعليقات المعلقة", + "menu_back": "← رجوع" + } + }, + "learning_feedbacks_pick": { + "title": "اختر تغذية راجعة للمراجعة", + "description": "حدد طلب مراجعة دورة لفتحه.", + "data": { + "selected_feedback": "حدد الدورة للمراجعة" + } + }, + "learning_feedbacks_empty": { + "title": "التغذية الراجعة للتعلم", + "description": "لم يتم العثور على طلبات التعليقات المعلقة.", + "menu_options": { + "menu_back": "← رجوع" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "رفض جميع طلبات التغذية الراجعة المعلقة", + "description": "أنت على وشك رفض **{count}** من طلبات التغذية الراجعة المعلقة. ستبقى الدورات المرفوضة في السجل لديك ولكنها لن تضيف إشارة تعلم جديدة. لا يمكن التراجع عن هذا.\n\n✅ حدد المربع أدناه وانقر فوق **إرسال** لرفض الكل.\n❌ اتركه بدون تحديد وانقر فوق **إرسال** للإلغاء والعودة.", + "data": { + "confirm_dismiss_all": "تأكيد: رفض جميع طلبات التعليقات المعلقة" + } + }, + "resolve_feedback": { + "title": "حل ردود الفعل", + "description": "{comparison_img}{candidates_table}\n**الملف الشخصي الذي تم اكتشافه**: {detected_profile} ({confidence_pct}%)\n**المدة المقدرة**: {est_duration_min} دقيقة\n**المدة الفعلية**: {act_duration_min} دقيقة\n\n__اختر إجراءً أدناه:__", + "data": { + "action": "ماذا تريد أن تفعل؟", + "corrected_profile": "البرنامج الصحيح (في حالة التصحيح)", + "corrected_duration": "المدة الصحيحة بالثواني (في حالة التصحيح)" + } + }, + "trim_cycle_select": { + "title": "دورة القطع - اختر دورة", + "description": "حدد دورة لقصها. ✓ = اكتمل، ⚠ = مستأنف، ✗ = متقطع", + "data": { + "cycle_id": "دورة" + } + }, + "trim_cycle": { + "title": "دورة القطع", + "description": "النافذة الحالية: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "فعل" + } + }, + "trim_cycle_start": { + "title": "ضبط بداية القطع", + "description": "نافذة الدورة: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nالبداية الحالية: **{current_wallclock}** (+{current_offset_min} دقيقة من بداية الدورة)\n\nاختر وقت بدء جديدًا باستخدام منتقي الساعة أدناه.", + "data": { + "trim_start_time": "وقت البدء الجديد", + "trim_start_min": "بداية جديدة (دقائق من بداية الدورة)" + } + }, + "trim_cycle_end": { + "title": "ضبط نهاية القطع", + "description": "نافذة الدورة: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nالنهاية الحالية: **{current_wallclock}** (+{current_offset_min} دقيقة من بداية الدورة)\n\nاختر وقت انتهاء جديدًا باستخدام منتقي الساعة أدناه.", + "data": { + "trim_end_time": "وقت الانتهاء الجديد", + "trim_end_min": "النهاية الجديدة (دقائق من بداية الدورة)" + } + } + }, + "error": { + "import_failed": "فشل الاستيراد. يرجى لصق ملف WashData صالح لتصدير JSON.", + "select_exactly_one": "حدد دورة واحدة فقط لهذا الإجراء.", + "select_at_least_one": "حدد دورة واحدة على الأقل لهذا الإجراء.", + "select_at_least_two": "حدد دورتين على الأقل لهذا الإجراء.", + "profile_exists": "يوجد بالفعل ملف تعريف بهذا الاسم", + "rename_failed": "فشل في إعادة تسمية الملف الشخصي. قد لا يكون الملف الشخصي موجودًا أو أن الاسم الجديد مأخوذ بالفعل.", + "assignment_failed": "فشل في تعيين ملف التعريف للدورة", + "duplicate_phase": "هناك مرحلة بهذا الاسم موجودة بالفعل.", + "phase_not_found": "لم يتم العثور على المرحلة المحددة.", + "invalid_phase_name": "لا يمكن أن يكون اسم المرحلة فارغًا.", + "phase_name_too_long": "اسم المرحلة طويل جدًا.", + "invalid_phase_range": "نطاق المرحلة غير صالح. يجب أن تكون النهاية أكبر من البداية.", + "invalid_phase_timestamp": "الطابع الزمني غير صالح. استخدم التنسيق YYYY-MM-DD HH:MM أو HH:MM.", + "overlapping_phase_ranges": "تتداخل نطاقات المرحلة. اضبط النطاقات وحاول مرة أخرى.", + "incomplete_phase_row": "يجب أن يتضمن كل صف مرحلة مرحلة بالإضافة إلى قيم البداية/النهاية الكاملة للوضع المحدد.", + "timestamp_mode_cycle_required": "الرجاء تحديد دورة المصدر عند استخدام وضع الطابع الزمني.", + "no_phase_ranges": "لا توجد نطاقات طورية متاحة لهذا الإجراء حتى الآن.", + "feedback_notification_title": "WashData: دورة التحقق ({device})", + "feedback_notification_message": "**الجهاز**: {device}\n**البرنامج**: {program} (نسبة الثقة {confidence}%)\n**الوقت**: {time}\n\nيحتاج WashData إلى مساعدتك للتحقق من هذه الدورة المكتشفة.\n\nيرجى الانتقال إلى **الإعدادات > الأجهزة والخدمات > WashData > التكوين > تعليقات التعلم** لتأكيد هذه النتيجة أو تصحيحها.", + "suggestions_ready_notification_title": "WashData: الإعدادات المقترحة جاهزة ({device})", + "suggestions_ready_notification_message": "يقدم مستشعر **الإعدادات المقترحة** الآن **{count}** توصيات قابلة للتنفيذ.\n\nلمراجعتها وتطبيقها: **الإعدادات > الأجهزة والخدمات > WashData > التكوين > الإعدادات المتقدمة > تطبيق القيم المقترحة**.\n\nالاقتراحات اختيارية ويتم عرضها للمراجعة قبل حفظها.", + "trim_range_invalid": "البداية يجب أن تكون قبل النهاية. يرجى ضبط نقاط القطع الخاصة بك.", + "trim_failed": "فشل القطع. ربما لم تعد الدورة موجودة أو أن النافذة فارغة.", + "invalid_split_timestamp": "الطابع الزمني غير صالح أو خارج نافذة الدورة. استخدم HH:MM أو HH:MM:SS ضمن نافذة الدورة.", + "no_split_segments_found": "تعذر إنشاء أي شرائح صالحة من هذه الطوابع الزمنية. تأكد من أن كل طابع زمني يقع داخل نافذة الدورة وأن طول الشرائح لا يقل عن دقيقة واحدة.", + "auto_tune_suggestion": "تم اكتشاف {device_type} {device_title} دورات شبحية. التغيير الأدنى المقترح للطاقة: {current_min}W -> {new_min}W (لا يتم تطبيقه تلقائيًا).", + "auto_tune_title": "ضبط تلقائي لـ WashData", + "auto_tune_fallback": "تم اكتشاف {device_type} {device_title} دورات شبحية. التغيير الأدنى المقترح للطاقة: {current_min}W -> {new_min}W (لا يتم تطبيقه تلقائيًا)." + }, + "abort": { + "no_cycles_found": "لم يتم العثور على دورات", + "no_split_segments_found": "لم يتم العثور على شرائح قابلة للتقسيم في الدورة المحددة.", + "cycle_not_found": "تعذر تحميل الدورة المحددة.", + "no_power_data": "لا تتوفر بيانات تتبع الطاقة للدورة المحددة.", + "no_profiles_found": "لم يتم العثور على أي ملفات شخصية. قم بإنشاء ملف تعريف أولاً.", + "no_profiles_for_matching": "لا توجد ملفات تعريف متاحة للمطابقة. قم بإنشاء الملفات الشخصية أولاً.", + "no_unlabeled_cycles": "تم بالفعل تسمية جميع الدورات", + "no_suggestions": "لا توجد قيم مقترحة متاحة حتى الآن. قم بتشغيل بضع دورات وحاول مرة أخرى.", + "no_predictions": "لا تنبؤات", + "no_custom_phases": "لم يتم العثور على مراحل مخصصة.", + "reprocess_success": "تمت إعادة معالجة {count} دورة بنجاح.", + "debug_data_cleared": "تم مسح بيانات تصحيح الأخطاء من {count} دورة بنجاح." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "بداية الدورة", + "cycle_finish": "إنهاء الدورة", + "cycle_live": "التقدم المباشر" + } + }, + "common_text": { + "options": { + "unlabeled": "(بدون تسمية)", + "create_new_profile": "إنشاء ملف تعريف جديد", + "remove_label": "إزالة التسمية", + "no_reference_cycle": "(لا توجد دورة مرجعية)", + "all_device_types": "جميع أنواع الأجهزة", + "current_suffix": "(حاضِر)", + "editor_select_info": "حدد دورة واحدة للتقسيم، أو أكثر من دورتين للدمج.", + "editor_select_info_split": "حدد دورة واحدة لتقسيمها.", + "editor_select_info_merge": "حدد دورتين أو أكثر لدمجها.", + "editor_select_info_delete": "حدد دورة واحدة أو أكثر لحذفها.", + "no_cycles_recorded": "لم يتم تسجيل أي دورات حتى الآن.", + "profile_summary_line": "- **{name}**: {count} دورات، {avg}م متوسط", + "no_profiles_created": "لم يتم إنشاء أي ملفات شخصية حتى الآن.", + "phase_preview": "معاينة المرحلة", + "phase_preview_no_curve": "متوسط ​​منحنى الملف الشخصي غير متوفر بعد. تشغيل/تسمية المزيد من الدورات لهذا الملف الشخصي.", + "average_power_curve": "متوسط ​​منحنى القوة", + "unit_min": "دقيقة", + "profile_option_fmt": "{name} ({count} دورات، ~{duration}م متوسط)", + "profile_option_short_fmt": "{name} ({count} دورات، ~{duration} م)", + "deleted_cycles_from_profile": "تم حذف {count} دورة من {profile}.", + "not_enough_data_graph": "لا توجد بيانات كافية لإنشاء الرسم البياني.", + "no_profiles_found": "لم يتم العثور على أي ملفات شخصية.", + "cycle_info_fmt": "الدورة: {start}، {duration}م، التيار: {label}", + "top_candidates_header": "### أبرز المرشحين", + "tbl_profile": "حساب تعريفي", + "tbl_confidence": "ثقة", + "tbl_correlation": "علاقة", + "tbl_duration_match": "مباراة المدة", + "detected_profile": "الملف الشخصي الذي تم اكتشافه", + "estimated_duration": "المدة المقدرة", + "actual_duration": "المدة الفعلية", + "choose_action": "__اختر إجراءً أدناه:__", + "tbl_count": "عدد", + "tbl_avg": "متوسط", + "tbl_min": "دقيقة", + "tbl_max": "الأعلى", + "tbl_energy_avg": "الطاقة (المتوسط)", + "tbl_energy_total": "الطاقة (المجموع)", + "tbl_consistency": "تتكون.", + "tbl_last_run": "التشغيل الأخير", + "graph_legend_title": "أسطورة الرسم البياني", + "graph_legend_body": "يمثل النطاق الأزرق الحد الأدنى والحد الأقصى لنطاق سحب الطاقة الذي تمت ملاحظته. يوضح الخط متوسط ​​منحنى القدرة.", + "recording_preview": "معاينة التسجيل", + "trim_start": "بداية القطع", + "trim_end": "نهاية القطع", + "split_preview_title": "معاينة الانقسام", + "split_preview_found_fmt": "تم العثور على {count} شريحة.", + "split_preview_confirm_fmt": "انقر فوق تأكيد لتقسيم هذه الدورة إلى {count} دورات منفصلة.", + "merge_preview_title": "معاينة الدمج", + "merge_preview_joining_fmt": "الانضمام إلى {count} دورات. سيتم ملء الفجوات بقراءات 0W.", + "editor_delete_preview_title": "معاينة الحذف", + "editor_delete_preview_intro": "سيتم حذف الدورات المحددة نهائيًا:", + "editor_delete_preview_confirm": "انقر فوق تأكيد لحذف سجلات هذه الدورات نهائيًا.", + "no_power_preview": "*لا توجد بيانات طاقة متاحة للمعاينة.*", + "profile_comparison": "مقارنة الملف الشخصي", + "actual_cycle_label": "هذه الدورة (فعلية)", + "storage_usage_header": "استخدام التخزين", + "storage_file_size": "حجم الملف", + "storage_cycles": "دورات", + "storage_profiles": "الملفات الشخصية", + "storage_debug_traces": "آثار التصحيح", + "overview_suffix": "ملخص", + "phase_preview_for_profile": "معاينة المرحلة للملف الشخصي", + "current_ranges_header": "النطاقات الحالية", + "assign_phases_help": "استخدم الإجراءات أدناه لإضافة النطاقات أو تعديلها أو حذفها أو حفظها.", + "profile_summary_header": "ملخص الملف الشخصي", + "recent_cycles_header": "الدورات الأخيرة", + "trim_cycle_preview_title": "معاينة دورة القطع", + "trim_cycle_preview_no_data": "لا توجد بيانات طاقة متاحة لهذه الدورة.", + "trim_cycle_preview_kept_suffix": "أبقى", + "table_program": "برنامج", + "table_when": "متى", + "table_length": "المدة", + "table_match": "المطابقة", + "table_profile": "حساب تعريفي", + "table_cycles": "دورات", + "table_avg_length": "متوسط المدة", + "table_last_run": "التشغيل الأخير", + "table_avg_energy": "متوسط الطاقة", + "table_detected_program": "البرنامج المكتشف", + "table_confidence": "ثقة", + "table_reported": "المبلغ عنه", + "phase_builtin_suffix": "(مدمج)", + "phase_none_available": "لا توجد مراحل متاحة.", + "settings_deprecation_warning": "⚠️ **نوع الجهاز المهمل:** تمت جدولة إزالة {device_type} في إصدار مستقبلي. لا ينتج عن خط الأنابيب المطابق لـ WashData نتائج موثوقة لفئة الأجهزة هذه. يستمر التكامل الخاص بك في العمل خلال فترة الإيقاف؛ لإسكات هذا التحذير، قم بتبديل **نوع الجهاز** أدناه إلى أحد الأنواع المدعومة (غسالة، مجفف، مجموعة غسالة ومجفف، غسالة أطباق، مقلاة هوائية، صانع الخبز، أو مضخة)، أو **أخرى (متقدمة)** إذا كان جهازك لا يتطابق مع أي من الأنواع المدعومة. **أخرى (متقدمة)** تشحن افتراضيات عامة عن عمد لم يتم ضبطها لأي جهاز محدد، لذلك ستحتاج إلى تكوين الحدود القصوى والمهلات والمعلمات المطابقة بنفسك؛ يتم الاحتفاظ بجميع إعداداتك الحالية عند التبديل.", + "phase_other_device_types": "أنواع الأجهزة الأخرى:" + } + }, + "interactive_editor_action": { + "options": { + "split": "تقسيم الدورة (البحث عن الثغرات)", + "merge": "دمج الدورات (ربط الأجزاء)", + "delete": "حذف الدورة (الدورات)" + } + }, + "split_mode": { + "options": { + "auto": "اكتشاف فجوات الخمول تلقائيًا", + "manual": "طابع/طوابع زمنية يدوية" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "الصيانة: إعادة معالجة البيانات وتحسينها", + "clear_debug_data": "مسح بيانات التصحيح (تحرير مساحة)", + "wipe_history": "مسح كافة البيانات لهذا الجهاز (لا رجعة فيه)", + "export_import": "تصدير/استيراد JSON مع الإعدادات (نسخ/لصق)" + } + }, + "export_import_mode": { + "options": { + "export": "تصدير كافة البيانات", + "import": "استيراد/دمج البيانات" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "التسمية التلقائية للدورات القديمة", + "select_cycle_to_label": "تسمية دورة محددة", + "select_cycle_to_delete": "حذف دورة", + "interactive_editor": "دمج/تقسيم المحرر التفاعلي", + "trim_cycle": "تقليم بيانات الدورة" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "إنشاء ملف تعريف جديد", + "edit_profile": "تحرير/إعادة تسمية الملف الشخصي", + "delete_profile": "حذف الملف الشخصي", + "profile_stats": "إحصائيات الملف الشخصي", + "cleanup_profile": "تنظيف التاريخ - الرسم البياني والحذف", + "assign_phases": "تعيين نطاقات المرحلة" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "إنشاء مرحلة جديدة", + "edit_custom_phase": "تحرير المرحلة", + "delete_custom_phase": "حذف المرحلة" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "إضافة نطاق المرحلة", + "edit_range": "تحرير نطاق المرحلة", + "delete_range": "حذف نطاق المرحلة", + "clear_ranges": "مسح كافة النطاقات", + "auto_detect_ranges": "مراحل الكشف التلقائي", + "save_ranges": "حفظ والعودة" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "تحديث الحالة", + "stop_recording": "إيقاف التسجيل (الحفظ والمعالجة)", + "start_recording": "ابدأ التسجيل الجديد", + "process_recording": "معالجة التسجيل الأخير (القص والحفظ)", + "discard_recording": "تجاهل التسجيل الأخير" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "إنشاء ملف تعريف جديد", + "existing_profile": "إضافة إلى الملف الشخصي الموجود", + "discard": "تجاهل التسجيل" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "تأكيد - الكشف الصحيح", + "correct": "صحيح - اختر البرنامج/المدة", + "ignore": "تجاهل - دورة إيجابية كاذبة/صاخبة", + "delete": "حذف - إزالة من التاريخ" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "مراحل الاسم والتطبيق", + "cancel": "العودة دون تغييرات" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "تعيين نقطة البداية", + "set_end": "تعيين نقطة النهاية", + "reset": "إعادة التعيين إلى المدة الكاملة", + "apply": "تطبيق تريم", + "cancel": "إلغاء" + } + }, + "device_type": { + "options": { + "washing_machine": "غسالة", + "dryer": "مجفف", + "washer_dryer": "كومبو غسالة ومجفف", + "dishwasher": "غسالة الأواني", + "coffee_machine": "ماكينة صنع القهوة (مهجورة)", + "ev": "مركبة كهربائية (مهجورة)", + "air_fryer": "المقلاة الهوائية", + "heat_pump": "المضخة الحرارية (مهملة)", + "bread_maker": "صانع الخبز", + "pump": "مضخة / مضخة مستنقع", + "oven": "فرن (مهجور)", + "other": "أخرى (متقدم)" + } + } + }, + "services": { + "label_cycle": { + "name": "دورة التسمية", + "description": "قم بتعيين ملف تعريف موجود لدورة سابقة، أو قم بإزالة التسمية.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة للتسمية." + }, + "cycle_id": { + "name": "معرف الدورة", + "description": "معرف الدورة المراد تسميتها." + }, + "profile_name": { + "name": "اسم الملف الشخصي", + "description": "اسم ملف التعريف الموجود (قم بإنشاء ملفات التعريف في قائمة إدارة الملفات الشخصية). اتركه فارغًا لإزالة التسمية." + } + } + }, + "create_profile": { + "name": "إنشاء الملف الشخصي", + "description": "قم بإنشاء ملف تعريف جديد (مستقل أو يعتمد على دورة مرجعية).", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة ." + }, + "profile_name": { + "name": "اسم الملف الشخصي", + "description": "اسم الملف الشخصي الجديد (على سبيل المثال، \"Heavy Duty\"، \"Delicates\")." + }, + "reference_cycle_id": { + "name": "معرف الدورة المرجعية", + "description": "معرف الدورة الاختياري لتأسيس ملف التعريف هذا." + } + } + }, + "delete_profile": { + "name": "حذف الملف الشخصي", + "description": "احذف ملف تعريف وقم بإلغاء تسمية الدورات التي تستخدمه اختياريًا.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة ." + }, + "profile_name": { + "name": "اسم الملف الشخصي", + "description": "الملف الشخصي المراد حذفه." + }, + "unlabel_cycles": { + "name": "دورات إلغاء التسمية", + "description": "قم بإزالة تسمية الملف الشخصي من الدورات التي تستخدم ملف التعريف هذا." + } + } + }, + "auto_label_cycles": { + "name": "التسمية التلقائية للدورات القديمة", + "description": "قم بتسمية الدورات غير المسماة بأثر رجعي باستخدام مطابقة ملف التعريف.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة ." + }, + "confidence_threshold": { + "name": "عتبة الثقة", + "description": "الحد الأدنى لثقة المطابقة (0.50-0.95) لتطبيق التصنيفات." + } + } + }, + "export_config": { + "name": "تكوين التصدير", + "description": "قم بتصدير الملفات الشخصية والدورات والإعدادات لهذا الجهاز إلى ملف JSON (لكل جهاز).", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة للتصدير." + }, + "path": { + "name": "طريق", + "description": "مسار الملف المطلق الاختياري للكتابة (الإعداد الافتراضي هو /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "استيراد التكوين", + "description": "قم باستيراد الملفات التعريفية والدورات والإعدادات لهذا الجهاز من ملف تصدير JSON (قد تتم الكتابة فوق الإعدادات).", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة المراد الاستيراد إليه." + }, + "path": { + "name": "طريق", + "description": "المسار المطلق لملف التصدير JSON المراد استيراده." + } + } + }, + "submit_cycle_feedback": { + "name": "إرسال تعليقات الدورة", + "description": "قم بتأكيد أو تصحيح البرنامج الذي تم اكتشافه تلقائيًا بعد انتهاء الدورة. قدِّم إما `entry_id` (متقدم) أو `device_id` (موصى به).", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز WashData (مستحسن بدلاً من إدخال_id)." + }, + "entry_id": { + "name": "معرف الدخول", + "description": "معرف إدخال التكوين للجهاز (بديل لـ Device_id)." + }, + "cycle_id": { + "name": "معرف الدورة", + "description": "معرف الدورة الموضح في إشعار/سجلات التعليقات." + }, + "user_confirmed": { + "name": "تأكيد البرنامج المكتشف", + "description": "اضبط على \"صحيح\" إذا كان البرنامج المكتشف صحيحًا." + }, + "corrected_profile": { + "name": "الملف الشخصي المصحح", + "description": "إذا لم يتم التأكيد، قم بتوفير اسم الملف الشخصي/البرنامج الصحيح." + }, + "corrected_duration": { + "name": "المدة المصححة (بالثواني)", + "description": "المدة المصححة الاختيارية بالثواني." + }, + "notes": { + "name": "ملحوظات", + "description": "ملاحظات اختيارية حول هذه الدورة." + } + } + }, + "record_start": { + "name": "بدء دورة التسجيل", + "description": "ابدأ بتسجيل دورة نظيفة يدويًا (يتجاوز كل منطق المطابقة).", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة للتسجيل." + } + } + }, + "record_stop": { + "name": "سجل توقف الدورة", + "description": "إيقاف التسجيل اليدوي.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز الغسالة لإيقاف التسجيل." + } + } + }, + "trim_cycle": { + "name": "دورة القطع", + "description": "قم بقص بيانات الطاقة الخاصة بالدورة الماضية إلى نافذة زمنية محددة.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "جهاز WashData." + }, + "cycle_id": { + "name": "معرف الدورة", + "description": "معرف الدورة المراد قطعها." + }, + "trim_start_s": { + "name": "بدء القطع (بالثواني)", + "description": "احتفظ بالبيانات من هذه الإزاحة في ثوانٍ. الافتراضي 0." + }, + "trim_end_s": { + "name": "نهاية القطع (ثواني)", + "description": "احتفظ بالبيانات حتى هذه الإزاحة في ثوانٍ. الافتراضي = المدة الكاملة." + } + } + }, + "pause_cycle": { + "name": "دورة الإيقاف المؤقت", + "description": "قم بإيقاف الدورة النشطة مؤقتًا لجهاز WashData.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "توقف جهاز WashData مؤقتًا." + } + } + }, + "resume_cycle": { + "name": "استئناف الدورة", + "description": "استئناف دورة متوقفة مؤقتًا لجهاز WashData.", + "fields": { + "device_id": { + "name": "جهاز", + "description": "سيتم استئناف تشغيل جهاز WashData." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "قيد التشغيل" + }, + "match_ambiguity": { + "name": "غموض المباراة" + } + }, + "sensor": { + "washer_state": { + "name": "الحالة", + "state": { + "off": "إيقاف", + "idle": "عاطل", + "starting": "البدء", + "running": "قيد التشغيل", + "paused": "متوقف مؤقتًا", + "user_paused": "متوقف مؤقتاً من قِبَل المستخدم", + "ending": "إنهاء", + "finished": "انتهى", + "anti_wrinkle": "مضاد للتجاعيد", + "delay_wait": "في انتظار البدء", + "interrupted": "تمت مقاطعته", + "force_stopped": "توقفت القوة", + "rinse": "شطف", + "unknown": "مجهول", + "clean": "نظيف" + } + }, + "washer_program": { + "name": "برنامج" + }, + "time_remaining": { + "name": "الوقت المتبقي" + }, + "total_duration": { + "name": "المدة الإجمالية" + }, + "cycle_progress": { + "name": "تقدم" + }, + "current_power": { + "name": "الطاقة الحالية" + }, + "elapsed_time": { + "name": "الوقت المنقضي" + }, + "debug_info": { + "name": "معلومات التصحيح" + }, + "match_confidence": { + "name": "مباراة الثقة" + }, + "top_candidates": { + "name": "كبار المرشحين", + "state": { + "none": "لا أحد" + } + }, + "current_phase": { + "name": "المرحلة الحالية" + }, + "wash_phase": { + "name": "مرحلة" + }, + "profile_cycle_count": { + "name": "عدد الملف الشخصي {profile_name}.", + "unit_of_measurement": "دورات" + }, + "suggestions": { + "name": "الإعدادات المقترحة" + }, + "pump_runs_today": { + "name": "تشغيل المضخة (آخر 24 ساعة)" + }, + "cycle_count": { + "name": "عدد الدورات" + } + }, + "select": { + "program_select": { + "name": "برنامج دورة", + "state": { + "auto_detect": "الكشف التلقائي" + } + } + }, + "button": { + "force_end_cycle": { + "name": "إنهاء قسري للدورة" + }, + "pause_cycle": { + "name": "دورة الإيقاف المؤقت" + }, + "resume_cycle": { + "name": "استئناف الدورة" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "مطلوب معرف جهاز صالح." + }, + "cycle_id_required": { + "message": "مطلوب معرف دورة صالح." + }, + "profile_name_required": { + "message": "مطلوب ملف تعريف صالح." + }, + "device_not_found": { + "message": "لم يتم العثور على الجهاز." + }, + "no_config_entry": { + "message": "لم يتم العثور على إدخال تكوين للجهاز." + }, + "integration_not_loaded": { + "message": "لم يتم تحميل التكامل لهذا الجهاز." + }, + "cycle_not_found_or_no_power": { + "message": "لم يتم العثور على الدورة أو لا تحتوي على بيانات طاقة." + }, + "trim_failed_empty_window": { + "message": "فشل القطع - لم يتم العثور على الدورة، أو لا توجد بيانات طاقة، أو أن النافذة الناتجة فارغة." + }, + "trim_invalid_range": { + "message": "يجب أن تكون trim_end_s أكبر من trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/bg.json b/custom_components/ha_washdata/translations/bg.json new file mode 100644 index 0000000..d1b6ede --- /dev/null +++ b/custom_components/ha_washdata/translations/bg.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Настройка на WashData", + "description": "Конфигурирайте вашата пералня или друг уред.\n\nНеобходим е сензор за мощност.\n\n**След това ще бъдете попитани дали искате да създадете своя първи профил.**", + "data": { + "name": "Име на устройството", + "device_type": "Тип устройство", + "power_sensor": "Сензор за мощност", + "min_power": "Минимален праг на мощност (W)" + }, + "data_description": { + "name": "Удобно име за това устройство (напр. „Перална машина“, „Миялна машина“).", + "device_type": "Какъв тип уред е това? Помага за персонализиране на откриването и етикетирането.", + "power_sensor": "Сензорният обект, който отчита консумацията на енергия в реално време (във ватове) от вашия интелигентен щепсел.", + "min_power": "Отчитанията на мощност над този праг (във ватове) показват, че уредът работи. Започнете с 2W за повечето устройства." + } + }, + "first_profile": { + "title": "Създаване на първи профил", + "description": "**Цикъл** е пълна работа на вашия уред (напр. зареждане с пране). **Профил** групира тези цикли по тип (напр. „Памук“, „Бързо пране“). Създаването на профил сега помага незабавно да оцени продължителността на интеграцията.\n\nМожете ръчно да изберете този профил в контролите на устройството, ако не бъде открит автоматично.\n\n**Оставете „Име на профил“ празно, за да пропуснете тази стъпка.**", + "data": { + "profile_name": "Име на профил (по избор)", + "manual_duration": "Очаквана продължителност (минути)" + } + } + }, + "error": { + "cannot_connect": "Неуспешно свързване", + "invalid_auth": "Невалидно удостоверяване", + "unknown": "Неочаквана грешка" + }, + "abort": { + "already_configured": "Устройството вече е конфигурирано" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Преглед на отзивите за обучение", + "settings": "Настройки", + "notifications": "Известия", + "manage_cycles": "Управление на цикли", + "manage_profiles": "Управление на профили", + "manage_phase_catalog": "Управление на каталога на фазите", + "record_cycle": "Цикъл на запис (ръчно)", + "diagnostics": "Диагностика и поддръжка" + } + }, + "apply_suggestions": { + "title": "Копиране на предложените стойности", + "description": "Това ще копира текущите предложени стойности във вашите запазени настройки. Това е еднократно действие и не позволява автоматично презаписване.\n\nПредложени стойности за копиране:\n{suggested}", + "data": { + "confirm": "Потвърдете действието за копиране" + } + }, + "apply_suggestions_confirm": { + "title": "Преглед на предложените промени", + "description": "WashData намери **{pending_count}** предложена(и) стойност(и) за прилагане.\n\nПромени:\n{changes}\n\n✅ Поставете отметка в квадратчето по-долу и щракнете върху **Изпращане**, за да приложите и запишете тези промени незабавно.\n❌ Оставете го без отметка и щракнете върху **Изпращане**, за да отмените и да се върнете назад.", + "data": { + "confirm_apply_suggestions": "Приложете и запазете тези промени" + } + }, + "settings": { + "title": "Настройки", + "description": "{deprecation_warning}Настройте праговете за откриване, обучението и разширеното поведение. Стандартните настройки работят добре за повечето настройки.\n\nПрепоръчани настройки, налични в момента: {suggestions_count}.\nАко е над 0, отворете Разширени настройки и използвайте 'Прилагане на предложените стойности', за да прегледате препоръките.", + "data": { + "apply_suggestions": "Прилагане на предложените стойности", + "device_type": "Тип устройство", + "power_sensor": "Обект на сензор за мощност", + "min_power": "Минимална мощност (W)", + "off_delay": "Закъснение при край на цикъла (s)", + "notify_actions": "Действия за уведомяване", + "notify_people": "Отложете доставката, докато тези хора не се приберат", + "notify_only_when_home": "Отлагане на известията, докато избраното лице не се прибере", + "notify_fire_events": "Задействане на събития за автоматизации", + "notify_start_services": "Начало на цикъла - Цели за уведомяване", + "notify_finish_services": "Край на цикъла - Цели за уведомяване", + "notify_live_services": "Напредък на живо - Цели за уведомяване", + "notify_before_end_minutes": "Известие преди завършване (минути преди края)", + "notify_title": "Заглавие на известието", + "notify_icon": "Икона за известяване", + "notify_start_message": "Стартиране на формат на съобщение", + "notify_finish_message": "Завършете формата на съобщението", + "notify_pre_complete_message": "Формат на съобщението преди завършване", + "show_advanced": "Редактиране на разширени настройки (следваща стъпка)" + }, + "data_description": { + "apply_suggestions": "Поставете отметка в това квадратче, за да копирате стойности, предложени от алгоритъма за обучение, във формуляра. Преглед преди запазване.\n\nПредложено (от обучението):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Изберете типа уред (пералня, сушилня, съдомиялна, кафе машина, EV). Този етикет се съхранява с всеки цикъл за по-добра организация.", + "power_sensor": "Сензорният обект, отчитащ мощността в реално време (във ватове). Можете да промените това по всяко време, без да губите исторически данни – полезно, ако смените интелигентен щепсел.", + "min_power": "Отчитанията на мощност над този праг (във ватове) показват, че уредът работи. Започнете с 2W за повечето устройства.", + "off_delay": "Секунди, изгладената мощност трябва да остане под прага на спиране, за да се отбележи завършване. По подразбиране: 120s.", + "notify_actions": "Опционална последователност от действия на Home Assistant, която да се изпълнява за известия (скриптове, уведомяване, TTS и т.н.). Ако е зададено, действията се изпълняват преди услугата за уведомяване за резервен вариант.", + "notify_people": "Обекти хора, използвани за стробиране на присъствие. Когато е активирано, доставката на известия се забавя, докато поне един избран човек не е вкъщи.", + "notify_only_when_home": "Ако е активирано, забавяне на известията, докато поне един избран човек не е у дома.", + "notify_fire_events": "Ако е активирано, задействайте събития на Home Assistant за начало и край на цикъл (ha_washdata_cycle_started и ha_washdata_cycle_ended), за да управлявате автоматизации.", + "notify_start_services": "Една или повече услуги за уведомяване, които да предупреждават, когато започне цикъл. Оставете празно, за да пропуснете началните известия.", + "notify_finish_services": "Една или повече услуги за уведомяване, за да предупреждават, когато даден цикъл приключи или наближи завършването си. Оставете празно, за да пропуснете известията за приключване.", + "notify_live_services": "Една или повече услуги за уведомяване, за да получават актуализации на напредъка на живо, докато цикълът работи (само за мобилно придружаващо приложение). Оставете празно, за да пропуснете актуализациите на живо.", + "notify_before_end_minutes": "Минути преди очаквания край на цикъла за задействане на известие. Задайте 0, за да деактивирате. По подразбиране: 0.", + "notify_title": "Персонализирано заглавие за известия. Поддържа {device} контейнер.", + "notify_icon": "Икона за използване за известия (напр. mdi:washing-machine). Оставете празно, за да изпратите без икона.", + "notify_start_message": "Съобщение се изпраща при започване на цикъл. Поддържа {device} контейнер.", + "notify_finish_message": "Съобщение се изпраща, когато цикълът приключи. Поддържа {device}, {duration}, {program}, {energy_kwh}, {cost} контейнери.", + "notify_pre_complete_message": "Текстът на повтарящите се актуализации на напредъка на живо, докато цикълът тече. Поддържа {device}, {minutes}, {program} контейнери." + } + }, + "notifications": { + "title": "Известия", + "description": "Конфигурирайте канали за уведомяване, шаблони и незадължителни актуализации на мобилния напредък на живо.", + "data": { + "notify_actions": "Действия за уведомяване", + "notify_people": "Отложете доставката, докато тези хора не се приберат", + "notify_only_when_home": "Отлагане на известията, докато избраното лице не се прибере", + "notify_fire_events": "Задействане на събития за автоматизации", + "notify_start_services": "Начало на цикъла - Цели за уведомяване", + "notify_finish_services": "Край на цикъла - Цели за уведомяване", + "notify_live_services": "Напредък на живо - Цели за уведомяване", + "notify_before_end_minutes": "Известие преди завършване (минути преди края)", + "notify_live_interval_seconds": "Интервал на актуализиране на живо (секунди)", + "notify_live_overrun_percent": "Разрешение за превишаване на актуализацията на живо (%)", + "notify_live_chronometer": "Таймер за обратно броене на хронометър", + "notify_title": "Заглавие на известието", + "notify_icon": "Икона за известяване", + "notify_start_message": "Стартиране на формат на съобщение", + "notify_finish_message": "Завършете формата на съобщението", + "notify_pre_complete_message": "Формат на съобщението преди завършване", + "energy_price_entity": "Цена на енергията - Субект (по избор)", + "energy_price_static": "Цена на енергията - статична стойност (по избор)", + "go_back": "Върни се без запазване", + "notify_reminder_message": "Формат на съобщението за напомняне", + "notify_timeout_seconds": "Автоматично изключване след (секунди)", + "notify_channel": "Канал за уведомяване (статус/жив/реминдер)", + "notify_finish_channel": "Завършен канал за уведомяване" + }, + "data_description": { + "go_back": "Отметнете това и натиснете Изпращане, за да отхвърлите всички промени по-горе и да се върнете в главното меню.", + "notify_actions": "Опционална последователност от действия на Home Assistant, която да се изпълнява за известия (скриптове, уведомяване, TTS и т.н.). Ако е зададено, действията се изпълняват преди услугата за уведомяване за резервен вариант.", + "notify_people": "Обекти хора, използвани за стробиране на присъствие. Когато е активирано, доставката на известия се забавя, докато поне един избран човек не е вкъщи.", + "notify_only_when_home": "Ако е активирано, забавяне на известията, докато поне един избран човек не е у дома.", + "notify_fire_events": "Ако е активирано, задействайте събития на Home Assistant за начало и край на цикъл (ha_washdata_cycle_started и ha_washdata_cycle_ended), за да управлявате автоматизации.", + "notify_start_services": "Една или повече услуги за уведомяване, които да предупреждават, когато започне цикъл (напр. notify.mobile_app_my_phone). Оставете празно, за да пропуснете началните известия.", + "notify_finish_services": "Една или повече услуги за уведомяване, за да предупреждават, когато даден цикъл приключи или наближи завършването си. Оставете празно, за да пропуснете известията за приключване.", + "notify_live_services": "Една или повече услуги за уведомяване, за да получават актуализации на напредъка на живо, докато цикълът работи (само за мобилно придружаващо приложение). Оставете празно, за да пропуснете актуализациите на живо.", + "notify_before_end_minutes": "Минути преди очаквания край на цикъла за задействане на известие. Задайте 0, за да деактивирате. По подразбиране: 0.", + "notify_live_interval_seconds": "Колко често се изпращат актуализации на напредъка по време на изпълнение. По-ниските стойности са по-отзивчиви, но консумират повече известия. Прилагат се минимум 30 секунди - стойности под 30 секунди се закръглят автоматично до 30 секунди.", + "notify_live_overrun_percent": "Марж на безопасност над прогнозния брой на актуализациите за дълги/продължаващи цикли (например 20% позволява 1,2x прогнозни актуализации).", + "notify_live_chronometer": "Когато е активирана, всяка актуализация на живо включва обратно броене на хронометъра до очакваното време за финал. Таймерът за известия отчита автоматично на устройството между актуализациите (само за придружаващото приложение за Android).", + "notify_title": "Персонализирано заглавие за известия. Поддържа {device} контейнер.", + "notify_icon": "Икона за използване за известия (напр. mdi:washing-machine). Оставете празно, за да изпратите без икона.", + "notify_start_message": "Съобщение се изпраща при започване на цикъл. Поддържа {device} контейнер.", + "notify_finish_message": "Съобщение се изпраща, когато цикълът приключи. Поддържа {device}, {duration}, {program}, {energy_kwh}, {cost} контейнери.", + "energy_price_entity": "Изберете цифров обект, който съдържа текущата цена на електроенергията (напр. sensor.electricity_price, input_number.energy_rate). Когато е зададено, активира контейнера {cost}. Има предимство пред статичната стойност, ако и двете са конфигурирани.", + "energy_price_static": "Фиксирана цена на електроенергия за kWh. Когато е зададено, активира контейнера {cost}. Игнорира се, ако даден обект е конфигуриран по-горе. Добавете своя валутен символ в шаблона на съобщението, напр. {cost} €.", + "notify_reminder_message": "Текстът на еднократното напомняне изпрати конфигурирания брой минути преди прогнозния край. Различно от повтарящите се актуализации на живо по-горе. Поддържа {device}, {minutes}, {program} контейнери.", + "notify_timeout_seconds": "Автоматично отхвърляне на уведомленията след толкова много секунди (препратено като приложение за компаньонка). Настройте се на 0, за да ги задържите докато се освободите. По подразбиране: 0.", + "notify_channel": "Android канал за уведомяване за състоянието, напредъка на живот и напомняне на уведомленията. Звукът и значението на канала са конфигурирани в приложението спътник за първи път името на канала се използва - WashData определя само името на канала. Оставете празно за приложението по подразбиране.", + "notify_finish_channel": "Отделете канала за завършен сигнал (също използван от напомнянето и натрапчивото подсещане за чакащото пране), за да може да има свой собствен звук. Оставете празни за повторна употреба на канала по-горе.", + "notify_pre_complete_message": "Текстът на повтарящите се актуализации на напредъка на живо, докато цикълът тече. Поддържа {device}, {minutes}, {program} контейнери." + } + }, + "advanced_settings": { + "title": "Разширени настройки", + "description": "Настройте праговете за откриване, обучението и разширеното поведение. Стандартните настройки работят добре за повечето конфигурации.", + "sections": { + "suggestions_section": { + "name": "Предложени настройки", + "description": "Налични са {suggestions_count} научени предложения. Отметнете 'Прилагане на предложените стойности', за да прегледате предложените промени преди запазване.", + "data": { + "apply_suggestions": "Прилагане на предложените стойности" + }, + "data_description": { + "apply_suggestions": "Поставете отметка в това квадратче, за да отворите стъпка за преглед за предложени стойности от алгоритъма за обучение. Нищо не се запазва автоматично.\n\nПредложено (от обучението):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Откриване и прагове на мощността", + "description": "Определя кога уредът се счита за ВКЛЮЧЕН или ИЗКЛЮЧЕН, енергийните прагове и времето на преходите между състоянията.", + "data": { + "start_duration_threshold": "Продължителност на стартиране на отскок (s)", + "start_energy_threshold": "Начален праг на енергия (Wh)", + "completion_min_seconds": "Минимално време за изпълнение (секунди)", + "start_threshold_w": "Начален праг (W)", + "stop_threshold_w": "Праг на спиране (W)", + "end_energy_threshold": "Краен праг на енергия (Wh)", + "running_dead_zone": "Работна мъртва зона (секунди)", + "end_repeat_count": "Край на броя на повторенията", + "min_off_gap": "Минимална разлика между цикли (с)", + "sampling_interval": "Интервал на вземане на проби (с)" + }, + "data_description": { + "start_duration_threshold": "Игнорирайте кратки пикове на мощността, по-кратки от тази продължителност (секунди). Филтрира пиковете при зареждане (напр. 60 W за 2 s), преди да започне истинският цикъл. По подразбиране: 5s.", + "start_energy_threshold": "Цикълът трябва да натрупа поне толкова много енергия (Wh) по време на СТАРТ фазата, за да бъде потвърден. По подразбиране: 0,005 Wh.", + "completion_min_seconds": "Цикли, по-кратки от това, ще бъдат маркирани като „прекъснати“, дори ако завършат естествено. По подразбиране: 600s.", + "start_threshold_w": "Мощността трябва да се повиши НАД този праг, за да СТАРТИРА цикъл. Задайте по-висока от вашата мощност в режим на готовност. По подразбиране: min_power + 1 W.", + "stop_threshold_w": "Мощността трябва да падне ПОД този праг, за да КРАЙ цикъл. Задайте малко над нулата, за да избегнете шума, предизвикващ фалшиви краища. По подразбиране: 0,6 * min_power.", + "end_energy_threshold": "Цикълът завършва само ако енергията в последния прозорец за забавяне на изключване е под тази стойност (Wh). Предотвратява фалшиви краища, ако мощността варира близо до нула. По подразбиране: 0,05 Wh.", + "running_dead_zone": "След като цикълът започне, игнорирайте спадовете на мощността за толкова много секунди, за да предотвратите откриване на фалшив край по време на първоначалната фаза на центрофугиране. Задайте 0, за да деактивирате. По подразбиране: 0s.", + "end_repeat_count": "Колко пъти условието за край (мощност под прага за off_delay) трябва да бъде изпълнено последователно преди края на цикъла. По-високите стойности предотвратяват фалшиви краища по време на паузи. По подразбиране: 1.", + "min_off_gap": "Ако даден цикъл започне в рамките на толкова секунди след края на предишния, те ще бъдат ОБЕДИНЕНИ в един цикъл.", + "sampling_interval": "Минимален интервал на вземане на проби в секунди. Актуализациите, по-бързи от тази скорост, ще бъдат игнорирани. По подразбиране: 30 s (2 s за перални машини, перални със сушилни и съдомиялни машини)." + } + }, + "matching_section": { + "name": "Съпоставяне на профили и обучение", + "description": "Определя колко агресивно WashData съпоставя активните цикли с научените профили и кога да поиска отзив.", + "data": { + "profile_match_min_duration_ratio": "Коефициент на минимална продължителност на съвпадението на профила (0,1-1,0)", + "profile_match_interval": "Интервал на съвпадение на профил (секунди)", + "profile_match_threshold": "Праг на съвпадение на профила", + "profile_unmatch_threshold": "Праг за несъответствие на профил", + "auto_label_confidence": "Увереност на автоматичното етикетиране (0-1)", + "learning_confidence": "Доверие при искане за обратна връзка (0-1)", + "suppress_feedback_notifications": "Потискане на известията за обратна връзка", + "duration_tolerance": "Толерантност на продължителността (0-0,5)", + "smoothing_window": "Изглаждащ прозорец (проби)", + "profile_duration_tolerance": "Толеранс на продължителността на съвпадението на профила (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Съотношение на минимална продължителност за съвпадение на профили. Работният цикъл трябва да бъде поне тази част от продължителността на профила, за да съответства. По-ниско = по-ранно съвпадение. По подразбиране: 0,50 (50%).", + "profile_match_interval": "Колко често (в секунди) да се прави опит за съвпадение на профил по време на цикъл. По-ниските стойности осигуряват по-бързо откриване, но използват повече CPU. По подразбиране: 300s (5 минути).", + "profile_match_threshold": "Минимален DTW резултат за сходство (0,0–1,0), необходим, за да се приеме профилът за съвпадение. По-високо = по-стриктно съответствие, по-малко фалшиви положителни резултати. По подразбиране: 0.4.", + "profile_unmatch_threshold": "Резултат за подобие на DTW (0,0–1,0), под който предварително съответстващ профил се отхвърля. Трябва да е по-нисък от прага на съответствие, за да се предотврати трептене. По подразбиране: 0,35.", + "auto_label_confidence": "Автоматично етикетиране на цикли на или над тази увереност при завършване (0,0–1,0).", + "learning_confidence": "Минимална увереност за искане на потребителска проверка чрез събитие (0,0–1,0).", + "suppress_feedback_notifications": "Когато е активиран, WashData ще продължи да проследява и изисква вътрешна обратна връзка, но няма да показва постоянни известия, които ви молят да потвърдите циклите. Полезно, когато циклите винаги се откриват правилно и смятате, че подканите ви разсейват.", + "duration_tolerance": "Толеранс за оценките на оставащото време по време на изпълнение (0,0–0,5 съответства на 0–50%).", + "smoothing_window": "Брой скорошни показания на мощността, използвани за изглаждане на пълзяща средна.", + "profile_duration_tolerance": "Допустимо отклонение, използвано от съпоставяне на профили за дисперсия на общата продължителност (0,0–0,5). Поведение по подразбиране: ±25%." + } + }, + "timing_section": { + "name": "Време, поддръжка и отстраняване на грешки", + "description": "Watchdog, нулиране на напредъка, автоматична поддръжка и показване на обекти за отстраняване на грешки.", + "data": { + "watchdog_interval": "Интервал на Watchdog (секунди)", + "no_update_active_timeout": "Време за изчакване без актуализация (s)", + "progress_reset_delay": "Забавяне на нулирането на напредъка (секунди)", + "auto_maintenance": "Активирайте автоматичната поддръжка", + "expose_debug_entities": "Разкриване на обекти за отстраняване на грешки", + "save_debug_traces": "Запазване на следите за отстраняване на грешки" + }, + "data_description": { + "watchdog_interval": "Секунди между проверките на watchdog по време на работа. По подразбиране: 5s. ПРЕДУПРЕЖДЕНИЕ: Уверете се, че това е ПО-ВИСОКО от интервала за актуализиране на вашия сензор, за да избегнете фалшиви спирания (напр. ако сензорът се актуализира на всеки 60 секунди, използвайте 65 секунди+).", + "no_update_active_timeout": "За сензори за публикуване при промяна: прекратете принудително АКТИВЕН цикъл само ако не пристигнат актуализации за толкова много секунди. Завършването с ниска мощност все още използва закъснението за изключване.", + "progress_reset_delay": "След завършване на цикъл (100%), изчакайте толкова секунди бездействие, преди да нулирате прогреса на 0%. По подразбиране: 300s.", + "auto_maintenance": "Активиране на автоматична поддръжка (поправяне на проби, обединяване на фрагменти).", + "expose_debug_entities": "Показване на разширени сензори (увереност, фаза, неяснота) за отстраняване на грешки.", + "save_debug_traces": "Съхранявайте подробни данни за класиране и проследяване на мощността в историята (увеличава използването на хранилището)." + } + }, + "anti_wrinkle_section": { + "name": "Щит против бръчки (сушилни)", + "description": "Игнорирайте завъртанията на барабана след края на цикъла, за да предотвратите призрачни цикли. Само за сушилня / пералня със сушилня.", + "data": { + "anti_wrinkle_enabled": "Щит против бръчки (само за сушилня)", + "anti_wrinkle_max_power": "Максимална мощност против бръчки (W)", + "anti_wrinkle_max_duration": "Максимална продължителност (s) против бръчки", + "anti_wrinkle_exit_power": "Изходна мощност против бръчки (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Игнорирайте кратките въртения на барабана с ниска мощност след края на цикъла, за да предотвратите повторни цикли (само за сушилня/пералня със сушилня).", + "anti_wrinkle_max_power": "Изблици на или под тази мощност се третират като ротации против бръчки вместо нови цикли. Прилага се само когато Щитът против бръчки е активиран.", + "anti_wrinkle_max_duration": "Максимална дължина на спукване, която да се третира като ротация против бръчки. Прилага се само когато Щитът против бръчки е активиран.", + "anti_wrinkle_exit_power": "Ако мощността падне под това ниво, незабавно излезте от анти-бръчките (в позиция 'изключено'). Прилага се само когато Щитът против бръчки е активиран." + } + }, + "delay_start_section": { + "name": "Откриване на отложен старт", + "description": "Третирайте продължителния режим на готовност с ниска консумация като състояние 'Изчакване за стартиране', докато цикълът действително не започне.", + "data": { + "delay_start_detect_enabled": "Разрешете откриването на отложен старт", + "delay_confirm_seconds": "Отложен старт: време за потвърждение на режим на готовност (s)", + "delay_timeout_hours": "Отложен старт: Максимално време на изчакване (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Когато е активирано, кратък скок на ниска мощност, последван от продължително захранване в режим на готовност, се разпознава като отложен старт. Сензорът за състояние показва „Изчакване за стартиране“ по време на забавянето. Не се проследява време или прогрес на програмата, докато цикълът действително не започне. Изисква start_threshold_w да бъде зададено над пиковата мощност на източване.", + "delay_confirm_seconds": "Мощността трябва да остане между прага за спиране (W) и прага за стартиране (W) за толкова секунди, преди WashData да премине в състояние 'Изчакване за стартиране'. Това филтрира кратки пикове при навигация в менюто. По подразбиране: 60 s.", + "delay_timeout_hours": "Безопасно изчакване: ако машината все още е в „Изчакване за стартиране“ след толкова много часове, WashData се нулира на Изкл. По подразбиране: 8 часа." + } + }, + "external_triggers_section": { + "name": "Външни тригери, врата и пауза", + "description": "Незадължителен външен сензор за край на цикъла, сензор за врата за откриване на пауза / изпразване и превключвател за прекъсване на захранването при пауза.", + "data": { + "external_end_trigger_enabled": "Активиране на външно задействане за край на цикъл", + "external_end_trigger": "Външен сензор за край на цикъла", + "external_end_trigger_inverted": "Инвертиране на логиката на задействане (задействане на ИЗКЛ.)", + "door_sensor_entity": "Сензор за врата", + "pause_cuts_power": "Прекъснете захранването при пауза", + "switch_entity": "Превключвател на обект (за спиране на захранването на пауза)", + "notify_unload_delay_minutes": "Забавяне на известието за чакащо пране (мин.)" + }, + "data_description": { + "external_end_trigger_enabled": "Разрешете наблюдението на външен двоичен сензор, за да задействате завършване на цикъла.", + "external_end_trigger": "Изберете двоичен сензорен обект. Когато този сензор се задейства, текущият цикъл ще завърши със статус „завършен“.", + "external_end_trigger_inverted": "По подразбиране тригерът се задейства, когато сензорът се включи. Поставете отметка в това квадратче, за да се задейства, когато сензорът се ИЗКЛЮЧИ вместо това.", + "door_sensor_entity": "Допълнителен бинарен сензор за вратата на машината (включено = отворено, изключено = затворено). Когато вратата се отвори по време на активен цикъл, WashData ще потвърди, че цикълът е умишлено поставен на пауза. След края на цикъла, ако вратата все още е затворена, състоянието се променя на „Почистено“, докато вратата не бъде отворена. Забележка: затварянето на вратата НЕ възобновява автоматично поставен на пауза цикъл - възобновяването трябва да се задейства ръчно чрез бутона Възобновяване на цикъла или услугата.", + "pause_cuts_power": "Когато поставяте на пауза чрез бутона или услугата Пауза на цикъл, изключете и конфигурирания превключвател. Влиза в сила само ако за това устройство е конфигуриран обект за превключване.", + "switch_entity": "Обектът за превключване, който да превключвате при пауза или възобновяване. Изисква се, когато е активирано „Прекъсване на захранването при пауза“.", + "notify_unload_delay_minutes": "Изпратете известие чрез канала за уведомяване за приключване, след като цикълът приключи и вратата не е била отваряна толкова много минути (изисква сензор за врата). Задайте 0, за да деактивирате. По подразбиране: 60 мин." + } + }, + "pump_section": { + "name": "Наблюдение на помпата", + "description": "Само за устройства от тип помпа. Задейства събитие, ако цикълът на помпата продължи по-дълго от това време.", + "data": { + "pump_stuck_duration": "Предупредителен праг (и) за блокиране на помпата (само помпа)" + }, + "data_description": { + "pump_stuck_duration": "Ако цикъл на помпа продължава да работи след толкова много секунди, събитие ha_washdata_pump_stuck се задейства веднъж. Използвайте това, за да откриете заседнал двигател (типичната работа на помпата е под 60 s). По подразбиране: 1800 s (30 min). Само тип помпено устройство." + } + }, + "device_link_section": { + "name": "Връзка с устройство", + "description": "По избор свържете това устройство WashData към съществуващо устройство Home Assistant (например смарт щепсел или самия уред). След това устройството WashData се показва като \"Свързано чрез\" това устройство. Оставете празни, за да запазите WashData като самостоятелно устройство.", + "data": { + "linked_device": "Свързано устройство" + }, + "data_description": { + "linked_device": "Изберете устройството, с което да свържете WashData. С това се добавя препратка \"Свързана чрез\" в регистъра на устройствата; WashData запазва своя собствена карта за устройство и структури. Изчистване на избора за премахване на връзката." + } + } + } + }, + "diagnostics": { + "title": "Диагностика и поддръжка", + "description": "Изпълнявайте действия по поддръжка като обединяване на фрагментирани цикли или мигриране на съхранени данни.\n\n**Използване на хранилище**\n\n- Размер на файла: {file_size_kb} KB\n- Цикли: {cycle_count}\n- Профили: {profile_count}\n- Следи за отстраняване на грешки: {debug_count}", + "menu_options": { + "reprocess_history": "Поддръжка: Повторна обработка и оптимизиране на данни", + "clear_debug_data": "Изчистване на данните за отстраняване на грешки (освобождаване на място)", + "wipe_history": "Изтриване на ВСИЧКИ данни за това устройство (необратимо)", + "export_import": "Експортиране/импортиране на JSON с настройки (копиране/поставяне)", + "menu_back": "← Назад" + } + }, + "clear_debug_data": { + "title": "Изчистване на данните за отстраняване на грешки", + "description": "Сигурни ли сте, че искате да изтриете всички съхранени следи за отстраняване на грешки? Това ще освободи място, но ще премахне подробната информация за класирането от минали цикли." + }, + "manage_cycles": { + "title": "Управление на цикли", + "description": "Скорошни цикли:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Автоматично етикетиране на стари цикли", + "select_cycle_to_label": "Изберете цикъл за етикетиране", + "select_cycle_to_delete": "Изтриване на цикъл", + "interactive_editor": "Интерактивен редактор за сливане/разделяне", + "trim_cycle_select": "Данни за цикъл на подрязване", + "menu_back": "← Назад" + } + }, + "manage_cycles_empty": { + "title": "Управление на цикли", + "description": "Все още няма записани цикли.", + "menu_options": { + "auto_label_cycles": "Автоматично етикетиране на стари цикли", + "menu_back": "← Назад" + } + }, + "manage_profiles": { + "title": "Управление на профили", + "description": "Резюме на профила:\n{profile_summary}", + "menu_options": { + "create_profile": "Създаване на нов профил", + "edit_profile": "Редактиране/Преименуване на профил", + "delete_profile_select": "Изтриване на профил", + "profile_stats": "Статистика на профила", + "cleanup_profile": "Почистване на хронологията - Графика и изтриване", + "assign_profile_phases_select": "Задайте фазови диапазони", + "menu_back": "← Назад" + } + }, + "manage_profiles_empty": { + "title": "Управление на профили", + "description": "Все още няма създадени профили.", + "menu_options": { + "create_profile": "Създаване на нов профил", + "menu_back": "← Назад" + } + }, + "manage_phase_catalog": { + "title": "Управление на каталога на фазите", + "description": "Каталог на фазите (по подразбиране за това устройство + персонализирани фази):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Създайте нова фаза", + "phase_catalog_edit_select": "Редактиране на фаза", + "phase_catalog_delete": "Изтриване на фаза", + "menu_back": "← Назад" + } + }, + "phase_catalog_create": { + "title": "Създайте персонализирана фаза", + "description": "Създайте персонализирано име и описание на фаза и изберете за кой тип устройство се прилага.", + "data": { + "target_device_type": "Тип целево устройство", + "phase_name": "Име на фаза", + "phase_description": "Описание на фазата" + } + }, + "phase_catalog_edit_select": { + "title": "Редактиране на персонализирана фаза", + "description": "Изберете персонализирана фаза за редактиране.", + "data": { + "phase_name": "Персонализирана фаза" + } + }, + "phase_catalog_edit": { + "title": "Редактиране на персонализирана фаза", + "description": "Фаза на редактиране: {phase_name}", + "data": { + "phase_name": "Име на фаза", + "phase_description": "Описание на фазата" + } + }, + "phase_catalog_delete": { + "title": "Изтриване на персонализирана фаза", + "description": "Изберете персонализирана фаза за изтриване. Всички присвоени диапазони, използващи тази фаза, ще бъдат премахнати.", + "data": { + "phase_name": "Персонализирана фаза" + } + }, + "assign_profile_phases": { + "title": "Задайте фазови диапазони", + "description": "Визуализация на фаза за профил: **{profile_name}**\n\n{timeline_svg}\n\n**Текущи диапазони:**\n{current_ranges}\n\nИзползвайте действията по-долу, за да добавяте, редактирате, изтривате или запазвате диапазони.", + "menu_options": { + "assign_profile_phases_add": "Добавяне на фазов диапазон", + "assign_profile_phases_edit_select": "Редактиране на фазовия диапазон", + "assign_profile_phases_delete": "Изтриване на фазов диапазон", + "phase_ranges_clear": "Изчистване на всички диапазони", + "assign_profile_phases_auto_detect": "Автоматично откриване на фази", + "phase_ranges_save": "Запазване и връщане", + "menu_back": "← Назад" + } + }, + "assign_profile_phases_add": { + "title": "Добавяне на фазов диапазон", + "description": "Добавете един диапазон от фази към текущата чернова.", + "data": { + "phase_name": "Фаза", + "start_min": "Начална минута", + "end_min": "Крайна минута" + } + }, + "assign_profile_phases_edit_select": { + "title": "Редактиране на фазовия диапазон", + "description": "Изберете диапазон за редактиране.", + "data": { + "range_index": "Фазов обхват" + } + }, + "assign_profile_phases_edit": { + "title": "Редактиране на фазовия диапазон", + "description": "Актуализирайте избрания фазов диапазон.", + "data": { + "phase_name": "Фаза", + "start_min": "Начална минута", + "end_min": "Крайна минута" + } + }, + "assign_profile_phases_delete": { + "title": "Изтриване на фазов диапазон", + "description": "Изберете диапазон, който да премахнете от черновата.", + "data": { + "range_index": "Фазов обхват" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Автоматично разпознати фази", + "description": "Профил: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} фази са открити автоматично.** Изберете действие по-долу.", + "data": { + "action": "Действие" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Име на откритите фази", + "description": "Профил: **{profile_name}**\n\n**{detected_count} открити фази:**\n{ranges_summary}\n\nЗадайте име на всяка фаза." + }, + "assign_profile_phases_select": { + "title": "Задайте фазови диапазони", + "description": "Изберете профил, за да конфигурирате фазови диапазони.", + "data": { + "profile": "Профил" + } + }, + "profile_stats": { + "title": "Статистика на профила", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Създаване на нов профил", + "description": "Създайте нов профил ръчно или от минал цикъл.\n\nПримери за имена на профили: „Деликатни“, „Тежкотоварни“, „Бързо пране“", + "data": { + "profile_name": "Име на профил", + "reference_cycle": "Референтен цикъл (по избор)", + "manual_duration": "Ръчна продължителност (минути)" + } + }, + "edit_profile": { + "title": "Редактиране на профил", + "description": "Изберете профил за преименуване", + "data": { + "profile": "Профил" + } + }, + "rename_profile": { + "title": "Редактиране на подробностите за профила", + "description": "Текущ профил: {current_name}", + "data": { + "new_name": "Име на профил", + "manual_duration": "Ръчна продължителност (минути)" + } + }, + "delete_profile_select": { + "title": "Изтриване на профил", + "description": "Изберете профил за изтриване", + "data": { + "profile": "Профил" + } + }, + "delete_profile_confirm": { + "title": "Потвърдете изтриване на профил", + "description": "⚠️ Това ще изтрие за постоянно потребителския профил: {profile_name}", + "data": { + "unlabel_cycles": "Премахнете етикета от цикли, използвайки този профил" + } + }, + "auto_label_cycles": { + "title": "Автоматично етикетиране на стари цикли", + "description": "Намерени са общо {total_count} цикъла. Профили: {profiles}", + "data": { + "confidence_threshold": "Минимална увереност (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Изберете цикъл за етикетиране", + "description": "✓ = завършено, ⚠ = възобновено, ✗ = прекъснато", + "data": { + "cycle_id": "Цикъл" + } + }, + "select_cycle_to_delete": { + "title": "Изтриване на цикъл", + "description": "⚠️ Това ще изтрие за постоянно избрания цикъл", + "data": { + "cycle_id": "Цикъл за изтриване" + } + }, + "label_cycle": { + "title": "Етикетиране на цикъл", + "description": "{cycle_info}", + "data": { + "profile_name": "Профил", + "new_profile_name": "Име на нов профил (ако се създава)" + } + }, + "post_process": { + "title": "История на процеса (сливане/разделяне)", + "description": "Почистете хронологията на цикъла чрез сливане на фрагментирани цикли и разделяне на неправилно слети цикли в рамките на времеви прозорец. Въведете брой часове за преглед назад (използвайте 999999 за всички).", + "data": { + "time_range": "Прозорец за ретроспекция (часове)", + "gap_seconds": "Сливане/Разделяне на интервал (секунди)" + }, + "data_description": { + "time_range": "Брой часове за сканиране за фрагментирани цикли за обединяване. Използвайте 999999 за обработка на всички исторически данни.", + "gap_seconds": "Праг за решаване на разделяне срещу сливане. Празнините ПО-КЪСИ от това се обединяват. Пропуските ПО-ДЪЛГИ от това се разделят. Намалете това, за да наложите разделяне на цикъл, който е обединен неправилно." + } + }, + "cleanup_profile": { + "title": "Изчистване на хронологията - Изберете профил", + "description": "Изберете профил за визуализиране. Това ще генерира графика, показваща всички минали цикли за този профил, за да помогне за идентифициране на извънредни стойности.", + "data": { + "profile": "Профил" + } + }, + "cleanup_select": { + "title": "Почистване на хронологията - Графика и изтриване", + "description": "![Графика]({graph_url})\n\n**Визуализиране {profile_name}**\n\nГрафиката показва отделни цикли като цветни линии. Идентифицирайте извънредни стойности (линии, далеч от групата) и съответстващи на цвета им в списъка по-долу, за да ги изтриете.\n\n**Изберете цикли за ПЕРМАНЕНТНО изтриване:**", + "data": { + "cycles_to_delete": "Цикли за изтриване (проверете съответстващите цветове)" + } + }, + "interactive_editor": { + "title": "Интерактивен редактор за сливане/разделяне", + "description": "Изберете ръчно действие, което да извършите върху историята на вашия цикъл.", + "menu_options": { + "editor_split": "Разделяне на цикъл (намерете пропуски)", + "editor_merge": "Обединяване на цикли (съединяване на фрагменти)", + "editor_delete": "Изтриване на цикъл(и)", + "menu_back": "← Назад" + } + }, + "editor_select": { + "title": "Изберете Цикли", + "description": "Изберете цикъла(ите) за обработка.\n\n{info_text}", + "data": { + "selected_cycles": "Цикли" + } + }, + "editor_configure": { + "title": "Конфигуриране и прилагане", + "description": "{preview_md}", + "data": { + "confirm_action": "Действие", + "merged_profile": "Резултатен профил", + "new_profile_name": "Ново име на профил", + "confirm_commit": "Да, потвърждавам това действие", + "segment_0_profile": "Профил на сегмент 1", + "segment_1_profile": "Профил на сегмент 2", + "segment_2_profile": "Профил на сегмент 3", + "segment_3_profile": "Профил на сегмент 4", + "segment_4_profile": "Профил на сегмент 5", + "segment_5_profile": "Профил на сегмент 6", + "segment_6_profile": "Профил на сегмент 7", + "segment_7_profile": "Профил на сегмент 8", + "segment_8_profile": "Профил на сегмент 9", + "segment_9_profile": "Профил на сегмент 10" + } + }, + "editor_split_params": { + "title": "Разделена конфигурация", + "description": "Регулирайте чувствителността за разделяне на този цикъл. Всеки интервал на празен ход, по-дълъг от това, ще доведе до разделяне.", + "data": { + "split_mode": "Метод на разделяне" + } + }, + "editor_split_auto_params": { + "title": "Конфигурация на автоматично разделяне", + "description": "Регулирайте чувствителността за разделяне на този цикъл. Всяка пауза на неактивност, по-дълга от тази, ще доведе до разделяне.", + "data": { + "min_gap_seconds": "Праг на паузата за разделяне (секунди)" + } + }, + "editor_split_manual_params": { + "title": "Ръчни времеви отметки за разделяне", + "description": "{preview_md}Прозорец на цикъла: **{cycle_start_wallclock} → {cycle_end_wallclock}** на {cycle_date}.\n\nВъведете една или повече времеви отметки за разделяне (`HH:MM` или `HH:MM:SS`), по една на ред. Цикълът ще бъде отрязан при всяка времева отметка — N времеви отметки създават N+1 сегмента.", + "data": { + "split_timestamps": "Времеви маркери за разделяне" + } + }, + "reprocess_history": { + "title": "Поддръжка: Повторна обработка и оптимизиране на данни", + "description": "Извършва дълбоко почистване на исторически данни:\n1. Подрязва показанията при нулева мощност (тишина).\n2. Коригира времето/продължителността на цикъла.\n3. Преизчислява всички статистически модели (обвивки).\n\n**Изпълнете това, за да коригирате проблеми с данните или да приложите нови алгоритми.**" + }, + "wipe_history": { + "title": "Изтриване на хронология (само за тестване)", + "description": "⚠️ Това ще изтрие за постоянно ВСИЧКИ съхранени цикли и профили за това устройство. Това не може да бъде отменено!" + }, + "export_import": { + "title": "Експортиране/импортиране на JSON", + "description": "Копиране/поставяне на цикли, профили и настройки за това устройство. Експортирайте по-долу или поставете JSON за импортиране.", + "data": { + "mode": "Действие", + "json_payload": "Пълна конфигурация JSON" + }, + "data_description": { + "mode": "Изберете Експортиране, за да копирате текущи данни и настройки, или Импортиране, за да поставите експортирани данни от друго WashData устройство.", + "json_payload": "За експортиране: копирайте този JSON (включва цикли, профили и всички фино настроени настройки). За импортиране: поставете JSON, експортиран от WashData." + } + }, + "record_cycle": { + "title": "Цикъл на запис", + "description": "Запишете ръчно цикъл, за да създадете чист профил без смущения.\n\nСтатус: **{status}**\nПродължителност: {duration} сек\nОбразци: {samples}", + "menu_options": { + "record_refresh": "Обновяване на състоянието", + "record_stop": "Спиране на записа (запазване и обработка)", + "record_start": "Започнете нов запис", + "record_process": "Обработка на последния запис (изрязване и запазване)", + "record_discard": "Отхвърляне на последния запис", + "menu_back": "← Назад" + } + }, + "record_process": { + "title": "Запис на процеса", + "description": "![Графика]({graph_url})\n\n**Графиката показва необработен запис.** Син = Запазване, Червен = Предложено изрязване.\n*Графиката е статична и не се актуализира с промени във формата.*\n\nЗаписът спря. Изрязванията са подравнени към откритата честота на дискретизация (~{sampling_rate}s).\n\nСурова продължителност: {duration} сек\nОбразци: {samples}", + "data": { + "head_trim": "Начало на изрязване (секунди)", + "tail_trim": "Край на изрязването (секунди)", + "save_mode": "Запазване на дестинацията", + "profile_name": "Име на профил" + } + }, + "learning_feedbacks": { + "title": "Отзиви за обучение", + "description": "Чакащи отзиви: {count}\n\n{pending_table}\n\nИзберете заявка за преглед на цикъл, която да обработите.", + "menu_options": { + "learning_feedbacks_pick": "Прегледай чакащ отзив", + "learning_feedbacks_dismiss_all": "Отхвърли всички чакащи отзиви", + "menu_back": "← Назад" + } + }, + "learning_feedbacks_pick": { + "title": "Изберете обратна връзка за преглед", + "description": "Изберете заявка за преглед на цикъл, която да отворите.", + "data": { + "selected_feedback": "Изберете цикъл за преглед" + } + }, + "learning_feedbacks_empty": { + "title": "Отзиви за обучение", + "description": "Няма открити чакащи заявки за обратна връзка.", + "menu_options": { + "menu_back": "← Назад" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Отхвърляне на всички чакащи обратни връзки", + "description": "На път сте да отхвърлите **{count}** чакащи заявки за обратна връзка. Отхвърлените цикли остават в историята ви, но няма да допринасят с нов сигнал за обучение. Това не може да бъде отменено.\n\n✅ Поставете отметка в квадратчето по-долу и щракнете върху **Изпращане**, за да отхвърлите всички.\n❌ Оставете го без отметка и щракнете върху **Изпращане**, за да отмените и да се върнете назад.", + "data": { + "confirm_dismiss_all": "Потвърди: отхвърли всички чакащи заявки за отзив" + } + }, + "resolve_feedback": { + "title": "Разрешаване на обратна връзка", + "description": "{comparison_img}{candidates_table}\n**Открит профил**: {detected_profile} ({confidence_pct}%)\n**Приблизителна продължителност**: {est_duration_min} мин\n**Действителна продължителност**: {act_duration_min} мин\n\n__Изберете действие по-долу:__", + "data": { + "action": "Какво бихте искали да правите?", + "corrected_profile": "Правилна програма (ако се коригира)", + "corrected_duration": "Правилна продължителност в секунди (ако се коригира)" + } + }, + "trim_cycle_select": { + "title": "Цикъл на изрязване - Изберете Цикъл", + "description": "Изберете цикъл за подрязване. ✓ = завършено, ⚠ = възобновено, ✗ = прекъснато", + "data": { + "cycle_id": "Цикъл" + } + }, + "trim_cycle": { + "title": "Цикъл на подрязване", + "description": "Текущ прозорец: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Действие" + } + }, + "trim_cycle_start": { + "title": "Задайте начало на изрязване", + "description": "Цикъл на прозореца: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТекущо начало: **{current_wallclock}** (+{current_offset_min} мин от началото на цикъла)\n\nИзберете нов начален час, като използвате инструмента за избор на часовник по-долу.", + "data": { + "trim_start_time": "Нов начален час", + "trim_start_min": "Нов старт (минути от началото на цикъла)" + } + }, + "trim_cycle_end": { + "title": "Задайте край на изрязване", + "description": "Цикъл на прозореца: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТекущ край: **{current_wallclock}** (+{current_offset_min} мин от началото на цикъла)\n\nИзберете нов краен час, като използвате инструмента за избор на часовник по-долу.", + "data": { + "trim_end_time": "Нов краен час", + "trim_end_min": "Нов край (минути от началото на цикъла)" + } + } + }, + "error": { + "import_failed": "Неуспешно импортиране. Моля, поставете валиден JSON за експортиране на WashData.", + "select_exactly_one": "Изберете точно един цикъл за това действие.", + "select_at_least_one": "Изберете поне един цикъл за това действие.", + "select_at_least_two": "Изберете поне два цикъла за това действие.", + "profile_exists": "Вече съществува профил с това име", + "rename_failed": "Неуспешно преименуване на профила. Профилът може да не съществува или вече е заето ново име.", + "assignment_failed": "Неуспешно присвояване на профил за цикъл", + "duplicate_phase": "Фаза с това име вече съществува.", + "phase_not_found": "Избраната фаза не е намерена.", + "invalid_phase_name": "Името на фазата не може да бъде празно.", + "phase_name_too_long": "Името на фазата е твърде дълго.", + "invalid_phase_range": "Фазовият диапазон е невалиден. Краят трябва да е по-голям от началото.", + "invalid_phase_timestamp": "Времето клеймо е невалидно. Използвайте формат ГГГГ-ММ-ДД ЧЧ:ММ или ЧЧ:ММ.", + "overlapping_phase_ranges": "Фазовите диапазони се припокриват. Коригирайте диапазоните и опитайте отново.", + "incomplete_phase_row": "Всеки ред от фази трябва да включва фаза плюс пълни начални/крайни стойности за избрания режим.", + "timestamp_mode_cycle_required": "Моля, изберете изходен цикъл, когато използвате режим на времево клеймо.", + "no_phase_ranges": "Все още няма налични фазови диапазони за това действие.", + "feedback_notification_title": "WashData: Проверете цикъл ({device})", + "feedback_notification_message": "**Устройство**: {device}\n**Програма**: {program} ({confidence}% увереност)\n**Време**: {time}\n\nWashData се нуждае от вашата помощ, за да провери този открит цикъл.\n\nМоля, отидете на **Настройки > Устройства и услуги > WashData > Конфигуриране > Отзиви за обучение**, за да потвърдите или коригирате този резултат.", + "suggestions_ready_notification_title": "WashData: Предложените настройки са готови ({device})", + "suggestions_ready_notification_message": "Сензорът **Предложени настройки** вече отчита **{count}** приложими препоръки.\n\nЗа да ги прегледате и приложите: **Настройки > Устройства и услуги > WashData > Конфигуриране > Разширени настройки > Прилагане на предложените стойности**.\n\nПредложенията не са задължителни и се показват за преглед, преди да запазите.", + "trim_range_invalid": "Началото трябва да е преди края. Моля, коригирайте точките на подрязване.", + "trim_failed": "Неуспешно изрязване. Възможно е цикълът вече да не съществува или прозорецът да е празен.", + "invalid_split_timestamp": "Времевият маркер е невалиден или е извън прозореца на цикъла. Използвайте HH:MM или HH:MM:SS в рамките на прозореца на цикъла.", + "no_split_segments_found": "От тези времеви маркери не можаха да бъдат създадени валидни сегменти. Уверете се, че всеки времеви маркер е в рамките на прозореца на цикъла и че сегментите са дълги поне 1 минута.", + "auto_tune_suggestion": "{device_type} {device_title} открити призрачни цикли. Предложена промяна на min_power: {current_min}W -> {new_min}W (не се прилага автоматично).", + "auto_tune_title": "Автоматична настройка на WashData", + "auto_tune_fallback": "{device_type} {device_title} открити призрачни цикли. Предложена промяна на min_power: {current_min}W -> {new_min}W (не се прилага автоматично)." + }, + "abort": { + "no_cycles_found": "Няма намерени цикли", + "no_split_segments_found": "В избрания цикъл не бяха намерени сегменти за разделяне.", + "cycle_not_found": "Избраният цикъл не можа да бъде зареден.", + "no_power_data": "Няма налични данни за проследяване на мощността за избрания цикъл.", + "no_profiles_found": "Няма намерени профили. Първо създайте профил.", + "no_profiles_for_matching": "Няма налични профили за съвпадение. Първо създайте профили.", + "no_unlabeled_cycles": "Всички цикли вече са етикетирани", + "no_suggestions": "Все още няма налични предложени стойности. Пуснете няколко цикъла и опитайте отново.", + "no_predictions": "Без прогнози", + "no_custom_phases": "Няма намерени персонализирани фази.", + "reprocess_success": "Успешно обработени повторно {count} цикъла.", + "debug_data_cleared": "Успешно изчистени данни за отстраняване на грешки от {count} цикъла." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Начало на цикъла", + "cycle_finish": "Край на цикъла", + "cycle_live": "Прогрес на живо" + } + }, + "common_text": { + "options": { + "unlabeled": "(без етикет)", + "create_new_profile": "Създаване на нов профил", + "remove_label": "Премахване на етикет", + "no_reference_cycle": "(Без референтен цикъл)", + "all_device_types": "Всички типове устройства", + "current_suffix": "(текущ)", + "editor_select_info": "Изберете 1 цикъл за разделяне или 2+ цикъла за сливане.", + "editor_select_info_split": "Изберете 1 цикъл за разделяне.", + "editor_select_info_merge": "Изберете 2+ цикъла за обединяване.", + "editor_select_info_delete": "Изберете 1+ цикъла за изтриване.", + "no_cycles_recorded": "Все още няма записани цикли.", + "profile_summary_line": "- **{name}**: {count} цикъла, {avg}m ср", + "no_profiles_created": "Все още няма създадени профили.", + "phase_preview": "Фазов преглед", + "phase_preview_no_curve": "Средната профилна крива все още не е налична. Изпълнение/маркиране на още цикли за този профил.", + "average_power_curve": "Средна крива на мощността", + "unit_min": "мин", + "profile_option_fmt": "{name} ({count} цикъла, ~{duration} м ср.)", + "profile_option_short_fmt": "{name} ({count} цикъла, ~{duration}m)", + "deleted_cycles_from_profile": "Изтрити {count} цикъла от {profile}.", + "not_enough_data_graph": "Няма достатъчно данни за генериране на графика.", + "no_profiles_found": "Няма намерени профили.", + "cycle_info_fmt": "Цикъл: {start}, {duration} м, Текуща: {label}", + "top_candidates_header": "### Топ кандидати", + "tbl_profile": "Профил", + "tbl_confidence": "Увереност", + "tbl_correlation": "Корелация", + "tbl_duration_match": "Съвпадение на продължителността", + "detected_profile": "Открит профил", + "estimated_duration": "Очаквана продължителност", + "actual_duration": "Действителна продължителност", + "choose_action": "__Изберете действие по-долу:__", + "tbl_count": "Брой", + "tbl_avg": "ср", + "tbl_min": "Мин", + "tbl_max": "Макс", + "tbl_energy_avg": "Енергия (ср.)", + "tbl_energy_total": "Енергия (общо)", + "tbl_consistency": "Постоянство", + "tbl_last_run": "Последна работа", + "graph_legend_title": "Легенда на графиката", + "graph_legend_body": "Синята лента представлява наблюдавания минимален и максимален обхват на консумирана мощност. Линията показва кривата на средната мощност.", + "recording_preview": "Предварителен преглед на записа", + "trim_start": "Начало на изрязване", + "trim_end": "Подрязване на края", + "split_preview_title": "Разделен преглед", + "split_preview_found_fmt": "Намерени са {count} сегмента.", + "split_preview_confirm_fmt": "Щракнете върху Потвърждаване, за да разделите този цикъл на {count} отделни цикъла.", + "merge_preview_title": "Преглед на обединяване", + "merge_preview_joining_fmt": "Присъединяване към {count} цикъла. Празнините ще бъдат запълнени с 0W показания.", + "editor_delete_preview_title": "Преглед преди изтриване", + "editor_delete_preview_intro": "Избраните цикли ще бъдат изтрити окончателно:", + "editor_delete_preview_confirm": "Натиснете Потвърди, за да изтриете окончателно тези записи на цикли.", + "no_power_preview": "*Няма налични данни за мощността за преглед.*", + "profile_comparison": "Сравнение на профили", + "actual_cycle_label": "Този цикъл (действителен)", + "storage_usage_header": "Използване на хранилище", + "storage_file_size": "Размер на файла", + "storage_cycles": "Цикли", + "storage_profiles": "Профили", + "storage_debug_traces": "Следи за отстраняване на грешки", + "overview_suffix": "Преглед", + "phase_preview_for_profile": "Визуализация на фаза за профил", + "current_ranges_header": "Текущи диапазони", + "assign_phases_help": "Използвайте действията по-долу, за да добавяте, редактирате, изтривате или запазвате диапазони.", + "profile_summary_header": "Резюме на профила", + "recent_cycles_header": "Последни цикли", + "trim_cycle_preview_title": "Преглед на циклично изрязване", + "trim_cycle_preview_no_data": "Няма налични данни за мощността за този цикъл.", + "trim_cycle_preview_kept_suffix": "запазени", + "table_program": "програма", + "table_when": "Кога", + "table_length": "Продължителност", + "table_match": "Съвпадение", + "table_profile": "Профил", + "table_cycles": "Цикли", + "table_avg_length": "Средна продължителност", + "table_last_run": "Последна работа", + "table_avg_energy": "Средна енергия", + "table_detected_program": "Открита програма", + "table_confidence": "Увереност", + "table_reported": "Докладвано", + "phase_builtin_suffix": "(вграден)", + "phase_none_available": "Няма налични фази.", + "settings_deprecation_warning": "⚠️ **Оттеглен тип устройство:** {device_type} е планирано за премахване в бъдеща версия. Съвпадащият тръбопровод на WashData не дава надеждни резултати за този клас уреди. Вашата интеграция продължава да работи през периода на отмяна; за да заглушите това предупреждение, превключете **Тип устройство** по-долу на един от поддържаните типове (пералня, сушилня, комбинирана пералня-сушилня, съдомиялна машина, фритюрник, хлебопекарна или помпа) или на **Друго (разширено)**, ако вашият уред не отговаря на нито един от поддържаните типове. **Друго (разширено)** изпраща умишлено общи настройки по подразбиране, които не са настроени за конкретно устройство, така че ще трябва сами да конфигурирате прагове, изчаквания и съвпадащи параметри; всички ваши съществуващи настройки се запазват, когато превключите.", + "phase_other_device_types": "Други видове устройства:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Разделяне на цикъл (намерете пропуски)", + "merge": "Обединяване на цикли (съединяване на фрагменти)", + "delete": "Изтриване на цикъл(и)" + } + }, + "split_mode": { + "options": { + "auto": "Автоматично откриване на неактивни паузи", + "manual": "Ръчни времеви маркери" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Поддръжка: Повторна обработка и оптимизиране на данни", + "clear_debug_data": "Изчистване на данните за отстраняване на грешки (освобождаване на място)", + "wipe_history": "Изтриване на ВСИЧКИ данни за това устройство (необратимо)", + "export_import": "Експортиране/импортиране на JSON с настройки (копиране/поставяне)" + } + }, + "export_import_mode": { + "options": { + "export": "Експортиране на всички данни", + "import": "Импортиране/обединяване на данни" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Автоматично етикетиране на стари цикли", + "select_cycle_to_label": "Изберете цикъл за етикетиране", + "select_cycle_to_delete": "Изтриване на цикъл", + "interactive_editor": "Интерактивен редактор за сливане/разделяне", + "trim_cycle": "Данни за цикъл на подрязване" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Създаване на нов профил", + "edit_profile": "Редактиране/Преименуване на профил", + "delete_profile": "Изтриване на профил", + "profile_stats": "Статистика на профила", + "cleanup_profile": "Почистване на хронологията - Графика и изтриване", + "assign_phases": "Задайте фазови диапазони" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Създайте нова фаза", + "edit_custom_phase": "Редактиране на фаза", + "delete_custom_phase": "Изтриване на фаза" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Добавяне на фазов диапазон", + "edit_range": "Редактиране на фазовия диапазон", + "delete_range": "Изтриване на фазов диапазон", + "clear_ranges": "Изчистване на всички диапазони", + "auto_detect_ranges": "Автоматично откриване на фази", + "save_ranges": "Запазване и връщане" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Обновяване на състоянието", + "stop_recording": "Спиране на записа (запазване и обработка)", + "start_recording": "Започнете нов запис", + "process_recording": "Обработка на последния запис (изрязване и запазване)", + "discard_recording": "Отхвърляне на последния запис" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Създаване на нов профил", + "existing_profile": "Добавяне към съществуващ профил", + "discard": "Отхвърляне на записа" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Потвърдете - правилно откриване", + "correct": "Правилно - Изберете програма/продължителност", + "ignore": "Игнориране - фалшив положителен/шумен цикъл", + "delete": "Изтриване - премахване от историята" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Наименувайте и прилагайте фази", + "cancel": "Върнете се назад без промени" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Задайте начална точка", + "set_end": "Задайте крайна точка", + "reset": "Нулирайте до пълна продължителност", + "apply": "Нанесете Trim", + "cancel": "Отказ" + } + }, + "device_type": { + "options": { + "washing_machine": "Пералня", + "dryer": "Сушилня", + "washer_dryer": "Комбинирана пералня-сушилня", + "dishwasher": "Съдомиялна машина", + "coffee_machine": "Кафе машина (оттеглена)", + "ev": "Електрическо превозно средство (оттеглено)", + "air_fryer": "Въздушен фритюрник", + "heat_pump": "Термопомпа (оттеглена)", + "bread_maker": "Пекарна за хляб", + "pump": "Помпа / Помпена помпа", + "oven": "Фурна (оттеглена)", + "other": "Друго (разширено)" + } + } + }, + "services": { + "label_cycle": { + "name": "Етикетиране на цикъл", + "description": "Присвоете съществуващ профил към минал цикъл или премахнете етикета.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералната машина за етикетиране." + }, + "cycle_id": { + "name": "ИД на цикъл", + "description": "ID на цикъла за етикетиране." + }, + "profile_name": { + "name": "Име на профил", + "description": "Името на съществуващ профил (създайте профили в менюто Управление на профили). Оставете празно, за да премахнете етикета." + } + } + }, + "create_profile": { + "name": "Създаване на профил", + "description": "Създайте нов профил (самостоятелен или базиран на референтен цикъл).", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята." + }, + "profile_name": { + "name": "Име на профил", + "description": "Име за новия профил (напр. „Heavy Duty“, „Delicates“)." + }, + "reference_cycle_id": { + "name": "ИД на референтен цикъл", + "description": "Незадължителен идентификатор на цикъл, на който да се основава този профил." + } + } + }, + "delete_profile": { + "name": "Изтриване на профил", + "description": "Изтрийте профил и по избор премахнете етикета на цикли, използвайки го.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята." + }, + "profile_name": { + "name": "Име на профил", + "description": "Профилът за изтриване." + }, + "unlabel_cycles": { + "name": "Премахване на етикети на цикли", + "description": "Премахнете етикета на профила от цикли, използващи този профил." + } + } + }, + "auto_label_cycles": { + "name": "Автоматично етикетиране на стари цикли", + "description": "Етикирайте със задна дата немаркирани цикли, като използвате съвпадение на профили.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята." + }, + "confidence_threshold": { + "name": "Праг на доверие", + "description": "Минимална степен на доверие (0,50-0,95) за прилагане на етикети." + } + } + }, + "export_config": { + "name": "Експортиране на конфигурация", + "description": "Експортирайте профилите, циклите и настройките на това устройство в JSON файл (на устройство).", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството пералня за износ." + }, + "path": { + "name": "Пътека", + "description": "Незадължителен абсолютен файлов път за запис (по подразбиране /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Импортиране на конфигурация", + "description": "Импортирайте профили, цикли и настройки за това устройство от JSON файл за експортиране (настройките може да бъдат презаписани).", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята за импортиране." + }, + "path": { + "name": "Пътека", + "description": "Абсолютен път до експортирания JSON файл за импортиране." + } + } + }, + "submit_cycle_feedback": { + "name": "Изпратете обратна връзка за цикъл", + "description": "Потвърдете или коригирайте автоматично открита програма след завършен цикъл. Въведете или `entry_id` (разширено) или `device_id` (препоръчително).", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството WashData (препоръчва се вместо entry_id)." + }, + "entry_id": { + "name": "ID за влизане", + "description": "ID на входа в конфигурацията за устройството (алтернатива на device_id)." + }, + "cycle_id": { + "name": "ИД на цикъл", + "description": "Идентификаторът на цикъла, показан в известието/регистрационните файлове за обратна връзка." + }, + "user_confirmed": { + "name": "Потвърдете откритата програма", + "description": "Задайте true, ако откритата програма е правилна." + }, + "corrected_profile": { + "name": "Коригиран профил", + "description": "Ако не е потвърдено, посочете правилното име на профил/програма." + }, + "corrected_duration": { + "name": "Коригирана продължителност (секунди)", + "description": "Незадължителна коригирана продължителност в секунди." + }, + "notes": { + "name": "Бележки", + "description": "Незадължителни бележки за този цикъл." + } + } + }, + "record_start": { + "name": "Начало на цикъла на запис", + "description": "Започнете ръчно записване на чист цикъл (заобикаля всички съответстващи логики).", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята за запис." + } + } + }, + "record_stop": { + "name": "Стоп на цикъла на запис", + "description": "Спрете ръчния запис.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството на пералнята за спиране на записа." + } + } + }, + "trim_cycle": { + "name": "Цикъл на подрязване", + "description": "Изрежете данните за мощността от минал цикъл до конкретен времеви прозорец.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството WashData." + }, + "cycle_id": { + "name": "ИД на цикъл", + "description": "ID на цикъла за подрязване." + }, + "trim_start_s": { + "name": "Начало на изрязване (секунди)", + "description": "Съхранявайте данните от това отместване за секунди. По подразбиране 0." + }, + "trim_end_s": { + "name": "Край на изрязването (секунди)", + "description": "Съхранявайте данните до това отместване за секунди. По подразбиране = пълна продължителност." + } + } + }, + "pause_cycle": { + "name": "Цикъл на пауза", + "description": "Поставете на пауза активния цикъл за устройство WashData.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството WashData за пауза." + } + } + }, + "resume_cycle": { + "name": "Цикъл на възобновяване", + "description": "Възобновете цикъл на пауза за устройство WashData.", + "fields": { + "device_id": { + "name": "устройство", + "description": "Устройството WashData за възобновяване." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "работа" + }, + "match_ambiguity": { + "name": "Неяснота на съвпадението" + } + }, + "sensor": { + "washer_state": { + "name": "състояние", + "state": { + "off": "Изкл", + "idle": "празен ход", + "starting": "Стартиране", + "running": "работи", + "paused": "На пауза", + "user_paused": "Поставен на пауза от потребителя", + "ending": "Край", + "finished": "Готово", + "anti_wrinkle": "Против бръчки", + "delay_wait": "Изчакване за стартиране", + "interrupted": "Прекъснат", + "force_stopped": "Принудително спряно", + "rinse": "Изплакване", + "unknown": "Неизвестен", + "clean": "Чисто" + } + }, + "washer_program": { + "name": "програма" + }, + "time_remaining": { + "name": "Оставащо време" + }, + "total_duration": { + "name": "Обща продължителност" + }, + "cycle_progress": { + "name": "Напредък" + }, + "current_power": { + "name": "Текуща мощност" + }, + "elapsed_time": { + "name": "Изминало време" + }, + "debug_info": { + "name": "Информация за отстраняване на грешки" + }, + "match_confidence": { + "name": "Съвпадение на увереността" + }, + "top_candidates": { + "name": "Топ кандидати", + "state": { + "none": "Няма" + } + }, + "current_phase": { + "name": "Текуща фаза" + }, + "wash_phase": { + "name": "Фаза" + }, + "profile_cycle_count": { + "name": "Профил {profile_name} брой", + "unit_of_measurement": "цикли" + }, + "suggestions": { + "name": "Предложени настройки" + }, + "pump_runs_today": { + "name": "Помпата работи (последните 24 часа)" + }, + "cycle_count": { + "name": "Брой цикли" + } + }, + "select": { + "program_select": { + "name": "Програма за цикъл", + "state": { + "auto_detect": "Автоматично откриване" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Цикъл на принудителен край" + }, + "pause_cycle": { + "name": "Цикъл на пауза" + }, + "resume_cycle": { + "name": "Цикъл на възобновяване" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Изисква се валиден device_id." + }, + "cycle_id_required": { + "message": "Изисква се валиден cycle_id." + }, + "profile_name_required": { + "message": "Изисква се валидно име на профил." + }, + "device_not_found": { + "message": "Устройството не е намерено." + }, + "no_config_entry": { + "message": "Не е намерен запис в конфигурацията за устройство." + }, + "integration_not_loaded": { + "message": "Интеграцията не е заредена за това устройство." + }, + "cycle_not_found_or_no_power": { + "message": "Цикълът не е намерен или няма данни за мощността." + }, + "trim_failed_empty_window": { + "message": "Подстригването е неуспешно - цикълът не е открит, няма данни за мощността или полученият прозорец е празен." + }, + "trim_invalid_range": { + "message": "trim_end_s трябва да е по-голямо от trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/bn.json b/custom_components/ha_washdata/translations/bn.json new file mode 100644 index 0000000..f0c6e85 --- /dev/null +++ b/custom_components/ha_washdata/translations/bn.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "ওয়াশডেটা সেটআপ", + "description": "আপনার ওয়াশিং মেশিন বা অন্যান্য যন্ত্রপাতি কনফিগার করুন।\n\nপাওয়ার সেন্সর প্রয়োজন।\n\n**পরবর্তীতে, আপনি আপনার প্রথম প্রোফাইল তৈরি করতে চান কিনা জিজ্ঞাসা করা হবে৷**", + "data": { + "name": "ডিভাইসের নাম", + "device_type": "ডিভাইসের ধরন", + "power_sensor": "পাওয়ার সেন্সর", + "min_power": "ন্যূনতম পাওয়ার থ্রেশহোল্ড (W)" + }, + "data_description": { + "name": "এই ডিভাইসের জন্য একটি বন্ধুত্বপূর্ণ নাম (যেমন, 'ওয়াশিং মেশিন', 'ডিশওয়াশার')।", + "device_type": "এটা কি ধরনের যন্ত্র? দর্জি সনাক্তকরণ এবং লেবেলিং সাহায্য করে.", + "power_sensor": "সেন্সর সত্তা যা আপনার স্মার্ট প্লাগ থেকে রিয়েল-টাইম পাওয়ার খরচ (ওয়াটে) রিপোর্ট করে।", + "min_power": "এই থ্রেশহোল্ডের উপরে পাওয়ার রিডিংগুলি (ওয়াটে) নির্দেশ করে যে যন্ত্রটি চলছে৷ বেশিরভাগ ডিভাইসের জন্য 2W দিয়ে শুরু করুন।" + } + }, + "first_profile": { + "title": "প্রথম প্রোফাইল তৈরি করুন", + "description": "একটি **সাইকেল** হল আপনার যন্ত্রের সম্পূর্ণ চালানো (যেমন, লন্ড্রির ভার)। একটি **প্রোফাইল** এই চক্রগুলিকে টাইপ অনুসারে গ্রুপ করে (যেমন, 'কটন', 'কুইক ওয়াশ')। এখন একটি প্রোফাইল তৈরি করা অবিলম্বে ইন্টিগ্রেশন অনুমান সময়কাল সাহায্য করে.\n\nস্বয়ংক্রিয়ভাবে সনাক্ত না হলে আপনি ডিভাইস নিয়ন্ত্রণে এই প্রোফাইলটি ম্যানুয়ালি নির্বাচন করতে পারেন।\n\n**এই ধাপটি এড়িয়ে যেতে 'প্রোফাইল নাম' খালি রাখুন।**", + "data": { + "profile_name": "প্রোফাইল নাম (ঐচ্ছিক)", + "manual_duration": "আনুমানিক সময়কাল (মিনিট)" + } + } + }, + "error": { + "cannot_connect": "সংযোগ করতে ব্যর্থ হয়েছে", + "invalid_auth": "অবৈধ প্রমাণীকরণ", + "unknown": "অপ্রত্যাশিত ত্রুটি" + }, + "abort": { + "already_configured": "ডিভাইস ইতিমধ্যে কনফিগার করা আছে" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "শেখার প্রতিক্রিয়া পর্যালোচনা করুন", + "settings": "সেটিংস", + "notifications": "বিজ্ঞপ্তি", + "manage_cycles": "সাইকেল পরিচালনা করুন", + "manage_profiles": "প্রোফাইল পরিচালনা করুন", + "manage_phase_catalog": "ফেজ ক্যাটালগ পরিচালনা করুন", + "record_cycle": "রেকর্ড সাইকেল (ম্যানুয়াল)", + "diagnostics": "ডায়াগনস্টিকস এবং রক্ষণাবেক্ষণ" + } + }, + "apply_suggestions": { + "title": "প্রস্তাবিত মান অনুলিপি করুন", + "description": "এটি আপনার সংরক্ষিত সেটিংসে বর্তমান প্রস্তাবিত মানগুলি অনুলিপি করবে৷ এটি একটি এককালীন ক্রিয়া এবং স্বয়ংক্রিয়-ওভাররাইট সক্ষম করে না৷\n\nকপি করার জন্য প্রস্তাবিত মান:\n{suggested}", + "data": { + "confirm": "অনুলিপি কর্ম নিশ্চিত করুন" + } + }, + "apply_suggestions_confirm": { + "title": "প্রস্তাবিত পরিবর্তন পর্যালোচনা করুন", + "description": "WashData পাওয়া গেছে **{pending_count}** প্রস্তাবিত মান(গুলি) প্রয়োগ করার জন্য।\n\nপরিবর্তন:\n{changes}\n\n✅ নীচের বাক্সে টিক চিহ্ন দিন এবং এই পরিবর্তনগুলি অবিলম্বে প্রয়োগ করতে এবং সংরক্ষণ করতে **জমা দিন** এ ক্লিক করুন।\n❌ এটিকে টিক চিহ্ন ছাড়াই ছেড়ে দিন এবং বাতিল করতে **জমা দিন** এ ক্লিক করুন এবং ফিরে যান।", + "data": { + "confirm_apply_suggestions": "এই পরিবর্তনগুলি প্রয়োগ করুন এবং সংরক্ষণ করুন" + } + }, + "settings": { + "title": "সেটিংস", + "description": "{deprecation_warning}সনাক্তকরণ থ্রেশহোল্ড, শেখা এবং উন্নত আচরণ সামঞ্জস্য করুন। বেশিরভাগ সেটআপের জন্য ডিফল্ট মান ভালো কাজ করে।\n\nবর্তমানে উপলব্ধ প্রস্তাবিত সেটিংস: {suggestions_count}।\nএটি 0-এর উপরে হলে, উন্নত সেটিংস খুলুন এবং সুপারিশগুলি পর্যালোচনা করতে 'প্রস্তাবিত মান প্রয়োগ করুন' ব্যবহার করুন।", + "data": { + "apply_suggestions": "প্রস্তাবিত মান প্রয়োগ করুন", + "device_type": "ডিভাইসের ধরন", + "power_sensor": "পাওয়ার সেন্সর সত্তা", + "min_power": "সর্বনিম্ন শক্তি (W)", + "off_delay": "চক্র শেষ বিলম্ব (গুলি)", + "notify_actions": "বিজ্ঞপ্তি কর্ম", + "notify_people": "এই লোকেরা বাড়িতে না হওয়া পর্যন্ত ডেলিভারি বিলম্বিত করুন", + "notify_only_when_home": "নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিলম্বিত করুন", + "notify_fire_events": "ফায়ার অটোমেশন ইভেন্ট", + "notify_start_services": "চক্র শুরু - বিজ্ঞপ্তি লক্ষ্য", + "notify_finish_services": "সাইকেল ফিনিশ - বিজ্ঞপ্তি লক্ষ্য", + "notify_live_services": "লাইভ অগ্রগতি - বিজ্ঞপ্তি লক্ষ্য", + "notify_before_end_minutes": "প্রাক-সমাপ্তির বিজ্ঞপ্তি (শেষ হওয়ার কয়েক মিনিট আগে)", + "notify_title": "বিজ্ঞপ্তি শিরোনাম", + "notify_icon": "বিজ্ঞপ্তি আইকন", + "notify_start_message": "বার্তা বিন্যাস শুরু করুন", + "notify_finish_message": "বার্তা বিন্যাস শেষ করুন", + "notify_pre_complete_message": "প্রি-কমপ্লিশন মেসেজ ফরম্যাট", + "show_advanced": "উন্নত সেটিংস সম্পাদনা করুন (পরবর্তী ধাপ)" + }, + "data_description": { + "apply_suggestions": "ফর্মটিতে শেখার অ্যালগরিদম দ্বারা প্রস্তাবিত মানগুলি অনুলিপি করতে এই বাক্সটি চেক করুন৷ সংরক্ষণ করার আগে পর্যালোচনা করুন।\n\nপ্রস্তাবিত (শিক্ষা থেকে):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "যন্ত্রের ধরন নির্বাচন করুন (ওয়াশিং মেশিন, ড্রায়ার, ডিশওয়াশার, কফি মেশিন, ইভি)। এই ট্যাগটি আরও ভাল সংগঠনের জন্য প্রতিটি চক্রের সাথে সংরক্ষণ করা হয়।", + "power_sensor": "রিয়েল-টাইম পাওয়ার রিপোর্টিং সেন্সর সত্তা (ওয়াটে)। আপনি ঐতিহাসিক ডেটা না হারিয়ে যেকোনও সময় এটি পরিবর্তন করতে পারেন-যদি আপনি একটি স্মার্ট প্লাগ প্রতিস্থাপন করেন তাহলে এটি কার্যকর।", + "min_power": "এই থ্রেশহোল্ডের উপরে পাওয়ার রিডিংগুলি (ওয়াটে) নির্দেশ করে যে যন্ত্রটি চলছে৷ বেশিরভাগ ডিভাইসের জন্য 2W দিয়ে শুরু করুন।", + "off_delay": "সেকেন্ড মসৃণ শক্তি সমাপ্তি চিহ্নিত করার জন্য স্টপ থ্রেশহোল্ডের নিচে থাকতে হবে। ডিফল্ট: 120", + "notify_actions": "বিজ্ঞপ্তিগুলির জন্য চালানোর জন্য ঐচ্ছিক হোম অ্যাসিস্ট্যান্ট অ্যাকশন সিকোয়েন্স (স্ক্রিপ্ট, বিজ্ঞপ্তি, TTS, ইত্যাদি)। সেট করা হলে, ফলব্যাক বিজ্ঞপ্তি পরিষেবার আগে অ্যাকশন চালানো হয়।", + "notify_people": "উপস্থিতি গেটিংয়ের জন্য ব্যবহৃত লোক সত্তা। যখন সক্রিয় থাকে, অন্তত একজন নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিতরণ বিলম্বিত হয়৷", + "notify_only_when_home": "যদি সক্রিয় থাকে, অন্তত একজন নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিলম্বিত করুন৷", + "notify_fire_events": "সক্ষম থাকলে, অটোমেশন চালানোর জন্য সাইকেল শুরু এবং শেষের (ha_washdata_cycle_started এবং ha_washdata_cycle_ended) হোম সহকারী ইভেন্টগুলিকে ফায়ার করুন৷", + "notify_start_services": "একটি চক্র শুরু হলে সতর্ক করার জন্য এক বা একাধিক পরিষেবাগুলিকে বিজ্ঞপ্তি দেয়৷ শুরুর বিজ্ঞপ্তিগুলি এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_finish_services": "যখন একটি চক্র শেষ হয় বা সমাপ্তির কাছাকাছি হয় তখন সতর্ক করার জন্য এক বা একাধিক পরিষেবাগুলিকে বিজ্ঞপ্তি দেয়৷ সমাপ্তির বিজ্ঞপ্তিগুলি এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_live_services": "চক্র চলাকালীন লাইভ অগ্রগতি আপডেট পেতে এক বা একাধিক পরিষেবাগুলিকে বিজ্ঞপ্তি দেয় (শুধুমাত্র মোবাইল সঙ্গী অ্যাপ)। লাইভ আপডেট এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_before_end_minutes": "একটি বিজ্ঞপ্তি ট্রিগার করার জন্য চক্রের আনুমানিক শেষের মিনিট আগে। নিষ্ক্রিয় করতে 0 এ সেট করুন। ডিফল্ট: 0।", + "notify_title": "বিজ্ঞপ্তির জন্য কাস্টম শিরোনাম। {device} স্থানধারক সমর্থন করে।", + "notify_icon": "বিজ্ঞপ্তির জন্য ব্যবহার করার জন্য আইকন (যেমন mdi:washing-machine)। ডিফল্ট ব্যবহার করতে খালি ছেড়ে দিন।", + "notify_start_message": "একটি চক্র শুরু হলে বার্তা পাঠানো হয়। {device} স্থানধারক সমর্থন করে।", + "notify_finish_message": "একটি চক্র শেষ হলে বার্তা পাঠানো হয়৷ {device}, {duration}, {program}, {energy_kwh}, {cost} স্থানধারককে সমর্থন করে।", + "notify_pre_complete_message": "চক্র চলাকালীন পুনরাবৃত্ত লাইভ অগ্রগতি আপডেটের পাঠ্য। {device}, {minutes}, {program} স্থানধারককে সমর্থন করে।" + } + }, + "notifications": { + "title": "বিজ্ঞপ্তি", + "description": "বিজ্ঞপ্তি চ্যানেল, টেমপ্লেট, এবং ঐচ্ছিক লাইভ মোবাইল অগ্রগতি আপডেট কনফিগার করুন।", + "data": { + "notify_actions": "বিজ্ঞপ্তি কর্ম", + "notify_people": "এই লোকেরা বাড়িতে না হওয়া পর্যন্ত ডেলিভারি বিলম্বিত করুন", + "notify_only_when_home": "নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিলম্বিত করুন", + "notify_fire_events": "ফায়ার অটোমেশন ইভেন্ট", + "notify_start_services": "চক্র শুরু - বিজ্ঞপ্তি লক্ষ্য", + "notify_finish_services": "সাইকেল ফিনিশ - বিজ্ঞপ্তি লক্ষ্য", + "notify_live_services": "লাইভ অগ্রগতি - বিজ্ঞপ্তি লক্ষ্য", + "notify_before_end_minutes": "প্রাক-সমাপ্তির বিজ্ঞপ্তি (শেষ হওয়ার কয়েক মিনিট আগে)", + "notify_live_interval_seconds": "লাইভ আপডেটের ব্যবধান (সেকেন্ড)", + "notify_live_overrun_percent": "লাইভ আপডেট ওভাররান অ্যালাউন্স (%)", + "notify_live_chronometer": "ক্রোনোমিটার কাউন্টডাউন টাইমার", + "notify_title": "বিজ্ঞপ্তি শিরোনাম", + "notify_icon": "বিজ্ঞপ্তি আইকন", + "notify_start_message": "বার্তা বিন্যাস শুরু করুন", + "notify_finish_message": "বার্তা বিন্যাস শেষ করুন", + "notify_pre_complete_message": "প্রি-কমপ্লিশন মেসেজ ফরম্যাট", + "energy_price_entity": "শক্তির মূল্য - সত্তা (ঐচ্ছিক)", + "energy_price_static": "এনার্জির দাম - স্ট্যাটিক মান (ঐচ্ছিক)", + "go_back": "সংরক্ষণ না করে ফিরে যান", + "notify_reminder_message": "তাগাদার বার্তা", + "notify_timeout_seconds": "(সেকেন্ড পরে এনক্রিপশন চালু হবে)", + "notify_channel": "বিজ্ঞপ্তি চ্যানেল (অবস্থা/পুনর্বাদযোগ্য)", + "notify_finish_channel": "চ্যানেল সমাপ্তি" + }, + "data_description": { + "go_back": "এটি টিক দিন এবং উপরের যেকোনো পরিবর্তন বাতিল করে মূল মেনুতে ফিরতে Submit ক্লিক করুন।", + "notify_actions": "বিজ্ঞপ্তিগুলির জন্য চালানোর জন্য ঐচ্ছিক হোম অ্যাসিস্ট্যান্ট অ্যাকশন সিকোয়েন্স (স্ক্রিপ্ট, বিজ্ঞপ্তি, TTS, ইত্যাদি)। সেট করা হলে, ফলব্যাক বিজ্ঞপ্তি পরিষেবার আগে অ্যাকশন চালানো হয়।", + "notify_people": "উপস্থিতি গেটিংয়ের জন্য ব্যবহৃত লোক সত্তা। যখন সক্রিয় থাকে, অন্তত একজন নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিতরণ বিলম্বিত হয়৷", + "notify_only_when_home": "যদি সক্রিয় থাকে, অন্তত একজন নির্বাচিত ব্যক্তি বাড়িতে না আসা পর্যন্ত বিজ্ঞপ্তি বিলম্বিত করুন৷", + "notify_fire_events": "সক্ষম থাকলে, অটোমেশন চালানোর জন্য সাইকেল শুরু এবং শেষের (ha_washdata_cycle_started এবং ha_washdata_cycle_ended) হোম সহকারী ইভেন্টগুলিকে ফায়ার করুন৷", + "notify_start_services": "একটি চক্র শুরু হলে সতর্ক করার জন্য এক বা একাধিক পরিষেবা বিজ্ঞপ্তি দেয় (যেমন notify.mobile_app_my_phone)। শুরুর বিজ্ঞপ্তিগুলি এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_finish_services": "যখন একটি চক্র শেষ হয় বা সমাপ্তির কাছাকাছি হয় তখন সতর্ক করার জন্য এক বা একাধিক পরিষেবাগুলিকে বিজ্ঞপ্তি দেয়৷ সমাপ্তির বিজ্ঞপ্তিগুলি এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_live_services": "চক্র চলাকালীন লাইভ অগ্রগতি আপডেট পেতে এক বা একাধিক পরিষেবাগুলিকে বিজ্ঞপ্তি দেয় (শুধুমাত্র মোবাইল সঙ্গী অ্যাপ)। লাইভ আপডেট এড়িয়ে যেতে খালি ছেড়ে দিন।", + "notify_before_end_minutes": "একটি বিজ্ঞপ্তি ট্রিগার করার জন্য চক্রের আনুমানিক শেষের মিনিট আগে। নিষ্ক্রিয় করতে 0 এ সেট করুন। ডিফল্ট: 0।", + "notify_live_interval_seconds": "চলমান অবস্থায় কত ঘন ঘন অগ্রগতি আপডেট পাঠানো হয়। নিম্ন মানগুলি আরও প্রতিক্রিয়াশীল তবে আরও বিজ্ঞপ্তি গ্রহণ করে৷ ন্যূনতম 30 সেকেন্ড প্রয়োগ করা হয় - 30 সেকেন্ডের নিচের মানগুলি স্বয়ংক্রিয়ভাবে 30 সেকেন্ড পর্যন্ত রাউন্ড করা হয়।", + "notify_live_overrun_percent": "দীর্ঘ/অতিরিক্ত চক্রের জন্য আনুমানিক আপডেট সংখ্যার উপরে নিরাপত্তা মার্জিন (উদাহরণস্বরূপ 20% 1.2x আনুমানিক আপডেটের অনুমতি দেয়)।", + "notify_live_chronometer": "সক্রিয় করা হলে, প্রতিটি লাইভ আপডেটে আনুমানিক সমাপ্তির সময়ের জন্য একটি ক্রোনোমিটার কাউন্টডাউন অন্তর্ভুক্ত থাকে। আপডেটের মধ্যে ডিভাইসে নোটিফিকেশন টাইমার স্বয়ংক্রিয়ভাবে টিক ডাউন হয়ে যায় (শুধুমাত্র অ্যান্ড্রয়েড সঙ্গী অ্যাপ)।", + "notify_title": "বিজ্ঞপ্তির জন্য কাস্টম শিরোনাম। {device} স্থানধারক সমর্থন করে।", + "notify_icon": "বিজ্ঞপ্তির জন্য ব্যবহার করার জন্য আইকন (যেমন mdi:washing-machine)। ডিফল্ট ব্যবহার করতে খালি ছেড়ে দিন।", + "notify_start_message": "একটি চক্র শুরু হলে বার্তা পাঠানো হয়। {device} স্থানধারক সমর্থন করে।", + "notify_finish_message": "একটি চক্র শেষ হলে বার্তা পাঠানো হয়৷ {device}, {duration}, {program}, {energy_kwh}, {cost} স্থানধারককে সমর্থন করে।", + "energy_price_entity": "একটি সাংখ্যিক সত্তা নির্বাচন করুন যা বর্তমান বিদ্যুতের মূল্য ধারণ করে (যেমন sensor.electricity_price, input_number.energy_rate)। সেট করা হলে, {cost} স্থানধারক সক্ষম করে। উভয় কনফিগার করা থাকলে স্ট্যাটিক মানের উপর অগ্রাধিকার নেয়।", + "energy_price_static": "প্রতি kWh বিদ্যুতের নির্দিষ্ট মূল্য। সেট করা হলে, {cost} স্থানধারক সক্ষম করে। উপরে একটি সত্তা কনফিগার করা থাকলে উপেক্ষা করা হয়। বার্তা টেমপ্লেটে আপনার মুদ্রার প্রতীক যোগ করুন, যেমন {cost} €", + "notify_reminder_message": "অনুমানকৃতভাবে শেষ হওয়ার আগে একবার উল্লেখকৃত লগ-ইনের লেখা । আবার শুরু থেকে সর্বশেষ খবর আসছে। {device}, {minutes}, {program}-র ইন্টারেক্টিভ অ্যাকাউন্ট।", + "notify_timeout_seconds": "এত সেকেন্ড পরে স্বয়ংক্রিয়ভাবে সংযুক্ত হওয়ার ফলে এই সূচনাবার্তাগুলি বাতিল করা হয় (এই এ্যাপটি 'সময়')। যতক্ষণ না বাতিল করা হয় ততক্ষণ পর্যন্ত সেট করুন। ডিফল্ট: 0।", + "notify_channel": "Android, অবস্থা এবং বিজ্ঞপ্তির জন্য চিহ্নিত প্রসেস। একটি চ্যানেলের জন্য শব্দ ও গুরুত্ব কনফিগার করা হয়েছে । প্রথমবারের মত চ্যানেলের নাম ব্যবহৃত হয় - ওয়াশস্ত্র শুধুমাত্র চ্যানেলের নাম নির্ধারণ করা হয় । ডিফল্ট অ্যাপ্লিকেশনের জন্য খালি রাখুন।", + "notify_finish_channel": "সর্বশেষ বিপদ সংকেতের জন্য তৈরি করা চ্যানেল (এর মাধ্যমে মনে করিয়ে দেওয়া হয় এবং লন্ড্রি দেখার জন্য) এর নিজস্ব শব্দ থাকে। উপরের চ্যানেল পুনরায় ব্যবহার করার জন্য ফাঁকা রাখুন ।", + "notify_pre_complete_message": "চক্র চলাকালীন পুনরাবৃত্ত লাইভ অগ্রগতি আপডেটের পাঠ্য। {device}, {minutes}, {program} স্থানধারককে সমর্থন করে।" + } + }, + "advanced_settings": { + "title": "উন্নত সেটিংস", + "description": "সনাক্তকরণ থ্রেশহোল্ড, শেখা এবং উন্নত আচরণ সামঞ্জস্য করুন। বেশিরভাগ সেটআপের জন্য ডিফল্ট মান ভালো কাজ করে।", + "sections": { + "suggestions_section": { + "name": "প্রস্তাবিত সেটিংস", + "description": "{suggestions_count}টি শেখা পরামর্শ উপলব্ধ আছে। সংরক্ষণের আগে প্রস্তাবিত পরিবর্তনগুলি পর্যালোচনা করতে 'প্রস্তাবিত মান প্রয়োগ করুন' টিক দিন।", + "data": { + "apply_suggestions": "প্রস্তাবিত মান প্রয়োগ করুন" + }, + "data_description": { + "apply_suggestions": "শেখার অ্যালগরিদম থেকে প্রস্তাবিত মানগুলির জন্য একটি পর্যালোচনা ধাপ খুলতে এই বাক্সটি চেক করুন। কিছুই স্বয়ংক্রিয়ভাবে সংরক্ষিত হয় না.\n\nপ্রস্তাবিত (শিক্ষা থেকে):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "সনাক্তকরণ ও পাওয়ার থ্রেশহোল্ড", + "description": "কখন যন্ত্রটি চালু বা বন্ধ বলে গণ্য হবে, এনার্জি গেট, এবং অবস্থার পরিবর্তনের সময় নির্ধারণ।", + "data": { + "start_duration_threshold": "ডিবাউন্সের সময়কাল (গুলি) শুরু করুন", + "start_energy_threshold": "স্টার্ট এনার্জি গেট (Wh)", + "completion_min_seconds": "সম্পূর্ণ করার সর্বনিম্ন রানটাইম (সেকেন্ড)", + "start_threshold_w": "স্টার্ট থ্রেশহোল্ড (W)", + "stop_threshold_w": "স্টপ থ্রেশহোল্ড (W)", + "end_energy_threshold": "এন্ড এনার্জি গেট (Wh)", + "running_dead_zone": "চলমান মৃত অঞ্চল (সেকেন্ড)", + "end_repeat_count": "পুনরাবৃত্তি গণনা শেষ করুন", + "min_off_gap": "সাইকেলের মধ্যে ন্যূনতম ব্যবধান", + "sampling_interval": "নমুনা ব্যবধান (গুলি)" + }, + "data_description": { + "start_duration_threshold": "এই সময়কালের (সেকেন্ড) থেকে ছোট পাওয়ার স্পাইকগুলিকে উপেক্ষা করুন। আসল চক্র শুরু হওয়ার আগে বুট স্পাইকগুলি ফিল্টার করে (যেমন, 2s এর জন্য 60W)। ডিফল্ট: 5s।", + "start_energy_threshold": "নিশ্চিত করতে START পর্বের সময় চক্রকে অন্তত এত বেশি শক্তি (Wh) জমা করতে হবে। ডিফল্ট: 0.005 Wh.", + "completion_min_seconds": "এর থেকে ছোট সাইকেলগুলি স্বাভাবিকভাবে শেষ হলেও 'বিঘ্নিত' হিসাবে চিহ্নিত করা হবে৷ ডিফল্ট: 600", + "start_threshold_w": "একটি চক্র শুরু করতে এই থ্রেশহোল্ডের উপরে শক্তি অবশ্যই উঠতে হবে। আপনার স্ট্যান্ডবাই পাওয়ারের চেয়ে বেশি সেট করুন। ডিফল্ট: min_power + 1 W.", + "stop_threshold_w": "একটি চক্র শেষ করতে শক্তি অবশ্যই এই থ্রেশহোল্ডের নীচে নামতে হবে। গোলমাল এড়াতে শূন্যের ঠিক উপরে সেট করুন মিথ্যা প্রান্তগুলিকে ট্রিগার করে৷ ডিফল্ট: 0.6 * মিনিট_পাওয়ার।", + "end_energy_threshold": "শেষ অফ-ডিলে উইন্ডোতে শক্তি এই মানের (Wh) নীচে থাকলেই চক্র শেষ হয়৷ শক্তি শূন্যের কাছাকাছি ওঠানামা করলে মিথ্যা শেষ প্রতিরোধ করে। ডিফল্ট: 0.05 Wh.", + "running_dead_zone": "চক্র শুরু হওয়ার পরে, প্রাথমিক স্পিন-আপ পর্বে মিথ্যা শেষ সনাক্তকরণ রোধ করতে এই কয়েক সেকেন্ডের জন্য পাওয়ার ডিপ উপেক্ষা করুন। নিষ্ক্রিয় করতে 0 এ সেট করুন। ডিফল্ট: 0s", + "end_repeat_count": "চক্রটি শেষ করার আগে পরপর কতবার সমাপ্তির অবস্থা (অফ_বিলম্বের জন্য থ্রেশহোল্ডের নিচে পাওয়ার) অবশ্যই পূরণ করতে হবে। উচ্চতর মান বিরতির সময় মিথ্যা সমাপ্তি প্রতিরোধ করে। ডিফল্ট: 1.", + "min_off_gap": "যদি একটি চক্র আগেরটি শেষ হওয়ার এই কয়েক সেকেন্ডের মধ্যে শুরু হয়, তবে সেগুলিকে একক চক্রে একত্রিত করা হবে।", + "sampling_interval": "সেকেন্ডে ন্যূনতম নমুনা ব্যবধান। এই হারের চেয়ে দ্রুত আপডেটগুলি উপেক্ষা করা হবে। ডিফল্ট: 30s (ওয়াশিং মেশিন, ওয়াশার-ড্রায়ার্স এবং ডিশ ওয়াশারের জন্য 2s)।" + } + }, + "matching_section": { + "name": "প্রোফাইল ম্যাচিং ও শেখা", + "description": "WashData চলমান চক্রগুলোকে শেখা প্রোফাইলের সাথে কতটা আগ্রাসীভাবে মেলায় এবং কখন প্রতিক্রিয়া চাইবে।", + "data": { + "profile_match_min_duration_ratio": "প্রোফাইল ম্যাচের সর্বনিম্ন সময়কাল অনুপাত (0.1-1.0)", + "profile_match_interval": "প্রোফাইল ম্যাচের ব্যবধান (সেকেন্ড)", + "profile_match_threshold": "প্রোফাইল ম্যাচ থ্রেশহোল্ড", + "profile_unmatch_threshold": "প্রোফাইল অমিল থ্রেশহোল্ড", + "auto_label_confidence": "অটো-লেবেল কনফিডেন্স (0-1)", + "learning_confidence": "প্রতিক্রিয়া অনুরোধ আত্মবিশ্বাস (0-1)", + "suppress_feedback_notifications": "প্রতিক্রিয়া বিজ্ঞপ্তি দমন করুন", + "duration_tolerance": "সময়কাল সহনশীলতা (0-0.5)", + "smoothing_window": "মসৃণ উইন্ডো (নমুনা)", + "profile_duration_tolerance": "প্রোফাইল ম্যাচের সময়কাল সহনশীলতা (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "প্রোফাইল মিলের জন্য ন্যূনতম সময়কাল অনুপাত। চলমান চক্রটি মিলতে প্রোফাইলের সময়কালের অন্তত এই ভগ্নাংশ হতে হবে। নিম্ন = পূর্বের মিল। ডিফল্ট: 0.50 (50%)।", + "profile_match_interval": "একটি চক্র চলাকালীন কত ঘন ঘন (সেকেন্ডে) প্রোফাইল মেলানোর চেষ্টা করতে হবে। নিম্ন মান দ্রুত সনাক্তকরণ প্রদান করে কিন্তু আরো CPU ব্যবহার করে। ডিফল্ট: 300s (5 মিনিট)।", + "profile_match_threshold": "ন্যূনতম DTW সাদৃশ্য স্কোর (0.0–1.0) একটি প্রোফাইলকে ম্যাচ হিসাবে বিবেচনা করতে হবে। উচ্চতর = কঠোর মিল, কম মিথ্যা ইতিবাচক। ডিফল্ট: 0.4।", + "profile_unmatch_threshold": "DTW সাদৃশ্য স্কোর (0.0–1.0) যার নিচে পূর্বে মিলে যাওয়া প্রোফাইল প্রত্যাখ্যান করা হয়েছে। ঝাঁকুনি প্রতিরোধ করতে ম্যাচ থ্রেশহোল্ডের চেয়ে কম হওয়া উচিত। ডিফল্ট: 0.35।", + "auto_label_confidence": "স্বয়ংক্রিয়ভাবে এই আত্মবিশ্বাসের উপরে বা তার উপরে সাইকেল লেবেল করুন সমাপ্তির উপর (0.0-1.0)।", + "learning_confidence": "একটি ইভেন্ট (0.0-1.0) এর মাধ্যমে ব্যবহারকারী যাচাইকরণের অনুরোধ করার জন্য ন্যূনতম আত্মবিশ্বাস।", + "suppress_feedback_notifications": "সক্রিয় থাকা অবস্থায়, WashData এখনও ট্র্যাক করবে এবং অভ্যন্তরীণভাবে প্রতিক্রিয়ার অনুরোধ করবে কিন্তু আপনাকে চক্র নিশ্চিত করতে বলে অবিরাম বিজ্ঞপ্তিগুলি দেখাবে না। দরকারী যখন চক্র সবসময় সঠিকভাবে সনাক্ত করা হয় এবং আপনি বিভ্রান্তিকর প্রম্পট খুঁজে পান।", + "duration_tolerance": "একটি দৌড়ের সময় সময়-অবশিষ্ট অনুমানের জন্য সহনশীলতা (0.0-0.5 0-50% এর সাথে মিলে যায়)।", + "smoothing_window": "চলন্ত-গড় মসৃণ করার জন্য ব্যবহৃত সাম্প্রতিক পাওয়ার রিডিংয়ের সংখ্যা।", + "profile_duration_tolerance": "মোট সময়কালের বৈচিত্র্য (0.0-0.5) এর জন্য প্রোফাইল ম্যাচিং দ্বারা ব্যবহৃত সহনশীলতা। ডিফল্ট আচরণ: ±25%।" + } + }, + "timing_section": { + "name": "সময়, রক্ষণাবেক্ষণ ও ডিবাগ", + "description": "ওয়াচডগ, অগ্রগতি রিসেট, স্বয়ংক্রিয় রক্ষণাবেক্ষণ, এবং ডিবাগ সত্তা প্রকাশ।", + "data": { + "watchdog_interval": "ওয়াচডগ ব্যবধান (সেকেন্ড)", + "no_update_active_timeout": "নো-আপডেট টাইমআউট (গুলি)", + "progress_reset_delay": "অগ্রগতি রিসেট বিলম্ব (সেকেন্ড)", + "auto_maintenance": "স্বয়ংক্রিয় রক্ষণাবেক্ষণ সক্ষম করুন৷", + "expose_debug_entities": "ডিবাগ সত্তা প্রকাশ করুন", + "save_debug_traces": "ডিবাগ ট্রেস সংরক্ষণ করুন" + }, + "data_description": { + "watchdog_interval": "চালানোর সময় ওয়াচডগ চেক মধ্যে সেকেন্ড. ডিফল্ট: 5s। সতর্কতা: মিথ্যা স্টপ এড়াতে এটি আপনার সেন্সরের আপডেট ব্যবধানের চেয়ে বেশি তা নিশ্চিত করুন (যেমন, যদি সেন্সর প্রতি 60 সেকেন্ডে আপডেট হয়, 65s+ ব্যবহার করুন)।", + "no_update_active_timeout": "পাবলিশ-অন-চেঞ্জ সেন্সরগুলির জন্য: যদি এই বহু সেকেন্ডের জন্য কোনও আপডেট না আসে তবে শুধুমাত্র একটি সক্রিয় চক্রকে জোর করে শেষ করুন৷ কম-পাওয়ার সমাপ্তি এখনও অফ বিলম্ব ব্যবহার করে।", + "progress_reset_delay": "একটি চক্র সম্পূর্ণ হওয়ার পরে (100%), অগ্রগতি 0% এ রিসেট করার আগে এই নিষ্ক্রিয়তার অনেক সেকেন্ড অপেক্ষা করুন। ডিফল্ট: 300", + "auto_maintenance": "স্বয়ংক্রিয় রক্ষণাবেক্ষণ সক্ষম করুন (মেরামত নমুনা, টুকরা মার্জ)।", + "expose_debug_entities": "ডিবাগিংয়ের জন্য উন্নত সেন্সর (আস্থা, পর্যায়, অস্পষ্টতা) দেখান।", + "save_debug_traces": "ইতিহাসে বিস্তারিত র‌্যাঙ্কিং এবং পাওয়ার ট্রেস ডেটা সংরক্ষণ করুন (সঞ্চয়স্থানের ব্যবহার বাড়ায়)।" + } + }, + "anti_wrinkle_section": { + "name": "অ্যান্টি-রিঙ্কেল শিল্ড (ড্রায়ার)", + "description": "ভূত চক্র রোধ করতে চক্র শেষ হওয়ার পর ড্রামের ঘূর্ণন উপেক্ষা করুন। শুধু ড্রায়ার / ওয়াশার-ড্রায়ারের জন্য।", + "data": { + "anti_wrinkle_enabled": "অ্যান্টি-রিঙ্কেল শিল্ড (শুধুমাত্র ড্রায়ার)", + "anti_wrinkle_max_power": "অ্যান্টি-রিঙ্কেল ম্যাক্স পাওয়ার (W)", + "anti_wrinkle_max_duration": "অ্যান্টি-রিঙ্কেল সর্বোচ্চ সময়কাল (গুলি)", + "anti_wrinkle_exit_power": "অ্যান্টি-রিঙ্কেল এক্সিট পাওয়ার (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "ভূত চক্র রোধ করতে একটি চক্র শেষ হওয়ার পরে ছোট কম-পাওয়ার ড্রাম ঘূর্ণন উপেক্ষা করুন (শুধুমাত্র ড্রায়ার/ওয়াশার-ড্রায়ার)।", + "anti_wrinkle_max_power": "এই শক্তিতে বা নীচে বিস্ফোরণগুলিকে নতুন চক্রের পরিবর্তে অ্যান্টি-রিঙ্কেল ঘূর্ণন হিসাবে বিবেচনা করা হয়। অ্যান্টি-রিঙ্কেল শিল্ড চালু থাকলেই প্রযোজ্য।", + "anti_wrinkle_max_duration": "অ্যান্টি-রিঙ্কেল ঘূর্ণন হিসাবে বিবেচনা করার জন্য সর্বাধিক বিস্ফোরণের দৈর্ঘ্য। অ্যান্টি-রিঙ্কেল শিল্ড চালু থাকলেই প্রযোজ্য।", + "anti_wrinkle_exit_power": "যদি শক্তি এই স্তরের নীচে নেমে যায়, অবিলম্বে অ্যান্টি-রিঙ্কেল থেকে প্রস্থান করুন (ট্রু অফ)। অ্যান্টি-রিঙ্কেল শিল্ড চালু থাকলেই প্রযোজ্য।" + } + }, + "delay_start_section": { + "name": "বিলম্বিত শুরু সনাক্তকরণ", + "description": "চক্রটি সত্যিই শুরু না হওয়া পর্যন্ত স্থায়ী নিম্ন-শক্তির স্ট্যান্ডবাইকে 'শুরু করার জন্য অপেক্ষা' হিসেবে ধরুন।", + "data": { + "delay_start_detect_enabled": "বিলম্বিত শুরু সনাক্তকরণ সক্ষম করুন", + "delay_confirm_seconds": "বিলম্বিত শুরু: স্ট্যান্ডবাই নিশ্চিতকরণ সময় (সেকেন্ড)", + "delay_timeout_hours": "বিলম্বিত শুরু: সর্বোচ্চ অপেক্ষার সময় (h)" + }, + "data_description": { + "delay_start_detect_enabled": "যখন সক্রিয় করা হয়, একটি সংক্ষিপ্ত কম-পাওয়ার ড্রেন স্পাইক এবং টেকসই স্ট্যান্ডবাই পাওয়ার দ্বারা অনুসরণ করা বিলম্বিত শুরু হিসাবে সনাক্ত করা হয়। রাজ্য সেন্সর বিলম্বের সময় 'শুরু করার জন্য অপেক্ষা' দেখায়। চক্রটি আসলে শুরু না হওয়া পর্যন্ত কোনও প্রোগ্রামের সময় বা অগ্রগতি ট্র্যাক করা হয় না। ড্রেন স্পাইক পাওয়ারের উপরে সেট করার জন্য start_threshold_w প্রয়োজন।", + "delay_confirm_seconds": "WashData 'শুরু করার জন্য অপেক্ষা' অবস্থায় যাওয়ার আগে পাওয়ারকে এত সেকেন্ড স্টপ থ্রেশহোল্ড (W) এবং স্টার্ট থ্রেশহোল্ড (W)-এর মধ্যে থাকতে হবে। এটি মেনু নেভিগেশনের সংক্ষিপ্ত পিকগুলোকে ফিল্টার করে। ডিফল্ট: 60 সেকেন্ড।", + "delay_timeout_hours": "সেফটি টাইমআউট: যদি অনেক ঘন্টা পরেও মেশিনটি 'শুরু হওয়ার অপেক্ষায়' থাকে, ওয়াশডেটা বন্ধ হয়ে যায়। ডিফল্ট: 8 ঘন্টা।" + } + }, + "external_triggers_section": { + "name": "বাহ্যিক ট্রিগার, দরজা ও বিরতি", + "description": "ঐচ্ছিক বাহ্যিক শেষ-ট্রিগার সেন্সর, বিরতি/পরিষ্কার শনাক্তকরণের জন্য দরজা সেন্সর, এবং বিরতির সময় পাওয়ার-কাট সুইচ।", + "data": { + "external_end_trigger_enabled": "এক্সটার্নাল সাইকেল এন্ড ট্রিগার সক্ষম করুন", + "external_end_trigger": "বাহ্যিক চক্র শেষ সেন্সর", + "external_end_trigger_inverted": "ইনভার্ট ট্রিগার লজিক (ট্রিগার অন অফ)", + "door_sensor_entity": "ডোর সেন্সর", + "pause_cuts_power": "বিরাম দেওয়ার সময় পাওয়ার কাটা", + "switch_entity": "সত্তা স্যুইচ করুন (পজ পাওয়ার কাটের জন্য)", + "notify_unload_delay_minutes": "লন্ড্রি অপেক্ষার বিজ্ঞপ্তি বিলম্ব (মিনিট)" + }, + "data_description": { + "external_end_trigger_enabled": "চক্র সমাপ্তি ট্রিগার করতে একটি বাহ্যিক বাইনারি সেন্সর নিরীক্ষণ সক্ষম করুন৷", + "external_end_trigger": "একটি বাইনারি সেন্সর সত্তা নির্বাচন করুন। যখন এই সেন্সরটি ট্রিগার হয়, বর্তমান চক্রটি 'সম্পূর্ণ' স্ট্যাটাস দিয়ে শেষ হবে।", + "external_end_trigger_inverted": "ডিফল্টরূপে, সেন্সর চালু হলে ট্রিগারটি জ্বলে। পরিবর্তে সেন্সর বন্ধ হয়ে গেলে ফায়ার করার জন্য এই বাক্সটি চেক করুন৷", + "door_sensor_entity": "মেশিনের দরজার জন্য ঐচ্ছিক বাইনারি সেন্সর (চালু = খোলা, বন্ধ = বন্ধ)। একটি সক্রিয় চক্র চলাকালীন দরজা খোলা হলে, WashData ইচ্ছাকৃতভাবে বিরতি হিসাবে চক্রটিকে নিশ্চিত করবে৷ চক্র শেষ হওয়ার পরে, যদি দরজাটি এখনও বন্ধ থাকে, তবে দরজা খোলা না হওয়া পর্যন্ত রাজ্যটি 'পরিষ্কার'-এ পরিবর্তিত হয়। দ্রষ্টব্য: দরজা বন্ধ করার ফলে একটি বিরতিকৃত চক্র স্বয়ংক্রিয়ভাবে পুনরায় শুরু হয় না - সারসংকলনটি অবশ্যই রিজিউম সাইকেল বোতাম বা পরিষেবার মাধ্যমে ম্যানুয়ালি ট্রিগার করতে হবে।", + "pause_cuts_power": "পজ সাইকেল বোতাম বা পরিষেবার মাধ্যমে বিরতি দেওয়ার সময়, কনফিগার করা সুইচ সত্তাটিও বন্ধ করুন। এই ডিভাইসের জন্য একটি সুইচ সত্তা কনফিগার করা থাকলেই কেবল কার্যকর হয়৷", + "switch_entity": "মেরামত অথবা রি-সেট করার জন্য ব্যবহৃত পার্টিশন যখন সংযোগ সক্রিয় করা হয় তখন প্রয়োজন বোধ করো ।", + "notify_unload_delay_minutes": "চক্রটি শেষ হওয়ার পরে ফিনিস নোটিফিকেশন চ্যানেলের মাধ্যমে একটি বিজ্ঞপ্তি পাঠান এবং এই অনেক মিনিটের জন্য দরজা খোলা হয়নি (ডোর সেন্সর প্রয়োজন)। নিষ্ক্রিয় করতে 0 এ সেট করুন। ডিফল্ট: 60 মিনিট।" + } + }, + "pump_section": { + "name": "পাম্প মনিটর", + "description": "শুধুমাত্র পাম্প ডিভাইস ধরন। পাম্প সাইকেল এই সময়সীমা ছাড়িয়ে গেলে একটি ইভেন্ট চালু করে।", + "data": { + "pump_stuck_duration": "পাম্প আটকে সতর্কতা থ্রেশহোল্ড (সেকেন্ড) (শুধুমাত্র পাম্প)" + }, + "data_description": { + "pump_stuck_duration": "যদি অনেক সেকেন্ডের পরেও একটি পাম্প চক্র চলতে থাকে, একটি ha_washdata_pump_stuck ইভেন্ট একবার ফায়ার করা হয়। একটি জ্যাম করা মোটর সনাক্ত করতে এটি ব্যবহার করুন (সাধারণত সাম্প পাম্প চালানো হয় 60 সেকেন্ডের নিচে)। ডিফল্ট: 1800 সেকেন্ড (30 মিনিট)। শুধুমাত্র পাম্প ডিভাইসের ধরন।" + } + }, + "device_link_section": { + "name": "ডিভাইস লিঙ্ক", + "description": "ঐচ্ছিকভাবে এই ওয়াশড ডাটা ডিভাইসটি একটি উপস্থিত Home Assistant ডিভাইসের সাথে সংযুক্ত করা হয়েছে (যেমন স্মার্ট প্লাগ-ইন অথবা অ্যাপ্লিকেশন নিজের)। ওয়াশিং ডাটা ডিভাইস এর মাধ্যমেই দেখা যাবে... ... ডিভাইসের সাথে সংযুক্ত হওয়ার জন্য। একটি স্বতন্ত্র ডিভাইস হিসেবে পরিষ্কার রাখুন।", + "data": { + "linked_device": "লিঙ্ক করা ডিভাইস" + }, + "data_description": { + "linked_device": "সংযুক্ত করার জন্য ডিভাইসটি নির্বাচন করুন । এটি ডিভাইস রেজিস্ট্রিতে উল্লেখ করা 'সংযোগ' যুক্ত করে; ওয়াশিং ডাটা তার নিজস্ব ডিভাইস কার্ড এবং সত্ত্বাতে রাখে। নির্বাচিত অংশ মুছে ফেলুন" + } + } + } + }, + "diagnostics": { + "title": "ডায়াগনস্টিকস এবং রক্ষণাবেক্ষণ", + "description": "খণ্ডিত চক্র একত্রিত করা বা সঞ্চিত ডেটা স্থানান্তর করার মতো রক্ষণাবেক্ষণ কর্মগুলি চালান৷\n\n**স্টোরেজ ব্যবহার**\n\n- ফাইলের আকার: {file_size_kb} KB\n- চক্র: {cycle_count}\n- প্রোফাইল: {profile_count}\n- ডিবাগ ট্রেস: {debug_count}", + "menu_options": { + "reprocess_history": "রক্ষণাবেক্ষণ: পুনঃপ্রক্রিয়া এবং ডেটা অপ্টিমাইজ করুন", + "clear_debug_data": "ডিবাগ ডেটা সাফ করুন (স্থান খালি করুন)", + "wipe_history": "এই ডিভাইসের জন্য সমস্ত ডেটা মুছুন (অপরিবর্তনযোগ্য)", + "export_import": "সেটিংস সহ JSON রপ্তানি/আমদানি করুন (কপি/পেস্ট)", + "menu_back": "← ফিরে যান" + } + }, + "clear_debug_data": { + "title": "ডিবাগ ডেটা সাফ করুন", + "description": "আপনি কি সব সঞ্চিত ডিবাগ ট্রেস মুছে ফেলার বিষয়ে নিশ্চিত? এটি স্থান খালি করবে কিন্তু অতীত চক্র থেকে বিশদ র‌্যাঙ্কিং তথ্য সরিয়ে দেবে।" + }, + "manage_cycles": { + "title": "সাইকেল পরিচালনা করুন", + "description": "সাম্প্রতিক চক্র:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "অটো-লেবেল পুরানো চক্র", + "select_cycle_to_label": "লেবেল নির্দিষ্ট চক্র", + "select_cycle_to_delete": "সাইকেল মুছুন", + "interactive_editor": "ইন্টারেক্টিভ এডিটর মার্জ/স্প্লিট করুন", + "trim_cycle_select": "ট্রিম সাইকেল ডেটা", + "menu_back": "← ফিরে যান" + } + }, + "manage_cycles_empty": { + "title": "সাইকেল পরিচালনা করুন", + "description": "কোন চক্র এখনও রেকর্ড করা হয়নি।", + "menu_options": { + "auto_label_cycles": "অটো-লেবেল পুরানো চক্র", + "menu_back": "← ফিরে যান" + } + }, + "manage_profiles": { + "title": "প্রোফাইল পরিচালনা করুন", + "description": "প্রোফাইল সারাংশ:\n{profile_summary}", + "menu_options": { + "create_profile": "নতুন প্রোফাইল তৈরি করুন", + "edit_profile": "প্রোফাইল সম্পাদনা/পুনঃনামকরণ করুন", + "delete_profile_select": "প্রোফাইল মুছুন", + "profile_stats": "প্রোফাইল পরিসংখ্যান", + "cleanup_profile": "ইতিহাস পরিষ্কার করুন - গ্রাফ এবং মুছুন", + "assign_profile_phases_select": "ফেজ রেঞ্জ বরাদ্দ", + "menu_back": "← ফিরে যান" + } + }, + "manage_profiles_empty": { + "title": "প্রোফাইল পরিচালনা করুন", + "description": "এখনো কোন প্রোফাইল তৈরি করা হয়নি.", + "menu_options": { + "create_profile": "নতুন প্রোফাইল তৈরি করুন", + "menu_back": "← ফিরে যান" + } + }, + "manage_phase_catalog": { + "title": "ফেজ ক্যাটালগ পরিচালনা করুন", + "description": "ফেজ ক্যাটালগ (এই ডিভাইসের জন্য ডিফল্ট + কাস্টম পর্যায়গুলি):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "নতুন ফেজ তৈরি করুন", + "phase_catalog_edit_select": "ফেজ সম্পাদনা করুন", + "phase_catalog_delete": "ফেজ মুছুন", + "menu_back": "← ফিরে যান" + } + }, + "phase_catalog_create": { + "title": "কাস্টম ফেজ তৈরি করুন", + "description": "একটি কাস্টম ফেজের নাম এবং বিবরণ তৈরি করুন এবং এটি কোন ডিভাইসের জন্য প্রযোজ্য তা চয়ন করুন৷", + "data": { + "target_device_type": "টার্গেট ডিভাইসের ধরন", + "phase_name": "ফেজের নাম", + "phase_description": "ফেজ বিবরণ" + } + }, + "phase_catalog_edit_select": { + "title": "কাস্টম ফেজ সম্পাদনা করুন", + "description": "সম্পাদনা করতে একটি কাস্টম ফেজ নির্বাচন করুন.", + "data": { + "phase_name": "কাস্টম ফেজ" + } + }, + "phase_catalog_edit": { + "title": "কাস্টম ফেজ সম্পাদনা করুন", + "description": "সম্পাদনা পর্ব: {phase_name}", + "data": { + "phase_name": "ফেজের নাম", + "phase_description": "ফেজ বিবরণ" + } + }, + "phase_catalog_delete": { + "title": "কাস্টম ফেজ মুছুন", + "description": "মুছে ফেলার জন্য একটি কাস্টম ফেজ নির্বাচন করুন। এই পর্ব ব্যবহার করে যেকোনও বরাদ্দকৃত ব্যাপ্তি মুছে ফেলা হবে।", + "data": { + "phase_name": "কাস্টম ফেজ" + } + }, + "assign_profile_phases": { + "title": "ফেজ রেঞ্জ বরাদ্দ", + "description": "প্রোফাইলের জন্য পর্যায় পূর্বরূপ: **{profile_name}**\n\n{timeline_svg}\n\n**বর্তমান রেঞ্জ:**\n{current_ranges}\n\nব্যাপ্তি যোগ করতে, সম্পাদনা করতে, মুছতে বা সংরক্ষণ করতে নিচের ক্রিয়াগুলি ব্যবহার করুন৷", + "menu_options": { + "assign_profile_phases_add": "ফেজ রেঞ্জ যোগ করুন", + "assign_profile_phases_edit_select": "ফেজ পরিসীমা সম্পাদনা করুন", + "assign_profile_phases_delete": "ফেজ রেঞ্জ মুছুন", + "phase_ranges_clear": "সমস্ত রেঞ্জ সাফ করুন", + "assign_profile_phases_auto_detect": "পর্যায়গুলি স্বয়ংক্রিয়ভাবে সনাক্ত করুন৷", + "phase_ranges_save": "সংরক্ষণ করুন এবং ফেরত দিন", + "menu_back": "← ফিরে যান" + } + }, + "assign_profile_phases_add": { + "title": "ফেজ রেঞ্জ যোগ করুন", + "description": "বর্তমান খসড়াতে একটি ফেজ পরিসর যোগ করুন।", + "data": { + "phase_name": "পর্যায়", + "start_min": "স্টার্ট মিনিট", + "end_min": "শেষ মিনিট" + } + }, + "assign_profile_phases_edit_select": { + "title": "ফেজ পরিসীমা সম্পাদনা করুন", + "description": "সম্পাদনা করার জন্য একটি পরিসর নির্বাচন করুন৷", + "data": { + "range_index": "ফেজ রেঞ্জ" + } + }, + "assign_profile_phases_edit": { + "title": "ফেজ পরিসীমা সম্পাদনা করুন", + "description": "নির্বাচিত ফেজ পরিসীমা আপডেট করুন।", + "data": { + "phase_name": "পর্যায়", + "start_min": "স্টার্ট মিনিট", + "end_min": "শেষ মিনিট" + } + }, + "assign_profile_phases_delete": { + "title": "ফেজ রেঞ্জ মুছুন", + "description": "খসড়া থেকে সরানোর জন্য একটি ব্যাপ্তি নির্বাচন করুন৷", + "data": { + "range_index": "ফেজ রেঞ্জ" + } + }, + "assign_profile_phases_auto_detect": { + "title": "স্বয়ংক্রিয়ভাবে সনাক্ত করা পর্যায়গুলি", + "description": "প্রোফাইল: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} পর্যায়(গুলি) স্বয়ংক্রিয়ভাবে শনাক্ত হয়েছে।** নিচের একটি ক্রিয়া বেছে নিন।", + "data": { + "action": "অ্যাকশন" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "নাম সনাক্ত পর্যায়", + "description": "প্রোফাইল: **{profile_name}**\n\n**{detected_count} পর্যায়(গুলি) সনাক্ত করা হয়েছে:**\n{ranges_summary}\n\nপ্রতিটি পর্বে একটি নাম বরাদ্দ করুন।" + }, + "assign_profile_phases_select": { + "title": "ফেজ রেঞ্জ বরাদ্দ", + "description": "ফেজ রেঞ্জ কনফিগার করতে একটি প্রোফাইল নির্বাচন করুন।", + "data": { + "profile": "প্রোফাইল" + } + }, + "profile_stats": { + "title": "প্রোফাইল পরিসংখ্যান", + "description": "{stats_table}" + }, + "create_profile": { + "title": "নতুন প্রোফাইল তৈরি করুন", + "description": "ম্যানুয়ালি বা অতীত চক্র থেকে একটি নতুন প্রোফাইল তৈরি করুন।\n\nপ্রোফাইল নামের উদাহরণ: 'উদাহরণমূলক', 'হেভি ডিউটি', 'কুইক ওয়াশ'", + "data": { + "profile_name": "প্রোফাইল নাম", + "reference_cycle": "রেফারেন্স সাইকেল (ঐচ্ছিক)", + "manual_duration": "ম্যানুয়াল সময়কাল (মিনিট)" + } + }, + "edit_profile": { + "title": "প্রোফাইল সম্পাদনা করুন", + "description": "নাম পরিবর্তন করতে একটি প্রোফাইল নির্বাচন করুন", + "data": { + "profile": "প্রোফাইল" + } + }, + "rename_profile": { + "title": "প্রোফাইল বিবরণ সম্পাদনা করুন", + "description": "বর্তমান প্রোফাইল: {current_name}", + "data": { + "new_name": "প্রোফাইল নাম", + "manual_duration": "ম্যানুয়াল সময়কাল (মিনিট)" + } + }, + "delete_profile_select": { + "title": "প্রোফাইল মুছুন", + "description": "মুছে ফেলার জন্য একটি প্রোফাইল নির্বাচন করুন", + "data": { + "profile": "প্রোফাইল" + } + }, + "delete_profile_confirm": { + "title": "প্রোফাইল মুছে ফেলা নিশ্চিত করুন", + "description": "⚠️ এটি স্থায়ীভাবে প্রোফাইল মুছে দেবে: {profile_name}", + "data": { + "unlabel_cycles": "এই প্রোফাইল ব্যবহার করে চক্র থেকে লেবেল সরান" + } + }, + "auto_label_cycles": { + "title": "অটো-লেবেল পুরানো চক্র", + "description": "মোট {total_count}টি চক্র পাওয়া গেছে। প্রোফাইল: {profiles}", + "data": { + "confidence_threshold": "ন্যূনতম আত্মবিশ্বাস (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "সাইকেল টু লেবেল নির্বাচন করুন", + "description": "✓ = সম্পন্ন, ⚠ = পুনরায় শুরু, ✗ = বাধাপ্রাপ্ত", + "data": { + "cycle_id": "চক্র" + } + }, + "select_cycle_to_delete": { + "title": "সাইকেল মুছুন", + "description": "⚠️ এটি স্থায়ীভাবে নির্বাচিত চক্র মুছে ফেলবে৷", + "data": { + "cycle_id": "মুছে ফেলার জন্য চক্র" + } + }, + "label_cycle": { + "title": "লেবেল চক্র", + "description": "{cycle_info}", + "data": { + "profile_name": "প্রোফাইল", + "new_profile_name": "নতুন প্রোফাইল নাম (যদি তৈরি করা হয়)" + } + }, + "post_process": { + "title": "প্রক্রিয়া ইতিহাস (একত্রীকরণ/বিভক্ত)", + "description": "খণ্ডিত রানগুলিকে একত্রিত করে এবং একটি টাইম উইন্ডোর মধ্যে ভুলভাবে মার্জ করা চক্রগুলিকে বিভক্ত করে চক্রের ইতিহাস পরিষ্কার করুন৷ ফিরে দেখার জন্য ঘন্টার সংখ্যা লিখুন (সকলের জন্য 999999 ব্যবহার করুন)।", + "data": { + "time_range": "লুকব্যাক উইন্ডো (ঘন্টা)", + "gap_seconds": "একত্রিত/বিভক্ত ব্যবধান (সেকেন্ড)" + }, + "data_description": { + "time_range": "একত্রিত করার জন্য খণ্ডিত চক্রের জন্য স্ক্যান করার জন্য ঘন্টার সংখ্যা৷ সমস্ত ঐতিহাসিক ডেটা প্রক্রিয়া করতে 999999 ব্যবহার করুন।", + "gap_seconds": "বিভক্ত বনাম একত্রীকরণের সিদ্ধান্ত নেওয়ার থ্রেশহোল্ড৷ এর চেয়ে ছোট ফাঁক একত্রিত করা হয়. এর চেয়ে দীর্ঘ ব্যবধান বিভক্ত হয়। ভুলভাবে মার্জ করা একটি চক্রের উপর জোর করে বিভক্ত করতে এটিকে কম করুন।" + } + }, + "cleanup_profile": { + "title": "ইতিহাস পরিষ্কার করুন - প্রোফাইল নির্বাচন করুন", + "description": "কল্পনা করার জন্য একটি প্রোফাইল নির্বাচন করুন। এটি বহিরাগতদের সনাক্ত করতে সাহায্য করার জন্য এই প্রোফাইলের সমস্ত অতীত চক্র দেখানো একটি গ্রাফ তৈরি করবে৷", + "data": { + "profile": "প্রোফাইল" + } + }, + "cleanup_select": { + "title": "ইতিহাস পরিষ্কার করুন - গ্রাফ এবং মুছুন", + "description": "![গ্রাফ]({graph_url})\n\n**ভিজ্যুয়ালাইজ করা {profile_name}**\n\nগ্রাফটি পৃথক চক্রকে রঙিন লাইন হিসাবে দেখায়। তাদের মুছে ফেলার জন্য নীচের তালিকায় বহিরাগতদের (গোষ্ঠী থেকে দূরে লাইন) সনাক্ত করুন এবং তাদের রঙের সাথে মিল করুন।\n\n**স্থায়ীভাবে মুছে ফেলার জন্য চক্র নির্বাচন করুন:**", + "data": { + "cycles_to_delete": "মুছে ফেলার জন্য সাইকেল (মেলে রং চেক করুন)" + } + }, + "interactive_editor": { + "title": "ইন্টারেক্টিভ এডিটর মার্জ/স্প্লিট করুন", + "description": "আপনার চক্রের ইতিহাসে সঞ্চালনের জন্য একটি ম্যানুয়াল অ্যাকশন নির্বাচন করুন।", + "menu_options": { + "editor_split": "একটি চক্র বিভক্ত করুন (শূন্যস্থান খুঁজুন)", + "editor_merge": "সাইকেল একত্র করুন (খন্ডে যোগ দিন)", + "editor_delete": "চক্র(গুলি) মুছুন", + "menu_back": "← ফিরে যান" + } + }, + "editor_select": { + "title": "সাইকেল নির্বাচন করুন", + "description": "প্রক্রিয়া করার জন্য চক্র(গুলি) নির্বাচন করুন।\n\n{info_text}", + "data": { + "selected_cycles": "সাইকেল" + } + }, + "editor_configure": { + "title": "কনফিগার করুন এবং প্রয়োগ করুন", + "description": "{preview_md}", + "data": { + "confirm_action": "অ্যাকশন", + "merged_profile": "ফলাফল প্রোফাইল", + "new_profile_name": "নতুন প্রোফাইল নাম", + "confirm_commit": "হ্যাঁ, আমি এই ক্রিয়াটি নিশ্চিত করছি", + "segment_0_profile": "সেগমেন্ট 1 প্রোফাইল", + "segment_1_profile": "সেগমেন্ট 2 প্রোফাইল", + "segment_2_profile": "সেগমেন্ট 3 প্রোফাইল", + "segment_3_profile": "সেগমেন্ট 4 প্রোফাইল", + "segment_4_profile": "সেগমেন্ট 5 প্রোফাইল", + "segment_5_profile": "সেগমেন্ট 6 প্রোফাইল", + "segment_6_profile": "সেগমেন্ট 7 প্রোফাইল", + "segment_7_profile": "সেগমেন্ট 8 প্রোফাইল", + "segment_8_profile": "সেগমেন্ট 9 প্রোফাইল", + "segment_9_profile": "সেগমেন্ট 10 প্রোফাইল" + } + }, + "editor_split_params": { + "title": "বিভক্ত কনফিগারেশন", + "description": "এই চক্রটি বিভক্ত করার জন্য সংবেদনশীলতা সামঞ্জস্য করুন। এর থেকে দীর্ঘ সময়ের কোনো নিষ্ক্রিয় ব্যবধান একটি বিভাজনের কারণ হবে।", + "data": { + "split_mode": "বিভাজন পদ্ধতি" + } + }, + "editor_split_auto_params": { + "title": "স্বয়ংক্রিয় বিভাজন কনফিগারেশন", + "description": "এই সাইকেলটি ভাগ করার সংবেদনশীলতা সামঞ্জস্য করুন। এর চেয়ে দীর্ঘ যেকোনো নিষ্ক্রিয় বিরতি বিভাজন ঘটাবে।", + "data": { + "min_gap_seconds": "বিভাজন বিরতি সীমা (সেকেন্ড)" + } + }, + "editor_split_manual_params": { + "title": "ম্যানুয়াল বিভাজন টাইমস্ট্যাম্প", + "description": "{preview_md}সাইকেল উইন্ডো: **{cycle_start_wallclock} → {cycle_end_wallclock}** তারিখ {cycle_date}.\n\nএক বা একাধিক বিভাজন টাইমস্ট্যাম্প (`HH:MM` বা `HH:MM:SS`) লিখুন, প্রতি লাইনে একটি করে। প্রতিটি টাইমস্ট্যাম্পে সাইকেলটি কাটা হবে — Nটি টাইমস্ট্যাম্পে N+1টি অংশ তৈরি হবে।", + "data": { + "split_timestamps": "বিভাজন টাইমস্ট্যাম্প" + } + }, + "reprocess_history": { + "title": "রক্ষণাবেক্ষণ: পুনঃপ্রক্রিয়া এবং ডেটা অপ্টিমাইজ করুন", + "description": "ঐতিহাসিক তথ্যের গভীর পরিচ্ছন্নতা সম্পাদন করে:\n1. শূন্য-পাওয়ার রিডিং (নীরবতা) ছাঁটাই করে।\n2. চক্রের সময়/সময়কাল ঠিক করে।\n3. সমস্ত পরিসংখ্যান মডেল (খাম) পুনরায় গণনা করে।\n\n**ডেটা সমস্যা সমাধান করতে বা নতুন অ্যালগরিদম প্রয়োগ করতে এটি চালান।**" + }, + "wipe_history": { + "title": "ইতিহাস মুছা (শুধুমাত্র পরীক্ষা)", + "description": "⚠️ এটি স্থায়ীভাবে এই ডিভাইসের জন্য সমস্ত সঞ্চিত চক্র এবং প্রোফাইল মুছে ফেলবে৷ এটি পূর্বাবস্থায় ফেরানো যাবে না!" + }, + "export_import": { + "title": "JSON রপ্তানি/আমদানি করুন", + "description": "এই ডিভাইসের জন্য চক্র, প্রোফাইল, এবং সেটিংস কপি/পেস্ট করুন। নীচে রপ্তানি করুন বা আমদানি করতে JSON পেস্ট করুন।", + "data": { + "mode": "অ্যাকশন", + "json_payload": "JSON কনফিগারেশন সম্পূর্ণ করুন" + }, + "data_description": { + "mode": "বর্তমান ডেটা এবং সেটিংস কপি করতে রপ্তানি বা অন্য ওয়াশডেটা ডিভাইস থেকে রপ্তানি করা ডেটা পেস্ট করতে আমদানি বেছে নিন।", + "json_payload": "রপ্তানির জন্য: এই JSON অনুলিপি করুন (চক্র, প্রোফাইল, এবং সমস্ত সূক্ষ্ম-টিউন করা সেটিংস অন্তর্ভুক্ত)। আমদানির জন্য: WashData থেকে রপ্তানি করা JSON পেস্ট করুন।" + } + }, + "record_cycle": { + "title": "রেকর্ড সাইকেল", + "description": "হস্তক্ষেপ ছাড়াই একটি পরিষ্কার প্রোফাইল তৈরি করতে ম্যানুয়ালি একটি চক্র রেকর্ড করুন।\n\nস্থিতি: **{status}**\nসময়কাল: {duration}সে\nনমুনা: {samples}", + "menu_options": { + "record_refresh": "রিফ্রেশ স্থিতি", + "record_stop": "রেকর্ডিং বন্ধ করুন (সংরক্ষণ এবং প্রক্রিয়া)", + "record_start": "নতুন রেকর্ডিং শুরু করুন", + "record_process": "শেষ রেকর্ডিং প্রক্রিয়া করুন (ছাঁটা ও সংরক্ষণ করুন)", + "record_discard": "শেষ রেকর্ডিং বাতিল করুন", + "menu_back": "← ফিরে যান" + } + }, + "record_process": { + "title": "প্রক্রিয়া রেকর্ডিং", + "description": "![গ্রাফ]({graph_url})\n\n**গ্রাফ অশোধিত রেকর্ডিং দেখায়।** নীল = রাখুন, লাল = প্রস্তাবিত ট্রিম।\n*গ্রাফ স্থির এবং ফর্ম পরিবর্তনের সাথে আপডেট হয় না।*\n\nরেকর্ডিং বন্ধ। ট্রিমগুলি সনাক্ত করা স্যাম্পলিং রেট (~{sampling_rate}গুলি) এর সাথে সারিবদ্ধ করা হয়৷\n\nকাঁচা সময়কাল: {duration}সে\nনমুনা: {samples}", + "data": { + "head_trim": "ট্রিম স্টার্ট (সেকেন্ড)", + "tail_trim": "ট্রিম এন্ড (সেকেন্ড)", + "save_mode": "গন্তব্য সংরক্ষণ করুন", + "profile_name": "প্রোফাইল নাম" + } + }, + "learning_feedbacks": { + "title": "শেখার প্রতিক্রিয়া", + "description": "মুলতুবি প্রতিক্রিয়া: {count}\n\n{pending_table}\n\nপ্রক্রিয়াকরণের জন্য একটি সাইকেল পর্যালোচনা অনুরোধ নির্বাচন করুন।", + "menu_options": { + "learning_feedbacks_pick": "একটি মুলতুবি প্রতিক্রিয়া পর্যালোচনা করুন", + "learning_feedbacks_dismiss_all": "সব মুলতুবি প্রতিক্রিয়া বাতিল করুন", + "menu_back": "← ফিরে যান" + } + }, + "learning_feedbacks_pick": { + "title": "পর্যালোচনার জন্য প্রতিক্রিয়া বেছে নিন", + "description": "খোলার জন্য একটি সাইকেল পর্যালোচনা অনুরোধ নির্বাচন করুন।", + "data": { + "selected_feedback": "পর্যালোচনার জন্য সাইকেল নির্বাচন করুন" + } + }, + "learning_feedbacks_empty": { + "title": "শেখার প্রতিক্রিয়া", + "description": "কোনো মুলতুবি প্রতিক্রিয়া অনুরোধ পাওয়া যায়নি.", + "menu_options": { + "menu_back": "← ফিরে যান" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "সমস্ত মুলতুবি প্রতিক্রিয়া বাতিল করুন", + "description": "আপনি **{count}**টি মুলতুবি প্রতিক্রিয়া অনুরোধ বাতিল করতে চলেছেন। বাতিল করা সাইকেলগুলি আপনার ইতিহাসে থাকবে, তবে নতুন শেখার সংকেতে অবদান রাখবে না। এটি পূর্বাবস্থায় ফেরানো যাবে না.\n\n✅ সবগুলো বাতিল করতে নিচের বাক্সে টিক দিন এবং **জমা দিন** এ ক্লিক করুন.\n❌ এটিকে টিক ছাড়া রেখে **জমা দিন** এ ক্লিক করে বাতিল করুন এবং ফিরে যান।", + "data": { + "confirm_dismiss_all": "নিশ্চিত করুন: সব মুলতুবি প্রতিক্রিয়া অনুরোধ বাতিল করুন" + } + }, + "resolve_feedback": { + "title": "প্রতিক্রিয়া সমাধান করুন", + "description": "{comparison_img}{candidates_table}\n**শনাক্ত করা প্রোফাইল**: {detected_profile} ({confidence_pct}%)\n**আনুমানিক সময়কাল**: {est_duration_min} মিনিট\n**প্রকৃত সময়কাল**: {act_duration_min} মিনিট\n\n__নীচে একটি কর্ম চয়ন করুন:__", + "data": { + "action": "আপনি কি করতে চান?", + "corrected_profile": "সঠিক প্রোগ্রাম (যদি সংশোধন করা হয়)", + "corrected_duration": "সেকেন্ডে সঠিক সময়কাল (যদি সংশোধন করা হয়)" + } + }, + "trim_cycle_select": { + "title": "ট্রিম সাইকেল - সাইকেল নির্বাচন করুন", + "description": "ট্রিম করার জন্য একটি চক্র নির্বাচন করুন। ✓ = সম্পন্ন, ⚠ = পুনরায় শুরু, ✗ = বাধাপ্রাপ্ত", + "data": { + "cycle_id": "চক্র" + } + }, + "trim_cycle": { + "title": "ট্রিম সাইকেল", + "description": "বর্তমান উইন্ডো: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "অ্যাকশন" + } + }, + "trim_cycle_start": { + "title": "ট্রিম স্টার্ট সেট করুন", + "description": "সাইকেল উইন্ডো: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nবর্তমান শুরু: **{current_wallclock}** (সাইকেল শুরু থেকে +{current_offset_min} মিনিট)\n\nনীচের ঘড়ি পিকার ব্যবহার করে একটি নতুন শুরুর সময় বেছে নিন।", + "data": { + "trim_start_time": "নতুন শুরুর সময়", + "trim_start_min": "নতুন শুরু (সাইকেল শুরু থেকে মিনিট)" + } + }, + "trim_cycle_end": { + "title": "ট্রিম এন্ড সেট করুন", + "description": "সাইকেল উইন্ডো: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nবর্তমান শেষ: **{current_wallclock}** (সাইকেল শুরু থেকে +{current_offset_min} মিনিট)\n\nনিচের ক্লক পিকার ব্যবহার করে একটি নতুন শেষ সময় বেছে নিন।", + "data": { + "trim_end_time": "নতুন শেষ সময়", + "trim_end_min": "নতুন শেষ (চক্র শুরু থেকে মিনিট)" + } + } + }, + "error": { + "import_failed": "আমদানি ব্যর্থ হয়েছে৷ অনুগ্রহ করে একটি বৈধ ওয়াশডেটা এক্সপোর্ট JSON পেস্ট করুন।", + "select_exactly_one": "এই কাজের জন্য ঠিক একটি সাইকেল নির্বাচন করুন।", + "select_at_least_one": "এই কাজের জন্য অন্তত একটি সাইকেল নির্বাচন করুন।", + "select_at_least_two": "এই কাজের জন্য অন্তত দুটি সাইকেল নির্বাচন করুন।", + "profile_exists": "এই নামের একটি প্রোফাইল ইতিমধ্যেই বিদ্যমান৷", + "rename_failed": "প্রোফাইল পুনঃনামকরণ ব্যর্থ হয়েছে. প্রোফাইল বিদ্যমান নাও থাকতে পারে বা নতুন নাম ইতিমধ্যেই নেওয়া হয়েছে৷", + "assignment_failed": "সাইকেলে প্রোফাইল বরাদ্দ করতে ব্যর্থ হয়েছে৷", + "duplicate_phase": "এই নামের একটি পর্যায় ইতিমধ্যেই বিদ্যমান।", + "phase_not_found": "নির্বাচিত পর্যায় পাওয়া যায়নি.", + "invalid_phase_name": "ফেজ নাম খালি হতে পারে না.", + "phase_name_too_long": "পর্যায় নামটি খুব দীর্ঘ৷", + "invalid_phase_range": "ফেজ ব্যাপ্তি অবৈধ৷ শেষ হতে হবে শুরুর চেয়ে বড়।", + "invalid_phase_timestamp": "টাইমস্ট্যাম্প অবৈধ৷ YYYY-MM-DD HH:MM বা HH:MM ফর্ম্যাট ব্যবহার করুন।", + "overlapping_phase_ranges": "ফেজ ব্যাপ্তি ওভারল্যাপ. ব্যাপ্তিগুলি সামঞ্জস্য করুন এবং আবার চেষ্টা করুন৷", + "incomplete_phase_row": "প্রতিটি ফেজ সারিতে অবশ্যই একটি ফেজ এবং নির্বাচিত মোডের জন্য সম্পূর্ণ শুরু/শেষ মান অন্তর্ভুক্ত করতে হবে।", + "timestamp_mode_cycle_required": "টাইমস্ট্যাম্প মোড ব্যবহার করার সময় একটি উত্স চক্র নির্বাচন করুন.", + "no_phase_ranges": "সেই কর্মের জন্য এখনও কোন ফেজ রেঞ্জ উপলব্ধ নেই।", + "feedback_notification_title": "ওয়াশডেটা: সাইকেল যাচাই করুন ({device})", + "feedback_notification_message": "**ডিভাইস**: {device}\n**প্রোগ্রাম**: {program} ({confidence}% আত্মবিশ্বাস)\n**সময়**: {time}\n\nএই শনাক্ত করা চক্রটি যাচাই করতে WashData-এর আপনার সাহায্য প্রয়োজন।\n\nএই ফলাফল নিশ্চিত করতে বা সংশোধন করতে অনুগ্রহ করে **সেটিংস > ডিভাইস ও পরিষেবা > ওয়াশডেটা > কনফিগার > লার্নিং ফিডব্যাক**-এ যান।", + "suggestions_ready_notification_title": "ওয়াশডেটা: প্রস্তাবিত সেটিংস প্রস্তুত ({device})", + "suggestions_ready_notification_message": "**প্রস্তাবিত সেটিংস** সেন্সর এখন **{count}** অ্যাকশনেবল সুপারিশের রিপোর্ট করে।\n\nসেগুলি পর্যালোচনা এবং প্রয়োগ করতে: **সেটিংস > ডিভাইস ও পরিষেবা > ওয়াশডেটা > কনফিগার > উন্নত সেটিংস > প্রস্তাবিত মান প্রয়োগ করুন**।\n\nপরামর্শগুলি ঐচ্ছিক এবং আপনি সংরক্ষণ করার আগে পর্যালোচনার জন্য দেখানো হয়৷", + "trim_range_invalid": "শুরু শেষ হওয়ার আগে হতে হবে। আপনার ট্রিম পয়েন্ট সমন্বয় করুন.", + "trim_failed": "ট্রিম ব্যর্থ হয়েছে. চক্রটি আর বিদ্যমান নাও থাকতে পারে বা উইন্ডোটি খালি।", + "invalid_split_timestamp": "টাইমস্ট্যাম্পটি অবৈধ বা সাইকেল উইন্ডোর বাইরে। সাইকেল উইন্ডোর মধ্যে HH:MM বা HH:MM:SS ব্যবহার করুন।", + "no_split_segments_found": "এই টাইমস্ট্যাম্পগুলো থেকে কোনো বৈধ সেগমেন্ট তৈরি করা যায়নি। নিশ্চিত করুন প্রতিটি টাইমস্ট্যাম্প সাইকেল উইন্ডোর মধ্যে আছে এবং প্রতিটি সেগমেন্ট কমপক্ষে 1 মিনিট দীর্ঘ।", + "auto_tune_suggestion": "{device_type} {device_title} ভূত চক্র সনাক্ত করা হয়েছে৷ প্রস্তাবিত min_power পরিবর্তন: {current_min}W -> {new_min}W (স্বয়ংক্রিয়ভাবে প্রয়োগ করা হয় না)।", + "auto_tune_title": "ওয়াশডেটা অটো-টিউন", + "auto_tune_fallback": "{device_type} {device_title} ভূত চক্র সনাক্ত করা হয়েছে৷ প্রস্তাবিত min_power পরিবর্তন: {current_min}W -> {new_min}W (স্বয়ংক্রিয়ভাবে প্রয়োগ করা হয় না)।" + }, + "abort": { + "no_cycles_found": "কোন চক্র পাওয়া যায়নি", + "no_split_segments_found": "নির্বাচিত সাইকেলে বিভাজ্য কোনো সেগমেন্ট পাওয়া যায়নি।", + "cycle_not_found": "নির্বাচিত সাইকেলটি লোড করা যায়নি।", + "no_power_data": "নির্বাচিত চক্রের জন্য কোনো পাওয়ার ট্রেস ডেটা উপলব্ধ নেই৷", + "no_profiles_found": "কোন প্রোফাইল পাওয়া যায়নি. প্রথমে একটি প্রোফাইল তৈরি করুন।", + "no_profiles_for_matching": "মিলের জন্য কোন প্রোফাইল উপলব্ধ নেই। প্রথমে প্রোফাইল তৈরি করুন।", + "no_unlabeled_cycles": "সমস্ত চক্র ইতিমধ্যে লেবেল করা হয়", + "no_suggestions": "কোন প্রস্তাবিত মান এখনও উপলব্ধ. কয়েকটি চক্র চালান এবং আবার চেষ্টা করুন।", + "no_predictions": "কোনো ভবিষ্যদ্বাণী নেই", + "no_custom_phases": "কোনো কাস্টম পর্যায় পাওয়া যায়নি।", + "reprocess_success": "সফলভাবে {count}টি চক্র পুনরায় প্রক্রিয়া করা হয়েছে৷", + "debug_data_cleared": "{count} চক্র থেকে ডিবাগ ডেটা সফলভাবে সাফ করা হয়েছে৷" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "সাইকেল স্টার্ট", + "cycle_finish": "সাইকেল ফিনিশ", + "cycle_live": "লাইভ অগ্রগতি" + } + }, + "common_text": { + "options": { + "unlabeled": "(লেবেলবিহীন)", + "create_new_profile": "নতুন প্রোফাইল তৈরি করুন", + "remove_label": "লেবেল সরান", + "no_reference_cycle": "(কোন রেফারেন্স চক্র নেই)", + "all_device_types": "সমস্ত ডিভাইস প্রকার", + "current_suffix": "(বর্তমান)", + "editor_select_info": "বিভক্ত করতে 1টি চক্র বা একত্রিত করতে 2+ চক্র নির্বাচন করুন৷", + "editor_select_info_split": "বিভাজন করতে 1 সাইকেল নির্বাচন করুন।", + "editor_select_info_merge": "একত্র করতে 2+ সাইকেল নির্বাচন করুন।", + "editor_select_info_delete": "মুছতে 1+ সাইকেল নির্বাচন করুন।", + "no_cycles_recorded": "কোন চক্র এখনও রেকর্ড করা হয়নি।", + "profile_summary_line": "- **{name}**: {count} চক্র, {avg}মি গড়", + "no_profiles_created": "এখনো কোন প্রোফাইল তৈরি করা হয়নি.", + "phase_preview": "ফেজ প্রিভিউ", + "phase_preview_no_curve": "গড় প্রোফাইল বক্ররেখা এখনও উপলব্ধ নয়. এই প্রোফাইলের জন্য আরো চক্র চালান/লেবেল করুন।", + "average_power_curve": "গড় শক্তি বক্ররেখা", + "unit_min": "মিনিট", + "profile_option_fmt": "{name} ({count} চক্র, ~{duration}মি গড়)", + "profile_option_short_fmt": "{name} ({count} চক্র, ~{duration}মি)", + "deleted_cycles_from_profile": "{profile} থেকে {count} চক্র মুছে ফেলা হয়েছে৷", + "not_enough_data_graph": "গ্রাফ তৈরি করার জন্য পর্যাপ্ত ডেটা নেই।", + "no_profiles_found": "কোন প্রোফাইল পাওয়া যায়নি.", + "cycle_info_fmt": "চক্র: {start}, {duration}মি, বর্তমান: {label}", + "top_candidates_header": "### শীর্ষ প্রার্থী", + "tbl_profile": "প্রোফাইল", + "tbl_confidence": "আত্মবিশ্বাস", + "tbl_correlation": "পারস্পরিক সম্পর্ক", + "tbl_duration_match": "সময়কাল ম্যাচ", + "detected_profile": "সনাক্ত করা প্রোফাইল", + "estimated_duration": "আনুমানিক সময়কাল", + "actual_duration": "প্রকৃত সময়কাল", + "choose_action": "__নীচে একটি কর্ম চয়ন করুন:__", + "tbl_count": "গণনা", + "tbl_avg": "গড়", + "tbl_min": "মিন", + "tbl_max": "সর্বোচ্চ", + "tbl_energy_avg": "শক্তি (গড়)", + "tbl_energy_total": "শক্তি (মোট)", + "tbl_consistency": "গঠিত।", + "tbl_last_run": "শেষ রান", + "graph_legend_title": "গ্রাফ কিংবদন্তি", + "graph_legend_body": "নীল ব্যান্ড ন্যূনতম এবং সর্বাধিক পাওয়ার ড্র পরিসীমা পর্যবেক্ষণ করে। লাইনটি গড় শক্তি বক্ররেখা দেখায়।", + "recording_preview": "রেকর্ডিং পূর্বরূপ", + "trim_start": "ট্রিম স্টার্ট", + "trim_end": "ট্রিম শেষ", + "split_preview_title": "বিভক্ত পূর্বরূপ", + "split_preview_found_fmt": "{count}টি সেগমেন্ট পাওয়া গেছে।", + "split_preview_confirm_fmt": "এই চক্রটিকে {count}টি পৃথক চক্রে বিভক্ত করতে নিশ্চিত করুন ক্লিক করুন৷", + "merge_preview_title": "পূর্বরূপ মার্জ করুন", + "merge_preview_joining_fmt": "{count} চক্রে যোগদান করা হচ্ছে। শূন্যস্থান 0W রিডিং দিয়ে পূরণ করা হবে।", + "editor_delete_preview_title": "মুছে ফেলার পূর্বরূপ", + "editor_delete_preview_intro": "নির্বাচিত সাইকেলগুলো স্থায়ীভাবে মুছে ফেলা হবে:", + "editor_delete_preview_confirm": "এই সাইকেল রেকর্ডগুলো স্থায়ীভাবে মুছতে Confirm ক্লিক করুন।", + "no_power_preview": "*প্রিভিউয়ের জন্য কোনো পাওয়ার ডেটা উপলব্ধ নেই।*", + "profile_comparison": "প্রোফাইল তুলনা", + "actual_cycle_label": "এই চক্র (প্রকৃত)", + "storage_usage_header": "স্টোরেজ ব্যবহার", + "storage_file_size": "ফাইলের আকার", + "storage_cycles": "সাইকেল", + "storage_profiles": "প্রোফাইল", + "storage_debug_traces": "ডিবাগ ট্রেস", + "overview_suffix": "ওভারভিউ", + "phase_preview_for_profile": "প্রোফাইলের জন্য ফেজ প্রিভিউ", + "current_ranges_header": "বর্তমান ব্যাপ্তি", + "assign_phases_help": "ব্যাপ্তি যোগ করতে, সম্পাদনা করতে, মুছতে বা সংরক্ষণ করতে নিচের ক্রিয়াগুলি ব্যবহার করুন৷", + "profile_summary_header": "প্রোফাইল সারাংশ", + "recent_cycles_header": "সাম্প্রতিক চক্র", + "trim_cycle_preview_title": "সাইকেল ট্রিম প্রিভিউ", + "trim_cycle_preview_no_data": "এই চক্রের জন্য কোন পাওয়ার ডেটা উপলব্ধ নেই৷", + "trim_cycle_preview_kept_suffix": "রাখা", + "table_program": "প্রোগ্রাম", + "table_when": "কখন", + "table_length": "সময়কাল", + "table_match": "মিল", + "table_profile": "প্রোফাইল", + "table_cycles": "সাইকেল", + "table_avg_length": "গড় সময়কাল", + "table_last_run": "শেষ রান", + "table_avg_energy": "গড় শক্তি", + "table_detected_program": "শনাক্তকৃত প্রোগ্রাম", + "table_confidence": "আত্মবিশ্বাস", + "table_reported": "রিপোর্ট করা হয়েছে", + "phase_builtin_suffix": "(অন্তর্নির্মিত)", + "phase_none_available": "কোন পর্যায় উপলব্ধ.", + "settings_deprecation_warning": "⚠️ **অপ্রচলিত ডিভাইসের ধরন:** {device_type} ভবিষ্যতের রিলিজে অপসারণের জন্য নির্ধারিত হয়েছে। WashData এর মিলে যাওয়া পাইপলাইন এই অ্যাপ্লায়েন্স ক্লাসের জন্য নির্ভরযোগ্য ফলাফল দেয় না। আপনার ইন্টিগ্রেশন অবচয় সময়ের মধ্যে কাজ করে; এই সতর্কতাটি নিঃশব্দ করতে, **ডিভাইসের ধরন** নিচের যেকোন একটি সমর্থিত প্রকারে (ওয়াশিং মেশিন, ড্রায়ার, ওয়াশার-ড্রায়ার কম্বো, ডিশওয়াশার, এয়ার ফ্রায়ার, ব্রেড মেকার, বা পাম্প) বা **অন্যান্য (অ্যাডভান্সড)** এ পরিবর্তন করুন যদি আপনার যন্ত্র সমর্থিত কোনো প্রকারের সাথে মেলে না। **অন্যান্য (অ্যাডভান্সড)** ইচ্ছাকৃতভাবে জেনেরিক ডিফল্ট পাঠায় যা কোনো নির্দিষ্ট যন্ত্রের জন্য টিউন করা হয় না, তাই আপনাকে থ্রেশহোল্ড, টাইমআউট এবং ম্যাচিং প্যারামিটারগুলি নিজেই কনফিগার করতে হবে; আপনি যখন স্যুইচ করেন তখন আপনার সমস্ত বিদ্যমান সেটিংস সংরক্ষিত থাকে।", + "phase_other_device_types": "অন্যান্য ডিভাইস প্রকার:" + } + }, + "interactive_editor_action": { + "options": { + "split": "একটি চক্র বিভক্ত করুন (শূন্যস্থান খুঁজুন)", + "merge": "সাইকেল একত্র করুন (খন্ডে যোগ দিন)", + "delete": "চক্র(গুলি) মুছুন" + } + }, + "split_mode": { + "options": { + "auto": "নিষ্ক্রিয় ফাঁক স্বয়ংক্রিয়ভাবে শনাক্ত করুন", + "manual": "ম্যানুয়াল টাইমস্ট্যাম্প" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "রক্ষণাবেক্ষণ: পুনঃপ্রক্রিয়া এবং ডেটা অপ্টিমাইজ করুন", + "clear_debug_data": "ডিবাগ ডেটা সাফ করুন (স্থান খালি করুন)", + "wipe_history": "এই ডিভাইসের জন্য সমস্ত ডেটা মুছুন (অপরিবর্তনযোগ্য)", + "export_import": "সেটিংস সহ JSON রপ্তানি/আমদানি করুন (কপি/পেস্ট)" + } + }, + "export_import_mode": { + "options": { + "export": "সমস্ত ডেটা রপ্তানি করুন", + "import": "ডেটা আমদানি/মার্জ করুন" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "অটো-লেবেল পুরানো চক্র", + "select_cycle_to_label": "লেবেল নির্দিষ্ট চক্র", + "select_cycle_to_delete": "সাইকেল মুছুন", + "interactive_editor": "ইন্টারেক্টিভ এডিটর মার্জ/স্প্লিট করুন", + "trim_cycle": "ট্রিম সাইকেল ডেটা" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "নতুন প্রোফাইল তৈরি করুন", + "edit_profile": "প্রোফাইল সম্পাদনা/পুনঃনামকরণ করুন", + "delete_profile": "প্রোফাইল মুছুন", + "profile_stats": "প্রোফাইল পরিসংখ্যান", + "cleanup_profile": "ইতিহাস পরিষ্কার করুন - গ্রাফ এবং মুছুন", + "assign_phases": "ফেজ রেঞ্জ বরাদ্দ" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "নতুন ফেজ তৈরি করুন", + "edit_custom_phase": "ফেজ সম্পাদনা করুন", + "delete_custom_phase": "ফেজ মুছুন" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "ফেজ রেঞ্জ যোগ করুন", + "edit_range": "ফেজ পরিসীমা সম্পাদনা করুন", + "delete_range": "ফেজ রেঞ্জ মুছুন", + "clear_ranges": "সমস্ত রেঞ্জ সাফ করুন", + "auto_detect_ranges": "পর্যায়গুলি স্বয়ংক্রিয়ভাবে সনাক্ত করুন৷", + "save_ranges": "সংরক্ষণ করুন এবং ফেরত দিন" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "রিফ্রেশ স্থিতি", + "stop_recording": "রেকর্ডিং বন্ধ করুন (সংরক্ষণ এবং প্রক্রিয়া)", + "start_recording": "নতুন রেকর্ডিং শুরু করুন", + "process_recording": "শেষ রেকর্ডিং প্রক্রিয়া করুন (ছাঁটা ও সংরক্ষণ করুন)", + "discard_recording": "শেষ রেকর্ডিং বাতিল করুন" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "নতুন প্রোফাইল তৈরি করুন", + "existing_profile": "বিদ্যমান প্রোফাইলে যোগ করুন", + "discard": "রেকর্ডিং বাতিল করুন" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "নিশ্চিত করুন - সঠিক সনাক্তকরণ", + "correct": "সঠিক - প্রোগ্রাম/সময়কাল বেছে নিন", + "ignore": "উপেক্ষা করুন - মিথ্যা ইতিবাচক/কোলাহলপূর্ণ চক্র", + "delete": "মুছুন - ইতিহাস থেকে সরান" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "নাম এবং পর্যায়গুলি প্রয়োগ করুন", + "cancel": "পরিবর্তন ছাড়াই ফিরে যান" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "স্টার্ট পয়েন্ট সেট করুন", + "set_end": "শেষ বিন্দু সেট করুন", + "reset": "সম্পূর্ণ মেয়াদে রিসেট করুন", + "apply": "ট্রিম প্রয়োগ করুন", + "cancel": "বাতিল করুন" + } + }, + "device_type": { + "options": { + "washing_machine": "ওয়াশিং মেশিন", + "dryer": "ড্রায়ার", + "washer_dryer": "ওয়াশার-ড্রায়ার কম্বো", + "dishwasher": "ডিশওয়াশার", + "coffee_machine": "কফি মেশিন (অপ্রচলিত)", + "ev": "বৈদ্যুতিক যান (অপ্রচলিত)", + "air_fryer": "এয়ার ফ্রায়ার", + "heat_pump": "তাপ পাম্প (অপ্রচলিত)", + "bread_maker": "রুটি মেকার", + "pump": "পাম্প / সাম্প পাম্প", + "oven": "ওভেন (অপ্রচলিত)", + "other": "অন্যান্য (উন্নত)" + } + } + }, + "services": { + "label_cycle": { + "name": "লেবেল চক্র", + "description": "একটি অতীত চক্রে একটি বিদ্যমান প্রোফাইল বরাদ্দ করুন, বা লেবেলটি সরান৷", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশিং মেশিন ডিভাইস লেবেল." + }, + "cycle_id": { + "name": "সাইকেল আইডি", + "description": "লেবেল করার জন্য চক্রের আইডি।" + }, + "profile_name": { + "name": "প্রোফাইল নাম", + "description": "একটি বিদ্যমান প্রোফাইলের নাম (প্রোফাইল পরিচালনা মেনুতে প্রোফাইল তৈরি করুন)। লেবেল সরাতে ফাঁকা ছেড়ে দিন।" + } + } + }, + "create_profile": { + "name": "প্রোফাইল তৈরি করুন", + "description": "একটি নতুন প্রোফাইল তৈরি করুন (স্বতন্ত্র বা একটি রেফারেন্স চক্রের উপর ভিত্তি করে)।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশিং মেশিন ডিভাইস।" + }, + "profile_name": { + "name": "প্রোফাইল নাম", + "description": "নতুন প্রোফাইলের নাম (যেমন 'হেভি ডিউটি', 'ডেলিকেটস')।" + }, + "reference_cycle_id": { + "name": "রেফারেন্স সাইকেল আইডি", + "description": "এই প্রোফাইলের উপর ভিত্তি করে ঐচ্ছিক সাইকেল আইডি।" + } + } + }, + "delete_profile": { + "name": "প্রোফাইল মুছুন", + "description": "একটি প্রোফাইল মুছুন এবং ঐচ্ছিকভাবে এটি ব্যবহার করে চক্রগুলি আনলেবেল করুন৷", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশিং মেশিন ডিভাইস।" + }, + "profile_name": { + "name": "প্রোফাইল নাম", + "description": "মুছে ফেলার জন্য প্রোফাইল." + }, + "unlabel_cycles": { + "name": "আনলেবেল সাইকেল", + "description": "এই প্রোফাইল ব্যবহার করে চক্র থেকে প্রোফাইল লেবেল সরান." + } + } + }, + "auto_label_cycles": { + "name": "অটো-লেবেল পুরানো চক্র", + "description": "প্রোফাইল ম্যাচিং ব্যবহার করে লেবেলবিহীন চক্রকে পূর্ববর্তীভাবে লেবেল করুন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশিং মেশিন ডিভাইস।" + }, + "confidence_threshold": { + "name": "আত্মবিশ্বাস থ্রেশহোল্ড", + "description": "লেবেল প্রয়োগ করার জন্য ন্যূনতম ম্যাচ কনফিডেন্স (0.50-0.95)।" + } + } + }, + "export_config": { + "name": "রপ্তানি কনফিগার", + "description": "এই ডিভাইসের প্রোফাইল, চক্র এবং সেটিংস একটি JSON ফাইলে (প্রতি ডিভাইসে) রপ্তানি করুন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "রপ্তানি করার জন্য ওয়াশিং মেশিন ডিভাইস।" + }, + "path": { + "name": "পথ", + "description": "লেখার জন্য ঐচ্ছিক পরম ফাইল পাথ (ডিফল্ট /config/ha_washdata_export_{entry}.json)।" + } + } + }, + "import_config": { + "name": "ইমপোর্ট কনফিগারেশন", + "description": "একটি JSON এক্সপোর্ট ফাইল থেকে এই ডিভাইসের জন্য প্রোফাইল, চক্র এবং সেটিংস আমদানি করুন (সেটিংস ওভাররাইট করা হতে পারে)।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "যে ওয়াশিং মেশিন ডিভাইসটি আমদানি করতে হবে।" + }, + "path": { + "name": "পথ", + "description": "আমদানি করার জন্য এক্সপোর্ট JSON ফাইলের সম্পূর্ণ পথ।" + } + } + }, + "submit_cycle_feedback": { + "name": "সাইকেল ফিডব্যাক জমা দিন", + "description": "একটি সম্পূর্ণ চক্রের পরে একটি স্বয়ংক্রিয়-সনাক্ত প্রোগ্রাম নিশ্চিত করুন বা সংশোধন করুন। হয় `entry_id` (উন্নত) অথবা `device_id` (প্রস্তাবিত) প্রদান করুন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশডেটা ডিভাইস (entry_id এর পরিবর্তে প্রস্তাবিত)।" + }, + "entry_id": { + "name": "এন্ট্রি আইডি", + "description": "ডিভাইসের জন্য কনফিগার এন্ট্রি আইডি (device_id-এর বিকল্প)।" + }, + "cycle_id": { + "name": "সাইকেল আইডি", + "description": "প্রতিক্রিয়া বিজ্ঞপ্তি / লগ দেখানো চক্র আইডি." + }, + "user_confirmed": { + "name": "সনাক্ত করা প্রোগ্রাম নিশ্চিত করুন", + "description": "সনাক্ত করা প্রোগ্রাম সঠিক হলে সত্য সেট করুন।" + }, + "corrected_profile": { + "name": "সঠিক প্রোফাইল", + "description": "নিশ্চিত না হলে, সঠিক প্রোফাইল/প্রোগ্রামের নাম দিন।" + }, + "corrected_duration": { + "name": "সঠিক সময়কাল (সেকেন্ড)", + "description": "সেকেন্ডের মধ্যে ঐচ্ছিক সংশোধন সময়কাল." + }, + "notes": { + "name": "নোট", + "description": "এই চক্র সম্পর্কে ঐচ্ছিক নোট." + } + } + }, + "record_start": { + "name": "রেকর্ড সাইকেল শুরু", + "description": "ম্যানুয়ালি একটি পরিষ্কার চক্র রেকর্ড করা শুরু করুন (সমস্ত মিলে যাওয়া যুক্তিকে বাইপাস করে)।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "রেকর্ড করার জন্য ওয়াশিং মেশিন ডিভাইস।" + } + } + }, + "record_stop": { + "name": "রেকর্ড সাইকেল স্টপ", + "description": "ম্যানুয়াল রেকর্ডিং বন্ধ করুন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "রেকর্ডিং বন্ধ করার জন্য ওয়াশিং মেশিন ডিভাইস।" + } + } + }, + "trim_cycle": { + "name": "ট্রিম সাইকেল", + "description": "একটি নির্দিষ্ট সময় উইন্ডোতে অতীত চক্রের পাওয়ার ডেটা ট্রিম করুন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশডেটা ডিভাইস।" + }, + "cycle_id": { + "name": "সাইকেল আইডি", + "description": "সাইকেলের আইডি ট্রিম করতে হবে।" + }, + "trim_start_s": { + "name": "ট্রিম স্টার্ট (সেকেন্ড)", + "description": "এই অফসেট থেকে সেকেন্ডের মধ্যে ডেটা রাখুন। ডিফল্ট 0।" + }, + "trim_end_s": { + "name": "ট্রিম এন্ড (সেকেন্ড)", + "description": "সেকেন্ডের মধ্যে এই অফসেট পর্যন্ত ডেটা রাখুন। ডিফল্ট = সম্পূর্ণ সময়কাল।" + } + } + }, + "pause_cycle": { + "name": "চক্র বিরতি", + "description": "একটি ওয়াশডেটা ডিভাইসের জন্য সক্রিয় চক্র বিরাম দিন।", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "পজ করার জন্য ওয়াশডেটা ডিভাইস।" + } + } + }, + "resume_cycle": { + "name": "চক্র পুনরায় শুরু করুন", + "description": "একটি WashData ডিভাইসের জন্য একটি বিরতি করা চক্র পুনরায় শুরু করুন৷", + "fields": { + "device_id": { + "name": "ডিভাইস", + "description": "ওয়াশডেটা ডিভাইসটি আবার চালু হবে।" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "চলছে" + }, + "match_ambiguity": { + "name": "অস্পষ্টতা ম্যাচ" + } + }, + "sensor": { + "washer_state": { + "name": "রাজ্য", + "state": { + "off": "বন্ধ", + "idle": "নিষ্ক্রিয়", + "starting": "শুরু হচ্ছে", + "running": "চলছে", + "paused": "বিরতি দেওয়া হয়েছে", + "user_paused": "ব্যবহারকারী দ্বারা বিরতি দেওয়া হয়েছে", + "ending": "শেষ", + "finished": "সমাপ্ত", + "anti_wrinkle": "অ্যান্টি-রিঙ্কেল", + "delay_wait": "শুরু করার অপেক্ষায়", + "interrupted": "বাধাপ্রাপ্ত", + "force_stopped": "ফোর্স স্টপড", + "rinse": "ধুয়ে ফেলুন", + "unknown": "অজানা", + "clean": "পরিষ্কার" + } + }, + "washer_program": { + "name": "প্রোগ্রাম" + }, + "time_remaining": { + "name": "বাকি সময়" + }, + "total_duration": { + "name": "মোট সময়কাল" + }, + "cycle_progress": { + "name": "অগ্রগতি" + }, + "current_power": { + "name": "বর্তমান শক্তি" + }, + "elapsed_time": { + "name": "অতিবাহিত সময়" + }, + "debug_info": { + "name": "ডিবাগ তথ্য" + }, + "match_confidence": { + "name": "ম্যাচ কনফিডেন্স" + }, + "top_candidates": { + "name": "শীর্ষ প্রার্থী", + "state": { + "none": "কোনোটিই নয়" + } + }, + "current_phase": { + "name": "বর্তমান পর্যায়" + }, + "wash_phase": { + "name": "পর্যায়" + }, + "profile_cycle_count": { + "name": "প্রোফাইল {profile_name} গণনা", + "unit_of_measurement": "চক্র" + }, + "suggestions": { + "name": "প্রস্তাবিত সেটিংস" + }, + "pump_runs_today": { + "name": "পাম্প রান (শেষ ২৪ ঘণ্টা)" + }, + "cycle_count": { + "name": "সাইকেল কাউন্ট" + } + }, + "select": { + "program_select": { + "name": "সাইকেল প্রোগ্রাম", + "state": { + "auto_detect": "অটো-ডিটেক্ট" + } + } + }, + "button": { + "force_end_cycle": { + "name": "ফোর্স এন্ড সাইকেল" + }, + "pause_cycle": { + "name": "চক্র বিরতি" + }, + "resume_cycle": { + "name": "চক্র পুনরায় শুরু করুন" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "একটি বৈধ device_id প্রয়োজন৷" + }, + "cycle_id_required": { + "message": "একটি বৈধ cycle_id প্রয়োজন।" + }, + "profile_name_required": { + "message": "একটি বৈধ profile_name প্রয়োজন৷" + }, + "device_not_found": { + "message": "ডিভাইস পাওয়া যায়নি." + }, + "no_config_entry": { + "message": "ডিভাইসের জন্য কোনো কনফিগারেশন এন্ট্রি পাওয়া যায়নি।" + }, + "integration_not_loaded": { + "message": "এই ডিভাইসের জন্য ইন্টিগ্রেশন লোড করা হয়নি।" + }, + "cycle_not_found_or_no_power": { + "message": "সাইকেল পাওয়া যায়নি বা পাওয়ার ডেটা নেই।" + }, + "trim_failed_empty_window": { + "message": "ট্রিম ব্যর্থ হয়েছে - চক্র পাওয়া যায়নি, কোনো পাওয়ার ডেটা নেই, বা ফলস্বরূপ উইন্ডোটি খালি।" + }, + "trim_invalid_range": { + "message": "trim_end_s অবশ্যই trim_start_s থেকে বড় হতে হবে।" + } + } +} diff --git a/custom_components/ha_washdata/translations/bs.json b/custom_components/ha_washdata/translations/bs.json new file mode 100644 index 0000000..9f4c5e0 --- /dev/null +++ b/custom_components/ha_washdata/translations/bs.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Setup", + "description": "Konfigurišite svoju mašinu za pranje veša ili drugi uređaj.\n\nPotreban je senzor snage.\n\n**Slijedeće ćete biti upitani da li želite da kreirate svoj prvi profil.**", + "data": { + "name": "Naziv uređaja", + "device_type": "Tip uređaja", + "power_sensor": "Senzor snage", + "min_power": "Minimalni prag snage (W)" + }, + "data_description": { + "name": "Prijateljski naziv za ovaj uređaj (npr. 'Mašina za pranje veša', 'Mašina za pranje sudova').", + "device_type": "Koja je ovo vrsta aparata? Pomaže u otkrivanju i označavanju po mjeri.", + "power_sensor": "Senzorski entitet koji izvještava o potrošnji energije u realnom vremenu (u vatima) iz vašeg pametnog priključka.", + "min_power": "Očitavanja snage iznad ovog praga (u vatima) pokazuju da uređaj radi. Počnite sa 2W za većinu uređaja." + } + }, + "first_profile": { + "title": "Kreirajte prvi profil", + "description": "**Ciklus** je kompletan rad vašeg uređaja (npr. pun veš). **Profil** grupiše ove cikluse prema vrsti (npr. 'Pamuk', 'Brzo pranje'). Kreiranje profila sada pomaže u procjeni trajanja integracije odmah.\n\nMožete ručno odabrati ovaj profil u kontrolama uređaja ako nije automatski otkriven.\n\n**Ostavite 'Naziv profila' prazno da preskočite ovaj korak.**", + "data": { + "profile_name": "Naziv profila (opcionalno)", + "manual_duration": "Procijenjeno trajanje (minuta)" + } + } + }, + "error": { + "cannot_connect": "Povezivanje nije uspjelo", + "invalid_auth": "Nevažeća autentifikacija", + "unknown": "Neočekivana greška" + }, + "abort": { + "already_configured": "Uređaj je već konfigurisan" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Pregledajte povratne informacije o učenju", + "settings": "Postavke", + "notifications": "Obavještenja", + "manage_cycles": "Upravljanje ciklusima", + "manage_profiles": "Upravljanje profilima", + "manage_phase_catalog": "Upravljanje katalogom faza", + "record_cycle": "Ciklus snimanja (ručno)", + "diagnostics": "Dijagnostika i održavanje" + } + }, + "apply_suggestions": { + "title": "Kopiraj predložene vrijednosti", + "description": "Ovo će kopirati trenutne predložene vrijednosti u vaše sačuvane postavke. Ovo je jednokratna radnja i ne omogućava automatsko prepisivanje.\n\nPredložene vrijednosti za kopiranje:\n{suggested}", + "data": { + "confirm": "Potvrdite radnju kopiranja" + } + }, + "apply_suggestions_confirm": { + "title": "Pregledajte predložene izmjene", + "description": "WashData je pronašao **{pending_count}** predložene vrijednosti za primjenu.\n\nPromjene:\n{changes}\n\n✅ Označite polje ispod i kliknite na **Pošalji** da odmah primijenite i sačuvate ove promjene.\n❌ Ostavite neoznačeno i kliknite na **Pošalji** da otkažete i vratite se.", + "data": { + "confirm_apply_suggestions": "Primijenite i sačuvajte ove promjene" + } + }, + "settings": { + "title": "Postavke", + "description": "{deprecation_warning}Podesite pragove detekcije, učenje i napredno ponašanje. Zadane postavke dobro rade za većinu postavki.\n\nTrenutno dostupne preporučene postavke: {suggestions_count}.\nAko je ovo iznad 0, otvorite Napredne postavke i koristite 'Primijeni predložene vrijednosti' za pregled preporuka.", + "data": { + "apply_suggestions": "Primijenite predložene vrijednosti", + "device_type": "Tip uređaja", + "power_sensor": "Entitet senzora snage", + "min_power": "Minimalna snaga (W)", + "off_delay": "Kašnjenje završetka ciklusa (s)", + "notify_actions": "Radnje obavještenja", + "notify_people": "Odgodite isporuku dok ovi ljudi nisu kod kuće", + "notify_only_when_home": "Odgodi obavještenja dok odabrana osoba nije kod kuće", + "notify_fire_events": "Pokreni događaje automatizacije", + "notify_start_services": "Početak ciklusa - Ciljevi obavijesti", + "notify_finish_services": "Završetak ciklusa - Ciljevi obavijesti", + "notify_live_services": "Napredak uživo - Ciljevi obavještenja", + "notify_before_end_minutes": "Obavijest prije završetka (minuta prije kraja)", + "notify_title": "Naslov obavijesti", + "notify_icon": "Ikona obaveštenja", + "notify_start_message": "Pokreni format poruke", + "notify_finish_message": "Završi format poruke", + "notify_pre_complete_message": "Format poruke prije završetka", + "show_advanced": "Uredite napredne postavke (sljedeći korak)" + }, + "data_description": { + "apply_suggestions": "Označite ovo polje za kopiranje vrijednosti koje je predložio algoritam učenja u obrazac. Pregledajte prije spremanja.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Odaberite vrstu uređaja (Mašina za pranje rublja, Mašina za sušenje, Mašina za pranje sudova, Mašina za kafu, EV). Ova oznaka se pohranjuje sa svakim ciklusom radi bolje organizacije.", + "power_sensor": "Senzorski entitet koji izvještava o snazi ​​u realnom vremenu (u vatima). Ovo možete promijeniti bilo kada bez gubitka historijskih podataka – korisno ako zamijenite pametni utikač.", + "min_power": "Očitavanja snage iznad ovog praga (u vatima) pokazuju da uređaj radi. Počnite sa 2W za većinu uređaja.", + "off_delay": "Sekunde izglađena snaga mora ostati ispod praga zaustavljanja da označi završetak. Podrazumevano: 120s.", + "notify_actions": "Opcioni niz akcija Home Assistant-a za pokretanje za obavještenja (skripte, obavijesti, TTS, itd.). Ako je postavljeno, radnje se pokreću prije servisa zamjenskog obavijesti.", + "notify_people": "Entiteti osoba koji se koriste za praćenje prisutnosti. Kada je omogućeno, dostava obavještenja se odgađa sve dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odgodite obavještenja dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktivirajte događaje Home Assistant za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_ended) za pokretanje automatizacije.", + "notify_start_services": "Jedan ili više servisa za obavještavanje da upozore kada ciklus počne. Ostavite prazno da preskočite obavještenja o početku.", + "notify_finish_services": "Jedna ili više službi za obavještavanje da upozore kada se ciklus završi ili se bliži završetku. Ostavite prazno da preskočite obavještenja o završetku.", + "notify_live_services": "Jedna ili više usluga obavještavanja za primanje ažuriranja napretka uživo dok ciklus traje (samo mobilna prateća aplikacija). Ostavite prazno da preskočite ažuriranja uživo.", + "notify_before_end_minutes": "Nekoliko minuta prije procijenjenog kraja ciklusa za pokretanje obavještenja. Postavite na 0 da biste onemogućili. Podrazumevano: 0.", + "notify_title": "Prilagođeni naslov za obavještenja. Podržava {device} čuvar mjesta.", + "notify_icon": "Ikona za korištenje za obavještenja (npr. mdi:washing-machine). Ostavite prazno za slanje bez ikone.", + "notify_start_message": "Poruka se šalje kada ciklus započne. Podržava {device} čuvar mjesta.", + "notify_finish_message": "Poruka se šalje kada se ciklus završi. Podržava {device}, {duration}, {program}, {energy_kwh}, {cost} čuvare mjesta.", + "notify_pre_complete_message": "Tekst ponavljajućih ažuriranja napretka uživo dok ciklus traje. Podržava {device}, {minutes}, {program} čuvare mjesta." + } + }, + "notifications": { + "title": "Obavještenja", + "description": "Konfigurirajte kanale obavještenja, šablone i opcionalna ažuriranja napretka na mobilnom uređaju.", + "data": { + "notify_actions": "Radnje obavještenja", + "notify_people": "Odgodite isporuku dok ovi ljudi nisu kod kuće", + "notify_only_when_home": "Odgodi obavještenja dok odabrana osoba nije kod kuće", + "notify_fire_events": "Pokreni događaje automatizacije", + "notify_start_services": "Početak ciklusa - Ciljevi obavijesti", + "notify_finish_services": "Završetak ciklusa - Ciljevi obavijesti", + "notify_live_services": "Napredak uživo - Ciljevi obavještenja", + "notify_before_end_minutes": "Obavijest prije završetka (minuta prije kraja)", + "notify_live_interval_seconds": "Interval ažuriranja uživo (sekunde)", + "notify_live_overrun_percent": "Dodatak za prekoračenje ažuriranja uživo (%)", + "notify_live_chronometer": "Tajmer za odbrojavanje hronometra", + "notify_title": "Naslov obavijesti", + "notify_icon": "Ikona obaveštenja", + "notify_start_message": "Pokreni format poruke", + "notify_finish_message": "Završi format poruke", + "notify_pre_complete_message": "Format poruke prije završetka", + "energy_price_entity": "Cijena energije - entitet (opcionalno)", + "energy_price_static": "Cijena energije - statička vrijednost (opcionalno)", + "go_back": "Vrati se bez spremanja", + "notify_reminder_message": "Format poruke podsjetnika", + "notify_timeout_seconds": "Automatsko odbacivanje nakon (sekunde)", + "notify_channel": "Kanal obavještenja (status/uživo/podsjetnik)", + "notify_finish_channel": "Završen kanal obavijesti" + }, + "data_description": { + "go_back": "Označite ovo i kliknite Pošalji da odbacite sve gore navedene promjene i vratite se u glavni meni.", + "notify_actions": "Opcioni niz akcija Home Assistant-a za pokretanje za obavještenja (skripte, obavijesti, TTS, itd.). Ako je postavljeno, radnje se pokreću prije servisa zamjenskog obavijesti.", + "notify_people": "Entiteti osoba koji se koriste za praćenje prisutnosti. Kada je omogućeno, dostava obavještenja se odgađa sve dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odgodite obavještenja dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktivirajte događaje Home Assistant za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_ended) za pokretanje automatizacije.", + "notify_start_services": "Jedan ili više servisa za obavještavanje za upozorenje kada ciklus počne (npr. notify.mobile_app_my_phone). Ostavite prazno da preskočite obavještenja o početku.", + "notify_finish_services": "Jedna ili više službi za obavještavanje da upozore kada se ciklus završi ili se bliži završetku. Ostavite prazno da preskočite obavještenja o završetku.", + "notify_live_services": "Jedna ili više usluga obavještavanja za primanje ažuriranja napretka uživo dok ciklus traje (samo mobilna prateća aplikacija). Ostavite prazno da preskočite ažuriranja uživo.", + "notify_before_end_minutes": "Nekoliko minuta prije procijenjenog kraja ciklusa za pokretanje obavještenja. Postavite na 0 da biste onemogućili. Podrazumevano: 0.", + "notify_live_interval_seconds": "Koliko često se ažuriranja napretka šalju tokom rada. Niže vrijednosti bolje reagiraju, ali troše više obavijesti. Primjenjuje se minimalno 30 sekundi - vrijednosti ispod 30 s se automatski zaokružuju na 30 s.", + "notify_live_overrun_percent": "Sigurnosna margina iznad procijenjenog broja ažuriranja za duge cikluse/prekoračivanje (na primjer, 20% dozvoljava 1,2x procijenjenih ažuriranja).", + "notify_live_chronometer": "Kada je omogućeno, svako ažuriranje uživo uključuje odbrojavanje kronometra do procijenjenog vremena završetka. Tajmer za obavještenja automatski otkucava na uređaju između ažuriranja (samo prateća aplikacija za Android).", + "notify_title": "Prilagođeni naslov za obavještenja. Podržava {device} čuvar mjesta.", + "notify_icon": "Ikona za korištenje za obavještenja (npr. mdi:washing-machine). Ostavite prazno za slanje bez ikone.", + "notify_start_message": "Poruka se šalje kada ciklus započne. Podržava {device} čuvar mjesta.", + "notify_finish_message": "Poruka se šalje kada se ciklus završi. Podržava {device}, {duration}, {program}, {energy_kwh}, {cost} čuvare mjesta.", + "energy_price_entity": "Odaberite numerički entitet koji drži trenutnu cijenu električne energije (npr. senzor.electricity_price, input_number.energy_rate). Kada je postavljeno, omogućava {cost} čuvar mjesta. Ima prednost u odnosu na statičku vrijednost ako su obje konfigurirane.", + "energy_price_static": "Fiksna cijena električne energije po kWh. Kada je postavljeno, omogućava {cost} čuvar mjesta. Zanemaruje se ako je entitet konfiguriran gore. Dodajte simbol valute u šablon poruke, npr. {cost} €.", + "notify_reminder_message": "Tekst jednokratnog podsjetnika poslao je konfigurirani broj minuta prije procijenjenog kraja. Za razliku od ponavljajućih ažuriranja uživo iznad. Podržava {device}, {minutes}, {program} čuvare mjesta.", + "notify_timeout_seconds": "Automatski odbaciti obavještenja nakon ovoliko sekundi (proslijeđeno kao \"vremensko ograničenje\" prateće aplikacije). Postavite na 0 da ih zadržite dok se ne odbace. Podrazumevano: 0.", + "notify_channel": "Android kanal za obavještenje prateće aplikacije za status, napredak uživo i obavještenja o podsjetnicima. Zvuk i važnost kanala se konfigurišu u pratećoj aplikaciji kada se prvi put koristi naziv kanala - WashData samo postavlja naziv kanala. Ostavite prazno za zadanu aplikaciju.", + "notify_finish_channel": "Odvojite Android kanal za završeno upozorenje (koji se također koristi za podsjetnik i zagovaranje na čekanju) tako da može imati svoj zvuk. Ostavite prazno da ponovo koristite kanal iznad.", + "notify_pre_complete_message": "Tekst ponavljajućih ažuriranja napretka uživo dok ciklus traje. Podržava {device}, {minutes}, {program} čuvare mjesta." + } + }, + "advanced_settings": { + "title": "Napredne postavke", + "description": "Podesite pragove detekcije, učenje i napredno ponašanje. Zadane postavke dobro rade za većinu konfiguracija.", + "sections": { + "suggestions_section": { + "name": "Predložene postavke", + "description": "Dostupno je {suggestions_count} naučenih prijedloga. Označite 'Primijeni predložene vrijednosti' da prije spremanja pregledate predložene promjene.", + "data": { + "apply_suggestions": "Primijenite predložene vrijednosti" + }, + "data_description": { + "apply_suggestions": "Označite ovo polje za otvaranje koraka pregleda za predložene vrijednosti iz algoritma učenja. Ništa se ne pohranjuje automatski.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekcija i pragovi snage", + "description": "Određuje kada se uređaj smatra UKLJUČENIM ili ISKLJUČENIM, energetske pragove i vremensko određivanje prijelaza stanja.", + "data": { + "start_duration_threshold": "Započni trajanje odbijanja (s)", + "start_energy_threshold": "Ulazni prag energije (Wh)", + "completion_min_seconds": "Završetak Minimalno vrijeme rada (sekunde)", + "start_threshold_w": "Početni prag (W)", + "stop_threshold_w": "Prag zaustavljanja (W)", + "end_energy_threshold": "Izlazni prag energije (Wh)", + "running_dead_zone": "Mrtva zona rada (sekunde)", + "end_repeat_count": "Broj ponavljanja kraja", + "min_off_gap": "Minimalni razmak između ciklusa (s)", + "sampling_interval": "Interval uzorkovanja (s)" + }, + "data_description": { + "start_duration_threshold": "Zanemarite kratke skokove snage kraće od ovog trajanja (sekunde). Filtrira skokove pri pokretanju (npr. 60W za 2s) prije početka pravog ciklusa. Podrazumevano: 5s.", + "start_energy_threshold": "Ciklus mora akumulirati najmanje ovoliko energije (Wh) tokom START faze da bi se potvrdio. Podrazumevano: 0,005 Wh.", + "completion_min_seconds": "Ciklusi kraći od ovoga će biti označeni kao 'prekinuti' čak i ako se završe prirodno. Podrazumevano: 600s.", + "start_threshold_w": "Snaga mora porasti IZNAD ove granične vrijednosti da započne ciklus. Postavite više od vaše snage u stanju pripravnosti. Zadano: min_power + 1 W.", + "stop_threshold_w": "Snaga mora pasti ISPOD ovog praga da bi se KRAJO ciklus. Postavite malo iznad nule kako biste izbjegli šum koji pokreće lažne krajeve. Zadano: 0,6 * min_power.", + "end_energy_threshold": "Ciklus se završava samo ako je energija u posljednjem prozoru odgode isključenja ispod ove vrijednosti (Wh). Sprečava lažne krajeve ako snaga fluktuira blizu nule. Podrazumevano: 0,05 Wh.", + "running_dead_zone": "Nakon što ciklus započne, zanemarite padove snage ovoliko sekundi kako biste spriječili lažno otkrivanje kraja tokom početne faze okretanja. Postavite na 0 da biste onemogućili. Podrazumevano: 0s.", + "end_repeat_count": "Koliko puta krajnji uslov (snaga ispod praga za off_delay) mora biti ispunjen uzastopno prije završetka ciklusa. Veće vrijednosti sprečavaju lažne završetak tokom pauza. Podrazumevano: 1.", + "min_off_gap": "Ako ciklus započne unutar ovoliko sekundi od završetka prethodnog, oni će biti SPOJENI u jedan ciklus.", + "sampling_interval": "Minimalni interval uzorkovanja u sekundama. Ažuriranja brža od ove stope će biti zanemarena. Podrazumevano: 30s (2s za veš mašine, mašine za sušenje veša i mašine za pranje sudova)." + } + }, + "matching_section": { + "name": "Usklađivanje profila i učenje", + "description": "Određuje koliko agresivno WashData upoređuje aktivne cikluse s naučenim profilima i kada tražiti povratne informacije.", + "data": { + "profile_match_min_duration_ratio": "Profil podudaranja minimalnog omjera trajanja (0,1-1,0)", + "profile_match_interval": "Interval podudaranja profila (sekunde)", + "profile_match_threshold": "Prag podudaranja profila", + "profile_unmatch_threshold": "Prag nepodudaranja profila", + "auto_label_confidence": "Pouzdanost automatske oznake (0-1)", + "learning_confidence": "Zahtjev za povratne informacije povjerenje (0-1)", + "suppress_feedback_notifications": "Poništi obavještenja o povratnim informacijama", + "duration_tolerance": "Tolerancija trajanja (0-0,5)", + "smoothing_window": "Prozor za izglađivanje (uzorci)", + "profile_duration_tolerance": "Tolerancija trajanja podudaranja profila (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimalni omjer trajanja za podudaranje profila. Ciklus rada mora biti najmanje ovaj dio trajanja profila da bi se podudarao. Niže = ranije podudaranje. Podrazumevano: 0,50 (50%).", + "profile_match_interval": "Koliko često (u sekundama) pokušavati uparivanje profila tokom ciklusa. Niže vrijednosti omogućavaju brže otkrivanje, ali koriste više CPU-a. Podrazumevano: 300s (5 minuta).", + "profile_match_threshold": "Minimalni rezultat sličnosti DTW (0,0–1,0) potreban da bi se profil smatrao podudarnim. Više = strože podudaranje, manje lažnih pozitivnih rezultata. Podrazumevano: 0.4.", + "profile_unmatch_threshold": "DTW rezultat sličnosti (0,0–1,0) ispod kojeg se odbacuje prethodno podudarni profil. Trebao bi biti niži od praga podudaranja kako bi se spriječilo treperenje. Podrazumevano: 0,35.", + "auto_label_confidence": "Automatski označi cikluse na ili iznad ove pouzdanosti po završetku (0,0–1,0).", + "learning_confidence": "Minimalno povjerenje za zahtjev za verifikaciju korisnika putem događaja (0,0–1,0).", + "suppress_feedback_notifications": "Kada je omogućen, WashData će i dalje interno pratiti i tražiti povratne informacije, ali neće prikazivati ​​stalna obavještenja u kojima se od vas traži da potvrdite cikluse. Korisno kada se ciklusi uvijek ispravno detektuju i ako vam upute ometaju pažnju.", + "duration_tolerance": "Tolerancija za procjene preostalog vremena tokom trčanja (0,0–0,5 odgovara 0–50%).", + "smoothing_window": "Broj nedavnih očitanja snage korištenih za izravnavanje pokretnog prosjeka.", + "profile_duration_tolerance": "Tolerancija koja se koristi za podudaranje profila za varijancu ukupnog trajanja (0,0–0,5). Zadano ponašanje: ±25%." + } + }, + "timing_section": { + "name": "Vremenski parametri, održavanje i otklanjanje grešaka", + "description": "Watchdog, reset napretka, automatsko održavanje i izlaganje debug entiteta.", + "data": { + "watchdog_interval": "Watchdog interval (sekunde)", + "no_update_active_timeout": "Istek bez ažuriranja (s)", + "progress_reset_delay": "Odgoda resetiranja napretka (sekunde)", + "auto_maintenance": "Omogućite automatsko održavanje", + "expose_debug_entities": "Izložite entitete za otklanjanje grešaka", + "save_debug_traces": "Sačuvajte tragove za otklanjanje grešaka" + }, + "data_description": { + "watchdog_interval": "Sekunde između nadzornih provjera tokom trčanja. Podrazumevano: 5s. UPOZORENJE: Uvjerite se da je ovo VIŠE od intervala ažuriranja vašeg senzora kako biste izbjegli lažna zaustavljanja (npr. ako se senzor ažurira svakih 60 s, koristite 65 s+).", + "no_update_active_timeout": "Za senzore za objavljivanje na promjeni: prisilno prekinuti AKTIVNI ciklus samo ako ažuriranja ne stignu ovoliko sekundi. Završetak sa malom potrošnjom još uvijek koristi odgodu isključivanja.", + "progress_reset_delay": "Nakon što se ciklus završi (100%), pričekajte ovoliko sekundi mirovanja prije nego što vratite napredak na 0%. Podrazumevano: 300s.", + "auto_maintenance": "Omogućite automatsko održavanje (popravite uzorke, spojite fragmente).", + "expose_debug_entities": "Prikaži napredne senzore (pouzdanje, faza, dvosmislenost) za otklanjanje grešaka.", + "save_debug_traces": "Čuvajte detaljne podatke o rangiranju i tragovima napajanja u historiji (povećava korištenje memorije)." + } + }, + "anti_wrinkle_section": { + "name": "Štit protiv bora (sušilice)", + "description": "Zanemarite okretanja bubnja nakon završetka ciklusa kako biste spriječili fantomske cikluse. Samo sušilica / mašina za pranje i sušenje.", + "data": { + "anti_wrinkle_enabled": "Štit protiv bora (samo sušilica)", + "anti_wrinkle_max_power": "Maksimalna snaga protiv bora (W)", + "anti_wrinkle_max_duration": "Maksimalno trajanje protiv bora (s)", + "anti_wrinkle_exit_power": "Izlazna snaga protiv bora (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Zanemarite kratke rotacije bubnja male snage nakon završetka ciklusa kako biste spriječili cikluse duhova (samo sušilica/mašina za pranje i sušenje).", + "anti_wrinkle_max_power": "Rafali na ili ispod ove snage tretiraju se kao rotacije protiv bora umjesto novih ciklusa. Primjenjuje se samo kada je omogućena zaštita od bora.", + "anti_wrinkle_max_duration": "Maksimalna dužina praska za tretiranje kao rotacija protiv bora. Primjenjuje se samo kada je omogućena zaštita od bora.", + "anti_wrinkle_exit_power": "Ako snaga padne ispod ovog nivoa, odmah izađite iz anti-bora (true off). Primjenjuje se samo kada je omogućena zaštita od bora." + } + }, + "delay_start_section": { + "name": "Detekcija odgođenog starta", + "description": "Trajni standby s niskom potrošnjom tretirajte kao stanje 'Čekanje na početak' dok ciklus stvarno ne počne.", + "data": { + "delay_start_detect_enabled": "Omogući otkrivanje odgođenog starta", + "delay_confirm_seconds": "Odgođeni start: vrijeme potvrde standby režima (s)", + "delay_timeout_hours": "Odgođeni početak: maksimalno vrijeme čekanja (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kada je omogućeno, kratak skok odvoda niske snage praćen stalnim napajanjem u stanju pripravnosti detektuje se kao odloženi početak. Senzor stanja pokazuje 'Čeka na pokretanje' tokom kašnjenja. Ne prati se vrijeme programa ili napredak sve dok ciklus zapravo ne počne. Zahtijeva da se start_threshold_w postavi iznad snage odvoda.", + "delay_confirm_seconds": "Snaga mora ostati između praga zaustavljanja (W) i praga pokretanja (W) ovoliko sekundi prije nego što WashData pređe u stanje 'Čekanje na početak'. Ovo filtrira kratke vrhove tokom navigacije kroz meni. Zadano: 60 s.", + "delay_timeout_hours": "Sigurnosno vremensko ograničenje: ako je mašina i dalje u 'Čeka na pokretanje' nakon ovoliko sati, WashData se vraća na Off. Standardno: 8 h." + } + }, + "external_triggers_section": { + "name": "Eksterni okidači, vrata i pauza", + "description": "Opcioni eksterni senzor završetka ciklusa, senzor vrata za detekciju pauze / pražnjenja i prekidač za gašenje napajanja tokom pauze.", + "data": { + "external_end_trigger_enabled": "Omogući okidač za završetak eksternog ciklusa", + "external_end_trigger": "Eksterni senzor završetka ciklusa", + "external_end_trigger_inverted": "Invertna logika okidača (okidač je isključen)", + "door_sensor_entity": "Senzor vrata", + "pause_cuts_power": "Isključite napajanje prilikom pauze", + "switch_entity": "Prebacite entitet (za pauziranje isključenja struje)", + "notify_unload_delay_minutes": "Kašnjenje obavijesti o čekanju rublja (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Omogućite praćenje eksternog binarnog senzora da biste pokrenuli završetak ciklusa.", + "external_end_trigger": "Odaberite binarni senzorski entitet. Kada se ovaj senzor aktivira, trenutni ciklus će se završiti sa statusom 'dovršeno'.", + "external_end_trigger_inverted": "Standardno, okidač se aktivira kada se senzor uključi. Označite ovo polje da se aktivira kada se senzor ISKLJUČI.", + "door_sensor_entity": "Opcioni binarni senzor za vrata mašine (uključeno = otvoreno, isključeno = zatvoreno). Kada se vrata otvore tokom aktivnog ciklusa, WashData će potvrditi da je ciklus namjerno pauziran. Nakon završetka ciklusa, ako su vrata i dalje zatvorena, stanje se mijenja u 'Clean' dok se vrata ne otvore. Napomena: zatvaranje vrata NE nastavlja automatski pauzirani ciklus - nastavak se mora pokrenuti ručno putem dugmeta Nastavi ciklus ili usluge.", + "pause_cuts_power": "Prilikom pauziranja pomoću tipke Pause Cycle ili usluge, također isključite konfigurirani entitet prekidača. Stupa na snagu samo ako je entitet prekidača konfiguriran za ovaj uređaj.", + "switch_entity": "Prebacivanje entiteta za prebacivanje kada pauzirate ili nastavljate. Potrebno kada je omogućeno 'Prekini napajanje prilikom pauze'.", + "notify_unload_delay_minutes": "Pošaljite obavijest putem kanala obavijesti o završetku nakon što se ciklus završi, a vrata nisu bila otvorena ovoliko minuta (zahtijeva senzor vrata). Postavite na 0 da biste onemogućili. Podrazumevano: 60 min." + } + }, + "pump_section": { + "name": "Nadzor pumpe", + "description": "Samo za uređaje tipa pumpa. Pokreće događaj ako ciklus pumpe traje duže od ovog vremena.", + "data": { + "pump_stuck_duration": "Prag (s) upozorenja zastoja pumpe (samo pumpa)" + }, + "data_description": { + "pump_stuck_duration": "Ako ciklus pumpe i dalje radi nakon ovoliko sekundi, događaj ha_washdata_pump_stuck se aktivira jednom. Upotrijebite ovo za otkrivanje zaglavljenog motora (uobičajeni rad pumpe ispod 60 s). Zadano: 1800 s (30 min). Samo tip uređaja za pumpu." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Opciono povežite ovaj WashData uređaj sa postojećim Home Assistant uređajem (na primjer, pametni utikač ili sam uređaj). Uređaj WashData se tada prikazuje kao \"Povezan putem\" tog uređaja. Ostavite prazno da WashData ostane kao samostalan uređaj.", + "data": { + "linked_device": "Povezani uređaj" + }, + "data_description": { + "linked_device": "Odaberite uređaj na koji želite povezati WashData. Ovo dodaje referencu 'Povezano putem' u registar uređaja; WashData čuva vlastitu karticu uređaja i entitete. Obrišite odabir da uklonite vezu." + } + } + } + }, + "diagnostics": { + "title": "Dijagnostika i održavanje", + "description": "Pokrenite akcije održavanja kao što je spajanje fragmentiranih ciklusa ili migracija pohranjenih podataka.\n\n**Upotreba pohrane**\n\n- Veličina fajla: {file_size_kb} KB\n- Ciklusi: {cycle_count}\n- Profili: {profile_count}\n- Tragovi otklanjanja grešaka: {debug_count}", + "menu_options": { + "reprocess_history": "Održavanje: ponovo obraditi i optimizirati podatke", + "clear_debug_data": "Obriši podatke za otklanjanje grešaka (oslobodite prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovratno)", + "export_import": "Izvezi/uvezi JSON sa postavkama (kopiraj/zalijepi)", + "menu_back": "← Nazad" + } + }, + "clear_debug_data": { + "title": "Obrišite podatke za otklanjanje grešaka", + "description": "Jeste li sigurni da želite izbrisati sve pohranjene tragove za otklanjanje grešaka? Ovo će osloboditi prostor, ali ukloniti detaljne informacije o rangiranju iz prošlih ciklusa." + }, + "manage_cycles": { + "title": "Upravljanje ciklusima", + "description": "Nedavni ciklusi:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "select_cycle_to_label": "Oznaci specifican ciklus", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/splitanja", + "trim_cycle_select": "Podaci ciklusa trim", + "menu_back": "← Nazad" + } + }, + "manage_cycles_empty": { + "title": "Upravljanje ciklusima", + "description": "Još uvijek nema zabilježenih ciklusa.", + "menu_options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "menu_back": "← Nazad" + } + }, + "manage_profiles": { + "title": "Upravljanje profilima", + "description": "Sažetak profila:\n{profile_summary}", + "menu_options": { + "create_profile": "Kreirajte novi profil", + "edit_profile": "Uredi/Preimenuj profil", + "delete_profile_select": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Čišćenje historije - Grafikon i brisanje", + "assign_profile_phases_select": "Dodijelite raspon faza", + "menu_back": "← Nazad" + } + }, + "manage_profiles_empty": { + "title": "Upravljanje profilima", + "description": "Još nema kreiranih profila.", + "menu_options": { + "create_profile": "Kreirajte novi profil", + "menu_back": "← Nazad" + } + }, + "manage_phase_catalog": { + "title": "Upravljanje katalogom faza", + "description": "Katalog faza (zadano za ovaj uređaj + prilagođene faze):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Kreirajte novu fazu", + "phase_catalog_edit_select": "Uredi fazu", + "phase_catalog_delete": "Izbriši fazu", + "menu_back": "← Nazad" + } + }, + "phase_catalog_create": { + "title": "Kreirajte prilagođenu fazu", + "description": "Kreirajte prilagođeni naziv faze i opis i odaberite na koji tip uređaja se primjenjuje.", + "data": { + "target_device_type": "Ciljna vrsta uređaja", + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_edit_select": { + "title": "Uredite prilagođenu fazu", + "description": "Odaberite prilagođenu fazu za uređivanje.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "phase_catalog_edit": { + "title": "Uredite prilagođenu fazu", + "description": "Faza uređivanja: {phase_name}", + "data": { + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_delete": { + "title": "Izbriši prilagođenu fazu", + "description": "Odaberite prilagođenu fazu za brisanje. Svi dodijeljeni opsezi koji koriste ovu fazu bit će uklonjeni.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "assign_profile_phases": { + "title": "Dodijelite raspon faza", + "description": "Pregled faze za profil: **{profile_name}**\n\n{timeline_svg}\n\n**Trenutni rasponi:**\n{current_ranges}\n\nKoristite radnje u nastavku za dodavanje, uređivanje, brisanje ili spremanje raspona.", + "menu_options": { + "assign_profile_phases_add": "Dodaj fazni opseg", + "assign_profile_phases_edit_select": "Uredi raspon faza", + "assign_profile_phases_delete": "Izbriši raspon faza", + "phase_ranges_clear": "Obriši sve opsege", + "assign_profile_phases_auto_detect": "Automatsko otkrivanje faza", + "phase_ranges_save": "Sačuvaj i vrati", + "menu_back": "← Nazad" + } + }, + "assign_profile_phases_add": { + "title": "Dodaj fazni opseg", + "description": "Dodajte jedan raspon faza trenutnom nacrtu.", + "data": { + "phase_name": "Faza", + "start_min": "Početna minuta", + "end_min": "Kraj minuta" + } + }, + "assign_profile_phases_edit_select": { + "title": "Uredi raspon faza", + "description": "Odaberite raspon za uređivanje.", + "data": { + "range_index": "Raspon faze" + } + }, + "assign_profile_phases_edit": { + "title": "Uredi raspon faza", + "description": "Ažurirajte odabrani raspon faza.", + "data": { + "phase_name": "Faza", + "start_min": "Početna minuta", + "end_min": "Kraj minuta" + } + }, + "assign_profile_phases_delete": { + "title": "Izbriši raspon faza", + "description": "Odaberite raspon koji želite ukloniti iz nacrta.", + "data": { + "range_index": "Raspon faze" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatski otkrivene faze", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Automatski otkrivena faza ({detected_count}).** Odaberite radnju ispod.", + "data": { + "action": "Akcija" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Naziv otkrivenih faza", + "description": "Profil: **{profile_name}**\n\n**Otkriveno je {detected_count} faza:**\n{ranges_summary}\n\nDodijelite naziv svakoj fazi." + }, + "assign_profile_phases_select": { + "title": "Dodijelite raspon faza", + "description": "Odaberite profil za konfiguriranje faznih raspona.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistika profila", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Kreirajte novi profil", + "description": "Kreirajte novi profil ručno ili iz prošlog ciklusa.\n\nPrimjeri imena profila: 'Delikate', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Ime profila", + "reference_cycle": "Referentni ciklus (opciono)", + "manual_duration": "Ručno trajanje (minuta)" + } + }, + "edit_profile": { + "title": "Uredi profil", + "description": "Odaberite profil koji želite preimenovati", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Uredite detalje profila", + "description": "Trenutni profil: {current_name}", + "data": { + "new_name": "Ime profila", + "manual_duration": "Ručno trajanje (minuta)" + } + }, + "delete_profile_select": { + "title": "Izbriši profil", + "description": "Odaberite profil za brisanje", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potvrdite brisanje profila", + "description": "⚠️ Ovo će trajno izbrisati profil: {profile_name}", + "data": { + "unlabel_cycles": "Uklonite naljepnicu s ciklusa koristeći ovaj profil" + } + }, + "auto_label_cycles": { + "title": "Automatski označi stare cikluse", + "description": "Ukupno pronađeno {total_count} ciklusa. Profili: {profiles}", + "data": { + "confidence_threshold": "Minimalno povjerenje (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Odaberite ciklus za označavanje", + "description": "✓ = završeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "select_cycle_to_delete": { + "title": "Izbriši ciklus", + "description": "⚠️ Ovo će trajno izbrisati odabrani ciklus", + "data": { + "cycle_id": "Ciklus" + } + }, + "label_cycle": { + "title": "Označi ciklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Novo ime profila (ako se kreira)" + } + }, + "post_process": { + "title": "Historija procesa (Spajanje/Splitanje)", + "description": "Očistite istoriju ciklusa spajanjem fragmentiranih ciklusa i cijepanjem pogrešno spojenih ciklusa unutar vremenskog prozora. Unesite broj sati da pogledate unazad (koristite 999999 za sve).", + "data": { + "time_range": "Vremenski okvir za pregled (sati)", + "gap_seconds": "Spajanje/razdvajanje jaza (sekunde)" + }, + "data_description": { + "time_range": "Broj sati za skeniranje fragmentiranih ciklusa za spajanje. Koristite 999999 za obradu svih historijskih podataka.", + "gap_seconds": "Prag za odlučivanje o razdvajanju ili spajanju. Spajaju se praznine KRAĆE od ovoga. Praznine DUGE od ovoga su podijeljene. Smanjite ovo da biste prisilili podjelu na ciklus koji je spojen pogrešno." + } + }, + "cleanup_profile": { + "title": "Očistite historiju - odaberite Profil", + "description": "Odaberite profil za vizualizaciju. Ovo će generirati grafikon koji prikazuje sve prošle cikluse za ovaj profil kako bi se lakše identificirali odstupnici.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Čišćenje historije - Grafikon i brisanje", + "description": "![Grafikon]({graph_url})\n\n**Vizueliziranje {profile_name}**\n\nGrafikon prikazuje pojedinačne cikluse kao obojene linije. Identifikujte odstupnike (linije udaljene od grupe) i podudaranje njihove boje na listi ispod da biste ih izbrisali.\n\n**Odaberite cikluse za TRAJNO brisanje:**", + "data": { + "cycles_to_delete": "Ciklusi za brisanje (provjerite odgovarajuće boje)" + } + }, + "interactive_editor": { + "title": "Interaktivni uređivač spajanja/splitanja", + "description": "Odaberite ručnu radnju koju ćete izvršiti na historiji ciklusa.", + "menu_options": { + "editor_split": "Podijelite ciklus (Pronađite praznine)", + "editor_merge": "Spajanje ciklusa (spajanje fragmenata)", + "editor_delete": "Izbriši ciklus(e)", + "menu_back": "← Nazad" + } + }, + "editor_select": { + "title": "Odaberite Ciklusi", + "description": "Odaberite ciklus(e) za obradu.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklusi" + } + }, + "editor_configure": { + "title": "Konfiguriši i primeni", + "description": "{preview_md}", + "data": { + "confirm_action": "Akcija", + "merged_profile": "Rezultirajući profil", + "new_profile_name": "Novo ime profila", + "confirm_commit": "Da, potvrđujem ovu radnju", + "segment_0_profile": "Segment 1 Profil", + "segment_1_profile": "Segment 2 Profil", + "segment_2_profile": "Segment 3 Profil", + "segment_3_profile": "Segment 4 Profil", + "segment_4_profile": "Segment 5 Profil", + "segment_5_profile": "Segment 6 Profil", + "segment_6_profile": "Segment 7 Profil", + "segment_7_profile": "Segment 8 Profil", + "segment_8_profile": "Segment 9 Profil", + "segment_9_profile": "Segment 10 Profil" + } + }, + "editor_split_params": { + "title": "Konfiguracija podjele", + "description": "Podesite osjetljivost za podjelu ovog ciklusa. Svaki razmak u praznom hodu duži od ovoga će uzrokovati rascjep.", + "data": { + "split_mode": "Metoda podjele" + } + }, + "editor_split_auto_params": { + "title": "Konfiguracija automatskog razdvajanja", + "description": "Prilagodite osjetljivost za razdvajanje ovog ciklusa. Svaki period mirovanja duži od ovoga uzrokovat će razdvajanje.", + "data": { + "min_gap_seconds": "Prag razmaka za razdvajanje (sekunde)" + } + }, + "editor_split_manual_params": { + "title": "Ručne vremenske oznake razdvajanja", + "description": "{preview_md}Prozor ciklusa: **{cycle_start_wallclock} → {cycle_end_wallclock}** na {cycle_date}.\n\nUnesite jednu ili više vremenskih oznaka razdvajanja (`HH:MM` ili `HH:MM:SS`), po jednu u svakom redu. Ciklus će biti presječen na svakoj vremenskoj oznaci — N vremenskih oznaka daje N+1 segmenata.", + "data": { + "split_timestamps": "Vremenske oznake podjele" + } + }, + "reprocess_history": { + "title": "Održavanje: ponovo obraditi i optimizirati podatke", + "description": "Vrši dubinsko čišćenje istorijskih podataka:\n1. Smanjuje očitanja nulte snage (tišina).\n2. Popravlja vrijeme/trajanje ciklusa.\n3. Preračunava sve statističke modele (koverte).\n\n**Pokrenite ovo da riješite probleme s podacima ili primijenite nove algoritme.**" + }, + "wipe_history": { + "title": "Obriši historiju (samo testiranje)", + "description": "⚠️ Ovo će trajno izbrisati SVE pohranjene cikluse i profile za ovaj uređaj. Ovo se ne može poništiti!" + }, + "export_import": { + "title": "Izvoz/uvoz JSON", + "description": "Kopiraj/zalijepi cikluse, profile I postavke za ovaj uređaj. Izvezite ispod ili zalijepite JSON za uvoz.", + "data": { + "mode": "Akcija", + "json_payload": "Kompletna konfiguracija JSON" + }, + "data_description": { + "mode": "Odaberite Izvezi da kopirate trenutne podatke i postavke ili Uvezi da zalijepite izvezene podatke s drugog WashData uređaja.", + "json_payload": "Za izvoz: kopirajte ovaj JSON (uključuje cikluse, profile i sva fino podešena podešavanja). Za uvoz: zalijepite JSON izvezen iz WashData." + } + }, + "record_cycle": { + "title": "Ciklus snimanja", + "description": "Ručno snimite ciklus kako biste kreirali čist profil bez smetnji.\n\nStatus: **{status}**\nTrajanje: {duration}s\nUzorci: {samples}", + "menu_options": { + "record_refresh": "Osvježi status", + "record_stop": "Zaustavi snimanje (Sačuvaj i obradi)", + "record_start": "Započni novo snimanje", + "record_process": "Obraditi posljednje snimanje (Podreži i sačuvaj)", + "record_discard": "Odbaci zadnji snimak", + "menu_back": "← Nazad" + } + }, + "record_process": { + "title": "Snimanje procesa", + "description": "![Grafikon]({graph_url})\n\n**Grafikon prikazuje neobrađeno snimanje.** Plava = Zadrži, Crvena = Predloženo obrezivanje.\n*Grafikon je statičan i ne ažurira se promjenama obrasca.*\n\nSnimanje je zaustavljeno. Trimovi su usklađeni sa otkrivenom stopom uzorkovanja (~{sampling_rate}s).\n\nNeobrađeno trajanje: {duration}s\nUzorci: {samples}", + "data": { + "head_trim": "Skratiti početak (sekunde)", + "tail_trim": "Kraj obrezivanja (sekunde)", + "save_mode": "Sačuvaj odredište", + "profile_name": "Ime profila" + } + }, + "learning_feedbacks": { + "title": "Povratne informacije o učenju", + "description": "Neriješene povratne informacije: {count}\n\n{pending_table}\n\nOdaberite zahtjev za pregled ciklusa za obradu.", + "menu_options": { + "learning_feedbacks_pick": "Pregledaj neriješenu povratnu informaciju", + "learning_feedbacks_dismiss_all": "Odbaci sve neriješene povratne informacije", + "menu_back": "← Nazad" + } + }, + "learning_feedbacks_pick": { + "title": "Odaberite povratne informacije za pregled", + "description": "Odaberite zahtjev za pregled ciklusa koji želite otvoriti.", + "data": { + "selected_feedback": "Odaberite ciklus za pregled" + } + }, + "learning_feedbacks_empty": { + "title": "Povratne informacije o učenju", + "description": "Nije pronađen nijedan zahtjev za povratne informacije na čekanju.", + "menu_options": { + "menu_back": "← Nazad" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Odbaci sve povratne informacije na čekanju", + "description": "Upravo ćete odbaciti **{count}** zahtjeva za povratne informacije na čekanju. Odbijeni ciklusi ostaju u vašoj historiji, ali neće doprinositi novom signalu učenja. Ovo se ne može poništiti.\n\n✅ Označite polje ispod i kliknite na **Pošalji** da odbacite sve.\n❌ Ostavite neoznačeno i kliknite na **Pošalji** da otkažete i vratite se.", + "data": { + "confirm_dismiss_all": "Potvrdi: odbaci sve neriješene zahtjeve za povratne informacije" + } + }, + "resolve_feedback": { + "title": "Razriješi povratne informacije", + "description": "{comparison_img}{candidates_table}\n**Otkriveni profil**: {detected_profile} ({confidence_pct}%)\n**Procijenjeno trajanje**: {est_duration_min} min\n**stvarno trajanje**: {act_duration_min} min\n\n__Odaberite akciju ispod:__", + "data": { + "action": "šta biste željeli raditi?", + "corrected_profile": "Ispravan program (ako se ispravlja)", + "corrected_duration": "Ispravno trajanje u sekundama (ako se ispravlja)" + } + }, + "trim_cycle_select": { + "title": "Podrezivanje ciklusa - Odaberite Ciklus", + "description": "Odaberite ciklus za podrezivanje. ✓ = završeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "trim_cycle": { + "title": "Podrezivanje ciklusa", + "description": "Trenutni prozor: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akcija" + } + }, + "trim_cycle_start": { + "title": "Postavite Trim Start", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni početak: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nOdaberite novo vrijeme početka koristeći birač sata ispod.", + "data": { + "trim_start_time": "Novo vrijeme početka", + "trim_start_min": "Novi početak (minuta od početka ciklusa)" + } + }, + "trim_cycle_end": { + "title": "Postavite Trim End", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni kraj: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nOdaberite novo vrijeme završetka koristeći birač sata ispod.", + "data": { + "trim_end_time": "Novo vreme završetka", + "trim_end_min": "Novi kraj (minuta od početka ciklusa)" + } + } + }, + "error": { + "import_failed": "Uvoz nije uspio. Zalijepite važeći JSON za izvoz WashData.", + "select_exactly_one": "Odaberite tačno jedan ciklus za ovu radnju.", + "select_at_least_one": "Odaberite najmanje jedan ciklus za ovu radnju.", + "select_at_least_two": "Odaberite najmanje dva ciklusa za ovu radnju.", + "profile_exists": "Profil sa ovim imenom već postoji", + "rename_failed": "Preimenovanje profila nije uspjelo. Profil možda ne postoji ili je novo ime već zauzeto.", + "assignment_failed": "Dodjeljivanje profila ciklusu nije uspjelo", + "duplicate_phase": "Faza sa ovim imenom već postoji.", + "phase_not_found": "Odabrana faza nije pronađena.", + "invalid_phase_name": "Naziv faze ne može biti prazan.", + "phase_name_too_long": "Naziv faze je predugačak.", + "invalid_phase_range": "Opseg faza je nevažeći. Kraj mora biti veći od početka.", + "invalid_phase_timestamp": "Vremenska oznaka je nevažeća. Koristite format YYYY-MM-DD HH:MM ili HH:MM.", + "overlapping_phase_ranges": "Opsezi faza se preklapaju. Podesite opsege i pokušajte ponovo.", + "incomplete_phase_row": "Svaki red faza mora uključivati ​​fazu plus kompletne početne/završne vrijednosti za odabrani način rada.", + "timestamp_mode_cycle_required": "Molimo odaberite izvorni ciklus kada koristite način vremenske oznake.", + "no_phase_ranges": "Još uvijek nema dostupnih raspona faza za tu radnju.", + "feedback_notification_title": "WashData: Potvrda ciklusa ({device})", + "feedback_notification_message": "**Uređaj**: {device}\n**Program**: {program} ({confidence}% povjerenja)\n**Vrijeme**: {time}\n\nWashData treba vašu pomoć da potvrdi ovaj otkriveni ciklus.\n\nMolimo idite na **Postavke > Uređaji i usluge > WashData > Konfiguriši > Povratne informacije o učenju** da potvrdite ili ispravite ovaj rezultat.", + "suggestions_ready_notification_title": "WashData: Spremne predložene postavke ({device})", + "suggestions_ready_notification_message": "Senzor **Predložene postavke** sada izvještava o **{count}** preporukama koje se mogu primijeniti.\n\nDa biste ih pregledali i primijenili: **Postavke > Uređaji i usluge > WashData > Konfiguriraj > Napredne postavke > Primijeni predložene vrijednosti**.\n\nPrijedlozi su opcioni i prikazani su za pregled prije nego što ih sačuvate.", + "trim_range_invalid": "Početak mora biti prije kraja. Molimo prilagodite svoje trim tačke.", + "trim_failed": "Trim nije uspio. Ciklus možda više ne postoji ili je prozor prazan.", + "invalid_split_timestamp": "Vremenska oznaka nije važeća ili je izvan prozora ciklusa. Koristite HH:MM ili HH:MM:SS unutar prozora ciklusa.", + "no_split_segments_found": "Iz ovih vremenskih oznaka nije bilo moguće napraviti važeće segmente. Provjerite da je svaka vremenska oznaka unutar prozora ciklusa i da segmenti traju najmanje 1 minutu.", + "auto_tune_suggestion": "{device_type} {device_title} je otkrio cikluse duhova. Predložena promjena min_power: {current_min}W -> {new_min}W (ne primjenjuje se automatski).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} je otkrio cikluse duhova. Predložena promjena min_power: {current_min}W -> {new_min}W (ne primjenjuje se automatski)." + }, + "abort": { + "no_cycles_found": "Nisu pronađeni ciklusi", + "no_split_segments_found": "U odabranom ciklusu nisu pronađeni segmenti koji se mogu razdvojiti.", + "cycle_not_found": "Odabrani ciklus nije bilo moguće učitati.", + "no_power_data": "Nema dostupnih podataka o tragovima napajanja za odabrani ciklus.", + "no_profiles_found": "Nema pronađenih profila. Prvo kreirajte profil.", + "no_profiles_for_matching": "Nema dostupnih profila za podudaranje. Prvo kreirajte profile.", + "no_unlabeled_cycles": "Svi ciklusi su već označeni", + "no_suggestions": "Još uvijek nema dostupnih predloženih vrijednosti. Izvršite nekoliko ciklusa i pokušajte ponovo.", + "no_predictions": "Nema predviđanja", + "no_custom_phases": "Nisu pronađene prilagođene faze.", + "reprocess_success": "Uspješno ponovno obrađeno {count} ciklusa.", + "debug_data_cleared": "Uspješno obrisani podaci za otklanjanje grešaka iz {count} ciklusa." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Početak ciklusa", + "cycle_finish": "Završetak ciklusa", + "cycle_live": "Napredak uživo" + } + }, + "common_text": { + "options": { + "unlabeled": "(neoznačeno)", + "create_new_profile": "Kreirajte novi profil", + "remove_label": "Ukloni oznaku", + "no_reference_cycle": "(bez referentnog ciklusa)", + "all_device_types": "Sve vrste uređaja", + "current_suffix": "(trenutni)", + "editor_select_info": "Odaberite 1 ciklus za razdvajanje ili 2+ ciklusa za spajanje.", + "editor_select_info_split": "Odaberite 1 ciklus za podjelu.", + "editor_select_info_merge": "Odaberite 2+ ciklusa za spajanje.", + "editor_select_info_delete": "Odaberite 1+ ciklusa za brisanje.", + "no_cycles_recorded": "Još uvijek nema zabilježenih ciklusa.", + "profile_summary_line": "- **{name}**: {count} ciklusa, {avg}m pros", + "no_profiles_created": "Još nema kreiranih profila.", + "phase_preview": "Pregled faze", + "phase_preview_no_curve": "Kriva prosječnog profila još nije dostupna. Pokreni/označi više ciklusa za ovaj profil.", + "average_power_curve": "Prosječna krivulja snage", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciklusa, ~{duration}m prosječno)", + "profile_option_short_fmt": "{name} ({count} ciklusa, ~{duration}m)", + "deleted_cycles_from_profile": "Izbrisano {count} ciklusa od {profile}.", + "not_enough_data_graph": "Nema dovoljno podataka za generiranje grafikona.", + "no_profiles_found": "Nema pronađenih profila.", + "cycle_info_fmt": "Ciklus: {start}, {duration}m, Struja: {label}", + "top_candidates_header": "### Najbolji kandidati", + "tbl_profile": "Profil", + "tbl_confidence": "Samopouzdanje", + "tbl_correlation": "Korelacija", + "tbl_duration_match": "Podudaranje trajanja", + "detected_profile": "Otkriveni profil", + "estimated_duration": "Procijenjeno trajanje", + "actual_duration": "Stvarno trajanje", + "choose_action": "__Odaberite akciju ispod:__", + "tbl_count": "Broj", + "tbl_avg": "Prosj.", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energija (prosj.)", + "tbl_energy_total": "Energija (ukupno)", + "tbl_consistency": "Konzist.", + "tbl_last_run": "Zadnje pokretanje", + "graph_legend_title": "Grafička legenda", + "graph_legend_body": "Plava traka predstavlja posmatrani raspon minimalne i maksimalne potrošnje. Linija prikazuje krivu prosječne snage.", + "recording_preview": "Pregled snimanja", + "trim_start": "Obreži početak", + "trim_end": "Obreži kraj", + "split_preview_title": "Pregled podjele", + "split_preview_found_fmt": "Pronađeno je {count} segmenata.", + "split_preview_confirm_fmt": "Kliknite na Potvrdi da podijelite ovaj ciklus na {count} zasebnih ciklusa.", + "merge_preview_title": "Pregled spajanja", + "merge_preview_joining_fmt": "Spajanje {count} ciklusa. Praznine će biti popunjene očitanjima od 0W.", + "editor_delete_preview_title": "Pregled brisanja", + "editor_delete_preview_intro": "Odabrani ciklusi bit će trajno izbrisani:", + "editor_delete_preview_confirm": "Kliknite Potvrdi da trajno izbrišete ove zapise ciklusa.", + "no_power_preview": "*Nema dostupnih podataka o snazi za pregled.*", + "profile_comparison": "Poređenje profila", + "actual_cycle_label": "Ovaj ciklus (stvarni)", + "storage_usage_header": "Upotreba pohrane", + "storage_file_size": "Veličina datoteke", + "storage_cycles": "Ciklusi", + "storage_profiles": "Profili", + "storage_debug_traces": "Tragovi otklanjanja grešaka", + "overview_suffix": "Pregled", + "phase_preview_for_profile": "Pregled faze za profil", + "current_ranges_header": "Trenutni rasponi", + "assign_phases_help": "Koristite radnje u nastavku za dodavanje, uređivanje, brisanje ili spremanje raspona.", + "profile_summary_header": "Sažetak profila", + "recent_cycles_header": "Nedavni ciklusi", + "trim_cycle_preview_title": "Cycle Trim Preview", + "trim_cycle_preview_no_data": "Nema dostupnih podataka o snazi ​​za ovaj ciklus.", + "trim_cycle_preview_kept_suffix": "zadržao", + "table_program": "Program", + "table_when": "Kada", + "table_length": "Trajanje", + "table_match": "Podudaranje", + "table_profile": "Profil", + "table_cycles": "Ciklusi", + "table_avg_length": "Prosj. trajanje", + "table_last_run": "Zadnje pokretanje", + "table_avg_energy": "Prosj. energija", + "table_detected_program": "Otkriveni program", + "table_confidence": "Samopouzdanje", + "table_reported": "Prijavljeno", + "phase_builtin_suffix": "(ugrađeno)", + "phase_none_available": "Nema dostupnih faza.", + "settings_deprecation_warning": "⚠️ **Zastarjeli tip uređaja:** {device_type} je zakazano za uklanjanje u budućem izdanju. WashData-ov odgovarajući cevovod ne daje pouzdane rezultate za ovu klasu uređaja. Vaša integracija nastavlja raditi kroz period zastare; da biste utišali ovo upozorenje, prebacite **Tip uređaja** u nastavku na jedan od podržanih tipova (mašina za pranje rublja, mašina za sušenje, kombinovana mašina za pranje i sušenje, mašina za pranje sudova, friteza za vazduh, pekač hleba ili pumpa) ili na **Ostalo (napredno)** ako vaš uređaj ne odgovara nijednom od podržanih tipova. **Ostalo (Napredno)** namerno isporučuje generičke zadane postavke koje nisu podešene za bilo koji određeni uređaj, tako da ćete morati sami da konfigurišete pragove, vremenska ograničenja i odgovarajuće parametre; sva vaša postojeća podešavanja su sačuvana kada se prebacite.", + "phase_other_device_types": "Ostale vrste uređaja:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Podijelite ciklus (Pronađite praznine)", + "merge": "Spajanje ciklusa (spajanje fragmenata)", + "delete": "Izbriši ciklus(e)" + } + }, + "split_mode": { + "options": { + "auto": "Automatski otkrij razmake neaktivnosti", + "manual": "Ručne vremenske oznake" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Održavanje: ponovo obraditi i optimizirati podatke", + "clear_debug_data": "Obriši podatke za otklanjanje grešaka (oslobodite prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovratno)", + "export_import": "Izvezi/uvezi JSON sa postavkama (kopiraj/zalijepi)" + } + }, + "export_import_mode": { + "options": { + "export": "Izvezi sve podatke", + "import": "Uvoz/spajanje podataka" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "select_cycle_to_label": "Oznaci specifican ciklus", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/splitanja", + "trim_cycle": "Podaci ciklusa trim" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Kreirajte novi profil", + "edit_profile": "Uredi/Preimenuj profil", + "delete_profile": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Čišćenje historije - Grafikon i brisanje", + "assign_phases": "Dodijelite raspon faza" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Kreirajte novu fazu", + "edit_custom_phase": "Uredi fazu", + "delete_custom_phase": "Izbriši fazu" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Dodaj fazni opseg", + "edit_range": "Uredi raspon faza", + "delete_range": "Izbriši raspon faza", + "clear_ranges": "Obriši sve opsege", + "auto_detect_ranges": "Automatsko otkrivanje faza", + "save_ranges": "Sačuvaj i vrati" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Osvježi status", + "stop_recording": "Zaustavi snimanje (Sačuvaj i obradi)", + "start_recording": "Započni novo snimanje", + "process_recording": "Obraditi posljednje snimanje (Podreži i sačuvaj)", + "discard_recording": "Odbaci zadnji snimak" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Kreirajte novi profil", + "existing_profile": "Dodaj postojećem profilu", + "discard": "Odbaci snimanje" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potvrdi - ispravna detekcija", + "correct": "Ispravno - Odaberite Program/Trajanje", + "ignore": "Ignoriraj - Lažno pozitivan/bučni ciklus", + "delete": "Izbriši - Ukloni iz istorije" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Imenujte i primenite faze", + "cancel": "Vratite se bez promjena" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Postavite početnu tačku", + "set_end": "Postavite krajnju tačku", + "reset": "Vratite na puno trajanje", + "apply": "Primijenite Trim", + "cancel": "Otkaži" + } + }, + "device_type": { + "options": { + "washing_machine": "Mašina za pranje veša", + "dryer": "Sušilica", + "washer_dryer": "Kombinirana perilica-sušilica", + "dishwasher": "Mašina za pranje sudova", + "coffee_machine": "Aparat za kafu (zastario)", + "ev": "Električno vozilo (zastarjelo)", + "air_fryer": "Friteza na vrući zrak", + "heat_pump": "Toplotna pumpa (zastarjela)", + "bread_maker": "Pekač hljeba", + "pump": "Pumpa / Drenažna pumpa", + "oven": "Pećnica (zastarjela)", + "other": "Ostalo (napredno)" + } + } + }, + "services": { + "label_cycle": { + "name": "Označi ciklus", + "description": "Dodijelite postojeći profil prošlom ciklusu ili uklonite oznaku.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje rublja za označavanje." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za označavanje." + }, + "profile_name": { + "name": "Ime profila", + "description": "Naziv postojećeg profila (kreirajte profile u meniju Upravljanje profilima). Ostavite prazno da uklonite naljepnicu." + } + } + }, + "create_profile": { + "name": "Kreirajte profil", + "description": "Kreirajte novi profil (samostalan ili zasnovan na referentnom ciklusu).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje veša." + }, + "profile_name": { + "name": "Ime profila", + "description": "Naziv za novi profil (npr. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "ID referentnog ciklusa", + "description": "Opcioni ID ciklusa za zasnivanje ovog profila." + } + } + }, + "delete_profile": { + "name": "Izbriši profil", + "description": "Izbrišite profil i opciono poništite označavanje ciklusa koji ga koriste.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje veša." + }, + "profile_name": { + "name": "Ime profila", + "description": "Profil za brisanje." + }, + "unlabel_cycles": { + "name": "Ukloni oznaku ciklusa", + "description": "Uklonite oznaku profila iz ciklusa koristeći ovaj profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatski označi stare cikluse", + "description": "Retroaktivno označite neoznačene cikluse koristeći podudaranje profila.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje veša." + }, + "confidence_threshold": { + "name": "Prag povjerenja", + "description": "Minimalna pouzdanost podudaranja (0,50-0,95) za primjenu oznaka." + } + } + }, + "export_config": { + "name": "Izvoz konfig", + "description": "Izvezite profile, cikluse i postavke ovog uređaja u JSON datoteku (po uređaju).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje rublja za izvoz." + }, + "path": { + "name": "Put", + "description": "Opciona apsolutna putanja fajla za pisanje (podrazumevano na /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Uvezi konfiguraciju", + "description": "Uvezite profile, cikluse i postavke za ovaj uređaj iz JSON datoteke za izvoz (postavke mogu biti prepisane).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za veš mašinu za uvoz." + }, + "path": { + "name": "Put", + "description": "Apsolutna putanja do JSON datoteke za izvoz za uvoz." + } + } + }, + "submit_cycle_feedback": { + "name": "Pošaljite povratne informacije o ciklusu", + "description": "Potvrdite ili ispravite automatski otkriveni program nakon završenog ciklusa. Navedite ili `entry_id` (napredno) ili `device_id` (preporučeno).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData (preporučuje se umjesto entry_id)." + }, + "entry_id": { + "name": "ID unosa", + "description": "ID unosa konfiguracije za uređaj (alternativa device_id)." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa prikazan u obavještenju o povratnim informacijama/logovima." + }, + "user_confirmed": { + "name": "Potvrdite otkriveni program", + "description": "Postavite true ako je otkriveni program ispravan." + }, + "corrected_profile": { + "name": "Ispravljen profil", + "description": "Ako nije potvrđeno, navedite ispravan naziv profila/programa." + }, + "corrected_duration": { + "name": "Ispravljeno trajanje (sekunde)", + "description": "Opciono ispravljeno trajanje u sekundama." + }, + "notes": { + "name": "Bilješke", + "description": "Opcione napomene o ovom ciklusu." + } + } + }, + "record_start": { + "name": "Početak ciklusa snimanja", + "description": "Počnite ručno snimati čisti ciklus (zaobilazi svu logiku podudaranja).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za snimanje perilice rublja." + } + } + }, + "record_stop": { + "name": "Zaustavljanje ciklusa snimanja", + "description": "Zaustavite ručno snimanje.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje veša za zaustavljanje snimanja." + } + } + }, + "trim_cycle": { + "name": "Podrezivanje ciklusa", + "description": "Skratite podatke o snazi ​​prošlog ciklusa na određeni vremenski prozor.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za skraćivanje." + }, + "trim_start_s": { + "name": "Skratiti početak (sekunde)", + "description": "Sačuvajte podatke od ovog pomaka u sekundama. Podrazumevano 0." + }, + "trim_end_s": { + "name": "Kraj obrezivanja (sekunde)", + "description": "Zadržite podatke do ovog pomaka u sekundama. Zadano = puno trajanje." + } + } + }, + "pause_cycle": { + "name": "Pauziraj ciklus", + "description": "Pauzirajte aktivni ciklus za WashData uređaj.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za pauziranje." + } + } + }, + "resume_cycle": { + "name": "Nastavi ciklus", + "description": "Nastavite pauzirani ciklus za WashData uređaj.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za nastavak." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Pokrenuto" + }, + "match_ambiguity": { + "name": "Dvosmislenost podudaranja" + } + }, + "sensor": { + "washer_state": { + "name": "Stanje", + "state": { + "off": "Isključeno", + "idle": "Mirovanje", + "starting": "Počinje", + "running": "Pokrenuto", + "paused": "Pauzirano", + "user_paused": "Pauzirano od strane korisnika", + "ending": "Kraj", + "finished": "Završeno", + "anti_wrinkle": "Anti-gužvanje", + "delay_wait": "Čeka se početak", + "interrupted": "Prekinut", + "force_stopped": "Prisilno zaustavljeno", + "rinse": "Isperite", + "unknown": "Nepoznato", + "clean": "Čisto" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Preostalo vrijeme" + }, + "total_duration": { + "name": "Ukupno trajanje" + }, + "cycle_progress": { + "name": "Napredak" + }, + "current_power": { + "name": "Trenutna snaga" + }, + "elapsed_time": { + "name": "Proteklo vrijeme" + }, + "debug_info": { + "name": "Informacije o otklanjanju grešaka" + }, + "match_confidence": { + "name": "Pouzdanost podudaranja" + }, + "top_candidates": { + "name": "Najbolji kandidati", + "state": { + "none": "Nema" + } + }, + "current_phase": { + "name": "Trenutna faza" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Broj profila {profile_name}", + "unit_of_measurement": "ciklusa" + }, + "suggestions": { + "name": "Predložene postavke" + }, + "pump_runs_today": { + "name": "Pumpa radi (posljednja 24 h)" + }, + "cycle_count": { + "name": "Broj ciklusa" + } + }, + "select": { + "program_select": { + "name": "Program ciklusa", + "state": { + "auto_detect": "Automatsko otkrivanje" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Prisilno završi ciklus" + }, + "pause_cycle": { + "name": "Pauziraj ciklus" + }, + "resume_cycle": { + "name": "Nastavi ciklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Potreban je važeći device_id." + }, + "cycle_id_required": { + "message": "Potreban je važeći cycle_id." + }, + "profile_name_required": { + "message": "Potrebno je važeće profile_name." + }, + "device_not_found": { + "message": "Uređaj nije pronađen." + }, + "no_config_entry": { + "message": "Nije pronađen unos konfiguracije za uređaj." + }, + "integration_not_loaded": { + "message": "Integracija nije učitana za ovaj uređaj." + }, + "cycle_not_found_or_no_power": { + "message": "Ciklus nije pronađen ili nema podataka o snazi." + }, + "trim_failed_empty_window": { + "message": "Trim nije uspio - ciklus nije pronađen, nema podataka o napajanju ili je rezultirajući prozor prazan." + }, + "trim_invalid_range": { + "message": "trim_end_s mora biti veći od trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ca.json b/custom_components/ha_washdata/translations/ca.json new file mode 100644 index 0000000..92d3ae3 --- /dev/null +++ b/custom_components/ha_washdata/translations/ca.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuració de WashData", + "description": "Configura la teva rentadora o un altre aparell.\n\nCal un sensor de potència.\n\n**A continuació, se us demanarà si voleu crear el vostre primer perfil.**", + "data": { + "name": "Nom del dispositiu", + "device_type": "Tipus de dispositiu", + "power_sensor": "Sensor de potència", + "min_power": "Llindar de potència mínima (W)" + }, + "data_description": { + "name": "Un nom senzill per a aquest dispositiu (p. ex., \"Rentadora\", \"Rentaplats\").", + "device_type": "Quin tipus d'aparell és aquest? Ajuda a personalitzar la detecció i l'etiquetatge.", + "power_sensor": "L'entitat sensora que informa del consum d'energia en temps real (en watts) des del vostre connector intel·ligent.", + "min_power": "Les lectures de potència per sobre d'aquest llindar (en watts) indiquen que l'aparell està en funcionament. Comenceu amb 2 W per a la majoria de dispositius." + } + }, + "first_profile": { + "title": "Crea el primer perfil", + "description": "Un **cicle** és una execució completa del vostre electrodomèstic (p. ex., una càrrega de roba). Un **perfil** agrupa aquests cicles per tipus (p. ex., \"Cotó\", \"rentat ràpid\"). La creació d'un perfil ara ajuda a estimar la durada de la integració immediatament.\n\nPodeu seleccionar aquest perfil manualment als controls del dispositiu si no es detecta automàticament.\n\n**Deixa el \"Nom del perfil\" en blanc per ometre aquest pas.**", + "data": { + "profile_name": "Nom del perfil (opcional)", + "manual_duration": "Durada estimada (minuts)" + } + } + }, + "error": { + "cannot_connect": "No s'ha pogut connectar", + "invalid_auth": "Autenticació no vàlida", + "unknown": "Error inesperat" + }, + "abort": { + "already_configured": "El dispositiu ja està configurat" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revisar els comentaris d'aprenentatge", + "settings": "Configuració", + "notifications": "Notificacions", + "manage_cycles": "Gestionar cicles", + "manage_profiles": "Gestionar perfils", + "manage_phase_catalog": "Gestionar el catàleg de fases", + "record_cycle": "Cicle de gravació (manual)", + "diagnostics": "Diagnòstic i Manteniment" + } + }, + "apply_suggestions": { + "title": "Copia els valors suggerits", + "description": "Això copiarà els valors actuals suggerits a la configuració desada. Aquesta és una acció única i no activa la sobreescritura automàtica.\n\nValors suggerits per copiar:\n{suggested}", + "data": { + "confirm": "Confirmeu l'acció de còpia" + } + }, + "apply_suggestions_confirm": { + "title": "Reviseu els canvis suggerits", + "description": "WashData ha trobat **{pending_count}** valors suggerits per aplicar.\n\nCanvis:\n{changes}\n\n✅ Marqueu la casella de sota i feu clic a **Envia** per aplicar i desar aquests canvis immediatament.\n❌ Deixeu-lo sense marcar i feu clic a **Envia** per cancel·lar-lo i tornar enrere.", + "data": { + "confirm_apply_suggestions": "Apliqueu i deseu aquests canvis" + } + }, + "settings": { + "title": "Configuració", + "description": "{deprecation_warning}Ajusta els llindars de detecció, l'aprenentatge i el comportament avançat. Els valors predeterminats funcionen bé per a la majoria de configuracions.\n\nConfiguració suggerida disponible actualment: {suggestions_count}.\nSi és superior a 0, obre la Configuració avançada i usa 'Aplica els valors suggerits' per revisar les recomanacions.", + "data": { + "apply_suggestions": "Aplicar els valors suggerits", + "device_type": "Tipus de dispositiu", + "power_sensor": "Entitat del sensor de potència", + "min_power": "Potència mínima (W)", + "off_delay": "Retard de finalització de cicle (s)", + "notify_actions": "Accions de notificació", + "notify_people": "Endarrerir el lliurament fins que aquestes persones siguin a casa", + "notify_only_when_home": "Retarda les notificacions fins que la persona seleccionada sigui a casa", + "notify_fire_events": "Activar esdeveniments d'automatització", + "notify_start_services": "Inici del cicle - Objectius de notificació", + "notify_finish_services": "Finalització del cicle: objectius de notificació", + "notify_live_services": "Progrés en directe: objectius de notificació", + "notify_before_end_minutes": "Notificació prèvia a la finalització (minuts abans del final)", + "notify_title": "Títol de la notificació", + "notify_icon": "Icona de notificació", + "notify_start_message": "Inicieu el format del missatge", + "notify_finish_message": "Finalitza el format del missatge", + "notify_pre_complete_message": "Format de missatge previ a la finalització", + "show_advanced": "Edita la configuració avançada (pas següent)" + }, + "data_description": { + "apply_suggestions": "Marqueu aquesta casella per copiar els valors suggerits per l'algoritme d'aprenentatge al formulari. Revisa abans de desar.\n\nSuggerit (de l'aprenentatge):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Seleccioneu el tipus d'aparell (rentadora, assecadora, rentavaixelles, cafetera, EV). Aquesta etiqueta s'emmagatzema amb cada cicle per a una millor organització.", + "power_sensor": "L'entitat sensora que informa de la potència en temps real (en watts). Podeu canviar-ho en qualsevol moment sense perdre les dades històriques; és útil si substituïu un connector intel·ligent.", + "min_power": "Les lectures de potència per sobre d'aquest llindar (en watts) indiquen que l'aparell està en funcionament. Comenceu amb 2 W per a la majoria de dispositius.", + "off_delay": "En segons, la potència suavitzada s'ha de mantenir per sota del llindar d'aturada per marcar la finalització. Per defecte: 120 s.", + "notify_actions": "Seqüència d'accions de Home Assistant opcional per executar-se per a les notificacions (scripts, notificació, TTS, etc.). Si s'estableix, les accions s'executen abans del servei de notificació alternativa.", + "notify_people": "Entitats de persones que s'utilitzen per al control de presència. Quan està activat, l'entrega de notificacions es retarda fins que almenys una persona seleccionada és a casa.", + "notify_only_when_home": "Si està activat, retarda les notificacions fins que almenys una persona seleccionada sigui a casa.", + "notify_fire_events": "Si està activat, activa els esdeveniments de Home Assistant per a l'inici i el final del cicle (ha_washdata_cycle_started i ha_washdata_cycle_ended) per impulsar les automatitzacions.", + "notify_start_services": "Un o més serveis de notificació per avisar quan comença un cicle. Deixa en blanc per saltar les notificacions d'inici.", + "notify_finish_services": "Un o més serveis de notificació per avisar quan un cicle acaba o s'apropa a la seva finalització. Deixa en blanc per saltar les notificacions de finalització.", + "notify_live_services": "Un o més serveis de notificació per rebre actualitzacions de progrés en directe mentre s'executa el cicle (només l'aplicació mòbil complementària). Deixeu el camp buit per saltar les actualitzacions en directe.", + "notify_before_end_minutes": "Minuts abans del final estimat del cicle per activar una notificació. Establiu a 0 per desactivar. Per defecte: 0.", + "notify_title": "Títol personalitzat per a les notificacions. Admet {device} marcador de posició.", + "notify_icon": "Icona per utilitzar per a les notificacions (p. ex. mdi:washing-machine). Deixeu-lo en blanc per enviar sense icona.", + "notify_start_message": "Missatge enviat quan comença un cicle. Admet {device} marcador de posició.", + "notify_finish_message": "Missatge enviat quan acaba un cicle. Admet marcadors de posició {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "El text de les actualitzacions recurrents de progrés en directe mentre s'executa el cicle. Admet marcadors de posició {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificacions", + "description": "Configura canals de notificació, plantilles i actualitzacions opcionals de progrés mòbils en directe.", + "data": { + "notify_actions": "Accions de notificació", + "notify_people": "Endarrerir el lliurament fins que aquestes persones siguin a casa", + "notify_only_when_home": "Retarda les notificacions fins que la persona seleccionada sigui a casa", + "notify_fire_events": "Activar esdeveniments d'automatització", + "notify_start_services": "Inici del cicle - Objectius de notificació", + "notify_finish_services": "Finalització del cicle: objectius de notificació", + "notify_live_services": "Progrés en directe: objectius de notificació", + "notify_before_end_minutes": "Notificació prèvia a la finalització (minuts abans del final)", + "notify_live_interval_seconds": "Interval d'actualització en directe (segons)", + "notify_live_overrun_percent": "Marge de seguretat per a sobrecàrrega d'actualitzacions en directe (%)", + "notify_live_chronometer": "Temporitzador de compte enrere del cronòmetre", + "notify_title": "Títol de la notificació", + "notify_icon": "Icona de notificació", + "notify_start_message": "Inicieu el format del missatge", + "notify_finish_message": "Finalitza el format del missatge", + "notify_pre_complete_message": "Format de missatge previ a la finalització", + "energy_price_entity": "Preu de l'energia - Entitat (opcional)", + "energy_price_static": "Preu de l'energia: valor estàtic (opcional)", + "go_back": "Torna enrere sense desar", + "notify_reminder_message": "Format de missatge de recordatori", + "notify_timeout_seconds": "Auto-ismes després (segons)", + "notify_channel": "Canal de notificació (estat/ viu/reminder)", + "notify_finish_channel": "Canal de notificació finalitzada" + }, + "data_description": { + "go_back": "Marqueu això i feu clic a Envia per descartar qualsevol canvi anterior i tornar al menú principal.", + "notify_actions": "Seqüència d'accions de Home Assistant opcional per executar-se per a les notificacions (scripts, notificació, TTS, etc.). Si s'estableix, les accions s'executen abans del servei de notificació alternativa.", + "notify_people": "Entitats de persones que s'utilitzen per al control de presència. Quan està activat, l'entrega de notificacions es retarda fins que almenys una persona seleccionada és a casa.", + "notify_only_when_home": "Si està activat, retarda les notificacions fins que almenys una persona seleccionada sigui a casa.", + "notify_fire_events": "Si està activat, activa els esdeveniments de Home Assistant per a l'inici i el final del cicle (ha_washdata_cycle_started i ha_washdata_cycle_ended) per impulsar les automatitzacions.", + "notify_start_services": "Un o més serveis de notificació per avisar quan comença un cicle (p. ex., notify.mobile_app_my_phone). Deixa en blanc per saltar les notificacions d'inici.", + "notify_finish_services": "Un o més serveis de notificació per avisar quan un cicle acaba o s'apropa a la seva finalització. Deixa en blanc per saltar les notificacions de finalització.", + "notify_live_services": "Un o més serveis de notificació per rebre actualitzacions de progrés en directe mentre s'executa el cicle (només l'aplicació mòbil complementària). Deixeu el camp buit per saltar les actualitzacions en directe.", + "notify_before_end_minutes": "Minuts abans del final estimat del cicle per activar una notificació. Establiu a 0 per desactivar. Per defecte: 0.", + "notify_live_interval_seconds": "Amb quina freqüència s'envien les actualitzacions de progrés mentre s'executen. Els valors més baixos són més sensibles, però consumeixen més notificacions. S'aplica un mínim de 30 segons: els valors inferiors a 30 s s'arrodoneixen automàticament a 30 s.", + "notify_live_overrun_percent": "Marge de seguretat per sobre del recompte d'actualitzacions estimat per a cicles llargs/excedits (per exemple, el 20% permet actualitzacions 1,2 vegades estimades).", + "notify_live_chronometer": "Quan està activat, cada actualització en directe inclou un compte enrere del cronòmetre fins a l'hora d'acabament estimada. El temporitzador de notificacions s'activa automàticament al dispositiu entre les actualitzacions (només l'aplicació complementària d'Android).", + "notify_title": "Títol personalitzat per a les notificacions. Admet {device} marcador de posició.", + "notify_icon": "Icona per utilitzar per a les notificacions (p. ex. mdi:washing-machine). Deixeu-lo en blanc per enviar sense icona.", + "notify_start_message": "Missatge enviat quan comença un cicle. Admet {device} marcador de posició.", + "notify_finish_message": "Missatge enviat quan acaba un cicle. Admet marcadors de posició {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Seleccioneu una entitat numèrica que contingui el preu actual de l'electricitat (p. ex., sensor.electricity_price, input_number.energy_rate). Quan s'estableix, activa el marcador de posició {cost}. Té prioritat sobre el valor estàtic si tots dos estan configurats.", + "energy_price_static": "Preu fix de l'electricitat per kWh. Quan s'estableix, activa el marcador de posició {cost}. S'ignora si una entitat està configurada més amunt. Afegiu el vostre símbol de moneda a la plantilla del missatge, p. ex. {cost} €.", + "notify_reminder_message": "Text del recordatori d' una vegada enviat el nombre de minuts configurats abans del final estimat. Discinta des de les actualitzacions de la vida recurrent a dalt. Implementació {device}, {minutes}, variable {program}.", + "notify_timeout_seconds": "Mostra automàticament les notificacions després d' aquests molts segons (reprodueix- les com a 'timeout' del company). Posa a 0 per a mantenir-los fins que es desconnectin. Omissió: 0.", + "notify_channel": "Canal de notificació Android per a l' estat, progrés i notificacions de recordatori. El so i la importància d' un canal estan configurats en l' aplicació del company la primera vegada que s' usa el nom del canal - RenshData només estableix el nom del canal. Deixa buit per omissió l' aplicació.", + "notify_finish_channel": "Separa el canal Android per a l' alerta finalitzada (també usat pel recordatori i l' expulsió de la roba) per tal que pugui tenir el seu propi so. Deixa buit per a tornar a utilitzar el canal de dalt.", + "notify_pre_complete_message": "El text de les actualitzacions recurrents de progrés en directe mentre s'executa el cicle. Admet marcadors de posició {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Configuració avançada", + "description": "Ajusta els llindars de detecció, l'aprenentatge i el comportament avançat. Els valors predeterminats funcionen bé per a la majoria de configuracions.", + "sections": { + "suggestions_section": { + "name": "Configuració suggerida", + "description": "{suggestions_count} suggeriments apresos disponibles. Marqueu Aplica els valors suggerits per revisar els canvis proposats abans de desar.", + "data": { + "apply_suggestions": "Aplicar els valors suggerits" + }, + "data_description": { + "apply_suggestions": "Marqueu aquesta casella per obrir un pas de revisió dels valors suggerits de l'algoritme d'aprenentatge. No es desa res automàticament.\n\nSuggerit (de l'aprenentatge):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detecció i llindars de potència", + "description": "Quan l'aparell es considera ENCÈS o APAGAT, llindars d'energia i temporització per a les transicions d'estat.", + "data": { + "start_duration_threshold": "Durada d'inici de rebot (s)", + "start_energy_threshold": "Inici Energy Gate (Wh)", + "completion_min_seconds": "Temps d'execució mínim de finalització (segons)", + "start_threshold_w": "Llindar inicial (W)", + "stop_threshold_w": "Llindar d'aturada (W)", + "end_energy_threshold": "Porta d'energia final (Wh)", + "running_dead_zone": "Running Dead Zone (segons)", + "end_repeat_count": "Finalitza el recompte de repeticions", + "min_off_gap": "Bretxa mínima entre cicles (s)", + "sampling_interval": "Interval (s) de mostreig" + }, + "data_description": { + "start_duration_threshold": "Ignoreu pics de potència breus més curts que aquesta durada (segons). Filtra els pics d'arrencada (p. ex., 60 W durant 2 segons) abans que comenci el cicle real. Per defecte: 5 s.", + "start_energy_threshold": "El cicle ha d'acumular almenys aquesta quantitat d'energia (Wh) durant la fase d'arrencada per confirmar-se. Per defecte: 0,005 Wh.", + "completion_min_seconds": "Els cicles més curts que aquest es marcaran com a \"interromputs\", encara que acabin de manera natural. Per defecte: 600 s.", + "start_threshold_w": "La potència ha de pujar per sobre d'aquest llindar per iniciar un cicle. Estableix una potència superior a la teva en espera. Per defecte: min_power + 1 W.", + "stop_threshold_w": "L'energia ha de baixar per sota d'aquest llindar per FINALITZAR un cicle. Establiu just per sobre de zero per evitar que el soroll provoqui finals falsos. Per defecte: 0,6 * min_power.", + "end_energy_threshold": "El cicle acaba només si l'energia de l'última finestra de retard d'apagada està per sota d'aquest valor (Wh). Impedeix finals falsos si la potència fluctua prop de zero. Per defecte: 0,05 Wh.", + "running_dead_zone": "Després d'iniciar el cicle, ignoreu les caigudes de potència durant aquests segons per evitar la detecció de finals falsos durant la fase inicial d'engegada. Establiu a 0 per desactivar. Per defecte: 0 s.", + "end_repeat_count": "Nombre de vegades que la condició final (potència per sota del llindar per off_delay) s'ha de complir consecutivament abans d'acabar el cicle. Els valors més alts eviten finals falsos durant les pauses. Per defecte: 1.", + "min_off_gap": "Si un cicle s'inicia en tants segons des de la finalització de l'anterior, es FUSIONARÀ en un sol cicle.", + "sampling_interval": "Interval mínim de mostreig en segons. Les actualitzacions més ràpides que aquesta velocitat s'ignoraran. Per defecte: 30 s (2 s per a rentadores, rentadores-assecadores i rentavaixelles)." + } + }, + "matching_section": { + "name": "Coincidència de perfils i aprenentatge", + "description": "Com d'agressivament WashData fa coincidir els cicles en execució amb els perfils apresos i quan demanar comentaris.", + "data": { + "profile_match_min_duration_ratio": "Relació de durada mínima de la coincidència del perfil (0,1-1,0)", + "profile_match_interval": "Interval de concordança del perfil (segons)", + "profile_match_threshold": "Llindar de concordança del perfil", + "profile_unmatch_threshold": "Llindar de no coincidència del perfil", + "auto_label_confidence": "Confiança de l'etiqueta automàtica (0-1)", + "learning_confidence": "Confiança de la sol·licitud de comentaris (0-1)", + "suppress_feedback_notifications": "Suprimeix les notificacions de comentaris", + "duration_tolerance": "Tolerància a la durada (0-0,5)", + "smoothing_window": "Finestra de suavització (mostres)", + "profile_duration_tolerance": "Tolerància a la durada de la coincidència del perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Relació de durada mínima per a la concordança del perfil. El cicle d'execució ha de ser com a mínim aquesta fracció de la durada del perfil perquè coincideixi. Inferior = coincidència anterior. Per defecte: 0,50 (50%).", + "profile_match_interval": "Amb quina freqüència (en segons) intentar la concordança del perfil durant un cicle. Els valors més baixos proporcionen una detecció més ràpida, però utilitzen més CPU. Per defecte: 300 s (5 minuts).", + "profile_match_threshold": "La puntuació mínima de similitud DTW (0,0–1,0) necessària per considerar un perfil com a coincidència. Major = concordança més estricta, menys falsos positius. Per defecte: 0,4.", + "profile_unmatch_threshold": "Puntuació de similitud DTW (0,0–1,0) per sota de la qual es rebutja un perfil coincident anteriorment. Hauria de ser inferior al llindar de coincidència per evitar el parpelleig. Per defecte: 0,35.", + "auto_label_confidence": "Etiqueteu automàticament els cicles amb aquesta confiança o per sobre en finalitzar (0,0-1,0).", + "learning_confidence": "Confiança mínima per sol·licitar la verificació de l'usuari mitjançant un esdeveniment (0,0–1,0).", + "suppress_feedback_notifications": "Quan està activat, WashData seguirà fent un seguiment i sol·licitant comentaris internament, però no mostrarà notificacions persistents que us demanin que confirmeu els cicles. Útil quan els cicles sempre es detecten correctament i trobeu que les indicacions distreuen.", + "duration_tolerance": "Tolerància per a les estimacions del temps restant durant una carrera (0,0-0,5 correspon al 0-50%).", + "smoothing_window": "Nombre de lectures de potència recents utilitzades per a la suavització de mitjana mòbil.", + "profile_duration_tolerance": "Tolerància utilitzada per la concordança de perfils per a la variació de la durada total (0,0-0,5). Comportament per defecte: ±25%." + } + }, + "timing_section": { + "name": "Temporització, manteniment i depuració", + "description": "Watchdog, reinici del progrés, manteniment automàtic i exposició d'entitats de depuració.", + "data": { + "watchdog_interval": "Interval de control (segons)", + "no_update_active_timeout": "Temps d'espera sense actualització (s)", + "progress_reset_delay": "Retard de restabliment del progrés (segons)", + "auto_maintenance": "Activa el manteniment automàtic", + "expose_debug_entities": "Exposa entitats de depuració", + "save_debug_traces": "Desa les traces de depuració" + }, + "data_description": { + "watchdog_interval": "Segons entre comprovacions del gos vigilant mentre s'executa. Per defecte: 5 s. ADVERTÈNCIA: assegureu-vos que sigui SUPERIOR que l'interval d'actualització del vostre sensor per evitar falses parades (p. ex., si el sensor s'actualitza cada 60 s, utilitzeu 65 s o més).", + "no_update_active_timeout": "Per als sensors de publicació al canvi: només s'ha de forçar la finalització d'un cicle ACTIV si no arriben actualitzacions durant tants segons. La finalització de baix consum encara utilitza el retard d'apagat.", + "progress_reset_delay": "Un cop finalitzat un cicle (100%), espereu aquests segons d'inactivitat abans de restablir el progrés al 0%. Per defecte: 300 s.", + "auto_maintenance": "Habiliteu el manteniment automàtic (repareu mostres, fusioneu fragments).", + "expose_debug_entities": "Mostra sensors avançats (confiança, fase, ambigüitat) per a la depuració.", + "save_debug_traces": "Emmagatzema dades detallades de classificació i traça de potència a l'historial (augmenta l'ús d'emmagatzematge)." + } + }, + "anti_wrinkle_section": { + "name": "Escut antiarrugues (assecadores)", + "description": "Ignora les rotacions del tambor després del cicle per evitar cicles fantasma. Només assecadora / rentadora-assecadora.", + "data": { + "anti_wrinkle_enabled": "Escut antiarrugues (només assecador)", + "anti_wrinkle_max_power": "Potència màxima antiarrugues (W)", + "anti_wrinkle_max_duration": "Durada màxima anti-arrugues (s)", + "anti_wrinkle_exit_power": "Potència de sortida anti-arrugues (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignoreu les rotacions curtes de tambor de baixa potència després d'acabar un cicle per evitar cicles fantasma (només assecadora/rentadora-assecadora).", + "anti_wrinkle_max_power": "Les ràfegues amb aquesta potència o per sota es tracten com a rotacions anti-arrugues en lloc de nous cicles. Només s'aplica quan l'escut antiarrugues està activat.", + "anti_wrinkle_max_duration": "Longitud màxima de l'esclat per tractar-se com a rotació anti-arrugues. Només s'aplica quan l'escut antiarrugues està activat.", + "anti_wrinkle_exit_power": "Si la potència cau per sota d'aquest nivell, sortiu de l'antiarrugues immediatament (apagat veritablement). Només s'aplica quan l'escut antiarrugues està activat." + } + }, + "delay_start_section": { + "name": "Detecció d'inici diferit", + "description": "Tracta l'espera sostinguda de baixa potència com a 'En espera per començar' fins que el cicle comenci realment.", + "data": { + "delay_start_detect_enabled": "Activa la detecció d'inici retardat", + "delay_confirm_seconds": "Inici diferit: temps de confirmació d'espera (s)", + "delay_timeout_hours": "Inici retardat: temps d'espera màxim (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Quan està activat, es detecta un augment de drenatge breu de baixa potència seguit d'una potència en espera sostinguda com a inici retardat. El sensor d'estat mostra \"Esperant per començar\" durant el retard. No es fa un seguiment del temps ni del progrés del programa fins que el cicle comença realment. Requereix que start_threshold_w s'estableixi per sobre de la potència del pic de drenatge.", + "delay_confirm_seconds": "La potència ha de romandre entre el Llindar d'aturada (W) i el Llindar d'inici (W) durant aquest nombre de segons abans que WashData entri a 'En espera per començar'. Filtra els pics breus de navegació pels menús. Per defecte: 60 s.", + "delay_timeout_hours": "Temps d'espera de seguretat: si la màquina encara està en \"Esperant per iniciar\" després d'aquestes moltes hores, WashData es restableix a Off. Per defecte: 8 h." + } + }, + "external_triggers_section": { + "name": "Activadors externs, porta i pausa", + "description": "Sensor extern opcional de fi de cicle, sensor de porta per a la detecció de pausa/net i interruptor de tall d'alimentació en pausar.", + "data": { + "external_end_trigger_enabled": "Activa l'activador de final de cicle extern", + "external_end_trigger": "Sensor de final de cicle extern", + "external_end_trigger_inverted": "Inverteix la lògica de l'activació (disparador desactivat)", + "door_sensor_entity": "Sensor de porta", + "pause_cuts_power": "Talleu l'alimentació quan feu una pausa", + "switch_entity": "Canvia d'entitat (per a la pausa del tall d'alimentació)", + "notify_unload_delay_minutes": "Retard de notificació d'espera de bugaderia (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Habiliteu la supervisió d'un sensor binari extern per activar la finalització del cicle.", + "external_end_trigger": "Seleccioneu una entitat de sensor binari. Quan aquest sensor s'activa, el cicle actual finalitzarà amb l'estat \"complet\".", + "external_end_trigger_inverted": "De manera predeterminada, el disparador s'activa quan el sensor s'activa. Marqueu aquesta casella per activar-se quan el sensor s'apagui.", + "door_sensor_entity": "Sensor binari opcional per a la porta de la màquina (encesa = oberta, apagada = tancada). Quan la porta s'obre durant un cicle actiu, WashData confirmarà el cicle com a pausa intencionada. Un cop finalitza el cicle, si la porta encara està tancada, l'estat canvia a \"Neteja\" fins que la porta s'obre. Nota: el tancament de la porta NO reprèn automàticament un cicle en pausa; el reinici s'ha d'activar manualment mitjançant el botó o el servei Reprèn el cicle.", + "pause_cuts_power": "Quan feu una pausa mitjançant el botó o el servei de pausa del cicle, també desactiveu l'entitat de commutació configurada. Només té efecte si es configura una entitat de commutació per a aquest dispositiu.", + "switch_entity": "L'entitat de commutació per alternar quan s'atura o es reprèn. Necessari quan l'opció \"Tallar l'alimentació en pausa\" està activada.", + "notify_unload_delay_minutes": "Envieu una notificació a través del canal de notificació d'acabament un cop finalitzi el cicle i la porta no s'hagi obert durant tants minuts (requereix el sensor de la porta). Establiu a 0 per desactivar. Per defecte: 60 min." + } + }, + "pump_section": { + "name": "Monitor de bomba", + "description": "Només per al tipus de dispositiu bomba. Dispara un esdeveniment si un cicle de bomba supera aquesta durada.", + "data": { + "pump_stuck_duration": "Llindar (s) d'alerta de bomba bloquejada (només bomba)" + }, + "data_description": { + "pump_stuck_duration": "Si un cicle de bomba encara s'està executant després d'aquests segons, s'activa un esdeveniment ha_washdata_pump_stuck. Utilitzeu-ho per detectar un motor encallat (el funcionament típic de la bomba de carter és inferior a 60 s). Per defecte: 1800 s (30 min). Només tipus de dispositiu de bomba." + } + }, + "device_link_section": { + "name": "Enllaç de dispositiu", + "description": "Enllaç opcional aquest dispositiu RenshData a un dispositiu Home Assistant existent (per exemple el connector intel· ligent o el mateix aparell). El dispositiu RenshData es mostra llavors com \"Connectat via\" aquest dispositiu. Deixa buit per mantenir RenshData com a un dispositiu independent.", + "data": { + "linked_device": "Dispositiu enllaçat" + }, + "data_description": { + "linked_device": "Seleccioneu el dispositiu a on connectar RenshData. Això afegeix una 'Connectada via' referència al registre del dispositiu; RenshData manté la seva pròpia targeta de dispositiu i entitats. Neteja la selecció per eliminar l' enllaç." + } + } + } + }, + "diagnostics": { + "title": "Diagnòstic i Manteniment", + "description": "Executeu accions de manteniment, com ara combinar cicles fragmentats o migrar les dades emmagatzemades.\n\n**Ús d'emmagatzematge**\n\n- Mida del fitxer: {file_size_kb} KB\n- Cicles: {cycle_count}\n- Perfils: {profile_count}\n- Traces de depuració: {debug_count}", + "menu_options": { + "reprocess_history": "Manteniment: reprocessament i optimització de dades", + "clear_debug_data": "Esborra les dades de depuració (allibera espai)", + "wipe_history": "Esborra TOTES les dades d'aquest dispositiu (irreversible)", + "export_import": "Exportar/Importar JSON amb la configuració (copiar/enganxar)", + "menu_back": "← Enrere" + } + }, + "clear_debug_data": { + "title": "Esborra les dades de depuració", + "description": "Esteu segur que voleu suprimir tots els rastres de depuració emmagatzemats? Això alliberarà espai però eliminarà la informació detallada de la classificació dels cicles anteriors." + }, + "manage_cycles": { + "title": "Gestionar cicles", + "description": "Cicles recents:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etiqueta automàtica de cicles antics", + "select_cycle_to_label": "Cicle específic de l'etiqueta", + "select_cycle_to_delete": "Suprimeix el cicle", + "interactive_editor": "Combinar/dividir Editor interactiu", + "trim_cycle_select": "Dades del cicle de retallada", + "menu_back": "← Enrere" + } + }, + "manage_cycles_empty": { + "title": "Gestionar cicles", + "description": "Encara no s'han registrat cicles.", + "menu_options": { + "auto_label_cycles": "Etiqueta automàtica de cicles antics", + "menu_back": "← Enrere" + } + }, + "manage_profiles": { + "title": "Gestionar perfils", + "description": "Resum del perfil:\n{profile_summary}", + "menu_options": { + "create_profile": "Crea un perfil nou", + "edit_profile": "Edita/canvia el nom del perfil", + "delete_profile_select": "Suprimeix el perfil", + "profile_stats": "Estadístiques de perfil", + "cleanup_profile": "Neteja l'historial: gràfic i suprimir", + "assign_profile_phases_select": "Assignar intervals de fase", + "menu_back": "← Enrere" + } + }, + "manage_profiles_empty": { + "title": "Gestionar perfils", + "description": "Encara no s'ha creat cap perfil.", + "menu_options": { + "create_profile": "Crea un perfil nou", + "menu_back": "← Enrere" + } + }, + "manage_phase_catalog": { + "title": "Gestionar el catàleg de fases", + "description": "Catàleg de fases (valors per defecte per a aquest dispositiu + fases personalitzades):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Crea una nova fase", + "phase_catalog_edit_select": "Fase d'edició", + "phase_catalog_delete": "Suprimeix la fase", + "menu_back": "← Enrere" + } + }, + "phase_catalog_create": { + "title": "Crea una fase personalitzada", + "description": "Creeu un nom i una descripció de fase personalitzats i trieu a quin tipus de dispositiu s'aplica.", + "data": { + "target_device_type": "Tipus de dispositiu objectiu", + "phase_name": "Nom de la fase", + "phase_description": "Descripció de la fase" + } + }, + "phase_catalog_edit_select": { + "title": "Edita la fase personalitzada", + "description": "Seleccioneu una fase personalitzada per editar.", + "data": { + "phase_name": "Fase personalitzada" + } + }, + "phase_catalog_edit": { + "title": "Edita la fase personalitzada", + "description": "Fase d'edició: {phase_name}", + "data": { + "phase_name": "Nom de la fase", + "phase_description": "Descripció de la fase" + } + }, + "phase_catalog_delete": { + "title": "Suprimeix la fase personalitzada", + "description": "Seleccioneu una fase personalitzada per suprimir. Tots els intervals assignats que utilitzin aquesta fase s'eliminaran.", + "data": { + "phase_name": "Fase personalitzada" + } + }, + "assign_profile_phases": { + "title": "Assignar intervals de fase", + "description": "Fase de previsualització del perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Intervals actuals:**\n{current_ranges}\n\nFeu servir les accions següents per afegir, editar, suprimir o desar intervals.", + "menu_options": { + "assign_profile_phases_add": "Afegeix l'interval de fases", + "assign_profile_phases_edit_select": "Edita l'interval de fases", + "assign_profile_phases_delete": "Suprimeix l'interval de fases", + "phase_ranges_clear": "Esborra tots els intervals", + "assign_profile_phases_auto_detect": "Autodetecció de fases", + "phase_ranges_save": "Guarda i torna", + "menu_back": "← Enrere" + } + }, + "assign_profile_phases_add": { + "title": "Afegeix l'interval de fases", + "description": "Afegiu un interval de fase a l'esborrany actual.", + "data": { + "phase_name": "Fase", + "start_min": "Minut d'inici", + "end_min": "Minut final" + } + }, + "assign_profile_phases_edit_select": { + "title": "Edita l'interval de fases", + "description": "Seleccioneu un interval per editar.", + "data": { + "range_index": "Interval de fases" + } + }, + "assign_profile_phases_edit": { + "title": "Edita l'interval de fases", + "description": "Actualitza l'interval de fase seleccionat.", + "data": { + "phase_name": "Fase", + "start_min": "Minut d'inici", + "end_min": "Minut final" + } + }, + "assign_profile_phases_delete": { + "title": "Suprimeix l'interval de fases", + "description": "Seleccioneu un interval per eliminar-lo de l'esborrany.", + "data": { + "range_index": "Interval de fases" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectades automàticament", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**S'han detectat {detected_count} fases automàticament.** Trieu una acció a continuació.", + "data": { + "action": "Acció" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nom Fases detectades", + "description": "Perfil: **{profile_name}**\n\n**S'han detectat {detected_count} fases:**\n{ranges_summary}\n\nAssigna un nom a cada fase." + }, + "assign_profile_phases_select": { + "title": "Assignar intervals de fase", + "description": "Seleccioneu un perfil per configurar els intervals de fase.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estadístiques de perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Crea un perfil nou", + "description": "Creeu un perfil nou manualment o a partir d'un cicle anterior.\n\nExemples de noms de perfil: \"Delicats\", \"Heavy Duty\", \"Quick Wash\"", + "data": { + "profile_name": "Nom del perfil", + "reference_cycle": "Cicle de referència (opcional)", + "manual_duration": "Durada manual (minuts)" + } + }, + "edit_profile": { + "title": "Edita el perfil", + "description": "Seleccioneu un perfil per canviar el nom", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Edita els detalls del perfil", + "description": "Perfil actual: {current_name}", + "data": { + "new_name": "Nom del perfil", + "manual_duration": "Durada manual (minuts)" + } + }, + "delete_profile_select": { + "title": "Suprimeix el perfil", + "description": "Seleccioneu un perfil per suprimir", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirmeu la supressió del perfil", + "description": "⚠️ Això suprimirà permanentment el perfil: {profile_name}", + "data": { + "unlabel_cycles": "Elimina l'etiqueta dels cicles amb aquest perfil" + } + }, + "auto_label_cycles": { + "title": "Etiqueta automàtica de cicles antics", + "description": "S'han trobat {total_count} cicles totals. Perfils: {profiles}", + "data": { + "confidence_threshold": "Confiança mínima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleccioneu Cicle per etiquetar", + "description": "✓ = completat, ⚠ = reprès, ✗ = interromput", + "data": { + "cycle_id": "Cicle" + } + }, + "select_cycle_to_delete": { + "title": "Suprimeix el cicle", + "description": "⚠️ Això suprimirà permanentment el cicle seleccionat", + "data": { + "cycle_id": "Cicle per esborrar" + } + }, + "label_cycle": { + "title": "Cicle d'etiquetes", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Nom del perfil nou (si s'està creant)" + } + }, + "post_process": { + "title": "Historial de processos (fusionar/dividir)", + "description": "Netegeu l'historial de cicles combinant execucions fragmentades i dividint cicles combinats incorrectament dins d'una finestra de temps. Introduïu el nombre d'hores per mirar enrere (utilitza 999999 per a tots).", + "data": { + "time_range": "Finestra de consulta (hores)", + "gap_seconds": "Combinació/Divisió de la bretxa (segons)" + }, + "data_description": { + "time_range": "Nombre d'hores per buscar cicles fragmentats per combinar-los. Utilitzeu 999999 per processar totes les dades històriques.", + "gap_seconds": "Llindar per decidir dividir o combinar. Els buits MÉS CURTS que aquest es combinen. Els buits MÉS LLARG que aquest es divideixen. Baixeu-ho per forçar una divisió en un cicle que s'ha fusionat incorrectament." + } + }, + "cleanup_profile": { + "title": "Neteja l'historial: seleccioneu el perfil", + "description": "Seleccioneu un perfil per visualitzar. Això generarà un gràfic que mostra tots els cicles passats d'aquest perfil per ajudar a identificar els valors atípics.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Neteja l'historial: gràfic i suprimir", + "description": "![Gràfic]({graph_url})\n\n**Visualització de {profile_name}**\n\nEl gràfic mostra els cicles individuals com a línies de colors. Identifiqueu els valors atípics (línies allunyades del grup) i feu coincidir el seu color a la llista següent per eliminar-los.\n\n**Seleccioneu els cicles per suprimir PERMANENTMENT:**", + "data": { + "cycles_to_delete": "Cicles per suprimir (comprova els colors coincidents)" + } + }, + "interactive_editor": { + "title": "Combinar/dividir Editor interactiu", + "description": "Seleccioneu una acció manual per dur a terme al vostre historial de cicles.", + "menu_options": { + "editor_split": "Dividir un cicle (Trobar buits)", + "editor_merge": "Cicles de combinació (uneix fragments)", + "editor_delete": "Suprimeix cicles", + "menu_back": "← Enrere" + } + }, + "editor_select": { + "title": "Seleccioneu Cicles", + "description": "Seleccioneu els cicles a processar.\n\n{info_text}", + "data": { + "selected_cycles": "Cicles" + } + }, + "editor_configure": { + "title": "Configura i aplica", + "description": "{preview_md}", + "data": { + "confirm_action": "Acció", + "merged_profile": "Perfil resultant", + "new_profile_name": "Nom del perfil nou", + "confirm_commit": "Sí, confirmo aquesta acció", + "segment_0_profile": "Perfil del segment 1", + "segment_1_profile": "Perfil del segment 2", + "segment_2_profile": "Perfil del segment 3", + "segment_3_profile": "Perfil del segment 4", + "segment_4_profile": "Perfil del segment 5", + "segment_5_profile": "Perfil del segment 6", + "segment_6_profile": "Perfil del segment 7", + "segment_7_profile": "Perfil del segment 8", + "segment_8_profile": "Perfil del segment 9", + "segment_9_profile": "Perfil del segment 10" + } + }, + "editor_split_params": { + "title": "Configuració dividida", + "description": "Ajusteu la sensibilitat per dividir aquest cicle. Qualsevol espai inactiu més llarg que això provocarà una divisió.", + "data": { + "split_mode": "Mètode de divisió" + } + }, + "editor_split_auto_params": { + "title": "Configuració de divisió automàtica", + "description": "Ajusteu la sensibilitat per dividir aquest cicle. Qualsevol interval d'inactivitat més llarg que aquest provocarà una divisió.", + "data": { + "min_gap_seconds": "Llindar de l'interval de divisió (segons)" + } + }, + "editor_split_manual_params": { + "title": "Marques horàries de divisió manual", + "description": "{preview_md}Finestra del cicle: **{cycle_start_wallclock} → {cycle_end_wallclock}** el {cycle_date}.\n\nIntroduïu una o més marques horàries de divisió (`HH:MM` o `HH:MM:SS`), una per línia. El cicle es tallarà a cada marca horària — N marques horàries produeixen N+1 segments.", + "data": { + "split_timestamps": "Marques horàries de divisió" + } + }, + "reprocess_history": { + "title": "Manteniment: reprocessament i optimització de dades", + "description": "Realitza una neteja profunda de les dades històriques:\n1. Retalla les lectures de potència zero (silenci).\n2. Arregla el temps/durada del cicle.\n3. Recalcula tots els models estadístics (sobres).\n\n**Executa això per solucionar problemes de dades o aplicar algorismes nous.**" + }, + "wipe_history": { + "title": "Esborra l'historial (només proves)", + "description": "⚠️ Això suprimirà permanentment TOTS els cicles i perfils emmagatzemats per a aquest dispositiu. Això no es pot desfer!" + }, + "export_import": { + "title": "Exporta/Importa JSON", + "description": "Copieu/enganxeu cicles, perfils I configuració d'aquest dispositiu. Exporteu a continuació o enganxeu JSON per importar.", + "data": { + "mode": "Acció", + "json_payload": "Configuració completa JSON" + }, + "data_description": { + "mode": "Trieu Exporta per copiar les dades i la configuració actuals, o Importa per enganxar les dades exportades des d'un altre dispositiu WashData.", + "json_payload": "Per a l'exportació: copieu aquest JSON (inclou cicles, perfils i tota la configuració ajustada). Per a la importació: enganxeu JSON exportat des de WashData." + } + }, + "record_cycle": { + "title": "Cicle de gravació", + "description": "Enregistreu manualment un cicle per crear un perfil net sense interferències.\n\nEstat: **{status}**\nDurada: {duration} s\nMostres: {samples}", + "menu_options": { + "record_refresh": "Estat d'actualització", + "record_stop": "Atura la gravació (Desa i processa)", + "record_start": "Comença una nova gravació", + "record_process": "Processa l'últim enregistrament (retalla i desa)", + "record_discard": "Descarta l'última gravació", + "menu_back": "← Enrere" + } + }, + "record_process": { + "title": "Enregistrament del procés", + "description": "![Gràfic]({graph_url})\n\n**El gràfic mostra l'enregistrament en brut.** Blau = Mantenir, Vermell = Retall proposat.\n*El gràfic és estàtic i no s'actualitza amb els canvis de formulari.*\n\nS'ha aturat la gravació. Els retalls s'alineen amb la freqüència de mostreig detectada (~{sampling_rate}s).\n\nDurada bruta: {duration} s\nMostres: {samples}", + "data": { + "head_trim": "Inici de retall (segons)", + "tail_trim": "Final de retall (segons)", + "save_mode": "Desa la destinació", + "profile_name": "Nom del perfil" + } + }, + "learning_feedbacks": { + "title": "Feedbacks d'aprenentatge", + "description": "Comentaris pendents: {count}\n\n{pending_table}\n\nSeleccioneu una sol·licitud de revisió de cicle per processar.", + "menu_options": { + "learning_feedbacks_pick": "Revisa un comentari pendent", + "learning_feedbacks_dismiss_all": "Descarta tots els comentaris pendents", + "menu_back": "← Enrere" + } + }, + "learning_feedbacks_pick": { + "title": "Trieu comentaris per revisar", + "description": "Seleccioneu una sol·licitud de revisió de cicle per obrir.", + "data": { + "selected_feedback": "Selecciona el cicle per revisar" + } + }, + "learning_feedbacks_empty": { + "title": "Feedbacks d'aprenentatge", + "description": "No s'han trobat sol·licituds de comentaris pendents.", + "menu_options": { + "menu_back": "← Enrere" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Descartar tots els comentaris pendents", + "description": "Esteu a punt de descartar **{count}** sol·licituds de comentaris pendents. Els cicles descartats continuaran a l'historial, però no aportaran cap senyal d'aprenentatge nou. Això no es pot desfer.\n\n✅ Marqueu la casella de sota i feu clic a **Envia** per descartar-ho tot.\n❌ Deixeu-la sense marcar i feu clic a **Envia** per cancel·lar i tornar enrere.", + "data": { + "confirm_dismiss_all": "Confirma: descarta totes les sol·licituds de comentaris pendents" + } + }, + "resolve_feedback": { + "title": "Resol comentaris", + "description": "{comparison_img}{candidates_table}\n**Perfil detectat**: {detected_profile} ({confidence_pct}%)\n**Durada estimada**: {est_duration_min} min\n**Durada real**: {act_duration_min} min\n\n__Tria una acció a continuació:__", + "data": { + "action": "Què t'agradaria fer?", + "corrected_profile": "Programa correcte (si es corregeix)", + "corrected_duration": "Durada correcta en segons (si es corregeix)" + } + }, + "trim_cycle_select": { + "title": "Cicle de retall: seleccioneu Cicle", + "description": "Seleccioneu un cicle per retallar. ✓ = completat, ⚠ = reprès, ✗ = interromput", + "data": { + "cycle_id": "Cicle" + } + }, + "trim_cycle": { + "title": "Cicle de tall", + "description": "Finestra actual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acció" + } + }, + "trim_cycle_start": { + "title": "Estableix Trim Start", + "description": "Finestra de cicle: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInici actual: **{current_wallclock}** (+{current_offset_min} min des de l'inici del cicle)\n\nTrieu una nova hora d'inici utilitzant el selector de rellotges que hi ha a continuació.", + "data": { + "trim_start_time": "Nova hora d'inici", + "trim_start_min": "Nou inici (minuts des de l'inici del cicle)" + } + }, + "trim_cycle_end": { + "title": "Estableix el final de retall", + "description": "Finestra de cicle: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFinal actual: **{current_wallclock}** (+{current_offset_min} min des de l'inici del cicle)\n\nTrieu una nova hora de finalització amb el selector de rellotge que hi ha a continuació.", + "data": { + "trim_end_time": "Nova hora de finalització", + "trim_end_min": "Nou final (minuts des de l'inici del cicle)" + } + } + }, + "error": { + "import_failed": "La importació ha fallat. Enganxeu un JSON d'exportació de WashData vàlid.", + "select_exactly_one": "Seleccioneu exactament un cicle per a aquesta acció.", + "select_at_least_one": "Seleccioneu almenys un cicle per a aquesta acció.", + "select_at_least_two": "Seleccioneu almenys dos cicles per a aquesta acció.", + "profile_exists": "Ja existeix un perfil amb aquest nom", + "rename_failed": "No s'ha pogut canviar el nom del perfil. És possible que el perfil no existeixi o que ja s'ha pres un nom nou.", + "assignment_failed": "No s'ha pogut assignar el perfil al cicle", + "duplicate_phase": "Ja existeix una fase amb aquest nom.", + "phase_not_found": "No s'ha trobat la fase seleccionada.", + "invalid_phase_name": "El nom de la fase no pot estar buit.", + "phase_name_too_long": "El nom de la fase és massa llarg.", + "invalid_phase_range": "L'interval de fases no és vàlid. El final ha de ser més gran que l'inici.", + "invalid_phase_timestamp": "La marca de temps no és vàlida. Utilitzeu el format AAAA-MM-DD HH:MM o HH:MM.", + "overlapping_phase_ranges": "Els intervals de fase es superposen. Ajusteu els intervals i torneu-ho a provar.", + "incomplete_phase_row": "Cada fila de fase ha d'incloure una fase més els valors d'inici i finalització complets per al mode seleccionat.", + "timestamp_mode_cycle_required": "Seleccioneu un cicle d'origen quan utilitzeu el mode de marca de temps.", + "no_phase_ranges": "Encara no hi ha rangs de fase disponibles per a aquesta acció.", + "feedback_notification_title": "WashData: verifica el cicle ({device})", + "feedback_notification_message": "**Dispositiu**: {device}\n**Programa**: {program} ({confidence}% de confiança)\n**Hora**: {time}\n\nWashData necessita la teva ajuda per verificar aquest cicle detectat.\n\nAneu a **Configuració > Dispositius i serveis > WashData > Configura > Comentaris d'aprenentatge** per confirmar o corregir aquest resultat.", + "suggestions_ready_notification_title": "WashData: configuracions suggerides a punt ({device})", + "suggestions_ready_notification_message": "El sensor de **Configuració suggerida** ara informa de **{count}** recomanacions accionables.\n\nPer revisar-los i aplicar-los: **Configuració > Dispositius i serveis > WashData > Configura > Configuració avançada > Aplica els valors suggerits**.\n\nEls suggeriments són opcionals i es mostren per revisar-los abans de desar-los.", + "trim_range_invalid": "L'inici ha de ser abans del final. Ajusteu els vostres punts de retallat.", + "trim_failed": "Ha fallat el retall. És possible que el cicle ja no existeixi o que la finestra estigui buida.", + "invalid_split_timestamp": "La marca horària no és vàlida o queda fora de la finestra del cicle. Feu servir HH:MM o HH:MM:SS dins de la finestra del cicle.", + "no_split_segments_found": "No s'han pogut produir segments vàlids a partir d'aquestes marques horàries. Assegureu-vos que cada marca horària és dins de la finestra del cicle i que els segments duren com a mínim 1 minut.", + "auto_tune_suggestion": "{device_type} {device_title} han detectat cicles fantasmes. Canvi de min_power suggerit: {current_min}W -> {new_min}W (no s'aplica automàticament).", + "auto_tune_title": "Sintonització automàtica de WashData", + "auto_tune_fallback": "{device_type} {device_title} han detectat cicles fantasmes. Canvi de min_power suggerit: {current_min}W -> {new_min}W (no s'aplica automàticament)." + }, + "abort": { + "no_cycles_found": "No s'han trobat cicles", + "no_split_segments_found": "No s'han trobat segments divisibles al cicle seleccionat.", + "cycle_not_found": "No s'ha pogut carregar el cicle seleccionat.", + "no_power_data": "No hi ha dades de traça de potència disponibles per al cicle seleccionat.", + "no_profiles_found": "No s'han trobat perfils. Creeu primer un perfil.", + "no_profiles_for_matching": "No hi ha perfils disponibles per fer coincidir. Creeu perfils primer.", + "no_unlabeled_cycles": "Tots els cicles ja estan etiquetats", + "no_suggestions": "Encara no hi ha valors suggerits disponibles. Executeu uns quants cicles i torneu-ho a provar.", + "no_predictions": "Sense prediccions", + "no_custom_phases": "No s'han trobat fases personalitzades.", + "reprocess_success": "S'han reprocessat correctament {count} cicles.", + "debug_data_cleared": "S'han esborrat correctament les dades de depuració de {count} cicles." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Inici del cicle", + "cycle_finish": "Finalització del cicle", + "cycle_live": "Progrés en viu" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sense etiquetar)", + "create_new_profile": "Crea un perfil nou", + "remove_label": "Elimina l'etiqueta", + "no_reference_cycle": "(Sense cicle de referència)", + "all_device_types": "Tots els tipus de dispositius", + "current_suffix": "(actual)", + "editor_select_info": "Seleccioneu 1 cicle per dividir o 2 o més cicles per combinar.", + "editor_select_info_split": "Seleccioneu 1 cicle per dividir.", + "editor_select_info_merge": "Seleccioneu 2 o més cicles per fusionar.", + "editor_select_info_delete": "Seleccioneu 1 o més cicles per eliminar.", + "no_cycles_recorded": "Encara no s'han registrat cicles.", + "profile_summary_line": "- **{name}**: {count} cicles, {avg}m de mitjana", + "no_profiles_created": "Encara no s'ha creat cap perfil.", + "phase_preview": "Fase de previsualització", + "phase_preview_no_curve": "La corba mitjana del perfil encara no està disponible. Executeu/etiqueteu més cicles per a aquest perfil.", + "average_power_curve": "Corba de potència mitjana", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cicles, ~{duration} m de mitjana)", + "profile_option_short_fmt": "{name} ({count} cicles, ~{duration} m)", + "deleted_cycles_from_profile": "S'han suprimit {count} cicles de {profile}.", + "not_enough_data_graph": "No hi ha prou dades per generar el gràfic.", + "no_profiles_found": "No s'han trobat perfils.", + "cycle_info_fmt": "Cicle: {start}, {duration}m, Actual: {label}", + "top_candidates_header": "### Principals candidats", + "tbl_profile": "Perfil", + "tbl_confidence": "Confiança", + "tbl_correlation": "Correlació", + "tbl_duration_match": "Coincidència de durada", + "detected_profile": "Perfil detectat", + "estimated_duration": "Durada estimada", + "actual_duration": "Durada real", + "choose_action": "__Tria una acció a continuació:__", + "tbl_count": "Recompte", + "tbl_avg": "Mitjana", + "tbl_min": "Min", + "tbl_max": "Màx", + "tbl_energy_avg": "Energia (mitjana)", + "tbl_energy_total": "Energia (total)", + "tbl_consistency": "Consistència", + "tbl_last_run": "Última execució", + "graph_legend_title": "Llegenda del gràfic", + "graph_legend_body": "La banda blava representa l'interval de consum de potència mínim i màxim observat. La línia mostra la corba de potència mitjana.", + "recording_preview": "Vista prèvia de la gravació", + "trim_start": "Inici de retall", + "trim_end": "Retalla final", + "split_preview_title": "Vista prèvia dividida", + "split_preview_found_fmt": "S'han trobat {count} segments.", + "split_preview_confirm_fmt": "Feu clic a Confirmar per dividir aquest cicle en {count} cicles separats.", + "merge_preview_title": "Vista prèvia de la combinació", + "merge_preview_joining_fmt": "S'està unint a {count} cicles. Els buits s'ompliran amb lectures de 0W.", + "editor_delete_preview_title": "Vista prèvia d'eliminació", + "editor_delete_preview_intro": "Els cicles seleccionats s'eliminaran permanentment:", + "editor_delete_preview_confirm": "Feu clic a Confirma per eliminar permanentment aquests registres de cicles.", + "no_power_preview": "*No hi ha dades de potència disponibles per a la vista prèvia.*", + "profile_comparison": "Comparació de perfils", + "actual_cycle_label": "Aquest cicle (real)", + "storage_usage_header": "Ús d'emmagatzematge", + "storage_file_size": "Mida del fitxer", + "storage_cycles": "Cicles", + "storage_profiles": "Perfils", + "storage_debug_traces": "Traces de depuració", + "overview_suffix": "Visió general", + "phase_preview_for_profile": "Fase de previsualització del perfil", + "current_ranges_header": "Intervals actuals", + "assign_phases_help": "Feu servir les accions següents per afegir, editar, suprimir o desar intervals.", + "profile_summary_header": "Resum del perfil", + "recent_cycles_header": "Cicles recents", + "trim_cycle_preview_title": "Previsualització retallat del cicle", + "trim_cycle_preview_no_data": "No hi ha dades de potència disponibles per a aquest cicle.", + "trim_cycle_preview_kept_suffix": "guardada", + "table_program": "Programa", + "table_when": "Quan", + "table_length": "Durada", + "table_match": "Coincidència", + "table_profile": "Perfil", + "table_cycles": "Cicles", + "table_avg_length": "Durada mitjana", + "table_last_run": "Última execució", + "table_avg_energy": "Energia mitjana", + "table_detected_program": "Programa detectat", + "table_confidence": "Confiança", + "table_reported": "Informat", + "phase_builtin_suffix": "(Incorporat)", + "phase_none_available": "No hi ha fases disponibles.", + "settings_deprecation_warning": "⚠️ **Tipus de dispositiu obsolet:** {device_type} està previst que s'elimini en una versió futura. El pipeline de concordança de WashData no produeix resultats fiables per a aquesta classe d'aparell. La vostra integració continua funcionant durant el període d'abandonament; per silenciar aquest avís, canvieu el **Tipus de dispositiu** a continuació a un dels tipus admesos (rentadora, assecadora, rentadora-assecadora combinada, rentavaixelles, fregidora d'aire, màquina de pa o bomba) o a **Altres (avançat)** si el vostre aparell no coincideix amb cap dels tipus admesos. **Altres (avançat)** inclou valors predeterminats genèrics intencionadament que no estan ajustats per a cap aparell específic, de manera que haureu de configurar vosaltres mateixos els llindars, els temps d'espera i els paràmetres coincidents; tots els vostres paràmetres existents es conserven quan canvieu.", + "phase_other_device_types": "Altres tipus de dispositius:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir un cicle (Trobar buits)", + "merge": "Cicles de combinació (uneix fragments)", + "delete": "Suprimeix cicles" + } + }, + "split_mode": { + "options": { + "auto": "Detecta automàticament els buits d'inactivitat", + "manual": "Marques horàries manuals" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Manteniment: reprocessament i optimització de dades", + "clear_debug_data": "Esborra les dades de depuració (allibera espai)", + "wipe_history": "Esborra TOTES les dades d'aquest dispositiu (irreversible)", + "export_import": "Exportar/Importar JSON amb la configuració (copiar/enganxar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exporta totes les dades", + "import": "Importa/Combina dades" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etiqueta automàtica de cicles antics", + "select_cycle_to_label": "Cicle específic de l'etiqueta", + "select_cycle_to_delete": "Suprimeix el cicle", + "interactive_editor": "Combinar/dividir Editor interactiu", + "trim_cycle": "Dades del cicle de retallada" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Crea un perfil nou", + "edit_profile": "Edita/canvia el nom del perfil", + "delete_profile": "Suprimeix el perfil", + "profile_stats": "Estadístiques de perfil", + "cleanup_profile": "Neteja l'historial: gràfic i suprimir", + "assign_phases": "Assignar intervals de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Crea una nova fase", + "edit_custom_phase": "Fase d'edició", + "delete_custom_phase": "Suprimeix la fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Afegeix l'interval de fases", + "edit_range": "Edita l'interval de fases", + "delete_range": "Suprimeix l'interval de fases", + "clear_ranges": "Esborra tots els intervals", + "auto_detect_ranges": "Autodetecció de fases", + "save_ranges": "Guarda i torna" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Estat d'actualització", + "stop_recording": "Atura la gravació (Desa i processa)", + "start_recording": "Comença una nova gravació", + "process_recording": "Processa l'últim enregistrament (retalla i desa)", + "discard_recording": "Descarta l'última gravació" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Crea un perfil nou", + "existing_profile": "Afegeix al perfil existent", + "discard": "Descarta la gravació" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - Detecció correcta", + "correct": "Correcte: seleccioneu Programa/Durada", + "ignore": "Ignorar - Fals positiu/cicle sorollós", + "delete": "Suprimeix - Elimina de l'historial" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Anomena i aplica les fases", + "cancel": "Tornar enrere sense canvis" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Estableix el punt d'inici", + "set_end": "Estableix el punt final", + "reset": "Restableix a la durada completa", + "apply": "Aplicar Trim", + "cancel": "Cancel·la" + } + }, + "device_type": { + "options": { + "washing_machine": "Rentadora", + "dryer": "Assecador", + "washer_dryer": "Combo de rentadora-assecadora", + "dishwasher": "Rentaplats", + "coffee_machine": "Màquina de cafè (obsoleta)", + "ev": "Vehicle elèctric (obsolet)", + "air_fryer": "Fregidora d'aire", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "Panificadora", + "pump": "Bomba / Bomba de sumidero", + "oven": "Forn (obsolet)", + "other": "Altres (avançat)" + } + } + }, + "services": { + "label_cycle": { + "name": "Cicle d'etiquetes", + "description": "Assigna un perfil existent a un cicle anterior o elimina l'etiqueta.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora per etiquetar." + }, + "cycle_id": { + "name": "ID del cicle", + "description": "L'identificador del cicle a etiquetar." + }, + "profile_name": { + "name": "Nom del perfil", + "description": "El nom d'un perfil existent (creeu perfils al menú Gestiona perfils). Deixa en blanc per eliminar l'etiqueta." + } + } + }, + "create_profile": { + "name": "Crea un perfil", + "description": "Creeu un perfil nou (autònom o basat en un cicle de referència).", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora." + }, + "profile_name": { + "name": "Nom del perfil", + "description": "Nom per al perfil nou (p. ex., \"Heavy Duty\", \"Delicades\")." + }, + "reference_cycle_id": { + "name": "ID del cicle de referència", + "description": "Identificador de cicle opcional per basar aquest perfil." + } + } + }, + "delete_profile": { + "name": "Suprimeix el perfil", + "description": "Suprimiu un perfil i, opcionalment, desetiqueteu els cicles que el fan servir.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora." + }, + "profile_name": { + "name": "Nom del perfil", + "description": "El perfil a esborrar." + }, + "unlabel_cycles": { + "name": "Desetiquetar els cicles", + "description": "Elimina l'etiqueta del perfil dels cicles amb aquest perfil." + } + } + }, + "auto_label_cycles": { + "name": "Etiqueta automàtica de cicles antics", + "description": "Etiqueta de manera retroactiva els cicles sense etiquetar mitjançant la concordança de perfils.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora." + }, + "confidence_threshold": { + "name": "Llindar de confiança", + "description": "Confiança de coincidència mínima (0,50-0,95) per aplicar etiquetes." + } + } + }, + "export_config": { + "name": "Exporta la configuració", + "description": "Exporta els perfils, els cicles i la configuració d'aquest dispositiu a un fitxer JSON (per dispositiu).", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora per exportar." + }, + "path": { + "name": "Camí", + "description": "Ruta del fitxer absoluta opcional per escriure (per defecte és /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importa la configuració", + "description": "Importa els perfils, els cicles i la configuració d'aquest dispositiu des d'un fitxer d'exportació JSON (la configuració es pot sobreescriure).", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora a on importar." + }, + "path": { + "name": "Camí", + "description": "Camí absolut al fitxer JSON d'exportació per importar." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar comentaris sobre el cicle", + "description": "Confirmeu o corregiu un programa detectat automàticament després d'un cicle completat. Proporcioneu `entry_id` (avançat) o `device_id` (recomanat).", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu WashData (recomanat en lloc d'entry_id)." + }, + "entry_id": { + "name": "ID d'entrada", + "description": "L'identificador de l'entrada de configuració del dispositiu (alternativa a device_id)." + }, + "cycle_id": { + "name": "ID del cicle", + "description": "L'identificador del cicle que es mostra a la notificació/registres de comentaris." + }, + "user_confirmed": { + "name": "Confirmeu el programa detectat", + "description": "Establiu true si el programa detectat és correcte." + }, + "corrected_profile": { + "name": "Perfil corregit", + "description": "Si no es confirma, proporcioneu el nom del perfil/programa correcte." + }, + "corrected_duration": { + "name": "Durada corregida (segons)", + "description": "Durada corregida opcional en segons." + }, + "notes": { + "name": "Notes", + "description": "Notes opcionals sobre aquest cicle." + } + } + }, + "record_start": { + "name": "Inici del cicle de gravació", + "description": "Comenceu a gravar manualment un cicle de neteja (obvia tota la lògica coincident).", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora per gravar." + } + } + }, + "record_stop": { + "name": "Parada del cicle de gravació", + "description": "Atura la gravació manual.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu de la rentadora per aturar la gravació." + } + } + }, + "trim_cycle": { + "name": "Cicle de tall", + "description": "Retalla les dades de potència d'un cicle passat a una finestra de temps específica.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu WashData." + }, + "cycle_id": { + "name": "ID del cicle", + "description": "L'ID del cicle a retallar." + }, + "trim_start_s": { + "name": "Inici de retall (segons)", + "description": "Conserva les dades d'aquest desplaçament en segons. Per defecte 0." + }, + "trim_end_s": { + "name": "Final de retalla (segons)", + "description": "Manteniu les dades amb aquest desplaçament en segons. Per defecte = durada completa." + } + } + }, + "pause_cycle": { + "name": "Cicle de pausa", + "description": "Posa en pausa el cicle actiu d'un dispositiu WashData.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu WashData per posar en pausa." + } + } + }, + "resume_cycle": { + "name": "Reprendre el cicle", + "description": "Reprèn un cicle en pausa per a un dispositiu WashData.", + "fields": { + "device_id": { + "name": "Dispositiu", + "description": "El dispositiu WashData per reprendre." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Córrer" + }, + "match_ambiguity": { + "name": "Ambigüitat de la coincidència" + } + }, + "sensor": { + "washer_state": { + "name": "Estat", + "state": { + "off": "Apagat", + "idle": "Inactiu", + "starting": "Començant", + "running": "Córrer", + "paused": "En pausa", + "user_paused": "En pausa per l'usuari", + "ending": "Finalització", + "finished": "Acabat", + "anti_wrinkle": "Antiarrugues", + "delay_wait": "Esperant per començar", + "interrupted": "Interromput", + "force_stopped": "Força aturada", + "rinse": "Esbandida", + "unknown": "Desconegut", + "clean": "Neteja" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Temps restant" + }, + "total_duration": { + "name": "Durada total" + }, + "cycle_progress": { + "name": "Progrés" + }, + "current_power": { + "name": "Potència actual" + }, + "elapsed_time": { + "name": "Temps transcorregut" + }, + "debug_info": { + "name": "Informació de depuració" + }, + "match_confidence": { + "name": "Confiança de coincidència" + }, + "top_candidates": { + "name": "Els millors candidats", + "state": { + "none": "Cap" + } + }, + "current_phase": { + "name": "Fase actual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Recompte del perfil {profile_name}", + "unit_of_measurement": "cicles" + }, + "suggestions": { + "name": "Configuració suggerida" + }, + "pump_runs_today": { + "name": "Funcionament de la bomba (últimes 24 h)" + }, + "cycle_count": { + "name": "Recompte de cicles" + } + }, + "select": { + "program_select": { + "name": "Programa del Cicle", + "state": { + "auto_detect": "Detecció automàtica" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Força final de cicle" + }, + "pause_cycle": { + "name": "Cicle de pausa" + }, + "resume_cycle": { + "name": "Reprendre el cicle" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Cal un device_id vàlid." + }, + "cycle_id_required": { + "message": "Es requereix un cycle_id vàlid." + }, + "profile_name_required": { + "message": "Cal un profile_name vàlid." + }, + "device_not_found": { + "message": "No s'ha trobat el dispositiu." + }, + "no_config_entry": { + "message": "No s'ha trobat cap entrada de configuració per al dispositiu." + }, + "integration_not_loaded": { + "message": "La integració no s'ha carregat per a aquest dispositiu." + }, + "cycle_not_found_or_no_power": { + "message": "No s'ha trobat el cicle o no té dades de potència." + }, + "trim_failed_empty_window": { + "message": "Error de retalla: no s'ha trobat el cicle, no hi ha dades d'energia o la finestra resultant està buida." + }, + "trim_invalid_range": { + "message": "trim_end_s ha de ser més gran que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/cs.json b/custom_components/ha_washdata/translations/cs.json new file mode 100644 index 0000000..c1f80cb --- /dev/null +++ b/custom_components/ha_washdata/translations/cs.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Nastavení WashData", + "description": "Nakonfigurujte si pračku nebo jiný spotřebič.\n\nJe vyžadován snímač napájení.\n\n**Dále budete dotázáni, zda chcete vytvořit svůj první profil.**", + "data": { + "name": "Název zařízení", + "device_type": "Typ zařízení", + "power_sensor": "Výkonový senzor", + "min_power": "Minimální práh výkonu (W)" + }, + "data_description": { + "name": "Popisný název pro toto zařízení (např. 'Pračka', 'Myčka nádobí').", + "device_type": "O jaký typ spotřebiče se jedná? Pomáhá přizpůsobit detekci a označování.", + "power_sensor": "Entita senzoru, která hlásí spotřebu energie v reálném čase (ve wattech) z vaší chytré zástrčky.", + "min_power": "Údaje o výkonu nad touto prahovou hodnotou (ve wattech) indikují, že spotřebič běží. U většiny zařízení začněte s 2W." + } + }, + "first_profile": { + "title": "Vytvořte první profil", + "description": "**Cyklus** je kompletní chod vašeho spotřebiče (např. dávka prádla). **Profil** seskupuje tyto cykly podle typu (např. 'Bavlna', 'Rychlé praní'). Vytvoření profilu nyní napomáhá okamžitému odhadu trvání integrace.\n\nPokud tento profil není detekován automaticky, můžete jej ručně vybrat v ovládacích prvcích zařízení.\n\n**Chcete-li tento krok přeskočit, ponechte pole Název profilu prázdné.**", + "data": { + "profile_name": "Název profilu (volitelné)", + "manual_duration": "Odhadovaná doba trvání (minuty)" + } + } + }, + "error": { + "cannot_connect": "Připojení se nezdařilo", + "invalid_auth": "Neplatné ověření", + "unknown": "Neočekávaná chyba" + }, + "abort": { + "already_configured": "Zařízení je již nakonfigurováno" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Zkontrolujte zpětnou vazbu k učení", + "settings": "Nastavení", + "notifications": "Oznámení", + "manage_cycles": "Správa cyklů", + "manage_profiles": "Správa profilů", + "manage_phase_catalog": "Správa katalogu fází", + "record_cycle": "Cyklus záznamu (ručně)", + "diagnostics": "Diagnostika a údržba" + } + }, + "apply_suggestions": { + "title": "Kopírovat navrhované hodnoty", + "description": "Tím se zkopírují aktuální navrhované hodnoty do uložených Nastavení. Toto je jednorázová akce a nepovoluje automatické přepisování.\n\nDoporučené hodnoty ke kopírování:\n{suggested}", + "data": { + "confirm": "Potvrďte akci kopírování" + } + }, + "apply_suggestions_confirm": { + "title": "Zkontrolujte navrhované změny", + "description": "WashData nalezeno **{pending_count}** navrhované hodnoty, které se mají použít.\n\nzměny:\n{changes}\n\n✅ Zaškrtněte políčko níže a kliknutím na **Odeslat** tyto změny okamžitě použijte a uložte.\n❌ Nechte to nezaškrtnuté a kliknutím na **Odeslat** zrušte a vraťte se zpět.", + "data": { + "confirm_apply_suggestions": "Použít a uložit tyto změny" + } + }, + "settings": { + "title": "Nastavení", + "description": "{deprecation_warning}Vylaďte prahy detekce, učení a pokročilé chování. Výchozí nastavení funguje dobře pro většinu nastavení.\n\nAktuálně dostupná doporučená nastavení: {suggestions_count}.\nPokud je toto číslo nad 0, otevřete Pokročilá nastavení a použijte 'Použít navrhované hodnoty' pro přehled doporučení.", + "data": { + "apply_suggestions": "Použít navrhované hodnoty", + "device_type": "Typ zařízení", + "power_sensor": "Entita snímače výkonu", + "min_power": "Minimální výkon (W)", + "off_delay": "Zpoždění konce cyklu (s)", + "notify_actions": "Akce oznámení", + "notify_people": "Odložte doručení, dokud nebudou tito lidé doma", + "notify_only_when_home": "Zpoždění oznámení, dokud nebude vybraná osoba doma", + "notify_fire_events": "Události spuštění automatizací", + "notify_start_services": "Spuštění cyklu - Cíle upozornění", + "notify_finish_services": "Dokončení cyklu - Cíle oznámení", + "notify_live_services": "Aktuální průběh - cíle upozornění", + "notify_before_end_minutes": "Upozornění před dokončením (minuty před koncem)", + "notify_title": "Název oznámení", + "notify_icon": "Ikona oznámení", + "notify_start_message": "Formát zprávy o zahájení", + "notify_finish_message": "Formát zprávy o ukončení", + "notify_pre_complete_message": "Formát zprávy před dokončením", + "show_advanced": "Upravit pokročilá nastavení (další krok)" + }, + "data_description": { + "apply_suggestions": "Zaškrtnutím tohoto políčka zkopírujete hodnoty navržené algoritmem učení do formuláře. Před uložením zkontrolujte.\n\nDoporučeno (z učení):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Vyberte typ spotřebiče (Pračka, Sušička, Myčka, Kávovar, EV). Tato značka se ukládá s každým cyklem pro lepší organizaci.", + "power_sensor": "Entita senzoru hlásící výkon v reálném čase (ve wattech). Toto můžete kdykoli změnit bez ztráty historických dat – užitečné, pokud vyměníte chytrou zástrčku.", + "min_power": "Údaje o výkonu nad touto prahovou hodnotou (ve wattech) indikují, že spotřebič běží. U většiny zařízení začněte s 2W.", + "off_delay": "V sekundách musí vyhlazený výkon zůstat pod prahem zastavení, aby bylo možné označit dokončení. Výchozí: 120 s.", + "notify_actions": "Volitelná sekvence akcí Home Assistant, která se spustí pro oznámení (skripty, oznámení, TTS atd.). Je-li nastaveno, akce se spouštějí před službou nouzového upozornění.", + "notify_people": "Osoby entity používané pro řízení přítomnosti. Je-li tato možnost povolena, doručení oznámení je odloženo, dokud nebude alespoň jedna vybraná osoba doma.", + "notify_only_when_home": "Pokud je tato možnost povolena, oznámení se odloží, dokud nebude alespoň jedna vybraná osoba doma.", + "notify_fire_events": "Pokud je povoleno, spouštět události Home Assistant pro začátek a konec cyklu (ha_washdata_cycle_started a ha_washdata_cycle_ended) pro řízení automatizace.", + "notify_start_services": "Jedna nebo více služeb upozorňujících na zahájení cyklu. Chcete-li přeskočit oznámení o zahájení, ponechte prázdné.", + "notify_finish_services": "Jedna nebo více oznamovacích služeb, které upozorní, když cyklus skončí nebo se blíží dokončení. Chcete-li přeskočit oznámení o dokončení, ponechte prázdné.", + "notify_live_services": "Jedna nebo více služeb upozorňujících na příjem aktuálních aktualizací průběhu cyklu (pouze mobilní doprovodná aplikace). Chcete-li přeskočit živé aktualizace, ponechte prázdné.", + "notify_before_end_minutes": "Minuty před odhadovaným koncem cyklu ke spuštění upozornění. Nastavením na 0 deaktivujete. Výchozí: 0.", + "notify_title": "Vlastní název pro oznámení. Podporuje zástupný symbol {device}.", + "notify_icon": "Ikona pro upozornění (např. mdi:washing-machine). Chcete-li odeslat bez ikony, ponechte prázdné.", + "notify_start_message": "Zpráva odeslaná při zahájení cyklu. Podporuje zástupný symbol {device}.", + "notify_finish_message": "Zpráva odeslána po skončení cyklu. Podporuje zástupné symboly {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Text opakujícího se živého průběhu se aktualizuje během běhu cyklu. Podporuje zástupné symboly {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Oznámení", + "description": "Nakonfigurujte oznamovací kanály, šablony a volitelné živé aktualizace průběhu mobilního telefonu.", + "data": { + "notify_actions": "Akce oznámení", + "notify_people": "Odložte doručení, dokud nebudou tito lidé doma", + "notify_only_when_home": "Zpoždění oznámení, dokud nebude vybraná osoba doma", + "notify_fire_events": "Události spuštění automatizací", + "notify_start_services": "Spuštění cyklu - Cíle upozornění", + "notify_finish_services": "Dokončení cyklu - Cíle oznámení", + "notify_live_services": "Aktuální průběh - cíle upozornění", + "notify_before_end_minutes": "Upozornění před dokončením (minuty před koncem)", + "notify_live_interval_seconds": "Interval živé aktualizace (sekundy)", + "notify_live_overrun_percent": "Povolení překročení živé aktualizace (%)", + "notify_live_chronometer": "Chronometr Odpočítávací časovač", + "notify_title": "Název oznámení", + "notify_icon": "Ikona oznámení", + "notify_start_message": "Formát zprávy o zahájení", + "notify_finish_message": "Formát zprávy o ukončení", + "notify_pre_complete_message": "Formát zprávy před dokončením", + "energy_price_entity": "Cena energie – entita (volitelné)", + "energy_price_static": "Cena energie – statická hodnota (volitelné)", + "go_back": "Vrátit se bez uložení", + "notify_reminder_message": "Name", + "notify_timeout_seconds": "Automatické propouštění po (sekundách)", + "notify_channel": "Kanál oznámení (stav / live / upomínka)", + "notify_finish_channel": "Dokončené oznámení kanál" + }, + "data_description": { + "go_back": "Zaškrtněte tuto možnost a kliknutím na Odeslat zahodíte všechny výše uvedené změny a vrátíte se do hlavní nabídky.", + "notify_actions": "Volitelná sekvence akcí Home Assistant, která se spustí pro oznámení (skripty, oznámení, TTS atd.). Je-li nastaveno, akce se spouštějí před službou nouzového upozornění.", + "notify_people": "Osoby entity používané pro řízení přítomnosti. Je-li tato možnost povolena, doručení oznámení je odloženo, dokud nebude alespoň jedna vybraná osoba doma.", + "notify_only_when_home": "Pokud je tato možnost povolena, oznámení se odloží, dokud nebude alespoň jedna vybraná osoba doma.", + "notify_fire_events": "Pokud je povoleno, spouštět události Home Assistant pro začátek a konec cyklu (ha_washdata_cycle_started a ha_washdata_cycle_ended) pro řízení automatizace.", + "notify_start_services": "Jedna nebo více služeb upozorňujících na zahájení cyklu (např. notify.mobile_app_my_phone). Chcete-li přeskočit oznámení o zahájení, ponechte prázdné.", + "notify_finish_services": "Jedna nebo více oznamovacích služeb, které upozorní, když cyklus skončí nebo se blíží dokončení. Chcete-li přeskočit oznámení o dokončení, ponechte prázdné.", + "notify_live_services": "Jedna nebo více služeb upozorňujících na příjem aktuálních aktualizací průběhu cyklu (pouze mobilní doprovodná aplikace). Chcete-li přeskočit živé aktualizace, ponechte prázdné.", + "notify_before_end_minutes": "Minuty před odhadovaným koncem cyklu ke spuštění upozornění. Nastavením na 0 deaktivujete. Výchozí: 0.", + "notify_live_interval_seconds": "Jak často se za běhu odesílají aktualizace průběhu. Nižší hodnoty reagují lépe, ale spotřebují více oznámení. Je vynuceno minimálně 30 sekund - hodnoty pod 30 s jsou automaticky zaokrouhleny na 30 s.", + "notify_live_overrun_percent": "Bezpečnostní rezerva nad odhadovaným počtem aktualizací pro dlouhé/přesahující cykly (například 20 % umožňuje 1,2x odhadované aktualizace).", + "notify_live_chronometer": "Je-li tato možnost povolena, každá živá aktualizace zahrnuje odpočítávání chronometru do odhadovaného času dokončení. Časovač upozornění na zařízení mezi aktualizacemi automaticky tiká (pouze doprovodná aplikace pro Android).", + "notify_title": "Vlastní název pro oznámení. Podporuje zástupný symbol {device}.", + "notify_icon": "Ikona pro upozornění (např. mdi:washing-machine). Chcete-li odeslat bez ikony, ponechte prázdné.", + "notify_start_message": "Zpráva odeslaná při zahájení cyklu. Podporuje zástupný symbol {device}.", + "notify_finish_message": "Zpráva odeslána po skončení cyklu. Podporuje zástupné symboly {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Vyberte číselnou entitu, která obsahuje aktuální cenu elektřiny (např. sensor.electricity_price, input_number.energy_rate). Je-li nastaven, aktivuje zástupný symbol {cost}. Pokud jsou obě nakonfigurovány, má přednost před statickou hodnotou.", + "energy_price_static": "Pevná cena elektřiny za kWh. Je-li nastaven, aktivuje zástupný symbol {cost}. Ignorováno, pokud je výše nakonfigurována entita. Přidejte svůj symbol měny do šablony zprávy, např. {cost} €.", + "notify_reminder_message": "Text jednorázové upomínky odeslal nakonfigurovaný počet minut před odhadovaným koncem. Rozdíl od opakujících se živých aktualizací výše. Podporuje {device}, {minutes}, {program} stojany.", + "notify_timeout_seconds": "Automaticky zamítnout oznámení po tolika sekundách (předán jako společník aplikace 'timeout'). Nastavit na0, aby je udrželi, dokud nebudou propuštěni. Výchozí:0.", + "notify_channel": "Name Zvuk a význam kanálu jsou nakonfigurovány ve společné aplikaci při prvním použití názvu kanálu - WashData pouze nastavuje název kanálu. Zanechat prázdné pro výchozí aplikaci.", + "notify_finish_channel": "Samostatný Android kanál pro dokončený poplach (také používán upomínkou a upozorněním na čekající prádlo), takže může mít svůj vlastní zvuk. Nechte prázdné pro opětovné použití výše uvedeného kanálu.", + "notify_pre_complete_message": "Text opakujícího se živého průběhu se aktualizuje během běhu cyklu. Podporuje zástupné symboly {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Pokročilá nastavení", + "description": "Vylaďte prahy detekce, učení a pokročilé chování. Výchozí nastavení funguje dobře pro většinu konfigurací.", + "sections": { + "suggestions_section": { + "name": "Navrhovaná nastavení", + "description": "K dispozici je {suggestions_count} naučených doporučení. Zaškrtněte 'Použít navrhované hodnoty', chcete-li před uložením zkontrolovat navržené změny.", + "data": { + "apply_suggestions": "Použít navrhované hodnoty" + }, + "data_description": { + "apply_suggestions": "Zaškrtnutím tohoto políčka otevřete krok kontroly navrhovaných hodnot z algoritmu učení. Nic se neukládá automaticky.\n\nDoporučeno (z učení):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekce a prahy výkonu", + "description": "Určuje, kdy je spotřebič považován za ZAPNUTÝ nebo VYPNUTÝ, energetické brány a časování přechodů stavů.", + "data": { + "start_duration_threshold": "Minimální délka spuštění (s)", + "start_energy_threshold": "Počáteční energetický práh (Wh)", + "completion_min_seconds": "Minimální doba běhu dokončení (v sekundách)", + "start_threshold_w": "Počáteční práh (W)", + "stop_threshold_w": "Práh zastavení (W)", + "end_energy_threshold": "Koncový energetický práh (Wh)", + "running_dead_zone": "Běžící mrtvá zóna (sekundy)", + "end_repeat_count": "Ukončit počet opakování", + "min_off_gap": "Minimální mezera mezi cykly (s)", + "sampling_interval": "Vzorkovací interval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorujte krátké skoky napájení kratší než toto trvání (sekundy). Odfiltruje špičky při spouštění (např. 60 W po dobu 2 s) před začátkem skutečného cyklu. Výchozí: 5 s.", + "start_energy_threshold": "Aby byl cyklus potvrzen, musí během fáze START akumulovat alespoň toto množství energie (Wh). Výchozí: 0,005 Wh.", + "completion_min_seconds": "Cykly kratší než tato budou označeny jako „přerušené“, i když skončí přirozeně. Výchozí: 600 s.", + "start_threshold_w": "Aby bylo možné spustit cyklus, musí výkon stoupnout NAD tuto prahovou hodnotu. Nastavte vyšší výkon, než je váš pohotovostní režim. Výchozí: min_power + 1 W.", + "stop_threshold_w": "K UKONČENÍ cyklu musí výkon klesnout POD tuto prahovou hodnotu. Nastavte těsně nad nulu, abyste zabránili šumu spouštějícímu falešné konce. Výchozí: 0,6 * min_power.", + "end_energy_threshold": "Cyklus se ukončí pouze v případě, že energie v posledním okně zpoždění vypnutí je pod touto hodnotou (Wh). Zabraňuje falešným koncům, pokud výkon kolísá blízko nuly. Výchozí: 0,05 Wh.", + "running_dead_zone": "Po spuštění cyklu ignorujte poklesy výkonu na mnoho sekund, abyste zabránili detekci falešného konce během počáteční fáze roztočení. Nastavením na 0 deaktivujete. Výchozí: 0s.", + "end_repeat_count": "Kolikrát musí být splněna podmínka ukončení (výkon pod prahovou hodnotou pro off_delay) za sebou před ukončením cyklu. Vyšší hodnoty zabraňují falešným koncům během pauz. Výchozí: 1.", + "min_off_gap": "Pokud cyklus začne během těchto mnoha sekund od konce předchozího, budou SLOŽENY do jediného cyklu.", + "sampling_interval": "Minimální interval vzorkování v sekundách. Aktualizace rychlejší než tato rychlost budou ignorovány. Výchozí: 30 s (2 s pro pračky, pračky se sušičkou a myčky nádobí)." + } + }, + "matching_section": { + "name": "Párování profilů a učení", + "description": "Určuje, jak agresivně WashData porovnává běžící cykly s naučenými profily a kdy má požádat o zpětnou vazbu.", + "data": { + "profile_match_min_duration_ratio": "Poměr minimálního trvání shody profilu (0,1–1,0)", + "profile_match_interval": "Interval shody profilu (v sekundách)", + "profile_match_threshold": "Práh shody profilu", + "profile_unmatch_threshold": "Prahová hodnota neshody profilu", + "auto_label_confidence": "Spolehlivost automatického označování (0-1)", + "learning_confidence": "Důvěra žádosti o zpětnou vazbu (0–1)", + "suppress_feedback_notifications": "Potlačit upozornění na zpětnou vazbu", + "duration_tolerance": "Tolerance trvání (0–0,5)", + "smoothing_window": "Vyhlazovací okno (ukázky)", + "profile_duration_tolerance": "Tolerance trvání shody profilu (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Poměr minimální doby trvání pro přizpůsobení profilu. Provozní cyklus musí být alespoň tento zlomek trvání profilu, aby odpovídal. Nižší = dřívější shoda. Výchozí: 0,50 (50 %).", + "profile_match_interval": "Jak často (v sekundách) se během cyklu pokoušet o spárování profilu. Nižší hodnoty poskytují rychlejší detekci, ale využívají více CPU. Výchozí: 300 s (5 minut).", + "profile_match_threshold": "Minimální skóre podobnosti DTW (0,0–1,0) požadované k tomu, aby byl profil považován za shodu. Vyšší = přísnější shoda, méně falešně pozitivních výsledků. Výchozí: 0,4.", + "profile_unmatch_threshold": "Skóre podobnosti DTW (0,0–1,0), pod kterým je dříve shodný profil zamítnut. Měla by být nižší než práh shody, aby se zabránilo blikání. Výchozí: 0,35.", + "auto_label_confidence": "Po dokončení automaticky označte cykly s touto spolehlivostí nebo nad ní (0,0–1,0).", + "learning_confidence": "Minimální spolehlivost pro vyžádání ověření uživatele prostřednictvím události (0,0–1,0).", + "suppress_feedback_notifications": "Pokud je tato možnost povolena, bude WashData stále interně sledovat a vyžadovat zpětnou vazbu, ale nebude zobrazovat trvalá oznámení s žádostí o potvrzení cyklů. Užitečné, když jsou cykly vždy správně detekovány a výzvy vám připadají rušivé.", + "duration_tolerance": "Tolerance pro odhady zbývajícího času během běhu (0,0–0,5 odpovídá 0–50 %).", + "smoothing_window": "Počet nedávných odečtů výkonu použitých pro vyhlazení klouzavého průměru.", + "profile_duration_tolerance": "Tolerance použitá při shodě profilů pro celkový rozptyl trvání (0,0–0,5). Výchozí chování: ±25 %." + } + }, + "timing_section": { + "name": "Časování, údržba a ladění", + "description": "Hlídací pes, reset průběhu, automatická údržba a zpřístupnění ladicích entit.", + "data": { + "watchdog_interval": "Interval hlídacího psa (v sekundách)", + "no_update_active_timeout": "Časový limit bez aktualizací (s)", + "progress_reset_delay": "Prodleva resetování průběhu (sekundy)", + "auto_maintenance": "Povolit automatickou údržbu", + "expose_debug_entities": "Vystavit ladicí entity", + "save_debug_traces": "Uložit stopy ladění" + }, + "data_description": { + "watchdog_interval": "Vteřiny mezi kontrolami hlídacího psa při běhu. Výchozí: 5 s. VAROVÁNÍ: Ujistěte se, že je VYŠŠÍ než interval aktualizace vašeho senzoru, abyste se vyhnuli falešným zastavením (např. pokud se senzor aktualizuje každých 60 s, použijte 65s+).", + "no_update_active_timeout": "Pro senzory publikování při změně: vynutit ukončení AKTIVNÍHO cyklu pouze v případě, že po tolik sekund nedorazí žádné aktualizace. Dokončení při nízké spotřebě stále používá zpoždění vypnutí.", + "progress_reset_delay": "Po dokončení cyklu (100 %) počkejte tolik sekund nečinnosti, než resetujete průběh na 0 %. Výchozí: 300 s.", + "auto_maintenance": "Povolit automatickou údržbu (opravit vzorky, sloučit fragmenty).", + "expose_debug_entities": "Zobrazit pokročilé senzory (Confidence, Phase, Ambiguity) pro ladění.", + "save_debug_traces": "Ukládejte podrobné údaje o hodnocení a sledování napájení v historii (zvyšuje využití úložiště)." + } + }, + "anti_wrinkle_section": { + "name": "Ochranný štít proti vráskám (sušičky)", + "description": "Ignorujte otáčky bubnu po skončení cyklu, aby nevznikaly duchové cyklů. Pouze pro sušičky / pračky se sušičkou.", + "data": { + "anti_wrinkle_enabled": "Ochranný štít proti vráskám (pouze sušička)", + "anti_wrinkle_max_power": "Maximální výkon proti vráskám (W)", + "anti_wrinkle_max_duration": "Maximální trvání proti vráskám (s)", + "anti_wrinkle_exit_power": "Výstupní výkon proti vráskám (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorujte krátké otáčky bubnu s nízkou spotřebou energie po skončení cyklu, abyste předešli cyklům „duchů“ (pouze sušička/pračka-sušička).", + "anti_wrinkle_max_power": "Shluky s tímto výkonem nebo pod ním jsou považovány za rotace proti vráskám namísto nových cyklů. Platí pouze v případě, že je aktivován štít proti vráskám.", + "anti_wrinkle_max_duration": "Maximální délka dávky, kterou lze považovat za rotaci proti vráskám. Platí pouze v případě, že je aktivován štít proti vráskám.", + "anti_wrinkle_exit_power": "Pokud výkon klesne pod tuto úroveň, okamžitě ukončete ochranu proti vráskám (true off). Platí pouze v případě, že je aktivován štít proti vráskám." + } + }, + "delay_start_section": { + "name": "Detekce odloženého startu", + "description": "Považujte trvalý pohotovostní režim s nízkou spotřebou za stav 'Čekání na spuštění', dokud cyklus skutečně nezačne.", + "data": { + "delay_start_detect_enabled": "Povolit detekci odloženého startu", + "delay_confirm_seconds": "Odložený start: doba potvrzení pohotovostního režimu (s)", + "delay_timeout_hours": "Odložený start: Maximální doba čekání (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Je-li povoleno, krátký výpadek nízké spotřeby následovaný trvalým napájením v pohotovostním režimu je detekován jako zpožděný start. Senzor stavu zobrazuje během zpoždění 'Čeká na spuštění'. Dokud cyklus skutečně nezačne, není sledován žádný čas programu ani průběh. Vyžaduje, aby start_threshold_w byl nastaven nad odběrovou špičkou.", + "delay_confirm_seconds": "Výkon musí zůstat mezi prahem zastavení (W) a počátečním prahem (W) po tento počet sekund, než WashData přejde do stavu 'Čekání na spuštění'. Odfiltruje krátké špičky při procházení menu. Výchozí: 60 s.", + "delay_timeout_hours": "Bezpečnostní časový limit: pokud je stroj i po tomto počtu hodin stále ve stavu „Čekání na spuštění“, WashData se resetuje na Vypnuto. Výchozí: 8 h." + } + }, + "external_triggers_section": { + "name": "Externí spouštěče, dveře a pauza", + "description": "Volitelný externí snímač konce cyklu, snímač dveří pro detekci pauzy / vyklizení a přepínač vypnutí napájení při pauze.", + "data": { + "external_end_trigger_enabled": "Povolit externí spouštění konce cyklu", + "external_end_trigger": "Externí snímač konce cyklu", + "external_end_trigger_inverted": "Invertovat logiku spouštěče (spouštěč je vypnutý)", + "door_sensor_entity": "Snímač dveří", + "pause_cuts_power": "Vypnout napájení při pozastavení", + "switch_entity": "Přepnout entitu (pro pozastavení vypnutí)", + "notify_unload_delay_minutes": "Zpoždění oznámení o čekání na prádelnu (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Povolit monitorování externího binárního senzoru pro spuštění dokončení cyklu.", + "external_end_trigger": "Vyberte entitu binárního senzoru. Když se tento senzor spustí, aktuální cyklus skončí se stavem „dokončeno“.", + "external_end_trigger_inverted": "Ve výchozím nastavení se spoušť spustí, když se senzor zapne. Zaškrtněte toto políčko, chcete-li místo toho aktivovat, když se senzor vypne.", + "door_sensor_entity": "Volitelný binární senzor pro dvířka stroje (zapnuto = otevřeno, vypnuto = zavřeno). Když se dvířka otevřou během aktivního cyklu, WashData potvrdí cyklus jako záměrně pozastavený. Po skončení cyklu, pokud jsou dvířka stále zavřená, se stav změní na 'Vyčistit', dokud se dvířka neotevře. Poznámka: zavření dvířek NEOBNOVÍ automaticky pozastavený cyklus – obnovení musí být spuštěno ručně pomocí tlačítka nebo služby Resume Cycle.", + "pause_cuts_power": "Při pozastavení pomocí tlačítka nebo služby Pause Cycle také vypněte nakonfigurovanou entitu přepínače. Platí pouze v případě, že je pro toto zařízení nakonfigurována entita přepínače.", + "switch_entity": "Entita přepínače, která se má přepnout při pozastavení nebo obnovení. Vyžadováno, když je povolena možnost „Cut Power When Pausing“.", + "notify_unload_delay_minutes": "Odešlete upozornění prostřednictvím kanálu upozornění na ukončení poté, co cyklus skončí a dveře nebyly po tolik minut otevřeny (vyžaduje čidlo dveří). Nastavením na 0 deaktivujete. Výchozí: 60 min." + } + }, + "pump_section": { + "name": "Monitor čerpadla", + "description": "Pouze pro zařízení typu čerpadlo. Vyvolá událost, pokud cyklus čerpadla běží déle než tato doba.", + "data": { + "pump_stuck_duration": "Práh upozornění na zaseknutí pumpy (s, pouze pumpa)" + }, + "data_description": { + "pump_stuck_duration": "Pokud cyklus pumpy stále běží i po tomto počtu sekund, událost ha_washdata_pump_stuck se spustí jednou. Použijte toto k detekci zaseknutého motoru (typický běh kalového čerpadla je kratší než 60 s). Výchozí: 1800 s (30 min). Pouze typ čerpadla." + } + }, + "device_link_section": { + "name": "Odkaz na zařízení", + "description": "Volitelně připojte toto zařízení Washington Data k existujícímu zařízení Home Assistant (např. inteligentní zástrčka nebo samotný spotřebič). Zařízení Washington Data je pak zobrazeno jako \"Připojeno přes\" toto zařízení. Nechte prázdné, aby se Washington Data jako samostatné zařízení.", + "data": { + "linked_device": "Spojené zařízení" + }, + "data_description": { + "linked_device": "Zvolte zařízení, ke kterému můžete připojit WashData. To přidává odkaz na \"Připojeno přes\" v registru zařízení; Washington Data si nechává svou vlastní kartu zařízení a subjekty. Vymazat výběr odstranit odkaz." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika a údržba", + "description": "Spusťte akce údržby, jako je slučování fragmentovaných cyklů nebo migrace uložených dat.\n\n**Využití úložiště**\n\n- Velikost souboru: {file_size_kb} KB\n- Cyklů: {cycle_count}\n- Profilů: {profile_count}\n- Stopy ladění: {debug_count}", + "menu_options": { + "reprocess_history": "Údržba: Přepracování a optimalizace dat", + "clear_debug_data": "Vymazat data ladění (uvolnit místo)", + "wipe_history": "Vymazat VŠECHNA data tohoto zařízení (nevratné)", + "export_import": "Export/import JSON s nastavením (kopírovat/vložit)", + "menu_back": "← Zpět" + } + }, + "clear_debug_data": { + "title": "Vymazat data ladění", + "description": "Opravdu chcete smazat všechna uložená trasování ladění? Tím se uvolní místo, ale odstraní se podrobné informace o hodnocení z minulých cyklů." + }, + "manage_cycles": { + "title": "Správa cyklů", + "description": "Nedávné cykly:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatické označení starých cyklů", + "select_cycle_to_label": "Specifický cyklus štítku", + "select_cycle_to_delete": "Smazat cyklus", + "interactive_editor": "Interaktivní editor sloučení/rozdělení", + "trim_cycle_select": "Data cyklu trimování", + "menu_back": "← Zpět" + } + }, + "manage_cycles_empty": { + "title": "Správa cyklů", + "description": "Dosud nebyly zaznamenány žádné cykly.", + "menu_options": { + "auto_label_cycles": "Automatické označení starých cyklů", + "menu_back": "← Zpět" + } + }, + "manage_profiles": { + "title": "Správa profilů", + "description": "Shrnutí profilu:\n{profile_summary}", + "menu_options": { + "create_profile": "Vytvořit nový profil", + "edit_profile": "Upravit/Přejmenovat profil", + "delete_profile_select": "Smazat profil", + "profile_stats": "Statistiky profilu", + "cleanup_profile": "Vyčistit historii – graf a smazání", + "assign_profile_phases_select": "Přiřadit fázové rozsahy", + "menu_back": "← Zpět" + } + }, + "manage_profiles_empty": { + "title": "Správa profilů", + "description": "Zatím nebyly vytvořeny žádné profily.", + "menu_options": { + "create_profile": "Vytvořit nový profil", + "menu_back": "← Zpět" + } + }, + "manage_phase_catalog": { + "title": "Správa katalogu fází", + "description": "Katalog fází (výchozí pro toto zařízení + vlastní fáze):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Vytvořit novou fázi", + "phase_catalog_edit_select": "Upravit fázi", + "phase_catalog_delete": "Smazat fázi", + "menu_back": "← Zpět" + } + }, + "phase_catalog_create": { + "title": "Vytvořit vlastní fázi", + "description": "Vytvořte vlastní název a popis fáze a vyberte typ zařízení, na který se vztahuje.", + "data": { + "target_device_type": "Typ cílového zařízení", + "phase_name": "Název fáze", + "phase_description": "Popis fáze" + } + }, + "phase_catalog_edit_select": { + "title": "Upravit vlastní fázi", + "description": "Vyberte vlastní fázi, kterou chcete upravit.", + "data": { + "phase_name": "Vlastní fáze" + } + }, + "phase_catalog_edit": { + "title": "Upravit vlastní fázi", + "description": "Fáze úprav: {phase_name}", + "data": { + "phase_name": "Název fáze", + "phase_description": "Popis fáze" + } + }, + "phase_catalog_delete": { + "title": "Smazat vlastní fázi", + "description": "Vyberte vlastní fázi, kterou chcete odstranit. Všechny přiřazené rozsahy pomocí této fáze budou odstraněny.", + "data": { + "phase_name": "Vlastní fáze" + } + }, + "assign_profile_phases": { + "title": "Přiřadit fázové rozsahy", + "description": "Náhled fáze pro profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuální rozsahy:**\n{current_ranges}\n\nPomocí akcí níže můžete přidat, upravit, odstranit nebo uložit rozsahy.", + "menu_options": { + "assign_profile_phases_add": "Přidat fázový rozsah", + "assign_profile_phases_edit_select": "Upravit rozsah fází", + "assign_profile_phases_delete": "Smazat fázový rozsah", + "phase_ranges_clear": "Vymazat všechny rozsahy", + "assign_profile_phases_auto_detect": "Automatická detekce fází", + "phase_ranges_save": "Uložit a vrátit", + "menu_back": "← Zpět" + } + }, + "assign_profile_phases_add": { + "title": "Přidat fázový rozsah", + "description": "Přidejte jeden fázový rozsah k aktuálnímu návrhu.", + "data": { + "phase_name": "Fáze", + "start_min": "Počáteční minuta", + "end_min": "Konec minuty" + } + }, + "assign_profile_phases_edit_select": { + "title": "Upravit rozsah fází", + "description": "Vyberte rozsah, který chcete upravit.", + "data": { + "range_index": "Fázový rozsah" + } + }, + "assign_profile_phases_edit": { + "title": "Upravit rozsah fází", + "description": "Aktualizujte vybraný fázový rozsah.", + "data": { + "phase_name": "Fáze", + "start_min": "Počáteční minuta", + "end_min": "Konec minuty" + } + }, + "assign_profile_phases_delete": { + "title": "Smazat fázový rozsah", + "description": "Vyberte rozsah, který chcete z konceptu odstranit.", + "data": { + "range_index": "Fázový rozsah" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automaticky detekované fáze", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Automaticky zjištěno {detected_count} fází.** Níže vyberte akci.", + "data": { + "action": "Akce" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Pojmenujte zjištěné fáze", + "description": "Profil: **{profile_name}**\n\n**Zjištěno {detected_count} fází:**\n{ranges_summary}\n\nPřiřaďte každé fázi název." + }, + "assign_profile_phases_select": { + "title": "Přiřadit fázové rozsahy", + "description": "Vyberte profil pro konfiguraci fázových rozsahů.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistiky profilu", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Vytvořit nový profil", + "description": "Vytvořte nový profil ručně nebo z minulého cyklu.\n\nPříklady názvů profilů: 'Delikatesy', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Název profilu", + "reference_cycle": "Referenční cyklus (volitelné)", + "manual_duration": "Manuální trvání (minuty)" + } + }, + "edit_profile": { + "title": "Upravit profil", + "description": "Vyberte profil, který chcete přejmenovat", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Upravit podrobnosti profilu", + "description": "Aktuální profil: {current_name}", + "data": { + "new_name": "Název profilu", + "manual_duration": "Manuální trvání (minuty)" + } + }, + "delete_profile_select": { + "title": "Smazat profil", + "description": "Vyberte profil, který chcete odstranit", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potvrďte Smazat profil", + "description": "⚠️ Tímto trvale smažete profil: {profile_name}", + "data": { + "unlabel_cycles": "Odebrat štítek z cyklů pomocí tohoto profilu" + } + }, + "auto_label_cycles": { + "title": "Automatické označení starých cyklů", + "description": "Nalezeno celkem {total_count} cyklů. Profily: {profiles}", + "data": { + "confidence_threshold": "Minimální důvěra (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Vyberte cyklus pro označení", + "description": "✓ = dokončeno, ⚠ = obnoveno, ✗ = přerušeno", + "data": { + "cycle_id": "Cyklus" + } + }, + "select_cycle_to_delete": { + "title": "Smazat cyklus", + "description": "⚠️ Tímto trvale smažete vybraný cyklus", + "data": { + "cycle_id": "ID cyklu" + } + }, + "label_cycle": { + "title": "Označit cyklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Název nového profilu (pokud se vytváří)" + } + }, + "post_process": { + "title": "Historie procesů (sloučení/rozdělení)", + "description": "Vyčistěte historii cyklů sloučením fragmentovaných běhů a rozdělením nesprávně sloučených cyklů v rámci časového okna. Zadejte počet hodin pro ohlédnutí zpět (pro všechny použijte 999999).", + "data": { + "time_range": "Hloubka pohledu (hodiny)", + "gap_seconds": "Sloučení/rozdělení mezery (v sekundách)" + }, + "data_description": { + "time_range": "Počet hodin pro vyhledávání fragmentovaných cyklů ke sloučení. Ke zpracování všech historických dat použijte číslo 999999.", + "gap_seconds": "Práh pro rozhodnutí rozdělení vs. sloučení. Mezery KRATŠÍ než toto jsou sloučeny. Mezery DELŠÍ než toto jsou rozděleny. Snižte toto, chcete-li vynutit rozdělení u cyklu, který byl sloučen nesprávně." + } + }, + "cleanup_profile": { + "title": "Historie vyčištění – vyberte Profil", + "description": "Vyberte profil, který chcete zobrazit. Tím se vygeneruje graf zobrazující všechny minulé cykly pro tento profil, který pomůže identifikovat odlehlé hodnoty.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Vyčistit historii – graf a smazání", + "description": "![Graf]({graph_url})\n\n**Vizualizace {profile_name}**\n\nGraf znázorňuje jednotlivé cykly jako barevné čáry. Identifikujte odlehlé hodnoty (čáry daleko od skupiny) a odpovídající jejich barvě v seznamu níže, abyste je odstranili.\n\n**Vyberte cykly, které chcete TRVALO smazat:**", + "data": { + "cycles_to_delete": "Cykly k odstranění (zaškrtněte odpovídající barvy)" + } + }, + "interactive_editor": { + "title": "Interaktivní editor sloučení/rozdělení", + "description": "Vyberte ruční akci, kterou chcete provést v historii cyklu.", + "menu_options": { + "editor_split": "Rozdělit cyklus (najít mezery)", + "editor_merge": "Sloučit cykly (spojit fragmenty)", + "editor_delete": "Smazat cykly", + "menu_back": "← Zpět" + } + }, + "editor_select": { + "title": "Vyberte Cykly", + "description": "Vyberte cyklus(y), který chcete zpracovat.\n\n{info_text}", + "data": { + "selected_cycles": "Cykly" + } + }, + "editor_configure": { + "title": "Konfigurovat a použít", + "description": "{preview_md}", + "data": { + "confirm_action": "Akce", + "merged_profile": "Výsledný profil", + "new_profile_name": "Název nového profilu", + "confirm_commit": "Ano, potvrzuji tuto akci", + "segment_0_profile": "Profil segmentu 1", + "segment_1_profile": "Profil segmentu 2", + "segment_2_profile": "Profil segmentu 3", + "segment_3_profile": "Profil segmentu 4", + "segment_4_profile": "Profil segmentu 5", + "segment_5_profile": "Profil segmentu 6", + "segment_6_profile": "Profil segmentu 7", + "segment_7_profile": "Profil segmentu 8", + "segment_8_profile": "Profil segmentu 9", + "segment_9_profile": "Profil segmentu 10" + } + }, + "editor_split_params": { + "title": "Konfigurace rozdělení", + "description": "Upravte citlivost pro rozdělení tohoto cyklu. Jakákoli mezera naprázdno delší než tato způsobí rozdělení.", + "data": { + "split_mode": "Metoda rozdělení" + } + }, + "editor_split_auto_params": { + "title": "Konfigurace automatického rozdělení", + "description": "Upravte citlivost rozdělení tohoto cyklu. Jakákoli nečinná mezera delší než tato způsobí rozdělení.", + "data": { + "min_gap_seconds": "Práh mezery pro rozdělení (sekundy)" + } + }, + "editor_split_manual_params": { + "title": "Ruční časová razítka rozdělení", + "description": "{preview_md}Okno cyklu: **{cycle_start_wallclock} → {cycle_end_wallclock}** dne {cycle_date}.\n\nZadejte jedno nebo více časových razítek rozdělení (`HH:MM` nebo `HH:MM:SS`), jedno na řádek. Cyklus bude rozdělen při každém časovém razítku — N časových razítek vytvoří N+1 segmentů.", + "data": { + "split_timestamps": "Časy rozdělení" + } + }, + "reprocess_history": { + "title": "Údržba: Přepracování a optimalizace dat", + "description": "Provádí hloubkové čištění historických dat:\n1. Ořízne naměřené hodnoty s nulovou spotřebou (ticho).\n2. Opravuje načasování/trvání cyklu.\n3. Přepočítá všechny statistické modely (obálky).\n\n**Spusťte toto, chcete-li opravit problémy s daty nebo použít nové algoritmy.**" + }, + "wipe_history": { + "title": "Vymazat historii (pouze testování)", + "description": "⚠️ Tímto trvale smažete VŠECHNY uložené cykly a profily pro toto zařízení. Toto nelze vrátit zpět!" + }, + "export_import": { + "title": "Export/import JSON", + "description": "Cykly kopírování/vkládání, profily a nastavení pro toto zařízení. Exportujte níže nebo pro import vložte JSON.", + "data": { + "mode": "Akce", + "json_payload": "Dokončete konfiguraci JSON" + }, + "data_description": { + "mode": "Zvolte Export pro zkopírování aktuálních dat a nastavení, nebo Import pro vložení exportovaných dat z jiného zařízení WashData.", + "json_payload": "Pro export: zkopírujte tento JSON (zahrnuje cykly, profily a všechna doladěná nastavení). Pro import: vložte JSON exportovaný z WashData." + } + }, + "record_cycle": { + "title": "Záznamový cyklus", + "description": "Ručně zaznamenejte cyklus, abyste vytvořili čistý profil bez rušení.\n\nStav: **{status}**\nDoba trvání: {duration} s\nUkázky: {samples}", + "menu_options": { + "record_refresh": "Stav obnovení", + "record_stop": "Zastavit nahrávání (Uložit a zpracovat)", + "record_start": "Spusťte nový záznam", + "record_process": "Zpracovat poslední záznam (oříznout a uložit)", + "record_discard": "Zahodit poslední nahrávku", + "menu_back": "← Zpět" + } + }, + "record_process": { + "title": "Procesní záznam", + "description": "![Graf]({graph_url})\n\n**Graf ukazuje nezpracovaný záznam.** Modrá = Zachovat, Červená = Navrhované oříznutí.\n*Graf je statický a neaktualizuje se změnami formuláře.*\n\nNahrávání zastaveno. Oříznutí jsou zarovnána podle zjištěné vzorkovací frekvence (~{sampling_rate} s).\n\nNezpracovaná délka: {duration} s\nUkázky: {samples}", + "data": { + "head_trim": "Oříznutí začátku (sekundy)", + "tail_trim": "Konec oříznutí (sekundy)", + "save_mode": "Uložit cíl", + "profile_name": "Název profilu" + } + }, + "learning_feedbacks": { + "title": "Zpětná vazba na učení", + "description": "Nevyřízená zpětná vazba: {count}\n\n{pending_table}\n\nVyberte požadavek na kontrolu cyklu, který chcete zpracovat.", + "menu_options": { + "learning_feedbacks_pick": "Zkontrolovat nevyřízenou zpětnou vazbu", + "learning_feedbacks_dismiss_all": "Zamítnout veškerou nevyřízenou zpětnou vazbu", + "menu_back": "← Zpět" + } + }, + "learning_feedbacks_pick": { + "title": "Vyberte zpětnou vazbu ke kontrole", + "description": "Vyberte požadavek na kontrolu cyklu, který chcete otevřít.", + "data": { + "selected_feedback": "Vyberte cyklus ke kontrole" + } + }, + "learning_feedbacks_empty": { + "title": "Zpětná vazba na učení", + "description": "Nebyly nalezeny žádné nevyřízené žádosti o zpětnou vazbu.", + "menu_options": { + "menu_back": "← Zpět" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Zamítnout veškerou čekající zpětnou vazbu", + "description": "Chystáte se zamítnout **{count}** čekajících žádostí o zpětnou vazbu. Zamítnuté cykly zůstanou v historii, ale nebudou přispívat novým signálem učení. Tuto akci nelze vrátit zpět.\n\n✅ Zaškrtněte políčko níže a kliknutím na **Odeslat** vše zamítněte.\n❌ Nechte políčko nezaškrtnuté a kliknutím na **Odeslat** akci zrušte a vraťte se zpět.", + "data": { + "confirm_dismiss_all": "Potvrdit: zamítnout všechny nevyřízené žádosti o zpětnou vazbu" + } + }, + "resolve_feedback": { + "title": "Vyřešte zpětnou vazbu", + "description": "{comparison_img}{candidates_table}\n**Zjištěný profil**: {detected_profile} ({confidence_pct}%)\n**Odhadovaná délka**: {est_duration_min} min\n**Skutečná délka**: {act_duration_min} min\n\n__Vyberte akci níže:__", + "data": { + "action": "Co chcete udělat?", + "corrected_profile": "Správný program (pokud opravujete)", + "corrected_duration": "Správná doba trvání v sekundách (pokud opravujete)" + } + }, + "trim_cycle_select": { + "title": "Ořezávací cyklus - Vyberte cyklus", + "description": "Vyberte cyklus, který chcete oříznout. ✓ = dokončeno, ⚠ = obnoveno, ✗ = přerušeno", + "data": { + "cycle_id": "Cyklus" + } + }, + "trim_cycle": { + "title": "Ořezávací cyklus", + "description": "Aktuální okno: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akce" + } + }, + "trim_cycle_start": { + "title": "Nastavte počáteční bod ořezu", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuální začátek: **{current_wallclock}** (+{current_offset_min} min od začátku cyklu)\n\nPomocí níže uvedeného nástroje pro výběr hodin vyberte nový čas zahájení.", + "data": { + "trim_start_time": "Nový čas zahájení", + "trim_start_min": "Nový start (minuty od začátku cyklu)" + } + }, + "trim_cycle_end": { + "title": "Nastavte konec oříznutí", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuální konec: **{current_wallclock}** (+{current_offset_min} min od začátku cyklu)\n\nPomocí níže uvedeného nástroje pro výběr hodin vyberte nový čas ukončení.", + "data": { + "trim_end_time": "Nový čas konce", + "trim_end_min": "Nový konec (minuty od začátku cyklu)" + } + } + }, + "error": { + "import_failed": "Import se nezdařil. Vložte platný soubor JSON exportu WashData.", + "select_exactly_one": "Vyberte pro tuto akci přesně jeden cyklus.", + "select_at_least_one": "Vyberte pro tuto akci alespoň jeden cyklus.", + "select_at_least_two": "Vyberte pro tuto akci alespoň dva cykly.", + "profile_exists": "Profil s tímto názvem již existuje", + "rename_failed": "Přejmenování profilu se nezdařilo. Profil možná neexistuje nebo je již obsazeno nové jméno.", + "assignment_failed": "Přiřazení profilu k cyklu se nezdařilo", + "duplicate_phase": "Fáze s tímto názvem již existuje.", + "phase_not_found": "Vybraná fáze nebyla nalezena.", + "invalid_phase_name": "Název fáze nemůže být prázdný.", + "phase_name_too_long": "Název fáze je příliš dlouhý.", + "invalid_phase_range": "Fázový rozsah je neplatný. Konec musí být větší než začátek.", + "invalid_phase_timestamp": "Časové razítko je neplatné. Použijte formát RRRR-MM-DD HH:MM nebo HH:MM.", + "overlapping_phase_ranges": "Fázové rozsahy se překrývají. Upravte rozsahy a zkuste to znovu.", + "incomplete_phase_row": "Každý řádek fáze musí obsahovat fázi plus úplné počáteční/koncové hodnoty pro vybraný režim.", + "timestamp_mode_cycle_required": "Při použití režimu časového razítka vyberte zdrojový cyklus.", + "no_phase_ranges": "Pro tuto akci zatím nejsou k dispozici žádné fázové rozsahy.", + "feedback_notification_title": "WashData: Ověřit cyklus ({device})", + "feedback_notification_message": "**Zařízení**: {device}\n**Program**: {program} (spolehlivost {confidence} %)\n**Čas**: {time}\n\nWashData potřebuje vaši pomoc k ověření tohoto zjištěného cyklu.\n\nPřejděte do **Nastavení > Zařízení a služby > WashData > Konfigurovat > Zpětná vazba učení** a potvrďte nebo opravte tento výsledek.", + "suggestions_ready_notification_title": "WashData: Navrhovaná nastavení připravena ({device})", + "suggestions_ready_notification_message": "Senzor **Suggested Settings** nyní hlásí **{count}** praktická doporučení.\n\nChcete-li je zkontrolovat a použít: **Nastavení > Zařízení a služby > WashData > Konfigurovat > Pokročilá nastavení > Použít navrhované hodnoty**.\n\nNávrhy jsou volitelné a před uložením se zobrazí ke kontrole.", + "trim_range_invalid": "Začátek musí být před koncem. Upravte si body střihu.", + "trim_failed": "Oříznutí se nezdařilo. Cyklus již možná neexistuje nebo je okno prázdné.", + "invalid_split_timestamp": "Časové razítko je neplatné nebo mimo okno cyklu. Použijte HH:MM nebo HH:MM:SS v rámci okna cyklu.", + "no_split_segments_found": "Z těchto časových razítek nebylo možné vytvořit žádné platné segmenty. Ujistěte se, že každé časové razítko spadá do okna cyklu a segmenty jsou dlouhé alespoň 1 minutu.", + "auto_tune_suggestion": "{device_type} {device_title} detekovaných cyklů duchů. Navrhovaná změna min_power: {current_min}W -> {new_min}W (nepoužije se automaticky).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} detekovaných cyklů duchů. Navrhovaná změna min_power: {current_min}W -> {new_min}W (nepoužije se automaticky)." + }, + "abort": { + "no_cycles_found": "Nebyly nalezeny žádné cykly", + "no_split_segments_found": "Ve vybraném cyklu nebyly nalezeny žádné rozdělitelné segmenty.", + "cycle_not_found": "Vybraný cyklus se nepodařilo načíst.", + "no_power_data": "Pro zvolený cyklus nejsou k dispozici žádná data sledování výkonu.", + "no_profiles_found": "Nebyly nalezeny žádné profily. Nejprve si vytvořte profil.", + "no_profiles_for_matching": "Nejsou k dispozici žádné profily pro shodu. Nejprve vytvořte profily.", + "no_unlabeled_cycles": "Všechny cykly jsou již označeny", + "no_suggestions": "Zatím nejsou k dispozici žádné navrhované hodnoty. Proveďte několik cyklů a zkuste to znovu.", + "no_predictions": "Žádné předpovědi", + "no_custom_phases": "Nebyly nalezeny žádné vlastní fáze.", + "reprocess_success": "Úspěšně znovu zpracováno {count} cyklů.", + "debug_data_cleared": "Úspěšně vymazána data ladění z {count} cyklů." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Spuštění cyklu", + "cycle_finish": "Dokončení cyklu", + "cycle_live": "Živý pokrok" + } + }, + "common_text": { + "options": { + "unlabeled": "(Neoznačeno)", + "create_new_profile": "Vytvořit nový profil", + "remove_label": "Odebrat štítek", + "no_reference_cycle": "(Žádný referenční cyklus)", + "all_device_types": "Všechny typy zařízení", + "current_suffix": "(aktuální)", + "editor_select_info": "Vyberte 1 cyklus pro rozdělení nebo 2+ cykly pro sloučení.", + "editor_select_info_split": "Vyberte 1 cyklus k rozdělení.", + "editor_select_info_merge": "Vyberte 2+ cyklů ke sloučení.", + "editor_select_info_delete": "Vyberte 1+ cyklů ke smazání.", + "no_cycles_recorded": "Dosud nebyly zaznamenány žádné cykly.", + "profile_summary_line": "- **{name}**: {count} cyklů, prům. {avg} m", + "no_profiles_created": "Zatím nebyly vytvořeny žádné profily.", + "phase_preview": "Náhled fáze", + "phase_preview_no_curve": "Křivka průměrného profilu zatím není k dispozici. Spustit/označit více cyklů pro tento profil.", + "average_power_curve": "Průměrná výkonová křivka", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cyklů, průměr ~{duration} m)", + "profile_option_short_fmt": "{name} ({count} cyklů, ~{duration} m)", + "deleted_cycles_from_profile": "Smazáno {count} cyklů z {profile}.", + "not_enough_data_graph": "K vytvoření grafu není dostatek dat.", + "no_profiles_found": "Nebyly nalezeny žádné profily.", + "cycle_info_fmt": "Cyklus: {start}, {duration}m, aktuální: {label}", + "top_candidates_header": "### Nejlepší kandidáti", + "tbl_profile": "Profil", + "tbl_confidence": "Důvěra", + "tbl_correlation": "Korelace", + "tbl_duration_match": "Shoda trvání", + "detected_profile": "Zjištěný profil", + "estimated_duration": "Odhadovaná doba trvání", + "actual_duration": "Skutečná doba trvání", + "choose_action": "__Vyberte akci níže:__", + "tbl_count": "Počet", + "tbl_avg": "Prům", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energie (prům.)", + "tbl_energy_total": "Energie (celkem)", + "tbl_consistency": "Skládat se.", + "tbl_last_run": "Poslední běh", + "graph_legend_title": "Legenda grafu", + "graph_legend_body": "Modrý pruh představuje minimální a maximální pozorovaný rozsah odběru energie. Čára ukazuje křivku průměrného výkonu.", + "recording_preview": "Náhled nahrávání", + "trim_start": "Oříznutí začátku", + "trim_end": "Oříznout konec", + "split_preview_title": "Rozdělit náhled", + "split_preview_found_fmt": "Počet nalezených segmentů: {count}.", + "split_preview_confirm_fmt": "Kliknutím na Potvrdit rozdělíte tento cyklus do {count} samostatných cyklů.", + "merge_preview_title": "Náhled sloučení", + "merge_preview_joining_fmt": "Připojování k {count} cyklům. Mezery budou vyplněny hodnotami 0W.", + "editor_delete_preview_title": "Náhled smazání", + "editor_delete_preview_intro": "Vybrané cykly budou trvale smazány:", + "editor_delete_preview_confirm": "Kliknutím na Potvrdit tyto záznamy cyklů trvale smažete.", + "no_power_preview": "*Pro náhled nejsou k dispozici žádné údaje o napájení.*", + "profile_comparison": "Porovnání profilu", + "actual_cycle_label": "Tento cyklus (skutečný)", + "storage_usage_header": "Využití úložiště", + "storage_file_size": "Velikost souboru", + "storage_cycles": "Cykly", + "storage_profiles": "Profily", + "storage_debug_traces": "Trasování ladění", + "overview_suffix": "Přehled", + "phase_preview_for_profile": "Náhled fáze pro profil", + "current_ranges_header": "Aktuální rozsahy", + "assign_phases_help": "Pomocí akcí níže můžete přidat, upravit, odstranit nebo uložit rozsahy.", + "profile_summary_header": "Shrnutí profilu", + "recent_cycles_header": "Nedávné cykly", + "trim_cycle_preview_title": "Náhled cyklu trimování", + "trim_cycle_preview_no_data": "Pro tento cyklus nejsou k dispozici žádné údaje o výkonu.", + "trim_cycle_preview_kept_suffix": "zachováno", + "table_program": "Program", + "table_when": "Kdy", + "table_length": "Délka", + "table_match": "Shoda", + "table_profile": "Profil", + "table_cycles": "Cykly", + "table_avg_length": "Prům. délka", + "table_last_run": "Poslední běh", + "table_avg_energy": "Prům. energie", + "table_detected_program": "Detekovaný program", + "table_confidence": "Důvěra", + "table_reported": "Nahlášeno", + "phase_builtin_suffix": "(Vestavěný)", + "phase_none_available": "Nejsou k dispozici žádné fáze.", + "settings_deprecation_warning": "⚠️ **Zastaralý typ zařízení:** Odstranění zařízení {device_type} je naplánováno v budoucí verzi. Porovnávací kanál WashData neposkytuje spolehlivé výsledky pro tuto třídu zařízení. Vaše integrace bude fungovat i během období ukončení podpory; chcete-li toto varování ztišit, přepněte **Typ zařízení** níže na jeden z podporovaných typů (pračka, sušička, kombinovaná pračka se sušičkou, myčka nádobí, fritéza, pekárna chleba nebo pumpa), nebo na **Jiné (pokročilé)**, pokud váš spotřebič neodpovídá žádnému z podporovaných typů. **Jiné (pokročilé)** dodává záměrně generická výchozí nastavení, která nejsou vyladěna pro žádné konkrétní zařízení, takže si budete muset sami nakonfigurovat prahové hodnoty, časové limity a odpovídající parametry; všechna vaše stávající nastavení jsou po přepnutí zachována.", + "phase_other_device_types": "Další typy zařízení:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Rozdělit cyklus (najít mezery)", + "merge": "Sloučit cykly (spojit fragmenty)", + "delete": "Smazat cykly" + } + }, + "split_mode": { + "options": { + "auto": "Automaticky zjistit mezery nečinnosti", + "manual": "Ruční časová razítka" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Údržba: Přepracování a optimalizace dat", + "clear_debug_data": "Vymazat data ladění (uvolnit místo)", + "wipe_history": "Vymazat VŠECHNA data tohoto zařízení (nevratné)", + "export_import": "Export/import JSON s nastavením (kopírovat/vložit)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportovat všechna data", + "import": "Importovat/sloučit data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatické označení starých cyklů", + "select_cycle_to_label": "Specifický cyklus štítku", + "select_cycle_to_delete": "Smazat cyklus", + "interactive_editor": "Interaktivní editor sloučení/rozdělení", + "trim_cycle": "Data cyklu trimování" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Vytvořit nový profil", + "edit_profile": "Upravit/Přejmenovat profil", + "delete_profile": "Smazat profil", + "profile_stats": "Statistiky profilu", + "cleanup_profile": "Vyčistit historii – graf a smazání", + "assign_phases": "Přiřadit fázové rozsahy" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Vytvořit novou fázi", + "edit_custom_phase": "Upravit fázi", + "delete_custom_phase": "Smazat fázi" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Přidat fázový rozsah", + "edit_range": "Upravit rozsah fází", + "delete_range": "Smazat fázový rozsah", + "clear_ranges": "Vymazat všechny rozsahy", + "auto_detect_ranges": "Automatická detekce fází", + "save_ranges": "Uložit a vrátit" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Stav obnovení", + "stop_recording": "Zastavit nahrávání (Uložit a zpracovat)", + "start_recording": "Spusťte nový záznam", + "process_recording": "Zpracovat poslední záznam (oříznout a uložit)", + "discard_recording": "Zahodit poslední nahrávku" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Vytvořit nový profil", + "existing_profile": "Přidat do stávajícího profilu", + "discard": "Zahodit záznam" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potvrdit - Správná detekce", + "correct": "Správně - Zvolte Program/Duration", + "ignore": "Ignorovat – cyklus falešně pozitivní/hlučný", + "delete": "Smazat – Odebrat z historie" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Fáze pojmenování a aplikace", + "cancel": "Vraťte se zpět beze změn" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Nastavte počáteční bod", + "set_end": "Nastavit koncový bod", + "reset": "Resetovat na plnou dobu trvání", + "apply": "Použít Trim", + "cancel": "Zrušit" + } + }, + "device_type": { + "options": { + "washing_machine": "Pračka", + "dryer": "Sušička", + "washer_dryer": "Kombinovaná pračka-sušička", + "dishwasher": "Myčka nádobí", + "coffee_machine": "Kávovar (zastaralý)", + "ev": "Elektrické vozidlo (zastaralé)", + "air_fryer": "Vzduchová fritéza", + "heat_pump": "Tepelné čerpadlo (zastaralé)", + "bread_maker": "Pekárna na chleba", + "pump": "Pumpa / Kalová pumpa", + "oven": "Trouba (zastaralé)", + "other": "Jiné (pokročilé)" + } + } + }, + "services": { + "label_cycle": { + "name": "Označit cyklus", + "description": "Přiřaďte existující profil k minulému cyklu nebo odeberte štítek.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky k označení." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu k označení." + }, + "profile_name": { + "name": "Název profilu", + "description": "Název existujícího profilu (profily vytvořte v nabídce Správa profilů). Chcete-li štítek odstranit, ponechte prázdné." + } + } + }, + "create_profile": { + "name": "Vytvořit profil", + "description": "Vytvořte nový profil (samostatný nebo založený na referenčním cyklu).", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky." + }, + "profile_name": { + "name": "Název profilu", + "description": "Název nového profilu (např. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "ID referenčního cyklu", + "description": "Volitelné ID cyklu, na kterém je založen tento profil." + } + } + }, + "delete_profile": { + "name": "Smazat profil", + "description": "Smazat profil a volitelně zrušit označení cyklů pomocí něj.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky." + }, + "profile_name": { + "name": "Název profilu", + "description": "Profil, který se má smazat." + }, + "unlabel_cycles": { + "name": "Zrušte označení cyklů", + "description": "Odebrat štítek profilu z cyklů používajících tento profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatické označení starých cyklů", + "description": "Zpětně označte neoznačené cykly pomocí párování profilu.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky." + }, + "confidence_threshold": { + "name": "Práh důvěry", + "description": "Minimální spolehlivost shody (0,50–0,95) pro použití štítků." + } + } + }, + "export_config": { + "name": "Export konfigurace", + "description": "Exportujte profily, cykly a nastavení tohoto zařízení do souboru JSON (na zařízení).", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky k exportu." + }, + "path": { + "name": "Cesta", + "description": "Volitelná absolutní cesta k souboru k zápisu (výchozí je /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importovat konfiguraci", + "description": "Importujte profily, cykly a nastavení pro toto zařízení z exportního souboru JSON (nastavení mohou být přepsána).", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky, do kterého se má importovat." + }, + "path": { + "name": "Cesta", + "description": "Absolutní cesta k exportovanému souboru JSON k importu." + } + } + }, + "submit_cycle_feedback": { + "name": "Odeslat zpětnou vazbu k cyklu", + "description": "Po dokončení cyklu potvrďte nebo opravte automaticky detekovaný program. Zadejte buď `entry_id` (pokročilé) nebo `device_id` (doporučeno).", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení WashData (doporučeno místo entry_id)." + }, + "entry_id": { + "name": "ID vstupu", + "description": "ID konfiguračního záznamu pro zařízení (alternativa k device_id)." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu zobrazené v upozornění / protokolech zpětné vazby." + }, + "user_confirmed": { + "name": "Potvrďte zjištěný program", + "description": "Nastavte true, pokud je detekovaný program správný." + }, + "corrected_profile": { + "name": "Opravený profil", + "description": "Pokud není potvrzeno, zadejte správný název profilu/programu." + }, + "corrected_duration": { + "name": "Opravené trvání (sekundy)", + "description": "Volitelná opravená doba trvání v sekundách." + }, + "notes": { + "name": "Poznámky", + "description": "Nepovinné poznámky k tomuto cyklu." + } + } + }, + "record_start": { + "name": "Zahájení záznamu cyklu", + "description": "Spusťte ruční záznam čistého cyklu (obchází veškerou odpovídající logiku).", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky pro záznam." + } + } + }, + "record_stop": { + "name": "Zastavení cyklu záznamu", + "description": "Zastavte ruční nahrávání.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení pračky pro zastavení nahrávání." + } + } + }, + "trim_cycle": { + "name": "Ořezávací cyklus", + "description": "Ořízněte údaje o výkonu z minulého cyklu na konkrétní časové okno.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení WashData." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu, který se má oříznout." + }, + "trim_start_s": { + "name": "Počátek ořezu (sekundy)", + "description": "Uchovávejte data z tohoto offsetu během několika sekund. Výchozí 0." + }, + "trim_end_s": { + "name": "Konec oříznutí (sekundy)", + "description": "Udržujte data až do tohoto posunu v sekundách. Výchozí = plné trvání." + } + } + }, + "pause_cycle": { + "name": "Pozastavit cyklus", + "description": "Pozastavte aktivní cyklus pro zařízení WashData.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Pozastavení zařízení WashData." + } + } + }, + "resume_cycle": { + "name": "Obnovit cyklus", + "description": "Obnovte pozastavený cyklus pro zařízení WashData.", + "fields": { + "device_id": { + "name": "Zařízení", + "description": "Zařízení WashData pro obnovení." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Spuštěno" + }, + "match_ambiguity": { + "name": "Nejednoznačnost shody" + } + }, + "sensor": { + "washer_state": { + "name": "Stav", + "state": { + "off": "Vypnuto", + "idle": "Nečinný", + "starting": "Spouštění", + "running": "Probíhá", + "paused": "Pozastaveno", + "user_paused": "Pozastaveno uživatelem", + "ending": "Dokončování", + "finished": "Dokončeno", + "anti_wrinkle": "Proti pomačkání", + "delay_wait": "Čekání na spuštění", + "interrupted": "Přerušeno", + "force_stopped": "Nuceně zastaveno", + "rinse": "Máchání", + "unknown": "Neznámý", + "clean": "Čistý" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Zbývající čas" + }, + "total_duration": { + "name": "Celková délka" + }, + "cycle_progress": { + "name": "Průběh" + }, + "current_power": { + "name": "Aktuální výkon" + }, + "elapsed_time": { + "name": "Uplynutý čas" + }, + "debug_info": { + "name": "Informace o ladění" + }, + "match_confidence": { + "name": "Spolehlivost shody" + }, + "top_candidates": { + "name": "Nejlepší kandidáti", + "state": { + "none": "Žádní" + } + }, + "current_phase": { + "name": "Aktuální fáze" + }, + "wash_phase": { + "name": "Fáze" + }, + "profile_cycle_count": { + "name": "Počet cyklů profilu {profile_name}", + "unit_of_measurement": "cyklů" + }, + "suggestions": { + "name": "Doporučená nastavení" + }, + "pump_runs_today": { + "name": "Spuštění čerpadla (posledních 24 h)" + }, + "cycle_count": { + "name": "Počet cyklů" + } + }, + "select": { + "program_select": { + "name": "Program cyklu", + "state": { + "auto_detect": "Automaticky" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Nuceně ukončit cyklus" + }, + "pause_cycle": { + "name": "Pozastavit cyklus" + }, + "resume_cycle": { + "name": "Obnovit cyklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Je vyžadováno platné device_id." + }, + "cycle_id_required": { + "message": "Je vyžadován platný cycle_id." + }, + "profile_name_required": { + "message": "Je vyžadován platný profile_name." + }, + "device_not_found": { + "message": "Zařízení nenalezeno." + }, + "no_config_entry": { + "message": "Pro zařízení nebyla nalezena žádná položka konfigurace." + }, + "integration_not_loaded": { + "message": "Integrace pro toto zařízení není načtena." + }, + "cycle_not_found_or_no_power": { + "message": "Cyklus nebyl nalezen nebo nemá žádné údaje o napájení." + }, + "trim_failed_empty_window": { + "message": "Oříznutí se nezdařilo – cyklus nebyl nalezen, nejsou žádná data o napájení nebo je výsledné okno prázdné." + }, + "trim_invalid_range": { + "message": "trim_end_s musí být větší než trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/cy.json b/custom_components/ha_washdata/translations/cy.json new file mode 100644 index 0000000..08d81ed --- /dev/null +++ b/custom_components/ha_washdata/translations/cy.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Gosod WashData", + "description": "Ffurfweddwch eich peiriant golchi neu declyn arall.\n\nMae angen synhwyrydd pŵer.\n\n**Nesaf, gofynnir i chi a ydych am greu eich proffil cyntaf.**", + "data": { + "name": "Enw Dyfais", + "device_type": "Math o Ddyfais", + "power_sensor": "Synhwyrydd Pŵer", + "min_power": "Trothwy Pŵer Isafswm (W)" + }, + "data_description": { + "name": "Enw cyfeillgar ar y ddyfais hon (e.e., 'Peiriant Golchi', 'Dishwasher').", + "device_type": "Pa fath o declyn yw hwn? Yn helpu i deilwra canfod a labelu.", + "power_sensor": "Yr endid synhwyrydd sy'n adrodd am ddefnydd pŵer amser real (mewn watiau) o'ch plwg clyfar.", + "min_power": "Mae darlleniadau pŵer uwchlaw'r trothwy hwn (mewn watiau) yn dangos bod y teclyn yn rhedeg. Dechreuwch gyda 2W ar gyfer y rhan fwyaf o ddyfeisiau." + } + }, + "first_profile": { + "title": "Creu Proffil Cyntaf", + "description": "Mae **Beic** yn rhediad cyflawn o'ch teclyn (e.e., llwyth o olchi dillad). Mae **Proffil** yn grwpio'r cylchoedd hyn yn ôl math (e.e., 'Cotton', 'Quick Wash'). Mae creu proffil nawr yn helpu'r integreiddio amcangyfrif hyd ar unwaith.\n\nGallwch ddewis y proffil hwn â llaw yn rheolyddion y ddyfais os na chaiff ei ganfod yn awtomatig.\n\n**Gadewch 'Enw Proffil' yn wag i hepgor y cam hwn.**", + "data": { + "profile_name": "Enw Proffil (Dewisol)", + "manual_duration": "Amcangyfrif o Hyd (munudau)" + } + } + }, + "error": { + "cannot_connect": "Wedi methu cysylltu", + "invalid_auth": "Dilysiad annilys", + "unknown": "Gwall annisgwyl" + }, + "abort": { + "already_configured": "Mae'r ddyfais eisoes wedi'i ffurfweddu" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Adolygu Adborth Dysgu", + "settings": "Gosodiadau", + "notifications": "Hysbysiadau", + "manage_cycles": "Rheoli Cylchoedd", + "manage_profiles": "Rheoli Proffiliau", + "manage_phase_catalog": "Rheoli Catalog Cyfnod", + "record_cycle": "Cylchred Record (Llawlyfr)", + "diagnostics": "Diagnosteg a Chynnal a Chadw" + } + }, + "apply_suggestions": { + "title": "Copïo Gwerthoedd a Awgrymir", + "description": "Bydd hyn yn copïo'r gwerthoedd presennol a awgrymir i'ch Gosodiadau sydd wedi'u cadw. Gweithred un-amser yw hon ac nid yw'n galluogi trosysgrifo awtomatig.\n\nGwerthoedd a awgrymir i'w copïo:\n{suggested}", + "data": { + "confirm": "Cadarnhau'r weithred copi" + } + }, + "apply_suggestions_confirm": { + "title": "Adolygu Newidiadau a Awgrymir", + "description": "Canfu WashData **{pending_count}** gwerth(au) awgrymedig i'w defnyddio.\n\nNewidiadau:\n{changes}\n\n✅ Ticiwch y blwch isod a chliciwch **Cyflwyno** i wneud cais ac arbedwch y newidiadau hyn ar unwaith.\n❌ Gadewch ef heb ei wirio a chliciwch **Cyflwyno** i ganslo a mynd yn ôl.", + "data": { + "confirm_apply_suggestions": "Gwneud cais ac arbed y newidiadau hyn" + } + }, + "settings": { + "title": "Gosodiadau", + "description": "{deprecation_warning}Trothwyon canfod tiwn, dysgu, ac ymddygiad uwch. Mae rhagosodiadau'n gweithio'n dda ar gyfer y rhan fwyaf o setiau.\n\nGosodiadau a awgrymir ar gael ar hyn o bryd: {suggestions_count}.\nOs yw hwn uwchlaw 0, agorwch Gosodiadau Uwch a defnyddiwch 'Cymhwyso Gwerthoedd a Awgrymir' i adolygu argymhellion.", + "data": { + "apply_suggestions": "Cymhwyso Gwerthoedd a Awgrymir", + "device_type": "Math o Ddyfais", + "power_sensor": "Endid Synhwyrydd Pŵer", + "min_power": "Isafswm Pŵer (W)", + "off_delay": "Oedi (au) Diwedd Beic", + "notify_actions": "Camau Hysbysu", + "notify_people": "Oedi Wrth Ddarlledu Nes Bod y Bobl Hyn Adref", + "notify_only_when_home": "Gohirio Hysbysiadau Hyd nes y bydd y Person a Ddetholir Adref", + "notify_fire_events": "Digwyddiadau Awtomeiddio Tân", + "notify_start_services": "Cychwyn Beicio - Targedau Hysbysu", + "notify_finish_services": "Gorffen Beic - Targedau Hysbysu", + "notify_live_services": "Cynnydd Byw - Targedau Hysbysu", + "notify_before_end_minutes": "Hysbysiad cyn-gwblhau (munudau cyn diwedd)", + "notify_title": "Teitl yr Hysbysiad", + "notify_icon": "Eicon Hysbysiad", + "notify_start_message": "Dechrau Fformat Neges", + "notify_finish_message": "Gorffen Fformat Neges", + "notify_pre_complete_message": "Fformat Neges Cyn Cwblhau", + "show_advanced": "Golygu Gosodiadau Uwch (Cam Nesaf)" + }, + "data_description": { + "apply_suggestions": "Ticiwch y blwch hwn i gopïo'r gwerthoedd a awgrymir gan yr algorithm dysgu i'r ffurflen. Adolygu cyn cadw.\n\nAwgrymir (o ddysgu):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Dewiswch y math o offer (Peiriant Golchi, Sychwr, Peiriant golchi llestri, Peiriant Coffi, EV). Mae'r tag hwn yn cael ei storio gyda phob cylch ar gyfer trefniadaeth well.", + "power_sensor": "Yr endid synhwyrydd sy'n adrodd am bŵer amser real (mewn watiau). Gallwch chi newid hyn unrhyw bryd heb golli data hanesyddol - mae'n ddefnyddiol os ydych chi'n amnewid plwg clyfar.", + "min_power": "Mae darlleniadau pŵer uwchlaw'r trothwy hwn (mewn watiau) yn dangos bod y teclyn yn rhedeg. Dechreuwch gyda 2W ar gyfer y rhan fwyaf o ddyfeisiau.", + "off_delay": "Eiliadau rhaid i'r pŵer llyfnhau aros o dan y trothwy stopio i nodi cwblhau. Diofyn: 120s.", + "notify_actions": "Dilyniant gweithredu Cynorthwyydd Cartref Dewisol i redeg am hysbysiadau (sgriptiau, hysbysu, TTS, ac ati). Os caiff ei osod, bydd camau gweithredu'n rhedeg cyn y gwasanaeth hysbysu wrth gefn.", + "notify_people": "Endidau pobl a ddefnyddir ar gyfer gatiau presenoldeb. Pan fydd wedi'i alluogi, bydd yr hysbysiad yn cael ei ohirio nes bod o leiaf un person dethol gartref.", + "notify_only_when_home": "Os caiff ei alluogi, gohiriwch hysbysiadau nes bod o leiaf un person a ddewiswyd gartref.", + "notify_fire_events": "Os yw wedi'i alluogi, tanio digwyddiadau Cynorthwyydd Cartref ar gyfer dechrau a diwedd beiciau (ha_washdata_cycle_started a ha_washdata_cycle_ended) i yrru awtomeiddio.", + "notify_start_services": "Mae un neu fwy yn hysbysu gwasanaethau i rybuddio pan fydd cylch yn dechrau. Gadewch yn wag i hepgor hysbysiadau cychwyn.", + "notify_finish_services": "Mae un neu fwy yn hysbysu gwasanaethau i rybuddio pan fydd cylch yn gorffen neu bron â chael ei gwblhau. Gadewch yn wag i hepgor hysbysiadau gorffen.", + "notify_live_services": "Mae un neu fwy yn hysbysu gwasanaethau i dderbyn diweddariadau cynnydd byw tra bod y cylch yn rhedeg (ap cydymaith symudol yn unig). Gadewch yn wag i hepgor diweddariadau byw.", + "notify_before_end_minutes": "Munudau cyn diwedd amcangyfrifedig y cylch i sbarduno hysbysiad. Gosodwch ar 0 i analluogi. Rhagosodedig: 0.", + "notify_title": "Teitl personol ar gyfer hysbysiadau. Yn cefnogi {device} dalfan.", + "notify_icon": "Eicon i'w ddefnyddio ar gyfer hysbysiadau (e.e. mdi: peiriant golchi). Gadewch yn wag i anfon heb eicon.", + "notify_start_message": "Neges wedi'i hanfon pan fydd cylch yn dechrau. Yn cefnogi {device} dalfan.", + "notify_finish_message": "Neges wedi'i hanfon pan fydd cylch yn gorffen. Yn cefnogi {device}, {duration}, {program}, {energy_kwh}, {cost} dalfan.", + "notify_pre_complete_message": "Testun y diweddariadau cynnydd byw cylchol tra bod y cylch yn rhedeg. Yn cefnogi {device}, {minutes}, {program} dalfan." + } + }, + "notifications": { + "title": "Hysbysiadau", + "description": "Ffurfweddu sianeli hysbysu, templedi, a diweddariadau cynnydd symudol byw dewisol.", + "data": { + "notify_actions": "Camau Hysbysu", + "notify_people": "Oedi Wrth Ddarlledu Nes Bod y Bobl Hyn Adref", + "notify_only_when_home": "Gohirio Hysbysiadau Hyd nes y bydd y Person a Ddetholir Adref", + "notify_fire_events": "Digwyddiadau Awtomeiddio Tân", + "notify_start_services": "Cychwyn Beicio - Targedau Hysbysu", + "notify_finish_services": "Gorffen Beic - Targedau Hysbysu", + "notify_live_services": "Cynnydd Byw - Targedau Hysbysu", + "notify_before_end_minutes": "Hysbysiad cyn-gwblhau (munudau cyn diwedd)", + "notify_live_interval_seconds": "Cyfnod Diweddariad Byw (eiliadau)", + "notify_live_overrun_percent": "Lwfans Gorwario Diweddariad Byw (%)", + "notify_live_chronometer": "Amserydd Cyfrif Down cronomedr", + "notify_title": "Teitl yr Hysbysiad", + "notify_icon": "Eicon Hysbysiad", + "notify_start_message": "Dechrau Fformat Neges", + "notify_finish_message": "Gorffen Fformat Neges", + "notify_pre_complete_message": "Fformat Neges Cyn Cwblhau", + "energy_price_entity": "Pris Ynni - Endid (dewisol)", + "energy_price_static": "Pris Ynni - Gwerth statig (dewisol)", + "go_back": "Ewch yn ôl heb gadw", + "notify_reminder_message": "Fformat Neges Atgoffa", + "notify_timeout_seconds": "Diswyddo'n awtomatig ar ôl (eiliadau)", + "notify_channel": "Sianel Hysbysu (statws / byw / nodyn atgoffa)", + "notify_finish_channel": "Sianel Hysbysu Gorffen" + }, + "data_description": { + "go_back": "Ticiwch hwn a chliciwch Cyflwyno i daflu unrhyw newidiadau uchod a dychwelyd i'r brif ddewislen.", + "notify_actions": "Dilyniant gweithredu Cynorthwyydd Cartref Dewisol i redeg am hysbysiadau (sgriptiau, hysbysu, TTS, ac ati). Os caiff ei osod, bydd camau gweithredu'n rhedeg cyn y gwasanaeth hysbysu wrth gefn.", + "notify_people": "Endidau pobl a ddefnyddir ar gyfer gatiau presenoldeb. Pan fydd wedi'i alluogi, bydd yr hysbysiad yn cael ei ohirio nes bod o leiaf un person dethol gartref.", + "notify_only_when_home": "Os caiff ei alluogi, gohiriwch hysbysiadau nes bod o leiaf un person a ddewiswyd gartref.", + "notify_fire_events": "Os yw wedi'i alluogi, tanio digwyddiadau Cynorthwyydd Cartref ar gyfer dechrau a diwedd beiciau (ha_washdata_cycle_started a ha_washdata_cycle_ended) i yrru awtomeiddio.", + "notify_start_services": "Mae un neu fwy yn hysbysu gwasanaethau i rybuddio pan fydd cylch yn dechrau (e.e. notify.mobile_app_my_phone). Gadewch yn wag i hepgor hysbysiadau cychwyn.", + "notify_finish_services": "Mae un neu fwy yn hysbysu gwasanaethau i rybuddio pan fydd cylch yn gorffen neu bron â chael ei gwblhau. Gadewch yn wag i hepgor hysbysiadau gorffen.", + "notify_live_services": "Mae un neu fwy yn hysbysu gwasanaethau i dderbyn diweddariadau cynnydd byw tra bod y cylch yn rhedeg (ap cydymaith symudol yn unig). Gadewch yn wag i hepgor diweddariadau byw.", + "notify_before_end_minutes": "Munudau cyn diwedd amcangyfrifedig y cylch i sbarduno hysbysiad. Gosodwch ar 0 i analluogi. Rhagosodedig: 0.", + "notify_live_interval_seconds": "Pa mor aml mae diweddariadau cynnydd yn cael eu hanfon wrth redeg. Mae gwerthoedd is yn fwy ymatebol ond yn defnyddio mwy o hysbysiadau. Mae isafswm o 30 eiliad yn cael ei orfodi - mae gwerthoedd o dan 30 s yn cael eu talgrynnu'n awtomatig hyd at 30 s.", + "notify_live_overrun_percent": "Ymyl diogelwch uwchlaw'r cyfrif diweddaru amcangyfrifedig ar gyfer cylchoedd hir/rhedegog (er enghraifft mae 20% yn caniatáu 1.2x o ddiweddariadau amcangyfrifedig).", + "notify_live_chronometer": "Pan fydd wedi'i alluogi, mae pob diweddariad byw yn cynnwys cyfrif cronomedr i'r amser gorffen amcangyfrifedig. Mae'r amserydd hysbysu yn ticio i lawr yn awtomatig ar y ddyfais rhwng diweddariadau (app cydymaith Android yn unig).", + "notify_title": "Teitl personol ar gyfer hysbysiadau. Yn cefnogi {device} dalfan.", + "notify_icon": "Eicon i'w ddefnyddio ar gyfer hysbysiadau (e.e. mdi: peiriant golchi). Gadewch yn wag i anfon heb eicon.", + "notify_start_message": "Neges wedi'i hanfon pan fydd cylch yn dechrau. Yn cefnogi {device} dalfan.", + "notify_finish_message": "Neges wedi'i hanfon pan fydd cylch yn gorffen. Yn cefnogi {device}, {duration}, {program}, {energy_kwh}, {cost} dalfan.", + "energy_price_entity": "Dewiswch endid rhifol sy'n dal y pris trydan cyfredol (e.e. sensor.electricity_price, mewnbwn_number.energy_rate). Pan fydd wedi'i osod, mae'n galluogi'r dalfan {cost}. Yn cael blaenoriaeth dros y gwerth statig os yw'r ddau wedi'u ffurfweddu.", + "energy_price_static": "Pris trydan sefydlog fesul kWh. Pan fydd wedi'i osod, mae'n galluogi'r dalfan {cost}. Wedi'i anwybyddu os yw endid wedi'i ffurfweddu uchod. Ychwanegwch eich symbol arian cyfred yn y templed neges, e.e. {cost} €.", + "notify_reminder_message": "Anfonodd testun y nodyn atgoffa un-amser y nifer cyfluniedig o funudau cyn y diwedd amcangyfrifedig. Yn wahanol i'r diweddariadau byw cylchol uchod. Yn cefnogi {device}, {minutes}, {program} dalfan.", + "notify_timeout_seconds": "Diystyru hysbysiadau yn awtomatig ar ôl hyn lawer o eiliadau (a anfonwyd ymlaen fel 'amser terfyn' yr ap cydymaith). Gosodwch ar 0 i'w cadw nes eu bod yn cael eu diswyddo. Rhagosodedig: 0.", + "notify_channel": "Android sianel hysbysu ap cydymaith ar gyfer statws, cynnydd byw a hysbysiadau atgoffa. Mae sain a phwysigrwydd sianel yn cael eu ffurfweddu yn yr app cydymaith y tro cyntaf i enw'r sianel gael ei ddefnyddio - mae WashData yn gosod enw'r sianel yn unig. Gadael yn wag ar gyfer y rhagosodiad app.", + "notify_finish_channel": "Sianel Android ar wahân ar gyfer y rhybudd gorffenedig (a ddefnyddir hefyd gan y nodyn atgoffa a'r peiriant aros golchi dillad) fel y gall gael ei sain ei hun. Gadewch yn wag i ailddefnyddio'r sianel uchod.", + "notify_pre_complete_message": "Testun y diweddariadau cynnydd byw cylchol tra bod y cylch yn rhedeg. Yn cefnogi {device}, {minutes}, {program} dalfan." + } + }, + "advanced_settings": { + "title": "Gosodiadau Uwch", + "description": "Tiwniwch drothwyon canfod, dysgu, ac ymddygiad uwch. Mae'r rhagosodiadau'n gweithio'n dda ar gyfer y rhan fwyaf o osodiadau.", + "sections": { + "suggestions_section": { + "name": "Gosodiadau a Awgrymir", + "description": "Mae {suggestions_count} o awgrymiadau wedi'u dysgu ar gael. Ticiwch Cymhwyso Gwerthoedd a Awgrymir i adolygu'r newidiadau arfaethedig cyn cadw.", + "data": { + "apply_suggestions": "Cymhwyso Gwerthoedd a Awgrymir" + }, + "data_description": { + "apply_suggestions": "Ticiwch y blwch hwn i agor cam adolygu ar gyfer gwerthoedd a awgrymir o'r algorithm dysgu. Nid oes dim yn cael ei arbed yn awtomatig.\n\nAwgrymir (o ddysgu):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Canfod a Throthwyon Pŵer", + "description": "Pryd mae'r ddyfais yn cael ei hystyried yn YMLAEN neu i FFWRDD, gatiau ynni, ac amseriad ar gyfer trosglwyddiadau cyflwr.", + "data": { + "start_duration_threshold": "Dechrau Debounce Hyd(s)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Cwblhau Isafswm Amser Rhedeg (eiliadau)", + "start_threshold_w": "Trothwy Cychwyn (W)", + "stop_threshold_w": "Trothwy Atal (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Parth Marw Rhedeg (eiliadau)", + "end_repeat_count": "Diwedd Cyfrif Ailadrodd", + "min_off_gap": "Bwlch Isaf Rhwng Cylchoedd (au)", + "sampling_interval": "Cyfwng(au) samplu" + }, + "data_description": { + "start_duration_threshold": "Anwybyddu pigau pŵer byr yn fyrrach na'r hyd hwn (eiliadau). Hidlo pigau bŵt allan (e.e., 60W am 2s) cyn i'r cylch go iawn ddechrau. Diofyn: 5s.", + "start_energy_threshold": "Rhaid i'r cylch gronni o leiaf cymaint â hyn o egni (Wh) yn ystod y cyfnod START i'w gadarnhau. Diofyn: 0.005 Wh.", + "completion_min_seconds": "Bydd beiciau sy'n fyrrach na hyn yn cael eu marcio fel rhai 'amhariadol' hyd yn oed os ydynt yn gorffen yn naturiol. Diofyn: 600s.", + "start_threshold_w": "Rhaid i bŵer godi UCHOD y trothwy hwn i DECHRAU cylchred. Gosodwch yn uwch na'ch pŵer wrth gefn. Diofyn: min_power + 1 W.", + "stop_threshold_w": "Mae'n rhaid i bŵer ddisgyn DAN y trothwy hwn i DDIWEDDU cylchred. Gosodwch ychydig yn uwch na sero i osgoi sŵn sy'n sbarduno pennau ffug. Diofyn: 0.6 * min_power.", + "end_energy_threshold": "Mae'r cylch yn dod i ben dim ond os yw ynni yn y ffenestr Oddi-Oedi olaf yn is na'r gwerth hwn (Wh). Yn atal diwedd ffug os yw pŵer yn amrywio bron i sero. Diofyn: 0.05 Wh.", + "running_dead_zone": "Ar ôl i'r cylch ddechrau, anwybyddwch dipiau pŵer am yr eiliadau lawer hyn i atal canfod diwedd ffug yn ystod y cyfnod deillio cychwynnol. Gosodwch ar 0 i analluogi. Rhagosodiad: 0s.", + "end_repeat_count": "Nifer o weithiau mae'n rhaid bodloni'r amod terfynol (pŵer o dan y trothwy ar gyfer off_delay) yn olynol cyn dod â'r cylch i ben. Mae gwerthoedd uwch yn atal pennau ffug yn ystod seibiannau. Diofyn: 1.", + "min_off_gap": "Os bydd cylchred yn dechrau o fewn yr eiliadau lawer hyn i'r diwedd un blaenorol, byddant yn cael eu HUNOD yn un gylchred.", + "sampling_interval": "Isafswm cyfwng samplu mewn eiliadau. Bydd diweddariadau cyflymach na'r gyfradd hon yn cael eu hanwybyddu. Diofyn: 30s (2s ar gyfer peiriannau golchi, peiriannau golchi a pheiriannau golchi llestri)." + } + }, + "matching_section": { + "name": "Cydweddu Proffiliau a Dysgu", + "description": "Pa mor ymosodol mae WashData yn cydweddu cylchoedd rhedeg â phroffiliau dysgedig a phryd i ofyn am adborth.", + "data": { + "profile_match_min_duration_ratio": "Cymhareb Cyfateb Proffil Isafswm Hyd (0.1-1.0)", + "profile_match_interval": "Cyfwng Paru Proffil (eiliadau)", + "profile_match_threshold": "Trothwy Cydweddu Proffil", + "profile_unmatch_threshold": "Trothwy Unmatch Proffil", + "auto_label_confidence": "Hyder Labelu Awto (0-1)", + "learning_confidence": "Hyder Cais am Adborth (0-1)", + "suppress_feedback_notifications": "Atal Hysbysiadau Adborth", + "duration_tolerance": "Goddefgarwch Hyd (0-0.5)", + "smoothing_window": "Ffenestr Llyfnu (samplau)", + "profile_duration_tolerance": "Proffil Paru Hyd Goddefgarwch (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Cymhareb hyd gofynnol ar gyfer paru proffil. Rhaid i'r cylchred redeg fod o leiaf y ffracsiwn hwn o hyd y proffil i gyd-fynd. Is = paru cynharach. Diofyn: 0.50 (50%).", + "profile_match_interval": "Pa mor aml (mewn eiliadau) i geisio paru proffil yn ystod cylchred. Mae gwerthoedd is yn darparu canfod cyflymach ond yn defnyddio mwy o CPU. Diofyn: 300s (5 munud).", + "profile_match_threshold": "Mae angen isafswm sgôr tebygrwydd DTW (0.0–1.0) i ystyried proffil fel cyfatebiaeth. Uwch = paru llymach, llai o bethau positif ffug. Diofyn: 0.4.", + "profile_unmatch_threshold": "Sgôr tebygrwydd DTW (0.0–1.0) islaw y mae proffil a oedd yn cyfateb yn flaenorol yn cael ei wrthod. Dylai fod yn is na'r trothwy paru i atal fflachio. Diofyn: 0.35.", + "auto_label_confidence": "Labelu cylchoedd yn awtomatig ar yr hyder hwn neu'n uwch na hynny ar ôl ei gwblhau (0.0–1.0).", + "learning_confidence": "Isafswm hyder i ofyn am ddilysiad defnyddiwr trwy ddigwyddiad (0.0–1.0).", + "suppress_feedback_notifications": "Pan fydd wedi'i alluogi, bydd WashData yn dal i olrhain a gofyn am adborth yn fewnol ond ni fydd yn dangos hysbysiadau cyson yn gofyn ichi gadarnhau cylchoedd. Mae'n ddefnyddiol pan fydd beiciau bob amser yn cael eu canfod yn gywir a'r anogwyr yn tynnu sylw.", + "duration_tolerance": "Goddefgarwch ar gyfer amcangyfrifon sy'n weddill o amser yn ystod rhediad (0.0-0.5 yn cyfateb i 0-50%).", + "smoothing_window": "Nifer y darlleniadau pŵer diweddar a ddefnyddiwyd ar gyfer llyfnu symud-cyfartaledd.", + "profile_duration_tolerance": "Goddefgarwch a ddefnyddir gan baru proffil ar gyfer amrywiad hyd cyfan (0.0-0.5). Ymddygiad rhagosodedig: ±25%." + } + }, + "timing_section": { + "name": "Amseriad, Cynnal a Chadw a Dadfygio", + "description": "Gwarchodwr, ailosod cynnydd, hunan-gynnal a chadw, ac amlygu endidau dadfygio.", + "data": { + "watchdog_interval": "Cyfnod Corff Gwarchod (eiliadau)", + "no_update_active_timeout": "Goramser(au) Dim Diweddariad", + "progress_reset_delay": "Oedi Ailosod Cynnydd (eiliadau)", + "auto_maintenance": "Galluogi Cynnal a Chadw Awtomatig", + "expose_debug_entities": "Endidau Dadfygio Datguddio", + "save_debug_traces": "Arbed Olion Dadfygio" + }, + "data_description": { + "watchdog_interval": "Eiliadau rhwng gwiriadau corff gwarchod tra'n rhedeg. Diofyn: 5s. RHYBUDD: Sicrhewch fod hwn yn UWCH na chyfwng diweddaru eich synhwyrydd i osgoi arosfannau ffug (e.e., os yw synhwyrydd yn diweddaru bob 60au, defnyddiwch 65s+).", + "no_update_active_timeout": "Ar gyfer synwyryddion cyhoeddi-ar-newid: dim ond gorfodi terfynu cylch ACTIVE os nad oes diweddariadau yn cyrraedd am yr eiliadau lawer hyn. Mae cwblhau pŵer isel yn dal i ddefnyddio'r Oedi Oddi.", + "progress_reset_delay": "Ar ôl i gylchred ddod i ben (100%), arhoswch yr eiliadau lawer hyn o segur cyn ailosod cynnydd i 0%. Diofyn: 300s.", + "auto_maintenance": "Galluogi cynnal a chadw ceir (samplau atgyweirio, uno darnau).", + "expose_debug_entities": "Dangos synwyryddion uwch (Hyder, Cyfnod, Amwysedd) ar gyfer dadfygio.", + "save_debug_traces": "Storio data graddio manwl ac olrhain pŵer mewn hanes (Yn cynyddu'r defnydd o storio)." + } + }, + "anti_wrinkle_section": { + "name": "Tarian Gwrth-Grychu (Sychwyr)", + "description": "Anwybyddwch gylchdroadau'r drwm ar ôl y cylch i atal cylchoedd ysbryd. Sychwr / peiriant golchi-sychu yn unig.", + "data": { + "anti_wrinkle_enabled": "Tarian Gwrth-Wrinkle (Sychwr yn Unig)", + "anti_wrinkle_max_power": "Pŵer Uchaf Gwrth-Wrinkle (W)", + "anti_wrinkle_max_duration": "Hyd(au) Uchaf Gwrth-Wrinkle", + "anti_wrinkle_exit_power": "Pŵer Gadael Gwrth-Wrinkle (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Anwybyddwch gylchdroadau drwm pŵer isel byr ar ôl i gylchred ddod i ben i atal cylchoedd ysbrydion (sychwr / sychwr golchi yn unig).", + "anti_wrinkle_max_power": "Mae pyliau ar neu islaw'r pŵer hwn yn cael eu trin fel cylchdroadau gwrth-wrinkle yn lle cylchoedd newydd. Dim ond pan fydd Anti-Wrinkle Shield wedi'i alluogi y mae'n berthnasol.", + "anti_wrinkle_max_duration": "Hyd byrstio uchaf i'w drin fel cylchdro gwrth-wrinkle. Dim ond pan fydd Anti-Wrinkle Shield wedi'i alluogi y mae'n berthnasol.", + "anti_wrinkle_exit_power": "Os bydd pŵer yn disgyn o dan y lefel hon, gadewch gwrth-wrinkle ar unwaith (yn wir i ffwrdd). Dim ond pan fydd Anti-Wrinkle Shield wedi'i alluogi y mae'n berthnasol." + } + }, + "delay_start_section": { + "name": "Canfod Cychwyn Gohiriedig", + "description": "Triniwch segur pŵer isel parhaus fel 'Yn Aros i Gychwyn' nes bod y cylch yn dechrau mewn gwirionedd.", + "data": { + "delay_start_detect_enabled": "Galluogi Canfod Cychwyn Oedi", + "delay_confirm_seconds": "Cychwyn Gohiriedig: Amser Cadarnhau Segur (s)", + "delay_timeout_hours": "Oedi Cychwyn: Uchafswm Amser Aros (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Pan fydd wedi'i alluogi, mae pigyn draen pŵer isel byr wedi'i ddilyn gan bŵer wrth gefn parhaus yn cael ei ganfod fel dechrau oedi. Mae'r synhwyrydd cyflwr yn dangos 'Aros i Gychwyn' yn ystod yr oedi. Ni chaiff unrhyw amser rhaglen na chynnydd ei olrhain nes bod y cylch yn dechrau. Mae angen gosod start_threshold_w uwchben pŵer pigyn y draen.", + "delay_confirm_seconds": "Rhaid i'r pŵer aros rhwng Trothwy Stopio (W) a Throthwy Cychwyn (W) am gymaint o eiliadau cyn i WashData fynd i gyflwr 'Yn Aros i Gychwyn'. Mae hyn yn hidlo brigau llywio byr yn y ddewislen. Rhagosodiad: 60 s.", + "delay_timeout_hours": "Goramser diogelwch: os yw'r peiriant yn dal i fod yn 'Aros i Gychwyn' ar ôl yr oriau lawer hyn, mae WashData yn ailosod i Off. Diofyn: 8 h." + } + }, + "external_triggers_section": { + "name": "Sbardunau Allanol, Drws a Saib", + "description": "Synhwyrydd sbardun diwedd allanol dewisol, synhwyrydd drws ar gyfer canfod saib/glân, a switsh torri pŵer wrth saib.", + "data": { + "external_end_trigger_enabled": "Galluogi Sbardun Diwedd Beic Allanol", + "external_end_trigger": "Synhwyrydd Terfyn Beic Allanol", + "external_end_trigger_inverted": "Rhesymeg Sbardun Gwrthdro (Sbardun wedi'i DIFFODD)", + "door_sensor_entity": "Synhwyrydd Drws", + "pause_cuts_power": "Torri Pŵer Wrth Oedi", + "switch_entity": "Newid Endid (ar gyfer Saib Power Cut)", + "notify_unload_delay_minutes": "Oedi Hysbysiad Aros Golchdy (munud)" + }, + "data_description": { + "external_end_trigger_enabled": "Galluogi monitro synhwyrydd deuaidd allanol i sbarduno cwblhau cylchred.", + "external_end_trigger": "Dewiswch endid synhwyrydd deuaidd. Pan fydd y synhwyrydd hwn yn sbarduno, bydd y cylch presennol yn dod i ben gyda statws 'cwblhawyd'.", + "external_end_trigger_inverted": "Yn ddiofyn, mae'r sbardun yn tanio pan fydd y synhwyrydd yn troi YMLAEN. Gwiriwch y blwch hwn i danio pan fydd y synhwyrydd yn diffodd yn lle hynny.", + "door_sensor_entity": "Synhwyrydd deuaidd dewisol ar gyfer drws y peiriant (ar = agored, i ffwrdd = ar gau). Pan fydd y drws yn agor yn ystod cylch gweithredol, bydd WashData yn cadarnhau bod y cylch wedi'i seibio'n fwriadol. Ar ôl i'r cylch ddod i ben, os yw'r drws yn dal i fod ar gau, mae'r cyflwr yn newid i 'Glan' nes bod y drws yn cael ei agor. Sylwch: NID yw cau'r drws yn ailddechrau cylch wedi'i seibio'n awtomatig - rhaid cychwyn ailddechrau â llaw trwy'r botwm neu'r gwasanaeth Ail-ddechrau Seiclo.", + "pause_cuts_power": "Wrth oedi trwy'r botwm Pause Cycle neu'r gwasanaeth, trowch yr endid switsh ffurfweddu i ffwrdd hefyd. Dim ond os yw endid switsh wedi'i ffurfweddu ar gyfer y ddyfais hon y daw i rym.", + "switch_entity": "Yr endid switsh i'w doglo wrth oedi neu ailddechrau. Yn ofynnol pan fydd 'Cut Power When Pausing' wedi'i alluogi.", + "notify_unload_delay_minutes": "Anfonwch hysbysiad trwy'r sianel hysbysu gorffen ar ôl i'r cylch ddod i ben ac nid yw'r drws wedi'i agor am lawer o funudau (angen Synhwyrydd Drws). Gosodwch ar 0 i analluogi. Diofyn: 60 mun." + } + }, + "pump_section": { + "name": "Monitro Pwmp", + "description": "Ar gyfer math dyfais pwmp yn unig. Mae'n tanio digwyddiad os yw cylch pwmp yn rhedeg yn hwy na'r hyd hwn.", + "data": { + "pump_stuck_duration": "Trothwy(au) Rhybudd Pwmp yn Sownd (Pwmp yn Unig)" + }, + "data_description": { + "pump_stuck_duration": "Os yw cylch pwmp yn dal i redeg ar ôl yr eiliadau lawer hyn, mae digwyddiad ha_washdata_pump_stuck yn cael ei danio unwaith. Defnyddiwch hwn i ganfod modur wedi'i jamio (mae rhediad pwmp swmp nodweddiadol o dan 60 s). Diofyn: 1800 s (30 mun). Math o ddyfais pwmp yn unig." + } + }, + "device_link_section": { + "name": "Cyswllt Dyfais", + "description": "Cysylltwch y ddyfais WashData hon â dyfais Home Assistant sy'n bodoli eisoes (er enghraifft y plwg clyfar neu'r teclyn ei hun). Yna dangosir y ddyfais WashData fel \"Connected through\" y ddyfais honno. Gadewch yn wag i gadw WashData fel dyfais annibynnol.", + "data": { + "linked_device": "Dyfais Cysylltiedig" + }, + "data_description": { + "linked_device": "Dewiswch y ddyfais i gysylltu WashData ag ef. Mae hyn yn ychwanegu cyfeirnod 'Connected through' yng nghofrestrfa'r dyfeisiau; Mae WashData yn cadw ei gerdyn dyfais a'i endidau ei hun. Cliriwch y dewisiad i ddileu'r ddolen." + } + } + } + }, + "diagnostics": { + "title": "Diagnosteg a Chynnal a Chadw", + "description": "Cynnal gweithredoedd cynnal a chadw fel uno cylchoedd tameidiog neu fudo data sydd wedi'i storio.\n\n**Defnydd Storio**\n\n- Maint Ffeil: {file_size_kb} KB\n- Cylchoedd: {cycle_count}\n- Proffiliau: {profile_count}\n- Olion Dadfygio: {debug_count}", + "menu_options": { + "reprocess_history": "Cynnal a Chadw: Ailbrosesu ac Optimeiddio Data", + "clear_debug_data": "Clirio Data Dadfygio (Rhyddhau lle)", + "wipe_history": "Sychwch POB data ar gyfer y ddyfais hon (anghildroadwy)", + "export_import": "Allforio/Mewnforio JSON gyda gosodiadau (copïo/gludo)", + "menu_back": "← Yn ôl" + } + }, + "clear_debug_data": { + "title": "Clirio Data Dadfygio", + "description": "Ydych chi'n siŵr eich bod am ddileu'r holl olion dadfygio sydd wedi'u storio? Bydd hyn yn rhyddhau lle ond yn dileu gwybodaeth raddio fanwl o gylchoedd y gorffennol." + }, + "manage_cycles": { + "title": "Rheoli Beiciau", + "description": "Cylchoedd diweddar:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Awto-Labelu Hen Feiciau", + "select_cycle_to_label": "Label Cylch Penodol", + "select_cycle_to_delete": "Dileu Beic", + "interactive_editor": "Cyfuno/Hollti Golygydd Rhyngweithiol", + "trim_cycle_select": "Trimio Data Beicio", + "menu_back": "← Yn ôl" + } + }, + "manage_cycles_empty": { + "title": "Rheoli Beiciau", + "description": "Dim cylchoedd wedi'u cofnodi eto.", + "menu_options": { + "auto_label_cycles": "Awto-Labelu Hen Feiciau", + "menu_back": "← Yn ôl" + } + }, + "manage_profiles": { + "title": "Rheoli Proffiliau", + "description": "Crynodeb Proffil:\n{profile_summary}", + "menu_options": { + "create_profile": "Creu Proffil Newydd", + "edit_profile": "Golygu/Ailenwi Proffil", + "delete_profile_select": "Dileu Proffil", + "profile_stats": "Ystadegau Proffil", + "cleanup_profile": "Glanhau Hanes - Graff a Dileu", + "assign_profile_phases_select": "Neilltuo Ystod Cyfnod", + "menu_back": "← Yn ôl" + } + }, + "manage_profiles_empty": { + "title": "Rheoli Proffiliau", + "description": "Dim proffiliau wedi'u creu eto.", + "menu_options": { + "create_profile": "Creu Proffil Newydd", + "menu_back": "← Yn ôl" + } + }, + "manage_phase_catalog": { + "title": "Rheoli Catalog Cyfnod", + "description": "Catalog cyfnodau (rhagosodiadau ar gyfer y ddyfais hon + cyfnodau arfer):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Creu Cyfnod Newydd", + "phase_catalog_edit_select": "Golygu Cyfnod", + "phase_catalog_delete": "Dileu Cyfnod", + "menu_back": "← Yn ôl" + } + }, + "phase_catalog_create": { + "title": "Creu Cyfnod Personol", + "description": "Creu enw cyfnod wedi'i deilwra a disgrifiad, a dewis pa fath o ddyfais y mae'n berthnasol iddo.", + "data": { + "target_device_type": "Math Dyfais Targed", + "phase_name": "Enw Cyfnod", + "phase_description": "Disgrifiad o'r Cyfnod" + } + }, + "phase_catalog_edit_select": { + "title": "Golygu Cyfnod Personol", + "description": "Dewiswch gyfnod wedi'i addasu i'w olygu.", + "data": { + "phase_name": "Cyfnod Custom" + } + }, + "phase_catalog_edit": { + "title": "Golygu Cyfnod Personol", + "description": "Cyfnod golygu: {phase_name}", + "data": { + "phase_name": "Enw Cyfnod", + "phase_description": "Disgrifiad o'r Cyfnod" + } + }, + "phase_catalog_delete": { + "title": "Dileu Cyfnod Personol", + "description": "Dewiswch gam personol i'w ddileu. Bydd unrhyw ystodau a neilltuwyd sy'n defnyddio'r cam hwn yn cael eu dileu.", + "data": { + "phase_name": "Cyfnod Custom" + } + }, + "assign_profile_phases": { + "title": "Neilltuo Ystod Cyfnod", + "description": "Rhagolwg cyfnod ar gyfer proffil: **{profile_name}**\n\n{timeline_svg}\n\n**Amrediadau presennol:**\n{current_ranges}\n\nDefnyddiwch y gweithredoedd isod i ychwanegu, golygu, dileu, neu gadw ystodau.", + "menu_options": { + "assign_profile_phases_add": "Ychwanegu Ystod Cyfnod", + "assign_profile_phases_edit_select": "Golygu Ystod Cyfnod", + "assign_profile_phases_delete": "Dileu Ystod Cyfnod", + "phase_ranges_clear": "Clirio Pob Ystod", + "assign_profile_phases_auto_detect": "Auto-canfod Cyfnodau", + "phase_ranges_save": "Cadw a Dychwelyd", + "menu_back": "← Yn ôl" + } + }, + "assign_profile_phases_add": { + "title": "Ychwanegu Ystod Cyfnod", + "description": "Ychwanegu ystod un cyfnod i'r drafft cyfredol.", + "data": { + "phase_name": "Cyfnod", + "start_min": "Dechrau Cofnod", + "end_min": "Munud Diwedd" + } + }, + "assign_profile_phases_edit_select": { + "title": "Golygu Ystod Cyfnod", + "description": "Dewiswch ystod i'w golygu.", + "data": { + "range_index": "Ystod Cyfnod" + } + }, + "assign_profile_phases_edit": { + "title": "Golygu Ystod Cyfnod", + "description": "Diweddaru'r ystod cyfnod a ddewiswyd.", + "data": { + "phase_name": "Cyfnod", + "start_min": "Dechrau Cofnod", + "end_min": "Munud Diwedd" + } + }, + "assign_profile_phases_delete": { + "title": "Dileu Ystod Cyfnod", + "description": "Dewiswch ystod i'w thynnu o'r drafft.", + "data": { + "range_index": "Ystod Cyfnod" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Cyfnodau a ganfuwyd yn awtomatig", + "description": "Proffil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} cam(au) wedi'u canfod yn awtomatig.** Dewiswch weithred isod.", + "data": { + "action": "Gweithred" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Enw Cyfnodau Canfyddedig", + "description": "Proffil: **{profile_name}**\n\n**{detected_count} cam(au) wedi'u canfod:**\n{ranges_summary}\n\nRhowch enw i bob cam." + }, + "assign_profile_phases_select": { + "title": "Neilltuo Ystod Cyfnod", + "description": "Dewiswch broffil i ffurfweddu ystodau cyfnodau.", + "data": { + "profile": "Proffil" + } + }, + "profile_stats": { + "title": "Ystadegau Proffil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Creu Proffil Newydd", + "description": "Creu proffil newydd â llaw neu o gylchred gorffennol.\n\nEnghreifftiau o enwau proffil: 'Delicates', 'Trwm Dyletswydd', 'Golchi Cyflym'", + "data": { + "profile_name": "Enw Proffil", + "reference_cycle": "Cylch Cyfeirio (dewisol)", + "manual_duration": "Hyd â llaw (munudau)" + } + }, + "edit_profile": { + "title": "Golygu Proffil", + "description": "Dewiswch broffil i'w ailenwi", + "data": { + "profile": "Proffil" + } + }, + "rename_profile": { + "title": "Golygu Manylion Proffil", + "description": "Proffil cyfredol: {current_name}", + "data": { + "new_name": "Enw Proffil", + "manual_duration": "Hyd â llaw (munudau)" + } + }, + "delete_profile_select": { + "title": "Dileu Proffil", + "description": "Dewiswch broffil i'w ddileu", + "data": { + "profile": "Proffil" + } + }, + "delete_profile_confirm": { + "title": "Cadarnhau Dileu Proffil", + "description": "⚠️ Bydd hyn yn dileu proffil yn barhaol: {profile_name}", + "data": { + "unlabel_cycles": "Tynnwch label o gylchoedd gan ddefnyddio'r proffil hwn" + } + }, + "auto_label_cycles": { + "title": "Awto-Labelu Hen Feiciau", + "description": "Wedi canfod cyfanswm o {total_count} gylchred. Proffiliau: {profiles}", + "data": { + "confidence_threshold": "Isafswm Hyder (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Dewiswch Beicio i Labelu", + "description": "✓ = cwblhau, ⚠ = ailddechrau, ✗ = torri ar draws", + "data": { + "cycle_id": "Beicio" + } + }, + "select_cycle_to_delete": { + "title": "Dileu Beic", + "description": "⚠️ Bydd hyn yn dileu'r cylch a ddewiswyd yn barhaol", + "data": { + "cycle_id": "Beicio i Dileu" + } + }, + "label_cycle": { + "title": "Labelu Cylch", + "description": "{cycle_info}", + "data": { + "profile_name": "Proffil", + "new_profile_name": "Enw Proffil Newydd (os yn creu)" + } + }, + "post_process": { + "title": "Hanes Proses (Uno/Hollti)", + "description": "Glanhewch hanes beicio trwy gyfuno rhediadau tameidiog a hollti cylchoedd a gyfunwyd yn anghywir o fewn ffenestr amser. Nodwch nifer yr oriau i edrych yn ôl (defnyddiwch 999999 i bawb).", + "data": { + "time_range": "Ffenest edrych yn ôl (oriau)", + "gap_seconds": "Cyfuno/Rhannu Bwlch (eiliadau)" + }, + "data_description": { + "time_range": "Nifer yr oriau i'w sganio am gylchoedd tameidiog i uno. Defnyddiwch 999999 i brosesu'r holl ddata hanesyddol.", + "gap_seconds": "Trothwy i benderfynu ar hollt yn erbyn uno. Mae bylchau SY'N BYTHACH na hyn yn cael eu huno. Mae bylchau HWY na hyn wedi'u hollti. Gostyngwch hyn i orfodi hollt ar gylchred a gyfunwyd yn anghywir." + } + }, + "cleanup_profile": { + "title": "Glanhau Hanes - Dewiswch Proffil", + "description": "Dewiswch broffil i'w ddelweddu. Bydd hyn yn cynhyrchu graff yn dangos yr holl gylchredau yn y gorffennol ar gyfer y proffil hwn i helpu i nodi allgleifion.", + "data": { + "profile": "Proffil" + } + }, + "cleanup_select": { + "title": "Glanhau Hanes - Graff a Dileu", + "description": "![graff]({graph_url})\n\n**Delweddu {profile_name}**\n\nMae'r graff yn dangos cylchoedd unigol fel llinellau lliw. Nodwch allgleifion (llinellau ymhell o'r grŵp) a chyfatebwch eu lliw yn y rhestr isod i'w dileu.\n\n**Dewiswch gylchoedd i'w dileu'n BARHAOL:**", + "data": { + "cycles_to_delete": "Cylchoedd i'w Dileu (gwiriwch y lliwiau cyfatebol)" + } + }, + "interactive_editor": { + "title": "Cyfuno/Hollti Golygydd Rhyngweithiol", + "description": "Dewiswch weithred â llaw i'w chyflawni ar eich hanes beicio.", + "menu_options": { + "editor_split": "Rhannu Cylch (Dod o hyd i fylchau)", + "editor_merge": "Cyfuno Cylchoedd (Ymuno darnau)", + "editor_delete": "Dileu Cylchoedd", + "menu_back": "← Yn ôl" + } + }, + "editor_select": { + "title": "Dewiswch Gylchoedd", + "description": "Dewiswch y cylch(au) i'w prosesu.\n\n{info_text}", + "data": { + "selected_cycles": "Beiciau" + } + }, + "editor_configure": { + "title": "Ffurfweddu a Chymhwyso", + "description": "{preview_md}", + "data": { + "confirm_action": "Gweithred", + "merged_profile": "Proffil Canlyniad", + "new_profile_name": "Enw Proffil Newydd", + "confirm_commit": "Ydw, rwy'n cadarnhau'r cam hwn", + "segment_0_profile": "Proffil Segment 1", + "segment_1_profile": "Proffil Segment 2", + "segment_2_profile": "Proffil Segment 3", + "segment_3_profile": "Proffil Segment 4", + "segment_4_profile": "Proffil Segment 5", + "segment_5_profile": "Proffil Segment 6", + "segment_6_profile": "Proffil Segment 7", + "segment_7_profile": "Proffil Segment 8", + "segment_8_profile": "Proffil Segment 9", + "segment_9_profile": "Proffil Segment 10" + } + }, + "editor_split_params": { + "title": "Ffurfweddiad Hollti", + "description": "Addaswch y sensitifrwydd ar gyfer hollti'r cylch hwn. Bydd unrhyw fwlch segur yn hwy na hyn yn achosi hollt.", + "data": { + "split_mode": "Dull hollti" + } + }, + "editor_split_auto_params": { + "title": "Ffurfweddiad Hollti Awtomatig", + "description": "Addaswch y sensitifrwydd ar gyfer hollti'r cylch hwn. Bydd unrhyw fwlch segur hirach na hwn yn achosi hollt.", + "data": { + "min_gap_seconds": "Trothwy Bwlch Hollti (eiliadau)" + } + }, + "editor_split_manual_params": { + "title": "Stampiau amser hollti â llaw", + "description": "{preview_md}Ffenestr y cylch: **{cycle_start_wallclock} → {cycle_end_wallclock}** ar {cycle_date}.\n\nRhowch un neu fwy o stampiau amser hollti (`HH:MM` neu `HH:MM:SS`), un fesul llinell. Bydd y cylch yn cael ei dorri ym mhob stamp amser — mae N stamp amser yn cynhyrchu N+1 segment.", + "data": { + "split_timestamps": "Stampiau Amser Hollti" + } + }, + "reprocess_history": { + "title": "Cynnal a Chadw: Ailbrosesu ac Optimeiddio Data", + "description": "Yn glanhau data hanesyddol yn ddwfn:\n1. Trimio darlleniadau pŵer sero (tawelwch).\n2. Yn trwsio amseriad/hyd y cylch.\n3. Yn ailgyfrifo'r holl fodelau ystadegol (amlenni).\n\n**Rhedwch hwn i drwsio problemau data neu gymhwyso algorithmau newydd.**" + }, + "wipe_history": { + "title": "Sychu Hanes (Profi yn Unig)", + "description": "⚠️ Bydd hyn yn dileu POB cylch a phroffil sydd wedi'u storio ar gyfer y ddyfais hon yn barhaol. Ni ellir dadwneud hyn!" + }, + "export_import": { + "title": "Allforio/Mewnforio JSON", + "description": "Copïo/gludo cylchoedd, proffiliau, A gosodiadau ar gyfer y ddyfais hon. Allforio isod neu bastio JSON i fewnforio.", + "data": { + "mode": "Gweithred", + "json_payload": "Cyfluniad Cyflawn JSON" + }, + "data_description": { + "mode": "Dewiswch Allforio i gopïo data a gosodiadau cyfredol, neu Mewnforio i gludo data wedi'i allforio o ddyfais WashData arall.", + "json_payload": "Ar gyfer Allforio: copïwch y JSON hwn (yn cynnwys cylchoedd, proffiliau, a'r holl leoliadau wedi'u mireinio). Ar gyfer Mewnforio: pastiwch JSON wedi'i allforio o WashData." + } + }, + "record_cycle": { + "title": "Cylch Cofnodi", + "description": "Cofnodwch gylchred â llaw i greu proffil glân heb ymyrraeth.\n\nStatws: **{status}**\nHyd: {duration}s\nSamplau: {samples}", + "menu_options": { + "record_refresh": "Statws Adnewyddu", + "record_stop": "Stopio Recordio (Cadw a Phrosesu)", + "record_start": "Dechrau Recordio Newydd", + "record_process": "Prosesu Recordiad Diwethaf (Trimio ac Arbed)", + "record_discard": "Gwaredu Recordiad Diwethaf", + "menu_back": "← Yn ôl" + } + }, + "record_process": { + "title": "Cofnodi Proses", + "description": "![graff]({graph_url})\n\n**Graff yn dangos recordiad amrwd.** Glas = Cadw, Coch = Trimio Arfaethedig.\n*Mae'r graff yn statig ac nid yw'n diweddaru gyda newidiadau ffurflen.*\n\nStopiwyd y recordio. Mae trimiau wedi'u halinio i'r gyfradd samplu a ganfuwyd (~{sampling_rate}s).\n\nAmrwd Hyd: {duration}s\nSamplau: {samples}", + "data": { + "head_trim": "Trimio Cychwyn (eiliadau)", + "tail_trim": "Diwedd Trimio (eiliadau)", + "save_mode": "Cadw Cyrchfan", + "profile_name": "Enw Proffil" + } + }, + "learning_feedbacks": { + "title": "Adborth Dysgu", + "description": "Adborth ar y gweill: {count}\n\n{pending_table}\n\nDewiswch gais adolygiad cylch i'w brosesu.", + "menu_options": { + "learning_feedbacks_pick": "Adolygu adborth sydd ar y gweill", + "learning_feedbacks_dismiss_all": "Diystyru'r holl adborth sydd ar y gweill", + "menu_back": "← Yn ôl" + } + }, + "learning_feedbacks_pick": { + "title": "Dewiswch adborth i'w adolygu", + "description": "Dewiswch gais adolygu cylch i'w agor.", + "data": { + "selected_feedback": "Dewiswch y cylch i'w adolygu" + } + }, + "learning_feedbacks_empty": { + "title": "Adborth Dysgu", + "description": "Ni chanfuwyd unrhyw geisiadau adborth sydd ar y gweill.", + "menu_options": { + "menu_back": "← Yn ôl" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Gwrthod Pob Adborth Sy'n Aros", + "description": "Rydych ar fin gwrthod **{count}** cais adborth sy'n aros. Bydd cylchoedd a wrthodwyd yn aros yn eich hanes ond ni fyddant yn cyfrannu signal dysgu newydd. Ni ellir dadwneud hyn.\n\n✅ Ticiwch y blwch isod a chliciwch **Cyflwyno** i wrthod popeth.\n❌ Gadewch ef heb ei dicio a chliciwch **Cyflwyno** i ganslo a mynd yn ôl.", + "data": { + "confirm_dismiss_all": "Cadarnhau: diystyru'r holl geisiadau adborth sydd ar y gweill" + } + }, + "resolve_feedback": { + "title": "Datrys Adborth", + "description": "{comparison_img}{candidates_table}\n**Proffil Wedi'i Ganfod**: {detected_profile} ({confidence_pct}%)\n**Amcangyfrif o Hyd**: {est_duration_min} mun\n**Hyd Gwirioneddol**: {act_duration_min} mun\n\n__Dewiswch weithred isod:__", + "data": { + "action": "Beth hoffech chi ei wneud?", + "corrected_profile": "Rhaglen Gywir (os yn cywiro)", + "corrected_duration": "Cywir Hyd mewn eiliadau (os yn cywiro)" + } + }, + "trim_cycle_select": { + "title": "Trim Cycle - Dewiswch Beic", + "description": "Dewiswch gylchred i'w docio. ✓ = cwblhau, ⚠ = ailddechrau, ✗ = torri ar draws", + "data": { + "cycle_id": "Beicio" + } + }, + "trim_cycle": { + "title": "Cylch Trimio", + "description": "Ffenestr gyfredol: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Gweithred" + } + }, + "trim_cycle_start": { + "title": "Gosod Trim Start", + "description": "Ffenestr feicio: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nCychwyn presennol: **{current_wallclock}** (+{current_offset_min} munud o ddechrau'r cylch)\n\nDewiswch amser cychwyn newydd gan ddefnyddio'r codwr cloc isod.", + "data": { + "trim_start_time": "Amser cychwyn newydd", + "trim_start_min": "Dechrau newydd (munudau o ddechrau'r beic)" + } + }, + "trim_cycle_end": { + "title": "Gosod Trim End", + "description": "Ffenestr feicio: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nDiwedd presennol: **{current_wallclock}** (+{current_offset_min} munud o ddechrau'r cylch)\n\nDewiswch amser gorffen newydd gan ddefnyddio'r codwr cloc isod.", + "data": { + "trim_end_time": "Amser gorffen newydd", + "trim_end_min": "Diwedd newydd (munudau o ddechrau'r beic)" + } + } + }, + "error": { + "import_failed": "Methodd y mewnforio. Gludwch allforyn WashData dilys JSON.", + "select_exactly_one": "Dewiswch union un cylch ar gyfer y weithred hon.", + "select_at_least_one": "Dewiswch o leiaf un cylch ar gyfer y weithred hon.", + "select_at_least_two": "Dewiswch o leiaf ddau gylch ar gyfer y weithred hon.", + "profile_exists": "Mae proffil gyda'r enw hwn eisoes yn bodoli", + "rename_failed": "Wedi methu ag ailenwi'r proffil. Efallai nad yw'r proffil yn bodoli neu fod enw newydd wedi'i gymryd yn barod.", + "assignment_failed": "Wedi methu ag aseinio proffil i feicio", + "duplicate_phase": "Mae cyfnod gyda'r enw hwn eisoes yn bodoli.", + "phase_not_found": "Ni chanfuwyd y cyfnod a ddewiswyd.", + "invalid_phase_name": "Ni all enw'r cyfnod fod yn wag.", + "phase_name_too_long": "Mae enw'r cyfnod yn rhy hir.", + "invalid_phase_range": "Mae'r ystod cyfnod yn annilys. Rhaid i'r diwedd fod yn fwy na'r dechrau.", + "invalid_phase_timestamp": "Mae'r stamp amser yn annilys. Defnyddiwch fformat BBBB-MM-DD HH:MM neu HH:MM.", + "overlapping_phase_ranges": "Mae ystodau cyfnodau yn gorgyffwrdd. Addaswch yr ystodau a cheisiwch eto.", + "incomplete_phase_row": "Rhaid i bob rhes cam gynnwys cam ynghyd â gwerthoedd cychwyn/diwedd cyflawn ar gyfer y modd a ddewiswyd.", + "timestamp_mode_cycle_required": "Dewiswch gylchred ffynhonnell wrth ddefnyddio modd stamp amser.", + "no_phase_ranges": "Nid oes unrhyw ystodau cyfnod ar gael ar gyfer y cam gweithredu hwnnw eto.", + "feedback_notification_title": "Golchi Data: Cadarnhau'r Cylch ({device})", + "feedback_notification_message": "**Dyfais**: {device}\n**Rhaglen**: {program} ({confidence}% hyder)\n**Amser**: {time}\n\nMae angen eich help ar WashData i ddilysu'r cylch canfyddedig hwn.\n\nEwch i **Gosodiadau > Dyfeisiau a Gwasanaethau > Golchi Data > Ffurfweddu > Adborth Dysgu** i gadarnhau neu gywiro'r canlyniad hwn.", + "suggestions_ready_notification_title": "Data Golchi: Gosodiadau Awgrymedig Yn Barod ({device})", + "suggestions_ready_notification_message": "Mae'r synhwyrydd **Gosodiadau a Awgrymir** bellach yn adrodd ar argymhellion y gellir eu gweithredu **{count}**.\n\nI'w hadolygu a'u cymhwyso: **Gosodiadau > Dyfeisiau a Gwasanaethau > Golchi Data > Ffurfweddu > Gosodiadau Uwch > Cymhwyso Gwerthoedd a Awgrymir**.\n\nMae awgrymiadau yn ddewisol ac yn cael eu dangos i'w hadolygu cyn i chi gadw.", + "trim_range_invalid": "Rhaid dechrau cyn diwedd. Addaswch eich pwyntiau trimio.", + "trim_failed": "Methodd trimio. Efallai nad yw'r cylch yn bodoli mwyach neu mae'r ffenestr yn wag.", + "invalid_split_timestamp": "Mae'r stamp amser yn annilys neu y tu allan i ffenestr y cylch. Defnyddiwch HH:MM neu HH:MM:SS o fewn ffenestr y cylch.", + "no_split_segments_found": "Nid oedd modd cynhyrchu unrhyw segmentau dilys o'r stampiau amser hyn. Sicrhewch fod pob stamp amser o fewn ffenestr y cylch a bod segmentau o leiaf 1 funud o hyd.", + "auto_tune_suggestion": "{device_type} {device_title} cylchoedd ysbrydion wedi'u canfod. Newid min_power a awgrymir: {current_min}W -> {new_min}W (heb ei gymhwyso'n awtomatig).", + "auto_tune_title": "Awto-Alaw WashData", + "auto_tune_fallback": "{device_type} {device_title} cylchoedd ysbrydion wedi'u canfod. Newid min_power a awgrymir: {current_min}W -> {new_min}W (heb ei gymhwyso'n awtomatig)." + }, + "abort": { + "no_cycles_found": "Heb ganfod cylchoedd", + "no_split_segments_found": "Ni chanfuwyd unrhyw segmentau y gellid eu hollti yn y cylch a ddewiswyd.", + "cycle_not_found": "Nid oedd modd llwytho'r cylch a ddewiswyd.", + "no_power_data": "Dim data olrhain pŵer ar gael ar gyfer y cylch a ddewiswyd.", + "no_profiles_found": "Heb ganfod proffiliau. Creu proffil yn gyntaf.", + "no_profiles_for_matching": "Dim proffiliau ar gael i'w paru. Creu proffiliau yn gyntaf.", + "no_unlabeled_cycles": "Mae pob cylch eisoes wedi'i labelu", + "no_suggestions": "Dim gwerthoedd awgrymedig ar gael eto. Rhedeg ychydig o gylchoedd a rhoi cynnig arall arni.", + "no_predictions": "Dim rhagfynegiadau", + "no_custom_phases": "Ni chanfuwyd unrhyw gamau personol.", + "reprocess_success": "Wedi ailbrosesu {count} cylch yn llwyddiannus.", + "debug_data_cleared": "Llwyddwyd i glirio data dadfygio o {count} gylchred." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cychwyn Beicio", + "cycle_finish": "Gorffen Beicio", + "cycle_live": "Cynnydd Byw" + } + }, + "common_text": { + "options": { + "unlabeled": "(Heb ei labelu)", + "create_new_profile": "Creu Proffil Newydd", + "remove_label": "Dileu Label", + "no_reference_cycle": "(Dim cylch cyfeirio)", + "all_device_types": "Pob Math o Ddyfais", + "current_suffix": "(cyfredol)", + "editor_select_info": "Dewiswch 1 cylch i rannu, neu 2+ gylchred i uno.", + "editor_select_info_split": "Dewiswch 1 cylch i'w hollti.", + "editor_select_info_merge": "Dewiswch 2+ gylch i'w cyfuno.", + "editor_select_info_delete": "Dewiswch 1+ cylch i'w ddileu.", + "no_cycles_recorded": "Dim cylchoedd wedi'u cofnodi eto.", + "profile_summary_line": "- **{name}**: {count} cylchredau, {avg}m cyf", + "no_profiles_created": "Dim proffiliau wedi'u creu eto.", + "phase_preview": "Rhagolwg Cyfnod", + "phase_preview_no_curve": "Nid yw cromlin proffil cyfartalog ar gael eto. Rhedeg/labelu mwy o gylchoedd ar gyfer y proffil hwn.", + "average_power_curve": "Cromlin Pŵer Cyfartalog", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cylch, ~{duration}m cyf)", + "profile_option_short_fmt": "{name} ({count} cylch, ~{duration}m)", + "deleted_cycles_from_profile": "Wedi dileu {count} cylch o {profile}.", + "not_enough_data_graph": "Dim digon o ddata i gynhyrchu graff.", + "no_profiles_found": "Heb ganfod proffiliau.", + "cycle_info_fmt": "Cylchred: {start}, {duration}m, Cyfredol: {label}", + "top_candidates_header": "### Ymgeiswyr Gorau", + "tbl_profile": "Proffil", + "tbl_confidence": "Hyder", + "tbl_correlation": "Cydberthynas", + "tbl_duration_match": "Hyd Match", + "detected_profile": "Proffil Wedi'i Ganfod", + "estimated_duration": "Hyd Amcangyfrif", + "actual_duration": "Hyd Gwirioneddol", + "choose_action": "__Dewiswch weithred isod:__", + "tbl_count": "Cyfri", + "tbl_avg": "Cyf", + "tbl_min": "Lleiaf", + "tbl_max": "Max", + "tbl_energy_avg": "Egni (Cyf)", + "tbl_energy_total": "Ynni (Cyfanswm)", + "tbl_consistency": "Cynwys.", + "tbl_last_run": "Rhedeg Olaf", + "graph_legend_title": "Chwedl Graff", + "graph_legend_body": "Mae'r band glas yn cynrychioli'r amrediad tynnu pŵer lleiaf ac uchaf a welwyd. Mae'r llinell yn dangos y gromlin pŵer cyfartalog.", + "recording_preview": "Rhagolwg Cofnodi", + "trim_start": "Dechrau Trimio", + "trim_end": "Trim End", + "split_preview_title": "Rhagolwg Hollti", + "split_preview_found_fmt": "Wedi dod o hyd i {count} segment.", + "split_preview_confirm_fmt": "Cliciwch Cadarnhau i rannu'r cylch hwn yn {count} gylchred ar wahân.", + "merge_preview_title": "Cyfuno Rhagolwg", + "merge_preview_joining_fmt": "Yn ymuno â {count} cylch. Bydd bylchau yn cael eu llenwi â darlleniadau 0W.", + "editor_delete_preview_title": "Rhagolwg Dileu", + "editor_delete_preview_intro": "Bydd y cylchoedd a ddewiswyd yn cael eu dileu'n barhaol:", + "editor_delete_preview_confirm": "Cliciwch Cadarnhau i ddileu'r cofnodion cylch hyn yn barhaol.", + "no_power_preview": "* Dim data pŵer ar gael ar gyfer rhagolwg.*", + "profile_comparison": "Cymhariaeth Proffil", + "actual_cycle_label": "Y cylch hwn (gwirioneddol)", + "storage_usage_header": "Defnydd Storio", + "storage_file_size": "Maint Ffeil", + "storage_cycles": "Beiciau", + "storage_profiles": "Proffiliau", + "storage_debug_traces": "Olion Dadfygio", + "overview_suffix": "Trosolwg", + "phase_preview_for_profile": "Rhagolwg cyfnod ar gyfer proffil", + "current_ranges_header": "Ystodau cyfredol", + "assign_phases_help": "Defnyddiwch y gweithredoedd isod i ychwanegu, golygu, dileu, neu gadw ystodau.", + "profile_summary_header": "Crynodeb Proffil", + "recent_cycles_header": "Cylchoedd diweddar", + "trim_cycle_preview_title": "Rhagolwg Trim Beic", + "trim_cycle_preview_no_data": "Nid oes data pŵer ar gael ar gyfer y cylch hwn.", + "trim_cycle_preview_kept_suffix": "cadw", + "table_program": "Rhaglen", + "table_when": "Pryd", + "table_length": "Hyd", + "table_match": "Cydweddiad", + "table_profile": "Proffil", + "table_cycles": "Beiciau", + "table_avg_length": "Hyd Cyf.", + "table_last_run": "Rhedeg Olaf", + "table_avg_energy": "Ynni Cyf.", + "table_detected_program": "Rhaglen a Ganfuwyd", + "table_confidence": "Hyder", + "table_reported": "Adroddwyd", + "phase_builtin_suffix": "(Adeiledig)", + "phase_none_available": "Dim cyfnodau ar gael.", + "settings_deprecation_warning": "⚠️ **Math o ddyfais anghymeradwy:** Mae {device_type} wedi'i drefnu i'w dynnu mewn datganiad yn y dyfodol. Nid yw piblinell baru WashData yn cynhyrchu canlyniadau dibynadwy ar gyfer y dosbarth offer hwn. Mae eich integreiddio yn parhau i weithio drwy'r cyfnod dibrisio; i dawelu’r rhybudd hwn, newidiwch **Math o Ddyfais** isod i un o’r mathau a gefnogir (Peiriant Golchi, Sychwr, Combo Golchwr-Sychwr, Peiriant golchi llestri, ffrïwr aer, gwneuthurwr bara, neu bwmp), neu i **Arall (Uwch)** os nad yw’ch teclyn yn cyfateb i unrhyw un o’r mathau a gefnogir. Mae **Arall (Uwch)** yn cludo rhagosodiadau generig bwriadol nad ydynt wedi'u tiwnio ar gyfer unrhyw declyn penodol, felly bydd angen i chi ffurfweddu trothwyon, goramser a pharamedrau cyfatebol eich hun; cedwir eich holl osodiadau presennol pan fyddwch yn newid.", + "phase_other_device_types": "Mathau eraill o ddyfeisiau:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Rhannu Cylch (Dod o hyd i fylchau)", + "merge": "Cyfuno Cylchoedd (Ymuno darnau)", + "delete": "Dileu Cylchoedd" + } + }, + "split_mode": { + "options": { + "auto": "Canfod bylchau segur yn awtomatig", + "manual": "Stamp(iau) amser â llaw" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Cynnal a Chadw: Ailbrosesu ac Optimeiddio Data", + "clear_debug_data": "Clirio Data Dadfygio (Rhyddhau lle)", + "wipe_history": "Sychwch POB data ar gyfer y ddyfais hon (anghildroadwy)", + "export_import": "Allforio/Mewnforio JSON gyda gosodiadau (copïo/gludo)" + } + }, + "export_import_mode": { + "options": { + "export": "Allforio Pob Data", + "import": "Mewnforio / Cyfuno Data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Awto-Labelu Hen Feiciau", + "select_cycle_to_label": "Label Cylch Penodol", + "select_cycle_to_delete": "Dileu Beic", + "interactive_editor": "Cyfuno/Hollti Golygydd Rhyngweithiol", + "trim_cycle": "Trimio Data Beicio" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Creu Proffil Newydd", + "edit_profile": "Golygu/Ailenwi Proffil", + "delete_profile": "Dileu Proffil", + "profile_stats": "Ystadegau Proffil", + "cleanup_profile": "Glanhau Hanes - Graff a Dileu", + "assign_phases": "Neilltuo Ystod Cyfnod" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Creu Cyfnod Newydd", + "edit_custom_phase": "Golygu Cyfnod", + "delete_custom_phase": "Dileu Cyfnod" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Ychwanegu Ystod Cyfnod", + "edit_range": "Golygu Ystod Cyfnod", + "delete_range": "Dileu Ystod Cyfnod", + "clear_ranges": "Clirio Pob Ystod", + "auto_detect_ranges": "Auto-canfod Cyfnodau", + "save_ranges": "Cadw a Dychwelyd" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Statws Adnewyddu", + "stop_recording": "Stopio Recordio (Cadw a Phrosesu)", + "start_recording": "Dechrau Recordio Newydd", + "process_recording": "Prosesu Recordiad Diwethaf (Trimio ac Arbed)", + "discard_recording": "Gwaredu Recordiad Diwethaf" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Creu Proffil Newydd", + "existing_profile": "Ychwanegu at y Proffil Presennol", + "discard": "Taflwch Recordio" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Cadarnhau - Canfod Cywir", + "correct": "Cywir - Dewiswch Raglen/Hyd", + "ignore": "Anwybyddu - Cylchred Anghywir/Swnllyd", + "delete": "Dileu - Dileu o Hanes" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Enw a chamau cymhwyso", + "cancel": "Ewch yn ôl heb newidiadau" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Gosod Man Cychwyn", + "set_end": "Gosod Pwynt Terfyn", + "reset": "Ailosod i Hyd Llawn", + "apply": "Gwneud cais Trim", + "cancel": "Canslo" + } + }, + "device_type": { + "options": { + "washing_machine": "Peiriant Golchi", + "dryer": "Sychwr", + "washer_dryer": "Combo Golchwr-Sychwr", + "dishwasher": "Peiriant golchi llestri", + "coffee_machine": "Peiriant Coffi (anghymeradwy)", + "ev": "Cerbyd Trydan (anghymeradwy)", + "air_fryer": "Fryer aer", + "heat_pump": "Pwmp Gwres (anghymeradwy)", + "bread_maker": "Gwneuthurwr Bara", + "pump": "Pwmp / Swmp Pwmp", + "oven": "Popty (anghymeradwy)", + "other": "Arall (Uwch)" + } + } + }, + "services": { + "label_cycle": { + "name": "Cylch Label", + "description": "Neilltuo proffil sy'n bodoli eisoes i gylchred gorffennol, neu ddileu'r label.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi i'w labelu." + }, + "cycle_id": { + "name": "ID Beic", + "description": "ID y gylchred i'w labelu." + }, + "profile_name": { + "name": "Enw Proffil", + "description": "Enw proffil sy'n bodoli eisoes (creu proffiliau yn y ddewislen Rheoli Proffiliau). Gadewch yn wag i dynnu label." + } + } + }, + "create_profile": { + "name": "Creu Proffil", + "description": "Creu proffil newydd (annibynnol neu yn seiliedig ar gylchred cyfeirio).", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi." + }, + "profile_name": { + "name": "Enw Proffil", + "description": "Enw'r proffil newydd (e.e. 'Trwm Dyletswydd', 'Delicates')." + }, + "reference_cycle_id": { + "name": "ID Beic Cyfeirnod", + "description": "ID cylch dewisol i seilio'r proffil hwn arno." + } + } + }, + "delete_profile": { + "name": "Dileu Proffil", + "description": "Dileu proffil a dad-labelu cylchoedd yn ei ddefnyddio.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi." + }, + "profile_name": { + "name": "Enw Proffil", + "description": "Y proffil i'w ddileu." + }, + "unlabel_cycles": { + "name": "Dad-labelu Cylchoedd", + "description": "Tynnwch label proffil o gylchoedd gan ddefnyddio'r proffil hwn." + } + } + }, + "auto_label_cycles": { + "name": "Awto-Labelu Hen Feiciau", + "description": "Labelu cylchoedd heb eu labelu yn ôl-weithredol gan ddefnyddio paru proffil.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi." + }, + "confidence_threshold": { + "name": "Trothwy Hyder", + "description": "Isafswm hyder cyfatebol (0.50-0.95) i gymhwyso labeli." + } + } + }, + "export_config": { + "name": "Allforio", + "description": "Allforio proffiliau, cylchoedd a gosodiadau'r ddyfais hon i ffeil JSON (fesul dyfais).", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Y ddyfais peiriant golchi i allforio." + }, + "path": { + "name": "Llwybr", + "description": "Llwybr ffeil absoliwt dewisol i'w ysgrifennu (diofyn i /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Mewnforio", + "description": "Mewnforio proffiliau, cylchoedd a gosodiadau ar gyfer y ddyfais hon o ffeil allforio JSON (efallai y bydd y gosodiadau wedi'u trosysgrifo).", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi i fewnforio iddo." + }, + "path": { + "name": "Llwybr", + "description": "Llwybr absoliwt i'r ffeil JSON allforio i'w mewnforio." + } + } + }, + "submit_cycle_feedback": { + "name": "Cyflwyno Adborth Beicio", + "description": "Cadarnhau neu gywiro rhaglen a ganfuwyd yn awtomatig ar ôl cylch gorffenedig. Darparwch naill ai `mynediad_id` (uwch) neu `device_id` (argymhellir).", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Y ddyfais WashData (argymhellir yn lle entry_id)." + }, + "entry_id": { + "name": "ID mynediad", + "description": "Yr id cofnod ffurfweddu ar gyfer y ddyfais (yn lle device_id)." + }, + "cycle_id": { + "name": "ID Beic", + "description": "Yr ID cylch a ddangosir yn yr hysbysiad adborth / logiau." + }, + "user_confirmed": { + "name": "Cadarnhau Rhaglen Wedi'i Chanfod", + "description": "Gosod yn wir os yw'r rhaglen a ganfuwyd yn gywir." + }, + "corrected_profile": { + "name": "Proffil wedi'i Gywiro", + "description": "Os na chaiff ei gadarnhau, rhowch enw cywir y proffil/rhaglen." + }, + "corrected_duration": { + "name": "Hyd wedi'i Gywiro (eiliadau)", + "description": "Hyd wedi'i gywiro opsiynol mewn eiliadau." + }, + "notes": { + "name": "Nodiadau", + "description": "Nodiadau dewisol am y cylch hwn." + } + } + }, + "record_start": { + "name": "Cofnodi Cychwyn Beicio", + "description": "Dechreuwch recordio cylch glân â llaw (gan osgoi'r holl resymeg gyfatebol).", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi i gofnodi." + } + } + }, + "record_stop": { + "name": "Cofnodi Stop Beic", + "description": "Stopio recordio â llaw.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Dyfais y peiriant golchi i roi'r gorau i recordio." + } + } + }, + "trim_cycle": { + "name": "Cylch Trimio", + "description": "Torrwch ddata pŵer cylchred gorffennol i ffenestr amser benodol.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Y ddyfais WashData." + }, + "cycle_id": { + "name": "ID Beic", + "description": "ID y cylch i'w docio." + }, + "trim_start_s": { + "name": "Trimio Cychwyn (eiliadau)", + "description": "Cadw data o'r gwrthbwyso hwn mewn eiliadau. Rhagosodiad 0." + }, + "trim_end_s": { + "name": "Diwedd Trimio (eiliadau)", + "description": "Cadw data hyd at y gwrthbwyso hwn mewn eiliadau. Diofyn = hyd llawn." + } + } + }, + "pause_cycle": { + "name": "Seibio Beicio", + "description": "Oedwch y cylch gweithredol ar gyfer dyfais WashData.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Y ddyfais WashData i seibio." + } + } + }, + "resume_cycle": { + "name": "Ail-ddechrau Beicio", + "description": "Ail-ddechrau cylch seibio ar gyfer dyfais WashData.", + "fields": { + "device_id": { + "name": "Dyfais", + "description": "Y ddyfais WashData i ailddechrau." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Rhedeg" + }, + "match_ambiguity": { + "name": "Amwysedd cyfatebol" + } + }, + "sensor": { + "washer_state": { + "name": "Cyflwr", + "state": { + "off": "I ffwrdd", + "idle": "Segur", + "starting": "Yn dechrau", + "running": "Rhedeg", + "paused": "Wedi seibio", + "user_paused": "Wedi'i oedi gan y defnyddiwr", + "ending": "Yn dod i ben", + "finished": "Wedi gorffen", + "anti_wrinkle": "Gwrth-Wrinkle", + "delay_wait": "Aros i Ddechrau", + "interrupted": "Torri ar draws", + "force_stopped": "Gorfod Wedi Stopio", + "rinse": "Rinsiwch", + "unknown": "Anhysbys", + "clean": "Glan" + } + }, + "washer_program": { + "name": "Rhaglen" + }, + "time_remaining": { + "name": "Amser yn weddill" + }, + "total_duration": { + "name": "Cyfanswm Hyd" + }, + "cycle_progress": { + "name": "Cynnydd" + }, + "current_power": { + "name": "Pŵer Cyfredol" + }, + "elapsed_time": { + "name": "Amser Aeth heibio" + }, + "debug_info": { + "name": "Gwybodaeth Dadfygio" + }, + "match_confidence": { + "name": "Hyder Cyfateb" + }, + "top_candidates": { + "name": "Ymgeiswyr Gorau", + "state": { + "none": "Dim" + } + }, + "current_phase": { + "name": "Cyfnod Presennol" + }, + "wash_phase": { + "name": "Cyfnod" + }, + "profile_cycle_count": { + "name": "Proffil {profile_name} Cyfrif", + "unit_of_measurement": "cylchoedd" + }, + "suggestions": { + "name": "Gosodiadau a Awgrymir" + }, + "pump_runs_today": { + "name": "Rhediadau Pwmp (24 h diwethaf)" + }, + "cycle_count": { + "name": "Cyfrif Beiciau" + } + }, + "select": { + "program_select": { + "name": "Rhaglen Beicio", + "state": { + "auto_detect": "Auto-Canfod" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Gorfodi Diwedd y Cylch" + }, + "pause_cycle": { + "name": "Seibio Beicio" + }, + "resume_cycle": { + "name": "Ail-ddechrau Beicio" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Mae angen ID device_id dilys." + }, + "cycle_id_required": { + "message": "Mae angen cycle_id dilys." + }, + "profile_name_required": { + "message": "Mae angen profile_name dilys." + }, + "device_not_found": { + "message": "Heb ganfod dyfais." + }, + "no_config_entry": { + "message": "Ni chanfuwyd cofnod ffurfweddu ar gyfer dyfais." + }, + "integration_not_loaded": { + "message": "Integreiddio heb ei lwytho ar gyfer y ddyfais hon." + }, + "cycle_not_found_or_no_power": { + "message": "Beic heb ei ganfod neu nid oes ganddo ddata pŵer." + }, + "trim_failed_empty_window": { + "message": "Methodd y trimio - ni chanfuwyd y cylch, dim data pŵer, neu mae'r ffenestr ganlyniadol yn wag." + }, + "trim_invalid_range": { + "message": "rhaid i trim_end_s fod yn fwy na trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/da.json b/custom_components/ha_washdata/translations/da.json new file mode 100644 index 0000000..1cb9972 --- /dev/null +++ b/custom_components/ha_washdata/translations/da.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData opsætning", + "description": "Konfigurer din vaskemaskine eller andet apparat.\n\nStrømsensor er påkrævet.\n\n**Derefter bliver du spurgt, om du vil oprette din første profil.**", + "data": { + "name": "Enhedens navn", + "device_type": "Enhedstype", + "power_sensor": "Strømsensor", + "min_power": "Minimum effekttærskel (W)" + }, + "data_description": { + "name": "Et venligt navn for denne enhed (f.eks. \"Vaskemaskine\", \"Opvaskemaskine\").", + "device_type": "Hvilken type apparat er dette? Hjælper med at skræddersy detektering og mærkning.", + "power_sensor": "Sensorenheden, der rapporterer strømforbrug i realtid (i watt) fra dit smartstik.", + "min_power": "Effektaflæsninger over denne tærskel (i watt) indikerer, at apparatet kører. Start med 2W for de fleste enheder." + } + }, + "first_profile": { + "title": "Opret første profil", + "description": "En **cyklus** er en komplet kørsel af dit apparat (f.eks. et læs vasketøj). En **Profil** grupperer disse cyklusser efter type (f.eks. \"Bomuld\", \"Hurtig vask\"). Oprettelse af en profil hjælper nu integrationen med at estimere varigheden med det samme.\n\nDu kan manuelt vælge denne profil i enhedskontrollerne, hvis den ikke registreres automatisk.\n\n**Lad \"Profilnavn\" stå tomt for at springe dette trin over.**", + "data": { + "profile_name": "Profilnavn (valgfrit)", + "manual_duration": "Estimeret varighed (minutter)" + } + } + }, + "error": { + "cannot_connect": "Kunne ikke oprette forbindelse", + "invalid_auth": "Ugyldig godkendelse", + "unknown": "Uventet fejl" + }, + "abort": { + "already_configured": "Enheden er allerede konfigureret" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Gennemgå læringsfeedback", + "settings": "Indstillinger", + "notifications": "Meddelelser", + "manage_cycles": "Administrer cyklusser", + "manage_profiles": "Administrer profiler", + "manage_phase_catalog": "Administrer fasekatalog", + "record_cycle": "Optagecyklus (manuel)", + "diagnostics": "Diagnostik og vedligeholdelse" + } + }, + "apply_suggestions": { + "title": "Kopier foreslåede værdier", + "description": "Dette vil kopiere de aktuelle foreslåede værdier til dine gemte indstillinger. Dette er en engangshandling og aktiverer ikke automatisk overskrivning.\n\nForeslåede værdier at kopiere:\n{suggested}", + "data": { + "confirm": "Bekræft kopihandling" + } + }, + "apply_suggestions_confirm": { + "title": "Gennemgå foreslåede ændringer", + "description": "WashData fundet **{pending_count}** foreslåede værdi(er) at anvende.\n\nÆndringer:\n{changes}\n\n✅ Marker afkrydsningsfeltet nedenfor, og klik på **Send** for at anvende og gemme disse ændringer med det samme.\n❌ Lad det være umarkeret, og klik på **Send** for at annullere og gå tilbage.", + "data": { + "confirm_apply_suggestions": "Anvend og gem disse ændringer" + } + }, + "settings": { + "title": "Indstillinger", + "description": "{deprecation_warning}Indstil registreringstærskler, indlæring og avanceret adfærd. Standarder fungerer godt for de fleste opsætninger.\n\nForeslåede indstillinger tilgængelige nu: {suggestions_count}.\nHvis dette er over 0, åbn Avancerede indstillinger og brug 'Anvend foreslåede værdier' til at gennemgå anbefalingerne.", + "data": { + "apply_suggestions": "Anvend foreslåede værdier", + "device_type": "Enhedstype", + "power_sensor": "Strømsensorenhed", + "min_power": "Minimum effekt (W)", + "off_delay": "Cyklusslutforsinkelse (s)", + "notify_actions": "Underretningshandlinger", + "notify_people": "Udsæt levering, indtil disse mennesker er hjemme", + "notify_only_when_home": "Forsink meddelelser, indtil den valgte person er hjemme", + "notify_fire_events": "Udløs automatiseringshændelser", + "notify_start_services": "Cyklusstart - Notifikationsmål", + "notify_finish_services": "Cyklus afsluttet - notifikationsmål", + "notify_live_services": "Live fremskridt - Notifikationsmål", + "notify_before_end_minutes": "Underretning om forudgående afslutning (minutter før afslutning)", + "notify_title": "Underretningens titel", + "notify_icon": "Meddelelsesikon", + "notify_start_message": "Start meddelelsesformat", + "notify_finish_message": "Afslut meddelelsesformat", + "notify_pre_complete_message": "Format for forudgående færdiggørelsesbesked", + "show_advanced": "Rediger avancerede indstillinger (næste trin)" + }, + "data_description": { + "apply_suggestions": "Marker dette felt for at kopiere værdier foreslået af indlæringsalgoritmen til formularen. Gennemgå før du gemmer.\n\nForeslået (fra læring):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Vælg type apparat (vaskemaskine, tørretumbler, opvaskemaskine, kaffemaskine, EV). Dette tag gemmes med hver cyklus for bedre organisering.", + "power_sensor": "Sensorenheden rapporterer effekt i realtid (i watt). Du kan ændre dette når som helst uden at miste historiske data - nyttigt, hvis du udskifter et smart stik.", + "min_power": "Effektaflæsninger over denne tærskel (i watt) indikerer, at apparatet kører. Start med 2W for de fleste enheder.", + "off_delay": "Sekunder skal den udjævnede effekt forblive under stoptærsklen for at markere fuldførelse. Standard: 120s.", + "notify_actions": "Valgfri Home Assistant-handlingssekvens til at køre for notifikationer (scripts, notify, TTS osv.). Hvis det er angivet, kører handlinger før reserveunderretningstjenesten.", + "notify_people": "Personenheder, der bruges til tilstedeværelsessporing. Når det er aktiveret, forsinkes leveringen af ​​meddelelser, indtil mindst én valgt person er hjemme.", + "notify_only_when_home": "Hvis det er aktiveret, skal du udskyde underretninger, indtil mindst én valgt person er hjemme.", + "notify_fire_events": "Hvis aktiveret, udløs Home Assistant-hændelser for cyklusstart og -slut (ha_washdata_cycle_started og ha_washdata_cycle_ended) for at drive automatiseringer.", + "notify_start_services": "En eller flere underretningstjenester for at advare, når en cyklus begynder. Lad stå tomt for at springe startmeddelelser over.", + "notify_finish_services": "En eller flere underretningstjenester for at advare, når en cyklus afsluttes eller nærmer sig fuldførelse. Lad feltet stå tomt for at springe færdiggørelsesmeddelelser over.", + "notify_live_services": "En eller flere underretningstjenester for at modtage live statusopdateringer, mens cyklussen kører (kun mobilapp). Lad stå tomt for at springe liveopdateringer over.", + "notify_before_end_minutes": "Minutter før den anslåede afslutning af cyklussen for at udløse en notifikation. Indstil til 0 for at deaktivere. Standard: 0.", + "notify_title": "Brugerdefineret titel til meddelelser. Understøtter {device} pladsholder.", + "notify_icon": "Ikon til brug for meddelelser (f.eks. mdi:vaskemaskine). Lad være tom for at sende uden ikon.", + "notify_start_message": "Besked sendt, når en cyklus starter. Understøtter {device} pladsholder.", + "notify_finish_message": "Besked sendt, når en cyklus afsluttes. Understøtter {device}, {duration}, {program}, {energy_kwh}, {cost} pladsholdere.", + "notify_pre_complete_message": "Tekst af de tilbagevendende live-fremskridtsopdateringer, mens cyklussen kører. Understøtter {device}, {minutes}, {program} pladsholdere." + } + }, + "notifications": { + "title": "Meddelelser", + "description": "Konfigurer underretningskanaler, skabeloner og valgfri live-opdateringer om mobilfremskridt.", + "data": { + "notify_actions": "Underretningshandlinger", + "notify_people": "Udsæt levering, indtil disse mennesker er hjemme", + "notify_only_when_home": "Forsink meddelelser, indtil den valgte person er hjemme", + "notify_fire_events": "Udløs automatiseringshændelser", + "notify_start_services": "Cyklusstart - Notifikationsmål", + "notify_finish_services": "Cyklus afsluttet - notifikationsmål", + "notify_live_services": "Live fremskridt - Notifikationsmål", + "notify_before_end_minutes": "Underretning om forudgående afslutning (minutter før afslutning)", + "notify_live_interval_seconds": "Liveopdateringsinterval (sekunder)", + "notify_live_overrun_percent": "Tilladt overskridelse for live-opdatering (%)", + "notify_live_chronometer": "Kronometer nedtællingstimer", + "notify_title": "Underretningens titel", + "notify_icon": "Meddelelsesikon", + "notify_start_message": "Start meddelelsesformat", + "notify_finish_message": "Afslut meddelelsesformat", + "notify_pre_complete_message": "Format for forudgående færdiggørelsesbesked", + "energy_price_entity": "Energipris – enhed (valgfrit)", + "energy_price_static": "Energipris - Statisk værdi (valgfrit)", + "go_back": "Gå tilbage uden at gemme", + "notify_reminder_message": "Brevformat", + "notify_timeout_seconds": "Auto- afskedige efter (sekunder)", + "notify_channel": "Meddelelseskanal (status / live / påmindelse)", + "notify_finish_channel": "Færdig meddelelseskanal" + }, + "data_description": { + "go_back": "Sæt flueben her og klik på Send for at kassere eventuelle ændringer ovenfor og vende tilbage til hovedmenuen.", + "notify_actions": "Valgfri Home Assistant-handlingssekvens til at køre for notifikationer (scripts, notify, TTS osv.). Hvis det er angivet, kører handlinger før reserveunderretningstjenesten.", + "notify_people": "Personenheder, der bruges til tilstedeværelsessporing. Når det er aktiveret, forsinkes leveringen af ​​meddelelser, indtil mindst én valgt person er hjemme.", + "notify_only_when_home": "Hvis det er aktiveret, skal du udskyde underretninger, indtil mindst én valgt person er hjemme.", + "notify_fire_events": "Hvis aktiveret, udløs Home Assistant-hændelser for cyklusstart og -slut (ha_washdata_cycle_started og ha_washdata_cycle_ended) for at drive automatiseringer.", + "notify_start_services": "En eller flere underretningstjenester for at advare, når en cyklus begynder (f.eks. notify.mobile_app_my_phone). Lad stå tomt for at springe startmeddelelser over.", + "notify_finish_services": "En eller flere underretningstjenester for at advare, når en cyklus afsluttes eller nærmer sig fuldførelse. Lad feltet stå tomt for at springe færdiggørelsesmeddelelser over.", + "notify_live_services": "En eller flere underretningstjenester for at modtage live statusopdateringer, mens cyklussen kører (kun mobilapp). Lad stå tomt for at springe liveopdateringer over.", + "notify_before_end_minutes": "Minutter før den anslåede afslutning af cyklussen for at udløse en notifikation. Indstil til 0 for at deaktivere. Standard: 0.", + "notify_live_interval_seconds": "Hvor ofte fremskridtsopdateringer sendes, mens de kører. Lavere værdier er mere lydhøre, men bruger flere meddelelser. Der håndhæves minimum 30 sekunder - værdier under 30 s rundes automatisk op til 30 s.", + "notify_live_overrun_percent": "Sikkerhedsmargen over det estimerede opdateringstal for lange/overløbende cyklusser (for eksempel tillader 20 % 1,2x estimerede opdateringer).", + "notify_live_chronometer": "Når den er aktiveret, inkluderer hver liveopdatering en kronometernedtælling til den estimerede sluttid. Notifikationstimeren tikker automatisk ned på enheden mellem opdateringer (kun medfølgende Android-app).", + "notify_title": "Brugerdefineret titel til meddelelser. Understøtter {device} pladsholder.", + "notify_icon": "Ikon til brug for meddelelser (f.eks. mdi:vaskemaskine). Lad være tom for at sende uden ikon.", + "notify_start_message": "Besked sendt, når en cyklus starter. Understøtter {device} pladsholder.", + "notify_finish_message": "Besked sendt, når en cyklus afsluttes. Understøtter {device}, {duration}, {program}, {energy_kwh}, {cost} pladsholdere.", + "energy_price_entity": "Vælg en numerisk enhed, der har den aktuelle elpris (f.eks. sensor.electricity_price, input_number.energy_rate). Når den er indstillet, aktiveres pladsholderen {cost}. Har forrang over den statiske værdi, hvis begge er konfigureret.", + "energy_price_static": "Fast elpris pr. kWh. Når den er indstillet, aktiveres pladsholderen {cost}. Ignoreres, hvis en enhed er konfigureret ovenfor. Tilføj dit valutasymbol i beskedskabelonen, f.eks. {cost} €.", + "notify_reminder_message": "Tekst af one-time påmindelse sendt det indstillede antal minutter før den anslåede afslutning. Diskret fra de tilbagevendende live opdateringer ovenfor. Understøtter {device}, {minutes}, {program} pladsholdere.", + "notify_timeout_seconds": "Automatisk afvise meddelelser efter dette mange sekunder (fremsendes som følgesvend app \"timeout\"). Sætter til 0 for at beholde dem, indtil de bliver afskediget. Standard: 0.", + "notify_channel": "Android følgesvend app meddelelse kanal for status, live fremskridt, og påmindelse meddelelser. Lyden og betydningen af en kanal er konfigureret i følgesvend app første gang kanalnavnet bruges - WashData kun sætter kanalnavnet. Efterlad tom for appen standard.", + "notify_finish_channel": "Separat Android kanal til den færdige alarm (bruges også af påmindelsen og den vedvarende påmindelse om ventende vasketøj), så det kan have sin egen lyd. Lad det være tomt at genbruge kanalen ovenfor.", + "notify_pre_complete_message": "Tekst af de tilbagevendende live-fremskridtsopdateringer, mens cyklussen kører. Understøtter {device}, {minutes}, {program} pladsholdere." + } + }, + "advanced_settings": { + "title": "Avancerede indstillinger", + "description": "Indstil registreringstærskler, indlæring og avanceret adfærd. Standarder fungerer godt for de fleste opsætninger.", + "sections": { + "suggestions_section": { + "name": "Foreslåede indstillinger", + "description": "{suggestions_count} lærte forslag tilgængelige. Sæt flueben ved Anvend foreslåede værdier for at gennemgå foreslåede ændringer, før du gemmer.", + "data": { + "apply_suggestions": "Anvend foreslåede værdier" + }, + "data_description": { + "apply_suggestions": "Marker dette felt for at åbne et gennemgangstrin for foreslåede værdier fra indlæringsalgoritmen. Intet gemmes automatisk.\n\nForeslået (fra læring):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detektion og effekttærskler", + "description": "Hvornår apparatet betragtes som TÆNDT eller SLUKKET, energigrænser og timing for tilstandsskift.", + "data": { + "start_duration_threshold": "Start afvisningsvarighed (s)", + "start_energy_threshold": "Start-energigrænse (Wh)", + "completion_min_seconds": "Gennemførelse Minimum Runtime (sekunder)", + "start_threshold_w": "Startgrænse (W)", + "stop_threshold_w": "Stoptærskel (W)", + "end_energy_threshold": "Slut-energigrænse (Wh)", + "running_dead_zone": "Kørende dødzone (sekunder)", + "end_repeat_count": "Slut gentagelsestælling", + "min_off_gap": "Minimumsgab mellem cyklusser (s)", + "sampling_interval": "Sampling interval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorer korte strømspidser, der er kortere end denne varighed (sekunder). Filtrerer støvlespidser (f.eks. 60 W i 2 s), før den rigtige cyklus starter. Standard: 5s.", + "start_energy_threshold": "Cyklus skal akkumulere mindst så meget energi (Wh) under START-fasen for at blive bekræftet. Standard: 0,005 Wh.", + "completion_min_seconds": "Cykler, der er kortere end dette, vil blive markeret som 'afbrudt', selvom de afsluttes naturligt. Standard: 600s.", + "start_threshold_w": "Effekten skal stige OVER denne tærskel for at STARTE en cyklus. Indstil højere end din standbyeffekt. Standard: min_power + 1 W.", + "stop_threshold_w": "Strømmen skal falde UNDER denne tærskel for at AFSLUTTE en cyklus. Indstil lige over nul for at undgå støj, der udløser falske ender. Standard: 0,6 * min_power.", + "end_energy_threshold": "Cyklus slutter kun, hvis energien i det sidste Off-Delay-vindue er under denne værdi (Wh). Forhindrer falske ender, hvis effekten svinger tæt på nul. Standard: 0,05 Wh.", + "running_dead_zone": "Efter cyklus starter, ignorer strømfald i disse mange sekunder for at forhindre falsk slutdetektion under den indledende spin-up fase. Indstil til 0 for at deaktivere. Standard: 0s.", + "end_repeat_count": "Antal gange, slutbetingelsen (effekt under tærsklen for off_delay) skal opfyldes fortløbende, før cyklussen afsluttes. Højere værdier forhindrer falske afslutninger under pauser. Standard: 1.", + "min_off_gap": "Hvis en cyklus starter inden for så mange sekunder efter, at den forrige sluttede, vil de blive fusioneret til en enkelt cyklus.", + "sampling_interval": "Minimum prøvetagningsinterval i sekunder. Opdateringer hurtigere end denne hastighed vil blive ignoreret. Standard: 30s (2s for vaskemaskiner, vaskemaskine-tørretumblere og opvaskemaskiner)." + } + }, + "matching_section": { + "name": "Profilmatchning og indlæring", + "description": "Hvor aggressivt WashData matcher kørende cyklusser mod lærte profiler, og hvornår der skal anmodes om feedback.", + "data": { + "profile_match_min_duration_ratio": "Min. varighedsforhold for profilmatch (0,1-1,0)", + "profile_match_interval": "Profilmatchinterval (sekunder)", + "profile_match_threshold": "Profilmatchtærskel", + "profile_unmatch_threshold": "Profil afmatchtærskel", + "auto_label_confidence": "Auto-label-sikkerhed (0-1)", + "learning_confidence": "Feedbackanmodning om tillid (0-1)", + "suppress_feedback_notifications": "Undertrykk feedbackmeddelelser", + "duration_tolerance": "Varighedstolerance (0-0,5)", + "smoothing_window": "Udglatningsvindue (prøver)", + "profile_duration_tolerance": "Tolerance for profilmatchsvarighed (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum varighedsforhold for profilmatchning. Løbecyklus skal være mindst denne brøkdel af profilvarigheden for at matche. Lavere = tidligere matchning. Standard: 0,50 (50%).", + "profile_match_interval": "Hvor ofte (i sekunder) der skal forsøges profilmatching under en cyklus. Lavere værdier giver hurtigere registrering, men bruger mere CPU. Standard: 300s (5 minutter).", + "profile_match_threshold": "Minimum DTW-lighedsscore (0,0-1,0) påkrævet for at betragte en profil som et match. Højere = strengere matchning, færre falske positive. Standard: 0,4.", + "profile_unmatch_threshold": "DTW-lighedsscore (0,0-1,0), hvorunder en tidligere matchet profil afvises. Bør være lavere end matchtærsklen for at forhindre flimren. Standard: 0,35.", + "auto_label_confidence": "Mærk automatisk cyklusser ved eller over denne konfidens ved afslutning (0,0-1,0).", + "learning_confidence": "Minimum tillid til at anmode om brugerbekræftelse via en hændelse (0.0–1.0).", + "suppress_feedback_notifications": "Når den er aktiveret, vil WashData stadig spore og anmode om feedback internt, men vil ikke vise vedvarende meddelelser, der beder dig om at bekræfte cyklusser. Nyttigt, når cyklusser altid detekteres korrekt, og du synes, meddelelserne er distraherende.", + "duration_tolerance": "Tolerance for resterende tidsestimat under en kørsel (0,0-0,5 svarer til 0-50%).", + "smoothing_window": "Antal seneste effektaflæsninger brugt til glidende gennemsnitsudjævning.", + "profile_duration_tolerance": "Tolerance brugt af profilmatchning for total varighedsvarians (0,0–0,5). Standardadfærd: ±25%." + } + }, + "timing_section": { + "name": "Timing, vedligeholdelse og fejlfinding", + "description": "Watchdog, nulstilling af fremskridt, automatisk vedligeholdelse og eksponering af debug-entiteter.", + "data": { + "watchdog_interval": "Watchdog-interval (sekunder)", + "no_update_active_timeout": "Timeout uden opdatering(er)", + "progress_reset_delay": "Forsinkelse af nulstilling af status (sekunder)", + "auto_maintenance": "Aktiver automatisk vedligeholdelse", + "expose_debug_entities": "Afslør debug-enheder", + "save_debug_traces": "Gem fejlretningsspor" + }, + "data_description": { + "watchdog_interval": "Sekunder mellem vagthund-tjek, mens du løber. Standard: 5s. ADVARSEL: Sørg for, at dette er HØJERE end din sensors opdateringsinterval for at undgå falske stop (f.eks. hvis sensoren opdateres hver 60., brug 65s+).", + "no_update_active_timeout": "For udgiv-ved-ændring-sensorer: Tving kun afslutning af en AKTIV cyklus, hvis der ikke kommer opdateringer i så mange sekunder. Lavt strømforbrug bruger stadig Off Delay.", + "progress_reset_delay": "Når en cyklus er fuldført (100 %), skal du vente så mange sekunder med tomgang, før du nulstiller forløbet til 0 %. Standard: 300s.", + "auto_maintenance": "Aktiver automatisk vedligeholdelse (reparer prøver, flet fragmenter).", + "expose_debug_entities": "Vis avancerede sensorer (Confidence, Phase, Ambiguity) til debugging.", + "save_debug_traces": "Gem detaljerede rangerings- og strømsporingsdata i historikken (øger lagerforbruget)." + } + }, + "anti_wrinkle_section": { + "name": "Antikrøl-beskyttelse (tørretumblere)", + "description": "Ignorer tromlerotationer efter cyklus for at forhindre spøgelsescyklusser. Kun tørretumbler / vaskemaskine med tørrefunktion.", + "data": { + "anti_wrinkle_enabled": "Anti-rynkeskjold (kun tørretumbler)", + "anti_wrinkle_max_power": "Anti-rynke maksimal effekt (W)", + "anti_wrinkle_max_duration": "Anti-rynke maks. varighed (s)", + "anti_wrinkle_exit_power": "Anti-Rynke Exit Power (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorer korte tromlerotationer med lav effekt efter en cyklus er afsluttet for at forhindre spøgelsescyklusser (kun tørretumbler/vaskemaskine).", + "anti_wrinkle_max_power": "Udbrud ved eller under denne effekt behandles som anti-rynke rotationer i stedet for nye cyklusser. Gælder kun, når Anti-Wrinkle Shield er aktiveret.", + "anti_wrinkle_max_duration": "Maksimal sprænglængde at behandle som anti-rynke rotation. Gælder kun, når Anti-Wrinkle Shield er aktiveret.", + "anti_wrinkle_exit_power": "Hvis strømmen falder til under dette niveau, afslutte anti-rynke øjeblikkeligt (true off). Gælder kun, når Anti-Wrinkle Shield er aktiveret." + } + }, + "delay_start_section": { + "name": "Registrering af forsinket start", + "description": "Behandl vedvarende standby med lav effekt som 'Venter på start', indtil cyklussen faktisk begynder.", + "data": { + "delay_start_detect_enabled": "Aktiver registrering af forsinket start", + "delay_confirm_seconds": "Forsinket start: bekræftelsestid for standby (s)", + "delay_timeout_hours": "Forsinket start: Maks. ventetid (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Når den er aktiveret, detekteres en kort dræningsspids med lav effekt efterfulgt af vedvarende standbystrøm som en forsinket start. Tilstandssensoren viser 'Venter på at starte' under forsinkelsen. Ingen programtid eller fremskridt spores, før cyklussen faktisk begynder. Kræver at start_threshold_w indstilles over dræningsspidseffekten.", + "delay_confirm_seconds": "Effekten skal forblive mellem Stoptærskel (W) og Starttærskel (W) i så mange sekunder, før WashData går til 'Venter på start'. Filtrerer korte spidser fra menunavigation væk. Standard: 60 s.", + "delay_timeout_hours": "Sikkerhedstimeout: Hvis maskinen stadig er i 'Venter på at starte' efter disse mange timer, nulstilles WashData til Off. Standard: 8 timer." + } + }, + "external_triggers_section": { + "name": "Eksterne triggere, dør og pause", + "description": "Valgfri ekstern sensor for cyklusslut, dørsensor til registrering af pause/ren tilstand og afbryder til at afbryde strøm ved pause.", + "data": { + "external_end_trigger_enabled": "Aktiver ekstern cyklusslutudløser", + "external_end_trigger": "Ekstern cyklusslutsensor", + "external_end_trigger_inverted": "Inverter triggerlogik (trigger til FRA)", + "door_sensor_entity": "Dørsensor", + "pause_cuts_power": "Afbryd strømmen under pause", + "switch_entity": "Kontaktenhed (til strømafbrydelse ved pause)", + "notify_unload_delay_minutes": "Forsinkelse af besked om tøjvask (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktiver overvågning af en ekstern binær sensor for at udløse cyklusafslutning.", + "external_end_trigger": "Vælg en binær sensorentitet. Når denne sensor udløses, vil den aktuelle cyklus afsluttes med status 'fuldført'.", + "external_end_trigger_inverted": "Som standard udløses udløseren, når sensoren tændes. Marker dette felt for at udløses, når sensoren i stedet slukker.", + "door_sensor_entity": "Valgfri binær sensor til maskindøren (tændt = åben, slukket = lukket). Når døren åbnes under en aktiv cyklus, vil WashData bekræfte cyklussen som bevidst sat på pause. Når cyklussen er afsluttet, hvis døren stadig er lukket, skifter tilstanden til 'Rens', indtil døren åbnes. Bemærk: lukning af døren genoptager IKKE automatisk en pauseret cyklus - genoptagelse skal udløses manuelt via knappen Genoptag cyklus eller service.", + "pause_cuts_power": "Når du holder pause via Pause Cycle-knappen eller tjenesten, skal du også deaktivere den konfigurerede switch-entitet. Træder kun i kraft, hvis en switch-entitet er konfigureret til denne enhed.", + "switch_entity": "Skiftenheden, der skal skiftes ved pause eller genoptagelse. Påkrævet, når 'Afbryd strøm ved pause' er aktiveret.", + "notify_unload_delay_minutes": "Send en meddelelse via slutmeddelelseskanalen, efter at cyklussen er afsluttet, og døren ikke er blevet åbnet i så mange minutter (kræver dørsensor). Indstil til 0 for at deaktivere. Standard: 60 min." + } + }, + "pump_section": { + "name": "Pumpeovervågning", + "description": "Kun for enhedstypen pumpe. Udløser en hændelse, hvis en pumpecyklus kører længere end denne varighed.", + "data": { + "pump_stuck_duration": "Advarselsgrænse for pumpefejl (kun pumpe)" + }, + "data_description": { + "pump_stuck_duration": "Hvis en pumpecyklus stadig kører efter så mange sekunder, udløses en ha_washdata_pump_stuck hændelse én gang. Brug denne til at detektere en blokeret motor (typisk pumpeløb er under 60 s). Standard: 1800 s (30 min). Kun pumpeenhedstype." + } + }, + "device_link_section": { + "name": "Enhedslink", + "description": "Du kan vælge at forbinde denne Washington Data enhed til en eksisterende Home Assistant enhed (for eksempel smart plug eller selve apparatet). WashData-enheden vises derefter som \"Forbundet via\" enheden. Efterlad tom for at holde WashData som en standalone enhed.", + "data": { + "linked_device": "Forbundet enhed" + }, + "data_description": { + "linked_device": "Vælg den enhed, der skal forbinde WashData til. Dette tilføjer en 'Forbundet via' reference i enhedsregistret; WashData holder sin egen enhed kort og enheder. Ryd markeringen for at fjerne linket." + } + } + } + }, + "diagnostics": { + "title": "Diagnostik og vedligeholdelse", + "description": "Kør vedligeholdelseshandlinger som at flette fragmenterede cyklusser eller migrere lagrede data.\n\n**Opbevaringsbrug**\n\n- Filstørrelse: {file_size_kb} KB\n- Cykler: {cycle_count}\n- Profiler: {profile_count}\n- Fejlretningsspor: {debug_count}", + "menu_options": { + "reprocess_history": "Vedligeholdelse: Genbearbejd og optimer data", + "clear_debug_data": "Ryd fejlretningsdata (frigør plads)", + "wipe_history": "Slet ALLE data for denne enhed (irreversibel)", + "export_import": "Eksporter/importer JSON med indstillinger (kopier/indsæt)", + "menu_back": "← Tilbage" + } + }, + "clear_debug_data": { + "title": "Ryd fejlretningsdata", + "description": "Er du sikker på, at du vil slette alle gemte fejlretningsspor? Dette vil frigøre plads, men fjerne detaljerede rangeringsoplysninger fra tidligere cyklusser." + }, + "manage_cycles": { + "title": "Administrer cyklusser", + "description": "Seneste cyklusser:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-mærke gamle cyklusser", + "select_cycle_to_label": "Etiketspecifik cyklus", + "select_cycle_to_delete": "Slet cyklus", + "interactive_editor": "Interaktiv flet/opdel-editor", + "trim_cycle_select": "Trim cyklusdata", + "menu_back": "← Tilbage" + } + }, + "manage_cycles_empty": { + "title": "Administrer cyklusser", + "description": "Ingen cyklusser registreret endnu.", + "menu_options": { + "auto_label_cycles": "Auto-mærke gamle cyklusser", + "menu_back": "← Tilbage" + } + }, + "manage_profiles": { + "title": "Administrer profiler", + "description": "Profiloversigt:\n{profile_summary}", + "menu_options": { + "create_profile": "Opret ny profil", + "edit_profile": "Rediger/Omdøb profil", + "delete_profile_select": "Slet profil", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Ryd op i historie - graf og slet", + "assign_profile_phases_select": "Tildel faseintervaller", + "menu_back": "← Tilbage" + } + }, + "manage_profiles_empty": { + "title": "Administrer profiler", + "description": "Ingen profiler oprettet endnu.", + "menu_options": { + "create_profile": "Opret ny profil", + "menu_back": "← Tilbage" + } + }, + "manage_phase_catalog": { + "title": "Administrer fasekatalog", + "description": "Fasekatalog (standard for denne enhed + tilpassede faser):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Opret ny fase", + "phase_catalog_edit_select": "Rediger fase", + "phase_catalog_delete": "Slet fase", + "menu_back": "← Tilbage" + } + }, + "phase_catalog_create": { + "title": "Opret brugerdefineret fase", + "description": "Opret et tilpasset fasenavn og beskrivelse, og vælg hvilken enhedstype det gælder for.", + "data": { + "target_device_type": "Målenhedstype", + "phase_name": "Fase navn", + "phase_description": "Fasebeskrivelse" + } + }, + "phase_catalog_edit_select": { + "title": "Rediger tilpasset fase", + "description": "Vælg en tilpasset fase for at redigere.", + "data": { + "phase_name": "Brugerdefineret fase" + } + }, + "phase_catalog_edit": { + "title": "Rediger tilpasset fase", + "description": "Redigeringsfase: {phase_name}", + "data": { + "phase_name": "Fase navn", + "phase_description": "Fasebeskrivelse" + } + }, + "phase_catalog_delete": { + "title": "Slet brugerdefineret fase", + "description": "Vælg en tilpasset fase for at slette. Alle tildelte områder, der bruger denne fase, vil blive fjernet.", + "data": { + "phase_name": "Brugerdefineret fase" + } + }, + "assign_profile_phases": { + "title": "Tildel faseintervaller", + "description": "Forhåndsvisning af fase for profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuelle intervaller:**\n{current_ranges}\n\nBrug handlingerne nedenfor til at tilføje, redigere, slette eller gemme områder.", + "menu_options": { + "assign_profile_phases_add": "Tilføj faseområde", + "assign_profile_phases_edit_select": "Rediger faseområde", + "assign_profile_phases_delete": "Slet faseområde", + "phase_ranges_clear": "Ryd alle områder", + "assign_profile_phases_auto_detect": "Automatisk registrering af faser", + "phase_ranges_save": "Gem og returner", + "menu_back": "← Tilbage" + } + }, + "assign_profile_phases_add": { + "title": "Tilføj faseområde", + "description": "Tilføj et faseområde til det aktuelle udkast.", + "data": { + "phase_name": "Fase", + "start_min": "Start minut", + "end_min": "Slutminut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Rediger faseområde", + "description": "Vælg et område, der skal redigeres.", + "data": { + "range_index": "Faseområde" + } + }, + "assign_profile_phases_edit": { + "title": "Rediger faseområde", + "description": "Opdater det valgte faseområde.", + "data": { + "phase_name": "Fase", + "start_min": "Start minut", + "end_min": "Slutminut" + } + }, + "assign_profile_phases_delete": { + "title": "Slet faseområde", + "description": "Vælg et område, der skal fjernes fra kladden.", + "data": { + "range_index": "Faseområde" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detekterede faser", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(r) registreret automatisk.** Vælg en handling nedenfor.", + "data": { + "action": "Handling" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Navn Detekterede faser", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fase(r) fundet:**\n{ranges_summary}\n\nTildel et navn til hver fase." + }, + "assign_profile_phases_select": { + "title": "Tildel faseintervaller", + "description": "Vælg en profil for at konfigurere faseintervaller.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profilstatistik", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Opret ny profil", + "description": "Opret en ny profil manuelt eller fra en tidligere cyklus.\n\nEksempler på profilnavne: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Profilnavn", + "reference_cycle": "Referencecyklus (valgfrit)", + "manual_duration": "Manuel varighed (minutter)" + } + }, + "edit_profile": { + "title": "Rediger profil", + "description": "Vælg en profil, der skal omdøbes", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Rediger profiloplysninger", + "description": "Nuværende profil: {current_name}", + "data": { + "new_name": "Profilnavn", + "manual_duration": "Manuel varighed (minutter)" + } + }, + "delete_profile_select": { + "title": "Slet profil", + "description": "Vælg en profil, der skal slettes", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Bekræft Slet profil", + "description": "⚠️ Dette vil permanent slette profilen: {profile_name}", + "data": { + "unlabel_cycles": "Fjern etiket fra cyklusser ved hjælp af denne profil" + } + }, + "auto_label_cycles": { + "title": "Auto-mærke gamle cyklusser", + "description": "Fundet {total_count} cyklusser i alt. Profiler: {profiles}", + "data": { + "confidence_threshold": "Minimum tillid (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Vælg cyklus til etiket", + "description": "✓ = afsluttet, ⚠ = genoptaget, ✗ = afbrudt", + "data": { + "cycle_id": "Cyklus" + } + }, + "select_cycle_to_delete": { + "title": "Slet cyklus", + "description": "⚠️ Dette vil permanent slette den valgte cyklus", + "data": { + "cycle_id": "Cyklus at slette" + } + }, + "label_cycle": { + "title": "Etiket cyklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nyt profilnavn (hvis oprettet)" + } + }, + "post_process": { + "title": "Proceshistorik (flet/opdel)", + "description": "Ryd op i cyklushistorikken ved at flette fragmenterede kørsler og opdele forkert flettede cyklusser inden for et tidsvindue. Indtast antal timer for at se tilbage (brug 999999 for alle).", + "data": { + "time_range": "Tilbagebliksvindue (timer)", + "gap_seconds": "Flet/opdel hul (sekunder)" + }, + "data_description": { + "time_range": "Antal timer at scanne for fragmenterede cyklusser at flette. Brug 999999 til at behandle alle historiske data.", + "gap_seconds": "Tærskel for at beslutte split vs merge. Mellemrum, der er KORTERE end dette, slås sammen. Længere huller end dette opdeles. Sænk denne for at fremtvinge en opdeling på en cyklus, der blev flettet forkert." + } + }, + "cleanup_profile": { + "title": "Ryd op i historik - Vælg profil", + "description": "Vælg en profil for at visualisere. Dette vil generere en graf, der viser alle tidligere cyklusser for denne profil for at hjælpe med at identificere outliers.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Ryd op i historie - graf og slet", + "description": "![Graph]({graph_url})\n\n**Visualiserer {profile_name}**\n\nGrafen viser individuelle cyklusser som farvede linjer. Identificer outliers (linjer langt fra gruppen) og match deres farve på listen nedenfor for at slette dem.\n\n**Vælg cyklusser, der skal slettes PERMANENT:**", + "data": { + "cycles_to_delete": "Cykler til sletning (tjek matchende farver)" + } + }, + "interactive_editor": { + "title": "Interaktiv flet/opdel-editor", + "description": "Vælg en manuel handling, der skal udføres på din cyklushistorik.", + "menu_options": { + "editor_split": "Opdel en cyklus (Find huller)", + "editor_merge": "Flet cyklusser (sammenføj fragmenter)", + "editor_delete": "Slet cyklus(er)", + "menu_back": "← Tilbage" + } + }, + "editor_select": { + "title": "Vælg cyklusser", + "description": "Vælg den eller de cyklusser, der skal behandles.\n\n{info_text}", + "data": { + "selected_cycles": "Cykler" + } + }, + "editor_configure": { + "title": "Konfigurer og anvend", + "description": "{preview_md}", + "data": { + "confirm_action": "Handling", + "merged_profile": "Resulterende profil", + "new_profile_name": "Nyt profilnavn", + "confirm_commit": "Ja, jeg bekræfter denne handling", + "segment_0_profile": "Segment 1 profil", + "segment_1_profile": "Segment 2 profil", + "segment_2_profile": "Segment 3 profil", + "segment_3_profile": "Segment 4 profil", + "segment_4_profile": "Segment 5 profil", + "segment_5_profile": "Segment 6 profil", + "segment_6_profile": "Segment 7 profil", + "segment_7_profile": "Segment 8 profil", + "segment_8_profile": "Segment 9 profil", + "segment_9_profile": "Segment 10 profil" + } + }, + "editor_split_params": { + "title": "Split konfiguration", + "description": "Juster følsomheden for at opdele denne cyklus. Ethvert tomgangsmellemrum, der er længere end dette, vil forårsage en opdeling.", + "data": { + "split_mode": "Splitmetode" + } + }, + "editor_split_auto_params": { + "title": "Konfiguration af automatisk opdeling", + "description": "Juster følsomheden for opdeling af denne cyklus. Enhver tomgangspause længere end dette vil medføre en opdeling.", + "data": { + "min_gap_seconds": "Tærskel for opdelingspause (sekunder)" + } + }, + "editor_split_manual_params": { + "title": "Manuelle opdelings-tidsstempler", + "description": "{preview_md}Cyklusvindue: **{cycle_start_wallclock} → {cycle_end_wallclock}** den {cycle_date}.\n\nIndtast et eller flere opdelings-tidsstempler (`HH:MM` eller `HH:MM:SS`), ét pr. linje. Cyklussen bliver delt ved hvert tidsstempel — N tidsstempler giver N+1 segmenter.", + "data": { + "split_timestamps": "Tidsstempler for opdeling" + } + }, + "reprocess_history": { + "title": "Vedligeholdelse: Genbearbejd og optimer data", + "description": "Udfører dyb rensning af historiske data:\n1. Trimmer nul-effekt-aflæsninger (tavshed).\n2. Retter cyklus timing/varighed.\n3. Genberegner alle statistiske modeller (kuverter).\n\n**Kør dette for at løse dataproblemer eller anvende nye algoritmer.**" + }, + "wipe_history": { + "title": "Slet historie (kun test)", + "description": "⚠️ Dette vil permanent slette ALLE gemte cyklusser og profiler for denne enhed. Dette kan ikke fortrydes!" + }, + "export_import": { + "title": "Eksporter/importer JSON", + "description": "Kopiér/indsæt cyklusser, profiler OG indstillinger for denne enhed. Eksporter nedenfor, eller indsæt JSON for at importere.", + "data": { + "mode": "Handling", + "json_payload": "Fuldfør konfiguration JSON" + }, + "data_description": { + "mode": "Vælg Eksporter for at kopiere aktuelle data og indstillinger, eller Importer for at indsætte eksporterede data fra en anden WashData-enhed.", + "json_payload": "Til eksport: kopier denne JSON (inkluderer cyklusser, profiler og alle finjusterede indstillinger). Til import: indsæt JSON eksporteret fra WashData." + } + }, + "record_cycle": { + "title": "Optag cyklus", + "description": "Optag manuelt en cyklus for at skabe en ren profil uden forstyrrelser.\n\nStatus: **{status}**\nVarighed: {duration}s\nEksempler: {samples}", + "menu_options": { + "record_refresh": "Opdater status", + "record_stop": "Stop optagelse (Gem og bearbejd)", + "record_start": "Start ny optagelse", + "record_process": "Behandle sidste optagelse (Trim & Gem)", + "record_discard": "Kassér sidste optagelse", + "menu_back": "← Tilbage" + } + }, + "record_process": { + "title": "Procesoptagelse", + "description": "![Graph]({graph_url})\n\n**Graf viser rå optagelse.** Blå = Behold, Rød = Foreslået trim.\n*Graf er statisk og opdateres ikke med formularændringer.*\n\nOptagelsen stoppede. Trim justeres til den detekterede prøvetagningshastighed (~{sampling_rate}s).\n\nRå varighed: {duration}s\nEksempler: {samples}", + "data": { + "head_trim": "Trimstart (sekunder)", + "tail_trim": "Trim slut (sekunder)", + "save_mode": "Gem destination", + "profile_name": "Profilnavn" + } + }, + "learning_feedbacks": { + "title": "Læringsfeedback", + "description": "Afventende feedback: {count}\n\n{pending_table}\n\nVælg en anmodning om cyklusgennemgang, der skal behandles.", + "menu_options": { + "learning_feedbacks_pick": "Gennemgå afventende feedback", + "learning_feedbacks_dismiss_all": "Afvis al afventende feedback", + "menu_back": "← Tilbage" + } + }, + "learning_feedbacks_pick": { + "title": "Vælg feedback til gennemgang", + "description": "Vælg en anmodning om cyklusgennemgang, der skal åbnes.", + "data": { + "selected_feedback": "Vælg cyklus til gennemgang" + } + }, + "learning_feedbacks_empty": { + "title": "Læringsfeedback", + "description": "Der blev ikke fundet nogen afventende feedbackanmodninger.", + "menu_options": { + "menu_back": "← Tilbage" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Afvis al ventende feedback", + "description": "Du er ved at afvise **{count}** ventende feedbackanmodninger. Afviste cyklusser forbliver i din historik, men bidrager ikke med nyt læringssignal. Dette kan ikke fortrydes.\n\n✅ Marker afkrydsningsfeltet nedenfor, og klik på **Send** for at afvise alle.\n❌ Lad det være umarkeret, og klik på **Send** for at annullere og gå tilbage.", + "data": { + "confirm_dismiss_all": "Bekræft: afvis alle afventende feedbackanmodninger" + } + }, + "resolve_feedback": { + "title": "Løs feedback", + "description": "{comparison_img}{candidates_table}\n**Opdaget profil**: {detected_profile} ({confidence_pct}%)\n**Estimeret varighed**: {est_duration_min} min\n**Faktisk varighed**: {act_duration_min} min\n\n__Vælg en handling nedenfor:__", + "data": { + "action": "Hvad vil du gerne lave?", + "corrected_profile": "Korrekt program (hvis der rettes)", + "corrected_duration": "Korrekt varighed i sekunder (hvis der rettes)" + } + }, + "trim_cycle_select": { + "title": "Trim cyklus - Vælg cyklus", + "description": "Vælg en cyklus at trimme. ✓ = afsluttet, ⚠ = genoptaget, ✗ = afbrudt", + "data": { + "cycle_id": "Cyklus" + } + }, + "trim_cycle": { + "title": "Trim cyklus", + "description": "Aktuelt vindue: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Handling" + } + }, + "trim_cycle_start": { + "title": "Indstil trimstart", + "description": "Cyklusvindue: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuværende start: **{current_wallclock}** (+{current_offset_min} min fra cyklusstart)\n\nVælg et nyt starttidspunkt ved hjælp af urvælgeren nedenfor.", + "data": { + "trim_start_time": "Nyt starttidspunkt", + "trim_start_min": "Ny start (minutter fra cyklusstart)" + } + }, + "trim_cycle_end": { + "title": "Indstil trim slutposition", + "description": "Cyklusvindue: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuværende slut: **{current_wallclock}** (+{current_offset_min} min. fra cyklusstart)\n\nVælg et nyt sluttidspunkt ved hjælp af urvælgeren nedenfor.", + "data": { + "trim_end_time": "Nyt sluttidspunkt", + "trim_end_min": "Ny slutning (minutter fra cyklusstart)" + } + } + }, + "error": { + "import_failed": "Import mislykkedes. Indsæt venligst en gyldig WashData-eksport-JSON.", + "select_exactly_one": "Vælg præcis én cyklus til denne handling.", + "select_at_least_one": "Vælg mindst én cyklus til denne handling.", + "select_at_least_two": "Vælg mindst to cyklusser til denne handling.", + "profile_exists": "Der findes allerede en profil med dette navn", + "rename_failed": "Profilen kunne ikke omdøbes. Profilen eksisterer muligvis ikke, eller der er allerede taget et nyt navn.", + "assignment_failed": "Kunne ikke tildele profil til cyklus", + "duplicate_phase": "En fase med dette navn eksisterer allerede.", + "phase_not_found": "Den valgte fase blev ikke fundet.", + "invalid_phase_name": "Fasenavnet må ikke være tomt.", + "phase_name_too_long": "Fasenavnet er for langt.", + "invalid_phase_range": "Faseinterval er ugyldigt. Slut skal være større end start.", + "invalid_phase_timestamp": "Tidsstemplet er ugyldigt. Brug formatet ÅÅÅÅ-MM-DD TT:MM eller TT:MM.", + "overlapping_phase_ranges": "Faseområder overlapper hinanden. Juster intervallerne, og prøv igen.", + "incomplete_phase_row": "Hver faserække skal indeholde en fase plus komplette start-/slutværdier for den valgte tilstand.", + "timestamp_mode_cycle_required": "Vælg venligst en kildecyklus, når du bruger tidsstempeltilstand.", + "no_phase_ranges": "Ingen faseintervaller er tilgængelige for den handling endnu.", + "feedback_notification_title": "WashData: Bekræft cyklus ({device})", + "feedback_notification_message": "**Enhed**: {device}\n**Program**: {program} ({confidence} % konfidens)\n**Tid**: {time}\n\nWashData har brug for din hjælp til at verificere denne detekterede cyklus.\n\nGå til **Indstillinger > Enheder og tjenester > WashData > Konfigurer > Læringsfeedback** for at bekræfte eller rette dette resultat.", + "suggestions_ready_notification_title": "WashData: Foreslåede indstillinger klar ({device})", + "suggestions_ready_notification_message": "Sensoren **Foreslåede indstillinger** rapporterer nu **{count}** handlingsrettede anbefalinger.\n\nSådan gennemgår og anvender du dem: **Indstillinger > Enheder og tjenester > WashData > Konfigurer > Avancerede indstillinger > Anvend foreslåede værdier**.\n\nForslag er valgfrie og vises til gennemgang, før du gemmer.", + "trim_range_invalid": "Start skal være før slut. Juster venligst dine trimpunkter.", + "trim_failed": "Trimning mislykkedes. Cyklussen eksisterer muligvis ikke længere, eller vinduet er tomt.", + "invalid_split_timestamp": "Tidsstemplet er ugyldigt eller uden for cyklusvinduet. Brug HH:MM eller HH:MM:SS inden for cyklusvinduet.", + "no_split_segments_found": "Der kunne ikke dannes gyldige segmenter ud fra disse tidsstempler. Sørg for, at hvert tidsstempel ligger inden for cyklusvinduet, og at segmenterne er mindst 1 minut lange.", + "auto_tune_suggestion": "{device_type} {device_title} registrerede spøgelsescyklusser. Foreslået min_effektændring: {current_min}W -> {new_min}W (ikke anvendt automatisk).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} registrerede spøgelsescyklusser. Foreslået min_effektændring: {current_min}W -> {new_min}W (ikke anvendt automatisk)." + }, + "abort": { + "no_cycles_found": "Ingen cyklusser fundet", + "no_split_segments_found": "Der blev ikke fundet nogen opdelbare segmenter i den valgte cyklus.", + "cycle_not_found": "Den valgte cyklus kunne ikke indlæses.", + "no_power_data": "Ingen strømsporingsdata tilgængelige for den valgte cyklus.", + "no_profiles_found": "Ingen profiler fundet. Opret en profil først.", + "no_profiles_for_matching": "Ingen profiler tilgængelige for matchning. Opret profiler først.", + "no_unlabeled_cycles": "Alle cyklusser er allerede mærket", + "no_suggestions": "Ingen foreslåede værdier tilgængelige endnu. Kør et par cyklusser og prøv igen.", + "no_predictions": "Ingen forudsigelser", + "no_custom_phases": "Ingen tilpassede faser fundet.", + "reprocess_success": "Genbehandlet {count} cyklusser.", + "debug_data_cleared": "Fejlretningsdata fra {count} cyklusser blev ryddet." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cyklus start", + "cycle_finish": "Cyklus afslutning", + "cycle_live": "Live fremskridt" + } + }, + "common_text": { + "options": { + "unlabeled": "(Umærket)", + "create_new_profile": "Opret ny profil", + "remove_label": "Fjern etiket", + "no_reference_cycle": "(Ingen referencecyklus)", + "all_device_types": "Alle enhedstyper", + "current_suffix": "(aktuel)", + "editor_select_info": "Vælg 1 cyklus for at opdele, eller 2+ cyklusser for at flette.", + "editor_select_info_split": "Vælg 1 cyklus, der skal opdeles.", + "editor_select_info_merge": "Vælg 2 eller flere cyklusser, der skal flettes.", + "editor_select_info_delete": "Vælg 1 eller flere cyklusser, der skal slettes.", + "no_cycles_recorded": "Ingen cyklusser registreret endnu.", + "profile_summary_line": "- **{name}**: {count} cyklusser, {avg}m gns", + "no_profiles_created": "Ingen profiler oprettet endnu.", + "phase_preview": "Forhåndsvisning af fase", + "phase_preview_no_curve": "Gennemsnitlig profilkurve er ikke tilgængelig endnu. Kør/mærk flere cyklusser for denne profil.", + "average_power_curve": "Gennemsnitlig effektkurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cyklusser, ~{duration}m gns.)", + "profile_option_short_fmt": "{name} ({count} cyklusser, ~{duration}m)", + "deleted_cycles_from_profile": "Slettede {count} cyklusser fra {profile}.", + "not_enough_data_graph": "Ikke nok data til at generere graf.", + "no_profiles_found": "Ingen profiler fundet.", + "cycle_info_fmt": "Cyklus: {start}, {duration}m, aktuel: {label}", + "top_candidates_header": "### Topkandidater", + "tbl_profile": "Profil", + "tbl_confidence": "Tillid", + "tbl_correlation": "Korrelation", + "tbl_duration_match": "Varighed Match", + "detected_profile": "Registreret profil", + "estimated_duration": "Estimeret varighed", + "actual_duration": "Faktisk varighed", + "choose_action": "__Vælg en handling nedenfor:__", + "tbl_count": "Tælle", + "tbl_avg": "Gns", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energi (Gns.)", + "tbl_energy_total": "Energi (i alt)", + "tbl_consistency": "Konsistens", + "tbl_last_run": "Sidste løb", + "graph_legend_title": "Grafforklaring", + "graph_legend_body": "Det blå bånd repræsenterer det observerede minimum og maksimum strømforbrug. Linjen viser den gennemsnitlige effektkurve.", + "recording_preview": "Forhåndsvisning af optagelse", + "trim_start": "Trim start", + "trim_end": "Trim ende", + "split_preview_title": "Opdel forhåndsvisning", + "split_preview_found_fmt": "Fandt {count} segmenter.", + "split_preview_confirm_fmt": "Klik på Bekræft for at opdele denne cyklus i {count} separate cyklusser.", + "merge_preview_title": "Forhåndsvisning af sammenfletning", + "merge_preview_joining_fmt": "Deltager i {count} cyklusser. Huller vil blive udfyldt med 0W aflæsninger.", + "editor_delete_preview_title": "Sletteforhåndsvisning", + "editor_delete_preview_intro": "De valgte cyklusser slettes permanent:", + "editor_delete_preview_confirm": "Klik på Bekræft for at slette disse cyklusposter permanent.", + "no_power_preview": "*Ingen strømdata tilgængelige til forhåndsvisning.*", + "profile_comparison": "Profil sammenligning", + "actual_cycle_label": "Denne cyklus (faktisk)", + "storage_usage_header": "Opbevaringsbrug", + "storage_file_size": "Filstørrelse", + "storage_cycles": "Cykler", + "storage_profiles": "Profiler", + "storage_debug_traces": "Fejlsøgningsspor", + "overview_suffix": "Oversigt", + "phase_preview_for_profile": "Forhåndsvisning af fase til profil", + "current_ranges_header": "Aktuelle intervaller", + "assign_phases_help": "Brug handlingerne nedenfor til at tilføje, redigere, slette eller gemme områder.", + "profile_summary_header": "Profiloversigt", + "recent_cycles_header": "Seneste cyklusser", + "trim_cycle_preview_title": "Forhåndsvisning af cyklustrim", + "trim_cycle_preview_no_data": "Ingen strømdata tilgængelige for denne cyklus.", + "trim_cycle_preview_kept_suffix": "holdt", + "table_program": "Program", + "table_when": "Hvornår", + "table_length": "Længde", + "table_match": "Match", + "table_profile": "Profil", + "table_cycles": "Cykler", + "table_avg_length": "Gns. længde", + "table_last_run": "Sidste løb", + "table_avg_energy": "Gns. energi", + "table_detected_program": "Registreret program", + "table_confidence": "Tillid", + "table_reported": "Rapporteret", + "phase_builtin_suffix": "(Indbygget)", + "phase_none_available": "Ingen faser tilgængelige.", + "settings_deprecation_warning": "⚠️ **Udgået enhedstype:** {device_type} er planlagt til fjernelse i en fremtidig udgivelse. WashDatas matchende pipeline giver ikke pålidelige resultater for denne apparatklasse. Din integration bliver ved med at fungere gennem afskrivningsperioden; For at slå denne advarsel fra, skal du skifte **Enhedstype** nedenfor til en af ​​de understøttede typer (vaskemaskine, tørretumbler, vaskemaskine-tørretumbler, opvaskemaskine, luftfrituregryde, brødmaskine eller pumpe), eller til **Andet (avanceret)**, hvis dit apparat ikke matcher nogen af ​​de understøttede typer. **Andet (avanceret)** sender bevidst generiske standardindstillinger, der ikke er indstillet til nogen specifik enhed, så du skal selv konfigurere tærskler, timeouts og matchende parametre; alle dine eksisterende indstillinger bevares, når du skifter.", + "phase_other_device_types": "Andre enhedstyper:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Opdel en cyklus (Find huller)", + "merge": "Flet cyklusser (sammenføj fragmenter)", + "delete": "Slet cyklus(er)" + } + }, + "split_mode": { + "options": { + "auto": "Registrer inaktive mellemrum automatisk", + "manual": "Manuelle tidsstempler" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Vedligeholdelse: Genbearbejd og optimer data", + "clear_debug_data": "Ryd fejlretningsdata (frigør plads)", + "wipe_history": "Slet ALLE data for denne enhed (irreversibel)", + "export_import": "Eksporter/importer JSON med indstillinger (kopier/indsæt)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksporter alle data", + "import": "Importer/flet data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Auto-mærke gamle cyklusser", + "select_cycle_to_label": "Etiketspecifik cyklus", + "select_cycle_to_delete": "Slet cyklus", + "interactive_editor": "Interaktiv flet/opdel-editor", + "trim_cycle": "Trim cyklusdata" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Opret ny profil", + "edit_profile": "Rediger/Omdøb profil", + "delete_profile": "Slet profil", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Ryd op i historie - graf og slet", + "assign_phases": "Tildel faseintervaller" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Opret ny fase", + "edit_custom_phase": "Rediger fase", + "delete_custom_phase": "Slet fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Tilføj faseområde", + "edit_range": "Rediger faseområde", + "delete_range": "Slet faseområde", + "clear_ranges": "Ryd alle områder", + "auto_detect_ranges": "Automatisk registrering af faser", + "save_ranges": "Gem og returner" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Opdater status", + "stop_recording": "Stop optagelse (Gem og bearbejd)", + "start_recording": "Start ny optagelse", + "process_recording": "Behandle sidste optagelse (Trim & Gem)", + "discard_recording": "Kassér sidste optagelse" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Opret ny profil", + "existing_profile": "Føj til eksisterende profil", + "discard": "Kassér optagelse" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bekræft - Korrekt registrering", + "correct": "Korrekt - Vælg Program/Varighed", + "ignore": "Ignorer - Falsk positiv/støjende cyklus", + "delete": "Slet - Fjern fra historik" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Navngiv og anvend faser", + "cancel": "Gå tilbage uden ændringer" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Indstil startpunkt", + "set_end": "Indstil slutpunkt", + "reset": "Nulstil til fuld varighed", + "apply": "Påfør Trim", + "cancel": "Ophæve" + } + }, + "device_type": { + "options": { + "washing_machine": "Vaskemaskine", + "dryer": "Tørretumbler", + "washer_dryer": "Vaske-tørretumbler kombi", + "dishwasher": "Opvaskemaskine", + "coffee_machine": "Kaffemaskine (forældet)", + "ev": "Elektrisk køretøj (udfaset)", + "air_fryer": "Air Fryer", + "heat_pump": "Varmepumpe (udgået)", + "bread_maker": "Brødmaskine", + "pump": "Pumpe / Sump Pumpe", + "oven": "Ovn (forældet)", + "other": "Andet (avanceret)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiket cyklus", + "description": "Tildel en eksisterende profil til en tidligere cyklus, eller fjern etiketten.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinen enhed til at mærke." + }, + "cycle_id": { + "name": "Cyklus ID", + "description": "ID'et for den cyklus, der skal mærkes." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Navnet på en eksisterende profil (opret profiler i menuen Administrer profiler). Lad være tomt for at fjerne etiketten." + } + } + }, + "create_profile": { + "name": "Opret profil", + "description": "Opret en ny profil (standalone eller baseret på en referencecyklus).", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinenheden." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Navn på den nye profil (f.eks. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Referencecyklus-id", + "description": "Valgfrit cyklus-id at basere denne profil på." + } + } + }, + "delete_profile": { + "name": "Slet profil", + "description": "Slet en profil og fjern eventuelt etiketter for cyklusser ved hjælp af den.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinenheden." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Profilen, der skal slettes." + }, + "unlabel_cycles": { + "name": "Fjern etiketter på cyklusser", + "description": "Fjern profiletiketten fra cyklusser ved hjælp af denne profil." + } + } + }, + "auto_label_cycles": { + "name": "Auto-mærke gamle cyklusser", + "description": "Mærk umærkede cyklusser med tilbagevirkende kraft ved hjælp af profilmatchning.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinenheden." + }, + "confidence_threshold": { + "name": "Tillidsgrænse", + "description": "Mindste matchkonfidens (0,50-0,95) for at anvende etiketter." + } + } + }, + "export_config": { + "name": "Eksporter Konfig", + "description": "Eksporter denne enheds profiler, cyklusser og indstillinger til en JSON-fil (pr. enhed).", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinen enhed til at eksportere." + }, + "path": { + "name": "Sti", + "description": "Valgfri absolut filsti at skrive (standard til /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importer konfiguration", + "description": "Importer profiler, cyklusser og indstillinger for denne enhed fra en JSON-eksportfil (indstillinger kan blive overskrevet).", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinen, der skal importeres til." + }, + "path": { + "name": "Sti", + "description": "Absolut sti til eksport-JSON-filen, der skal importeres." + } + } + }, + "submit_cycle_feedback": { + "name": "Indsend cyklusfeedback", + "description": "Bekræft eller ret et auto-detekteret program efter en afsluttet cyklus. Angiv enten \"entry_id\" (avanceret) eller \"device_id\" (anbefales).", + "fields": { + "device_id": { + "name": "Enhed", + "description": "WashData-enheden (anbefales i stedet for entry_id)." + }, + "entry_id": { + "name": "Indgangs-id", + "description": "Konfigurationsindgangs-id'et for enheden (alternativ til device_id)." + }, + "cycle_id": { + "name": "Cyklus ID", + "description": "Cyklus-id'et vist i feedbackmeddelelsen/logfilerne." + }, + "user_confirmed": { + "name": "Bekræft fundet program", + "description": "Indstil sand, hvis det detekterede program er korrekt." + }, + "corrected_profile": { + "name": "Korrigeret profil", + "description": "Hvis det ikke bekræftes, skal du angive det korrekte profil-/programnavn." + }, + "corrected_duration": { + "name": "Korrigeret varighed (sekunder)", + "description": "Valgfri korrigeret varighed i sekunder." + }, + "notes": { + "name": "Noter", + "description": "Valgfrie bemærkninger om denne cyklus." + } + } + }, + "record_start": { + "name": "Optag cyklus start", + "description": "Start manuelt at optage en ren cyklus (omgår al matchende logik).", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinen enhed til at optage." + } + } + }, + "record_stop": { + "name": "Optag cyklus stop", + "description": "Stop manuel optagelse.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "Vaskemaskinen enheden til at stoppe optagelsen." + } + } + }, + "trim_cycle": { + "name": "Trim cyklus", + "description": "Trim effektdataene for en tidligere cyklus til et bestemt tidsvindue.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "WashData-enheden." + }, + "cycle_id": { + "name": "Cyklus ID", + "description": "ID'et for den cyklus, der skal trimmes." + }, + "trim_start_s": { + "name": "Trimstart (sekunder)", + "description": "Gem data fra denne offset i sekunder. Standard 0." + }, + "trim_end_s": { + "name": "Trim slut (sekunder)", + "description": "Hold data op til denne offset i sekunder. Standard = fuld varighed." + } + } + }, + "pause_cycle": { + "name": "Pause cyklus", + "description": "Sæt den aktive cyklus på pause for en WashData-enhed.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "WashData-enheden til pause." + } + } + }, + "resume_cycle": { + "name": "Genoptag cyklus", + "description": "Genoptag en afbrudt cyklus for en WashData-enhed.", + "fields": { + "device_id": { + "name": "Enhed", + "description": "WashData-enheden skal genoptages." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Løb" + }, + "match_ambiguity": { + "name": "Matchtvetydighed" + } + }, + "sensor": { + "washer_state": { + "name": "Tilstand", + "state": { + "off": "Slukket", + "idle": "Ledig", + "starting": "Starter", + "running": "Løb", + "paused": "Pause", + "user_paused": "Sat på pause af brugeren", + "ending": "Slutning", + "finished": "Færdig", + "anti_wrinkle": "Anti-Rynke", + "delay_wait": "Venter på at starte", + "interrupted": "Afbrudt", + "force_stopped": "Tving stoppet", + "rinse": "Skylle", + "unknown": "Ukendt", + "clean": "Ren" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Tid tilbage" + }, + "total_duration": { + "name": "Samlet varighed" + }, + "cycle_progress": { + "name": "Fremskridt" + }, + "current_power": { + "name": "Nuværende strøm" + }, + "elapsed_time": { + "name": "Forløbet tid" + }, + "debug_info": { + "name": "Fejlretningsinfo" + }, + "match_confidence": { + "name": "Matchsikkerhed" + }, + "top_candidates": { + "name": "Topkandidater", + "state": { + "none": "Ingen" + } + }, + "current_phase": { + "name": "Nuværende fase" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Antal profil {profile_name}", + "unit_of_measurement": "cyklusser" + }, + "suggestions": { + "name": "Foreslåede indstillinger" + }, + "pump_runs_today": { + "name": "Pumpen kører (sidste 24 timer)" + }, + "cycle_count": { + "name": "Cyklustælling" + } + }, + "select": { + "program_select": { + "name": "Cyklus program", + "state": { + "auto_detect": "Automatisk registrering" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Tving afslutningscyklus" + }, + "pause_cycle": { + "name": "Pause cyklus" + }, + "resume_cycle": { + "name": "Genoptag cyklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Et gyldigt device_id er påkrævet." + }, + "cycle_id_required": { + "message": "Et gyldigt cycle_id er påkrævet." + }, + "profile_name_required": { + "message": "Et gyldigt profilnavn er påkrævet." + }, + "device_not_found": { + "message": "Enheden blev ikke fundet." + }, + "no_config_entry": { + "message": "Ingen konfigurationsindgang fundet for enheden." + }, + "integration_not_loaded": { + "message": "Integration ikke indlæst for denne enhed." + }, + "cycle_not_found_or_no_power": { + "message": "Cyklus ikke fundet eller har ingen strømdata." + }, + "trim_failed_empty_window": { + "message": "Trim mislykkedes - cyklus blev ikke fundet, ingen strømdata, eller det resulterende vindue er tomt." + }, + "trim_invalid_range": { + "message": "trim_end_s skal være større end trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/de.json b/custom_components/ha_washdata/translations/de.json new file mode 100644 index 0000000..96bb9fe --- /dev/null +++ b/custom_components/ha_washdata/translations/de.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-Setup", + "description": "Konfigurieren Sie Ihre Waschmaschine oder ein anderes Gerät.\n\nLeistungssensor ist erforderlich.\n\n**Als nächstes werden Sie gefragt, ob Sie Ihr erstes Profil erstellen möchten.**", + "data": { + "name": "Gerätename", + "device_type": "Gerätetyp", + "power_sensor": "Leistungssensor", + "min_power": "Mindestleistungsschwelle (W)" + }, + "data_description": { + "name": "Ein benutzerfreundlicher Name für dieses Gerät (z. B. „Waschmaschine“, „Geschirrspüler“).", + "device_type": "Um welche Art von Gerät handelt es sich? Dies hilft bei der individuellen Erkennung und Zuordnung.", + "power_sensor": "Die Sensoreinheit, die den Stromverbrauch (in Watt) Ihres Smart Plugs in Echtzeit meldet.", + "min_power": "Leistungswerte über diesem Schwellwert (in Watt) zeigen an, dass das Gerät läuft. Für die meisten Geräte ist 2W ein guter Anfang." + } + }, + "first_profile": { + "title": "Erstes Profil erstellen", + "description": "Ein **Zyklus** ist ein vollständiger Durchlauf Ihres Geräts (z. B. eine Ladung Wäsche). Ein **Profil** gruppiert diese Programme nach Typ (z. B. „Baumwolle“, „Schnellwäsche“). Wenn Sie jetzt ein Profil erstellen, kann die Integrationsdauer sofort geschätzt werden.\n\nSie können dieses Profil in der Gerätesteuerung manuell auswählen, wenn es nicht automatisch erkannt wird.\n\n**Lassen Sie „Profilname“ leer, um diesen Schritt zu überspringen.**", + "data": { + "profile_name": "Profilname (optional)", + "manual_duration": "Geschätzte Dauer (Minuten)" + } + } + }, + "error": { + "cannot_connect": "Verbindung konnte nicht hergestellt werden", + "invalid_auth": "Ungültige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "abort": { + "already_configured": "Gerät ist bereits konfiguriert" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Lern-Feedbacks überprüfen", + "settings": "Einstellungen", + "notifications": "Benachrichtigungen", + "manage_cycles": "Zyklen verwalten", + "manage_profiles": "Profile verwalten", + "manage_phase_catalog": "Phasenkatalog verwalten", + "record_cycle": "Aufnahmezyklus (manuell)", + "diagnostics": "Diagnose und Wartung" + } + }, + "apply_suggestions": { + "title": "Vorgeschlagene Werte kopieren", + "description": "Dadurch werden die aktuellen Vorschlagswerte in Ihre gespeicherten Einstellungen kopiert. Dies ist eine einmalige Aktion und ermöglicht keine automatischen Überschreibungen.\n\nVorgeschlagene Werte zum Kopieren:\n{suggested}", + "data": { + "confirm": "Kopiervorgang bestätigen" + } + }, + "apply_suggestions_confirm": { + "title": "Vorgeschlagenen Änderungen überprüfen", + "description": "WashData hat **{pending_count}** vorgeschlagene(n) Wert(e) gefunden, die angewendet werden sollen.\n\nÄnderungen:\n{changes}\n\n✅ Aktivieren Sie das Kontrollkästchen unten und klicken Sie auf **Senden**, um diese Änderungen sofort zu übernehmen und zu speichern.\n❌ Lassen Sie es deaktiviert und klicken Sie auf **Senden**, um den Vorgang abzubrechen und zurückzukehren.", + "data": { + "confirm_apply_suggestions": "Übernehmen und speichern Sie diese Änderungen" + } + }, + "settings": { + "title": "Einstellungen", + "description": "{deprecation_warning}Erkennungsschwellwerte optimieren, Lernen und erweitertes Verhalten. Die Standardeinstellungen funktionieren für die meisten Setups gut.\n\nDerzeit verfügbare empfohlene Einstellungen: {suggestions_count}.\nWenn dies über 0 liegt, öffnen Sie die Erweiterten Einstellungen und verwenden Sie 'Vorgeschlagene Werte anwenden', um die Empfehlungen zu überprüfen.", + "data": { + "apply_suggestions": "Vorgeschlagene Werte anwenden", + "device_type": "Gerätetyp", + "power_sensor": "Leistungssensoreinheit", + "min_power": "Mindestleistung (W)", + "off_delay": "Zyklusendeverzögerung (s)", + "notify_actions": "Benachrichtigungsaktionen", + "notify_people": "Benachrichtigungen warten auf folgende Personen", + "notify_only_when_home": "Benachrichtigungen zurückhalten, bis ausgewählte Person zu Hause ist", + "notify_fire_events": "Home Assistant-Ereignisse auslösen", + "notify_start_services": "Zyklusstart – Benachrichtigungsziele", + "notify_finish_services": "Zyklusende – Benachrichtigungsziele", + "notify_live_services": "Live-Fortschritt – Benachrichtigungsziele", + "notify_before_end_minutes": "Benachrichtigung vor Abschluss (Minuten vor Ende)", + "notify_title": "Titel der Benachrichtigung", + "notify_icon": "Benachrichtigungssymbol", + "notify_start_message": "Format für Start-Benachrichtigung", + "notify_finish_message": "Format für Abschluss-Benachrichtigung", + "notify_pre_complete_message": "Format für Hinweis-Benachrichtigung", + "show_advanced": "Erweiterte Einstellungen bearbeiten (nächster Schritt)" + }, + "data_description": { + "apply_suggestions": "Aktivieren um vom Lernalgorithmus vorgeschlagene Werte in das Formular zu kopieren. Vor dem Speichern überprüfen.\n\nEmpfohlen (vom Lernen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Gerätetyp wählen (Waschmaschine, Trockner, Geschirrspüler, Kaffeemaschine, Elektroauto). Dieses Tag wird zur besseren Organisation bei jedem Zyklus gespeichert.", + "power_sensor": "Die Sensoreinheit meldet die Leistung in Echtzeit (in Watt). Sie können dies jederzeit ändern, ohne historische Daten zu verlieren – nützlich, wenn Sie einen Smart Plug austauschen.", + "min_power": "Leistungswerte über diesem Schwellwert (in Watt) zeigen an, dass das Gerät läuft. Für die meisten Geräte ist 2W ein guter Anfang.", + "off_delay": "Die geglättete Leistung muss für diesen Zeitraum (in Sekunden) unter dem Stoppschwellwert bleiben, um den Abschluss zu markieren. Standard: 120s.", + "notify_actions": "Optionale Home Assistant-Aktionssequenz zur Ausführung für Benachrichtigungen (Skripte, Benachrichtigung, TTS usw.). Wenn festgelegt, werden Aktionen vor dem Fallback-Benachrichtigungsdienst ausgeführt.", + "notify_people": "Personenentitäten für verzögerte Benachrichtigungen. Wenn diese Option aktiviert ist, wird die Benachrichtigung verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_only_when_home": "Wenn diese Option aktiviert ist, werden Benachrichtigungen verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_fire_events": "Wenn aktiviert, werden Home Assistant-Ereignisse für Zyklusstart und -ende (ha_washdata_cycle_started und ha_washdata_cycle_ended) ausgelöst, um Automatisierungen auszulösen.", + "notify_start_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus beginnt. Leer lassen, um Startbenachrichtigungen zu überspringen.", + "notify_finish_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus endet oder kurz vor dem Abschluss steht. Lassen Sie das Feld leer, um Abschlussbenachrichtigungen zu überspringen.", + "notify_live_services": "Ein oder mehrere Benachrichtigungsdienste, um während der Ausführung des Zyklus Live-Fortschrittsaktualisierungen zu erhalten (nur mobile Begleit-App). Leer lassen, um Live-Updates zu überspringen.", + "notify_before_end_minutes": "Wie viele Minuten vor dem voraussichtlichen Ende des Zyklus soll eine Benachrichtigung gesendet werden. Zum Deaktivieren auf 0 setzen. Standard: 0.", + "notify_title": "Benutzerdefinierter Titel für Benachrichtigungen. Unterstützt den Platzhalter {device}.", + "notify_icon": "Symbol zur Verwendung für Benachrichtigungen (z. B. mdi:washing-machine). Feld leer lassen, um ohne Symbol zu senden.", + "notify_start_message": "Nachricht wird gesendet, wenn ein Zyklus beginnt. Unterstützt den Platzhalter {device}.", + "notify_finish_message": "Nachricht wird gesendet, wenn ein Zyklus beendet ist. Unterstützt die Platzhalter {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Text der wiederkehrenden Live-Fortschrittsaktualisierungen während der Ausführung des Zyklus. Unterstützt die Platzhalter {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Benachrichtigungen", + "description": "Benachrichtigungskanäle, Vorlagen und optionale Live-Fortschrittsaktualisierungen für Mobilgeräte konfigurieren.", + "data": { + "notify_actions": "Benachrichtigungsaktionen", + "notify_people": "Benachrichtigungen warten auf folgende Personen", + "notify_only_when_home": "Benachrichtigungen zurückhalten, bis ausgewählte Person zu Hause ist", + "notify_fire_events": "Home Assistant-Ereignisse auslösen", + "notify_start_services": "Zyklusstart – Benachrichtigungsziele", + "notify_finish_services": "Zyklusende – Benachrichtigungsziele", + "notify_live_services": "Live-Fortschritt – Benachrichtigungsziele", + "notify_before_end_minutes": "Benachrichtigung vor Abschluss (Minuten vor Ende)", + "notify_live_interval_seconds": "Live-Update-Intervall (Sekunden)", + "notify_live_overrun_percent": "Live-Update-Überschreitungszuschlag (%)", + "notify_live_chronometer": "Chronometer-Countdown-Timer", + "notify_title": "Titel der Benachrichtigung", + "notify_icon": "Benachrichtigungssymbol", + "notify_start_message": "Format für Start-Benachrichtigung", + "notify_finish_message": "Format für Abschluss-Benachrichtigung", + "notify_pre_complete_message": "Format für Hinweis-Benachrichtigung", + "energy_price_entity": "Energiepreis – Entität (optional)", + "energy_price_static": "Energiepreis – Statischer Wert (optional)", + "go_back": "Ohne Speichern zurückgehen", + "notify_reminder_message": "Reminder-Nachrichtenformat", + "notify_timeout_seconds": "Auto-Demiss Nach (Sekunden)", + "notify_channel": "Benachrichtigungskanal (Status/Live/Erinnerung)", + "notify_finish_channel": "Fertiger Notifizierungskanal" + }, + "data_description": { + "go_back": "Aktivieren Sie dies und klicken Sie auf Senden, um alle obigen Änderungen zu verwerfen und zum Hauptmenü zurückzukehren.", + "notify_actions": "Optionale Home Assistant-Aktionssequenz zur Ausführung für Benachrichtigungen (Skripte, Benachrichtigung, TTS usw.). Wenn festgelegt, werden Aktionen vor dem Fallback-Benachrichtigungsdienst ausgeführt.", + "notify_people": "Personenentitäten für verzögerte Benachrichtigungen. Wenn diese Option aktiviert ist, wird die Benachrichtigung verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_only_when_home": "Wenn diese Option aktiviert ist, werden Benachrichtigungen verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_fire_events": "Wenn aktiviert, werden Home Assistant-Ereignisse für Zyklusstart und -ende (ha_washdata_cycle_started und ha_washdata_cycle_ended) ausgelöst, um Automatisierungen auszulösen.", + "notify_start_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus beginnt (z. B. notify.mobile_app_my_phone). Leer lassen, um Startbenachrichtigungen zu überspringen.", + "notify_finish_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus endet oder kurz vor dem Abschluss steht. Lassen Sie das Feld leer, um Abschlussbenachrichtigungen zu überspringen.", + "notify_live_services": "Ein oder mehrere Benachrichtigungsdienste, um während der Ausführung des Zyklus Live-Fortschrittsaktualisierungen zu erhalten (nur mobile Begleit-App). Leer lassen, um Live-Updates zu überspringen.", + "notify_before_end_minutes": "Wie viele Minuten vor dem voraussichtlichen Ende des Zyklus soll eine Benachrichtigung gesendet werden. Zum Deaktivieren auf 0 setzen. Standard: 0.", + "notify_live_interval_seconds": "Wie oft Fortschrittsaktualisierungen während der Ausführung gesendet werden. Niedrigere Werte führen zu einer schnelleren Reaktion, verbrauchen jedoch mehr Benachrichtigungen. Es wird ein Minimum von 30 Sekunden erzwungen – Werte unter 30 Sekunden werden automatisch auf 30 Sekunden aufgerundet.", + "notify_live_overrun_percent": "Sicherheitsmarge über der geschätzten Aktualisierungsanzahl für lange/überlaufende Zyklen (z. B. 20 % ermöglichen das 1,2-fache der geschätzten Aktualisierungen).", + "notify_live_chronometer": "Wenn diese Option aktiviert ist, beinhaltet jedes Live-Update einen Chronometer-Countdown bis zur voraussichtlichen Zielzeit. Der Benachrichtigungstimer läuft auf dem Gerät zwischen Aktualisierungen automatisch ab (nur Android-Begleit-App).", + "notify_title": "Benutzerdefinierter Titel für Benachrichtigungen. Unterstützt den Platzhalter {device}.", + "notify_icon": "Symbol zur Verwendung für Benachrichtigungen (z. B. mdi:washing-machine). Feld leer lassen, um ohne Symbol zu senden.", + "notify_start_message": "Nachricht wird gesendet, wenn ein Zyklus beginnt. Unterstützt den Platzhalter {device}.", + "notify_finish_message": "Nachricht wird gesendet, wenn ein Zyklus beendet ist. Unterstützt die Platzhalter {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Wählen Sie eine numerische Entität aus, die den aktuellen Strompreis enthält (z. B. sensor.electricity_price, input_number.energy_rate). Wenn festgelegt, wird der Platzhalter {cost} aktiviert. Hat Vorrang vor dem statischen Wert, wenn beide konfiguriert sind.", + "energy_price_static": "Fester Strompreis pro kWh. Wenn festgelegt, wird der Platzhalter {cost} aktiviert. Wird ignoriert, wenn oben eine Entität konfiguriert ist. Fügen Sie Ihr Währungssymbol in die Nachrichtenvorlage ein, z. B. {cost} €.", + "notify_reminder_message": "Text der einmaligen Erinnerung schickte die konfigurierte Anzahl von Minuten vor dem geschätzten Ende. Unterscheiden Sie sich von den wiederkehrenden Live-Updates oben. Unterstützt {device}, {minutes}, {program} Platzhalter.", + "notify_timeout_seconds": "Automatische Kündigung von Benachrichtigungen nach diesen vielen Sekunden (vorüberwiesen als die Begleit-App 'Timeout'). Setzen Sie auf 0, um sie bis zur Entlassung zu halten. Standard: 0.", + "notify_channel": "Android Begleit-App-Benachrichtigungskanal für Status, Live-Fortschritt und Erinnerungs-Benachrichtigungen. Der Klang und die Bedeutung eines Kanals werden in der Begleit-App zum ersten Mal konfiguriert, wenn der Kanalname verwendet wird - WashData setzt nur den Kanalnamen. Lassen Sie für den App-Standard leer.", + "notify_finish_channel": "Separater Android-Kanal für den fertigen Alarm (auch von der Erinnerung und der Wäsche-Warte-Nag) so kann es seinen eigenen Klang haben. Lassen Sie leer, um den Kanal oben wieder zu verwenden.", + "notify_pre_complete_message": "Text der wiederkehrenden Live-Fortschrittsaktualisierungen während der Ausführung des Zyklus. Unterstützt die Platzhalter {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Erweiterte Einstellungen", + "description": "Erkennungsschwellwerte, Lernen und erweitertes Verhalten optimieren. Die Standardeinstellungen funktionieren für die meisten Setups gut.", + "sections": { + "suggestions_section": { + "name": "Empfohlene Einstellungen", + "description": "{suggestions_count} gelernte Vorschläge verfügbar. Aktivieren Sie 'Vorgeschlagene Werte anwenden', um vorgeschlagene Änderungen vor dem Speichern zu überprüfen.", + "data": { + "apply_suggestions": "Vorgeschlagene Werte anwenden" + }, + "data_description": { + "apply_suggestions": "Aktivieren um einen Überprüfungsschritt für vorgeschlagene Werte vom Lernalgorithmus zu öffnen. Es wird nichts automatisch gespeichert.\n\nEmpfohlen (vom Lernen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Erkennung & Leistungsschwellen", + "description": "Wann das Gerät als EIN oder AUS gilt, Energieschwellen und Zeitsteuerung für Zustandsübergänge.", + "data": { + "start_duration_threshold": "Start-Entprellungsdauer (s)", + "start_energy_threshold": "Startenergieschwelle (Wh)", + "completion_min_seconds": "Mindestlaufzeit für den Abschluss (Sekunden)", + "start_threshold_w": "Startschwelle (W)", + "stop_threshold_w": "Stoppschwelle (W)", + "end_energy_threshold": "Endenergieschwelle (Wh)", + "running_dead_zone": "Running Dead Zone (Sekunden)", + "end_repeat_count": "Anzahl der Wiederholungen beenden", + "min_off_gap": "Min. Abstand zwischen Zyklen (s)", + "sampling_interval": "Abtastintervall (s)" + }, + "data_description": { + "start_duration_threshold": "Kurze Leistungsspitzen ignorieren, die kürzer als diese Dauer (Sekunden) sind. Filtert Startspitzen (z. B. 60 W für 2 Sekunden) heraus, bevor der eigentliche Zyklus beginnt. Standard: 5s.", + "start_energy_threshold": "Um bestätigt zu werden, muss der Zyklus während der START-Phase mindestens so viel Energie (Wh) akkumulieren. Standard: 0,005 Wh.", + "completion_min_seconds": "Zyklen, die kürzer sind, werden als „unterbrochen“ markiert, auch wenn sie natürlich enden. Standard: 600s.", + "start_threshold_w": "Die Leistung muss ÜBER diesen Schwellwert ansteigen, um einen Zyklus zu starten. Höher einstellen als Ihre Standby-Leistung. Standard: min_power + 1 W.", + "stop_threshold_w": "Die Leistung muss UNTER diesen Schwellwert fallen, um einen Zyklus zu BEENDEN. Den Wert knapp über Null einstellen, um zu vermeiden, dass Rauschen falsche Enden auslöst. Standard: 0,6 * min_power.", + "end_energy_threshold": "Der Zyklus endet nur, wenn die Energie im letzten Ausschaltverzögerungsfenster unter diesem Wert (Wh) liegt. Verhindert falsche Enden, wenn die Leistung nahe Null schwankt. Standard: 0,05 Wh.", + "running_dead_zone": "Nach dem Start des Zyklus Stromeinbrüche für diesen Zeitraum (in Sekunden) ignorieren, um eine falsche Enderkennung während der anfänglichen Hochlaufphase zu verhindern. Zum Deaktivieren auf 0 setzen. Standard: 0s.", + "end_repeat_count": "Gibt an, wie oft die Endbedingung (Leistung unter dem Schwellwert für Ausschaltverzögerung) nacheinander erfüllt sein muss, bevor der Zyklus beendet wird. Höhere Werte verhindern falsches Ende während Pausen. Standard: 1.", + "min_off_gap": "Wenn ein Zyklus innerhalb dieser Anzahl von Sekunden nach dem Ende des vorherigen beginnt, werden sie zu einem einzigen Zyklus zusammengeführt.", + "sampling_interval": "Mindestabtastintervall in Sekunden. Aktualisierungen, die schneller als diese Rate sind, werden ignoriert. Standard: 30 Sek. (2 Sek. für Waschmaschinen, Waschtrockner und Geschirrspüler)." + } + }, + "matching_section": { + "name": "Profilabgleich & Lernen", + "description": "Wie aggressiv WashData laufende Zyklen mit gelernten Profilen abgleicht und wann um Feedback gebeten wird.", + "data": { + "profile_match_min_duration_ratio": "Verhältnis der Mindestdauer der Profilübereinstimmung (0,1–1,0)", + "profile_match_interval": "Profil-Match-Intervall (Sekunden)", + "profile_match_threshold": "Schwellwert für Profilübereinstimmung", + "profile_unmatch_threshold": "Schwellwert für nicht übereinstimmendes Profil", + "auto_label_confidence": "Auto-Label-Konfidenz (0-1)", + "learning_confidence": "Vertrauen der Feedback-Anfrage (0-1)", + "suppress_feedback_notifications": "Feedback-Benachrichtigungen unterdrücken", + "duration_tolerance": "Dauertoleranz (0–0,5)", + "smoothing_window": "Glättungsfenster (Beispiele)", + "profile_duration_tolerance": "Toleranz der Profilübereinstimmungsdauer (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Mindestdauerverhältnis für den Profilabgleich. Damit der Laufzyklus übereinstimmt, muss er mindestens diesen Bruchteil der Profildauer betragen. Niedriger = frühere Übereinstimmung. Standard: 0,50 (50 %).", + "profile_match_interval": "Wie oft (in Sekunden) der Profilabgleich während eines Zyklus versucht werden soll. Niedrigere Werte ermöglichen eine schnellere Erkennung, beanspruchen jedoch mehr CPU. Standard: 300s (5 Minuten).", + "profile_match_threshold": "Mindest-DTW-Ähnlichkeitswert (0,0–1,0), der erforderlich ist, um ein Profil als Übereinstimmung zu betrachten. Höher = strengere Übereinstimmung, weniger Fehlalarme. Standard: 0,4.", + "profile_unmatch_threshold": "DTW-Ähnlichkeitswert (0,0–1,0), unterhalb dessen ein zuvor übereinstimmendes Profil abgelehnt wird. Sollte niedriger als der Übereinstimmungsschwellwert sein, um ein Flackern zu verhindern. Standard: 0,35.", + "auto_label_confidence": "Zyklen über diesem Konfidenzniveau nach Abschluss automatisch zuordnen (0,0–1,0).", + "learning_confidence": "Mindestkonfidenz für die Anforderung einer Benutzerverifizierung über ein Ereignis (0,0–1,0).", + "suppress_feedback_notifications": "Wenn diese Option aktiviert ist, verfolgt WashData weiterhin intern Feedback und fordert es an, zeigt jedoch keine dauerhaften Benachrichtigungen an, in denen Sie zur Bestätigung von Zyklen aufgefordert werden. Nützlich, wenn Zyklen immer korrekt erkannt werden und Sie die Eingabeaufforderungen als störend empfinden.", + "duration_tolerance": "Toleranz für Schätzungen der verbleibenden Zeit während eines Laufs (0,0–0,5 entspricht 0–50 %).", + "smoothing_window": "Anzahl der letzten Leistungsmesswerte, die für die Glättung des gleitenden Durchschnitts verwendet werden.", + "profile_duration_tolerance": "Vom Profilabgleich verwendete Toleranz für die Gesamtdauervarianz (0,0–0,5). Standardverhalten: ±25 %." + } + }, + "timing_section": { + "name": "Zeitverhalten, Wartung & Debug", + "description": "Watchdog, Fortschrittsrücksetzung, automatische Wartung und Sichtbarkeit von Debug-Entitäten.", + "data": { + "watchdog_interval": "Watchdog-Intervall (Sekunden)", + "no_update_active_timeout": "Zeitüberschreitung bei fehlender Aktualisierung (s)", + "progress_reset_delay": "Verzögerung beim Zurücksetzen des Fortschritts (Sekunden)", + "auto_maintenance": "Automatische Wartung aktivieren", + "expose_debug_entities": "Debug-Entitäten verfügbar machen", + "save_debug_traces": "Debug-Traces speichern" + }, + "data_description": { + "watchdog_interval": "Sekunden zwischen Watchdog-Prüfungen während der Ausführung. Standard: 5s. WARNUNG: Stellen Sie sicher, dass dieser Wert HÖHER ist als das Aktualisierungsintervall Ihres Sensors, um falsche Stopps zu vermeiden (z. B. wenn der Sensor alle 60 Sekunden aktualisiert wird, verwenden Sie 65 Sekunden+).", + "no_update_active_timeout": "Für Publish-on-Change-Sensoren: Beenden eines ACTIVE-Zyklus nur erzwingen, wenn für diesen Zeitraum (in Sekunden) keine Aktualisierungen eintreffen. Beim Abschluss mit geringem Stromverbrauch wird weiterhin die Ausschaltverzögerung verwendet.", + "progress_reset_delay": "Nachdem ein Zyklus abgeschlossen ist (100 %), so viele Sekunden Leerlauf abwarten, bevor der Fortschritt auf 0 % zurückgesetzt wird. Standard: 300s.", + "auto_maintenance": "Automatische Wartung aktivieren (Beispiele reparieren, Fragmente zusammenführen).", + "expose_debug_entities": "Erweiterte Sensoren (Konfidenz, Phase, Mehrdeutigkeit) zum Debuggen anzeigen.", + "save_debug_traces": "Detaillierte Ranking- und Stromverläufe im Verlauf speichern (erhöht die Speichernutzung)." + } + }, + "anti_wrinkle_section": { + "name": "Knitterschutz (Trockner)", + "description": "Ignoriert Trommeldrehungen nach dem Zyklus, um Geisterzyklen zu verhindern. Nur für Trockner / Waschtrockner.", + "data": { + "anti_wrinkle_enabled": "Knitterschutz (nur Trockner)", + "anti_wrinkle_max_power": "Knitterschutz-Höchstleistung (W)", + "anti_wrinkle_max_duration": "Knitterschutz-Höchstdauer (s)", + "anti_wrinkle_exit_power": "Knitterschutz-Abschlussleistung (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Kurze Trommelumdrehungen mit geringer Leistung nach Programmende ignorieren, um Geisterzyklen zu vermeiden (nur Trockner/Waschtrockner).", + "anti_wrinkle_max_power": "Leistungsspitzen unter diesem Schwellwert werden als Knitterschutz-Rotationen und nicht als neue Zyklen behandelt. Gilt nur, wenn Knitterschutz aktiviert ist.", + "anti_wrinkle_max_duration": "Maximale Burst-Länge zur Behandlung als Knitterschutz-Rotation. Gilt nur, wenn Knitterschutz aktiviert ist.", + "anti_wrinkle_exit_power": "Wenn die Leistung unter diesen Wert fällt, die Knitterschutz-Funktion sofort beenden (tatsächliches Ende). Gilt nur, wenn Knitterschutz aktiviert ist." + } + }, + "delay_start_section": { + "name": "Erkennung für verzögerten Start", + "description": "Behandelt anhaltenden Niedrigleistungs-Standby als 'Wartet auf Start', bis der Zyklus tatsächlich beginnt.", + "data": { + "delay_start_detect_enabled": "Aktivieren Sie die verzögerte Starterkennung", + "delay_confirm_seconds": "Verzögerter Start: Bestätigungszeit für Standby (s)", + "delay_timeout_hours": "Verzögerter Start: Maximale Wartezeit (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Wenn diese Option aktiviert ist, wird eine kurze Leistungsspitze bei geringem Stromverbrauch, gefolgt von einer anhaltenden Standby-Leistung, als verzögerter Start erkannt. Der Statussensor zeigt während der Verzögerung „Warten auf Start“ an. Es werden weder Programmzeit noch Fortschritt verfolgt, bis der Zyklus tatsächlich beginnt. Erfordert, dass start_threshold_w über der Drain-Spike-Leistung eingestellt wird.", + "delay_confirm_seconds": "Die Leistung muss für so viele Sekunden zwischen Stoppschwelle (W) und Startschwelle (W) bleiben, bevor WashData in 'Wartet auf Start' wechselt. Kurze Spitzen durch Menünavigation werden herausgefiltert. Standard: 60 s.", + "delay_timeout_hours": "Sicherheits-Timeout: Wenn sich die Maschine nach dieser Anzahl von Stunden immer noch im Zustand „Warten auf Start“ befindet, wird WashData auf „Aus“ zurückgesetzt. Standard: 8 Stunden." + } + }, + "external_triggers_section": { + "name": "Externe Auslöser, Tür & Pause", + "description": "Optionaler externer Sensor für Zyklusende, Türsensor für Pause-/Sauber-Erkennung und Schalter zum Unterbrechen der Stromversorgung bei Pause.", + "data": { + "external_end_trigger_enabled": "Externen Zyklusende-Trigger aktivieren", + "external_end_trigger": "Externer Zyklusendesensor", + "external_end_trigger_inverted": "Triggerlogik umkehren (Trigger auf AUS)", + "door_sensor_entity": "Türsensor", + "pause_cuts_power": "Unterbrechen Sie die Stromversorgung, wenn Sie pausieren", + "switch_entity": "Entität wechseln (für Stromausfall pausieren)", + "notify_unload_delay_minutes": "Verzögerung der Wäsche-Warten-Benachrichtigung (Min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Überwachung eines externen Binärsensors aktivieren, um den Abschluss des Zyklus auszulösen.", + "external_end_trigger": "Binäre Sensoreinheit auswählen. Wenn dieser Sensor auslöst, endet der aktuelle Zyklus mit dem Status „abgeschlossen“.", + "external_end_trigger_inverted": "Standardmäßig wird der Zyklus beendet, wenn der Sensor eingeschaltet wird. Aktivieren um stattdessen zu beenden, wenn der Sensor ausgeschaltet wird.", + "door_sensor_entity": "Optionaler binärer Sensor für die Maschinentür (ein = offen, aus = geschlossen). Wenn sich die Tür während eines aktiven Zyklus öffnet, bestätigt WashData, dass der Zyklus absichtlich angehalten wurde. Wenn der Zyklus beendet ist und die Tür noch geschlossen ist, wechselt der Zustand zu „Sauber”, bis die Tür geöffnet wird. Hinweis: Durch das Schließen der Tür wird ein angehaltener Zyklus NICHT automatisch fortgesetzt. Die Wiederaufnahme muss manuell über die Schaltfläche „Zyklus fortsetzen“ oder den entsprechenden Dienst ausgelöst werden.", + "pause_cuts_power": "Wenn Sie über die Schaltfläche „Zyklus anhalten“ oder den Dienst anhalten, schalten Sie auch die konfigurierte Schaltereinheit aus. Wird nur wirksam, wenn für dieses Gerät eine Switch-Entität konfiguriert ist.", + "switch_entity": "Die Schalterentität, die beim Anhalten oder Fortsetzen umgeschaltet wird. Erforderlich, wenn „Stromversorgung bei Pause abschalten“ aktiviert ist.", + "notify_unload_delay_minutes": "Senden Sie eine Benachrichtigung über den Abschlussbenachrichtigungskanal, nachdem der Zyklus beendet ist und die Tür so viele Minuten lang nicht geöffnet wurde (erfordert Türsensor). Zum Deaktivieren auf 0 setzen. Standard: 60 Min." + } + }, + "pump_section": { + "name": "Pumpenüberwachung", + "description": "Nur für den Gerätetyp Pumpe. Löst ein Ereignis aus, wenn ein Pumpenzyklus länger als diese Dauer läuft.", + "data": { + "pump_stuck_duration": "Schwellenwert(e) für Warnung „Pumpe blockiert“ (nur Pumpe)" + }, + "data_description": { + "pump_stuck_duration": "Wenn nach dieser Anzahl von Sekunden immer noch ein Pumpenzyklus läuft, wird einmalig ein ha_washdata_pump_stuck-Ereignis ausgelöst. Verwenden Sie dies, um einen blockierten Motor zu erkennen (die typische Laufzeit der Sumpfpumpe beträgt weniger als 60 s). Standard: 1800 s (30 min). Nur Pumpengerätetyp." + } + }, + "device_link_section": { + "name": "Link zur Übersicht", + "description": "Optional verbinden Sie dieses WashData-Gerät mit einem vorhandenen Home Assistant-Gerät (z.B. dem Smart Plug oder dem Gerät selbst). Das WashData-Gerät wird dann als \"Connected via\" dieses Geräts angezeigt. Lassen Sie die WashData als eigenständiges Gerät leer.", + "data": { + "linked_device": "Verknüpftes Gerät" + }, + "data_description": { + "linked_device": "Wählen Sie das Gerät, um WashData zu verbinden. Dies fügt eine 'Connected via' Referenz in der Geräte-Registrierung hinzu; WashData hält seine eigene Gerätekarte und -einrichtungen. Löschen Sie die Auswahl, um den Link zu entfernen." + } + } + } + }, + "diagnostics": { + "title": "Diagnose und Wartung", + "description": "Wartungsaktionen durchführen, z. B. das Zusammenführen fragmentierter Zyklen oder das Migrieren gespeicherter Daten.\n\n**Speichernutzung**\n\n- Dateigröße: {file_size_kb} KB\n- Zyklen: {cycle_count}\n- Profile: {profile_count}\n- Debug-Traces: {debug_count}", + "menu_options": { + "reprocess_history": "Wartung: Daten erneut verarbeiten und optimieren", + "clear_debug_data": "Debug-Daten löschen (Speicherplatz freigeben)", + "wipe_history": "ALLE Daten für dieses Gerät löschen (unwiderruflich)", + "export_import": "JSON mit Einstellungen exportieren/importieren (Kopieren/Einfügen)", + "menu_back": "← Zurück" + } + }, + "clear_debug_data": { + "title": "Debug-Daten löschen", + "description": "Sind Sie sicher, dass Sie alle gespeicherten Debug-Traces löschen möchten? Dadurch wird Speicherplatz frei, aber detaillierte Ranking-Informationen aus vergangenen Zyklen werden entfernt." + }, + "manage_cycles": { + "title": "Zyklen verwalten", + "description": "Letzte Zyklen:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Alte Zyklen automatisch zuordnen", + "select_cycle_to_label": "Zyklus einzeln zuordnen", + "select_cycle_to_delete": "Zyklus löschen", + "interactive_editor": "Interaktiver Editor zum Zusammenführen/Teilen", + "trim_cycle_select": "Zyklusdaten zuschneiden", + "menu_back": "← Zurück" + } + }, + "manage_cycles_empty": { + "title": "Zyklen verwalten", + "description": "Noch keine Zyklen aufgezeichnet.", + "menu_options": { + "auto_label_cycles": "Alte Zyklen automatisch zuordnen", + "menu_back": "← Zurück" + } + }, + "manage_profiles": { + "title": "Profile verwalten", + "description": "Profilzusammenfassung:\n{profile_summary}", + "menu_options": { + "create_profile": "Neues Profil erstellen", + "edit_profile": "Profil bearbeiten/umbenennen", + "delete_profile_select": "Profil löschen", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Verlauf bereinigen – grafisch darstellen und löschen", + "assign_profile_phases_select": "Phasenzeiträume zuweisen", + "menu_back": "← Zurück" + } + }, + "manage_profiles_empty": { + "title": "Profile verwalten", + "description": "Noch keine Profile erstellt.", + "menu_options": { + "create_profile": "Neues Profil erstellen", + "menu_back": "← Zurück" + } + }, + "manage_phase_catalog": { + "title": "Phasenkatalog verwalten", + "description": "Phasenkatalog (Standardeinstellungen für dieses Gerät + benutzerdefinierte Phasen):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Neue Phase erstellen", + "phase_catalog_edit_select": "Phase bearbeiten", + "phase_catalog_delete": "Phase löschen", + "menu_back": "← Zurück" + } + }, + "phase_catalog_create": { + "title": "Benutzerdefinierte Phase erstellen", + "description": "Benutzerdefinierte Phase erstellen und Gerätetyp zuweisen.", + "data": { + "target_device_type": "Zielgerätetyp", + "phase_name": "Phasenname", + "phase_description": "Phasenbeschreibung" + } + }, + "phase_catalog_edit_select": { + "title": "Benutzerdefinierte Phase bearbeiten", + "description": "Benutzerdefinierte Phase zum Bearbeiten auswählen.", + "data": { + "phase_name": "Benutzerdefinierte Phase" + } + }, + "phase_catalog_edit": { + "title": "Benutzerdefinierte Phase bearbeiten", + "description": "Bearbeitungsphase: {phase_name}", + "data": { + "phase_name": "Phasenname", + "phase_description": "Phasenbeschreibung" + } + }, + "phase_catalog_delete": { + "title": "Benutzerdefinierte Phase löschen", + "description": "Benutzerdefinierte Phase zum Löschen auswählen. Alle zugewiesenen Zeiträume, die diese Phase verwenden, werden entfernt.", + "data": { + "phase_name": "Benutzerdefinierte Phase" + } + }, + "assign_profile_phases": { + "title": "Phasenzeiträume zuweisen", + "description": "Phasenvorschau für Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuelle Zeiträume:**\n{current_ranges}\n\nFolgende Aktionen verwenden, um Zeiträume hinzuzufügen, zu bearbeiten, zu löschen oder zu speichern.", + "menu_options": { + "assign_profile_phases_add": "Phasenzeitraum hinzufügen", + "assign_profile_phases_edit_select": "Phasenzeitraum bearbeiten", + "assign_profile_phases_delete": "Phasenzeitraum löschen", + "phase_ranges_clear": "Alle Zeiträume löschen", + "assign_profile_phases_auto_detect": "Phasen automatisch erkennen", + "phase_ranges_save": "Speichern und zurückgeben", + "menu_back": "← Zurück" + } + }, + "assign_profile_phases_add": { + "title": "Phasenzeitraum hinzufügen", + "description": "Dem aktuellen Entwurf einen Phasenzeitraum hinzufügen.", + "data": { + "phase_name": "Phase", + "start_min": "Startminute", + "end_min": "Endminute" + } + }, + "assign_profile_phases_edit_select": { + "title": "Phasenzeitraum bearbeiten", + "description": "Zeitraum zum Bearbeiten auswählen.", + "data": { + "range_index": "Phasenzeitraum" + } + }, + "assign_profile_phases_edit": { + "title": "Phasenzeitraum bearbeiten", + "description": "Den ausgewählten Zeitraum anpassen.", + "data": { + "phase_name": "Phase", + "start_min": "Startminute", + "end_min": "Endminute" + } + }, + "assign_profile_phases_delete": { + "title": "Phasenzeitraum löschen", + "description": "Zeitraum auswählen, der aus dem Entwurf entfernt werden soll.", + "data": { + "range_index": "Phasenzeitraum" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatisch erkannte Phasen", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} Phase(n) automatisch erkannt.** Wählen Sie unten eine Aktion aus.", + "data": { + "action": "Aktion" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Erkannte Phasen benennen", + "description": "Profil: **{profile_name}**\n\n**{detected_count} Phase(n) erkannt:**\n{ranges_summary}\n\nJeder Phase einen Namen zuweisen." + }, + "assign_profile_phases_select": { + "title": "Phasenzeiträume zuweisen", + "description": "Profil auswählen, um Phasenzeiträume zu konfigurieren.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profilstatistik", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Neues Profil erstellen", + "description": "Ein neues Profil manuell oder aus einem vergangenen Zyklus erstellen.\n\nBeispiele für Profilnamen: „Feinwäsche“, „Kochwäsche“, „Schnellwäsche“", + "data": { + "profile_name": "Profilname", + "reference_cycle": "Referenzzyklus (optional)", + "manual_duration": "Manuelle Dauer (Minuten)" + } + }, + "edit_profile": { + "title": "Profil bearbeiten", + "description": "Profil zum Umbenennen auswählen", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Profildetails bearbeiten", + "description": "Aktuelles Profil: {current_name}", + "data": { + "new_name": "Profilname", + "manual_duration": "Manuelle Dauer (Minuten)" + } + }, + "delete_profile_select": { + "title": "Profil löschen", + "description": "Profil zum Löschen auswählen", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Profillöschung bestätigen.", + "description": "⚠️ Dadurch wird das Profil dauerhaft gelöscht: {profile_name}", + "data": { + "unlabel_cycles": "Entfernt die Zuordnung dieses Profils von allen Zyklen" + } + }, + "auto_label_cycles": { + "title": "Alte Zyklen automatisch zuordnen", + "description": "Insgesamt {total_count} Zyklen gefunden. Profile: {profiles}", + "data": { + "confidence_threshold": "Minimales Vertrauen (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Zyklus zum Zuordnen auswählen", + "description": "✓ = abgeschlossen, ⚠ = fortgesetzt, ✗ = unterbrochen", + "data": { + "cycle_id": "Zyklus" + } + }, + "select_cycle_to_delete": { + "title": "Zyklus löschen", + "description": "⚠️ Dadurch wird der ausgewählte Zyklus dauerhaft gelöscht", + "data": { + "cycle_id": "Zyklus zum Löschen auswählen" + } + }, + "label_cycle": { + "title": "Zyklus zuordnen", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Neuer Profilname (falls erstellt)" + } + }, + "post_process": { + "title": "Prozessverlauf (Zusammenführen/Aufteilen)", + "description": "Bereinigen Sie den Zyklusverlauf, indem Sie fragmentierte Läufe zusammenführen und falsch zusammengeführte Zyklen innerhalb eines Zeitfensters aufteilen. Geben Sie die Anzahl der Stunden ein, in denen Sie zurückblicken möchten (verwenden Sie 999999 für alle).", + "data": { + "time_range": "Lookback-Fenster (Stunden)", + "gap_seconds": "Zusammenführungs-/Aufteilungslücke (Sekunden)" + }, + "data_description": { + "time_range": "Anzahl der Stunden, die nach fragmentierten Zyklen zum Zusammenführen gescannt werden sollen. Verwenden Sie 999999, um alle historischen Daten zu verarbeiten.", + "gap_seconds": "Schwellwert für die Entscheidung über Aufteilung oder Zusammenführung. Lücken, die kürzer sind, werden zusammengeführt. Längere Lücken werden geteilt. Verringern Sie diesen Wert, um eine Teilung eines Zyklus zu erzwingen, der falsch zusammengeführt wurde." + } + }, + "cleanup_profile": { + "title": "Verlauf bereinigen – Profil auswählen", + "description": "Profil zur Visualisierung auswählen. Dadurch wird ein Diagramm erstellt, das alle vergangenen Zyklen für dieses Profil zeigt, um Ausreißer zu identifizieren.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Verlauf bereinigen – grafisch darstellen und löschen", + "description": "![Grafik]({graph_url})\n\n**Visualisierung von {profile_name}**\n\nDie Grafik zeigt einzelne Zyklen als farbige Linien. Identifizieren Sie Ausreißer (Linien, die weit von der Gruppe entfernt sind) und gleichen Sie ihre Farbe in der Liste unten ab, um sie zu löschen.\n\n**Zyklen auswählen, die DAUERHAFT gelöscht werden sollen:**", + "data": { + "cycles_to_delete": "Zu löschende Zyklen (passende Farben prüfen)" + } + }, + "interactive_editor": { + "title": "Interaktiver Editor zum Zusammenführen/Teilen", + "description": "Manuelle Aktion auswählen, die auf Zyklusverlauf angewendet werden soll.", + "menu_options": { + "editor_split": "Einen Zyklus aufteilen (Lücken finden)", + "editor_merge": "Zyklen zusammenführen (Fragmente verbinden)", + "editor_delete": "Zyklus(e) löschen", + "menu_back": "← Zurück" + } + }, + "editor_select": { + "title": "Zyklen auswählen", + "description": "Zu verarbeitende Zyklen auswählen.\n\n{info_text}", + "data": { + "selected_cycles": "Zyklen" + } + }, + "editor_configure": { + "title": "Konfigurieren und anwenden", + "description": "{preview_md}", + "data": { + "confirm_action": "Aktion", + "merged_profile": "Resultierendes Profil", + "new_profile_name": "Neuer Profilname", + "confirm_commit": "Ja, ich bestätige diese Aktion", + "segment_0_profile": "Segment 1-Profil", + "segment_1_profile": "Segment 2-Profil", + "segment_2_profile": "Segment 3-Profil", + "segment_3_profile": "Segment 4-Profil", + "segment_4_profile": "Segment 5-Profil", + "segment_5_profile": "Segment 6-Profil", + "segment_6_profile": "Segment 7-Profil", + "segment_7_profile": "Segment 8-Profil", + "segment_8_profile": "Segment 9-Profil", + "segment_9_profile": "Segment 10-Profil" + } + }, + "editor_split_params": { + "title": "Split-Konfiguration", + "description": "Empfindlichkeit für die Aufteilung dieses Zyklus anpassen. Jede längere Leerlauflücke führt zu einer Spaltung.", + "data": { + "split_mode": "Split-Methode" + } + }, + "editor_split_auto_params": { + "title": "Automatische Aufteilung konfigurieren", + "description": "Passen Sie die Empfindlichkeit für das Aufteilen dieses Zyklus an. Jede Leerlaufpause, die länger als dieser Wert ist, führt zu einer Aufteilung.", + "data": { + "min_gap_seconds": "Schwellwert für Aufteilungslücke (Sekunden)" + } + }, + "editor_split_manual_params": { + "title": "Manuelle Zeitstempel für die Aufteilung", + "description": "{preview_md}Zyklusfenster: **{cycle_start_wallclock} → {cycle_end_wallclock}** am {cycle_date}.\n\nGeben Sie einen oder mehrere Zeitstempel für die Aufteilung ein (`HH:MM` oder `HH:MM:SS`), einen pro Zeile. Der Zyklus wird an jedem Zeitstempel geteilt — N Zeitstempel erzeugen N+1 Segmente.", + "data": { + "split_timestamps": "Zeitstempel zum Trennen" + } + }, + "reprocess_history": { + "title": "Wartung: Daten erneut verarbeiten und optimieren", + "description": "Führt eine gründliche Bereinigung historischer Daten durch:\n1. Reduziert Nullleistungswerte (Stille).\n2. Korrigiert Zykluszeitpunkt/-dauer.\n3. Berechnet alle statistischen Modelle (Hüllkurven) neu.\n\n**Führen Sie dies aus, um Datenprobleme zu beheben oder neue Algorithmen anzuwenden.**" + }, + "wipe_history": { + "title": "Verlauf löschen (nur zum Testen)", + "description": "⚠️ Dadurch werden ALLE gespeicherten Zyklen und Profile für dieses Gerät dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden!" + }, + "export_import": { + "title": "JSON exportieren/importieren", + "description": "Kopieren/Einfügen von Zyklen, Profilen und Einstellungen für dieses Gerät. Exportieren oder JSON zum Importieren einfügen.", + "data": { + "mode": "Aktion", + "json_payload": "Komplette Konfiguration JSON" + }, + "data_description": { + "mode": "Wählen Sie „Exportieren“, um aktuelle Daten und Einstellungen zu kopieren, oder „Importieren“, um exportierte Daten von einem anderen WashData-Gerät einzufügen.", + "json_payload": "Für den Export: Dieses JSON kopieren (enthält Zyklen, Profile und alle erweiterten Einstellungen). Für den Import: JSON einfügen, das aus WashData exportiert wurde." + } + }, + "record_cycle": { + "title": "Aufnahmezyklus", + "description": "Einen Zyklus manuell aufzeichnen, um ein sauberes Profil ohne Störungen zu erstellen.\n\nStatus: **{status}**\nDauer: {duration}s\nBeispiele: {samples}", + "menu_options": { + "record_refresh": "Status aktualisieren", + "record_stop": "Aufnahme beenden (Speichern und verarbeiten)", + "record_start": "Neue Aufnahme starten", + "record_process": "Letzte Aufnahme verarbeiten (Zuschneiden und Speichern)", + "record_discard": "Letzte Aufnahme verwerfen", + "menu_back": "← Zurück" + } + }, + "record_process": { + "title": "Prozessaufzeichnung", + "description": "![Grafik]({graph_url})\n\n**Das Diagramm zeigt die Rohaufzeichnung.** Blau = Behalten, Rot = Vorgeschlagener Zuschnitt.\n*Das Diagramm ist statisch und wird bei Formularänderungen nicht aktualisiert.*\n\nDie Aufnahme wurde gestoppt. Zuschnitte werden an der erkannten Abtastrate (~{sampling_rate}s) ausgerichtet.\n\nRohdauer: {duration}s\nBeispiele: {samples}", + "data": { + "head_trim": "Anfang zuschneiden (Sekunden)", + "tail_trim": "Ende zuschneiden (Sekunden)", + "save_mode": "Ziel speichern", + "profile_name": "Profilname" + } + }, + "learning_feedbacks": { + "title": "Lern-Feedbacks", + "description": "Ausstehendes Feedback: {count}\n\n{pending_table}\n\nWählen Sie eine Zyklusüberprüfungsanfrage zur Bearbeitung aus.", + "menu_options": { + "learning_feedbacks_pick": "Ausstehendes Feedback prüfen", + "learning_feedbacks_dismiss_all": "Gesamtes ausstehendes Feedback verwerfen", + "menu_back": "← Zurück" + } + }, + "learning_feedbacks_pick": { + "title": "Feedback zur Überprüfung auswählen", + "description": "Wählen Sie eine Zyklus-Überprüfungsanfrage zum Öffnen aus.", + "data": { + "selected_feedback": "Zu überprüfenden Zyklus auswählen" + } + }, + "learning_feedbacks_empty": { + "title": "Lern-Feedbacks", + "description": "Es wurden keine ausstehenden Feedbacks gefunden.", + "menu_options": { + "menu_back": "← Zurück" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Alle ausstehenden Feedbacks verwerfen", + "description": "Sie sind dabei, **{count}** ausstehende Feedback-Anfrage(n) zu verwerfen. Verworfene Zyklen bleiben in Ihrem Verlauf, tragen jedoch kein neues Lernsignal mehr bei. Dies kann nicht rückgängig gemacht werden.\n\n✅ Aktivieren Sie das Kontrollkästchen unten und klicken Sie auf **Senden**, um alle zu verwerfen.\n❌ Lassen Sie es deaktiviert und klicken Sie auf **Senden**, um den Vorgang abzubrechen und zurückzukehren.", + "data": { + "confirm_dismiss_all": "Bestätigen: alle ausstehenden Feedback-Anfragen verwerfen" + } + }, + "resolve_feedback": { + "title": "Feedback beantworten", + "description": "{comparison_img}{candidates_table}\n**Erkanntes Profil**: {detected_profile} ({confidence_pct} %)\n**Geschätzte Dauer**: {est_duration_min} Min\n**Tatsächliche Dauer**: {act_duration_min} Min\n\n__Wählen Sie unten eine Aktion aus:__", + "data": { + "action": "Was möchten Sie tun?", + "corrected_profile": "Richtiges Programm (bei Korrektur)", + "corrected_duration": "Richtige Dauer in Sekunden (bei Korrektur)" + } + }, + "trim_cycle_select": { + "title": "Zyklus zuschneiden – Zyklus auswählen", + "description": "Zyklus zum Zuschneiden auswählen. ✓ = abgeschlossen, ⚠ = fortgesetzt, ✗ = unterbrochen", + "data": { + "cycle_id": "Zyklus" + } + }, + "trim_cycle": { + "title": "Zyklus zuschneiden", + "description": "Aktuelles Fenster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aktion" + } + }, + "trim_cycle_start": { + "title": "Anfang zuschneiden", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktueller Start: **{current_wallclock}** (+{current_offset_min} Min. ab Zyklusstart)\n\nWählen Sie mit der Uhrauswahl unten eine neue Startzeit aus.", + "data": { + "trim_start_time": "Neue Startzeit", + "trim_start_min": "Neustart (Minuten ab Zyklusstart)" + } + }, + "trim_cycle_end": { + "title": "Ende zuschneiden", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuelles Ende: **{current_wallclock}** (+{current_offset_min} Min. ab Zyklusstart)\n\nWählen Sie mit der Uhrauswahl unten eine neue Endzeit aus.", + "data": { + "trim_end_time": "Neue Endzeit", + "trim_end_min": "Neues Ende (Minuten ab Zyklusbeginn)" + } + } + }, + "error": { + "import_failed": "Der Import ist fehlgeschlagen. Bitte einen gültigen WashData-JSON-Export einfügen.", + "select_exactly_one": "Wählen Sie genau einen Zyklus für diese Aktion aus.", + "select_at_least_one": "Wählen Sie mindestens einen Zyklus für diese Aktion aus.", + "select_at_least_two": "Wählen Sie mindestens zwei Zyklen für diese Aktion aus.", + "profile_exists": "Ein Profil mit diesem Namen existiert bereits", + "rename_failed": "Profil konnte nicht umbenannt werden. Das Profil existiert möglicherweise nicht oder der neue Name ist bereits vergeben.", + "assignment_failed": "Profil konnte dem Zyklus nicht zugewiesen werden", + "duplicate_phase": "Eine Phase mit diesem Namen existiert bereits.", + "phase_not_found": "Die ausgewählte Phase wurde nicht gefunden.", + "invalid_phase_name": "Der Phasenname darf nicht leer sein.", + "phase_name_too_long": "Der Phasenname ist zu lang.", + "invalid_phase_range": "Der Phasenzeitraum ist ungültig. Ende muss größer als Anfang sein.", + "invalid_phase_timestamp": "Der Zeitstempel ist ungültig. Verwenden Sie das Format JJJJ-MM-TT HH:MM oder HH:MM.", + "overlapping_phase_ranges": "Phasenzeiträume überschneiden sich. Passen Sie die Zeiträume an und versuchen Sie es erneut.", + "incomplete_phase_row": "Jede Phasenzeile muss eine Phase sowie vollständige Start-/Endwerte für den ausgewählten Modus enthalten.", + "timestamp_mode_cycle_required": "Bitte wählen Sie einen Quellzyklus aus, wenn Sie den Zeitstempelmodus verwenden.", + "no_phase_ranges": "Für diese Aktion sind noch keine Phasenzeiträume verfügbar.", + "feedback_notification_title": "WashData: Zyklus überprüfen ({device})", + "feedback_notification_message": "**Gerät**: {device}\n**Programm**: {program} ({confidence} % Konfidenz)\n**Zeit**: {time}\n\nWashData benötigt Ihre Hilfe, um diesen erkannten Zyklus zu überprüfen.\n\nBitte gehen Sie zu **Einstellungen > Geräte & Dienste > WashData > Konfigurieren > Lern-Feedbacks**, um dieses Ergebnis zu bestätigen oder zu korrigieren.", + "suggestions_ready_notification_title": "WashData: Vorgeschlagene Einstellungen bereit ({device})", + "suggestions_ready_notification_message": "Der Sensor **Vorgeschlagene Einstellungen** meldet jetzt **{count}** umsetzbare Empfehlungen.\n\nUm sie zu überprüfen und anzuwenden: **Einstellungen > Geräte & Dienste > WashData > Konfigurieren > Erweiterte Einstellungen > Vorgeschlagene Werte anwenden**.\n\nVorschläge sind optional und werden vor dem Speichern zur Überprüfung angezeigt.", + "trim_range_invalid": "Der Start muss vor dem Ende liegen. Bitte Zuschnittspunkte anpassen.", + "trim_failed": "Das Zuschneiden ist fehlgeschlagen. Möglicherweise existiert der Zyklus nicht mehr oder das Fenster ist leer.", + "invalid_split_timestamp": "Der Zeitstempel ist ungültig oder liegt außerhalb des Zyklusfensters. Verwenden Sie HH:MM oder HH:MM:SS innerhalb des Zyklusfensters.", + "no_split_segments_found": "Aus diesen Zeitstempeln konnten keine gültigen Segmente erzeugt werden. Stellen Sie sicher, dass jeder Zeitstempel innerhalb des Zyklusfensters liegt und Segmente mindestens 1 Minute lang sind.", + "auto_tune_suggestion": "{device_type} {device_title} hat Geisterzyklen erkannt. Vorgeschlagene Änderung der Mindestleistung: {current_min}W -> {new_min}W (wird nicht automatisch angewendet).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} hat Geisterzyklen erkannt. Vorgeschlagene Änderung der Mindestleistung: {current_min}W -> {new_min}W (wird nicht automatisch angewendet)." + }, + "abort": { + "no_cycles_found": "Keine Zyklen gefunden", + "no_split_segments_found": "Im ausgewählten Zyklus wurden keine teilbaren Segmente gefunden.", + "cycle_not_found": "Der ausgewählte Zyklus konnte nicht geladen werden.", + "no_power_data": "Für den ausgewählten Zyklus sind keine Leistungsverläufe verfügbar.", + "no_profiles_found": "Keine Profile gefunden. Erstellen Sie zunächst ein Profil.", + "no_profiles_for_matching": "Keine Profile zum Abgleich verfügbar. Erstellen Sie zunächst Profile.", + "no_unlabeled_cycles": "Alle Zyklen sind bereits zugeordnet", + "no_suggestions": "Noch keine Wertevorschläge verfügbar. Führen Sie einige Zyklen durch und versuchen Sie es erneut.", + "no_predictions": "Keine Vorhersagen", + "no_custom_phases": "Keine benutzerdefinierten Phasen gefunden.", + "reprocess_success": "{count} Zyklen erfolgreich erneut verarbeitet.", + "debug_data_cleared": "Debug-Daten aus {count} Zyklen wurden erfolgreich gelöscht." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Zyklusstart", + "cycle_finish": "Zyklusende", + "cycle_live": "Live-Fortschritt" + } + }, + "common_text": { + "options": { + "unlabeled": "(nicht zugeordnet)", + "create_new_profile": "Neues Profil erstellen", + "remove_label": "Zuordnung entfernen", + "no_reference_cycle": "(Kein Referenzzyklus)", + "all_device_types": "Alle Gerätetypen", + "current_suffix": "(aktuell)", + "editor_select_info": "Einen Zyklus zum Teilen oder mehrere Zyklen zum Zusammenführen auswählen.", + "editor_select_info_split": "Wählen Sie 1 Zyklus zum Aufteilen aus.", + "editor_select_info_merge": "Wählen Sie 2 oder mehr Zyklen zum Zusammenführen aus.", + "editor_select_info_delete": "Wählen Sie 1 oder mehr Zyklen zum Löschen aus.", + "no_cycles_recorded": "Noch keine Zyklen aufgezeichnet.", + "profile_summary_line": "- **{name}**: {count} Zyklen, {avg}m durchschnittlich", + "no_profiles_created": "Noch keine Profile erstellt.", + "phase_preview": "Phasenvorschau", + "phase_preview_no_curve": "Die durchschnittliche Profilkurve ist noch nicht verfügbar. Weitere Zyklen für dieses Profil ausführen/zuordnen.", + "average_power_curve": "Durchschnittliche Leistungskurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} Zyklen, ~{duration}m Durchschnitt)", + "profile_option_short_fmt": "{name} ({count} Zyklen, ~{duration}m)", + "deleted_cycles_from_profile": "{count} Zyklen von {profile} gelöscht.", + "not_enough_data_graph": "Nicht genügend Daten, um ein Diagramm zu erstellen.", + "no_profiles_found": "Keine Profile gefunden.", + "cycle_info_fmt": "Zyklus: {start}, {duration}m, Strom: {label}", + "top_candidates_header": "### Top-Kandidaten", + "tbl_profile": "Profil", + "tbl_confidence": "Vertrauen", + "tbl_correlation": "Korrelation", + "tbl_duration_match": "Dauer-Match", + "detected_profile": "Erkanntes Profil", + "estimated_duration": "Geschätzte Dauer", + "actual_duration": "Tatsächliche Dauer", + "choose_action": "__Aktion auswählen:__", + "tbl_count": "Zählen", + "tbl_avg": "Durchschn", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energie (Durchschn.)", + "tbl_energy_total": "Energie (Gesamt)", + "tbl_consistency": "Bestehen.", + "tbl_last_run": "Letzter Lauf", + "graph_legend_title": "Diagrammlegende", + "graph_legend_body": "Das blaue Band stellt den beobachteten minimalen und maximalen Leistungsaufnahmebereich dar. Die Linie zeigt die durchschnittliche Leistungskurve.", + "recording_preview": "Aufnahmevorschau", + "trim_start": "Anfang zuschneiden", + "trim_end": "Ende zuschneiden", + "split_preview_title": "Geteilte Vorschau", + "split_preview_found_fmt": "{count} Segmente gefunden.", + "split_preview_confirm_fmt": "Klicken Sie auf „Bestätigen“, um diesen Zyklus in {count} separate Zyklen aufzuteilen.", + "merge_preview_title": "Vorschau zusammenführen", + "merge_preview_joining_fmt": "Tritt {count} Zyklen bei. Lücken werden mit 0W-Messwerten gefüllt.", + "editor_delete_preview_title": "Löschvorschau", + "editor_delete_preview_intro": "Die ausgewählten Zyklen werden dauerhaft gelöscht:", + "editor_delete_preview_confirm": "Klicken Sie auf Bestätigen, um diese Zyklusdatensätze dauerhaft zu löschen.", + "no_power_preview": "*Keine Leistungsdaten für die Vorschau verfügbar.*", + "profile_comparison": "Profilvergleich", + "actual_cycle_label": "Dieser Zyklus (aktuell)", + "storage_usage_header": "Speichernutzung", + "storage_file_size": "Dateigröße", + "storage_cycles": "Zyklen", + "storage_profiles": "Profile", + "storage_debug_traces": "Debug-Traces", + "overview_suffix": "Überblick", + "phase_preview_for_profile": "Phasenvorschau für Profil", + "current_ranges_header": "Aktuelle Zeiträume", + "assign_phases_help": "Folgende Aktionen verwenden, um Zeiträume hinzuzufügen, zu bearbeiten, zu löschen oder zu speichern.", + "profile_summary_header": "Profilzusammenfassung", + "recent_cycles_header": "Aktuelle Zyklen", + "trim_cycle_preview_title": "Zykluszuschnittsvorschau", + "trim_cycle_preview_no_data": "Für diesen Zyklus sind keine Leistungsdaten verfügbar.", + "trim_cycle_preview_kept_suffix": "gehalten", + "table_program": "Programm", + "table_when": "Wann", + "table_length": "Dauer", + "table_match": "Treffer", + "table_profile": "Profil", + "table_cycles": "Zyklen", + "table_avg_length": "Ø Dauer", + "table_last_run": "Letzter Lauf", + "table_avg_energy": "Ø Energie", + "table_detected_program": "Erkanntes Programm", + "table_confidence": "Vertrauen", + "table_reported": "Gemeldet", + "phase_builtin_suffix": "(Eingebaut)", + "phase_none_available": "Keine Phasen verfügbar.", + "settings_deprecation_warning": "⚠️ **Veralteter Gerätetyp:** {device_type} soll in einer zukünftigen Version entfernt werden. Die Matching-Pipeline von WashData liefert für diese Appliance-Klasse keine zuverlässigen Ergebnisse. Ihre Integration funktioniert während des veralteten Zeitraums weiterhin. Um diese Warnung auszuschalten, schalten Sie **Gerätetyp** unten auf einen der unterstützten Typen um (Waschmaschine, Trockner, Wasch-Trockner-Kombi, Geschirrspüler, Heißluftfritteuse, Brotbackautomat oder Pumpe) oder auf **Andere (Erweitert)**, wenn Ihr Gerät keinem der unterstützten Typen entspricht. **Andere (Erweiterte)** liefert absichtlich generische Standardeinstellungen, die nicht auf eine bestimmte Appliance abgestimmt sind, sodass Sie Schwellenwerte, Zeitüberschreitungen und passende Parameter selbst konfigurieren müssen; Alle Ihre bestehenden Einstellungen bleiben beim Wechsel erhalten.", + "phase_other_device_types": "Weitere Gerätetypen:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Einen Zyklus aufteilen (Lücken finden)", + "merge": "Zyklen zusammenführen (Fragmente verbinden)", + "delete": "Zyklus(e) löschen" + } + }, + "split_mode": { + "options": { + "auto": "Leerlauflücken automatisch erkennen", + "manual": "Manuelle Zeitstempel" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Wartung: Daten erneut verarbeiten und optimieren", + "clear_debug_data": "Debug-Daten löschen (Speicherplatz freigeben)", + "wipe_history": "ALLE Daten für dieses Gerät löschen (unwiderruflich)", + "export_import": "JSON mit Einstellungen exportieren/importieren (Kopieren/Einfügen)" + } + }, + "export_import_mode": { + "options": { + "export": "Alle Daten exportieren", + "import": "Daten importieren/zusammenführen" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Alte Zyklen automatisch zuordnen", + "select_cycle_to_label": "Zyklus einzeln zuordnen", + "select_cycle_to_delete": "Zyklus löschen", + "interactive_editor": "Interaktiver Editor zum Zusammenführen/Teilen", + "trim_cycle": "Zyklusdaten zuschneiden" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Neues Profil erstellen", + "edit_profile": "Profil bearbeiten/umbenennen", + "delete_profile": "Profil löschen", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Verlauf bereinigen – grafisch darstellen und löschen", + "assign_phases": "Phasenzeiträume zuweisen" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Neue Phase erstellen", + "edit_custom_phase": "Phase bearbeiten", + "delete_custom_phase": "Phase löschen" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Phasenzeitraum hinzufügen", + "edit_range": "Phasenzeitraum bearbeiten", + "delete_range": "Phasenzeitraum löschen", + "clear_ranges": "Alle Zeiträume löschen", + "auto_detect_ranges": "Phasen automatisch erkennen", + "save_ranges": "Speichern und zurückgeben" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Status aktualisieren", + "stop_recording": "Aufnahme beenden (Speichern und verarbeiten)", + "start_recording": "Neue Aufnahme starten", + "process_recording": "Letzte Aufnahme verarbeiten (Zuschneiden und Speichern)", + "discard_recording": "Letzte Aufnahme verwerfen" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Neues Profil erstellen", + "existing_profile": "Zum vorhandenen Profil hinzufügen", + "discard": "Aufnahme verwerfen" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bestätigen – richtige Erkennung", + "correct": "Korrigieren – Programm/Dauer auswählen", + "ignore": "Ignorieren – falsch positiver/verrauschter Zyklus", + "delete": "Löschen – Aus dem Verlauf entfernen" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Phasen benennen und anwenden", + "cancel": "Änderungen verwerfen" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Startpunkt festlegen", + "set_end": "Endpunkt festlegen", + "reset": "Auf volle Dauer zurücksetzen", + "apply": "Zuschnitt anwenden", + "cancel": "Stornieren" + } + }, + "device_type": { + "options": { + "washing_machine": "Waschmaschine", + "dryer": "Trockner", + "washer_dryer": "Waschmaschine-Trockner-Kombination", + "dishwasher": "Spülmaschine", + "coffee_machine": "Kaffeemaschine (veraltet)", + "ev": "Elektrofahrzeug (veraltet)", + "air_fryer": "Luftfritteuse", + "heat_pump": "Wärmepumpe (veraltet)", + "bread_maker": "Brotbackautomat", + "pump": "Pumpe / Sumpfpumpe", + "oven": "Ofen (veraltet)", + "other": "Andere (Fortgeschrittene)" + } + } + }, + "services": { + "label_cycle": { + "name": "Zyklus zuordnen", + "description": "Einen Zyklus einem Profil zuordnen oder die Zuordnung aufheben.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät für die Zuordnung." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die ID des zuzuordnenden Zyklus." + }, + "profile_name": { + "name": "Profilname", + "description": "Der Name eines vorhandenen Profils (Profile erstellen im Menü „Profile verwalten“). Feld leer lassen, um die Zuordnung zu entfernen." + } + } + }, + "create_profile": { + "name": "Profil erstellen", + "description": "Ein neues Profil erstellen (leer oder basierend auf einem Referenzzyklus).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät." + }, + "profile_name": { + "name": "Profilname", + "description": "Name für das neue Profil (z. B. „Heavy Duty“, „Delicates“)." + }, + "reference_cycle_id": { + "name": "Referenzzyklus-ID", + "description": "Optionale Zyklus-ID, auf der dieses Profil basieren soll." + } + } + }, + "delete_profile": { + "name": "Profil löschen", + "description": "Profil löschen und optional alle Zuordnungen von Zyklen aufheben, die es verwenden.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät." + }, + "profile_name": { + "name": "Profilname", + "description": "Das zu löschende Profil." + }, + "unlabel_cycles": { + "name": "Zyklen-Zuordnung aufheben", + "description": "Zuordnung aller Zyklen aufheben, die dieses Profil verwenden." + } + } + }, + "auto_label_cycles": { + "name": "Alte Zyklen automatisch zuordnen", + "description": "Nicht zugeordnete Zyklen rückwirkend mithilfe des Profilabgleichs zuordnen.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät." + }, + "confidence_threshold": { + "name": "Vertrauensschwellwert", + "description": "Mindestübereinstimmungskonfidenz (0,50–0,95) zum Anwenden von Zuordnungen." + } + } + }, + "export_config": { + "name": "Konfiguration exportieren", + "description": "Profile, Zyklen und Einstellungen dieses Geräts in eine JSON-Datei exportieren (pro Gerät).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das zu exportierende WashData-Gerät." + }, + "path": { + "name": "Pfad", + "description": "Optionaler absoluter Dateipfad zum Schreiben (standardmäßig /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Konfiguration importieren", + "description": "Profile, Zyklen und Einstellungen für dieses Gerät aus einer JSON-Exportdatei importieren (Einstellungen werden möglicherweise überschrieben).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät, in das importiert werden soll." + }, + "path": { + "name": "Pfad", + "description": "Absoluter Pfad zur zu importierenden JSON-Exportdatei." + } + } + }, + "submit_cycle_feedback": { + "name": "Zyklus-Feedback bestätigen", + "description": "Ein automatisch erkanntes Programm nach einem abgeschlossenen Zyklus bestätigen oder korrigieren. Geben Sie entweder „entry_id“ (erweitert) oder „device_id“ (empfohlen) an.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät (empfohlen anstelle von „entry_id“)." + }, + "entry_id": { + "name": "Eintrags-ID", + "description": "Die Konfigurationseintrags-ID für das Gerät (Alternative zu device_id)." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die in der Feedback-Benachrichtigung/in den Protokollen angezeigte Zyklus-ID." + }, + "user_confirmed": { + "name": "Das erkannte Programm bestätigen", + "description": "Auf „true“ setzen, wenn das erkannte Programm korrekt ist." + }, + "corrected_profile": { + "name": "Korrigiertes Profil", + "description": "Wenn dies nicht bestätigt wird, den korrekten Profil-/Programmnamen angeben." + }, + "corrected_duration": { + "name": "Korrigierte Dauer (Sekunden)", + "description": "Optionale korrigierte Dauer in Sekunden." + }, + "notes": { + "name": "Notizen", + "description": "Optionale Hinweise zu diesem Zyklus." + } + } + }, + "record_start": { + "name": "Zyklusstart aufzeichnen", + "description": "Manuelle Aufzeichnung eines sauberen Zyklus starten (umgeht alle Matching-Funktionen).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät zum Aufnehmen." + } + } + }, + "record_stop": { + "name": "Aufnahmezyklusstopp", + "description": "Die manuelle Aufnahme beenden.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät stoppt die Aufnahme." + } + } + }, + "trim_cycle": { + "name": "Zyklus zuschneiden", + "description": "Die Leistungsdaten eines vergangenen Zyklus auf ein bestimmtes Zeitfenster zuschneiden.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die ID des zuzuschneidenden Zyklus." + }, + "trim_start_s": { + "name": "Anfang zuschneiden (Sekunden)", + "description": "Daten ab diesem Zeitpunkt (in Sekunden) behalten. Standard 0." + }, + "trim_end_s": { + "name": "Ende zuschneiden (Sekunden)", + "description": "Daten bis zu diesem Zeitpunkt behalten. Standard = Zyklusende." + } + } + }, + "pause_cycle": { + "name": "Zyklus anhalten", + "description": "Unterbrechen Sie den aktiven Zyklus für ein WashData-Gerät.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät, das angehalten werden soll." + } + } + }, + "resume_cycle": { + "name": "Zyklus fortsetzen", + "description": "Setzen Sie einen angehaltenen Zyklus für ein WashData-Gerät fort.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät, das fortgesetzt werden soll." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Läuft" + }, + "match_ambiguity": { + "name": "Übereinstimmungsmehrdeutigkeit" + } + }, + "sensor": { + "washer_state": { + "name": "Zustand", + "state": { + "off": "Aus", + "idle": "Leerlauf", + "starting": "Beginnt", + "running": "Läuft", + "paused": "Angehalten", + "user_paused": "Vom Benutzer angehalten", + "ending": "Ende", + "finished": "Fertig", + "anti_wrinkle": "Knitterschutz", + "delay_wait": "Warten auf den Start", + "interrupted": "Unterbrochen", + "force_stopped": "Erzwungen beendet", + "rinse": "Spülen", + "unknown": "Unbekannt", + "clean": "Sauber" + } + }, + "washer_program": { + "name": "Programm" + }, + "time_remaining": { + "name": "Verbleibende Zeit" + }, + "total_duration": { + "name": "Gesamtdauer" + }, + "cycle_progress": { + "name": "Fortschritt" + }, + "current_power": { + "name": "Aktuelle Leistung" + }, + "elapsed_time": { + "name": "Verstrichene Zeit" + }, + "debug_info": { + "name": "Debug-Info" + }, + "match_confidence": { + "name": "Match-Vertrauen" + }, + "top_candidates": { + "name": "Top-Kandidaten", + "state": { + "none": "Keiner" + } + }, + "current_phase": { + "name": "Aktuelle Phase" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Anzahl", + "unit_of_measurement": "Zyklen" + }, + "suggestions": { + "name": "Empfohlene Einstellungen" + }, + "pump_runs_today": { + "name": "Pumpenläufe (letzte 24 Stunden)" + }, + "cycle_count": { + "name": "Zyklusanzahl" + } + }, + "select": { + "program_select": { + "name": "Zyklusprogramm", + "state": { + "auto_detect": "Automatische Erkennung" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Zyklusende erzwingen" + }, + "pause_cycle": { + "name": "Zyklus anhalten" + }, + "resume_cycle": { + "name": "Zyklus fortsetzen" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Eine gültige Geräte-ID ist erforderlich." + }, + "cycle_id_required": { + "message": "Eine gültige cycle_id ist erforderlich." + }, + "profile_name_required": { + "message": "Ein gültiger Profilname ist erforderlich." + }, + "device_not_found": { + "message": "Gerät nicht gefunden." + }, + "no_config_entry": { + "message": "Für das Gerät wurde kein Konfigurationseintrag gefunden." + }, + "integration_not_loaded": { + "message": "Die Integration wurde für dieses Gerät nicht geladen." + }, + "cycle_not_found_or_no_power": { + "message": "Zyklus nicht gefunden oder hat keine Leistungsdaten." + }, + "trim_failed_empty_window": { + "message": "Zuschneiden fehlgeschlagen – Zyklus nicht gefunden, keine Leistungsdaten oder resultierendes Fenster ist leer." + }, + "trim_invalid_range": { + "message": "trim_end_s muss größer als trim_start_s sein." + } + } +} diff --git a/custom_components/ha_washdata/translations/el.json b/custom_components/ha_washdata/translations/el.json new file mode 100644 index 0000000..5f6e17f --- /dev/null +++ b/custom_components/ha_washdata/translations/el.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Ρύθμιση WashData", + "description": "Διαμορφώστε το πλυντήριο ή άλλη συσκευή σας.\n\nΑπαιτείται αισθητήρας ισχύος.\n\n**Στη συνέχεια, θα ερωτηθείτε αν θέλετε να δημιουργήσετε το πρώτο σας προφίλ.**", + "data": { + "name": "Όνομα συσκευής", + "device_type": "Τύπος συσκευής", + "power_sensor": "Αισθητήρας ισχύος", + "min_power": "Ελάχιστο όριο ισχύος (W)" + }, + "data_description": { + "name": "Ένα φιλικό όνομα για αυτή τη συσκευή (π.χ. «Πλυντήριο ρούχων», «Πλυντήριο πιάτων»).", + "device_type": "Τι είδους συσκευή είναι αυτή; Βοηθά στην προσαρμογή της ανίχνευσης και της επισήμανσης.", + "power_sensor": "Η οντότητα αισθητήρα που αναφέρει σε πραγματικό χρόνο την κατανάλωση ισχύος (σε watt) από το έξυπνο βύσμα σας.", + "min_power": "Μετρήσεις ισχύος πάνω από αυτό το όριο (σε watt) υποδεικνύουν ότι η συσκευή λειτουργεί. Ξεκινήστε με 2 W για τις περισσότερες συσκευές." + } + }, + "first_profile": { + "title": "Δημιουργία πρώτου προφίλ", + "description": "Ένας **Κύκλος** είναι μια πλήρης λειτουργία της συσκευής σας (π.χ. ένα φορτίο ρούχων). Ένα **Προφίλ** ομαδοποιεί αυτούς τους κύκλους ανά τύπο (π.χ. «Βαμβάκι», «Γρήγορο πλύσιμο»). Η δημιουργία ενός προφίλ τώρα βοηθά την ενσωμάτωση να εκτιμά αμέσως τη διάρκεια.\n\nΜπορείτε να επιλέξετε χειροκίνητα αυτό το προφίλ από τα χειριστήρια της συσκευής, εάν δεν εντοπιστεί αυτόματα.\n\n**Αφήστε το «Όνομα προφίλ» κενό για να παραλείψετε αυτό το βήμα.**", + "data": { + "profile_name": "Όνομα προφίλ (Προαιρετικό)", + "manual_duration": "Εκτιμώμενη διάρκεια (λεπτά)" + } + } + }, + "error": { + "cannot_connect": "Αποτυχία σύνδεσης", + "invalid_auth": "Μη έγκυρη αυθεντικοποίηση", + "unknown": "Απρόσμενο σφάλμα" + }, + "abort": { + "already_configured": "Η συσκευή έχει ήδη διαμορφωθεί" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Εξέταση ανατροφοδοτήσεων μάθησης", + "settings": "Ρυθμίσεις", + "notifications": "Ειδοποιήσεις", + "manage_cycles": "Διαχείριση κύκλων", + "manage_profiles": "Διαχείριση προφίλ", + "manage_phase_catalog": "Διαχείριση καταλόγου φάσεων", + "record_cycle": "Εγγραφή κύκλου (χειροκίνητα)", + "diagnostics": "Διαγνωστικά και συντήρηση" + } + }, + "apply_suggestions": { + "title": "Αντιγραφή προτεινόμενων τιμών", + "description": "Αυτό θα αντιγράψει τις τρέχουσες προτεινόμενες τιμές στις αποθηκευμένες ρυθμίσεις σας. Αυτή η ενέργεια γίνεται μία φορά και δεν ενεργοποιεί αυτόματη αντικατάσταση.\n\nΠροτεινόμενες τιμές για αντιγραφή:\n{suggested}", + "data": { + "confirm": "Επιβεβαίωση αντιγραφής" + } + }, + "apply_suggestions_confirm": { + "title": "Έλεγχος προτεινόμενων αλλαγών", + "description": "Το WashData βρέθηκε **{pending_count}** προτεινόμενες τιμές για εφαρμογή.\n\nΑλλαγές:\n{changes}\n\n✅ Επιλέξτε το παρακάτω πλαίσιο και κάντε κλικ στην **Υποβολή** για να εφαρμόσετε και να αποθηκεύσετε αυτές τις αλλαγές αμέσως.\n❌ Αφήστε το μη επιλεγμένο και κάντε κλικ στο **Υποβολή** για ακύρωση και επιστροφή.", + "data": { + "confirm_apply_suggestions": "Εφαρμόστε και αποθηκεύστε αυτές τις αλλαγές" + } + }, + "settings": { + "title": "Ρυθμίσεις", + "description": "{deprecation_warning}Ρυθμίστε τα κατώφλια ανίχνευσης, τη μάθηση και τη σύνθετη συμπεριφορά. Οι προεπιλογές λειτουργούν καλά για τις περισσότερες ρυθμίσεις.\n\nΔιαθέσιμες προτεινόμενες ρυθμίσεις: {suggestions_count}.\nΑν αυτό είναι πάνω από 0, ανοίξτε τις Σύνθετες ρυθμίσεις και χρησιμοποιήστε «Εφαρμογή προτεινόμενων τιμών» για να εξετάσετε τις συστάσεις.", + "data": { + "apply_suggestions": "Εφαρμογή προτεινόμενων τιμών", + "device_type": "Τύπος συσκευής", + "power_sensor": "Οντότητα αισθητήρα ισχύος", + "min_power": "Ελάχιστη ισχύς (W)", + "off_delay": "Καθυστέρηση λήξης κύκλου (s)", + "notify_actions": "Ενέργειες ειδοποίησης", + "notify_people": "Καθυστέρηση παράδοσης έως ότου αυτά τα άτομα είναι σπίτι", + "notify_only_when_home": "Καθυστέρηση ειδοποιήσεων μέχρι το επιλεγμένο άτομο να είναι σπίτι", + "notify_fire_events": "Ενεργοποίηση συμβάντων αυτοματισμού", + "notify_start_services": "Έναρξη κύκλου - Στόχοι ειδοποιήσεων", + "notify_finish_services": "Τερματισμός κύκλου - Στόχοι ειδοποιήσεων", + "notify_live_services": "Ζωντανή πρόοδος - Στόχοι ειδοποιήσεων", + "notify_before_end_minutes": "Ειδοποίηση πριν την ολοκλήρωση (λεπτά πριν το τέλος)", + "notify_title": "Τίτλος ειδοποίησης", + "notify_icon": "Εικονίδιο ειδοποίησης", + "notify_start_message": "Μορφή μηνύματος εκκίνησης", + "notify_finish_message": "Μορφή μηνύματος ολοκλήρωσης", + "notify_pre_complete_message": "Μορφή μηνύματος προ-ολοκλήρωσης", + "show_advanced": "Επεξεργασία σύνθετων ρυθμίσεων (επόμενο βήμα)" + }, + "data_description": { + "apply_suggestions": "Επιλέξτε αυτό το πλαίσιο για να αντιγράψετε τιμές που προτείνει ο αλγόριθμος μάθησης στη φόρμα. Ελέγξτε πριν αποθηκεύσετε.\n\nΠροτεινόμενα (από μάθηση):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Επιλέξτε τον τύπο συσκευής (Πλυντήριο ρούχων, Στεγνωτήριο, Πλυντήριο πιάτων, Καφετιέρα, EV). Αυτή η ετικέτα αποθηκεύεται με κάθε κύκλο για καλύτερη οργάνωση.", + "power_sensor": "Η οντότητα αισθητήρα που αναφέρει ισχύ σε πραγματικό χρόνο (σε watt). Μπορείτε να το αλλάξετε ανά πάσα στιγμή χωρίς να χάσετε ιστορικά δεδομένα-χρήσιμο αν αντικαταστήσετε ένα έξυπνο βύσμα.", + "min_power": "Μετρήσεις ισχύος πάνω από αυτό το όριο (σε watt) υποδεικνύουν ότι η συσκευή λειτουργεί. Ξεκινήστε με 2 W για τις περισσότερες συσκευές.", + "off_delay": "Δευτερόλεπτα που η εξομαλυμένη ισχύς πρέπει να παραμείνει κάτω από το κατώφλι διακοπής για να σηματοδοτηθεί η ολοκλήρωση. Προεπιλογή: 120 s.", + "notify_actions": "Προαιρετική ακολουθία ενεργειών Home Assistant για ειδοποιήσεις (scripts, notify, TTS, κ.λπ.). Αν οριστεί, οι ενέργειες εκτελούνται πριν από την εναλλακτική υπηρεσία ειδοποίησης.", + "notify_people": "Οντότητες ατόμων για έλεγχο παρουσίας. Όταν είναι ενεργοποιημένο, η παράδοση ειδοποιήσεων καθυστερεί έως ότου τουλάχιστον ένα επιλεγμένο άτομο επιστρέψει σπίτι.", + "notify_only_when_home": "Αν είναι ενεργοποιημένο, καθυστερεί τις ειδοποιήσεις έως ότου τουλάχιστον ένα επιλεγμένο άτομο βρίσκεται σπίτι.", + "notify_fire_events": "Αν είναι ενεργοποιημένο, ενεργοποιεί συμβάντα Home Assistant για έναρξη και λήξη κύκλου (ha_washdata_cycle_started και ha_washdata_cycle_ended) για αυτοματισμούς.", + "notify_start_services": "Μία ή περισσότερες υπηρεσίες ειδοποιούν για να ειδοποιήσουν όταν ξεκινά ένας κύκλος. Αφήστε κενό για να παραλείψετε τις ειδοποιήσεις έναρξης.", + "notify_finish_services": "Μία ή περισσότερες υπηρεσίες ειδοποιούν για να ειδοποιήσουν όταν ένας κύκλος τελειώνει ή πλησιάζει στην ολοκλήρωση. Αφήστε κενό για να παραλείψετε τις ειδοποιήσεις λήξης.", + "notify_live_services": "Μία ή περισσότερες υπηρεσίες ειδοποίησης για λήψη ζωντανών ενημερώσεων προόδου ενώ εκτελείται ο κύκλος (μόνο συνοδευτική εφαρμογή για κινητά). Αφήστε κενό για να παραλείψετε ζωντανές ενημερώσεις.", + "notify_before_end_minutes": "Λεπτά πριν το εκτιμώμενο τέλος του κύκλου για ενεργοποίηση ειδοποίησης. Ορίστε 0 για απενεργοποίηση. Προεπιλογή: 0.", + "notify_title": "Προσαρμοσμένος τίτλος για ειδοποιήσεις. Υποστηρίζει το placeholder {device}.", + "notify_icon": "Εικονίδιο για ειδοποιήσεις (π.χ. mdi:washing-machine). Αφήστε κενό για αποστολή χωρίς εικονίδιο.", + "notify_start_message": "Μήνυμα που αποστέλλεται όταν ξεκινά ένας κύκλος. Υποστηρίζει το placeholder {device}.", + "notify_finish_message": "Μήνυμα που αποστέλλεται όταν ολοκληρωθεί ένας κύκλος. Υποστηρίζει τα placeholders {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Το κείμενο των επαναλαμβανόμενων ζωντανών ενημερώσεων προόδου ενώ εκτελείται ο κύκλος. Υποστηρίζει σύμβολα κράτησης θέσης {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Ειδοποιήσεις", + "description": "Διαμορφώστε τα κανάλια ειδοποιήσεων, τα πρότυπα και τις προαιρετικές ζωντανές ενημερώσεις προόδου για κινητά.", + "data": { + "notify_actions": "Ενέργειες ειδοποίησης", + "notify_people": "Καθυστέρηση παράδοσης έως ότου αυτά τα άτομα είναι σπίτι", + "notify_only_when_home": "Καθυστέρηση ειδοποιήσεων μέχρι το επιλεγμένο άτομο να είναι σπίτι", + "notify_fire_events": "Ενεργοποίηση συμβάντων αυτοματισμού", + "notify_start_services": "Έναρξη κύκλου - Στόχοι ειδοποιήσεων", + "notify_finish_services": "Τερματισμός κύκλου - Στόχοι ειδοποιήσεων", + "notify_live_services": "Ζωντανή πρόοδος - Στόχοι ειδοποιήσεων", + "notify_before_end_minutes": "Ειδοποίηση πριν την ολοκλήρωση (λεπτά πριν το τέλος)", + "notify_live_interval_seconds": "Διάστημα ζωντανής ενημέρωσης (δευτερόλεπτα)", + "notify_live_overrun_percent": "Περιθώριο υπέρβασης ζωντανής ενημέρωσης (%)", + "notify_live_chronometer": "Χρονόμετρο αντίστροφης μέτρησης", + "notify_title": "Τίτλος ειδοποίησης", + "notify_icon": "Εικονίδιο ειδοποίησης", + "notify_start_message": "Μορφή μηνύματος εκκίνησης", + "notify_finish_message": "Μορφή μηνύματος ολοκλήρωσης", + "notify_pre_complete_message": "Μορφή μηνύματος προ-ολοκλήρωσης", + "energy_price_entity": "Τιμή ενέργειας - Οντότητα (προαιρετικό)", + "energy_price_static": "Τιμή ενέργειας - Στατική τιμή (προαιρετικό)", + "go_back": "Επιστροφή χωρίς αποθήκευση", + "notify_reminder_message": "Μορφή μηνύματος υπενθύμισης", + "notify_timeout_seconds": "Αυτόματη απόρριψη μετά (δευτερόλεπτα)", + "notify_channel": "Κανάλι κοινοποίησης (status/live/reminer)", + "notify_finish_channel": "Τελειωμένο κανάλι ειδοποιήσεων" + }, + "data_description": { + "go_back": "Επιλέξτε αυτό και πατήστε Υποβολή για να απορρίψετε τυχόν παραπάνω αλλαγές και να επιστρέψετε στο κύριο μενού.", + "notify_actions": "Προαιρετική ακολουθία ενεργειών Home Assistant για ειδοποιήσεις (scripts, notify, TTS, κ.λπ.). Αν οριστεί, οι ενέργειες εκτελούνται πριν από την εναλλακτική υπηρεσία ειδοποίησης.", + "notify_people": "Οντότητες ατόμων για έλεγχο παρουσίας. Όταν είναι ενεργοποιημένο, η παράδοση ειδοποιήσεων καθυστερεί έως ότου τουλάχιστον ένα επιλεγμένο άτομο επιστρέψει σπίτι.", + "notify_only_when_home": "Αν είναι ενεργοποιημένο, καθυστερεί τις ειδοποιήσεις έως ότου τουλάχιστον ένα επιλεγμένο άτομο βρίσκεται σπίτι.", + "notify_fire_events": "Αν είναι ενεργοποιημένο, ενεργοποιεί συμβάντα Home Assistant για έναρξη και λήξη κύκλου (ha_washdata_cycle_started και ha_washdata_cycle_ended) για αυτοματισμούς.", + "notify_start_services": "Μία ή περισσότερες υπηρεσίες ειδοποίησης για ειδοποίηση όταν ξεκινά ένας κύκλος (π.χ. notify.mobile_app_my_phone). Αφήστε κενό για να παραλείψετε τις ειδοποιήσεις έναρξης.", + "notify_finish_services": "Μία ή περισσότερες υπηρεσίες ειδοποιούν για να ειδοποιήσουν όταν ένας κύκλος τελειώνει ή πλησιάζει στην ολοκλήρωση. Αφήστε κενό για να παραλείψετε τις ειδοποιήσεις λήξης.", + "notify_live_services": "Μία ή περισσότερες υπηρεσίες ειδοποίησης για λήψη ζωντανών ενημερώσεων προόδου ενώ εκτελείται ο κύκλος (μόνο συνοδευτική εφαρμογή για κινητά). Αφήστε κενό για να παραλείψετε ζωντανές ενημερώσεις.", + "notify_before_end_minutes": "Λεπτά πριν το εκτιμώμενο τέλος του κύκλου για ενεργοποίηση ειδοποίησης. Ορίστε 0 για απενεργοποίηση. Προεπιλογή: 0.", + "notify_live_interval_seconds": "Πόσο συχνά αποστέλλονται ενημερώσεις προόδου κατά τη λειτουργία. Χαμηλότερες τιμές είναι πιο αποκριτικές αλλά καταναλώνουν περισσότερες ειδοποιήσεις. Εφαρμόζεται ελάχιστο όριο 30 δευτερολέπτων - τιμές κάτω των 30 s στρογγυλοποιούνται αυτόματα στα 30 s.", + "notify_live_overrun_percent": "Περιθώριο ασφαλείας πάνω από τον εκτιμώμενο αριθμό ενημερώσεων για μεγάλους/υπερχειλισμένους κύκλους (π.χ. το 20% επιτρέπει 1,2x εκτιμώμενες ενημερώσεις).", + "notify_live_chronometer": "Όταν είναι ενεργοποιημένη, κάθε ζωντανή ενημέρωση περιλαμβάνει μια αντίστροφη μέτρηση χρονομέτρου για τον εκτιμώμενο χρόνο τερματισμού. Το χρονόμετρο ειδοποιήσεων μειώνεται αυτόματα στη συσκευή μεταξύ των ενημερώσεων (μόνο συνοδευτική εφαρμογή Android).", + "notify_title": "Προσαρμοσμένος τίτλος για ειδοποιήσεις. Υποστηρίζει το placeholder {device}.", + "notify_icon": "Εικονίδιο για ειδοποιήσεις (π.χ. mdi:washing-machine). Αφήστε κενό για αποστολή χωρίς εικονίδιο.", + "notify_start_message": "Μήνυμα που αποστέλλεται όταν ξεκινά ένας κύκλος. Υποστηρίζει το placeholder {device}.", + "notify_finish_message": "Μήνυμα που αποστέλλεται όταν ολοκληρωθεί ένας κύκλος. Υποστηρίζει τα placeholders {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Επιλέξτε μια αριθμητική οντότητα που έχει την τρέχουσα τιμή ηλεκτρικής ενέργειας (π.χ. sensor.electricity_price, input_number.energy_rate). Όταν οριστεί, ενεργοποιεί το σύμβολο κράτησης θέσης {cost}. Υπερισχύει της στατικής τιμής εάν έχουν διαμορφωθεί και οι δύο.", + "energy_price_static": "Σταθερή τιμή ηλεκτρικής ενέργειας ανά kWh. Όταν οριστεί, ενεργοποιεί το σύμβολο κράτησης θέσης {cost}. Αγνοείται εάν μια οντότητα έχει διαμορφωθεί παραπάνω. Προσθέστε το σύμβολο του νομίσματός σας στο πρότυπο μηνύματος, π.χ. {cost} €.", + "notify_reminder_message": "Το κείμενο της μιας υπενθύμισης έστειλε τον ρυθμισμένο αριθμό λεπτών πριν το εκτιμώμενο τέλος. Διακριτικό από τις επαναλαμβανόμενες ζωντανές ενημερώσεις παραπάνω. Υποστηρίζει {device}, {minutes}, {program} κατόχους θέσης.", + "notify_timeout_seconds": "Αυτόματη απόρριψη ειδοποιήσεων μετά από αυτά τα πολλά δευτερόλεπτα (προωθήθηκε ως η συνοδευτική εφαρμογή 'timeout'). Ρυθμίστε το 0 να τους κρατήσει μέχρι να απολυθούν. Προκαθορισμένο: 0.", + "notify_channel": "Android κανάλι ειδοποίησης συνοδών εφαρμογών για κατάσταση, ζωντανή πρόοδο και ειδοποιήσεις υπενθύμισης. Ο ήχος και η σημασία ενός καναλιού ρυθμίζονται στην εφαρμογή συνοδών την πρώτη φορά που χρησιμοποιείται το όνομα καναλιού - WashData ορίζει μόνο το όνομα καναλιού. Αφήστε το κενό για την προκαθορισμένη εφαρμογή.", + "notify_finish_channel": "Ξεχωριστό κανάλι Android για το τελικό σήμα συναγερμού (που χρησιμοποιείται επίσης από την υπενθύμιση και την επίμονη ειδοποίηση αναμονής πλυντηρίου) ώστε να μπορεί να έχει τον δικό του ήχο. Αφήστε το κενό για να επαναχρησιμοποιηθεί το παραπάνω κανάλι.", + "notify_pre_complete_message": "Το κείμενο των επαναλαμβανόμενων ζωντανών ενημερώσεων προόδου ενώ εκτελείται ο κύκλος. Υποστηρίζει σύμβολα κράτησης θέσης {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Σύνθετες ρυθμίσεις", + "description": "Ρυθμίστε τα κατώφλια ανίχνευσης, τη μάθηση και τη σύνθετη συμπεριφορά. Οι προεπιλογές λειτουργούν καλά για τις περισσότερες ρυθμίσεις.", + "sections": { + "suggestions_section": { + "name": "Προτεινόμενες ρυθμίσεις", + "description": "{suggestions_count} μαθημένες προτάσεις διαθέσιμες. Επιλέξτε Εφαρμογή προτεινόμενων τιμών για να εξετάσετε τις προτεινόμενες αλλαγές πριν από την αποθήκευση.", + "data": { + "apply_suggestions": "Εφαρμογή προτεινόμενων τιμών" + }, + "data_description": { + "apply_suggestions": "Επιλέξτε αυτό το πλαίσιο για να ανοίξετε ένα βήμα ελέγχου για προτεινόμενες τιμές από τον αλγόριθμο μάθησης. Τίποτα δεν αποθηκεύεται αυτόματα.\n\nΠροτεινόμενα (από μάθηση):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Ανίχνευση και κατώφλια ισχύος", + "description": "Πότε η συσκευή θεωρείται ΕΝΕΡΓΗ ή ΑΝΕΝΕΡΓΗ, ενεργειακές πύλες και χρονισμός για μεταβάσεις κατάστασης.", + "data": { + "start_duration_threshold": "Διάρκεια αναπήδησης εκκίνησης (s)", + "start_energy_threshold": "Ενεργειακή πύλη εκκίνησης (Wh)", + "completion_min_seconds": "Ελάχιστος χρόνος ολοκλήρωσης (δευτερόλεπτα)", + "start_threshold_w": "Κατώφλι εκκίνησης (W)", + "stop_threshold_w": "Κατώφλι διακοπής (W)", + "end_energy_threshold": "Ενεργειακή πύλη λήξης (Wh)", + "running_dead_zone": "Νεκρή ζώνη κατά τη λειτουργία (δευτερόλεπτα)", + "end_repeat_count": "Αριθμός επαναλήψεων συνθήκης λήξης", + "min_off_gap": "Ελάχιστο κενό μεταξύ κύκλων (s)", + "sampling_interval": "Διάστημα δειγματοληψίας (s)" + }, + "data_description": { + "start_duration_threshold": "Αγνοεί σύντομες αιχμές ισχύος μικρότερες από αυτή τη διάρκεια (δευτερόλεπτα). Φιλτράρει αιχμές εκκίνησης (π.χ. 60 W για 2 s) πριν την πραγματική έναρξη κύκλου. Προεπιλογή: 5 s.", + "start_energy_threshold": "Ο κύκλος πρέπει να συσσωρεύσει τουλάχιστον αυτή την ενέργεια (Wh) κατά τη φάση ΕΚΚΙΝΗΣΗΣ για επιβεβαίωση. Προεπιλογή: 0,005 Wh.", + "completion_min_seconds": "Κύκλοι μικρότεροι από αυτό θα επισημαίνονται ως «διακοπτόμενοι» ακόμα κι αν τελειώσουν φυσικά. Προεπιλογή: 600 s.", + "start_threshold_w": "Η ισχύς πρέπει να ανέβει ΠΑΝΩ από αυτό το κατώφλι για να ΞΕΚΙΝΗΣΕΙ ένας κύκλος. Ρυθμίστε υψηλότερα από την ισχύ αναμονής. Προεπιλογή: min_power + 1 W.", + "stop_threshold_w": "Η ισχύς πρέπει να πέσει ΚΑΤΩ από αυτό το κατώφλι για να ΤΕΛΕΙΩΣΕΙ ένας κύκλος. Ρυθμίστε λίγο πάνω από το μηδέν για να αποφύγετε ψευδείς λήξεις από θόρυβο. Προεπιλογή: 0,6 × min_power.", + "end_energy_threshold": "Ο κύκλος τελειώνει μόνο αν η ενέργεια στο τελευταίο παράθυρο απενεργοποίησης είναι κάτω από αυτή την τιμή (Wh). Αποτρέπει ψευδείς λήξεις αν η ισχύς κυμαίνεται κοντά στο μηδέν. Προεπιλογή: 0,05 Wh.", + "running_dead_zone": "Μετά την έναρξη του κύκλου, αγνοεί πτώσεις ισχύος για τόσα δευτερόλεπτα για να αποτραπεί ψευδής ανίχνευση λήξης κατά την αρχική φάση εκκίνησης. Ορίστε 0 για απενεργοποίηση. Προεπιλογή: 0 s.", + "end_repeat_count": "Πόσες φορές η συνθήκη λήξης (ισχύς κάτω από το κατώφλι για off_delay) πρέπει να πληρούται διαδοχικά πριν τελειώσει ο κύκλος. Υψηλότερες τιμές αποτρέπουν ψευδείς λήξεις κατά τις παύσεις. Προεπιλογή: 1.", + "min_off_gap": "Αν ένας κύκλος ξεκινήσει μέσα σε τόσα δευτερόλεπτα από το τέλος του προηγούμενου, θα ΣΥΓΧΩΝΕΥΤΟΥΝ σε έναν κύκλο.", + "sampling_interval": "Ελάχιστο διάστημα δειγματοληψίας σε δευτερόλεπτα. Οι ενημερώσεις που είναι ταχύτερες από αυτό το ποσοστό θα αγνοηθούν. Προεπιλογή: 30s (2s για πλυντήρια ρούχων, πλυντήρια-στεγνωτήρια και πλυντήρια πιάτων)." + } + }, + "matching_section": { + "name": "Αντιστοίχιση προφίλ και μάθηση", + "description": "Πόσο επιθετικά το WashData αντιστοιχίζει ενεργούς κύκλους με μαθημένα προφίλ και πότε να ζητά ανατροφοδότηση.", + "data": { + "profile_match_min_duration_ratio": "Ελάχιστη αναλογία διάρκειας αντιστοίχισης προφίλ (0,1-1,0)", + "profile_match_interval": "Διάστημα αντιστοίχισης προφίλ (δευτερόλεπτα)", + "profile_match_threshold": "Κατώφλι αντιστοίχισης προφίλ", + "profile_unmatch_threshold": "Κατώφλι αποτυχίας αντιστοίχισης προφίλ", + "auto_label_confidence": "Εμπιστοσύνη αυτόματης επισήμανσης (0-1)", + "learning_confidence": "Εμπιστοσύνη αιτήματος ανατροφοδότησης (0-1)", + "suppress_feedback_notifications": "Καταργήστε τις ειδοποιήσεις σχολίων", + "duration_tolerance": "Ανοχή διάρκειας (0-0,5)", + "smoothing_window": "Παράθυρο εξομάλυνσης (δείγματα)", + "profile_duration_tolerance": "Ανοχή διάρκειας αντιστοίχισης προφίλ (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Ελάχιστη αναλογία διάρκειας για αντιστοίχιση προφίλ. Ο τρέχων κύκλος πρέπει να είναι τουλάχιστον αυτό το κλάσμα της διάρκειας του προφίλ για να αντιστοιχιστεί. Χαμηλότερο = νωρίτερη αντιστοίχιση. Προεπιλογή: 0,50 (50%).", + "profile_match_interval": "Πόσο συχνά (σε δευτερόλεπτα) να γίνεται αντιστοίχιση προφίλ κατά τη διάρκεια ενός κύκλου. Χαμηλότερες τιμές παρέχουν ταχύτερη ανίχνευση αλλά χρησιμοποιούν περισσότερη CPU. Προεπιλογή: 300 s (5 λεπτά).", + "profile_match_threshold": "Ελάχιστη βαθμολογία ομοιότητας DTW (0,0–1,0) για να θεωρηθεί ένα προφίλ ταίριασμα. Υψηλότερο = αυστηρότερη αντιστοίχιση, λιγότερα ψευδώς θετικά. Προεπιλογή: 0,4.", + "profile_unmatch_threshold": "Βαθμολογία ομοιότητας DTW (0,0–1,0) κάτω από την οποία απορρίπτεται ένα προηγουμένως αντιστοιχισμένο προφίλ. Πρέπει να είναι χαμηλότερο από το κατώφλι αντιστοίχισης για να αποφεύγεται τρεμόπαιγμα. Προεπιλογή: 0,35.", + "auto_label_confidence": "Αυτόματη επισήμανση κύκλων σε ή πάνω από αυτό το επίπεδο εμπιστοσύνης κατά την ολοκλήρωση (0,0–1,0).", + "learning_confidence": "Ελάχιστη εμπιστοσύνη για αίτημα επαλήθευσης από χρήστη μέσω συμβάντος (0,0–1,0).", + "suppress_feedback_notifications": "Όταν είναι ενεργοποιημένο, το WashData θα εξακολουθεί να παρακολουθεί και να ζητά σχόλια εσωτερικά, αλλά δεν θα εμφανίζει επίμονες ειδοποιήσεις που σας ζητούν να επιβεβαιώσετε τους κύκλους. Χρήσιμο όταν οι κύκλοι εντοπίζονται πάντα σωστά και βρίσκετε τα μηνύματα που αποσπούν την προσοχή.", + "duration_tolerance": "Ανοχή για εκτιμήσεις υπολειπόμενου χρόνου κατά τη λειτουργία (0,0–0,5 αντιστοιχεί σε 0–50%).", + "smoothing_window": "Αριθμός πρόσφατων μετρήσεων ισχύος για τον κινούμενο μέσο όρο εξομάλυνσης.", + "profile_duration_tolerance": "Ανοχή που χρησιμοποιείται από την αντιστοίχιση προφίλ για τη συνολική διακύμανση διάρκειας (0,0–0,5). Προεπιλεγμένη συμπεριφορά: ±25%." + } + }, + "timing_section": { + "name": "Χρονισμός, συντήρηση και αποσφαλμάτωση", + "description": "Watchdog, επαναφορά προόδου, αυτόματη συντήρηση και έκθεση οντοτήτων αποσφαλμάτωσης.", + "data": { + "watchdog_interval": "Διάστημα watchdog (δευτερόλεπτα)", + "no_update_active_timeout": "Χρονικό όριο απουσίας ενημέρωσης (s)", + "progress_reset_delay": "Καθυστέρηση επαναφοράς προόδου (δευτερόλεπτα)", + "auto_maintenance": "Ενεργοποίηση αυτόματης συντήρησης", + "expose_debug_entities": "Εμφάνιση οντοτήτων εντοπισμού σφαλμάτων", + "save_debug_traces": "Αποθήκευση ιχνών εντοπισμού σφαλμάτων" + }, + "data_description": { + "watchdog_interval": "Δευτερόλεπτα μεταξύ ελέγχων watchdog κατά τη λειτουργία. Προεπιλογή: 5 s. ΠΡΟΣΟΧΗ: Βεβαιωθείτε ότι αυτό είναι ΥΨΗΛΟΤΕΡΟ από το διάστημα ενημέρωσης του αισθητήρα για να αποφύγετε ψευδείς διακοπές (π.χ. αν ο αισθητήρας ενημερώνεται κάθε 60 s, χρησιμοποιήστε 65 s+).", + "no_update_active_timeout": "Για αισθητήρες που δημοσιεύουν μόνο σε αλλαγή: τερματίζει αναγκαστικά έναν ΕΝΕΡΓΟ κύκλο μόνο αν δεν υπάρξουν ενημερώσεις για τόσα δευτερόλεπτα. Η ολοκλήρωση σε χαμηλή ισχύ εξακολουθεί να χρησιμοποιεί την καθυστέρηση απενεργοποίησης.", + "progress_reset_delay": "Αφού ολοκληρωθεί ένας κύκλος (100%), αναμένει τόσα δευτερόλεπτα αδράνειας πριν επαναφέρει την πρόοδο στο 0%. Προεπιλογή: 300 s.", + "auto_maintenance": "Ενεργοποίηση αυτόματης συντήρησης (επισκευή δειγμάτων, συγχώνευση θραυσμάτων).", + "expose_debug_entities": "Εμφάνιση σύνθετων αισθητήρων (Εμπιστοσύνη, Φάση, Αμφισημία) για εντοπισμό σφαλμάτων.", + "save_debug_traces": "Αποθήκευση λεπτομερών δεδομένων κατάταξης και ίχνους ισχύος στο ιστορικό (αυξάνει τη χρήση αποθηκευτικού χώρου)." + } + }, + "anti_wrinkle_section": { + "name": "Ασπίδα κατά του τσαλακώματος (Στεγνωτήρια)", + "description": "Αγνοήστε τις περιστροφές του κάδου μετά το τέλος του κύκλου για να αποτρέψετε κύκλους-φαντάσματα. Μόνο για στεγνωτήριο / πλυντήριο-στεγνωτήριο.", + "data": { + "anti_wrinkle_enabled": "Προστασία κατά ρυτίδων (μόνο στεγνωτήριο/πλυντήριο-στεγνωτήριο)", + "anti_wrinkle_max_power": "Μέγιστη ισχύς κατά ρυτίδων (W) (μόνο στεγνωτήριο/πλυντήριο-στεγνωτήριο)", + "anti_wrinkle_max_duration": "Μέγιστη διάρκεια κατά ρυτίδων (s) (μόνο στεγνωτήριο/πλυντήριο-στεγνωτήριο)", + "anti_wrinkle_exit_power": "Ισχύς εξόδου κατά ρυτίδων (W) (μόνο στεγνωτήριο/πλυντήριο-στεγνωτήριο)" + }, + "data_description": { + "anti_wrinkle_enabled": "Αγνοεί σύντομες περιστροφές τυμπάνου χαμηλής ισχύος μετά το τέλος ενός κύκλου για να αποτρέψει ψευδείς κύκλους (μόνο στεγνωτήριο/πλυντήριο-στεγνωτήριο).", + "anti_wrinkle_max_power": "Εκρήξεις ισχύος σε ή κάτω από αυτό το επίπεδο αντιμετωπίζονται ως περιστροφές κατά ρυτίδων αντί για νέους κύκλους. Ισχύει μόνο όταν είναι ενεργοποιημένη η Προστασία κατά ρυτίδων.", + "anti_wrinkle_max_duration": "Μέγιστη διάρκεια έκρηξης για περιστροφή κατά ρυτίδων. Ισχύει μόνο όταν είναι ενεργοποιημένη η Προστασία κατά ρυτίδων.", + "anti_wrinkle_exit_power": "Αν η ισχύς πέσει κάτω από αυτό το επίπεδο, έξοδος από τη λειτουργία κατά ρυτίδων αμέσως (πραγματική απενεργοποίηση). Ισχύει μόνο όταν είναι ενεργοποιημένη η Προστασία κατά ρυτίδων." + } + }, + "delay_start_section": { + "name": "Ανίχνευση καθυστερημένης έναρξης", + "description": "Αντιμετωπίστε τη συνεχή αναμονή χαμηλής ισχύος ως 'Αναμονή για έναρξη' μέχρι να ξεκινήσει πραγματικά ο κύκλος.", + "data": { + "delay_start_detect_enabled": "Ενεργοποιήστε τον εντοπισμό καθυστερημένης έναρξης", + "delay_confirm_seconds": "Καθυστερημένη έναρξη: Χρόνος επιβεβαίωσης αναμονής (s)", + "delay_timeout_hours": "Καθυστερημένη έναρξη: Μέγιστος χρόνος αναμονής (ω)" + }, + "data_description": { + "delay_start_detect_enabled": "Όταν είναι ενεργοποιημένη, μια σύντομη ακίδα αποστράγγισης χαμηλής κατανάλωσης που ακολουθείται από παρατεταμένη ισχύ αναμονής εντοπίζεται ως καθυστερημένη εκκίνηση. Ο αισθητήρας κατάστασης δείχνει «Αναμονή για έναρξη» κατά τη διάρκεια της καθυστέρησης. Δεν παρακολουθείται ο χρόνος ή η πρόοδος του προγράμματος μέχρι να ξεκινήσει πραγματικά ο κύκλος. Απαιτεί το start_threshold_w να οριστεί πάνω από την ισχύ της ακίδας αποστράγγισης.", + "delay_confirm_seconds": "Η ισχύς πρέπει να παραμένει μεταξύ Κατωφλίου Διακοπής (W) και Κατωφλίου Έναρξης (W) για τόσα δευτερόλεπτα πριν το WashData περάσει στην κατάσταση 'Αναμονή για έναρξη'. Φιλτράρει σύντομες αιχμές από την πλοήγηση στο μενού. Προεπιλογή: 60 s.", + "delay_timeout_hours": "Χρονικό όριο ασφαλείας: εάν το μηχάνημα εξακολουθεί να βρίσκεται σε «Αναμονή για εκκίνηση» μετά από τόσες ώρες, το WashData επανέρχεται στο Off. Προεπιλογή: 8 ώρες." + } + }, + "external_triggers_section": { + "name": "Εξωτερικοί ενεργοποιητές, πόρτα και παύση", + "description": "Προαιρετικός εξωτερικός αισθητήρας ενεργοποίησης τέλους, αισθητήρας πόρτας για ανίχνευση παύσης/καθαρού και διακόπτης αποκοπής ρεύματος κατά την παύση.", + "data": { + "external_end_trigger_enabled": "Ενεργοποίηση εξωτερικής ενεργοποίησης λήξης κύκλου", + "external_end_trigger": "Εξωτερικός αισθητήρας λήξης κύκλου", + "external_end_trigger_inverted": "Αντιστροφή λογικής ενεργοποίησης (ενεργοποίηση στο OFF)", + "door_sensor_entity": "Αισθητήρας πόρτας", + "pause_cuts_power": "Κόψτε την τροφοδοσία κατά την παύση", + "switch_entity": "Εναλλαγή οντότητας (για παύση διακοπής ρεύματος)", + "notify_unload_delay_minutes": "Καθυστέρηση ειδοποίησης αναμονής πλυντηρίου (λεπτά)" + }, + "data_description": { + "external_end_trigger_enabled": "Ενεργοποίηση παρακολούθησης εξωτερικού δυαδικού αισθητήρα για ενεργοποίηση ολοκλήρωσης κύκλου.", + "external_end_trigger": "Επιλέξτε μια οντότητα δυαδικού αισθητήρα. Όταν αυτός ο αισθητήρας ενεργοποιηθεί, ο τρέχων κύκλος θα τελειώσει με κατάσταση «ολοκληρώθηκε».", + "external_end_trigger_inverted": "Κατά προεπιλογή, η ενεργοποίηση γίνεται όταν ο αισθητήρας ενεργοποιηθεί (ON). Επιλέξτε αυτό το πλαίσιο για ενεργοποίηση όταν ο αισθητήρας απενεργοποιηθεί (OFF).", + "door_sensor_entity": "Προαιρετικός δυαδικός αισθητήρας για την πόρτα του μηχανήματος (on = ανοιχτό, off = κλειστό). Όταν η πόρτα ανοίγει κατά τη διάρκεια ενός ενεργού κύκλου, το WashData θα επιβεβαιώσει τον κύκλο ως σκόπιμη παύση. Μετά το τέλος του κύκλου, εάν η πόρτα είναι ακόμα κλειστή, η κατάσταση αλλάζει σε «Καθαρός» μέχρι να ανοίξει η πόρτα. Σημείωση: το κλείσιμο της πόρτας ΔΕΝ επαναλαμβάνει αυτόματα έναν κύκλο σε παύση - η συνέχιση πρέπει να ενεργοποιηθεί χειροκίνητα μέσω του κουμπιού ή του σέρβις του Κύκλου Συνέχισης.", + "pause_cuts_power": "Όταν κάνετε παύση μέσω του κουμπιού ή της υπηρεσίας Pause Cycle, απενεργοποιήστε επίσης τη διαμορφωμένη οντότητα διακόπτη. Εφαρμόζεται μόνο εάν έχει διαμορφωθεί μια οντότητα διακόπτη για αυτήν τη συσκευή.", + "switch_entity": "Η οντότητα διακόπτη για εναλλαγή κατά την παύση ή τη συνέχιση. Απαιτείται όταν είναι ενεργοποιημένη η \"Διακοπή ρεύματος κατά την παύση\".", + "notify_unload_delay_minutes": "Στείλτε μια ειδοποίηση μέσω του καναλιού ειδοποίησης τερματισμού αφού τελειώσει ο κύκλος και η πόρτα δεν έχει ανοίξει για τόσα πολλά λεπτά (απαιτείται αισθητήρας πόρτας). Ορίστε στο 0 για απενεργοποίηση. Προεπιλογή: 60 λεπτά." + } + }, + "pump_section": { + "name": "Παρακολούθηση αντλίας", + "description": "Μόνο για τύπο συσκευής αντλίας. Εκκινεί ένα συμβάν αν ένας κύκλος αντλίας διαρκεί περισσότερο από αυτή τη διάρκεια.", + "data": { + "pump_stuck_duration": "Όριο (α) ειδοποίησης κολλημένης αντλίας (μόνο για αντλία)" + }, + "data_description": { + "pump_stuck_duration": "Εάν ένας κύκλος αντλίας εξακολουθεί να εκτελείται μετά από τόσα πολλά δευτερόλεπτα, ένα συμβάν ha_washdata_pump_stuck ενεργοποιείται μία φορά. Χρησιμοποιήστε το για να εντοπίσετε εμπλοκή κινητήρα (η τυπική λειτουργία αντλίας φρεατίου είναι μικρότερη από 60 δευτερόλεπτα). Προεπιλογή: 1800 s (30 λεπτά). Μόνο τύπος συσκευής αντλίας." + } + }, + "device_link_section": { + "name": "Δεσμός συσκευής", + "description": "Προαιρετικά συνδέστε αυτή τη συσκευή WashData με μια υπάρχουσα συσκευή Home Assistant (για παράδειγμα το έξυπνο βύσμα ή η ίδια η συσκευή). Η συσκευή WashData εμφανίζεται στη συνέχεια ως \" Connected μέσω\" αυτής της συσκευής. Αφήστε το κενό για να διατηρήσετε τα WashData ως αυτόνομη συσκευή.", + "data": { + "linked_device": "Συνδεμένη συσκευή" + }, + "data_description": { + "linked_device": "Επιλέξτε τη συσκευή για τη σύνδεση WashData. Αυτό προσθέτει μια αναφορά 'Connected via' στο μητρώο συσκευών· το WashData διατηρεί τη δική του κάρτα συσκευής και οντότητες. Καθαρίστε την επιλογή για να αφαιρέσετε το σύνδεσμο." + } + } + } + }, + "diagnostics": { + "title": "Διαγνωστικά και συντήρηση", + "description": "Εκτελέστε ενέργειες συντήρησης, όπως συγχώνευση κατακερματισμένων κύκλων ή μετεγκατάσταση αποθηκευμένων δεδομένων.\n\n**Χρήση αποθηκευτικού χώρου**\n\n- Μέγεθος αρχείου: {file_size_kb} KB\n- Κύκλοι: {cycle_count}\n- Προφίλ: {profile_count}\n- Ίχνη εντοπισμού σφαλμάτων: {debug_count}", + "menu_options": { + "reprocess_history": "Συντήρηση: επανεπεξεργασία και βελτιστοποίηση δεδομένων", + "clear_debug_data": "Εκκαθάριση δεδομένων εντοπισμού σφαλμάτων (ελευθέρωση χώρου)", + "wipe_history": "Διαγραφή ΟΛΩΝ των δεδομένων για αυτή τη συσκευή (μη αναστρέψιμο)", + "export_import": "Εξαγωγή/εισαγωγή JSON με ρυθμίσεις (αντιγραφή/επικόλληση)", + "menu_back": "← Πίσω" + } + }, + "clear_debug_data": { + "title": "Εκκαθάριση δεδομένων εντοπισμού σφαλμάτων", + "description": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλα τα αποθηκευμένα ίχνη εντοπισμού σφαλμάτων; Αυτό θα ελευθερώσει χώρο αλλά θα αφαιρέσει λεπτομερείς πληροφορίες κατάταξης από παλαιότερους κύκλους." + }, + "manage_cycles": { + "title": "Διαχείριση κύκλων", + "description": "Πρόσφατοι κύκλοι:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Αυτόματη επισήμανση παλαιών κύκλων", + "select_cycle_to_label": "Επισήμανση συγκεκριμένου κύκλου", + "select_cycle_to_delete": "Διαγραφή κύκλου", + "interactive_editor": "Διαδραστικός επεξεργαστής συγχώνευσης/διαχωρισμού", + "trim_cycle_select": "Περικοπή δεδομένων κύκλου", + "menu_back": "← Πίσω" + } + }, + "manage_cycles_empty": { + "title": "Διαχείριση κύκλων", + "description": "Δεν έχουν καταγραφεί ακόμη κύκλοι.", + "menu_options": { + "auto_label_cycles": "Αυτόματη επισήμανση παλαιών κύκλων", + "menu_back": "← Πίσω" + } + }, + "manage_profiles": { + "title": "Διαχείριση προφίλ", + "description": "Σύνοψη προφίλ:\n{profile_summary}", + "menu_options": { + "create_profile": "Δημιουργία νέου προφίλ", + "edit_profile": "Επεξεργασία/μετονομασία προφίλ", + "delete_profile_select": "Διαγραφή προφίλ", + "profile_stats": "Στατιστικά προφίλ", + "cleanup_profile": "Εκκαθάριση ιστορικού - Γράφημα και διαγραφή", + "assign_profile_phases_select": "Εκχώρηση εύρους φάσεων", + "menu_back": "← Πίσω" + } + }, + "manage_profiles_empty": { + "title": "Διαχείριση προφίλ", + "description": "Δεν έχουν δημιουργηθεί ακόμα προφίλ.", + "menu_options": { + "create_profile": "Δημιουργία νέου προφίλ", + "menu_back": "← Πίσω" + } + }, + "manage_phase_catalog": { + "title": "Διαχείριση καταλόγου φάσεων", + "description": "Κατάλογος φάσεων (προεπιλογές για αυτή τη συσκευή + προσαρμοσμένες φάσεις):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Δημιουργία νέας φάσης", + "phase_catalog_edit_select": "Επεξεργασία φάσης", + "phase_catalog_delete": "Διαγραφή φάσης", + "menu_back": "← Πίσω" + } + }, + "phase_catalog_create": { + "title": "Δημιουργία προσαρμοσμένης φάσης", + "description": "Δημιουργήστε ένα προσαρμοσμένο όνομα και περιγραφή φάσης και επιλέξτε τον τύπο συσκευής στον οποίο εφαρμόζεται.", + "data": { + "target_device_type": "Τύπος συσκευής-στόχου", + "phase_name": "Όνομα φάσης", + "phase_description": "Περιγραφή φάσης" + } + }, + "phase_catalog_edit_select": { + "title": "Επεξεργασία προσαρμοσμένης φάσης", + "description": "Επιλέξτε μια προσαρμοσμένη φάση για επεξεργασία.", + "data": { + "phase_name": "Προσαρμοσμένη φάση" + } + }, + "phase_catalog_edit": { + "title": "Επεξεργασία προσαρμοσμένης φάσης", + "description": "Επεξεργασία φάσης: {phase_name}", + "data": { + "phase_name": "Όνομα φάσης", + "phase_description": "Περιγραφή φάσης" + } + }, + "phase_catalog_delete": { + "title": "Διαγραφή προσαρμοσμένης φάσης", + "description": "Επιλέξτε μια προσαρμοσμένη φάση για διαγραφή. Τυχόν εκχωρημένα εύρη που χρησιμοποιούν αυτή τη φάση θα αφαιρεθούν.", + "data": { + "phase_name": "Προσαρμοσμένη φάση" + } + }, + "assign_profile_phases": { + "title": "Εκχώρηση εύρους φάσεων", + "description": "Προεπισκόπηση φάσης για προφίλ: **{profile_name}**\n\n{timeline_svg}\n\n**Τρέχοντα εύρη:**\n{current_ranges}\n\nΧρησιμοποιήστε τις παρακάτω ενέργειες για να προσθέσετε, επεξεργαστείτε, διαγράψετε ή αποθηκεύσετε εύρη.", + "menu_options": { + "assign_profile_phases_add": "Προσθήκη εύρους φάσης", + "assign_profile_phases_edit_select": "Επεξεργασία εύρους φάσης", + "assign_profile_phases_delete": "Διαγραφή εύρους φάσης", + "phase_ranges_clear": "Εκκαθάριση όλων των εύρων", + "assign_profile_phases_auto_detect": "Αυτόματη ανίχνευση φάσεων", + "phase_ranges_save": "Αποθήκευση και επιστροφή", + "menu_back": "← Πίσω" + } + }, + "assign_profile_phases_add": { + "title": "Προσθήκη εύρους φάσης", + "description": "Προσθέστε ένα εύρος φάσης στο τρέχον πρόχειρο.", + "data": { + "phase_name": "Φάση", + "start_min": "Λεπτό έναρξης", + "end_min": "Λεπτό λήξης" + } + }, + "assign_profile_phases_edit_select": { + "title": "Επεξεργασία εύρους φάσης", + "description": "Επιλέξτε ένα εύρος για επεξεργασία.", + "data": { + "range_index": "Εύρος φάσης" + } + }, + "assign_profile_phases_edit": { + "title": "Επεξεργασία εύρους φάσης", + "description": "Ενημερώστε το επιλεγμένο εύρος φάσης.", + "data": { + "phase_name": "Φάση", + "start_min": "Λεπτό έναρξης", + "end_min": "Λεπτό λήξης" + } + }, + "assign_profile_phases_delete": { + "title": "Διαγραφή εύρους φάσης", + "description": "Επιλέξτε ένα εύρος για αφαίρεση από το πρόχειρο.", + "data": { + "range_index": "Εύρος φάσης" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Αυτόματα ανιχνευμένες φάσεις", + "description": "Προφίλ: **{profile_name}**\n\n{timeline_svg}\n\n**Εντοπίστηκαν αυτόματα {detected_count} φάσεις.** Επιλέξτε μια ενέργεια παρακάτω.", + "data": { + "action": "Ενέργεια" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Ονομασία ανιχνευμένων φάσεων", + "description": "Προφίλ: **{profile_name}**\n\n**Εντοπίστηκαν {detected_count} φάσεις:**\n{ranges_summary}\n\nΔώστε ένα όνομα σε κάθε φάση." + }, + "assign_profile_phases_select": { + "title": "Εκχώρηση εύρους φάσεων", + "description": "Επιλέξτε ένα προφίλ για να διαμορφώσετε τα εύρη φάσεων.", + "data": { + "profile": "Προφίλ" + } + }, + "profile_stats": { + "title": "Στατιστικά προφίλ", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Δημιουργία νέου προφίλ", + "description": "Δημιουργήστε ένα νέο προφίλ χειροκίνητα ή από έναν παλαιότερο κύκλο.\n\nΠαραδείγματα ονομάτων προφίλ: «Ευαίσθητα», «Βαρύ πλύσιμο», «Γρήγορο πλύσιμο»", + "data": { + "profile_name": "Όνομα προφίλ", + "reference_cycle": "Κύκλος αναφοράς (προαιρετικό)", + "manual_duration": "Χειροκίνητη διάρκεια (λεπτά)" + } + }, + "edit_profile": { + "title": "Επεξεργασία προφίλ", + "description": "Επιλέξτε ένα προφίλ για μετονομασία", + "data": { + "profile": "Προφίλ" + } + }, + "rename_profile": { + "title": "Επεξεργασία στοιχείων προφίλ", + "description": "Τρέχον προφίλ: {current_name}", + "data": { + "new_name": "Όνομα προφίλ", + "manual_duration": "Χειροκίνητη διάρκεια (λεπτά)" + } + }, + "delete_profile_select": { + "title": "Διαγραφή προφίλ", + "description": "Επιλέξτε ένα προφίλ για διαγραφή", + "data": { + "profile": "Προφίλ" + } + }, + "delete_profile_confirm": { + "title": "Επιβεβαίωση διαγραφής προφίλ", + "description": "⚠️ Αυτό θα διαγράψει οριστικά το προφίλ: {profile_name}", + "data": { + "unlabel_cycles": "Αφαίρεση ετικέτας από κύκλους που χρησιμοποιούν αυτό το προφίλ" + } + }, + "auto_label_cycles": { + "title": "Αυτόματη επισήμανση παλαιών κύκλων", + "description": "Βρέθηκαν {total_count} συνολικά κύκλοι. Προφίλ: {profiles}", + "data": { + "confidence_threshold": "Ελάχιστη εμπιστοσύνη (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Επιλογή κύκλου για επισήμανση", + "description": "✓ = ολοκληρώθηκε, ⚠ = συνεχίστηκε, ✗ = διακόπηκε", + "data": { + "cycle_id": "Κύκλος" + } + }, + "select_cycle_to_delete": { + "title": "Διαγραφή κύκλου", + "description": "⚠️ Αυτό θα διαγράψει οριστικά τον επιλεγμένο κύκλο", + "data": { + "cycle_id": "Κύκλος προς διαγραφή" + } + }, + "label_cycle": { + "title": "Επισήμανση κύκλου", + "description": "{cycle_info}", + "data": { + "profile_name": "Προφίλ", + "new_profile_name": "Νέο όνομα προφίλ (αν δημιουργείται)" + } + }, + "post_process": { + "title": "Επεξεργασία ιστορικού (Συγχώνευση/Διαχωρισμός)", + "description": "Εκκαθαρίστε το ιστορικό κύκλων συγχωνεύοντας κατακερματισμένες εκτελέσεις και χωρίζοντας λανθασμένα συγχωνευμένους κύκλους μέσα σε ένα χρονικό παράθυρο. Εισαγάγετε τον αριθμό των ωρών για να κοιτάξετε πίσω (χρησιμοποιήστε 999999 για όλους).", + "data": { + "time_range": "Παράθυρο αναδρομής (ώρες)", + "gap_seconds": "Κενό συγχώνευσης/διαχωρισμού (δευτερόλεπτα)" + }, + "data_description": { + "time_range": "Αριθμός ωρών για σάρωση για συγχώνευση κατακερματισμένων κύκλων. Χρησιμοποιήστε το 999999 για να επεξεργαστείτε όλα τα ιστορικά δεδομένα.", + "gap_seconds": "Κατώφλι για απόφαση διαχωρισμού ή συγχώνευσης. Κενά ΜΙΚΡΟΤΕΡΑ από αυτό συγχωνεύονται. Κενά ΜΕΓΑΛΥΤΕΡΑ από αυτό διαχωρίζονται. Μειώστε αυτό για να εξαναγκάσετε διαχωρισμό σε κύκλο που συγχωνεύτηκε λανθασμένα." + } + }, + "cleanup_profile": { + "title": "Εκκαθάριση ιστορικού - Επιλογή προφίλ", + "description": "Επιλέξτε ένα προφίλ για οπτικοποίηση. Αυτό θα δημιουργήσει ένα γράφημα που δείχνει όλους τους παλαιότερους κύκλους για αυτό το προφίλ, βοηθώντας στον εντοπισμό ακραίων τιμών.", + "data": { + "profile": "Προφίλ" + } + }, + "cleanup_select": { + "title": "Εκκαθάριση ιστορικού - Γράφημα και διαγραφή", + "description": "![Γράφημα]({graph_url})\n\n**Οπτικοποίηση {profile_name}**\n\nΤο γράφημα δείχνει μεμονωμένους κύκλους ως έγχρωμες γραμμές. Εντοπίστε ακραίες τιμές (γραμμές μακριά από την ομάδα) και αντιστοιχίστε το χρώμα τους με τη λίστα παρακάτω για να τις διαγράψετε.\n\n**Επιλέξτε κύκλους για ΟΡΙΣΤΙΚΗ διαγραφή:**", + "data": { + "cycles_to_delete": "Κύκλοι προς διαγραφή (αντιστοιχίστε χρώματα)" + } + }, + "interactive_editor": { + "title": "Διαδραστικός επεξεργαστής συγχώνευσης/διαχωρισμού", + "description": "Επιλέξτε μια χειροκίνητη ενέργεια για εκτέλεση στο ιστορικό των κύκλων σας.", + "menu_options": { + "editor_split": "Διαχωρισμός κύκλου (εύρεση κενών)", + "editor_merge": "Συγχώνευση κύκλων (ένωση θραυσμάτων)", + "editor_delete": "Διαγραφή κύκλου/κύκλων", + "menu_back": "← Πίσω" + } + }, + "editor_select": { + "title": "Επιλογή κύκλων", + "description": "Επιλέξτε τους κύκλους προς επεξεργασία.\n\n{info_text}", + "data": { + "selected_cycles": "Κύκλοι" + } + }, + "editor_configure": { + "title": "Διαμόρφωση και εφαρμογή", + "description": "{preview_md}", + "data": { + "confirm_action": "Ενέργεια", + "merged_profile": "Προκύπτον προφίλ", + "new_profile_name": "Νέο όνομα προφίλ", + "confirm_commit": "Ναι, επιβεβαιώνω αυτή την ενέργεια", + "segment_0_profile": "Προφίλ τμήματος 1", + "segment_1_profile": "Προφίλ τμήματος 2", + "segment_2_profile": "Προφίλ τμήματος 3", + "segment_3_profile": "Προφίλ τμήματος 4", + "segment_4_profile": "Προφίλ τμήματος 5", + "segment_5_profile": "Προφίλ τμήματος 6", + "segment_6_profile": "Προφίλ τμήματος 7", + "segment_7_profile": "Προφίλ τμήματος 8", + "segment_8_profile": "Προφίλ τμήματος 9", + "segment_9_profile": "Προφίλ τμήματος 10" + } + }, + "editor_split_params": { + "title": "Διαμόρφωση διαχωρισμού", + "description": "Ρυθμίστε την ευαισθησία για τον διαχωρισμό αυτού του κύκλου. Οποιοδήποτε κενό αδράνειας μεγαλύτερο από αυτό θα προκαλέσει διαχωρισμό.", + "data": { + "split_mode": "Μέθοδος διαχωρισμού" + } + }, + "editor_split_auto_params": { + "title": "Ρύθμιση αυτόματου διαχωρισμού", + "description": "Προσαρμόστε την ευαισθησία για τον διαχωρισμό αυτού του κύκλου. Οποιοδήποτε κενό αδράνειας μεγαλύτερο από αυτό θα προκαλέσει διαχωρισμό.", + "data": { + "min_gap_seconds": "Όριο κενού διαχωρισμού (δευτερόλεπτα)" + } + }, + "editor_split_manual_params": { + "title": "Χειροκίνητες χρονικές σημάνσεις διαχωρισμού", + "description": "{preview_md}Παράθυρο κύκλου: **{cycle_start_wallclock} → {cycle_end_wallclock}** στις {cycle_date}.\n\nΕισαγάγετε μία ή περισσότερες χρονικές σημάνσεις διαχωρισμού (`HH:MM` ή `HH:MM:SS`), μία ανά γραμμή. Ο κύκλος θα κοπεί σε κάθε χρονική σήμανση — N χρονικές σημάνσεις παράγουν N+1 τμήματα.", + "data": { + "split_timestamps": "Χρονικές σημάνσεις διαχωρισμού" + } + }, + "reprocess_history": { + "title": "Συντήρηση: επανεπεξεργασία και βελτιστοποίηση δεδομένων", + "description": "Εκτελεί βαθύ καθαρισμό ιστορικών δεδομένων:\n1. Περικόπτει μετρήσεις μηδενικής ισχύος (σιωπή).\n2. Διορθώνει χρονισμό/διάρκεια κύκλου.\n3. Επανυπολογίζει όλα τα στατιστικά μοντέλα (φακέλους).\n\n**Εκτελέστε αυτό για διόρθωση προβλημάτων δεδομένων ή εφαρμογή νέων αλγορίθμων.**" + }, + "wipe_history": { + "title": "Εκκαθάριση ιστορικού (μόνο για δοκιμές)", + "description": "⚠️ Αυτό θα διαγράψει ΟΛΟΥΣ τους αποθηκευμένους κύκλους και προφίλ για αυτή τη συσκευή. Αυτό δεν μπορεί να αναιρεθεί!" + }, + "export_import": { + "title": "Εξαγωγή/Εισαγωγή JSON", + "description": "Αντιγραφή/επικόλληση κύκλων, προφίλ ΚΑΙ ρυθμίσεων για αυτή τη συσκευή. Κάντε εξαγωγή παρακάτω ή επικολλήστε JSON για εισαγωγή.", + "data": { + "mode": "Ενέργεια", + "json_payload": "Πλήρης διαμόρφωση JSON" + }, + "data_description": { + "mode": "Επιλέξτε Εξαγωγή για αντιγραφή τρεχόντων δεδομένων και ρυθμίσεων, ή Εισαγωγή για επικόλληση δεδομένων από άλλη συσκευή WashData.", + "json_payload": "Για Εξαγωγή: αντιγράψτε αυτό το JSON (περιλαμβάνει κύκλους, προφίλ και όλες τις ρυθμίσεις). Για Εισαγωγή: επικολλήστε JSON που εξάχθηκε από WashData." + } + }, + "record_cycle": { + "title": "Εγγραφή κύκλου", + "description": "Εγγράψτε χειροκίνητα έναν κύκλο για να δημιουργήσετε ένα καθαρό προφίλ χωρίς παρεμβολές.\n\nΚατάσταση: **{status}**\nΔιάρκεια: {duration} s\nΔείγματα: {samples}", + "menu_options": { + "record_refresh": "Ανανέωση κατάστασης", + "record_stop": "Διακοπή εγγραφής (αποθήκευση και επεξεργασία)", + "record_start": "Έναρξη νέας εγγραφής", + "record_process": "Επεξεργασία τελευταίας εγγραφής (περικοπή και αποθήκευση)", + "record_discard": "Απόρριψη τελευταίας εγγραφής", + "menu_back": "← Πίσω" + } + }, + "record_process": { + "title": "Επεξεργασία εγγραφής", + "description": "![Γράφημα]({graph_url})\n\n**Το γράφημα δείχνει την ακατέργαστη εγγραφή.** Μπλε = Διατήρηση, Κόκκινο = Προτεινόμενη περικοπή.\n*Το γράφημα είναι στατικό και δεν ενημερώνεται με αλλαγές φόρμας.*\n\nΗ εγγραφή σταμάτησε. Οι περικοπές ευθυγραμμίζονται με τον εντοπισμένο ρυθμό δειγματοληψίας (~{sampling_rate} s).\n\nΑκατέργαστη διάρκεια: {duration} s\nΔείγματα: {samples}", + "data": { + "head_trim": "Περικοπή αρχής (δευτερόλεπτα)", + "tail_trim": "Περικοπή τέλους (δευτερόλεπτα)", + "save_mode": "Προορισμός αποθήκευσης", + "profile_name": "Όνομα προφίλ" + } + }, + "learning_feedbacks": { + "title": "Ανατροφοδοτήσεις μάθησης", + "description": "Εκκρεμείς ανατροφοδοτήσεις: {count}\n\n{pending_table}\n\nΕπιλέξτε ένα αίτημα αναθεώρησης κύκλου για επεξεργασία.", + "menu_options": { + "learning_feedbacks_pick": "Εξέταση εκκρεμούς ανατροφοδότησης", + "learning_feedbacks_dismiss_all": "Απόρριψη όλων των εκκρεμών ανατροφοδοτήσεων", + "menu_back": "← Πίσω" + } + }, + "learning_feedbacks_pick": { + "title": "Επιλέξτε ανατροφοδότηση για εξέταση", + "description": "Επιλέξτε ένα αίτημα εξέτασης κύκλου για να ανοίξει.", + "data": { + "selected_feedback": "Επιλέξτε κύκλο για εξέταση" + } + }, + "learning_feedbacks_empty": { + "title": "Ανατροφοδοτήσεις μάθησης", + "description": "Δεν βρέθηκαν εκκρεμή αιτήματα ανατροφοδότησης.", + "menu_options": { + "menu_back": "← Πίσω" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Απόρριψη όλων των εκκρεμών ανατροφοδοτήσεων", + "description": "Πρόκειται να απορρίψετε **{count}** εκκρεμή αιτήματα ανατροφοδότησης. Οι απορριφθέντες κύκλοι παραμένουν στο ιστορικό σας, αλλά δεν θα συμβάλλουν με νέο σήμα μάθησης. Αυτή η ενέργεια δεν μπορεί να αναιρεθεί.\n\n✅ Επιλέξτε το παρακάτω πλαίσιο και κάντε κλικ στην **Υποβολή** για να απορρίψετε τα πάντα.\n❌ Αφήστε το μη επιλεγμένο και κάντε κλικ στην **Υποβολή** για ακύρωση και επιστροφή.", + "data": { + "confirm_dismiss_all": "Επιβεβαίωση: απόρριψη όλων των εκκρεμών αιτημάτων ανατροφοδότησης" + } + }, + "resolve_feedback": { + "title": "Επίλυση ανατροφοδότησης", + "description": "{comparison_img}{candidates_table}\n**Εντοπισμένο προφίλ**: {detected_profile} ({confidence_pct}%)\n**Εκτιμώμενη διάρκεια**: {est_duration_min} λεπτά\n**Πραγματική διάρκεια**: {act_duration_min} λεπτά\n\n__Επιλέξτε μια ενέργεια παρακάτω:__", + "data": { + "action": "Τι θέλετε να κάνετε;", + "corrected_profile": "Σωστό πρόγραμμα (αν διορθώνετε)", + "corrected_duration": "Σωστή διάρκεια σε δευτερόλεπτα (αν διορθώνετε)" + } + }, + "trim_cycle_select": { + "title": "Περικοπή κύκλου - Επιλογή κύκλου", + "description": "Επιλέξτε έναν κύκλο για περικοπή. ✓ = ολοκληρώθηκε, ⚠ = συνεχίστηκε, ✗ = διακόπηκε", + "data": { + "cycle_id": "Κύκλος" + } + }, + "trim_cycle": { + "title": "Περικοπή κύκλου", + "description": "Τρέχον παράθυρο: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Ενέργεια" + } + }, + "trim_cycle_start": { + "title": "Ορισμός σημείου έναρξης περικοπής", + "description": "Παράθυρο κύκλου: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nΤρέχουσα έναρξη: **{current_wallclock}** (+{current_offset_min} λεπτά από την έναρξη του κύκλου)\n\nΕπιλέξτε μια νέα ώρα έναρξης χρησιμοποιώντας τον επιλογέα ρολογιού παρακάτω.", + "data": { + "trim_start_time": "Νέα ώρα έναρξης", + "trim_start_min": "Νέα εκκίνηση (λεπτά από την έναρξη του κύκλου)" + } + }, + "trim_cycle_end": { + "title": "Ορισμός σημείου λήξης περικοπής", + "description": "Παράθυρο κύκλου: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nΤρέχον τέλος: **{current_wallclock}** (+{current_offset_min} λεπτά από την έναρξη του κύκλου)\n\nΕπιλέξτε μια νέα ώρα λήξης χρησιμοποιώντας τον παρακάτω επιλογέα ρολογιού.", + "data": { + "trim_end_time": "Νέα ώρα λήξης", + "trim_end_min": "Νέο τέλος (λεπτά από την έναρξη του κύκλου)" + } + } + }, + "error": { + "import_failed": "Η εισαγωγή απέτυχε. Επικολλήστε ένα έγκυρο JSON εξαγωγής WashData.", + "select_exactly_one": "Επιλέξτε ακριβώς έναν κύκλο για αυτή την ενέργεια.", + "select_at_least_one": "Επιλέξτε τουλάχιστον έναν κύκλο για αυτή την ενέργεια.", + "select_at_least_two": "Επιλέξτε τουλάχιστον δύο κύκλους για αυτή την ενέργεια.", + "profile_exists": "Υπάρχει ήδη ένα προφίλ με αυτό το όνομα", + "rename_failed": "Αποτυχία μετονομασίας προφίλ. Το προφίλ μπορεί να μην υπάρχει ή το νέο όνομα είναι ήδη κατειλημμένο.", + "assignment_failed": "Αποτυχία εκχώρησης προφίλ σε κύκλο", + "duplicate_phase": "Υπάρχει ήδη φάση με αυτό το όνομα.", + "phase_not_found": "Η επιλεγμένη φάση δεν βρέθηκε.", + "invalid_phase_name": "Το όνομα φάσης δεν μπορεί να είναι κενό.", + "phase_name_too_long": "Το όνομα της φάσης είναι πολύ μεγάλο.", + "invalid_phase_range": "Το εύρος φάσης δεν είναι έγκυρο. Η λήξη πρέπει να είναι μεγαλύτερη από την έναρξη.", + "invalid_phase_timestamp": "Η χρονοσφραγίδα δεν είναι έγκυρη. Χρησιμοποιήστε τη μορφή ΕΕΕΕ-ΜΜ-ΗΗ ΩΩ:ΛΛ ή ΩΩ:ΛΛ.", + "overlapping_phase_ranges": "Τα εύρη φάσεων επικαλύπτονται. Προσαρμόστε τα εύρη και δοκιμάστε ξανά.", + "incomplete_phase_row": "Κάθε σειρά φάσης πρέπει να περιλαμβάνει μια φάση και πλήρεις τιμές έναρξης/λήξης για την επιλεγμένη λειτουργία.", + "timestamp_mode_cycle_required": "Επιλέξτε έναν κύκλο πηγής όταν χρησιμοποιείτε λειτουργία χρονοσφραγίδας.", + "no_phase_ranges": "Δεν υπάρχουν ακόμη διαθέσιμα εύρη φάσεων για αυτή την ενέργεια.", + "feedback_notification_title": "WashData: Επαλήθευση κύκλου ({device})", + "feedback_notification_message": "**Συσκευή**: {device}\n**Πρόγραμμα**: {program} ({confidence}% εμπιστοσύνη)\n**Ώρα**: {time}\n\nΤο WashData χρειάζεται τη βοήθειά σας για να επαληθεύσει τον εντοπισμένο κύκλο.\n\nΜεταβείτε στις **Ρυθμίσεις > Συσκευές και υπηρεσίες > WashData > Διαμόρφωση > Ανατροφοδοτήσεις μάθησης** για να επιβεβαιώσετε ή να διορθώσετε αυτό το αποτέλεσμα.", + "suggestions_ready_notification_title": "WashData: Προτεινόμενες ρυθμίσεις έτοιμες ({device})", + "suggestions_ready_notification_message": "Ο αισθητήρας **Προτεινόμενες ρυθμίσεις** αναφέρει τώρα **{count}** εφαρμόσιμες συστάσεις.\n\nΓια να τις ελέγξετε και εφαρμόσετε: **Ρυθμίσεις > Συσκευές και υπηρεσίες > WashData > Διαμόρφωση > Σύνθετες ρυθμίσεις > Εφαρμογή προτεινόμενων τιμών**.\n\nΟι προτάσεις είναι προαιρετικές και εμφανίζονται για έλεγχο πριν αποθηκευτούν.", + "trim_range_invalid": "Η έναρξη πρέπει να είναι πριν τη λήξη. Προσαρμόστε τα σημεία περικοπής.", + "trim_failed": "Η περικοπή απέτυχε. Ο κύκλος μπορεί να μην υπάρχει πλέον ή το παράθυρο είναι κενό.", + "invalid_split_timestamp": "Η χρονική σήμανση είναι άκυρη ή εκτός του παραθύρου του κύκλου. Χρησιμοποιήστε HH:MM ή HH:MM:SS μέσα στο παράθυρο του κύκλου.", + "no_split_segments_found": "Δεν ήταν δυνατή η παραγωγή έγκυρων τμημάτων από αυτές τις χρονικές σημάνσεις. Βεβαιωθείτε ότι κάθε χρονική σήμανση βρίσκεται μέσα στο παράθυρο του κύκλου και ότι τα τμήματα έχουν διάρκεια τουλάχιστον 1 λεπτό.", + "auto_tune_suggestion": "Η συσκευή {device_type} {device_title} εντόπισε κύκλους φαντασμάτων. Προτεινόμενη αλλαγή ελάχιστης ισχύος: {current_min}W -> {new_min}W (δεν εφαρμόζεται αυτόματα).", + "auto_tune_title": "Αυτόματος συντονισμός WashData", + "auto_tune_fallback": "Η συσκευή {device_type} {device_title} εντόπισε κύκλους φαντασμάτων. Προτεινόμενη αλλαγή ελάχιστης ισχύος: {current_min}W -> {new_min}W (δεν εφαρμόζεται αυτόματα)." + }, + "abort": { + "no_cycles_found": "Δεν βρέθηκαν κύκλοι", + "no_split_segments_found": "Δεν βρέθηκαν τμήματα προς διαχωρισμό στον επιλεγμένο κύκλο.", + "cycle_not_found": "Δεν ήταν δυνατή η φόρτωση του επιλεγμένου κύκλου.", + "no_power_data": "Δεν υπάρχουν διαθέσιμα δεδομένα ίχνους ισχύος για τον επιλεγμένο κύκλο.", + "no_profiles_found": "Δεν βρέθηκαν προφίλ. Δημιουργήστε πρώτα ένα προφίλ.", + "no_profiles_for_matching": "Δεν υπάρχουν διαθέσιμα προφίλ για αντιστοίχιση. Δημιουργήστε πρώτα προφίλ.", + "no_unlabeled_cycles": "Όλοι οι κύκλοι έχουν ήδη επισημανθεί", + "no_suggestions": "Δεν υπάρχουν ακόμα διαθέσιμες προτεινόμενες τιμές. Εκτελέστε μερικούς κύκλους και δοκιμάστε ξανά.", + "no_predictions": "Δεν υπάρχουν προβλέψεις", + "no_custom_phases": "Δεν βρέθηκαν προσαρμοσμένες φάσεις.", + "reprocess_success": "Επιτυχής επανεπεξεργασία {count} κύκλων.", + "debug_data_cleared": "Τα δεδομένα εντοπισμού σφαλμάτων διαγράφηκαν επιτυχώς από {count} κύκλους." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Έναρξη κύκλου", + "cycle_finish": "Λήξη κύκλου", + "cycle_live": "Ζωντανή πρόοδος" + } + }, + "common_text": { + "options": { + "unlabeled": "(Χωρίς ετικέτα)", + "create_new_profile": "Δημιουργία νέου προφίλ", + "remove_label": "Αφαίρεση ετικέτας", + "no_reference_cycle": "(Χωρίς κύκλο αναφοράς)", + "all_device_types": "Όλοι οι τύποι συσκευών", + "current_suffix": "(τρέχον)", + "editor_select_info": "Επιλέξτε 1 κύκλο για διαχωρισμό ή 2+ κύκλους για συγχώνευση.", + "editor_select_info_split": "Επιλέξτε 1 κύκλο για διαχωρισμό.", + "editor_select_info_merge": "Επιλέξτε 2+ κύκλους για συγχώνευση.", + "editor_select_info_delete": "Επιλέξτε 1+ κύκλο/κύκλους για διαγραφή.", + "no_cycles_recorded": "Δεν έχουν καταγραφεί ακόμη κύκλοι.", + "profile_summary_line": "- **{name}**: {count} κύκλοι, μέσος όρος {avg} λεπτά", + "no_profiles_created": "Δεν έχουν δημιουργηθεί ακόμα προφίλ.", + "phase_preview": "Προεπισκόπηση φάσης", + "phase_preview_no_curve": "Η μέση καμπύλη προφίλ δεν είναι ακόμα διαθέσιμη. Εκτελέστε/επισημάνετε περισσότερους κύκλους για αυτό το προφίλ.", + "average_power_curve": "Μέση καμπύλη ισχύος", + "unit_min": "λεπτ", + "profile_option_fmt": "{name} ({count} κύκλοι, ~{duration} λεπτά μέσος όρος)", + "profile_option_short_fmt": "{name} ({count} κύκλοι, ~{duration} λεπτά)", + "deleted_cycles_from_profile": "Διαγράφηκαν {count} κύκλοι από {profile}.", + "not_enough_data_graph": "Δεν υπάρχουν αρκετά δεδομένα για τη δημιουργία γραφήματος.", + "no_profiles_found": "Δεν βρέθηκαν προφίλ.", + "cycle_info_fmt": "Κύκλος: {start}, {duration} λεπτά, Τρέχον: {label}", + "top_candidates_header": "### Κορυφαίοι υποψήφιοι", + "tbl_profile": "Προφίλ", + "tbl_confidence": "Εμπιστοσύνη", + "tbl_correlation": "Συσχέτιση", + "tbl_duration_match": "Αντιστοίχιση διάρκειας", + "detected_profile": "Εντοπισμένο προφίλ", + "estimated_duration": "Εκτιμώμενη διάρκεια", + "actual_duration": "Πραγματική διάρκεια", + "choose_action": "__Επιλέξτε μια ενέργεια παρακάτω:__", + "tbl_count": "Αριθμός", + "tbl_avg": "Μέσος", + "tbl_min": "Ελάχ", + "tbl_max": "Μέγ", + "tbl_energy_avg": "Ενέργεια (μέσος)", + "tbl_energy_total": "Ενέργεια (σύνολο)", + "tbl_consistency": "Συνέπεια", + "tbl_last_run": "Τελευταία εκτέλεση", + "graph_legend_title": "Υπόμνημα γραφήματος", + "graph_legend_body": "Η μπλε ζώνη αντιπροσωπεύει το παρατηρούμενο εύρος ελάχιστης και μέγιστης κατανάλωσης ισχύος. Η γραμμή δείχνει τη μέση καμπύλη ισχύος.", + "recording_preview": "Προεπισκόπηση εγγραφής", + "trim_start": "Έναρξη περικοπής", + "trim_end": "Λήξη περικοπής", + "split_preview_title": "Προεπισκόπηση διαχωρισμού", + "split_preview_found_fmt": "Βρέθηκαν {count} τμήματα.", + "split_preview_confirm_fmt": "Κάντε κλικ στο Επιβεβαίωση για να χωρίσετε αυτόν τον κύκλο σε {count} ξεχωριστούς κύκλους.", + "merge_preview_title": "Προεπισκόπηση συγχώνευσης", + "merge_preview_joining_fmt": "Συγχώνευση {count} κύκλων. Τα κενά θα συμπληρωθούν με μετρήσεις 0 W.", + "editor_delete_preview_title": "Προεπισκόπηση διαγραφής", + "editor_delete_preview_intro": "Οι επιλεγμένοι κύκλοι θα διαγραφούν οριστικά:", + "editor_delete_preview_confirm": "Κάντε κλικ στην Επιβεβαίωση για να διαγράψετε οριστικά αυτές τις εγγραφές κύκλων.", + "no_power_preview": "*Δεν υπάρχουν διαθέσιμα δεδομένα ισχύος για προεπισκόπηση.*", + "profile_comparison": "Σύγκριση προφίλ", + "actual_cycle_label": "Αυτός ο κύκλος (πραγματικός)", + "storage_usage_header": "Χρήση αποθηκευτικού χώρου", + "storage_file_size": "Μέγεθος αρχείου", + "storage_cycles": "Κύκλοι", + "storage_profiles": "Προφίλ", + "storage_debug_traces": "Ίχνη εντοπισμού σφαλμάτων", + "overview_suffix": "Επισκόπηση", + "phase_preview_for_profile": "Προεπισκόπηση φάσης για προφίλ", + "current_ranges_header": "Τρέχοντα εύρη", + "assign_phases_help": "Χρησιμοποιήστε τις παρακάτω ενέργειες για να προσθέσετε, επεξεργαστείτε, διαγράψετε ή αποθηκεύσετε εύρη.", + "profile_summary_header": "Σύνοψη προφίλ", + "recent_cycles_header": "Πρόσφατοι κύκλοι", + "trim_cycle_preview_title": "Προεπισκόπηση περικοπής κύκλου", + "trim_cycle_preview_no_data": "Δεν υπάρχουν διαθέσιμα δεδομένα ισχύος για αυτόν τον κύκλο.", + "trim_cycle_preview_kept_suffix": "διατηρήθηκε", + "table_program": "Πρόγραμμα", + "table_when": "Πότε", + "table_length": "Διάρκεια", + "table_match": "Ταίριασμα", + "table_profile": "Προφίλ", + "table_cycles": "Κύκλοι", + "table_avg_length": "Μέση διάρκεια", + "table_last_run": "Τελευταία εκτέλεση", + "table_avg_energy": "Μέση ενέργεια", + "table_detected_program": "Ανιχνευμένο πρόγραμμα", + "table_confidence": "Εμπιστοσύνη", + "table_reported": "Αναφέρθηκε", + "phase_builtin_suffix": "(Ενσωματωμένο)", + "phase_none_available": "Δεν υπάρχουν διαθέσιμες φάσεις.", + "settings_deprecation_warning": "⚠️ **Καταργημένος τύπος συσκευής:** Το {device_type} έχει προγραμματιστεί για κατάργηση σε μελλοντική κυκλοφορία. Η αντιστοίχιση της γραμμής WashData δεν παράγει αξιόπιστα αποτελέσματα για αυτήν την κατηγορία συσκευών. Η ενσωμάτωσή σας συνεχίζει να λειτουργεί κατά τη διάρκεια της περιόδου κατάργησης. για να θέσετε σε σίγαση αυτήν την προειδοποίηση, αλλάξτε τον **Τύπος συσκευής** παρακάτω σε έναν από τους υποστηριζόμενους τύπους (πλυντήριο ρούχων, στεγνωτήριο, πλυντήριο-στεγνωτήριο ρούχων, πλυντήριο πιάτων, φριτέζα αέρα, παρασκευή ψωμιού ή αντλία) ή σε **Άλλο (Για προχωρημένους)** εάν η συσκευή σας δεν ταιριάζει με κανέναν από τους υποστηριζόμενους τύπους. Το **Άλλο (Για προχωρημένους)** αποστέλλει σκόπιμα γενικές προεπιλογές που δεν είναι ρυθμισμένες για καμία συγκεκριμένη συσκευή, επομένως θα χρειαστεί να διαμορφώσετε μόνοι σας τα όρια, τα χρονικά όρια και τις παραμέτρους που ταιριάζουν. όλες οι υπάρχουσες ρυθμίσεις σας διατηρούνται όταν κάνετε εναλλαγή.", + "phase_other_device_types": "Άλλοι τύποι συσκευών:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Διαχωρισμός κύκλου (εύρεση κενών)", + "merge": "Συγχώνευση κύκλων (ένωση θραυσμάτων)", + "delete": "Διαγραφή κύκλου/κύκλων" + } + }, + "split_mode": { + "options": { + "auto": "Αυτόματος εντοπισμός αδρανών κενών", + "manual": "Χειροκίνητη(ες) χρονική(ές) σήμανση(εις)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Συντήρηση: επανεπεξεργασία και βελτιστοποίηση δεδομένων", + "clear_debug_data": "Εκκαθάριση δεδομένων εντοπισμού σφαλμάτων (ελευθέρωση χώρου)", + "wipe_history": "Διαγραφή ΟΛΩΝ των δεδομένων για αυτή τη συσκευή (μη αναστρέψιμο)", + "export_import": "Εξαγωγή/εισαγωγή JSON με ρυθμίσεις (αντιγραφή/επικόλληση)" + } + }, + "export_import_mode": { + "options": { + "export": "Εξαγωγή όλων των δεδομένων", + "import": "Εισαγωγή/συγχώνευση δεδομένων" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Αυτόματη επισήμανση παλαιών κύκλων", + "select_cycle_to_label": "Επισήμανση συγκεκριμένου κύκλου", + "select_cycle_to_delete": "Διαγραφή κύκλου", + "interactive_editor": "Διαδραστικός επεξεργαστής συγχώνευσης/διαχωρισμού", + "trim_cycle": "Περικοπή δεδομένων κύκλου" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Δημιουργία νέου προφίλ", + "edit_profile": "Επεξεργασία/μετονομασία προφίλ", + "delete_profile": "Διαγραφή προφίλ", + "profile_stats": "Στατιστικά προφίλ", + "cleanup_profile": "Εκκαθάριση ιστορικού - γράφημα και διαγραφή", + "assign_phases": "Εκχώρηση εύρους φάσεων" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Δημιουργία νέας φάσης", + "edit_custom_phase": "Επεξεργασία φάσης", + "delete_custom_phase": "Διαγραφή φάσης" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Προσθήκη εύρους φάσης", + "edit_range": "Επεξεργασία εύρους φάσης", + "delete_range": "Διαγραφή εύρους φάσης", + "clear_ranges": "Εκκαθάριση όλων των εύρων", + "auto_detect_ranges": "Αυτόματη ανίχνευση φάσεων", + "save_ranges": "Αποθήκευση και επιστροφή" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Ανανέωση κατάστασης", + "stop_recording": "Διακοπή εγγραφής (αποθήκευση και επεξεργασία)", + "start_recording": "Έναρξη νέας εγγραφής", + "process_recording": "Επεξεργασία τελευταίας εγγραφής (περικοπή και αποθήκευση)", + "discard_recording": "Απόρριψη τελευταίας εγγραφής" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Δημιουργία νέου προφίλ", + "existing_profile": "Προσθήκη σε υπάρχον προφίλ", + "discard": "Απόρριψη εγγραφής" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Επιβεβαίωση - Σωστή ανίχνευση", + "correct": "Διόρθωση - Επιλογή προγράμματος/διάρκειας", + "ignore": "Αγνόηση - Ψευδώς θετικός/θορυβώδης κύκλος", + "delete": "Διαγραφή - Αφαίρεση από το ιστορικό" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Ονομασία και εφαρμογή φάσεων", + "cancel": "Επιστροφή χωρίς αλλαγές" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Ορισμός σημείου έναρξης", + "set_end": "Ορισμός σημείου λήξης", + "reset": "Επαναφορά σε πλήρη διάρκεια", + "apply": "Εφαρμογή περικοπής", + "cancel": "Ακύρωση" + } + }, + "device_type": { + "options": { + "washing_machine": "Πλυντήριο", + "dryer": "Στεγνωτήριο", + "washer_dryer": "Συνδυασμός πλυντηρίου-στεγνωτηρίου", + "dishwasher": "Πλυντήριο πιάτων", + "coffee_machine": "Καφετιέρα (καταργημένη)", + "ev": "Ηλεκτρικό όχημα (καταργήθηκε)", + "air_fryer": "Φριτέζα αέρα", + "heat_pump": "Αντλία θερμότητας (καταργημένη)", + "bread_maker": "Αρτοπαρασκευαστής", + "pump": "Αντλία / Αντλία κάρτερ", + "oven": "Φούρνος (καταργημένος)", + "other": "Άλλο (Για προχωρημένους)" + } + } + }, + "services": { + "label_cycle": { + "name": "Επισήμανση κύκλου", + "description": "Εκχωρήστε ένα υπάρχον προφίλ σε έναν παλαιότερο κύκλο ή αφαιρέστε την ετικέτα.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για επισήμανση." + }, + "cycle_id": { + "name": "Αναγνωριστικό κύκλου", + "description": "Το αναγνωριστικό του κύκλου προς επισήμανση." + }, + "profile_name": { + "name": "Όνομα προφίλ", + "description": "Το όνομα ενός υπάρχοντος προφίλ (δημιουργήστε προφίλ από το μενού Διαχείριση προφίλ). Αφήστε κενό για αφαίρεση ετικέτας." + } + } + }, + "create_profile": { + "name": "Δημιουργία προφίλ", + "description": "Δημιουργήστε ένα νέο προφίλ (αυτόνομο ή βασισμένο σε κύκλο αναφοράς).", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData." + }, + "profile_name": { + "name": "Όνομα προφίλ", + "description": "Όνομα για το νέο προφίλ (π.χ. «Βαρύ πλύσιμο», «Ευαίσθητα»)." + }, + "reference_cycle_id": { + "name": "Αναγνωριστικό κύκλου αναφοράς", + "description": "Προαιρετικό αναγνωριστικό κύκλου στον οποίο να βασιστεί αυτό το προφίλ." + } + } + }, + "delete_profile": { + "name": "Διαγραφή προφίλ", + "description": "Διαγράψτε ένα προφίλ και προαιρετικά αφαιρέστε ετικέτες από κύκλους που το χρησιμοποιούν.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData." + }, + "profile_name": { + "name": "Όνομα προφίλ", + "description": "Το προφίλ προς διαγραφή." + }, + "unlabel_cycles": { + "name": "Αφαίρεση ετικετών κύκλων", + "description": "Αφαιρέστε την ετικέτα προφίλ από κύκλους που χρησιμοποιούν αυτό το προφίλ." + } + } + }, + "auto_label_cycles": { + "name": "Αυτόματη επισήμανση παλαιών κύκλων", + "description": "Αναδρομική επισήμανση κύκλων χωρίς ετικέτα χρησιμοποιώντας αντιστοίχιση προφίλ.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData." + }, + "confidence_threshold": { + "name": "Κατώφλι εμπιστοσύνης", + "description": "Ελάχιστη εμπιστοσύνη αντιστοίχισης (0,50-0,95) για εφαρμογή ετικετών." + } + } + }, + "export_config": { + "name": "Εξαγωγή διαμόρφωσης", + "description": "Εξαγωγή προφίλ, κύκλων και ρυθμίσεων αυτής της συσκευής σε αρχείο JSON (ανά συσκευή).", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για εξαγωγή." + }, + "path": { + "name": "Διαδρομή", + "description": "Προαιρετική απόλυτη διαδρομή αρχείου για εγγραφή (προεπιλογή: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Εισαγωγή διαμόρφωσης", + "description": "Εισαγωγή προφίλ, κύκλων και ρυθμίσεων για αυτή τη συσκευή από αρχείο JSON εξαγωγής (οι ρυθμίσεις ενδέχεται να αντικατασταθούν).", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για εισαγωγή." + }, + "path": { + "name": "Διαδρομή", + "description": "Απόλυτη διαδρομή προς το αρχείο JSON εξαγωγής για εισαγωγή." + } + } + }, + "submit_cycle_feedback": { + "name": "Υποβολή ανατροφοδότησης κύκλου", + "description": "Επιβεβαιώστε ή διορθώστε ένα αυτόματα εντοπισμένο πρόγραμμα μετά από ολοκληρωμένο κύκλο. Δώστε είτε `entry_id` (σύνθετο) ή `device_id` (συνιστάται).", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData (συνιστάται αντί για entry_id)." + }, + "entry_id": { + "name": "Αναγνωριστικό καταχώρισης", + "description": "Το αναγνωριστικό καταχώρισης διαμόρφωσης για τη συσκευή (εναλλακτικό του device_id)." + }, + "cycle_id": { + "name": "Αναγνωριστικό κύκλου", + "description": "Το αναγνωριστικό κύκλου που εμφανίζεται στην ειδοποίηση ανατροφοδότησης / αρχεία καταγραφής." + }, + "user_confirmed": { + "name": "Επιβεβαίωση εντοπισμένου προγράμματος", + "description": "Ορίστε true αν το εντοπισμένο πρόγραμμα είναι σωστό." + }, + "corrected_profile": { + "name": "Διορθωμένο προφίλ", + "description": "Αν δεν επιβεβαιωθεί, δώστε το σωστό όνομα προφίλ/προγράμματος." + }, + "corrected_duration": { + "name": "Διορθωμένη διάρκεια (δευτερόλεπτα)", + "description": "Προαιρετική διορθωμένη διάρκεια σε δευτερόλεπτα." + }, + "notes": { + "name": "Σημειώσεις", + "description": "Προαιρετικές σημειώσεις για αυτόν τον κύκλο." + } + } + }, + "record_start": { + "name": "Έναρξη εγγραφής κύκλου", + "description": "Ξεκινήστε χειροκίνητη εγγραφή ενός καθαρού κύκλου (παρακάμπτει όλη τη λογική αντιστοίχισης).", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για εγγραφή." + } + } + }, + "record_stop": { + "name": "Διακοπή εγγραφής κύκλου", + "description": "Διακοπή χειροκίνητης εγγραφής.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για διακοπή εγγραφής." + } + } + }, + "trim_cycle": { + "name": "Περικοπή κύκλου", + "description": "Περικοπή των δεδομένων ισχύος ενός παλαιότερου κύκλου σε συγκεκριμένο χρονικό παράθυρο.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData." + }, + "cycle_id": { + "name": "Αναγνωριστικό κύκλου", + "description": "Το αναγνωριστικό του κύκλου προς περικοπή." + }, + "trim_start_s": { + "name": "Έναρξη περικοπής (δευτερόλεπτα)", + "description": "Διατήρηση δεδομένων από αυτή τη μετατόπιση σε δευτερόλεπτα. Προεπιλογή 0." + }, + "trim_end_s": { + "name": "Λήξη περικοπής (δευτερόλεπτα)", + "description": "Διατήρηση δεδομένων έως αυτή τη μετατόπιση σε δευτερόλεπτα. Προεπιλογή = πλήρης διάρκεια." + } + } + }, + "pause_cycle": { + "name": "Κύκλος παύσης", + "description": "Παύση του ενεργού κύκλου για μια συσκευή WashData.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για παύση." + } + } + }, + "resume_cycle": { + "name": "Κύκλος Συνέχισης", + "description": "Συνέχιση ενός κύκλου σε παύση για μια συσκευή WashData.", + "fields": { + "device_id": { + "name": "Συσκευή", + "description": "Η συσκευή WashData για συνέχιση." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Σε λειτουργία" + }, + "match_ambiguity": { + "name": "Αμφισημία αντιστοίχισης" + } + }, + "sensor": { + "washer_state": { + "name": "Κατάσταση", + "state": { + "off": "Ανενεργό", + "idle": "Σε αναμονή", + "starting": "Εκκίνηση", + "running": "Σε λειτουργία", + "paused": "Σε παύση", + "user_paused": "Σε παύση από τον χρήστη", + "ending": "Ολοκλήρωση", + "finished": "Ολοκληρώθηκε", + "anti_wrinkle": "Κατά ρυτίδων", + "delay_wait": "Αναμονή για την έναρξη", + "interrupted": "Διακόπηκε", + "force_stopped": "Αναγκαστική διακοπή", + "rinse": "Ξέπλυμα", + "unknown": "Άγνωστο", + "clean": "Καθαρός" + } + }, + "washer_program": { + "name": "Πρόγραμμα" + }, + "time_remaining": { + "name": "Υπολειπόμενος χρόνος" + }, + "total_duration": { + "name": "Συνολική διάρκεια" + }, + "cycle_progress": { + "name": "Πρόοδος" + }, + "current_power": { + "name": "Τρέχουσα ισχύς" + }, + "elapsed_time": { + "name": "Χρόνος που πέρασε" + }, + "debug_info": { + "name": "Πληροφορίες εντοπισμού σφαλμάτων" + }, + "match_confidence": { + "name": "Εμπιστοσύνη αντιστοίχισης" + }, + "top_candidates": { + "name": "Κορυφαίοι υποψήφιοι", + "state": { + "none": "Κανένας" + } + }, + "current_phase": { + "name": "Τρέχουσα φάση" + }, + "wash_phase": { + "name": "Φάση" + }, + "profile_cycle_count": { + "name": "Πλήθος κύκλων προφίλ {profile_name}", + "unit_of_measurement": "κύκλοι" + }, + "suggestions": { + "name": "Διαθέσιμες προτεινόμενες ρυθμίσεις" + }, + "pump_runs_today": { + "name": "Λειτουργίες αντλίας (Τελευταίες 24 ώρες)" + }, + "cycle_count": { + "name": "Αριθμός Κύκλων" + } + }, + "select": { + "program_select": { + "name": "Πρόγραμμα κύκλου", + "state": { + "auto_detect": "Αυτόματη ανίχνευση" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Αναγκαστική λήξη κύκλου" + }, + "pause_cycle": { + "name": "Κύκλος παύσης" + }, + "resume_cycle": { + "name": "Κύκλος Συνέχισης" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Απαιτείται έγκυρο αναγνωριστικό συσκευής." + }, + "cycle_id_required": { + "message": "Απαιτείται έγκυρο cycle_id." + }, + "profile_name_required": { + "message": "Απαιτείται έγκυρο profile_name." + }, + "device_not_found": { + "message": "Η συσκευή δεν βρέθηκε." + }, + "no_config_entry": { + "message": "Δεν βρέθηκε καταχώριση διαμόρφωσης για τη συσκευή." + }, + "integration_not_loaded": { + "message": "Η ενσωμάτωση δεν έχει φορτωθεί για αυτή τη συσκευή." + }, + "cycle_not_found_or_no_power": { + "message": "Ο κύκλος δεν βρέθηκε ή δεν έχει δεδομένα ισχύος." + }, + "trim_failed_empty_window": { + "message": "Η περικοπή απέτυχε - δεν βρέθηκε κύκλος, δεν υπάρχουν δεδομένα ισχύος ή το προκύπτον παράθυρο είναι κενό." + }, + "trim_invalid_range": { + "message": "Το trim_end_s πρέπει να είναι μεγαλύτερο από το trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/en.json b/custom_components/ha_washdata/translations/en.json new file mode 100644 index 0000000..9b3650e --- /dev/null +++ b/custom_components/ha_washdata/translations/en.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Setup", + "description": "Configure your washing machine or other appliance.\n\nPower sensor is required.\n\n**Next, you will be asked if you want to create your first profile.**", + "data": { + "name": "Device Name", + "device_type": "Device Type", + "power_sensor": "Power Sensor", + "min_power": "Minimum Power Threshold (W)" + }, + "data_description": { + "name": "A friendly name for this device (e.g., 'Washing Machine', 'Dishwasher').", + "device_type": "What type of appliance is this? Helps tailor detection and labeling.", + "power_sensor": "The sensor entity that reports real-time power consumption (in watts) from your smart plug.", + "min_power": "Power readings above this threshold (in watts) indicate the appliance is running. Start with 2W for most devices." + } + }, + "first_profile": { + "title": "Create First Profile", + "description": "A **Cycle** is a complete run of your appliance (e.g., a load of laundry). A **Profile** groups these cycles by type (e.g., 'Cotton', 'Quick Wash'). Creating a profile now helps the integration estimate duration immediately.\n\nYou can manually select this profile in the device controls if it's not detected automatically.\n\n**Leave 'Profile Name' empty to skip this step.**", + "data": { + "profile_name": "Profile Name (Optional)", + "manual_duration": "Estimated Duration (minutes)" + } + } + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_auth": "Invalid authentication", + "unknown": "Unexpected error" + }, + "abort": { + "already_configured": "Device is already configured" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Review Learning Feedbacks", + "settings": "Settings", + "notifications": "Notifications", + "manage_cycles": "Manage Cycles", + "manage_profiles": "Manage Profiles", + "manage_phase_catalog": "Manage Phase Catalog", + "record_cycle": "Record Cycle (Manual)", + "diagnostics": "Diagnostics & Maintenance" + } + }, + "apply_suggestions": { + "title": "Copy Suggested Values", + "description": "This will copy the current suggested values into your saved Settings. This is a one-time action and does not enable auto-overwrites.\n\nSuggested values to copy:\n{suggested}", + "data": { + "confirm": "Confirm copy action" + } + }, + "apply_suggestions_confirm": { + "title": "Review Suggested Changes", + "description": "WashData found **{pending_count}** suggested value(s) to apply.\n\nChanges:\n{changes}\n\n✅ Check the box below and click **Submit** to apply and save these changes immediately.\n❌ Leave it unchecked and click **Submit** to cancel and go back.", + "data": { + "confirm_apply_suggestions": "Apply and save these changes" + } + }, + "settings": { + "title": "Settings", + "description": "{deprecation_warning}Tune detection thresholds, learning, and advanced behavior. Defaults work well for most setups.\n\nSuggested Settings currently available: {suggestions_count}.\nIf this is above 0, open Advanced Settings and use Apply Suggested Values to review recommendations.", + "data": { + "apply_suggestions": "Apply Suggested Values", + "device_type": "Device Type", + "power_sensor": "Power Sensor Entity", + "min_power": "Minimum Power (W)", + "off_delay": "Cycle End Delay (s)", + "notify_actions": "Notification Actions", + "notify_people": "Delay Delivery Until These People Are Home", + "notify_only_when_home": "Delay Notifications Until Selected Person Is Home", + "notify_fire_events": "Fire Automation Events", + "notify_start_services": "Cycle Start - Notification Targets", + "notify_finish_services": "Cycle Finish - Notification Targets", + "notify_live_services": "Live Progress - Notification Targets", + "notify_before_end_minutes": "Pre-completion Notification (minutes before end)", + "notify_title": "Notification Title", + "notify_icon": "Notification Icon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Finish Message Format", + "notify_pre_complete_message": "Live Update Message Format", + "show_advanced": "Edit Advanced Settings (Next Step)" + }, + "data_description": { + "apply_suggestions": "Check this box to copy values suggested by the learning algorithm into the form. Review before saving.\n\nSuggested (from learning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Select the type of appliance (Washing Machine, Dryer, Dishwasher, Coffee Machine, EV, Bread Maker, Pump). This tag is stored with each cycle for better organization.", + "power_sensor": "The sensor entity reporting real-time power (in watts). You can change this anytime without losing historical data — useful if you replace a smart plug.", + "min_power": "Power readings above this threshold (in watts) indicate the appliance is running. Start with 2W for most devices.", + "off_delay": "Seconds the smoothed power must stay below the stop threshold to mark completion. Default: 120s.", + "notify_actions": "Optional Home Assistant action sequence to run for notifications (scripts, notify, TTS, etc.). If set, actions run before per-event service targets.", + "notify_people": "People entities used for presence gating. When enabled, notification delivery is delayed until at least one selected person is home.", + "notify_only_when_home": "If enabled, delay notifications until at least one selected person is home.", + "notify_fire_events": "If enabled, fire Home Assistant events for cycle start and end (ha_washdata_cycle_started and ha_washdata_cycle_ended) to drive automations.", + "notify_start_services": "One or more notify services to alert when a cycle begins. Leave empty to skip start notifications.", + "notify_finish_services": "One or more notify services to alert when a cycle finishes or nears completion. Leave empty to skip finish notifications.", + "notify_live_services": "One or more notify services to receive live progress updates while the cycle runs (mobile companion app only). Leave empty to skip live updates.", + "notify_before_end_minutes": "Minutes before the estimated end of the cycle to trigger a notification. Set to 0 to disable. Default: 0.", + "notify_title": "Custom title for notifications. Supports {device} placeholder.", + "notify_icon": "Icon to use for notifications (e.g. mdi:washing-machine). Leave empty to send no icon.", + "notify_start_message": "Message sent when a cycle starts. Supports {device} placeholder.", + "notify_finish_message": "Message sent when a cycle finishes. Supports {device}, {duration}, {program}, {energy_kwh}, {cost} placeholders.", + "notify_pre_complete_message": "Text of the recurring live progress updates while the cycle runs. Supports {device}, {minutes}, {program} placeholders." + } + }, + "notifications": { + "title": "Notifications", + "description": "Configure notification targets per event type and optional live mobile progress updates. Each event type can be sent to a different set of recipients.", + "data": { + "notify_actions": "Notification Actions", + "notify_people": "Delay Delivery Until These People Are Home", + "notify_only_when_home": "Delay Notifications Until Selected Person Is Home", + "notify_fire_events": "Fire Automation Events", + "notify_start_services": "Cycle Start - Notification Targets", + "notify_finish_services": "Cycle Finish - Notification Targets", + "notify_live_services": "Live Progress - Notification Targets", + "notify_before_end_minutes": "Pre-completion Notification (minutes before end)", + "notify_live_interval_seconds": "Live Update Interval (seconds)", + "notify_live_overrun_percent": "Live Update Overrun Allowance (%)", + "notify_live_chronometer": "Chronometer Countdown Timer", + "notify_title": "Notification Title", + "notify_icon": "Notification Icon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Finish Message Format", + "notify_pre_complete_message": "Live Update Message Format", + "notify_reminder_message": "Reminder Message Format", + "notify_timeout_seconds": "Auto-dismiss After (seconds)", + "notify_channel": "Notification Channel (status/live/reminder)", + "notify_finish_channel": "Finished Notification Channel", + "energy_price_entity": "Energy Price - Entity (optional)", + "energy_price_static": "Energy Price - Static value (optional)", + "go_back": "Go back without saving" + }, + "data_description": { + "go_back": "Tick this and click Submit to discard any changes above and return to the main menu.", + "notify_actions": "Optional Home Assistant action sequence to run for every notification event (scripts, notify, TTS, etc.). Actions fire before the per-event service targets.", + "notify_people": "People entities used for presence gating. When enabled, notification delivery is delayed until at least one selected person is home.", + "notify_only_when_home": "If enabled, delay notifications until at least one selected person is home.", + "notify_fire_events": "If enabled, fire Home Assistant events for cycle start and end (ha_washdata_cycle_started and ha_washdata_cycle_ended) to drive automations.", + "notify_start_services": "One or more notify services to alert when a cycle begins (e.g. notify.mobile_app_my_phone). Leave empty to skip start notifications.", + "notify_finish_services": "One or more notify services to alert when a cycle finishes or nears completion. Leave empty to skip finish notifications.", + "notify_live_services": "One or more notify services to receive live progress updates while the cycle runs (mobile companion app only). Leave empty to skip live updates.", + "notify_before_end_minutes": "Minutes before the estimated end of the cycle to trigger a notification. Set to 0 to disable. Default: 0.", + "notify_live_interval_seconds": "How often progress updates are sent while running. Lower values are more responsive but consume more notifications. A minimum of 30 seconds is enforced - values below 30 s are automatically rounded up to 30 s.", + "notify_live_overrun_percent": "Safety margin above the estimated update count for long/overrunning cycles (for example 20% allows 1.2x estimated updates).", + "notify_live_chronometer": "When enabled, each live update includes a chronometer countdown to the estimated finish time. The notification timer ticks down automatically on the device between updates (Android companion app only).", + "notify_title": "Custom title for notifications. Supports {device} placeholder.", + "notify_icon": "Icon to use for notifications (e.g. mdi:washing-machine). Leave empty to send no icon.", + "notify_start_message": "Message sent when a cycle starts. Supports {device} placeholder.", + "notify_finish_message": "Message sent when a cycle finishes. Supports {device}, {duration}, {program}, {energy_kwh}, {cost} placeholders.", + "notify_pre_complete_message": "Text of the recurring live progress updates while the cycle runs. Supports {device}, {minutes}, {program} placeholders.", + "notify_reminder_message": "Text of the one-time reminder sent the configured number of minutes before the estimated end. Distinct from the recurring live updates above. Supports {device}, {minutes}, {program} placeholders.", + "notify_timeout_seconds": "Automatically dismiss notifications after this many seconds (forwarded as the companion app 'timeout'). Set to 0 to keep them until dismissed. Default: 0.", + "notify_channel": "Android companion app notification channel for status, live progress, and reminder notifications. The sound and importance of a channel are configured in the companion app the first time the channel name is used - WashData only sets the channel name. Leave empty for the app default.", + "notify_finish_channel": "Separate Android channel for the finished alert (also used by the reminder and the laundry-waiting nag) so it can have its own sound. Leave empty to reuse the channel above.", + "energy_price_entity": "Select a numeric entity that holds the current electricity price (e.g. sensor.electricity_price, input_number.energy_rate). When set, enables the {cost} placeholder. Takes precedence over the static value if both are configured.", + "energy_price_static": "Fixed electricity price per kWh. When set, enables the {cost} placeholder. Ignored if an entity is configured above. Add your currency symbol in the message template, e.g. {cost} €." + } + }, + "advanced_settings": { + "title": "Advanced Settings", + "description": "Tune detection thresholds, learning, and advanced behavior. Defaults work well for most setups.", + "sections": { + "suggestions_section": { + "name": "Suggested Settings", + "description": "{suggestions_count} learned suggestions available. Tick Apply Suggested Values to review proposed changes before saving.", + "data": { + "apply_suggestions": "Apply Suggested Values" + }, + "data_description": { + "apply_suggestions": "Check this box to open a review step for suggested values from the learning algorithm. Nothing is saved automatically.\n\nSuggested (from learning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detection & Power Thresholds", + "description": "When the appliance is considered ON or OFF, energy gates, and timing for state transitions.", + "data": { + "start_duration_threshold": "Start Debounce Duration (s)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Completion Minimum Runtime (seconds)", + "start_threshold_w": "Start Threshold (W)", + "stop_threshold_w": "Stop Threshold (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Running Dead Zone (seconds)", + "end_repeat_count": "End Repeat Count", + "min_off_gap": "Min Gap Between Cycles (s)", + "sampling_interval": "Sampling Interval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignore brief power spikes shorter than this duration (seconds). Filters out boot spikes (e.g., 60W for 2s) before real cycle starts. Default: 5s.", + "start_energy_threshold": "Cycle must accumulate at least this much energy (Wh) during the START phase to be confirmed. Default: 0.2 Wh.", + "completion_min_seconds": "Cycles shorter than this will be marked as 'interrupted' even if they finish naturally. Default: 600s.", + "start_threshold_w": "Power must rise ABOVE this threshold to START a cycle. Set higher than your standby power. Default: min_power + 1 W.", + "stop_threshold_w": "Power must fall BELOW this threshold to END a cycle. Set just above zero to avoid noise triggering false ends. Default: 0.6 * min_power.", + "end_energy_threshold": "Cycle ends only if energy in the last Off-Delay window is below this value (Wh). Prevents false ends if power fluctuates near zero. Default: 0.05 Wh.", + "running_dead_zone": "After cycle starts, ignore power dips for this many seconds to prevent false end detection during initial spin-up phase. Set to 0 to disable. Default: 3s.", + "end_repeat_count": "Number of times the end condition (power below threshold for off_delay) must be met consecutively before ending the cycle. Higher values prevent false ends during pauses. Default: 1.", + "min_off_gap": "If a cycle starts within this many seconds of the previous one ending, they will be MERGED into a single cycle.", + "sampling_interval": "Minimum sampling interval in seconds. Updates faster than this rate will be ignored. Default: 30s (2s for washing machines, washer-dryers, and dishwashers)." + } + }, + "matching_section": { + "name": "Profile Matching & Learning", + "description": "How aggressively WashData matches running cycles against learned profiles and when to ask for feedback.", + "data": { + "profile_match_min_duration_ratio": "Profile Match Min Duration Ratio (0.1-1.0)", + "profile_match_interval": "Profile Match Interval (seconds)", + "profile_match_threshold": "Profile Match Threshold", + "profile_unmatch_threshold": "Profile Unmatch Threshold", + "auto_label_confidence": "Auto-Label Confidence (0-1)", + "learning_confidence": "Feedback Request Confidence (0-1)", + "suppress_feedback_notifications": "Suppress Feedback Notifications", + "duration_tolerance": "Duration Tolerance (0-0.5)", + "smoothing_window": "Smoothing Window (samples)", + "profile_duration_tolerance": "Profile Match Duration Tolerance (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum duration ratio for profile matching. Running cycle must be at least this fraction of profile duration to match. Lower = earlier matching. Default: 0.10 (10%).", + "profile_match_interval": "How often (in seconds) to attempt profile matching during a cycle. Lower values provide faster detection but use more CPU. Default: 300s (5 minutes).", + "profile_match_threshold": "Minimum DTW similarity score (0.0–1.0) required to consider a profile as a match. Higher = stricter matching, fewer false positives. Default: 0.4.", + "profile_unmatch_threshold": "DTW similarity score (0.0–1.0) below which a previously matched profile is rejected. Should be lower than match threshold to prevent flickering. Default: 0.35.", + "auto_label_confidence": "Automatically label cycles at or above this confidence on completion (0.0–1.0).", + "learning_confidence": "Minimum confidence to request user verification via an event (0.0–1.0).", + "suppress_feedback_notifications": "When enabled, WashData will still track and request feedback internally but will not show persistent notifications asking you to confirm cycles. Useful when cycles are always detected correctly and you find the prompts distracting.", + "duration_tolerance": "Tolerance for time-remaining estimates during a run (0.0–0.5 corresponds to 0–50%).", + "smoothing_window": "Number of recent power readings used for moving-average smoothing.", + "profile_duration_tolerance": "Tolerance used by profile matching for total duration variance (0.0–0.5). Default behavior: ±25%." + } + }, + "timing_section": { + "name": "Timing, Maintenance & Debug", + "description": "Watchdog, progress reset, auto-maintenance, and debug entity exposure.", + "data": { + "watchdog_interval": "Watchdog Interval (seconds)", + "no_update_active_timeout": "No-Update Timeout (s)", + "progress_reset_delay": "Progress Reset Delay (seconds)", + "auto_maintenance": "Enable Auto-Maintenance", + "expose_debug_entities": "Expose Debug Entities", + "save_debug_traces": "Save Debug Traces" + }, + "data_description": { + "watchdog_interval": "Seconds between watchdog checks while running. Default: 30s. WARNING: Ensure this is HIGHER than your sensor's update interval to avoid false stops.", + "no_update_active_timeout": "For publish-on-change sensors: only force-end an ACTIVE cycle if no updates arrive for this many seconds. Low-power completion still uses the Off Delay.", + "progress_reset_delay": "After a cycle completes (100%), wait this many seconds of idle before resetting progress to 0%. Default: 1800s.", + "auto_maintenance": "Enable auto-maintenance (repair samples and perform routine cleanup).", + "expose_debug_entities": "Show advanced sensors (Confidence, Phase, Ambiguity) for debugging.", + "save_debug_traces": "Store detailed ranking and power trace data in history (Increases storage usage)." + } + }, + "anti_wrinkle_section": { + "name": "Anti-Wrinkle Shield (Dryers)", + "description": "Ignore post-cycle drum rotations to prevent ghost cycles. Dryer / washer-dryer only.", + "data": { + "anti_wrinkle_enabled": "Anti-Wrinkle Shield (Dryer/Washer-Dryer Only)", + "anti_wrinkle_max_power": "Anti-Wrinkle Max Power (W)", + "anti_wrinkle_max_duration": "Anti-Wrinkle Max Duration (s)", + "anti_wrinkle_exit_power": "Anti-Wrinkle Exit Power (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignore short low-power drum rotations after a cycle ends to prevent ghost cycles (dryer/washer-dryer only).", + "anti_wrinkle_max_power": "Bursts at or below this power are treated as anti-wrinkle rotations instead of new cycles. Only applies when Anti-Wrinkle Shield is enabled.", + "anti_wrinkle_max_duration": "Maximum burst length to treat as anti-wrinkle rotation. Only applies when Anti-Wrinkle Shield is enabled.", + "anti_wrinkle_exit_power": "If power drops below this level, exit anti-wrinkle immediately (true off). Only applies when Anti-Wrinkle Shield is enabled." + } + }, + "delay_start_section": { + "name": "Delayed Start Detection", + "description": "Treat sustained low-power standby as 'Waiting to Start' until the cycle actually begins.", + "data": { + "delay_start_detect_enabled": "Enable Delayed Start Detection", + "delay_confirm_seconds": "Delayed Start: Standby Confirm Time (s)", + "delay_timeout_hours": "Delayed Start: Max Wait Time (h)" + }, + "data_description": { + "delay_start_detect_enabled": "When enabled, sustained low power between Stop Threshold and Start Threshold (W) is treated as a delayed start. The state sensor shows 'Waiting to Start' until power crosses Start Threshold (W) for long enough to count as a real cycle. Tune Start Threshold (W) above any background tumble/anti-damp power your appliance draws while waiting.", + "delay_confirm_seconds": "Power must stay between Stop Threshold (W) and Start Threshold (W) for this many seconds before WashData enters 'Waiting to Start'. Filters out brief menu-navigation peaks. Default: 60 s.", + "delay_timeout_hours": "Safety timeout: if the machine is still in 'Waiting to Start' after this many hours, WashData resets to Off. Default: 8 h." + } + }, + "external_triggers_section": { + "name": "External Triggers, Door & Pause", + "description": "Optional external end-trigger sensor, door sensor for pause/clean detection, and pause-power-cut switch.", + "data": { + "external_end_trigger_enabled": "Enable External Cycle End Trigger", + "external_end_trigger": "External Cycle End Sensor", + "external_end_trigger_inverted": "Invert Trigger Logic (Trigger on OFF)", + "door_sensor_entity": "Door Sensor", + "pause_cuts_power": "Cut Power When Pausing", + "switch_entity": "Switch Entity (for Pause Power Cut)", + "notify_unload_delay_minutes": "Laundry Waiting Notification Delay (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Enable monitoring of an external binary sensor to trigger cycle completion.", + "external_end_trigger": "Select a binary sensor entity. When this sensor triggers, the current cycle will end with status 'completed'.", + "external_end_trigger_inverted": "By default, the trigger fires when the sensor turns ON. Check this box to fire when the sensor turns OFF instead.", + "door_sensor_entity": "Optional binary sensor for the machine door (on = open, off = closed). When the door opens during an active cycle, WashData will confirm the cycle as intentionally paused. After the cycle ends, if the door is still closed, the state changes to 'Clean' until the door is opened. Note: closing the door does NOT auto-resume a paused cycle - resume must be triggered manually via the Resume Cycle button or service.", + "pause_cuts_power": "When pausing via the Pause Cycle button or service, also turn off the configured switch entity. Only takes effect if a switch entity is configured for this device.", + "switch_entity": "The switch entity to toggle when pausing or resuming. Required when 'Cut Power When Pausing' is enabled.", + "notify_unload_delay_minutes": "Send a notification via the finish notification channel after the cycle ends and the door has not been opened for this many minutes (requires Door Sensor). Set to 0 to disable. Default: 60 min." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Optionally link this WashData device to an existing Home Assistant device (for example the smart plug or the appliance itself). The WashData device is then shown as \"Connected via\" that device. Leave empty to keep WashData as a standalone device.", + "data": { + "linked_device": "Linked Device" + }, + "data_description": { + "linked_device": "Select the device to connect WashData to. This adds a 'Connected via' reference in the device registry; WashData keeps its own device card and entities. Clear the selection to remove the link." + } + }, + "pump_section": { + "name": "Pump Monitor", + "description": "Pump device type only. Fires an event if a pump cycle runs past this duration.", + "data": { + "pump_stuck_duration": "Pump Stuck Alert Threshold (s) (Pump Only)" + }, + "data_description": { + "pump_stuck_duration": "If a pump cycle is still running after this many seconds, a ha_washdata_pump_stuck event is fired once. Use this to detect a jammed motor (typical sump pump run is under 60 s). Default: 1800 s (30 min). Pump device type only." + } + } + } + }, + "diagnostics": { + "title": "Diagnostics & Maintenance", + "description": "Run maintenance actions like merging fragmented cycles or migrating stored data.\n\n**Storage Usage**\n\n- File Size: {file_size_kb} KB\n- Cycles: {cycle_count}\n- Profiles: {profile_count}\n- Debug Traces: {debug_count}", + "menu_options": { + "reprocess_history": "Maintenance: Reprocess & Optimize Data", + "clear_debug_data": "Clear Debug Data (Free up space)", + "wipe_history": "Wipe ALL data for this device (irreversible)", + "export_import": "Export/Import JSON with settings (copy/paste)", + "menu_back": "← Back" + } + }, + "clear_debug_data": { + "title": "Clear Debug Data", + "description": "Are you sure you want to delete all stored debug traces? This will free up space but remove detailed ranking info from past cycles." + }, + "manage_cycles": { + "title": "Manage Cycles", + "description": "### Recent cycles\n\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "select_cycle_to_label": "Label Specific Cycle", + "select_cycle_to_delete": "Delete Cycle", + "interactive_editor": "Merge/Split Interactive Editor", + "trim_cycle_select": "Trim Cycle Data", + "menu_back": "← Back" + } + }, + "manage_cycles_empty": { + "title": "Manage Cycles", + "description": "No cycles recorded yet.", + "menu_options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "menu_back": "← Back" + } + }, + "manage_profiles": { + "title": "Manage Profiles", + "description": "Profile Summary:\n{profile_summary}", + "menu_options": { + "create_profile": "Create New Profile", + "edit_profile": "Edit/Rename Profile", + "delete_profile_select": "Delete Profile", + "profile_stats": "Profile Statistics", + "cleanup_profile": "Clean Up History - Graph & Delete", + "assign_profile_phases_select": "Assign Phase Ranges", + "menu_back": "← Back" + } + }, + "manage_profiles_empty": { + "title": "Manage Profiles", + "description": "No profiles created yet.", + "menu_options": { + "create_profile": "Create New Profile", + "menu_back": "← Back" + } + }, + "manage_phase_catalog": { + "title": "Manage Phase Catalog", + "description": "Phase catalog (defaults for this device + custom phases):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Create New Phase", + "phase_catalog_edit_select": "Edit Phase", + "phase_catalog_delete": "Delete Phase", + "menu_back": "← Back" + } + }, + "phase_catalog_create": { + "title": "Create Custom Phase", + "description": "Create a custom phase name and description, and choose which device type it applies to.", + "data": { + "target_device_type": "Target Device Type", + "phase_name": "Phase Name", + "phase_description": "Phase Description" + } + }, + "phase_catalog_edit_select": { + "title": "Edit Custom Phase", + "description": "Select a custom phase to edit.", + "data": { + "phase_name": "Custom Phase" + } + }, + "phase_catalog_edit": { + "title": "Edit Custom Phase", + "description": "Editing phase: {phase_name}", + "data": { + "phase_name": "Phase Name", + "phase_description": "Phase Description" + } + }, + "phase_catalog_delete": { + "title": "Delete Custom Phase", + "description": "Select a custom phase to delete. Any assigned ranges using this phase will be removed.", + "data": { + "phase_name": "Custom Phase" + } + }, + "assign_profile_phases": { + "title": "Assign Phase Ranges", + "description": "Phase preview for profile: **{profile_name}**\n\n{timeline_svg}\n\n**Current ranges:**\n{current_ranges}\n\nUse actions below to add, edit, delete, or save ranges.", + "menu_options": { + "assign_profile_phases_add": "Add Phase Range", + "assign_profile_phases_edit_select": "Edit Phase Range", + "assign_profile_phases_delete": "Delete Phase Range", + "phase_ranges_clear": "Clear All Ranges", + "assign_profile_phases_auto_detect": "Auto-detect Phases", + "phase_ranges_save": "Save and Return", + "menu_back": "← Back" + } + }, + "assign_profile_phases_add": { + "title": "Add Phase Range", + "description": "Add one phase range to the current draft.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minute", + "end_min": "End Minute" + } + }, + "assign_profile_phases_edit_select": { + "title": "Edit Phase Range", + "description": "Select a range to edit.", + "data": { + "range_index": "Phase Range" + } + }, + "assign_profile_phases_edit": { + "title": "Edit Phase Range", + "description": "Update the selected phase range.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minute", + "end_min": "End Minute" + } + }, + "assign_profile_phases_delete": { + "title": "Delete Phase Range", + "description": "Select a range to remove from the draft.", + "data": { + "range_index": "Phase Range" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detected Phases", + "description": "Profile: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} phase(s) detected automatically.** Choose an action below.", + "data": { + "action": "Action" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Name Detected Phases", + "description": "Profile: **{profile_name}**\n\n**{detected_count} phase(s) detected:**\n{ranges_summary}\n\nAssign a name to each phase." + }, + "assign_profile_phases_select": { + "title": "Assign Phase Ranges", + "description": "Select a profile to configure phase ranges.", + "data": { + "profile": "Profile" + } + }, + "profile_stats": { + "title": "Profile Statistics", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Create New Profile", + "description": "Create a new profile manually or from a past cycle.\n\nProfile name examples: 'Cotton 40°C', 'Eco', 'Quick Wash', 'Cotton + Cupboard Dry'.\n\n**What the matcher uses:** power-consumption shape, total cycle duration, and total energy. It does not read temperature or spin settings directly.\n\n**Profiles that will be reliably auto-detected:** programs with clearly different durations or power patterns - e.g. a 20-minute Quick Wash vs a 3-hour Cotton cycle, or a wash-only cycle vs a wash+dry combo (which typically runs 2-3× longer).\n\n**Profiles that may need manual selection:** variants of the same program that differ only in temperature or spin speed (e.g. Cotton 40°C vs Cotton 60°C) often produce similar power shapes. The system will still attempt to distinguish them and learn from your corrections over time, but expect to confirm matches manually at first.\n\n**Washer-dryer tip:** create separate profiles for wash-only and wash+dry runs - the duration and energy difference is large enough for reliable auto-detection.", + "data": { + "profile_name": "Profile Name", + "reference_cycle": "Reference Cycle (optional)", + "manual_duration": "Manual Duration (minutes)" + } + }, + "edit_profile": { + "title": "Edit Profile", + "description": "Select a profile to rename", + "data": { + "profile": "Profile" + } + }, + "rename_profile": { + "title": "Edit Profile Details", + "description": "Current profile: {current_name}", + "data": { + "new_name": "Profile Name", + "manual_duration": "Manual Duration (minutes)" + } + }, + "delete_profile_select": { + "title": "Delete Profile", + "description": "Select a profile to delete", + "data": { + "profile": "Profile" + } + }, + "delete_profile_confirm": { + "title": "Confirm Delete Profile", + "description": "⚠️ This will permanently delete profile: {profile_name}", + "data": { + "unlabel_cycles": "Remove label from cycles using this profile" + } + }, + "auto_label_cycles": { + "title": "Auto-Label Old Cycles", + "description": "Found {total_count} total cycles. Profiles: {profiles}", + "data": { + "confidence_threshold": "Minimum Confidence (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Select Cycle to Label", + "description": "✓ = completed, ⚠ = resumed, ✗ = interrupted", + "data": { + "cycle_id": "Cycle" + } + }, + "select_cycle_to_delete": { + "title": "Delete Cycle", + "description": "⚠️ This will permanently delete the selected cycle", + "data": { + "cycle_id": "Cycle to Delete" + } + }, + "label_cycle": { + "title": "Label Cycle", + "description": "{cycle_info}", + "data": { + "profile_name": "Profile", + "new_profile_name": "New Profile Name (if creating)" + } + }, + "post_process": { + "title": "Process History (Merge/Split)", + "description": "Clean up cycle history by merging fragmented runs and splitting incorrectly merged cycles within a time window. Enter number of hours to look back (use 999999 for all).", + "data": { + "time_range": "Lookback Window (hours)", + "gap_seconds": "Merge/Split Gap (seconds)" + }, + "data_description": { + "time_range": "Number of hours to scan for fragmented cycles to merge. Use 999999 to process all historical data.", + "gap_seconds": "Threshold to decide split vs merge. Gaps SHORTER than this are merged. Gaps LONGER than this are split. Lower this to force a split on a cycle that was merged incorrectly." + } + }, + "cleanup_profile": { + "title": "Clean Up History - Select Profile", + "description": "Select a profile to visualize. This will generate a graph showing all past cycles for this profile to help identify outliers.", + "data": { + "profile": "Profile" + } + }, + "cleanup_select": { + "title": "Clean Up History - Graph & Delete", + "description": "![Graph]({graph_url})\n\n**Visualizing {profile_name}**\n\nThe graph shows individual cycles as colored lines. Identify outliers (lines far from the group) and matching their color in the list below to delete them.\n\n**Select cycles to PERMANENTLY delete:**", + "data": { + "cycles_to_delete": "Cycles to Delete (check matching colors)" + } + }, + "interactive_editor": { + "title": "Merge/Split Interactive Editor", + "description": "Select a manual action to perform on your cycle history.", + "menu_options": { + "editor_split": "Split a Cycle (Find gaps)", + "editor_merge": "Merge Cycles (Join fragments)", + "editor_delete": "Delete Cycle(s)", + "menu_back": "← Back" + } + }, + "editor_select": { + "title": "Select Cycles", + "description": "Select the cycle(s) to process.\n\n{info_text}", + "data": { + "selected_cycles": "Cycles" + } + }, + "editor_configure": { + "title": "Configure & Apply", + "description": "{preview_md}", + "data": { + "confirm_action": "Action", + "merged_profile": "Resulting Profile", + "new_profile_name": "New Profile Name", + "confirm_commit": "Yes, I confirm this action", + "segment_0_profile": "Segment 1 Profile", + "segment_1_profile": "Segment 2 Profile", + "segment_2_profile": "Segment 3 Profile", + "segment_3_profile": "Segment 4 Profile", + "segment_4_profile": "Segment 5 Profile", + "segment_5_profile": "Segment 6 Profile", + "segment_6_profile": "Segment 7 Profile", + "segment_7_profile": "Segment 8 Profile", + "segment_8_profile": "Segment 9 Profile", + "segment_9_profile": "Segment 10 Profile" + } + }, + "editor_split_params": { + "title": "Split Method", + "description": "Choose how to split this cycle. **Auto-detect** finds idle gaps in the power data; **Manual timestamp** lets you set explicit split points (useful when one cycle ran straight into the next with no idle gap between them).", + "data": { + "split_mode": "Split Method" + } + }, + "editor_split_auto_params": { + "title": "Auto-Split Configuration", + "description": "Adjust the sensitivity for splitting this cycle. Any idle gap longer than this will cause a split.", + "data": { + "min_gap_seconds": "Split Gap Threshold (seconds)" + } + }, + "editor_split_manual_params": { + "title": "Manual Split Timestamps", + "description": "{preview_md}Cycle window: **{cycle_start_wallclock} → {cycle_end_wallclock}** on {cycle_date}.\n\nEnter one or more split timestamps (`HH:MM` or `HH:MM:SS`), one per line. The cycle will be cut at each timestamp — N timestamps produce N+1 segments.", + "data": { + "split_timestamps": "Split Timestamps" + } + }, + "reprocess_history": { + "title": "Maintenance: Reprocess & Optimize Data", + "description": "Performs deep cleaning of historical data:\n1. Trims zero-power readings (silence).\n2. Fixes cycle timing/duration.\n3. Recalculates all statistical models (envelopes).\n\n**Run this to fix data issues or apply new algorithms.**" + }, + "wipe_history": { + "title": "Wipe History (Testing Only)", + "description": "⚠️ This will permanently delete ALL stored cycles and profiles for this device. This cannot be undone!" + }, + "export_import": { + "title": "Export/Import JSON", + "description": "Copy/paste cycles, profiles, AND settings for this device. Export below or paste JSON to import.", + "data": { + "mode": "Action", + "json_payload": "Complete Configuration JSON" + }, + "data_description": { + "mode": "Choose Export to copy current data and settings, or Import to paste exported data from another WashData device.", + "json_payload": "For Export: copy this JSON (includes cycles, profiles, and all fine-tuned settings). For Import: paste JSON exported from WashData." + } + }, + "record_cycle": { + "title": "Record Cycle", + "description": "Manually record a cycle to create a clean profile without interference.\n\nStatus: **{status}**\nDuration: {duration}s\nSamples: {samples}", + "menu_options": { + "record_refresh": "Refresh Status", + "record_stop": "Stop Recording (Save & Process)", + "record_start": "Start New Recording", + "record_process": "Process Last Recording (Trim & Save)", + "record_discard": "Discard Last Recording", + "menu_back": "← Back" + } + }, + "record_process": { + "title": "Process Recording", + "description": "![Graph]({graph_url})\n\n**Graph shows raw recording.** Blue = Keep, Red = Proposed Trim.\n*Graph is static and does not update with form changes.*\n\nRecording stopped. Trims are aligned to the detected sampling rate (~{sampling_rate}s).\n\nRaw Duration: {duration}s\nSamples: {samples}", + "data": { + "head_trim": "Trim Start (seconds)", + "tail_trim": "Trim End (seconds)", + "save_mode": "Save Destination", + "profile_name": "Profile Name" + } + }, + "learning_feedbacks": { + "title": "Learning Feedbacks", + "description": "{count} pending review request(s).\n\n{pending_table}", + "menu_options": { + "learning_feedbacks_pick": "Review a pending feedback", + "learning_feedbacks_dismiss_all": "Dismiss all pending feedback", + "menu_back": "← Back" + } + }, + "learning_feedbacks_pick": { + "title": "Pick Feedback to Review", + "description": "Select a cycle review request to open.", + "data": { + "selected_feedback": "Select Cycle to Review" + } + }, + "learning_feedbacks_empty": { + "title": "Learning Feedbacks", + "description": "No pending feedback requests found.", + "menu_options": { + "menu_back": "← Back" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Dismiss All Pending Feedbacks", + "description": "You are about to dismiss **{count}** pending feedback request(s). Dismissed cycles remain in your history but will not contribute new learning signal. This cannot be undone.\n\n✅ Check the box below and click **Submit** to dismiss all.\n❌ Leave it unchecked and click **Submit** to cancel and go back.", + "data": { + "confirm_dismiss_all": "Confirm: dismiss all pending feedback requests" + } + }, + "resolve_feedback": { + "title": "Resolve Feedback", + "description": "{comparison_img}{candidates_table}\n**Detected Profile**: {detected_profile} ({confidence_pct}%)\n**Estimated Duration**: {est_duration_min} min\n**Actual Duration**: {act_duration_min} min\n\n__Choose an action below:__", + "data": { + "action": "What would you like to do?", + "corrected_profile": "Correct Program (if correcting)", + "corrected_duration": "Correct Duration in minutes (if correcting)" + } + }, + "trim_cycle_select": { + "title": "Trim Cycle - Select Cycle", + "description": "Select a cycle to trim. ✓ = completed, ⚠ = resumed, ✗ = interrupted", + "data": { + "cycle_id": "Cycle" + } + }, + "trim_cycle": { + "title": "Trim Cycle", + "description": "Current window: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Action" + } + }, + "trim_cycle_start": { + "title": "Set Trim Start", + "description": "Cycle window: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nCurrent start: **{current_wallclock}** (+{current_offset_min} min from cycle start)\n\nPick a new start time using the clock picker below.", + "data": { + "trim_start_time": "New start time", + "trim_start_min": "New start (minutes from cycle start)" + } + }, + "trim_cycle_end": { + "title": "Set Trim End", + "description": "Cycle window: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nCurrent end: **{current_wallclock}** (+{current_offset_min} min from cycle start)\n\nPick a new end time using the clock picker below.", + "data": { + "trim_end_time": "New end time", + "trim_end_min": "New end (minutes from cycle start)" + } + } + }, + "error": { + "import_failed": "Import failed. Please paste a valid WashData export JSON.", + "select_exactly_one": "Select exactly one cycle for this action.", + "select_at_least_one": "Select at least one cycle for this action.", + "select_at_least_two": "Select at least two cycles for this action.", + "profile_exists": "A profile with this name already exists", + "rename_failed": "Failed to rename profile. Profile may not exist or new name is already taken.", + "assignment_failed": "Failed to assign profile to cycle", + "duplicate_phase": "A phase with this name already exists.", + "phase_not_found": "Selected phase was not found.", + "invalid_phase_name": "Phase name cannot be empty.", + "phase_name_too_long": "Phase name is too long.", + "invalid_phase_range": "Phase range is invalid. End must be greater than start.", + "invalid_phase_timestamp": "Timestamp is invalid. Use YYYY-MM-DD HH:MM or HH:MM format.", + "overlapping_phase_ranges": "Phase ranges overlap. Adjust the ranges and try again.", + "incomplete_phase_row": "Each phase row must include a phase plus complete start/end values for the selected mode.", + "timestamp_mode_cycle_required": "Please select a source cycle when using timestamp mode.", + "no_phase_ranges": "No phase ranges are available for that action yet.", + "feedback_notification_title": "WashData: Verify Cycle ({device})", + "feedback_notification_message": "**Device**: {device}\n**Program**: {program} ({confidence}% confidence)\n**Time**: {time}\n\nWashData needs your help to verify this detected cycle.\n\nPlease go to **Settings > Devices & Services > WashData > Configure > Learning Feedbacks** to confirm or correct this result.", + "suggestions_ready_notification_title": "WashData: Suggested Settings Ready ({device})", + "suggestions_ready_notification_message": "The **Suggested Settings** sensor now reports **{count}** actionable recommendations.\n\nTo review and apply them: **Settings > Devices & Services > WashData > Configure > Advanced Settings > Apply Suggested Values**.\n\nSuggestions are optional and shown for review before you save.", + "trim_range_invalid": "Start must be before end. Please adjust your trim points.", + "trim_failed": "Trim failed. The cycle may no longer exist or the window is empty.", + "invalid_split_timestamp": "Timestamp is invalid or outside the cycle window. Use HH:MM or HH:MM:SS within the cycle's window.", + "no_split_segments_found": "No valid segments could be produced from these timestamps. Make sure each timestamp is inside the cycle window and segments are at least 1 minute long.", + "auto_tune_suggestion": "{device_type} {device_title} detected ghost cycles. Suggested min_power change: {current_min}W -> {new_min}W (not applied automatically).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} detected ghost cycles. Suggested min_power change: {current_min}W -> {new_min}W (not applied automatically)." + }, + "abort": { + "no_cycles_found": "No cycles found", + "no_split_segments_found": "No splittable segments were found in the selected cycle.", + "cycle_not_found": "The selected cycle could not be loaded.", + "no_power_data": "No power trace data available for the selected cycle.", + "no_profiles_found": "No profiles found. Create a profile first.", + "no_profiles_for_matching": "No profiles available for matching. Create profiles first.", + "no_unlabeled_cycles": "All cycles are already labeled", + "no_suggestions": "No suggested values available yet. Run a few cycles and try again.", + "no_predictions": "No predictions", + "no_custom_phases": "No custom phases found.", + "reprocess_success": "Successfully reprocessed {count} cycles.", + "debug_data_cleared": "Successfully cleared debug data from {count} cycles." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cycle Start", + "cycle_finish": "Cycle Finish", + "cycle_live": "Live Progress" + } + }, + "device_type": { + "options": { + "washing_machine": "Washing Machine", + "dryer": "Dryer", + "washer_dryer": "Washer-Dryer Combo", + "dishwasher": "Dishwasher", + "coffee_machine": "Coffee Machine (deprecated)", + "ev": "Electric Vehicle (deprecated)", + "air_fryer": "Air Fryer", + "heat_pump": "Heat Pump (deprecated)", + "bread_maker": "Bread Maker", + "pump": "Pump / Sump Pump", + "oven": "Oven (deprecated)", + "other": "Other (Advanced)" + } + }, + "common_text": { + "options": { + "unlabeled": "(Unlabeled)", + "create_new_profile": "Create New Profile", + "remove_label": "Remove Label", + "no_reference_cycle": "(No reference cycle)", + "all_device_types": "All Device Types", + "current_suffix": "(current)", + "phase_builtin_suffix": "(Built-in)", + "phase_none_available": "No phases available.", + "phase_other_device_types": "Other device types:", + "settings_deprecation_warning": "⚠️ **Deprecated device type:** {device_type} is scheduled for removal in a future release. WashData's matching pipeline does not produce reliable results for this appliance class. Your integration keeps working through the deprecation period; to silence this warning, switch **Device Type** below to one of the supported types (Washing Machine, Dryer, Washer-Dryer Combo, Dishwasher, Air Fryer, Bread Maker, or Pump), or to **Other (Advanced)** if your appliance does not match any of the supported types. **Other (Advanced)** ships intentionally generic defaults that are not tuned for any specific appliance, so you will need to configure thresholds, timeouts, and matching parameters yourself; all your existing settings are preserved when you switch.", + "editor_select_info": "Select 1 cycle to split, or 2+ cycles to merge.", + "editor_select_info_split": "Select 1 cycle to split.", + "editor_select_info_merge": "Select 2+ cycles to merge.", + "editor_select_info_delete": "Select 1+ cycle(s) to delete.", + "no_cycles_recorded": "No cycles recorded yet.", + "profile_summary_line": "- **{name}**: {count} cycles, {avg}m avg", + "no_profiles_created": "No profiles created yet.", + "phase_preview": "Phase Preview", + "phase_preview_no_curve": "Average profile curve is not available yet. Run/label more cycles for this profile.", + "average_power_curve": "Average Power Curve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cycles, ~{duration}m avg)", + "profile_option_short_fmt": "{name} ({count} cycles, ~{duration}m)", + "deleted_cycles_from_profile": "Deleted {count} cycles from {profile}.", + "not_enough_data_graph": "Not enough data to generate graph.", + "no_profiles_found": "No profiles found.", + "cycle_info_fmt": "Cycle: {start}, {duration}m, Current: {label}", + "top_candidates_header": "### Top Candidates", + "tbl_profile": "Profile", + "tbl_confidence": "Confidence", + "tbl_correlation": "Correlation", + "tbl_duration_match": "Duration Match", + "detected_profile": "Detected Profile", + "estimated_duration": "Estimated Duration", + "actual_duration": "Actual Duration", + "choose_action": "__Choose an action below:__", + "tbl_count": "Count", + "tbl_avg": "Avg", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energy (Avg)", + "tbl_energy_total": "Energy (Total)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Last Run", + "graph_legend_title": "Graph Legend", + "graph_legend_body": "The blue band represents the minimum and maximum power draw range observed. The line shows the average power curve.", + "recording_preview": "Recording Preview", + "trim_start": "Trim Start", + "trim_end": "Trim End", + "split_preview_title": "Split Preview", + "split_preview_found_fmt": "Found {count} segments.", + "split_preview_confirm_fmt": "Click Confirm to split this cycle into {count} separate cycles.", + "merge_preview_title": "Merge Preview", + "merge_preview_joining_fmt": "Joining {count} cycles. Gaps will be filled with 0W readings.", + "editor_delete_preview_title": "Delete Preview", + "editor_delete_preview_intro": "The selected cycles will be permanently deleted:", + "editor_delete_preview_confirm": "Click Confirm to permanently delete these cycle records.", + "no_power_preview": "*No power data available for preview.*", + "profile_comparison": "Profile Comparison", + "actual_cycle_label": "This cycle (actual)", + "storage_usage_header": "Storage Usage", + "storage_file_size": "File Size", + "storage_cycles": "Cycles", + "storage_profiles": "Profiles", + "storage_debug_traces": "Debug Traces", + "overview_suffix": "Overview", + "phase_preview_for_profile": "Phase preview for profile", + "current_ranges_header": "Current ranges", + "assign_phases_help": "Use actions below to add, edit, delete, or save ranges.", + "profile_summary_header": "Profile Summary", + "recent_cycles_header": "Recent cycles", + "trim_cycle_preview_title": "Cycle Trim Preview", + "trim_cycle_preview_no_data": "No power data available for this cycle.", + "trim_cycle_preview_kept_suffix": "kept", + "table_program": "Program", + "table_when": "When", + "table_length": "Length", + "table_match": "Match", + "table_profile": "Profile", + "table_cycles": "Cycles", + "table_avg_length": "Avg Length", + "table_last_run": "Last Run", + "table_avg_energy": "Avg Energy", + "table_detected_program": "Detected Program", + "table_confidence": "Confidence", + "table_reported": "Reported" + } + }, + "interactive_editor_action": { + "options": { + "split": "Split a Cycle (Find gaps)", + "merge": "Merge Cycles (Join fragments)", + "delete": "Delete Cycle(s)" + } + }, + "split_mode": { + "options": { + "auto": "Auto-detect idle gaps", + "manual": "Manual timestamp(s)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Maintenance: Reprocess & Optimize Data", + "clear_debug_data": "Clear Debug Data (Free up space)", + "wipe_history": "Wipe ALL data for this device (irreversible)", + "export_import": "Export/Import JSON with settings (copy/paste)" + } + }, + "export_import_mode": { + "options": { + "export": "Export All Data", + "import": "Import/Merge Data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Auto-Label Old Cycles", + "select_cycle_to_label": "Label Specific Cycle", + "select_cycle_to_delete": "Delete Cycle", + "interactive_editor": "Merge/Split Interactive Editor", + "trim_cycle": "Trim Cycle Data" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Create New Profile", + "edit_profile": "Edit/Rename Profile", + "delete_profile": "Delete Profile", + "profile_stats": "Profile Statistics", + "cleanup_profile": "Clean Up History - Graph & Delete", + "assign_phases": "Assign Phase Ranges" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Create New Phase", + "edit_custom_phase": "Edit Phase", + "delete_custom_phase": "Delete Phase" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Name & apply phases", + "cancel": "Go back without changes" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Add Phase Range", + "edit_range": "Edit Phase Range", + "delete_range": "Delete Phase Range", + "clear_ranges": "Clear All Ranges", + "auto_detect_ranges": "Auto-detect Phases", + "save_ranges": "Save and Return" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Refresh Status", + "stop_recording": "Stop Recording (Save & Process)", + "start_recording": "Start New Recording", + "process_recording": "Process Last Recording (Trim & Save)", + "discard_recording": "Discard Last Recording" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Create New Profile", + "existing_profile": "Add to Existing Profile", + "discard": "Discard Recording" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirm - Correct Detection", + "correct": "Correct - Choose Program/Duration", + "ignore": "Ignore - False Positive/Noisy Cycle", + "delete": "Delete - Remove from History" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Set Start Point", + "set_end": "Set End Point", + "reset": "Reset to Full Duration", + "apply": "Apply Trim", + "cancel": "Cancel" + } + } + }, + "services": { + "label_cycle": { + "name": "Label Cycle", + "description": "Assign an existing profile to a past cycle, or remove the label.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to label." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The ID of the cycle to label." + }, + "profile_name": { + "name": "Profile Name", + "description": "The name of an existing profile (create profiles in Manage Profiles menu). Leave blank to remove label." + } + } + }, + "create_profile": { + "name": "Create Profile", + "description": "Create a new profile (standalone or based on a reference cycle).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "profile_name": { + "name": "Profile Name", + "description": "Name for the new profile (e.g. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Reference Cycle ID", + "description": "Optional cycle ID to base this profile on." + } + } + }, + "delete_profile": { + "name": "Delete Profile", + "description": "Delete a profile and optionally unlabel cycles using it.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "profile_name": { + "name": "Profile Name", + "description": "The profile to delete." + }, + "unlabel_cycles": { + "name": "Unlabel Cycles", + "description": "Remove profile label from cycles using this profile." + } + } + }, + "auto_label_cycles": { + "name": "Auto-Label Old Cycles", + "description": "Retroactively label unlabeled cycles using profile matching.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "confidence_threshold": { + "name": "Confidence Threshold", + "description": "Minimum match confidence (0.50-0.95) to apply labels." + } + } + }, + "export_config": { + "name": "Export Config", + "description": "Export this device's profiles, cycles, and settings to a JSON file (per device).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to export." + }, + "path": { + "name": "Path", + "description": "Optional absolute file path to write (defaults to /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Import Config", + "description": "Import profiles, cycles, and settings for this device from a JSON export file (settings may be overwritten).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to import into." + }, + "path": { + "name": "Path", + "description": "Absolute path to the export JSON file to import." + } + } + }, + "submit_cycle_feedback": { + "name": "Submit Cycle Feedback", + "description": "Confirm or correct an auto-detected program after a completed cycle. Provide either `entry_id` (advanced) or `device_id` (recommended).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device (recommended instead of entry_id)." + }, + "entry_id": { + "name": "Entry ID", + "description": "The config entry id for the device (alternative to device_id)." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The cycle id shown in the feedback notification / logs." + }, + "user_confirmed": { + "name": "Confirm Detected Program", + "description": "Set true if the detected program is correct." + }, + "corrected_profile": { + "name": "Corrected Profile", + "description": "If not confirmed, provide the correct profile/program name." + }, + "corrected_duration": { + "name": "Corrected Duration (seconds)", + "description": "Optional corrected duration in seconds." + }, + "notes": { + "name": "Notes", + "description": "Optional notes about this cycle." + } + } + }, + "record_start": { + "name": "Record Cycle Start", + "description": "Start manually recording a clean cycle (bypasses all matching logic).", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to record." + } + } + }, + "record_stop": { + "name": "Record Cycle Stop", + "description": "Stop manual recording.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to stop recording." + } + } + }, + "trim_cycle": { + "name": "Trim Cycle", + "description": "Trim the power data of a past cycle to a specific time window.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device." + }, + "cycle_id": { + "name": "Cycle ID", + "description": "The ID of the cycle to trim." + }, + "trim_start_s": { + "name": "Trim Start (seconds)", + "description": "Keep data from this offset in seconds. Default 0." + }, + "trim_end_s": { + "name": "Trim End (seconds)", + "description": "Keep data up to this offset in seconds. Default = full duration." + } + } + }, + "pause_cycle": { + "name": "Pause Cycle", + "description": "Pause the active cycle for a WashData device.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to pause." + } + } + }, + "resume_cycle": { + "name": "Resume Cycle", + "description": "Resume a paused cycle for a WashData device.", + "fields": { + "device_id": { + "name": "Device", + "description": "The WashData device to resume." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Running" + }, + "match_ambiguity": { + "name": "Match Ambiguity" + } + }, + "sensor": { + "washer_state": { + "name": "State", + "state": { + "off": "Off", + "idle": "Idle", + "starting": "Starting", + "running": "Running", + "paused": "Paused", + "user_paused": "Paused by user", + "ending": "Ending", + "finished": "Finished", + "anti_wrinkle": "Anti-Wrinkle", + "interrupted": "Interrupted", + "force_stopped": "Force Stopped", + "rinse": "Rinse", + "unknown": "Unknown", + "clean": "Clean", + "delay_wait": "Delay Start" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Time Remaining" + }, + "total_duration": { + "name": "Total Duration" + }, + "cycle_progress": { + "name": "Progress" + }, + "current_power": { + "name": "Current Power" + }, + "elapsed_time": { + "name": "Elapsed Time" + }, + "debug_info": { + "name": "Debug Info" + }, + "match_confidence": { + "name": "Match Confidence" + }, + "top_candidates": { + "name": "Top Candidates", + "state": { + "none": "None" + } + }, + "current_phase": { + "name": "Current Phase" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Profile {profile_name} Count", + "unit_of_measurement": "cycles" + }, + "suggestions": { + "name": "Suggested Settings Available" + }, + "pump_runs_today": { + "name": "Pump Runs (Last 24 h)" + }, + "cycle_count": { + "name": "Cycle Count" + } + }, + "select": { + "program_select": { + "name": "Cycle Program", + "state": { + "auto_detect": "Auto-Detect" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Force End Cycle" + }, + "pause_cycle": { + "name": "Pause Cycle" + }, + "resume_cycle": { + "name": "Resume Cycle" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "A valid device_id is required." + }, + "cycle_id_required": { + "message": "A valid cycle_id is required." + }, + "profile_name_required": { + "message": "A valid profile_name is required." + }, + "device_not_found": { + "message": "Device not found." + }, + "no_config_entry": { + "message": "No config entry found for device." + }, + "integration_not_loaded": { + "message": "Integration not loaded for this device." + }, + "cycle_not_found_or_no_power": { + "message": "Cycle not found or has no power data." + }, + "trim_failed_empty_window": { + "message": "Trim failed - cycle not found, no power data, or resulting window is empty." + }, + "trim_invalid_range": { + "message": "trim_end_s must be greater than trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/eo.json b/custom_components/ha_washdata/translations/eo.json new file mode 100644 index 0000000..900a462 --- /dev/null +++ b/custom_components/ha_washdata/translations/eo.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Agordo de WashData", + "description": "Agordu vian lavmaŝinon aŭ alian aparaton.\n\nPotenca sensilo estas bezonata.\n\n**Poste oni demandos vin ĉu vi volas krei vian unuan profilon.**", + "data": { + "name": "Nomo de aparato", + "device_type": "Tipo de aparato", + "power_sensor": "Potenca sensilo", + "min_power": "Minimuma potenca sojlo (W)" + }, + "data_description": { + "name": "Amika nomo por ĉi tiu aparato (ekz. 'Lavmaŝino', 'Telerlavaparato').", + "device_type": "Kia aparato ĉi tio estas? Helpas adapti detekton kaj etikedadon.", + "power_sensor": "La sensila ento kiu raportas realtempan elektrokonsumon (en vatoj) de via inteligenta ŝtopilo.", + "min_power": "Potencaj valoroj super ĉi tiu sojlo (en vatoj) indikas ke la aparato funkcias. Komencu per 2W por plej multaj aparatoj." + } + }, + "first_profile": { + "title": "Krei unuan profilon", + "description": "**Ciklo** estas kompleta laboro de via aparato (ekz. unu ŝarĝo da lavotaĵo). **Profilo** grupigas tiujn ciklojn laŭ tipo (ekz. 'Kotono', 'Rapida Lavo'). Krei profilon nun helpas la integracion tuj taksi la daŭron.\n\nVi povas permane elekti ĉi tiun profilon en la aparataj regiloj se ĝi ne estas aŭtomate detektita.\n\n**Lasu 'Profilon Nomo' malplena por preteriri ĉi tiun paŝon.**", + "data": { + "profile_name": "Profila nomo (laŭvola)", + "manual_duration": "Laŭtaksa daŭro (minutoj)" + } + } + }, + "error": { + "cannot_connect": "Malsukcesis konekti", + "invalid_auth": "Nevalida aŭtentigo", + "unknown": "Neatendita eraro" + }, + "abort": { + "already_configured": "Aparato jam estas agordita" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revizii lernajn reagojn", + "settings": "Agordoj", + "notifications": "Sciigoj", + "manage_cycles": "Administri ciklojn", + "manage_profiles": "Administri profilojn", + "manage_phase_catalog": "Administri fazan katalogon", + "record_cycle": "Registri ciklon (permane)", + "diagnostics": "Diagnostiko kaj prizorgado" + } + }, + "apply_suggestions": { + "title": "Kopii sugestitajn valorojn", + "description": "Ĉi tio kopios la aktualajn sugestitajn valorojn en viajn konservitajn agordojn. Ĉi tiu estas unufoja ago kaj ne ebligas aŭtomatajn anstataŭigojn.\n\nSugestitaj valoroj por kopii:\n{suggested}", + "data": { + "confirm": "Konfirmi kopian agon" + } + }, + "apply_suggestions_confirm": { + "title": "Revizii sugestitajn ŝanĝojn", + "description": "WashData trovis **{pending_count}** proponitajn valorojn por apliki.\n\nŜanĝoj:\n{changes}\n\n✅ Marku la suban skatolon kaj alklaku **Submeti** por apliki kaj konservi ĉi tiujn ŝanĝojn tuj.\n❌ Lasu ĝin nemarkita kaj alklaku **Submeti** por nuligi kaj reiri.", + "data": { + "confirm_apply_suggestions": "Apliku kaj konservu ĉi tiujn ŝanĝojn" + } + }, + "settings": { + "title": "Agordoj", + "description": "{deprecation_warning}Agordu detektajn sojlojn, lernadon kaj altnivelan konduton. La defaŭltaj valoroj funkcias bone por plej multaj agordoj.\n\nSugestitaj agordoj nuntempe disponeblaj: {suggestions_count}.\nSe ĉi tio estas super 0, malfermu Altnivelaj Agordoj kaj uzu Apliki Sugestitajn Valorojn por revizii rekomendojn.", + "data": { + "apply_suggestions": "Apliki sugestitajn valorojn", + "device_type": "Tipo de aparato", + "power_sensor": "Potenca sensila ento", + "min_power": "Minimuma potenco (W)", + "off_delay": "Cikla fina prokrasto (s)", + "notify_actions": "Sciigaj agoj", + "notify_people": "Prokrasti liveradon ĝis tiuj homoj estas hejme", + "notify_only_when_home": "Prokrasti sciigojn ĝis elektita persono estas hejme", + "notify_fire_events": "Lanĉi aŭtomatigajn eventojn", + "notify_start_services": "Cikla Komenco - Sciigo-Celoj", + "notify_finish_services": "Cikla Finiĝo - Sciigo-Celoj", + "notify_live_services": "Viva Progreso - Sciigo-Celoj", + "notify_before_end_minutes": "Antaŭ-kompletiga sciigo (minutoj antaŭ fino)", + "notify_title": "Sciiga titolo", + "notify_icon": "Sciiga piktogramo", + "notify_start_message": "Formulo de komenca mesaĝo", + "notify_finish_message": "Formulo de fina mesaĝo", + "notify_pre_complete_message": "Formulo de antaŭ-kompletiga mesaĝo", + "show_advanced": "Redakti altnivelaj agordojn (sekva paŝo)" + }, + "data_description": { + "apply_suggestions": "Marku ĉi tiun skatolon por kopii valorojn sugestitajn de la lerna algoritmo en la formularon. Revizii antaŭ konservi.\n\nSugesto (de lernado):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Elektu la tipon de aparato (Lavmaŝino, Sekigilo, Telerlavaparato, Kafmaŝino, EV). Ĉi tiu etikedo estas konservita kun ĉiu ciklo por pli bona organizado.", + "power_sensor": "La sensila ento raportanta realtempan potencon (en vatoj). Vi povas ŝanĝi ĝin iam ajn sen perdi historiajn datumojn - utile se vi anstataŭigas inteligen ŝtopilon.", + "min_power": "Potencaj valoroj super ĉi tiu sojlo (en vatoj) indikas ke la aparato funkcias. Komencu per 2W por plej multaj aparatoj.", + "off_delay": "Sekundoj dum kiuj la glatigita potenco devas resti sub la haltsojlo por marki kompletadon. Defaŭlte: 120s.", + "notify_actions": "Laŭvola ago-sekvenco de Home Assistant por sciigoj (skriptoj, notify, TTS, ktp.). Se agordita, agoj ekfunkciiĝas antaŭ la rezerva sciiga servo.", + "notify_people": "Personaj entoj uzataj por ĉeesta pordego. Kiam ŝaltita, sciiga livero prokrastiĝas ĝis almenaŭ unu elektita persono estas hejme.", + "notify_only_when_home": "Se ŝaltita, prokrasti sciigojn ĝis almenaŭ unu elektita persono estas hejme.", + "notify_fire_events": "Se ŝaltita, lanĉi Home Assistant eventojn por cikla komenco kaj fino (ha_washdata_cycle_started kaj ha_washdata_cycle_ended) por aŭtomatigoj.", + "notify_start_services": "Unu aŭ pluraj sciigu servojn por atentigi kiam ciklo komenciĝas. Lasu malplena por preterlasi startajn sciigojn.", + "notify_finish_services": "Unu aŭ pluraj sciigu servojn por atentigi kiam ciklo finiĝas aŭ preskaŭ finiĝas. Lasu malplena por preterlasi finajn sciigojn.", + "notify_live_services": "Unu aŭ pluraj sciigas servojn por ricevi vivajn progresajn ĝisdatigojn dum la ciklo funkcias (nur por movebla akompana aplikaĵo). Lasu malplena por preterlasi vivajn ĝisdatigojn.", + "notify_before_end_minutes": "Minutoj antaŭ la laŭtaksa fino de la ciklo por ekigi sciigon. Agordu al 0 por malŝalti. Defaŭlte: 0.", + "notify_title": "Propra titolo por sciigoj. Subtenas {device} lokanombron.", + "notify_icon": "Piktogramo por sciigoj (ekz. mdi:washing-machine). Lasu malplena por sendi sen piktogramo.", + "notify_start_message": "Mesaĝo sendita kiam ciklo komenciĝas. Subtenas {device} lokanombron.", + "notify_finish_message": "Mesaĝo sendita kiam ciklo finiĝas. Subtenas {device}, {duration}, {program}, {energy_kwh}, {cost} lokanombrojn.", + "notify_pre_complete_message": "Teksto de la ripetaj vivaj progresaj ĝisdatigoj dum la ciklo funkcias. Subtenas {device}, {minutes}, {program} anstataŭaĵojn." + } + }, + "notifications": { + "title": "Sciigoj", + "description": "Agordu sciigajn kanalojn, ŝablonojn kaj laŭvolajn ŝarĝajn progresajn ĝisdatigojn.", + "data": { + "notify_actions": "Sciigaj agoj", + "notify_people": "Prokrasti liveradon ĝis tiuj homoj estas hejme", + "notify_only_when_home": "Prokrasti sciigojn ĝis elektita persono estas hejme", + "notify_fire_events": "Lanĉi aŭtomatigajn eventojn", + "notify_start_services": "Cikla Komenco - Sciigo-Celoj", + "notify_finish_services": "Cikla Finiĝo - Sciigo-Celoj", + "notify_live_services": "Viva Progreso - Sciigo-Celoj", + "notify_before_end_minutes": "Antaŭ-kompletiga sciigo (minutoj antaŭ fino)", + "notify_live_interval_seconds": "Vivupdata intervalo (sekundoj)", + "notify_live_overrun_percent": "Vivupdata superkuro-toleranco (%)", + "notify_live_chronometer": "Kronometra Retronombrada Temporizilo", + "notify_title": "Sciiga titolo", + "notify_icon": "Sciiga piktogramo", + "notify_start_message": "Formulo de komenca mesaĝo", + "notify_finish_message": "Formulo de fina mesaĝo", + "notify_pre_complete_message": "Formulo de antaŭ-kompletiga mesaĝo", + "energy_price_entity": "Energia Prezo - Ento (laŭvola)", + "energy_price_static": "Energia Prezo - Senmova valoro (laŭvola)", + "go_back": "Reiri sen konservi", + "notify_reminder_message": "Memora Mesaĝo Pormat", + "notify_timeout_seconds": "Auto-dismiss After (sekundoj)", + "notify_channel": "Sciiga Kanalo (stato/viva/rememorigilo)", + "notify_finish_channel": "Finita Notification Channel" + }, + "data_description": { + "go_back": "Marku ĉi tion kaj alklaku Sendi por forĵeti iujn suprajn ŝanĝojn kaj reveni al la ĉefa menuo.", + "notify_actions": "Laŭvola ago-sekvenco de Home Assistant por sciigoj (skriptoj, notify, TTS, ktp.). Se agordita, agoj ekfunkciiĝas antaŭ la rezerva sciiga servo.", + "notify_people": "Personaj entoj uzataj por ĉeesta pordego. Kiam ŝaltita, sciiga livero prokrastiĝas ĝis almenaŭ unu elektita persono estas hejme.", + "notify_only_when_home": "Se ŝaltita, prokrasti sciigojn ĝis almenaŭ unu elektita persono estas hejme.", + "notify_fire_events": "Se ŝaltita, lanĉi Home Assistant eventojn por cikla komenco kaj fino (ha_washdata_cycle_started kaj ha_washdata_cycle_ended) por aŭtomatigoj.", + "notify_start_services": "Unu aŭ pluraj sciigaj servoj por atentigi kiam ciklo komenciĝas (ekz. notify.mobile_app_my_phone). Lasu malplena por preterlasi startajn sciigojn.", + "notify_finish_services": "Unu aŭ pluraj sciigu servojn por atentigi kiam ciklo finiĝas aŭ preskaŭ finiĝas. Lasu malplena por preterlasi finajn sciigojn.", + "notify_live_services": "Unu aŭ pluraj sciigas servojn por ricevi vivajn progresajn ĝisdatigojn dum la ciklo funkcias (nur por movebla akompana aplikaĵo). Lasu malplena por preterlasi vivajn ĝisdatigojn.", + "notify_before_end_minutes": "Minutoj antaŭ la laŭtaksa fino de la ciklo por ekigi sciigon. Agordu al 0 por malŝalti. Defaŭlte: 0.", + "notify_live_interval_seconds": "Kiom ofte progresaj ĝisdatigoj estas senditaj dum funkciado. Pli malaltaj valoroj estas pli respondemaj sed konsumas pli da sciigoj. Minimume 30 sekundoj estas devigita - valoroj sub 30s aŭtomate rondiĝas al 30s.", + "notify_live_overrun_percent": "Sekureca marĝeno super la laŭtaksa ĝisdatiga kalkulo por longaj/superkurantaj cikloj (ekzemple 20% permesas 1.2x laŭtaksajn ĝisdatigojn).", + "notify_live_chronometer": "Kiam ĝi estas ebligita, ĉiu viva ĝisdatigo inkluzivas kronometran retronombradon ĝis la laŭtaksa fintempo. La sciiga tempigilo aŭtomate malaltiĝas sur la aparato inter ĝisdatigoj (nur Android-akompana aplikaĵo).", + "notify_title": "Propra titolo por sciigoj. Subtenas {device} lokanombron.", + "notify_icon": "Piktogramo por sciigoj (ekz. mdi:washing-machine). Lasu malplena por sendi sen piktogramo.", + "notify_start_message": "Mesaĝo sendita kiam ciklo komenciĝas. Subtenas {device} lokanombron.", + "notify_finish_message": "Mesaĝo sendita kiam ciklo finiĝas. Subtenas {device}, {duration}, {program}, {energy_kwh}, {cost} lokanombrojn.", + "energy_price_entity": "Elektu nombran enton, kiu tenas la nunan elektroprezon (ekz. sensor.electricity_price, input_number.energy_rate). Kiam agordita, ebligas la lokokupilon {cost}. Havas prioritaton super la senmova valoro se ambaŭ estas agorditaj.", + "energy_price_static": "Fiksa elektroprezo por kWh. Kiam agordita, ebligas la lokokupilon {cost}. Ignorita se ento estas agordita supre. Aldonu vian valutan simbolon en la mesaĝŝablono, ekz. {cost} €.", + "notify_reminder_message": "Teksto de la unufoja memorigilo sendis la agordita nombro da minutoj antaŭ la laŭtaksa fino. Distingu de la ripetaj vivaj ĝisdatigoj supre. Subtenas {device}, {minutes}, {program} anstataŭaĵojn.", + "notify_timeout_seconds": "aŭtomate malakceptas sciigojn post tiuj multaj sekundoj (antaŭjuĝitaj kiel la kunulprogramo \"tempon\". Agordu al 0 por teni ilin ĝis malakceptita. Defaŭlto: 0.", + "notify_channel": "Kunulprogramadkanalo por statuso, vivas progreson, kaj memor sciigojn. La sono kaj graveco de kanalo estas formitaj en la kunulprogramo la unuan fojon la kanalnomo estas uzita - WashData nur metas la kanalnomon. Lasu malplena por la app defaŭlto.", + "notify_finish_channel": "Aparta kanalo por la preta alarmo (ankaŭ uzata de la memorigilo kaj la atend-alarmo pri lavotaĵo) tiel ĝi povas havi sian propran sonon. Lasu malplena recikli la kanalon supre.", + "notify_pre_complete_message": "Teksto de la ripetaj vivaj progresaj ĝisdatigoj dum la ciklo funkcias. Subtenas {device}, {minutes}, {program} anstataŭaĵojn." + } + }, + "advanced_settings": { + "title": "Altnivelaj agordoj", + "description": "Agordu detektajn sojlojn, lernadon kaj altnivelan konduton. La defaŭltaj valoroj funkcias bone por plej multaj agordoj.", + "sections": { + "suggestions_section": { + "name": "Sugestitaj agordoj", + "description": "{suggestions_count} lernitaj sugestoj disponeblas. Marku Apliki Sugestitajn Valorojn por revizii proponitajn ŝanĝojn antaŭ konservi.", + "data": { + "apply_suggestions": "Apliki sugestitajn valorojn" + }, + "data_description": { + "apply_suggestions": "Marku ĉi tiun skatolon por malfermi revizian paŝon por sugestitaj valoroj de la lerna algoritmo. Nenio estas konservita aŭtomate.\n\nSugesto (de lernado):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekto kaj Potencaj Sojloj", + "description": "Kiam la aparato estas konsiderata EN aŭ EL, energiaj limoj kaj tempigo por statotransiroj.", + "data": { + "start_duration_threshold": "Starta cimfiltra daŭro (s)", + "start_energy_threshold": "Starta energia pordego (Wh)", + "completion_min_seconds": "Kompletiga minimuma ekzekuta tempo (sekundoj)", + "start_threshold_w": "Starta sojlo (W)", + "stop_threshold_w": "Halta sojlo (W)", + "end_energy_threshold": "Fina energia pordego (Wh)", + "running_dead_zone": "Ekzekuta mortzoneto (sekundoj)", + "end_repeat_count": "Fina ripeta kalkulo", + "min_off_gap": "Minimuma intertempo inter cikloj (s)", + "sampling_interval": "Specimena intervalo (s)" + }, + "data_description": { + "start_duration_threshold": "Ignori mallongajn potencajn pintojn pli mallongajn ol ĉi tiu daŭro (sekundoj). Filtras ekigajn pintojn (ekz. 60W por 2s) antaŭ veraj ciklaj komencoj. Defaŭlte: 5s.", + "start_energy_threshold": "Ciklo devas akumuli almenaŭ ĉi tiom da energio (Wh) dum la STARTA fazo por esti konfirmita. Defaŭlte: 0.005Wh.", + "completion_min_seconds": "Cikloj pli mallongaj ol ĉi tio estos markitaj kiel 'interrompitaj' eĉ se ili finiĝas nature. Defaŭlte: 600s.", + "start_threshold_w": "Potenco devas SUPERI ĉi tiun sojlon por KOMENCI ciklon. Agordu pli alte ol via atenda potenco. Defaŭlte: min_power + 1W.", + "stop_threshold_w": "Potenco devas FALI SUB ĉi tiun sojlon por FINI ciklon. Agordu iom super nulo por eviti bruon kiu ekigas falsajn finojn. Defaŭlte: 0.6 × min_power.", + "end_energy_threshold": "Ciklo finiĝas nur se energio en la lasta Malŝalta-Prokrasta fenestro estas sub ĉi tiu valoro (Wh). Malhelpos falsajn finojn se potenco fluktuacias proksime al nulo. Defaŭlte: 0.05Wh.", + "running_dead_zone": "Post kiam ciklo komenciĝas, ignori potencajn falojn dum ĉi tiom da sekundoj por malhelpi falsajn fin-detektojn dum komenca startfazo. Agordu al 0 por malŝalti. Defaŭlte: 0s.", + "end_repeat_count": "Nombro da fojoj kiom la fina kondiĉo (potenco sub sojlo dum off_delay) devas esti plenumita sinsekve antaŭ fini la ciklon. Pli altaj valoroj malhelpos falsajn finojn dum paŭzoj. Defaŭlte: 1.", + "min_off_gap": "Se ciklo komenciĝas ene de ĉi tiom da sekundoj post la fino de la antaŭa, ili estos KUNFANDITAJ en ununuran ciklon.", + "sampling_interval": "Minimuma specimena intervalo en sekundoj. Ĝisdatigoj pli rapide ol ĉi tiu tarifo estos ignoritaj. Defaŭlte: 30s (2s por lavmaŝinoj, lavsekigiloj kaj vazlaviloj)." + } + }, + "matching_section": { + "name": "Profilkongruigo kaj Lernado", + "description": "Kiom agreseme WashData kongruigas kurantajn ciklojn kun lernitaj profiloj kaj kiam peti reagojn.", + "data": { + "profile_match_min_duration_ratio": "Profila kongrua minimuma daŭra proporcio (0.1-1.0)", + "profile_match_interval": "Profila kongrua intervalo (sekundoj)", + "profile_match_threshold": "Profila kongrua sojlo", + "profile_unmatch_threshold": "Profila malkongrua sojlo", + "auto_label_confidence": "Aŭtomata etikedada konfido (0-1)", + "learning_confidence": "Reago-peta konfido (0-1)", + "suppress_feedback_notifications": "Subpremi Sciigojn pri Reago", + "duration_tolerance": "Daŭra toleranco (0-0.5)", + "smoothing_window": "Glatiganta fenestro (specimenoj)", + "profile_duration_tolerance": "Profila kongrua daŭra toleranco (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimuma daŭra proporcio por profila kongruo. La ekzekutanta ciklo devas esti almenaŭ ĉi tiu frakcio de la profila daŭro por kongrui. Pli malalta = pli frua kongruo. Defaŭlte: 0.50 (50%).", + "profile_match_interval": "Kiom ofte (en sekundoj) provi profilan kongruon dum ciklo. Pli malaltaj valoroj donas pli rapidan detekton sed uzas pli da CPU. Defaŭlte: 300s (5 minutoj).", + "profile_match_threshold": "Minimuma DTW simileca poentaro (0.0–1.0) bezonata por konsideri profilon kiel kongruon. Pli alta = pli strikta kongruo, malpli da falsaj pozitivoj. Defaŭlte: 0.4.", + "profile_unmatch_threshold": "DTW simileca poentaro (0.0–1.0) sub kiu antaŭe kongruinta profilo estas malaprobita. Devas esti pli malalta ol la kongrua sojlo por eviti flakeron. Defaŭlte: 0.35.", + "auto_label_confidence": "Aŭtomate etikedi ciklojn je aŭ super ĉi tiu konfido ĉe kompletado (0.0–1.0).", + "learning_confidence": "Minimuma konfido por peti uzantan konfirmon per evento (0.0–1.0).", + "suppress_feedback_notifications": "Kiam ĝi estas ebligita, WashData ankoraŭ spuros kaj petos retrosciigon interne sed ne montros konstantajn sciigojn petante vin konfirmi ciklojn. Utila kiam cikloj ĉiam estas detektitaj ĝuste kaj vi trovas la instigojn distraj.", + "duration_tolerance": "Toleranco por restanta-tempo-takstoj dum ekzekuto (0.0–0.5 egalas al 0–50%).", + "smoothing_window": "Nombro da lastatempaj potencaj legaĵoj uzataj por movanta-averaĝa glatigo.", + "profile_duration_tolerance": "Toleranco uzata de profila kongruo por totala daŭra vario (0.0–0.5). Defaŭlta konduto: ±25%." + } + }, + "timing_section": { + "name": "Tempigo, Prizorgado kaj Sencimigo", + "description": "Gardohundo, progresa restarigo, aŭtomata prizorgado kaj montrado de sencimigaj entoj.", + "data": { + "watchdog_interval": "Gardohunda intervalo (sekundoj)", + "no_update_active_timeout": "Tempofino sen ĝisdatigo (s)", + "progress_reset_delay": "Progresa restarigo-prokrasto (sekundoj)", + "auto_maintenance": "Ŝalti aŭtomatan prizorgadon", + "expose_debug_entities": "Montri sencimigajn entojn", + "save_debug_traces": "Konservi sencimigajn spurojn" + }, + "data_description": { + "watchdog_interval": "Sekundoj inter gardohundaj kontroloj dum ekzekuto. Defaŭlte: 5s. AVERTO: Certigu ke ĉi tio estas PLI ALTA ol la ĝisdatiga intervalo de via sensilo por eviti falsajn haltojn (ekz. se sensilo ĝisdatigas ĉiun 60s, uzu 65s+).", + "no_update_active_timeout": "Por publikigu-sur-ŝanĝo sensiloj: nur perforte fini AKTIVAN ciklon se neniuj ĝisdatigoj alvenas dum ĉi tiom da sekundoj. Malalta-pova kompletado ankoraŭ uzas la Malŝaltan Prokraston.", + "progress_reset_delay": "Post kiam ciklo kompletaĵis (100%), atendi ĉi tiom da sekundoj da senaktiveco antaŭ restarigi progreson al 0%. Defaŭlte: 300s.", + "auto_maintenance": "Ŝalti aŭtomatan prizorgadon (ripari specimenojn, kunfandi fragmentojn).", + "expose_debug_entities": "Montri altnivelaj sensilojn (Konfido, Fazo, Ambigueco) por sencimigado.", + "save_debug_traces": "Konservi detalajn rangordajn kaj potencajn spurdatumojn en historio (Pliigas stokadan uzadon)." + } + }, + "anti_wrinkle_section": { + "name": "Kontraŭ-falda ŝirmilo (Sekigiloj)", + "description": "Ignoru tamburajn rotaciojn post la ciklo por malhelpi fantomajn ciklojn. Nur sekigilo / lav-sekigilo.", + "data": { + "anti_wrinkle_enabled": "Kontraŭfaldiga ŝildo (nur sekigilo/lavmaŝino-sekigilo)", + "anti_wrinkle_max_power": "Kontraŭfaldiga maksimuma potenco (W) (nur sekigilo/lavmaŝino-sekigilo)", + "anti_wrinkle_max_duration": "Kontraŭfaldiga maksimuma daŭro (s) (nur sekigilo/lavmaŝino-sekigilo)", + "anti_wrinkle_exit_power": "Kontraŭfaldiga elira potenco (W) (nur sekigilo/lavmaŝino-sekigilo)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignori mallongajn malalta-povajn tamburajn rotaciojn post kiam ciklo finiĝas por malhelpi fantomajn ciklojn (nur sekigilo/lavmaŝino-sekigilo).", + "anti_wrinkle_max_power": "Eksplodoj je aŭ sub ĉi tiu potenco estas traktataj kiel kontraŭfaldiga rotacioj anstataŭ novaj cikloj. Validas nur kiam Kontraŭfaldiga Ŝildo estas ŝaltita.", + "anti_wrinkle_max_duration": "Maksimuma eksplodlongo por trakti kiel kontraŭfaldigar rotacion. Validas nur kiam Kontraŭfaldiga Ŝildo estas ŝaltita.", + "anti_wrinkle_exit_power": "Se potenco falas sub ĉi tiun nivelon, tuj eliri kontraŭfaldigecon (vera malŝalto). Validas nur kiam Kontraŭfaldiga Ŝildo estas ŝaltita." + } + }, + "delay_start_section": { + "name": "Detekto de Prokrastita Komenco", + "description": "Traktu daŭran malaltpotencan atendoreĝimon kiel 'Atendante komencon' ĝis la ciklo vere komenciĝas.", + "data": { + "delay_start_detect_enabled": "Ebligu Malfruan Komencan Detekton", + "delay_confirm_seconds": "Prokrastita Komenco: Atenda Konfirmtempo (s)", + "delay_timeout_hours": "Prokrastita Komenco: Maksimuma Atendotempo (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Se ebligite, mallonga malalt-motora drenpiko sekvita per daŭranta ŝancatendopotenco estas detektita kiel prokrastita komenco. La ŝtatsensilo montras 'Atendante Komenci' dum la prokrasto. Neniu programa tempo aŭ progreso estas spurita ĝis la ciklo efektive komenciĝas. Postulas start_threshold_w esti agordita super la drenpika potenco.", + "delay_confirm_seconds": "Potenco devas resti inter Halta Sojlo (W) kaj Komenca Sojlo (W) dum tiom da sekundoj antaŭ ol WashData eniras la staton 'Atendante komencon'. Tio filtras mallongajn pintojn dum menua navigado. Defaŭlto: 60 s.", + "delay_timeout_hours": "Sekureca tempodaŭro: se la maŝino ankoraŭ estas en \"Atendante komenci\" post ĉi tiuj multaj horoj, WashData rekomenciĝas al Off. Defaŭlte: 8 h." + } + }, + "external_triggers_section": { + "name": "Eksteraj Ellasiloj, Pordo kaj Paŭzo", + "description": "Nedeviga ekstera sensilo por ciklofino, porda sensilo por paŭzo/pura detekto, kaj ŝaltilo por potenco-detranĉo dum paŭzo.", + "data": { + "external_end_trigger_enabled": "Ŝalti eksteran ciklan fin-ekigilon", + "external_end_trigger": "Ekstera cikla fina sensilo", + "external_end_trigger_inverted": "Inversigi ekigan logikon (ekigi ĉe MALŜALTO)", + "door_sensor_entity": "Porda Sensilo", + "pause_cuts_power": "Tranĉi Potencon Kiam Paŭzo", + "switch_entity": "Ŝaltilo-Entaĵo (por Paŭzo-Potento)", + "notify_unload_delay_minutes": "Prokrasto de Sciigo de Atendo de Lavejo (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Ŝalti monitoradon de ekstera duuma sensilo por ekigi ciklan kompletadon.", + "external_end_trigger": "Elektu duuman sensilan enton. Kiam ĉi tiu sensilo ekigas, la aktuala ciklo finiĝos kun statuso 'kompletigita'.", + "external_end_trigger_inverted": "Defaŭlte, la ekigilo pafas kiam la sensilo ŝaltiĝas. Marku ĉi tiun skatolon por pafi kiam la sensilo malŝaltiĝas anstataŭe.", + "door_sensor_entity": "Laŭvola binara sensilo por la maŝinpordo (ŝaltita = malfermita, malŝaltita = fermita). Kiam la pordo malfermiĝas dum aktiva ciklo, WashData konfirmos la ciklon kiel intencite paŭzitan. Post kiam la ciklo finiĝas, se la pordo ankoraŭ estas fermita, la stato ŝanĝiĝas al \"Puriga\" ĝis la pordo estas malfermita. Noto: fermi la pordon NE aŭtomate rekomencas paŭzitan ciklon - rekomenco devas esti ekigita permane per la butono aŭ servo de Rekomenco de Ciklo.", + "pause_cuts_power": "Dum paŭzo per la butono aŭ servo de Paŭzo-Ciklo, ankaŭ malŝaltu la agorditan ŝaltilon. Nur efektiviĝas se ŝanĝa ento estas agordita por ĉi tiu aparato.", + "switch_entity": "La ŝanĝa ento por ŝanĝi dum paŭzo aŭ rekomenco. Bezonata kiam 'Eltondi potencon dum paŭzo' estas ebligita.", + "notify_unload_delay_minutes": "Sendu sciigon per la finfina sciiga kanalo post kiam la ciklo finiĝas kaj la pordo ne estis malfermita dum tiom da minutoj (postulas Pordo-Sensilon). Agordu al 0 por malŝalti. Defaŭlte: 60 min." + } + }, + "pump_section": { + "name": "Pumpila monitoro", + "description": "Nur por pumpila aparatotipo. Sendas eventon se pumpila ciklo daŭras pli longe ol ĉi tiu daŭro.", + "data": { + "pump_stuck_duration": "Pumpilo Ŝtopiĝinta Alerta Sojlo (s) (Pumpilo Nur)" + }, + "data_description": { + "pump_stuck_duration": "Se pumpciklo daŭre funkcias post tiom da sekundoj, evento ha_washdata_pump_stuck estas lanĉita unufoje. Uzu ĉi tion por detekti ŝtopiĝintan motoron (tipa sumppumpilfunkciado estas malpli ol 60 s). Defaŭlte: 1800 s (30 min). Tipo de pumpilo nur." + } + }, + "device_link_section": { + "name": "Device ligo", + "description": "Laŭvola ligo tiu WashData aparato al ekzistanta Home Assistant aparato (ekzemple la inteligenta ŝtopilo aŭ la aplaŭdo mem). La WashData aparato tiam estas montrita kiel \"Konsekcita per\" tiu aparato. Lasu malplena konservi WashData kiel memstara aparato.", + "data": { + "linked_device": "Aliĝilo Device" + }, + "data_description": { + "linked_device": "Elekti la aparaton por ligi WashData al. Tio aldonas \"Connected per\" referenco en la aparatoregistro; WashData retenas sian propran aparatokarton kaj unuojn. Klara la elekto forigi la ligon." + } + } + } + }, + "diagnostics": { + "title": "Diagnostiko kaj prizorgado", + "description": "Ruli prizorgadajn agojn kiel kunfandado de fragmentitaj cikloj aŭ migracio de stokitaj datumoj.\n\n**Stokada Uzado**\n\n- Dosiergrandeco: {file_size_kb} KB\n- Cikloj: {cycle_count}\n- Profiloj: {profile_count}\n- Sencimigaj Spuroj: {debug_count}", + "menu_options": { + "reprocess_history": "Prizorgado: Reprocesi kaj optimumigi datumojn", + "clear_debug_data": "Forigi sencimigajn datumojn (liberigi spacon)", + "wipe_history": "Forviŝi ĈIUJN datumojn por ĉi tiu aparato (nereversigebla)", + "export_import": "Eksporti/importi JSON kun agordoj (kopii/glui)", + "menu_back": "← Reen" + } + }, + "clear_debug_data": { + "title": "Forigi sencimigajn datumojn", + "description": "Ĉu vi certas ke vi volas forigi ĉiujn stokitajn sencimigajn spurojn? Ĉi tio liberigos spacon sed forigos detalajn rangordajn informojn el pasintaj cikloj." + }, + "manage_cycles": { + "title": "Administri ciklojn", + "description": "Lastatempaj cikloj:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Aŭtomate etikedi malnovajn ciklojn", + "select_cycle_to_label": "Etikedi specifan ciklon", + "select_cycle_to_delete": "Forigi ciklon", + "interactive_editor": "Interaga redaktisto por kunfando/divido", + "trim_cycle_select": "Tondi ciklajn datumojn", + "menu_back": "← Reen" + } + }, + "manage_cycles_empty": { + "title": "Administri ciklojn", + "description": "Neniuj cikloj registritaj ankoraŭ.", + "menu_options": { + "auto_label_cycles": "Aŭtomate etikedi malnovajn ciklojn", + "menu_back": "← Reen" + } + }, + "manage_profiles": { + "title": "Administri profilojn", + "description": "Profila resumo:\n{profile_summary}", + "menu_options": { + "create_profile": "Krei novan profilon", + "edit_profile": "Redakti/renomigi profilon", + "delete_profile_select": "Forigi profilon", + "profile_stats": "Profilej statistikoj", + "cleanup_profile": "Purigi historion - Grafiko kaj forigo", + "assign_profile_phases_select": "Asigni fazajn intervalojn", + "menu_back": "← Reen" + } + }, + "manage_profiles_empty": { + "title": "Administri profilojn", + "description": "Neniuj profiloj kreitaj ankoraŭ.", + "menu_options": { + "create_profile": "Krei novan profilon", + "menu_back": "← Reen" + } + }, + "manage_phase_catalog": { + "title": "Administri fazan katalogon", + "description": "Faza katalogo (defaŭltoj por ĉi tiu aparato + propraj fazoj):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Krei novan fazon", + "phase_catalog_edit_select": "Redakti fazon", + "phase_catalog_delete": "Forigi fazon", + "menu_back": "← Reen" + } + }, + "phase_catalog_create": { + "title": "Krei propran fazon", + "description": "Kreu propran fazan nomon kaj priskribon, kaj elektu al kiu aparattipo ĝi aplikeblas.", + "data": { + "target_device_type": "Cela aparato tipo", + "phase_name": "Faza nomo", + "phase_description": "Faza priskribo" + } + }, + "phase_catalog_edit_select": { + "title": "Redakti propran fazon", + "description": "Elektu propran fazon por redakti.", + "data": { + "phase_name": "Propra fazo" + } + }, + "phase_catalog_edit": { + "title": "Redakti propran fazon", + "description": "Redaktante fazon: {phase_name}", + "data": { + "phase_name": "Faza nomo", + "phase_description": "Faza priskribo" + } + }, + "phase_catalog_delete": { + "title": "Forigi propran fazon", + "description": "Elektu propran fazon por forigi. Iuj asignitaj intervaloj uzantaj ĉi tiun fazon estos forigitaj.", + "data": { + "phase_name": "Propra fazo" + } + }, + "assign_profile_phases": { + "title": "Asigni fazajn intervalojn", + "description": "Faza antaŭvido por profilo: **{profile_name}**\n\n{timeline_svg}\n\n**Aktualaj intervaloj:**\n{current_ranges}\n\nUzu agojn sube por aldoni, redakti, forigi aŭ konservi intervalojn.", + "menu_options": { + "assign_profile_phases_add": "Aldoni fazan intervalon", + "assign_profile_phases_edit_select": "Redakti fazan intervalon", + "assign_profile_phases_delete": "Forigi fazan intervalon", + "phase_ranges_clear": "Forigi ĉiujn intervalojn", + "assign_profile_phases_auto_detect": "Aŭtomate detekti fazojn", + "phase_ranges_save": "Konservi kaj reveni", + "menu_back": "← Reen" + } + }, + "assign_profile_phases_add": { + "title": "Aldoni fazan intervalon", + "description": "Aldonu unu fazan intervalon al la aktuala skizo.", + "data": { + "phase_name": "Fazo", + "start_min": "Startminuto", + "end_min": "Finminuto" + } + }, + "assign_profile_phases_edit_select": { + "title": "Redakti fazan intervalon", + "description": "Elektu intervalon por redakti.", + "data": { + "range_index": "Faza intervalo" + } + }, + "assign_profile_phases_edit": { + "title": "Redakti fazan intervalon", + "description": "Ĝisdatigi la elektitan fazan intervalon.", + "data": { + "phase_name": "Fazo", + "start_min": "Startminuto", + "end_min": "Finminuto" + } + }, + "assign_profile_phases_delete": { + "title": "Forigi fazan intervalon", + "description": "Elektu intervalon por forigi el la skizo.", + "data": { + "range_index": "Faza intervalo" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Aŭtomate detektitaj fazoj", + "description": "Profilo: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fazoj aŭtomate detektitaj.** Elektu agon sube.", + "data": { + "action": "Ago" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nomi detektitajn fazojn", + "description": "Profilo: **{profile_name}**\n\n**{detected_count} fazoj detektitaj:**\n{ranges_summary}\n\nAsignu nomon al ĉiu fazo." + }, + "assign_profile_phases_select": { + "title": "Asigni fazajn intervalojn", + "description": "Elektu profilon por agordi fazajn intervalojn.", + "data": { + "profile": "Profilo" + } + }, + "profile_stats": { + "title": "Profilej statistikoj", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Krei novan profilon", + "description": "Kreu novan profilon permane aŭ el pasinta ciklo.\n\nEkzemplaj profilej nomoj: 'Delikatoj', 'Peza Lavo', 'Rapida Lavo'", + "data": { + "profile_name": "Profila nomo", + "reference_cycle": "Referenca ciklo (laŭvola)", + "manual_duration": "Permana daŭro (minutoj)" + } + }, + "edit_profile": { + "title": "Redakti profilon", + "description": "Elektu profilon por renomigi", + "data": { + "profile": "Profilo" + } + }, + "rename_profile": { + "title": "Redakti profilejn detalojn", + "description": "Aktuala profilo: {current_name}", + "data": { + "new_name": "Profila nomo", + "manual_duration": "Permana daŭro (minutoj)" + } + }, + "delete_profile_select": { + "title": "Forigi profilon", + "description": "Elektu profilon por forigi", + "data": { + "profile": "Profilo" + } + }, + "delete_profile_confirm": { + "title": "Konfirmu forigi profilon", + "description": "⚠️ Ĉi tio konstante forigos profilon: {profile_name}", + "data": { + "unlabel_cycles": "Forigu etikedon de cikloj uzantaj ĉi tiun profilon" + } + }, + "auto_label_cycles": { + "title": "Aŭtomate etikedi malnovajn ciklojn", + "description": "Trovis {total_count} entutajn ciklojn. Profiloj: {profiles}", + "data": { + "confidence_threshold": "Minimuma konfido (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Elekti ciklon por etikedi", + "description": "✓ = kompletigita, ⚠ = rekomencita, ✗ = interrompita", + "data": { + "cycle_id": "Ciklo" + } + }, + "select_cycle_to_delete": { + "title": "Forigi ciklon", + "description": "⚠️ Ĉi tio konstante forigos la elektitan ciklon", + "data": { + "cycle_id": "Ciklo por forigi" + } + }, + "label_cycle": { + "title": "Etikedi ciklon", + "description": "{cycle_info}", + "data": { + "profile_name": "Profilo", + "new_profile_name": "Nova profila nomo (se kreante)" + } + }, + "post_process": { + "title": "Procezi historion (Kunfandi/Dividi)", + "description": "Purigu ciklohistorion kunfandante fragmentajn kurojn kaj dividante malĝuste kunfanditajn ciklojn ene de tempofenestro. Enigu nombron da horoj por retrorigardi (uzu 999999 por ĉiuj).", + "data": { + "time_range": "Retrorigarda fenestro (horoj)", + "gap_seconds": "Kunfandi/Dividi intertempo (sekundoj)" + }, + "data_description": { + "time_range": "Nombro da horoj por skani por kunfandi fragmentajn ciklojn. Uzu 999999 por prilabori ĉiujn historiajn datumojn.", + "gap_seconds": "Sojlo por decidi divido kontraŭ kunfando. Intertempoj PLI MALLONGAJ ol ĉi tio estas kunfanditaj. Intertempoj PLI LONGAJ ol ĉi tio estas dividitaj. Malaltigu ĉi tion por devigi dividon sur ciklo kiu estis kunfandita malĝuste." + } + }, + "cleanup_profile": { + "title": "Purigi historion - Elekti profilon", + "description": "Elektu profilon por bildigi. Ĉi tio generos grafeon montrantan ĉiujn pasintajn ciklojn por ĉi tiu profilo por helpi identigi eksteraĵojn.", + "data": { + "profile": "Profilo" + } + }, + "cleanup_select": { + "title": "Purigi historion - Grafiko kaj forigo", + "description": "![Grafiko]({graph_url})\n\n**Vidigu {profile_name}**\n\nLa grafikaĵo montras individuajn ciklojn kiel kolorajn liniojn. Identigu eksteraĵojn (liniojn malproksime de la grupo) kaj kongruu ilian koloron en la suba listo por forigi ilin.\n\n**Elektu ciklojn por KONSTANTE forigi:**", + "data": { + "cycles_to_delete": "Cikloj por forigi (kongruu kolorojn)" + } + }, + "interactive_editor": { + "title": "Interaga redaktisto por kunfando/divido", + "description": "Elektu manan agon por plenumi en via ciklohistorio.", + "menu_options": { + "editor_split": "Dividi ciklon (trovi intertempojn)", + "editor_merge": "Kunfandi ciklojn (kunigi fragmentojn)", + "editor_delete": "Forigi ciklo(j)n", + "menu_back": "← Reen" + } + }, + "editor_select": { + "title": "Elekti ciklojn", + "description": "Elektu la ciklo(j)n por procezi.\n\n{info_text}", + "data": { + "selected_cycles": "Cikloj" + } + }, + "editor_configure": { + "title": "Agordi kaj apliki", + "description": "{preview_md}", + "data": { + "confirm_action": "Ago", + "merged_profile": "Rezulta profilo", + "new_profile_name": "Nova profila nomo", + "confirm_commit": "Jes, mi konfirmas ĉi tiun agon", + "segment_0_profile": "Segmento 1 Profilo", + "segment_1_profile": "Segmento 2 Profilo", + "segment_2_profile": "Segmento 3 Profilo", + "segment_3_profile": "Segmento 4 Profilo", + "segment_4_profile": "Segmento 5 Profilo", + "segment_5_profile": "Segmento 6 Profilo", + "segment_6_profile": "Segmento 7 Profilo", + "segment_7_profile": "Segmento 8 Profilo", + "segment_8_profile": "Segmento 9 Profilo", + "segment_9_profile": "Segmento 10 Profilo" + } + }, + "editor_split_params": { + "title": "Divida agordo", + "description": "Ĝustigi la sentemon por dividi ĉi tiun ciklon. Ajna senaktiva interspaco pli longa ol tio kaŭzos dividon.", + "data": { + "split_mode": "Divida metodo" + } + }, + "editor_split_auto_params": { + "title": "Aŭtomata divid-agordo", + "description": "Ĝustigu la sentemon por dividi ĉi tiun ciklon. Ajna senaktiva paŭzo pli longa ol ĉi tio kaŭzos dividon.", + "data": { + "min_gap_seconds": "Sojlo de divid-interspaco (sekundoj)" + } + }, + "editor_split_manual_params": { + "title": "Manaj divid-tempomarkoj", + "description": "{preview_md}Cikla fenestro: **{cycle_start_wallclock} → {cycle_end_wallclock}** je {cycle_date}.\n\nEnigu unu aŭ plurajn divid-tempomarkojn (`HH:MM` aŭ `HH:MM:SS`), unu en ĉiu linio. La ciklo estos tranĉita ĉe ĉiu tempomarko — N tempomarkoj produktas N+1 segmentojn.", + "data": { + "split_timestamps": "Disigaj Tempomarkoj" + } + }, + "reprocess_history": { + "title": "Prizorgado: Reprocesi kaj optimumigi datumojn", + "description": "Elfaras profundan purigadon de historiaj datumoj:\n1. Tondas nul-potencajn legaĵojn (silento).\n2. Riparas ciklan tempon/daŭron.\n3. Rekalkulas ĉiujn statistikajn modelojn (kovertojn).\n\n**Rulu ĉi tion por ripari datumajn problemojn aŭ apliki novajn algoritmojn.**" + }, + "wipe_history": { + "title": "Viŝi historion (nur por testado)", + "description": "⚠️ Ĉi tio konstante forigos ĈIUJN stokitajn ciklojn kaj profilojn por ĉi tiu aparato. Ĉi tio ne povas esti malfarita!" + }, + "export_import": { + "title": "Eksporti/Importi JSON", + "description": "Kopiu/gluu ciklojn, profilojn, KAJ agordojn por ĉi tiu aparato. Eksportu sube aŭ algluu JSON por importi.", + "data": { + "mode": "Ago", + "json_payload": "Kompleta agordo JSON" + }, + "data_description": { + "mode": "Elektu Eksporti por kopii aktualajn datumojn kaj agordojn, aŭ Importi por alglui eksportitajn datumojn de alia WashData-aparato.", + "json_payload": "Por Eksporti: kopiu ĉi tiun JSON (inkludas ciklojn, profilojn kaj ĉiujn precize agordojn). Por Importi: algluu JSON eksportitan de WashData." + } + }, + "record_cycle": { + "title": "Registri ciklon", + "description": "Mane registri ciklon por krei puran profilon sen interfero.\n\nStatuso: **{status}**\nDaŭro: {duration}s\nSpecimenoj: {samples}", + "menu_options": { + "record_refresh": "Refreŝigi staton", + "record_stop": "Ĉesi registradon (konservi kaj procezi)", + "record_start": "Komenci novan registradon", + "record_process": "Procezi lastan registradon (tondadi kaj konservi)", + "record_discard": "Forĵeti lastan registradon", + "menu_back": "← Reen" + } + }, + "record_process": { + "title": "Procezi registradon", + "description": "![Grafiko]({graph_url})\n\n**Grafeo montras krudan registradon.** Blua = Konservi, Ruĝa = Proponita tondado.\n*Grafiko estas senmova kaj ne ĝisdatiĝas kun formaj ŝanĝoj.*\n\nRegistrado ĉesis. Tondiloj estas vicigitaj al la detektita specimena kadencio (~{sampling_rate}s).\n\nKruda daŭro: {duration}s\nSpecimenoj: {samples}", + "data": { + "head_trim": "Tondado komenco (sekundoj)", + "tail_trim": "Tondado fino (sekundoj)", + "save_mode": "Konserva celo", + "profile_name": "Profila nomo" + } + }, + "learning_feedbacks": { + "title": "Lernaj reagoj", + "description": "Atendantaj reagoj: {count}\n\n{pending_table}\n\nElektu ciklan revizian peton por prilabori.", + "menu_options": { + "learning_feedbacks_pick": "Revizii atendan reagon", + "learning_feedbacks_dismiss_all": "Forĵeti ĉiujn atendantajn reagojn", + "menu_back": "← Reen" + } + }, + "learning_feedbacks_pick": { + "title": "Elektu reagojn por revizii", + "description": "Elektu peton pri cikla revizio por malfermi.", + "data": { + "selected_feedback": "Elektu ciklon por revizii" + } + }, + "learning_feedbacks_empty": { + "title": "Lernaj reagoj", + "description": "Neniuj pritraktataj reagopetoj trovitaj.", + "menu_options": { + "menu_back": "← Reen" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Malakcepti ĉiujn atendantajn reagojn", + "description": "Vi estas preta malakcepti **{count}** atendantajn reagpetojn. Malakceptitaj cikloj restas en via historio sed ne aldonos novan lernan signalon. Ĉi tio ne estas malfarebla.\n\n✅ Marku la suban skatolon kaj alklaku **Submeti** por malakcepti ĉion.\n❌ Lasu ĝin nemarkita kaj alklaku **Submeti** por nuligi kaj reiri.", + "data": { + "confirm_dismiss_all": "Konfirmi: forĵeti ĉiujn atendantajn reagpetojn" + } + }, + "resolve_feedback": { + "title": "Solvi reagon", + "description": "{comparison_img}{candidates_table}\n**Detektita profilo**: {detected_profile} ({confidence_pct}%)\n**Taksita daŭro**: {est_duration_min} min\n**Fakta daŭro**: {act_duration_min} min\n\n__Elektu agon sube:__", + "data": { + "action": "Kion vi ŝatus fari?", + "corrected_profile": "Ĝusta programo (se korektante)", + "corrected_duration": "Ĝusta daŭro en sekundoj (se korektante)" + } + }, + "trim_cycle_select": { + "title": "Tondi ciklon - Elekti ciklon", + "description": "Elektu ciklon por tondi. ✓ = finita, ⚠ = rekomencita, ✗ = interrompita", + "data": { + "cycle_id": "Ciklo" + } + }, + "trim_cycle": { + "title": "Tondi ciklon", + "description": "Aktuala fenestro: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Ago" + } + }, + "trim_cycle_start": { + "title": "Agordi tondadan komencon", + "description": "Cikla fenestro: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuna komenco: **{current_wallclock}** (+{current_offset_min} min de ciklokomenco)\n\nElektu novan komencan horon uzante la horloĝan elektilon sube.", + "data": { + "trim_start_time": "Nova komenca tempo", + "trim_start_min": "Nova komenco (minutoj de ciklokomenco)" + } + }, + "trim_cycle_end": { + "title": "Agordi tondadan finon", + "description": "Cikla fenestro: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuna fino: **{current_wallclock}** (+{current_offset_min} min de ciklokomenco)\n\nElektu novan fintempon uzante la horloĝan elektilon sube.", + "data": { + "trim_end_time": "Nova fintempo", + "trim_end_min": "Nova fino (minutoj de ciklokomenco)" + } + } + }, + "error": { + "import_failed": "Importo malsukcesis. Bonvolu alglui validan WashData-eksportan JSON.", + "select_exactly_one": "Elektu ĝuste unu ciklon por ĉi tiu ago.", + "select_at_least_one": "Elektu almenaŭ unu ciklon por ĉi tiu ago.", + "select_at_least_two": "Elektu almenaŭ du ciklojn por ĉi tiu ago.", + "profile_exists": "Profilo kun ĉi tiu nomo jam ekzistas", + "rename_failed": "Malsukcesis renomigi profilon. Profilo eble ne ekzistas aŭ nova nomo jam estas uzata.", + "assignment_failed": "Malsukcesis asigni profilon al ciklo", + "duplicate_phase": "Fazo kun ĉi tiu nomo jam ekzistas.", + "phase_not_found": "Elektita fazo ne estis trovita.", + "invalid_phase_name": "Faza nomo ne povas esti malplena.", + "phase_name_too_long": "Faza nomo estas tro longa.", + "invalid_phase_range": "Faza intervalo estas nevalida. Fino devas esti pli granda ol komenco.", + "invalid_phase_timestamp": "Tempostampo estas nevalida. Uzu la formaton YYYY-MM-DD HH:MM aŭ HH:MM.", + "overlapping_phase_ranges": "Fazaj intervaloj interkovras. Ĝustigi la intervalojn kaj provi denove.", + "incomplete_phase_row": "Ĉiu faza vico devas inkludi fazon kaj pliajn kompletajn komenco/finvalorojn por la elektita reĝimo.", + "timestamp_mode_cycle_required": "Bonvolu elekti fonta ciklon kiam vi uzas tempostampilan reĝimon.", + "no_phase_ranges": "Neniuj fazaj intervaloj ankoraŭ disponeblaj por tiu ago.", + "feedback_notification_title": "WashData: Kontrolu ciklon ({device})", + "feedback_notification_message": "**Aparato**: {device}\n**Programo**: {program} ({confidence}% konfido)\n**Tempo**: {time}\n\nWashData bezonas vian helpon por kontroli ĉi tiun detektitan ciklon.\n\nBonvolu iri al **Agordoj > Aparatoj kaj Servoj > WashData > Agordi > Lernaj Reagoj** por konfirmi aŭ korekti ĉi tiun rezulton.", + "suggestions_ready_notification_title": "WashData: Sugestitaj agordoj pretaj ({device})", + "suggestions_ready_notification_message": "La **Sugestitaj Agordoj** sensilo nun raportas **{count}** ageblajn rekomendojn.\n\nPor revizii kaj apliki ilin: **Agordoj > Aparatoj kaj Servoj > WashData > Agordi > Altnivelaj Agordoj > Apliki Sugestitajn Valorojn**.\n\nSugestoj estas laŭvolaj kaj montrataj por revizio antaŭ ol vi konservas.", + "trim_range_invalid": "Komenco devas esti antaŭ fino. Bonvolu ĝustigi viajn tondadajn punktojn.", + "trim_failed": "Tondado malsukcesis. La ciklo eble ne plu ekzistas aŭ la fenestro estas malplena.", + "invalid_split_timestamp": "Tempomarko estas nevalida aŭ ekster la cikla fenestro. Uzu HH:MM aŭ HH:MM:SS ene de la cikla fenestro.", + "no_split_segments_found": "Neniuj validaj segmentoj povis esti produktitaj el ĉi tiuj tempomarkoj. Certigu, ke ĉiu tempomarko estas ene de la cikla fenestro kaj ke segmentoj longas almenaŭ 1 minuton.", + "auto_tune_suggestion": "{device_type} {device_title} detektis fantomajn ciklojn. Sugestita min_potenca ŝanĝo: {current_min}W -> {new_min}W (ne aplikata aŭtomate).", + "auto_tune_title": "Aŭtomatagordo de WashData", + "auto_tune_fallback": "{device_type} {device_title} detektis fantomajn ciklojn. Sugestita min_potenca ŝanĝo: {current_min}W -> {new_min}W (ne aplikata aŭtomate)." + }, + "abort": { + "no_cycles_found": "Neniuj cikloj trovitaj", + "no_split_segments_found": "Neniuj divideblaj segmentoj estis trovitaj en la elektita ciklo.", + "cycle_not_found": "La elektita ciklo ne povis esti ŝargita.", + "no_power_data": "Neniuj potencaj spurdatumoj disponeblaj por la elektita ciklo.", + "no_profiles_found": "Neniuj profiloj trovitaj. Kreu profilon unue.", + "no_profiles_for_matching": "Neniuj profiloj disponeblaj por kongruo. Kreu profilojn unue.", + "no_unlabeled_cycles": "Ĉiuj cikloj jam estas etikeditaj", + "no_suggestions": "Ankoraŭ neniuj sugestitaj valoroj disponeblaj. Rulu kelkajn ciklojn kaj provu denove.", + "no_predictions": "Neniuj prognozoj", + "no_custom_phases": "Neniuj propraj fazoj trovitaj.", + "reprocess_success": "Sukcese reprocesitaj {count} cikloj.", + "debug_data_cleared": "Sukcese forigitaj sencimigaj datumoj el {count} cikloj." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cikla komenco", + "cycle_finish": "Cikla fino", + "cycle_live": "Viva progreso" + } + }, + "common_text": { + "options": { + "unlabeled": "(Neetikedita)", + "create_new_profile": "Krei novan profilon", + "remove_label": "Forigi etikedon", + "no_reference_cycle": "(Neniu referenca ciklo)", + "all_device_types": "Ĉiuj aparataj tipoj", + "current_suffix": "(aktuala)", + "editor_select_info": "Elektu 1 ciklon por dividi, aŭ 2+ ciklojn por kunfandi.", + "editor_select_info_split": "Elektu 1 ciklon por disigi.", + "editor_select_info_merge": "Elektu 2+ ciklojn por kunfandi.", + "editor_select_info_delete": "Elektu 1+ ciklo(j)n por forigi.", + "no_cycles_recorded": "Neniuj cikloj ankoraŭ registritaj.", + "profile_summary_line": "- **{name}**: {count} cikloj, mezo {avg}m", + "no_profiles_created": "Neniuj profiloj kreitaj ankoraŭ.", + "phase_preview": "Faza antaŭvido", + "phase_preview_no_curve": "Averaĝa profila kurbo ankoraŭ ne disponebla. Rulu/etikedi pli da cikloj por ĉi tiu profilo.", + "average_power_curve": "Averaĝa potenca kurbo", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cikloj, ~{duration}m mezo)", + "profile_option_short_fmt": "{name} ({count} cikloj, ~{duration}m)", + "deleted_cycles_from_profile": "Forigitaj {count} cikloj de {profile}.", + "not_enough_data_graph": "Ne sufiĉas datumoj por generi grafeon.", + "no_profiles_found": "Neniuj profiloj trovitaj.", + "cycle_info_fmt": "Ciklo: {start}, {duration}m, Aktuala: {label}", + "top_candidates_header": "### Plej bonaj kandidatoj", + "tbl_profile": "Profilo", + "tbl_confidence": "Konfido", + "tbl_correlation": "Korelacio", + "tbl_duration_match": "Daŭra kongruo", + "detected_profile": "Detektita profilo", + "estimated_duration": "Taksita daŭro", + "actual_duration": "Fakta daŭro", + "choose_action": "__Elektu agon sube:__", + "tbl_count": "Kalkulo", + "tbl_avg": "Mezo", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energio (mezo)", + "tbl_energy_total": "Energio (totalo)", + "tbl_consistency": "Konsisteco", + "tbl_last_run": "Lasta ekzekuto", + "graph_legend_title": "Grafika legendo", + "graph_legend_body": "La blua bendo reprezentas la minimuman kaj maksimuman observitan potenckonsumo-amplitudon. La linio montras la averaĝan potencan kurbon.", + "recording_preview": "Registrada antaŭvido", + "trim_start": "Tondada komenco", + "trim_end": "Tondada fino", + "split_preview_title": "Divida antaŭvido", + "split_preview_found_fmt": "Trovitaj {count} segmentoj.", + "split_preview_confirm_fmt": "Klaku Konfirmi por dividi ĉi tiun ciklon en {count} apartajn ciklojn.", + "merge_preview_title": "Kunfanda antaŭvido", + "merge_preview_joining_fmt": "Kunfandante {count} ciklojn. Intertempoj estos plenigitaj per 0W-legaĵoj.", + "editor_delete_preview_title": "Foriga Antaŭrigardo", + "editor_delete_preview_intro": "La elektitaj cikloj estos konstante forigitaj:", + "editor_delete_preview_confirm": "Alklaku Konfirmi por konstante forigi ĉi tiujn ciklajn registrojn.", + "no_power_preview": "*Neniuj potencaj datumoj disponeblaj por antaŭvido.*", + "profile_comparison": "Profila komparo", + "actual_cycle_label": "Ĉi tiu ciklo (fakta)", + "storage_usage_header": "Stokada uzado", + "storage_file_size": "Dosiergrandeco", + "storage_cycles": "Cikloj", + "storage_profiles": "Profiloj", + "storage_debug_traces": "Sencimigaj spuroj", + "overview_suffix": "Superrigardo", + "phase_preview_for_profile": "Faza antaŭvido por profilo", + "current_ranges_header": "Aktualaj intervaloj", + "assign_phases_help": "Uzu agojn sube por aldoni, redakti, forigi aŭ konservi intervalojn.", + "profile_summary_header": "Profila resumo", + "recent_cycles_header": "Lastatempaj cikloj", + "trim_cycle_preview_title": "Cikla tondada antaŭvido", + "trim_cycle_preview_no_data": "Neniuj potencaj datumoj disponeblaj por ĉi tiu ciklo.", + "trim_cycle_preview_kept_suffix": "konservita", + "table_program": "Programo", + "table_when": "Kiam", + "table_length": "Daŭro", + "table_match": "Kongruo", + "table_profile": "Profilo", + "table_cycles": "Cikloj", + "table_avg_length": "Meza Daŭro", + "table_last_run": "Lasta ekzekuto", + "table_avg_energy": "Meza Energio", + "table_detected_program": "Detektita Programo", + "table_confidence": "Konfido", + "table_reported": "Raportita", + "phase_builtin_suffix": "(Enkonstruita)", + "phase_none_available": "Neniuj fazoj haveblaj.", + "settings_deprecation_warning": "⚠️ **Malrekomendita aparato-tipo:** {device_type} estas planita por forigo en estonta eldono. La kongrua dukto de WashData ne produktas fidindajn rezultojn por ĉi tiu aparato-klaso. Via integriĝo daŭre funkcias tra la malrekomendita periodo; por silentigi ĉi tiun averton, ŝanĝu **Aparato-Tipo** malsupre al unu el la subtenataj tipoj (Lavmaŝino, Sekigilo, Lavmaŝino-Sekigilo Kombo, Vazlavilo, Aerfritilo, Panmaŝino aŭ Pumpilo), aŭ al **Aliaj (Altnivelaj)** se via aparato ne kongruas kun iu ajn el la subtenataj tipoj. **Aliaj (Altnivelaj)** sendas intencite senmarkajn defaŭltojn kiuj ne estas agorditaj por iu specifa aparato, do vi devos mem agordi sojlojn, tempomalpermesojn kaj kongruajn parametrojn; ĉiuj viaj ekzistantaj agordoj estas konservitaj kiam vi ŝanĝas.", + "phase_other_device_types": "Aliaj aparatoj:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividi ciklon (trovi intertempojn)", + "merge": "Kunfandi ciklojn (kunigi fragmentojn)", + "delete": "Forigi ciklo(j)n" + } + }, + "split_mode": { + "options": { + "auto": "Aŭtomate detekti senaktivajn interspacojn", + "manual": "Manaj tempomarko(j)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Prizorgado: reprocesi kaj optimumigi datumojn", + "clear_debug_data": "Forigi sencimigajn datumojn (liberigi spacon)", + "wipe_history": "Forviŝi ĈIUJN datumojn por ĉi tiu aparato (nereversigebla)", + "export_import": "Eksporti/importi JSON kun agordoj (kopii/glui)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksporti ĉiujn datumojn", + "import": "Importi/kunfandi datumojn" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Aŭtomate etikedi malnovajn ciklojn", + "select_cycle_to_label": "Etikedi specifan ciklon", + "select_cycle_to_delete": "Forigi ciklon", + "interactive_editor": "Interaga redaktisto por kunfando/divido", + "trim_cycle": "Tondi ciklajn datumojn" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Krei novan profilon", + "edit_profile": "Redakti/renomigi profilon", + "delete_profile": "Forigi profilon", + "profile_stats": "Profilej statistikoj", + "cleanup_profile": "Purigi historion - grafiko kaj forigo", + "assign_phases": "Asigni fazajn intervalojn" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Krei novan fazon", + "edit_custom_phase": "Redakti fazon", + "delete_custom_phase": "Forigi fazon" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Aldoni fazan intervalon", + "edit_range": "Redakti fazan intervalon", + "delete_range": "Forigi fazan intervalon", + "clear_ranges": "Forigi ĉiujn intervalojn", + "auto_detect_ranges": "Aŭtomate detekti fazojn", + "save_ranges": "Konservi kaj reveni" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Refreŝigi staton", + "stop_recording": "Ĉesi registradon (konservi kaj procezi)", + "start_recording": "Komenci novan registradon", + "process_recording": "Procezi lastan registradon (tondadi kaj konservi)", + "discard_recording": "Forĵeti lastan registradon" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Krei novan profilon", + "existing_profile": "Aldoni al ekzistanta profilo", + "discard": "Forĵeti registradon" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Konfirmi - Ĝusta detektado", + "correct": "Korekti - Elekti programon/daŭron", + "ignore": "Ignori - Falsa pozitiva/brua ciklo", + "delete": "Forigi - Forigi el historio" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nomi kaj apliki fazojn", + "cancel": "Reiri sen ŝanĝoj" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Agordi komencan punkton", + "set_end": "Agordi finan punkton", + "reset": "Restarigi al plena daŭro", + "apply": "Apliki tondadon", + "cancel": "Nuligi" + } + }, + "device_type": { + "options": { + "washing_machine": "Lavmaŝino", + "dryer": "Sekigilo", + "washer_dryer": "Kombo Lav-Sekigilo", + "dishwasher": "Vazlavilo", + "coffee_machine": "Kafomaŝino (malrekomendita)", + "ev": "Elektra Veturilo (malrekomendita)", + "air_fryer": "Aera Fritilo", + "heat_pump": "Varmopumpilo (malrekomendita)", + "bread_maker": "Panfaristo", + "pump": "Pumpilo / Sumpa pumpilo", + "oven": "Forno (malrekomendita)", + "other": "Aliaj (Altnivelaj)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etikedi ciklon", + "description": "Asigni ekzistantan profilon al pasinta ciklo, aŭ forigi la etikedon.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por etikedi." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "La ID de la ciklo por etikedi." + }, + "profile_name": { + "name": "Profila nomo", + "description": "La nomo de ekzistanta profilo (kreu profilojn en la menuo Administri Profilojn). Lasu malplena por forigi etikedon." + } + } + }, + "create_profile": { + "name": "Krei profilon", + "description": "Krei novan profilon (sendependa aŭ bazita sur referenca ciklo).", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato." + }, + "profile_name": { + "name": "Profila nomo", + "description": "Nomo por la nova profilo (ekz. 'Peza Lavo', 'Delikatoj')." + }, + "reference_cycle_id": { + "name": "Referenca cikla ID", + "description": "Laŭvola cikla ID sur kiu bazi ĉi tiun profilon." + } + } + }, + "delete_profile": { + "name": "Forigi profilon", + "description": "Forigi profilon kaj laŭvole maletiked ciklojn uzantajn ĝin.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato." + }, + "profile_name": { + "name": "Profila nomo", + "description": "La profilo por forigi." + }, + "unlabel_cycles": { + "name": "Maletikedu ciklojn", + "description": "Forigi profiletikedon de cikloj uzantaj ĉi tiun profilon." + } + } + }, + "auto_label_cycles": { + "name": "Aŭtomate etikedi malnovajn ciklojn", + "description": "Retroaktive etikedi neetikeditajn ciklojn uzante profilan kongruon.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato." + }, + "confidence_threshold": { + "name": "Konfida sojlo", + "description": "Minimuma kongrua konfido (0.50-0.95) por apliki etikedojn." + } + } + }, + "export_config": { + "name": "Eksporti agordon", + "description": "Eksporti la profilojn, ciklojn kaj agordojn de ĉi tiu aparato al JSON-dosiero (po aparato).", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por eksporti." + }, + "path": { + "name": "Vojo", + "description": "Laŭvola absoluta dosiera vojo por skribi (defaŭlte al /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importi agordon", + "description": "Importi profilojn, ciklojn kaj agordojn por ĉi tiu aparato el JSON-eksporta dosiero (agordoj povas esti anstataŭitaj).", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por importi." + }, + "path": { + "name": "Vojo", + "description": "Absoluta vojo al la eksporta JSON-dosiero por importi." + } + } + }, + "submit_cycle_feedback": { + "name": "Sendi ciklan reagon", + "description": "Konfirmi aŭ korekti aŭtomate detektitan programon post finita ciklo. Provizu aŭ `entry_id` (altnivela) aŭ `device_id` (rekomendita).", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato (rekomendita anstataŭ entry_id)." + }, + "entry_id": { + "name": "Eniga ID", + "description": "La agorda eniga ID por la aparato (alternativo al device_id)." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "La cikla identigilo montrita en la reaga sciigo / protokoloj." + }, + "user_confirmed": { + "name": "Konfirmi detektitan programon", + "description": "Agordu vera se la detektita programo estas ĝusta." + }, + "corrected_profile": { + "name": "Korektita profilo", + "description": "Se ne konfirmita, donu la ĝustan profilan/programan nomon." + }, + "corrected_duration": { + "name": "Korektita daŭro (sekundoj)", + "description": "Laŭvola korektita daŭro en sekundoj." + }, + "notes": { + "name": "Notoj", + "description": "Laŭvolaj notoj pri ĉi tiu ciklo." + } + } + }, + "record_start": { + "name": "Registri ciklan komencon", + "description": "Komenci mane registri puran ciklon (preterpasas ĉian kongruigan logikon).", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por registri." + } + } + }, + "record_stop": { + "name": "Registri ciklan halton", + "description": "Ĉesi manan registradon.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por ĉesi registradon." + } + } + }, + "trim_cycle": { + "name": "Tondi ciklon", + "description": "Tondadi la potencajn datumojn de pasinta ciklo al specifa tempofenestro.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "La ID de la ciklo por tondadi." + }, + "trim_start_s": { + "name": "Tondada komenco (sekundoj)", + "description": "Konservi datumojn de ĉi tiu ofseto en sekundoj. Defaŭlte 0." + }, + "trim_end_s": { + "name": "Tondada fino (sekundoj)", + "description": "Konservi datumojn ĝis ĉi tiu ofseto en sekundoj. Defaŭlte = plena daŭro." + } + } + }, + "pause_cycle": { + "name": "Paŭza Ciklo", + "description": "Paŭzu la aktivan ciklon por WashData-aparato.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato por paŭzi." + } + } + }, + "resume_cycle": { + "name": "Rekomencu Ciklon", + "description": "Rekomencu paŭzitan ciklon por WashData-aparato.", + "fields": { + "device_id": { + "name": "Aparato", + "description": "La WashData-aparato rekomencos." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Funkcianta" + }, + "match_ambiguity": { + "name": "Kongrua ambigueco" + } + }, + "sensor": { + "washer_state": { + "name": "Stato", + "state": { + "off": "Malŝaltita", + "idle": "Senokupa", + "starting": "Startas", + "running": "Funkcianta", + "paused": "Paŭzita", + "user_paused": "Paŭzita de uzanto", + "ending": "Finiĝanta", + "finished": "Finita", + "anti_wrinkle": "Kontraŭfaldiga", + "delay_wait": "Atendante Komenci", + "interrupted": "Interrompita", + "force_stopped": "Perforte haltita", + "rinse": "Rinso", + "unknown": "Nekonata", + "clean": "Pura" + } + }, + "washer_program": { + "name": "Programo" + }, + "time_remaining": { + "name": "Restanta tempo" + }, + "total_duration": { + "name": "Tuta daŭro" + }, + "cycle_progress": { + "name": "Progreso" + }, + "current_power": { + "name": "Aktuala potenco" + }, + "elapsed_time": { + "name": "Pasinta tempo" + }, + "debug_info": { + "name": "Sencimigaj informoj" + }, + "match_confidence": { + "name": "Kongrua konfido" + }, + "top_candidates": { + "name": "Plej bonaj kandidatoj", + "state": { + "none": "Neniu" + } + }, + "current_phase": { + "name": "Aktuala fazo" + }, + "wash_phase": { + "name": "Fazo" + }, + "profile_cycle_count": { + "name": "Profilo {profile_name} kalkulo", + "unit_of_measurement": "cikloj" + }, + "suggestions": { + "name": "Disponeblaj sugestitaj agordoj" + }, + "pump_runs_today": { + "name": "Pumpilaj Funkcioj (Lastaj 24 horoj)" + }, + "cycle_count": { + "name": "Ciklokalkulo" + } + }, + "select": { + "program_select": { + "name": "Cikla programo", + "state": { + "auto_detect": "Aŭtomata detektado" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Perforte fini ciklon" + }, + "pause_cycle": { + "name": "Paŭza Ciklo" + }, + "resume_cycle": { + "name": "Rekomencu Ciklon" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Valida device_id estas bezonata." + }, + "cycle_id_required": { + "message": "Valida cycle_id estas bezonata." + }, + "profile_name_required": { + "message": "Valida profile_name estas bezonata." + }, + "device_not_found": { + "message": "Aparato ne trovita." + }, + "no_config_entry": { + "message": "Neniu agorda eniro trovita por aparato." + }, + "integration_not_loaded": { + "message": "Integraĵo ne ŝarĝita por ĉi tiu aparato." + }, + "cycle_not_found_or_no_power": { + "message": "Ciklo ne trovita aŭ havas neniujn potencajn datumojn." + }, + "trim_failed_empty_window": { + "message": "Tondado malsukcesis - ciklo ne trovita, neniuj potencaj spurdatumoj, aŭ rezulta fenestro estas malplena." + }, + "trim_invalid_range": { + "message": "trim_end_s devas esti pli granda ol trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/es-419.json b/custom_components/ha_washdata/translations/es-419.json new file mode 100644 index 0000000..61ebcb3 --- /dev/null +++ b/custom_components/ha_washdata/translations/es-419.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuración de WashData", + "description": "Configura tu lavadora u otro electrodoméstico.\n\nSe requiere un sensor de potencia.\n\n**A continuación, se te preguntará si deseas crear tu primer perfil.**", + "data": { + "name": "Nombre del dispositivo", + "device_type": "Tipo de dispositivo", + "power_sensor": "Sensor de potencia", + "min_power": "Umbral mínimo de potencia (W)" + }, + "data_description": { + "name": "Un nombre descriptivo para este dispositivo (por ejemplo, \"Lavadora\", \"Lavavajillas\").", + "device_type": "¿Qué tipo de electrodoméstico es este? Ayuda a personalizar la detección y el etiquetado.", + "power_sensor": "La entidad de sensor que informa el consumo de potencia en tiempo real (en vatios) desde tu enchufe inteligente.", + "min_power": "Las lecturas de potencia por encima de este umbral (en vatios) indican que el electrodoméstico está en funcionamiento. Empieza con 2 W para la mayoría de los dispositivos." + } + }, + "first_profile": { + "title": "Crear primer perfil", + "description": "Un **Ciclo** es una ejecución completa del electrodoméstico (por ejemplo, una carga de ropa). Un **Perfil** agrupa estos ciclos por tipo (por ejemplo, \"Algodón\", \"Lavado rápido\"). Crear un perfil ahora ayuda a la integración a estimar la duración de inmediato.\n\nPuedes seleccionar este perfil manualmente en los controles del dispositivo si no se detecta automáticamente.\n\n**Deja el \"Nombre del perfil\" vacío para omitir este paso.**", + "data": { + "profile_name": "Nombre del perfil (opcional)", + "manual_duration": "Duración estimada (minutos)" + } + } + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticación no válida", + "unknown": "Error inesperado" + }, + "abort": { + "already_configured": "El dispositivo ya está configurado" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revisar retroalimentación de aprendizaje", + "settings": "Ajustes", + "notifications": "Notificaciones", + "manage_cycles": "Gestionar ciclos", + "manage_profiles": "Gestionar perfiles", + "manage_phase_catalog": "Gestionar catálogo de fases", + "record_cycle": "Registrar ciclo (manual)", + "diagnostics": "Diagnóstico y mantenimiento" + } + }, + "apply_suggestions": { + "title": "Copiar valores sugeridos", + "description": "Esto copiará los valores sugeridos actuales en tu configuración guardada. Esta es una acción única y no habilita sobreescrituras automáticas.\n\nValores sugeridos para copiar:\n{suggested}", + "data": { + "confirm": "Confirmar copia" + } + }, + "apply_suggestions_confirm": { + "title": "Revisar cambios sugeridos", + "description": "WashData encontró **{pending_count}** valores sugeridos para aplicar.\n\nCambios:\n{changes}\n\n✅ Marque la casilla a continuación y haga clic en **Enviar** para aplicar y guardar estos cambios inmediatamente.\n❌ Déjalo sin marcar y haz clic en **Enviar** para cancelar y regresar.", + "data": { + "confirm_apply_suggestions": "Aplicar y guardar estos cambios." + } + }, + "settings": { + "title": "Ajustes", + "description": "{deprecation_warning}Ajusta los umbrales de detección, el aprendizaje y el comportamiento avanzado. Los valores predeterminados funcionan bien para la mayoría de las configuraciones.\n\nAjustes sugeridos disponibles actualmente: {suggestions_count}.\nSi es mayor que 0, abre Configuración avanzada y usa Aplicar valores sugeridos para revisar las recomendaciones.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos", + "device_type": "Tipo de dispositivo", + "power_sensor": "Entidad del sensor de potencia", + "min_power": "Potencia mínima (W)", + "off_delay": "Retraso de fin de ciclo (s)", + "notify_actions": "Acciones de notificación", + "notify_people": "Retrasar entrega hasta que estas personas estén en casa", + "notify_only_when_home": "Retrasar notificaciones hasta que la persona seleccionada esté en casa", + "notify_fire_events": "Disparar eventos de automatización", + "notify_start_services": "Inicio del ciclo: objetivos de notificación", + "notify_finish_services": "Fin del ciclo: objetivos de notificación", + "notify_live_services": "Progreso en vivo: objetivos de notificación", + "notify_before_end_minutes": "Notificación previa a la finalización (minutos antes del final)", + "notify_title": "Título de la notificación", + "notify_icon": "Ícono de notificación", + "notify_start_message": "Formato del mensaje de inicio", + "notify_finish_message": "Formato del mensaje de finalización", + "notify_pre_complete_message": "Formato del mensaje previo a la finalización", + "show_advanced": "Editar configuración avanzada (siguiente paso)" + }, + "data_description": { + "apply_suggestions": "Marca esta casilla para copiar los valores sugeridos por el algoritmo de aprendizaje en el formulario. Revisa antes de guardar.\n\nSugerido (del aprendizaje):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selecciona el tipo de electrodoméstico (Lavadora, Secadora, Lavavajillas, Cafetera, Vehículo eléctrico). Esta etiqueta se almacena con cada ciclo para una mejor organización.", + "power_sensor": "La entidad de sensor que informa la potencia en tiempo real (en vatios). Puedes cambiarla en cualquier momento sin perder datos históricos, lo cual es útil si reemplazas un enchufe inteligente.", + "min_power": "Las lecturas de potencia por encima de este umbral (en vatios) indican que el electrodoméstico está en funcionamiento. Empieza con 2 W para la mayoría de los dispositivos.", + "off_delay": "Segundos que la potencia suavizada debe mantenerse por debajo del umbral de parada para marcar la finalización. Predeterminado: 120 s.", + "notify_actions": "Secuencia de acciones opcional de Home Assistant para notificaciones (scripts, notify, TTS, etc.). Si se configura, las acciones se ejecutan antes del servicio de notificación de reserva.", + "notify_people": "Entidades de personas usadas para control de presencia. Cuando está habilitado, la entrega de notificaciones se retrasa hasta que al menos una persona seleccionada esté en casa.", + "notify_only_when_home": "Si está habilitado, retrasa las notificaciones hasta que al menos una persona seleccionada esté en casa.", + "notify_fire_events": "Si está habilitado, dispara eventos de Home Assistant para el inicio y fin de ciclo (ha_washdata_cycle_started y ha_washdata_cycle_ended) para automatizaciones.", + "notify_start_services": "Uno o más servicios de notificación para alertar cuando comienza un ciclo. Déjelo vacío para omitir las notificaciones de inicio.", + "notify_finish_services": "Uno o más servicios de notificación para alertar cuando un ciclo finaliza o está a punto de completarse. Déjelo vacío para omitir las notificaciones de finalización.", + "notify_live_services": "Uno o más servicios de notificación para recibir actualizaciones de progreso en vivo mientras se ejecuta el ciclo (solo aplicación móvil complementaria). Déjelo vacío para omitir las actualizaciones en vivo.", + "notify_before_end_minutes": "Minutos antes del fin estimado del ciclo para activar una notificación. Pon 0 para deshabilitar. Predeterminado: 0.", + "notify_title": "Título personalizado para notificaciones. Admite el marcador {device}.", + "notify_icon": "Ícono para notificaciones (por ejemplo, mdi:washing-machine). Deja vacío para enviar sin ícono.", + "notify_start_message": "Mensaje enviado cuando comienza un ciclo. Admite el marcador {device}.", + "notify_finish_message": "Mensaje enviado cuando finaliza un ciclo. Admite los marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texto de las actualizaciones recurrentes del progreso en vivo mientras se ejecuta el ciclo. Admite marcadores de posición {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificaciones", + "description": "Configura los canales de notificación, las plantillas y las actualizaciones opcionales de progreso en vivo para móviles.", + "data": { + "notify_actions": "Acciones de notificación", + "notify_people": "Retrasar entrega hasta que estas personas estén en casa", + "notify_only_when_home": "Retrasar notificaciones hasta que la persona seleccionada esté en casa", + "notify_fire_events": "Disparar eventos de automatización", + "notify_start_services": "Inicio del ciclo: objetivos de notificación", + "notify_finish_services": "Fin del ciclo: objetivos de notificación", + "notify_live_services": "Progreso en vivo: objetivos de notificación", + "notify_before_end_minutes": "Notificación previa a la finalización (minutos antes del final)", + "notify_live_interval_seconds": "Intervalo de actualización en vivo (segundos)", + "notify_live_overrun_percent": "Margen de sobrepasar el tiempo estimado en actualizaciones en vivo (%)", + "notify_live_chronometer": "Temporizador de cuenta atrás del cronómetro", + "notify_title": "Título de la notificación", + "notify_icon": "Ícono de notificación", + "notify_start_message": "Formato del mensaje de inicio", + "notify_finish_message": "Formato del mensaje de finalización", + "notify_pre_complete_message": "Formato del mensaje previo a la finalización", + "energy_price_entity": "Precio de la energía: entidad (opcional)", + "energy_price_static": "Precio de la energía: valor estático (opcional)", + "go_back": "Volver sin guardar", + "notify_reminder_message": "Formato de mensaje de recordatorio", + "notify_timeout_seconds": "Auto-desestimación Después (segundos)", + "notify_channel": "Canal de notificación (Estado/live/reminder)", + "notify_finish_channel": "Canal de notificación terminado" + }, + "data_description": { + "go_back": "Marca esto y haz clic en Enviar para descartar los cambios anteriores y volver al menú principal.", + "notify_actions": "Secuencia de acciones opcional de Home Assistant para notificaciones (scripts, notify, TTS, etc.). Si se configura, las acciones se ejecutan antes del servicio de notificación de reserva.", + "notify_people": "Entidades de personas usadas para control de presencia. Cuando está habilitado, la entrega de notificaciones se retrasa hasta que al menos una persona seleccionada esté en casa.", + "notify_only_when_home": "Si está habilitado, retrasa las notificaciones hasta que al menos una persona seleccionada esté en casa.", + "notify_fire_events": "Si está habilitado, dispara eventos de Home Assistant para el inicio y fin de ciclo (ha_washdata_cycle_started y ha_washdata_cycle_ended) para automatizaciones.", + "notify_start_services": "Uno o más servicios de notificación para avisar cuando comienza un ciclo (por ejemplo, notify.mobile_app_my_phone). Déjelo vacío para omitir las notificaciones de inicio.", + "notify_finish_services": "Uno o más servicios de notificación para alertar cuando un ciclo finaliza o está a punto de completarse. Déjelo vacío para omitir las notificaciones de finalización.", + "notify_live_services": "Uno o más servicios de notificación para recibir actualizaciones de progreso en vivo mientras se ejecuta el ciclo (solo aplicación móvil complementaria). Déjelo vacío para omitir las actualizaciones en vivo.", + "notify_before_end_minutes": "Minutos antes del fin estimado del ciclo para activar una notificación. Pon 0 para deshabilitar. Predeterminado: 0.", + "notify_live_interval_seconds": "Con qué frecuencia se envían actualizaciones de progreso durante el funcionamiento. Los valores más bajos son más responsivos pero consumen más notificaciones. Se aplica un mínimo de 30 segundos; los valores por debajo de 30 s se redondean automáticamente a 30 s.", + "notify_live_overrun_percent": "Margen de seguridad sobre el recuento estimado de actualizaciones para ciclos largos o que se extienden (por ejemplo, el 20% permite 1,2 veces las actualizaciones estimadas).", + "notify_live_chronometer": "Cuando está habilitada, cada actualización en vivo incluye una cuenta regresiva del cronómetro hasta el tiempo estimado de finalización. El temporizador de notificaciones avanza automáticamente en el dispositivo entre actualizaciones (solo aplicación complementaria de Android).", + "notify_title": "Título personalizado para notificaciones. Admite el marcador {device}.", + "notify_icon": "Ícono para notificaciones (por ejemplo, mdi:washing-machine). Deja vacío para enviar sin ícono.", + "notify_start_message": "Mensaje enviado cuando comienza un ciclo. Admite el marcador {device}.", + "notify_finish_message": "Mensaje enviado cuando finaliza un ciclo. Admite los marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Seleccione una entidad numérica que contenga el precio actual de la electricidad (por ejemplo, sensor.electricity_price, input_number.energy_rate). Cuando está configurado, habilita el marcador de posición {cost}. Tiene prioridad sobre el valor estático si ambos están configurados.", + "energy_price_static": "Precio fijo de la electricidad por kWh. Cuando está configurado, habilita el marcador de posición {cost}. Se ignora si una entidad está configurada arriba. Agregue su símbolo de moneda en la plantilla de mensaje, p. {cost}€.", + "notify_reminder_message": "Texto del recordatorio de una sola vez envió el número configurado de minutos antes del final estimado. Distinto de las actualizaciones en vivo recurrentes arriba. Apoyo {device}, {minutes}, {program} propietarios de puestos.", + "notify_timeout_seconds": "Descargue automáticamente las notificaciones después de estos muchos segundos (en adelante como la aplicación de acompañamiento 'timeout'). Se establece a 0 para mantenerlos hasta que sea despedido. Predeterminado: 0.", + "notify_channel": "Android canal de notificación de aplicaciones acompañantes para el estado, progreso en vivo y notificaciones de recordatorio. El sonido y la importancia de un canal se configuran en la aplicación compañera la primera vez que se utiliza el nombre del canal - WashData sólo establece el nombre del canal. Deja vacío para el defecto de la aplicación.", + "notify_finish_channel": "Separar Android canal para la alerta terminada (también utilizado por el recordatorio y el aviso persistente de espera de la colada) para que pueda tener su propio sonido. Deja vacío para reutilizar el canal de arriba.", + "notify_pre_complete_message": "Texto de las actualizaciones recurrentes del progreso en vivo mientras se ejecuta el ciclo. Admite marcadores de posición {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Configuración avanzada", + "description": "Ajusta los umbrales de detección, el aprendizaje y el comportamiento avanzado. Los valores predeterminados funcionan bien para la mayoría de las configuraciones.", + "sections": { + "suggestions_section": { + "name": "Ajustes sugeridos", + "description": "{suggestions_count} sugerencias aprendidas disponibles. Marca Aplicar valores sugeridos para revisar los cambios propuestos antes de guardar.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos" + }, + "data_description": { + "apply_suggestions": "Marca esta casilla para abrir un paso de revisión de los valores sugeridos por el algoritmo de aprendizaje. Nada se guarda automáticamente.\n\nSugerido (del aprendizaje):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detección y umbrales de potencia", + "description": "Cuándo se considera que el aparato está ENCENDIDO o APAGADO, umbrales de energía y temporización de las transiciones de estado.", + "data": { + "start_duration_threshold": "Duración de antirrebote de inicio (s)", + "start_energy_threshold": "Umbral de energía de inicio (Wh)", + "completion_min_seconds": "Tiempo mínimo de ejecución para completar (segundos)", + "start_threshold_w": "Umbral de inicio (W)", + "stop_threshold_w": "Umbral de parada (W)", + "end_energy_threshold": "Umbral de energía de fin (Wh)", + "running_dead_zone": "Zona muerta en ejecución (segundos)", + "end_repeat_count": "Recuento de repeticiones de fin", + "min_off_gap": "Brecha mínima entre ciclos (s)", + "sampling_interval": "Intervalo de muestreo (s)" + }, + "data_description": { + "start_duration_threshold": "Ignora los picos breves de potencia más cortos que esta duración (segundos). Filtra picos de arranque (por ejemplo, 60 W durante 2 s) antes del inicio real del ciclo. Predeterminado: 5 s.", + "start_energy_threshold": "El ciclo debe acumular al menos esta energía (Wh) durante la fase de INICIO para confirmarse. Predeterminado: 0,005 Wh.", + "completion_min_seconds": "Los ciclos más cortos se marcarán como \"interrumpidos\" incluso si finalizan de forma natural. Predeterminado: 600 s.", + "start_threshold_w": "La potencia debe superar ESTE umbral para INICIAR un ciclo. Configúralo por encima de tu potencia en espera. Predeterminado: min_power + 1 W.", + "stop_threshold_w": "La potencia debe caer POR DEBAJO de este umbral para FINALIZAR un ciclo. Configúralo justo por encima de cero para evitar que el ruido provoque finales falsos. Predeterminado: 0,6 × min_power.", + "end_energy_threshold": "El ciclo finaliza solo si la energía en la última ventana de retraso de apagado está por debajo de este valor (Wh). Evita finales falsos si la potencia fluctúa cerca de cero. Predeterminado: 0,05 Wh.", + "running_dead_zone": "Tras el inicio del ciclo, ignora las caídas de potencia durante estos segundos para evitar la detección falsa del final durante la fase de arranque. Pon 0 para deshabilitar. Predeterminado: 0 s.", + "end_repeat_count": "Número de veces que la condición de fin (potencia por debajo del umbral durante off_delay) debe cumplirse consecutivamente antes de finalizar el ciclo. Valores más altos evitan finales falsos durante las pausas. Predeterminado: 1.", + "min_off_gap": "Si un ciclo comienza dentro de estos segundos tras finalizar el anterior, se FUSIONARÁN en un único ciclo.", + "sampling_interval": "Intervalo mínimo de muestreo en segundos. Se ignorarán las actualizaciones más rápidas que esta velocidad. Valor predeterminado: 30 s (2 s para lavadoras, lavadoras-secadoras y lavavajillas)." + } + }, + "matching_section": { + "name": "Coincidencia de perfiles y aprendizaje", + "description": "Qué tan agresivamente WashData hace coincidir los ciclos en curso con los perfiles aprendidos y cuándo pedir comentarios.", + "data": { + "profile_match_min_duration_ratio": "Proporción mínima de duración para coincidencia de perfil (0,1-1,0)", + "profile_match_interval": "Intervalo de coincidencia de perfil (segundos)", + "profile_match_threshold": "Umbral de coincidencia de perfil", + "profile_unmatch_threshold": "Umbral de no coincidencia de perfil", + "auto_label_confidence": "Confianza de etiquetado automático (0-1)", + "learning_confidence": "Confianza mínima para solicitar retroalimentación (0-1)", + "suppress_feedback_notifications": "Suprimir notificaciones de comentarios", + "duration_tolerance": "Tolerancia de duración (0-0,5)", + "smoothing_window": "Ventana de suavizado (muestras)", + "profile_duration_tolerance": "Tolerancia de duración para coincidencia de perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Proporción mínima de duración para la coincidencia de perfil. El ciclo en ejecución debe ser al menos esta fracción de la duración del perfil para coincidir. Más bajo = coincidencia más temprana. Predeterminado: 0,50 (50%).", + "profile_match_interval": "Con qué frecuencia (en segundos) intentar la coincidencia de perfil durante un ciclo. Los valores más bajos dan una detección más rápida pero usan más CPU. Predeterminado: 300 s (5 minutos).", + "profile_match_threshold": "Puntuación mínima de similitud DTW (0,0–1,0) requerida para considerar un perfil como coincidencia. Más alto = coincidencia más estricta, menos falsos positivos. Predeterminado: 0,4.", + "profile_unmatch_threshold": "Puntuación de similitud DTW (0,0–1,0) por debajo de la cual se rechaza un perfil previamente coincidente. Debe ser inferior al umbral de coincidencia para evitar parpadeos. Predeterminado: 0,35.", + "auto_label_confidence": "Etiqueta automáticamente los ciclos con esta confianza o superior al finalizar (0,0–1,0).", + "learning_confidence": "Confianza mínima para solicitar verificación del usuario mediante un evento (0,0–1,0).", + "suppress_feedback_notifications": "Cuando está habilitado, WashData seguirá rastreando y solicitando comentarios internamente, pero no mostrará notificaciones persistentes pidiéndole que confirme los ciclos. Útil cuando los ciclos siempre se detectan correctamente y las indicaciones le distraen.", + "duration_tolerance": "Tolerancia para las estimaciones de tiempo restante durante una ejecución (0,0–0,5 corresponde a 0–50%).", + "smoothing_window": "Número de lecturas de potencia recientes usadas para el suavizado de media móvil.", + "profile_duration_tolerance": "Tolerancia usada por la coincidencia de perfil para la variación de duración total (0,0–0,5). Comportamiento predeterminado: ±25%." + } + }, + "timing_section": { + "name": "Tiempo, mantenimiento y depuración", + "description": "Watchdog, reinicio del progreso, mantenimiento automático y exposición de entidades de depuración.", + "data": { + "watchdog_interval": "Intervalo de vigilancia (segundos)", + "no_update_active_timeout": "Tiempo de espera sin actualización (s)", + "progress_reset_delay": "Retraso de restablecimiento del progreso (segundos)", + "auto_maintenance": "Habilitar mantenimiento automático", + "expose_debug_entities": "Exponer entidades de depuración", + "save_debug_traces": "Guardar trazas de depuración" + }, + "data_description": { + "watchdog_interval": "Segundos entre comprobaciones de vigilancia durante la ejecución. Predeterminado: 5 s. ADVERTENCIA: Asegúrate de que sea MAYOR que el intervalo de actualización de tu sensor para evitar paradas falsas (por ejemplo, si el sensor se actualiza cada 60 s, usa 65 s o más).", + "no_update_active_timeout": "Para sensores que publican solo al cambiar: fuerza el fin de un ciclo ACTIVO solo si no llegan actualizaciones durante estos segundos. La finalización de bajo consumo sigue usando el retraso de apagado.", + "progress_reset_delay": "Tras completar un ciclo (100%), espera estos segundos de inactividad antes de restablecer el progreso al 0%. Predeterminado: 300 s.", + "auto_maintenance": "Habilitar mantenimiento automático (reparar muestras, fusionar fragmentos).", + "expose_debug_entities": "Mostrar sensores avanzados (Confianza, Fase, Ambigüedad) para depuración.", + "save_debug_traces": "Almacenar datos detallados de clasificación y trazas de potencia en el historial (aumenta el uso de almacenamiento)." + } + }, + "anti_wrinkle_section": { + "name": "Escudo antiarrugas (secadoras)", + "description": "Ignora los giros del tambor tras el ciclo para evitar ciclos fantasma. Solo secadora / lavasecadora.", + "data": { + "anti_wrinkle_enabled": "Escudo antiarrugas (solo secadora/lavadora-secadora)", + "anti_wrinkle_max_power": "Potencia máxima antiarrugas (W) (solo secadora/lavadora-secadora)", + "anti_wrinkle_max_duration": "Duración máxima antiarrugas (s) (solo secadora/lavadora-secadora)", + "anti_wrinkle_exit_power": "Potencia de salida antiarrugas (W) (solo secadora/lavadora-secadora)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignora las breves rotaciones del tambor a baja potencia tras el fin de un ciclo para evitar ciclos fantasma (solo secadora/lavadora-secadora).", + "anti_wrinkle_max_power": "Las ráfagas a esta potencia o por debajo se tratan como rotaciones antiarrugas en lugar de nuevos ciclos. Solo se aplica cuando el Escudo antiarrugas está habilitado.", + "anti_wrinkle_max_duration": "Duración máxima de ráfaga para tratar como rotación antiarrugas. Solo se aplica cuando el Escudo antiarrugas está habilitado.", + "anti_wrinkle_exit_power": "Si la potencia cae por debajo de este nivel, sale del modo antiarrugas inmediatamente (apagado real). Solo se aplica cuando el Escudo antiarrugas está habilitado." + } + }, + "delay_start_section": { + "name": "Detección de inicio diferido", + "description": "Trata el modo de espera sostenido de baja potencia como 'Esperando para iniciar' hasta que el ciclo comience realmente.", + "data": { + "delay_start_detect_enabled": "Habilitar la detección de inicio retrasado", + "delay_confirm_seconds": "Inicio diferido: tiempo de confirmación en espera (s)", + "delay_timeout_hours": "Inicio retrasado: tiempo máximo de espera (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Cuando está habilitado, se detecta un breve pico de consumo de baja potencia seguido de energía de reserva sostenida como un inicio retrasado. El sensor de estado muestra \"Esperando para comenzar\" durante el retraso. No se realiza un seguimiento del tiempo ni del progreso del programa hasta que el ciclo realmente comienza. Requiere que start_threshold_w se establezca por encima de la potencia del pico de drenaje.", + "delay_confirm_seconds": "La potencia debe permanecer entre Umbral de parada (W) y Umbral de inicio (W) durante este número de segundos antes de que WashData pase a 'Esperando para iniciar'. Filtra picos breves al navegar por menús. Predeterminado: 60 s.", + "delay_timeout_hours": "Tiempo de espera de seguridad: si la máquina todavía está en 'Esperando para iniciar' después de tantas horas, WashData se restablece en Apagado. Predeterminado: 8 h." + } + }, + "external_triggers_section": { + "name": "Disparadores externos, puerta y pausa", + "description": "Sensor externo opcional de fin de ciclo, sensor de puerta para detección de pausa/limpio e interruptor de corte de energía al pausar.", + "data": { + "external_end_trigger_enabled": "Habilitar activador externo de fin de ciclo", + "external_end_trigger": "Sensor externo de fin de ciclo", + "external_end_trigger_inverted": "Invertir lógica de activación (activar en OFF)", + "door_sensor_entity": "Sensor de puerta", + "pause_cuts_power": "Cortar la energía al hacer una pausa", + "switch_entity": "Cambiar entidad (para pausar el corte de energía)", + "notify_unload_delay_minutes": "Retraso de notificación de espera de lavandería (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Habilitar la monitorización de un sensor binario externo para activar la finalización del ciclo.", + "external_end_trigger": "Selecciona una entidad de sensor binario. Cuando este sensor se activa, el ciclo actual finalizará con el estado \"completado\".", + "external_end_trigger_inverted": "De forma predeterminada, el activador se dispara cuando el sensor se enciende (ON). Marca esta casilla para disparar cuando el sensor se apague (OFF).", + "door_sensor_entity": "Sensor binario opcional para la puerta de la máquina (on = abierto, off = cerrado). Cuando la puerta se abre durante un ciclo activo, WashData confirmará que el ciclo está pausado intencionalmente. Una vez finalizado el ciclo, si la puerta aún está cerrada, el estado cambia a \"Limpiar\" hasta que se abre la puerta. Nota: cerrar la puerta NO reanuda automáticamente un ciclo en pausa; la reanudación debe activarse manualmente mediante el botón Reanudar ciclo o el servicio.", + "pause_cuts_power": "Al hacer una pausa mediante el botón o servicio Pause Cycle, también apague la entidad de conmutación configurada. Solo tiene efecto si se configura una entidad de conmutación para este dispositivo.", + "switch_entity": "La entidad de cambio para alternar al pausar o reanudar. Requerido cuando está habilitado 'Cortar energía al pausar'.", + "notify_unload_delay_minutes": "Envíe una notificación a través del canal de notificación de finalización después de que finalice el ciclo y la puerta no se haya abierto durante tantos minutos (requiere sensor de puerta). Establezca en 0 para desactivar. Predeterminado: 60 min." + } + }, + "pump_section": { + "name": "Monitor de bomba", + "description": "Solo para el tipo de dispositivo bomba. Dispara un evento si un ciclo de bomba supera esta duración.", + "data": { + "pump_stuck_duration": "Umbrales de alerta de bomba atascada (sólo bomba)" + }, + "data_description": { + "pump_stuck_duration": "Si un ciclo de bomba sigue ejecutándose después de tantos segundos, se activa una vez un evento ha_washdata_pump_stuck. Utilícelo para detectar un motor atascado (el funcionamiento típico de la bomba de sumidero es inferior a 60 s). Predeterminado: 1800 s (30 min). Solo tipo de dispositivo de bomba." + } + }, + "device_link_section": { + "name": "Enlace de dispositivo", + "description": "Opcionalmente vincula este dispositivo WashData a un dispositivo existente Home Assistant (por ejemplo, el plug inteligente o el propio aparato). El dispositivo WashData se muestra entonces como \"Connected via\" ese dispositivo. Deja vacío para mantener a WashData como un dispositivo independiente.", + "data": { + "linked_device": "Dispositivo vinculado" + }, + "data_description": { + "linked_device": "Seleccione el dispositivo para conectar WashData a. Esto agrega una referencia 'Connected via' en el registro del dispositivo; WashData mantiene su propia tarjeta de dispositivo y entidades. Limpiar la selección para eliminar el enlace." + } + } + } + }, + "diagnostics": { + "title": "Diagnóstico y mantenimiento", + "description": "Ejecuta acciones de mantenimiento como fusionar ciclos fragmentados o migrar datos almacenados.\n\n**Uso de almacenamiento**\n\n- Tamaño del archivo: {file_size_kb} KB\n- Ciclos: {cycle_count}\n- Perfiles: {profile_count}\n- Trazas de depuración: {debug_count}", + "menu_options": { + "reprocess_history": "Mantenimiento: reprocesar y optimizar datos", + "clear_debug_data": "Borrar datos de depuración (liberar espacio)", + "wipe_history": "Borrar TODOS los datos de este dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con ajustes (copiar/pegar)", + "menu_back": "← Volver" + } + }, + "clear_debug_data": { + "title": "Borrar datos de depuración", + "description": "¿Estás seguro de que deseas eliminar todas las trazas de depuración almacenadas? Esto liberará espacio pero eliminará la información detallada de clasificación de ciclos anteriores." + }, + "manage_cycles": { + "title": "Gestionar ciclos", + "description": "Ciclos recientes:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "select_cycle_to_label": "Etiquetar ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusión/división", + "trim_cycle_select": "Recortar datos del ciclo", + "menu_back": "← Volver" + } + }, + "manage_cycles_empty": { + "title": "Gestionar ciclos", + "description": "Todavía no se han registrado ciclos.", + "menu_options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "menu_back": "← Volver" + } + }, + "manage_profiles": { + "title": "Gestionar perfiles", + "description": "Resumen de perfiles:\n{profile_summary}", + "menu_options": { + "create_profile": "Crear nuevo perfil", + "edit_profile": "Editar/cambiar nombre del perfil", + "delete_profile_select": "Eliminar perfil", + "profile_stats": "Estadísticas de perfil", + "cleanup_profile": "Limpiar historial - Gráfico y eliminación", + "assign_profile_phases_select": "Asignar rangos de fase", + "menu_back": "← Volver" + } + }, + "manage_profiles_empty": { + "title": "Gestionar perfiles", + "description": "Todavía no se han creado perfiles.", + "menu_options": { + "create_profile": "Crear nuevo perfil", + "menu_back": "← Volver" + } + }, + "manage_phase_catalog": { + "title": "Gestionar catálogo de fases", + "description": "Catálogo de fases (predeterminados para este dispositivo + fases personalizadas):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Crear nueva fase", + "phase_catalog_edit_select": "Editar fase", + "phase_catalog_delete": "Eliminar fase", + "menu_back": "← Volver" + } + }, + "phase_catalog_create": { + "title": "Crear fase personalizada", + "description": "Crea un nombre y descripción de fase personalizados, y elige a qué tipo de dispositivo se aplica.", + "data": { + "target_device_type": "Tipo de dispositivo de destino", + "phase_name": "Nombre de la fase", + "phase_description": "Descripción de la fase" + } + }, + "phase_catalog_edit_select": { + "title": "Editar fase personalizada", + "description": "Selecciona una fase personalizada para editar.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "phase_catalog_edit": { + "title": "Editar fase personalizada", + "description": "Editando fase: {phase_name}", + "data": { + "phase_name": "Nombre de la fase", + "phase_description": "Descripción de la fase" + } + }, + "phase_catalog_delete": { + "title": "Eliminar fase personalizada", + "description": "Selecciona una fase personalizada para eliminar. Se eliminarán todos los rangos asignados que usen esta fase.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "assign_profile_phases": { + "title": "Asignar rangos de fase", + "description": "Vista previa de fase para el perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Rangos actuales:**\n{current_ranges}\n\nUsa las acciones de abajo para añadir, editar, eliminar o guardar rangos.", + "menu_options": { + "assign_profile_phases_add": "Añadir rango de fase", + "assign_profile_phases_edit_select": "Editar rango de fase", + "assign_profile_phases_delete": "Eliminar rango de fase", + "phase_ranges_clear": "Borrar todos los rangos", + "assign_profile_phases_auto_detect": "Detectar fases automáticamente", + "phase_ranges_save": "Guardar y volver", + "menu_back": "← Volver" + } + }, + "assign_profile_phases_add": { + "title": "Añadir rango de fase", + "description": "Añade un rango de fase al borrador actual.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto de fin" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editar rango de fase", + "description": "Selecciona un rango para editar.", + "data": { + "range_index": "Rango de fase" + } + }, + "assign_profile_phases_edit": { + "title": "Editar rango de fase", + "description": "Actualiza el rango de fase seleccionado.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto de fin" + } + }, + "assign_profile_phases_delete": { + "title": "Eliminar rango de fase", + "description": "Selecciona un rango para eliminar del borrador.", + "data": { + "range_index": "Rango de fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectadas automáticamente", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) detectadas automáticamente.** Elige una acción a continuación.", + "data": { + "action": "Acción" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nombrar fases detectadas", + "description": "Perfil: **{profile_name}**\n\n**{detected_count} fase(s) detectada(s):**\n{ranges_summary}\n\nAsigna un nombre a cada fase." + }, + "assign_profile_phases_select": { + "title": "Asignar rangos de fase", + "description": "Selecciona un perfil para configurar los rangos de fases.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estadísticas de perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Crear nuevo perfil", + "description": "Crea un nuevo perfil manualmente o a partir de un ciclo anterior.\n\nEjemplos de nombres de perfil: \"Delicados\", \"Resistente\", \"Lavado rápido\"", + "data": { + "profile_name": "Nombre del perfil", + "reference_cycle": "Ciclo de referencia (opcional)", + "manual_duration": "Duración manual (minutos)" + } + }, + "edit_profile": { + "title": "Editar perfil", + "description": "Selecciona un perfil para cambiar el nombre", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Editar detalles del perfil", + "description": "Perfil actual: {current_name}", + "data": { + "new_name": "Nombre del perfil", + "manual_duration": "Duración manual (minutos)" + } + }, + "delete_profile_select": { + "title": "Eliminar perfil", + "description": "Selecciona un perfil para eliminar", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirmar eliminación de perfil", + "description": "⚠️ Esto eliminará permanentemente el perfil: {profile_name}", + "data": { + "unlabel_cycles": "Quitar etiqueta de los ciclos que usan este perfil" + } + }, + "auto_label_cycles": { + "title": "Etiquetar automáticamente ciclos antiguos", + "description": "Se encontraron {total_count} ciclos en total. Perfiles: {profiles}", + "data": { + "confidence_threshold": "Confianza mínima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleccionar ciclo para etiquetar", + "description": "✓ = completado, ⚠ = reanudado, ✗ = interrumpido", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Eliminar ciclo", + "description": "⚠️ Esto eliminará permanentemente el ciclo seleccionado", + "data": { + "cycle_id": "Ciclo a eliminar" + } + }, + "label_cycle": { + "title": "Etiquetar ciclo", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Nuevo nombre de perfil (si se está creando)" + } + }, + "post_process": { + "title": "Procesar historial (fusionar/dividir)", + "description": "Limpie el historial de ciclos fusionando ejecuciones fragmentadas y dividiendo ciclos fusionados incorrectamente dentro de una ventana de tiempo. Ingrese el número de horas para mirar hacia atrás (use 999999 para todas).", + "data": { + "time_range": "Ventana de retrospección (horas)", + "gap_seconds": "Brecha para fusión/división (segundos)" + }, + "data_description": { + "time_range": "Número de horas para buscar ciclos fragmentados que se fusionarán. Utilice 999999 para procesar todos los datos históricos.", + "gap_seconds": "Umbral para decidir dividir o fusionar. Las brechas MÁS CORTAS que esto se fusionan. Las brechas MÁS LARGAS que esto se dividen. Reduce esto para forzar una división en un ciclo fusionado incorrectamente." + } + }, + "cleanup_profile": { + "title": "Limpiar historial - Seleccionar perfil", + "description": "Selecciona un perfil para visualizar. Esto generará un gráfico con todos los ciclos anteriores de este perfil para ayudar a identificar valores atípicos.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Limpiar historial - Gráfico y eliminación", + "description": "![Gráfico]({graph_url})\n\n**Visualizando {profile_name}**\n\nEl gráfico muestra ciclos individuales como líneas de colores. Identifica los valores atípicos (líneas alejadas del grupo) y busca su color en la lista de abajo para eliminarlos.\n\n**Selecciona ciclos para eliminar PERMANENTEMENTE:**", + "data": { + "cycles_to_delete": "Ciclos a eliminar (verifica los colores coincidentes)" + } + }, + "interactive_editor": { + "title": "Editor interactivo de fusión/división", + "description": "Selecciona una acción manual para realizar en tu historial de ciclos.", + "menu_options": { + "editor_split": "Dividir un ciclo (encontrar brechas)", + "editor_merge": "Fusionar ciclos (unir fragmentos)", + "editor_delete": "Eliminar ciclo(s)", + "menu_back": "← Volver" + } + }, + "editor_select": { + "title": "Seleccionar ciclos", + "description": "Selecciona los ciclos a procesar.\n\n{info_text}", + "data": { + "selected_cycles": "Ciclos" + } + }, + "editor_configure": { + "title": "Configurar y aplicar", + "description": "{preview_md}", + "data": { + "confirm_action": "Acción", + "merged_profile": "Perfil resultante", + "new_profile_name": "Nuevo nombre de perfil", + "confirm_commit": "Sí, confirmo esta acción", + "segment_0_profile": "Perfil del segmento 1", + "segment_1_profile": "Perfil del segmento 2", + "segment_2_profile": "Perfil del segmento 3", + "segment_3_profile": "Perfil del segmento 4", + "segment_4_profile": "Perfil del segmento 5", + "segment_5_profile": "Perfil del segmento 6", + "segment_6_profile": "Perfil del segmento 7", + "segment_7_profile": "Perfil del segmento 8", + "segment_8_profile": "Perfil del segmento 9", + "segment_9_profile": "Perfil del segmento 10" + } + }, + "editor_split_params": { + "title": "Configuración de división", + "description": "Ajusta la sensibilidad para dividir este ciclo. Cualquier pausa inactiva mayor que esta causará una división.", + "data": { + "split_mode": "Método de división" + } + }, + "editor_split_auto_params": { + "title": "Configuración de división automática", + "description": "Ajusta la sensibilidad para dividir este ciclo. Cualquier intervalo de inactividad más largo que este provocará una división.", + "data": { + "min_gap_seconds": "Umbral de intervalo de división (segundos)" + } + }, + "editor_split_manual_params": { + "title": "Marcas de tiempo de división manual", + "description": "{preview_md}Ventana del ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** el {cycle_date}.\n\nIngresa una o más marcas de tiempo de división (`HH:MM` o `HH:MM:SS`), una por línea. El ciclo se cortará en cada marca de tiempo — N marcas de tiempo producen N+1 segmentos.", + "data": { + "split_timestamps": "Marcas de tiempo de división" + } + }, + "reprocess_history": { + "title": "Mantenimiento: reprocesar y optimizar datos", + "description": "Realiza una limpieza profunda de los datos históricos:\n1. Elimina las lecturas de potencia cero (silencio).\n2. Corrige el tiempo/duración del ciclo.\n3. Recalcula todos los modelos estadísticos (envolventes).\n\n**Ejecuta esto para solucionar problemas de datos o aplicar nuevos algoritmos.**" + }, + "wipe_history": { + "title": "Borrar historial (solo para pruebas)", + "description": "⚠️ Esto eliminará TODOS los ciclos y perfiles almacenados para este dispositivo. ¡Esta acción no se puede deshacer!" + }, + "export_import": { + "title": "Exportar/Importar JSON", + "description": "Copia/pega ciclos, perfiles Y ajustes de este dispositivo. Exporta abajo o pega JSON para importar.", + "data": { + "mode": "Acción", + "json_payload": "Configuración completa JSON" + }, + "data_description": { + "mode": "Elige Exportar para copiar los datos y ajustes actuales, o Importar para pegar datos exportados desde otro dispositivo WashData.", + "json_payload": "Para exportar: copia este JSON (incluye ciclos, perfiles y todos los ajustes). Para importar: pega el JSON exportado desde WashData." + } + }, + "record_cycle": { + "title": "Registrar ciclo", + "description": "Registra manualmente un ciclo para crear un perfil limpio sin interferencias.\n\nEstado: **{status}**\nDuración: {duration} s\nMuestras: {samples}", + "menu_options": { + "record_refresh": "Actualizar estado", + "record_stop": "Detener grabación (guardar y procesar)", + "record_start": "Iniciar nueva grabación", + "record_process": "Procesar última grabación (recortar y guardar)", + "record_discard": "Descartar última grabación", + "menu_back": "← Volver" + } + }, + "record_process": { + "title": "Procesar grabación", + "description": "![Gráfico]({graph_url})\n\n**El gráfico muestra la grabación sin procesar.** Azul = Conservar, Rojo = Recorte propuesto.\n*El gráfico es estático y no se actualiza con los cambios del formulario.*\n\nLa grabación se detuvo. Los recortes están alineados con la frecuencia de muestreo detectada (~{sampling_rate} s).\n\nDuración sin procesar: {duration} s\nMuestras: {samples}", + "data": { + "head_trim": "Recorte de inicio (segundos)", + "tail_trim": "Recorte de fin (segundos)", + "save_mode": "Destino de guardado", + "profile_name": "Nombre del perfil" + } + }, + "learning_feedbacks": { + "title": "Retroalimentación de aprendizaje", + "description": "Comentarios pendientes: {count}\n\n{pending_table}\n\nSelecciona una solicitud de revisión de ciclo para procesar.", + "menu_options": { + "learning_feedbacks_pick": "Revisar un comentario pendiente", + "learning_feedbacks_dismiss_all": "Descartar todos los comentarios pendientes", + "menu_back": "← Volver" + } + }, + "learning_feedbacks_pick": { + "title": "Seleccionar retroalimentación para revisar", + "description": "Seleccione una solicitud de revisión de ciclo para abrir.", + "data": { + "selected_feedback": "Seleccionar ciclo para revisar" + } + }, + "learning_feedbacks_empty": { + "title": "Retroalimentación de aprendizaje", + "description": "No se encontraron solicitudes de retroalimentación pendientes.", + "menu_options": { + "menu_back": "← Volver" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Descartar todas las solicitudes de retroalimentación pendientes", + "description": "Está a punto de descartar **{count}** solicitud(es) de retroalimentación pendiente(s). Los ciclos descartados permanecen en su historial, pero no aportarán nueva señal de aprendizaje. Esto no se puede deshacer.\n\n✅ Marque la casilla a continuación y haga clic en **Enviar** para descartar todo.\n❌ Déjela sin marcar y haga clic en **Enviar** para cancelar y regresar.", + "data": { + "confirm_dismiss_all": "Confirmar: descartar todas las solicitudes de comentarios pendientes" + } + }, + "resolve_feedback": { + "title": "Resolver retroalimentación", + "description": "{comparison_img}{candidates_table}\n**Perfil detectado**: {detected_profile} ({confidence_pct}%)\n**Duración estimada**: {est_duration_min} min\n**Duración real**: {act_duration_min} min\n\n__Elige una acción a continuación:__", + "data": { + "action": "¿Qué te gustaría hacer?", + "corrected_profile": "Programa correcto (si se está corrigiendo)", + "corrected_duration": "Duración correcta en segundos (si se está corrigiendo)" + } + }, + "trim_cycle_select": { + "title": "Recortar ciclo - Seleccionar ciclo", + "description": "Selecciona un ciclo para recortar. ✓ = completado, ⚠ = reanudado, ✗ = interrumpido", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Recortar ciclo", + "description": "Ventana actual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acción" + } + }, + "trim_cycle_start": { + "title": "Establecer inicio del recorte", + "description": "Ventana de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInicio actual: **{current_wallclock}** (+{current_offset_min} min desde el inicio del ciclo)\n\nElija una nueva hora de inicio usando el selector de reloj a continuación.", + "data": { + "trim_start_time": "Nueva hora de inicio", + "trim_start_min": "Nuevo inicio (minutos desde el inicio del ciclo)" + } + }, + "trim_cycle_end": { + "title": "Establecer fin del recorte", + "description": "Ventana de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFin actual: **{current_wallclock}** (+{current_offset_min} min desde el inicio del ciclo)\n\nElija una nueva hora de finalización utilizando el selector de reloj a continuación.", + "data": { + "trim_end_time": "Nuevo tiempo de finalización", + "trim_end_min": "Nuevo fin (minutos desde el inicio del ciclo)" + } + } + }, + "error": { + "import_failed": "Error al importar. Pega un JSON de exportación de WashData válido.", + "select_exactly_one": "Selecciona exactamente un ciclo para esta acción.", + "select_at_least_one": "Selecciona al menos un ciclo para esta acción.", + "select_at_least_two": "Selecciona al menos dos ciclos para esta acción.", + "profile_exists": "Ya existe un perfil con este nombre", + "rename_failed": "No se pudo cambiar el nombre del perfil. Es posible que el perfil no exista o que el nuevo nombre ya esté en uso.", + "assignment_failed": "No se pudo asignar el perfil al ciclo", + "duplicate_phase": "Ya existe una fase con este nombre.", + "phase_not_found": "No se encontró la fase seleccionada.", + "invalid_phase_name": "El nombre de la fase no puede estar vacío.", + "phase_name_too_long": "El nombre de la fase es demasiado largo.", + "invalid_phase_range": "El rango de fase no es válido. El fin debe ser mayor que el inicio.", + "invalid_phase_timestamp": "La marca de tiempo no es válida. Usa el formato AAAA-MM-DD HH:MM o HH:MM.", + "overlapping_phase_ranges": "Los rangos de fase se solapan. Ajusta los rangos y vuelve a intentarlo.", + "incomplete_phase_row": "Cada fila de fase debe incluir una fase más los valores completos de inicio/fin para el modo seleccionado.", + "timestamp_mode_cycle_required": "Selecciona un ciclo de origen cuando uses el modo de marca de tiempo.", + "no_phase_ranges": "Todavía no hay rangos de fase disponibles para esa acción.", + "feedback_notification_title": "WashData: Verificar ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programa**: {program} ({confidence}% de confianza)\n**Hora**: {time}\n\nWashData necesita tu ayuda para verificar este ciclo detectado.\n\nVe a **Configuración > Dispositivos y servicios > WashData > Configurar > Retroalimentación de aprendizaje** para confirmar o corregir este resultado.", + "suggestions_ready_notification_title": "WashData: Ajustes sugeridos listos ({device})", + "suggestions_ready_notification_message": "El sensor de **Ajustes sugeridos** ahora reporta **{count}** recomendaciones aplicables.\n\nPara revisarlas y aplicarlas: **Configuración > Dispositivos y servicios > WashData > Configurar > Configuración avanzada > Aplicar valores sugeridos**.\n\nLas sugerencias son opcionales y se muestran para revisar antes de guardar.", + "trim_range_invalid": "El inicio debe ser anterior al fin. Por favor, ajusta los puntos de recorte.", + "trim_failed": "El recorte falló. Es posible que el ciclo ya no exista o que la ventana esté vacía.", + "invalid_split_timestamp": "La marca de tiempo no es válida o está fuera de la ventana del ciclo. Usa HH:MM o HH:MM:SS dentro de la ventana del ciclo.", + "no_split_segments_found": "No se pudieron generar segmentos válidos a partir de estas marcas de tiempo. Asegúrate de que cada marca de tiempo esté dentro de la ventana del ciclo y de que los segmentos duren al menos 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} ciclos fantasma detectados. Cambio de min_power sugerido: {current_min}W -> {new_min}W (no se aplica automáticamente).", + "auto_tune_title": "Ajuste automático de WashData", + "auto_tune_fallback": "{device_type} {device_title} ciclos fantasma detectados. Cambio de min_power sugerido: {current_min}W -> {new_min}W (no se aplica automáticamente)." + }, + "abort": { + "no_cycles_found": "No se encontraron ciclos", + "no_split_segments_found": "No se encontraron segmentos divisibles en el ciclo seleccionado.", + "cycle_not_found": "No se pudo cargar el ciclo seleccionado.", + "no_power_data": "No hay datos de traza de potencia disponibles para el ciclo seleccionado.", + "no_profiles_found": "No se encontraron perfiles. Crea un perfil primero.", + "no_profiles_for_matching": "No hay perfiles disponibles para la coincidencia. Crea perfiles primero.", + "no_unlabeled_cycles": "Todos los ciclos ya están etiquetados", + "no_suggestions": "Todavía no hay valores sugeridos disponibles. Ejecuta algunos ciclos y vuelve a intentarlo.", + "no_predictions": "Sin predicciones", + "no_custom_phases": "No se encontraron fases personalizadas.", + "reprocess_success": "{count} ciclos reprocesados correctamente.", + "debug_data_cleared": "Datos de depuración eliminados correctamente de {count} ciclos." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Inicio de ciclo", + "cycle_finish": "Fin de ciclo", + "cycle_live": "Progreso en vivo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sin etiquetar)", + "create_new_profile": "Crear nuevo perfil", + "remove_label": "Quitar etiqueta", + "no_reference_cycle": "(Sin ciclo de referencia)", + "all_device_types": "Todos los tipos de dispositivo", + "current_suffix": "(actual)", + "editor_select_info": "Selecciona 1 ciclo para dividir, o 2 o más ciclos para fusionar.", + "editor_select_info_split": "Selecciona 1 ciclo para dividir.", + "editor_select_info_merge": "Selecciona 2 o más ciclos para fusionar.", + "editor_select_info_delete": "Selecciona 1 o más ciclos para eliminar.", + "no_cycles_recorded": "Todavía no se han registrado ciclos.", + "profile_summary_line": "- **{name}**: {count} ciclos, promedio {avg} min", + "no_profiles_created": "Todavía no se han creado perfiles.", + "phase_preview": "Vista previa de fase", + "phase_preview_no_curve": "La curva de perfil promedio aún no está disponible. Ejecuta/etiqueta más ciclos para este perfil.", + "average_power_curve": "Curva de potencia promedio", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciclos, ~{duration} min de promedio)", + "profile_option_short_fmt": "{name} ({count} ciclos, ~{duration} min)", + "deleted_cycles_from_profile": "Se eliminaron {count} ciclos de {profile}.", + "not_enough_data_graph": "No hay suficientes datos para generar el gráfico.", + "no_profiles_found": "No se encontraron perfiles.", + "cycle_info_fmt": "Ciclo: {start}, {duration} min, Actual: {label}", + "top_candidates_header": "### Mejores candidatos", + "tbl_profile": "Perfil", + "tbl_confidence": "Confianza", + "tbl_correlation": "Correlación", + "tbl_duration_match": "Coincidencia de duración", + "detected_profile": "Perfil detectado", + "estimated_duration": "Duración estimada", + "actual_duration": "Duración real", + "choose_action": "__Elige una acción a continuación:__", + "tbl_count": "Recuento", + "tbl_avg": "Prom.", + "tbl_min": "Mín.", + "tbl_max": "Máx.", + "tbl_energy_avg": "Energía (prom.)", + "tbl_energy_total": "Energía (total)", + "tbl_consistency": "Consistencia", + "tbl_last_run": "Última ejecución", + "graph_legend_title": "Leyenda del gráfico", + "graph_legend_body": "La banda azul representa el rango mínimo y máximo de consumo de potencia observado. La línea muestra la curva de potencia promedio.", + "recording_preview": "Vista previa de grabación", + "trim_start": "Inicio de recorte", + "trim_end": "Fin de recorte", + "split_preview_title": "Vista previa de división", + "split_preview_found_fmt": "Se encontraron {count} segmentos.", + "split_preview_confirm_fmt": "Haz clic en Confirmar para dividir este ciclo en {count} ciclos separados.", + "merge_preview_title": "Vista previa de fusión", + "merge_preview_joining_fmt": "Uniendo {count} ciclos. Las brechas se rellenarán con lecturas de 0 W.", + "editor_delete_preview_title": "Vista previa de eliminación", + "editor_delete_preview_intro": "Los ciclos seleccionados se eliminarán permanentemente:", + "editor_delete_preview_confirm": "Haz clic en Confirmar para eliminar permanentemente estos registros de ciclos.", + "no_power_preview": "*No hay datos de potencia disponibles para vista previa.*", + "profile_comparison": "Comparación de perfiles", + "actual_cycle_label": "Este ciclo (real)", + "storage_usage_header": "Uso de almacenamiento", + "storage_file_size": "Tamaño del archivo", + "storage_cycles": "Ciclos", + "storage_profiles": "Perfiles", + "storage_debug_traces": "Trazas de depuración", + "overview_suffix": "Resumen", + "phase_preview_for_profile": "Vista previa de fase para perfil", + "current_ranges_header": "Rangos actuales", + "assign_phases_help": "Usa las acciones de abajo para añadir, editar, eliminar o guardar rangos.", + "profile_summary_header": "Resumen de perfiles", + "recent_cycles_header": "Ciclos recientes", + "trim_cycle_preview_title": "Vista previa de recorte de ciclo", + "trim_cycle_preview_no_data": "No hay datos de potencia disponibles para este ciclo.", + "trim_cycle_preview_kept_suffix": "conservado", + "table_program": "Programa", + "table_when": "Cuándo", + "table_length": "Duración", + "table_match": "Coincidencia", + "table_profile": "Perfil", + "table_cycles": "Ciclos", + "table_avg_length": "Duración prom.", + "table_last_run": "Última ejecución", + "table_avg_energy": "Energía prom.", + "table_detected_program": "Programa detectado", + "table_confidence": "Confianza", + "table_reported": "Informado", + "phase_builtin_suffix": "(Incorporado)", + "phase_none_available": "No hay fases disponibles.", + "settings_deprecation_warning": "⚠️ **Tipo de dispositivo obsoleto:** {device_type} está programado para eliminarse en una versión futura. El proceso de coincidencia de WashData no produce resultados confiables para esta clase de electrodomésticos. Su integración sigue funcionando durante el período de desuso; Para silenciar esta advertencia, cambie el **Tipo de dispositivo** a continuación a uno de los tipos admitidos (lavadora, secadora, combinación de lavadora y secadora, lavavajillas, freidora, máquina para hacer pan o bomba), o a **Otro (avanzado)** si su electrodoméstico no coincide con ninguno de los tipos admitidos. **Otro (Avanzado)** incluye valores predeterminados intencionalmente genéricos que no están ajustados para ningún dispositivo específico, por lo que deberá configurar los umbrales, los tiempos de espera y los parámetros coincidentes usted mismo; todas sus configuraciones existentes se conservan cuando cambia.", + "phase_other_device_types": "Otros tipos de dispositivos:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir un ciclo (encontrar brechas)", + "merge": "Fusionar ciclos (unir fragmentos)", + "delete": "Eliminar ciclo(s)" + } + }, + "split_mode": { + "options": { + "auto": "Detectar automáticamente huecos de inactividad", + "manual": "Marcas de tiempo manuales" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Mantenimiento: reprocesar y optimizar datos", + "clear_debug_data": "Borrar datos de depuración (liberar espacio)", + "wipe_history": "Borrar TODOS los datos de este dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con ajustes (copiar/pegar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportar todos los datos", + "import": "Importar/fusionar datos" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "select_cycle_to_label": "Etiquetar ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusión/división", + "trim_cycle": "Recortar datos del ciclo" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Crear nuevo perfil", + "edit_profile": "Editar/cambiar nombre del perfil", + "delete_profile": "Eliminar perfil", + "profile_stats": "Estadísticas de perfil", + "cleanup_profile": "Limpiar historial - gráfico y eliminación", + "assign_phases": "Asignar rangos de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Crear nueva fase", + "edit_custom_phase": "Editar fase", + "delete_custom_phase": "Eliminar fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Añadir rango de fase", + "edit_range": "Editar rango de fase", + "delete_range": "Eliminar rango de fase", + "clear_ranges": "Borrar todos los rangos", + "auto_detect_ranges": "Detectar fases automáticamente", + "save_ranges": "Guardar y volver" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Actualizar estado", + "stop_recording": "Detener grabación (guardar y procesar)", + "start_recording": "Iniciar nueva grabación", + "process_recording": "Procesar última grabación (recortar y guardar)", + "discard_recording": "Descartar última grabación" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Crear nuevo perfil", + "existing_profile": "Añadir al perfil existente", + "discard": "Descartar grabación" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - Detección correcta", + "correct": "Corregir - Elegir programa/duración", + "ignore": "Ignorar - Falso positivo/ciclo ruidoso", + "delete": "Eliminar - Quitar del historial" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nombrar y aplicar fases", + "cancel": "Volver sin cambios" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Establecer punto de inicio", + "set_end": "Establecer punto de fin", + "reset": "Restablecer a duración completa", + "apply": "Aplicar recorte", + "cancel": "Cancelar" + } + }, + "device_type": { + "options": { + "washing_machine": "Lavadora", + "dryer": "Secadora", + "washer_dryer": "Combinación de lavadora y secadora", + "dishwasher": "Lavavajillas", + "coffee_machine": "Máquina de café (obsoleta)", + "ev": "Vehículo eléctrico (obsoleto)", + "air_fryer": "Freidora de aire", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "fabricante de pan", + "pump": "Bomba / Bomba de sumidero", + "oven": "Horno (obsoleto)", + "other": "Otro (avanzado)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiquetar ciclo", + "description": "Asigna un perfil existente a un ciclo anterior, o quita la etiqueta.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a etiquetar." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo a etiquetar." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "El nombre de un perfil existente (crea perfiles en el menú Gestionar perfiles). Deja vacío para quitar la etiqueta." + } + } + }, + "create_profile": { + "name": "Crear perfil", + "description": "Crea un nuevo perfil (independiente o basado en un ciclo de referencia).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "Nombre para el nuevo perfil (por ejemplo, \"Resistente\", \"Delicados\")." + }, + "reference_cycle_id": { + "name": "ID del ciclo de referencia", + "description": "ID de ciclo opcional en el que basar este perfil." + } + } + }, + "delete_profile": { + "name": "Eliminar perfil", + "description": "Elimina un perfil y, opcionalmente, quita las etiquetas de los ciclos que lo usan.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "El perfil a eliminar." + }, + "unlabel_cycles": { + "name": "Quitar etiqueta de ciclos", + "description": "Quitar la etiqueta de perfil de los ciclos que usan este perfil." + } + } + }, + "auto_label_cycles": { + "name": "Etiquetar automáticamente ciclos antiguos", + "description": "Etiqueta retroactivamente ciclos sin etiquetar usando la coincidencia de perfiles.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "confidence_threshold": { + "name": "Umbral de confianza", + "description": "Confianza mínima de coincidencia (0,50-0,95) para aplicar etiquetas." + } + } + }, + "export_config": { + "name": "Exportar configuración", + "description": "Exporta los perfiles, ciclos y ajustes de este dispositivo a un archivo JSON (por dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a exportar." + }, + "path": { + "name": "Ruta", + "description": "Ruta de archivo absoluta opcional para escribir (por defecto: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importar configuración", + "description": "Importa perfiles, ciclos y ajustes para este dispositivo desde un archivo JSON de exportación (los ajustes pueden sobrescribirse).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData al que importar." + }, + "path": { + "name": "Ruta", + "description": "Ruta absoluta al archivo JSON de exportación a importar." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar retroalimentación del ciclo", + "description": "Confirma o corrige un programa detectado automáticamente tras un ciclo completado. Proporciona `entry_id` (avanzado) o `device_id` (recomendado).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData (recomendado en lugar de entry_id)." + }, + "entry_id": { + "name": "ID de entrada", + "description": "El ID de entrada de configuración del dispositivo (alternativa a device_id)." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo que aparece en la notificación de retroalimentación / registros." + }, + "user_confirmed": { + "name": "Confirmar programa detectado", + "description": "Establece verdadero si el programa detectado es correcto." + }, + "corrected_profile": { + "name": "Perfil corregido", + "description": "Si no se confirma, proporciona el nombre correcto del perfil/programa." + }, + "corrected_duration": { + "name": "Duración corregida (segundos)", + "description": "Duración corregida opcional en segundos." + }, + "notes": { + "name": "Notas", + "description": "Notas opcionales sobre este ciclo." + } + } + }, + "record_start": { + "name": "Iniciar grabación de ciclo", + "description": "Empieza a grabar manualmente un ciclo limpio (omite toda la lógica de coincidencia).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a grabar." + } + } + }, + "record_stop": { + "name": "Detener grabación de ciclo", + "description": "Detiene la grabación manual.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData para detener la grabación." + } + } + }, + "trim_cycle": { + "name": "Recortar ciclo", + "description": "Recorta los datos de potencia de un ciclo anterior a una ventana de tiempo específica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo a recortar." + }, + "trim_start_s": { + "name": "Inicio del recorte (segundos)", + "description": "Conservar datos desde este desplazamiento en segundos. Predeterminado: 0." + }, + "trim_end_s": { + "name": "Fin del recorte (segundos)", + "description": "Conservar datos hasta este desplazamiento en segundos. Predeterminado = duración completa." + } + } + }, + "pause_cycle": { + "name": "Ciclo de pausa", + "description": "Pausa el ciclo activo de un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData se pausará." + } + } + }, + "resume_cycle": { + "name": "Reanudar ciclo", + "description": "Reanudar un ciclo en pausa para un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData para reanudar." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "En funcionamiento" + }, + "match_ambiguity": { + "name": "Ambigüedad de coincidencia" + } + }, + "sensor": { + "washer_state": { + "name": "Estado", + "state": { + "off": "Apagado", + "idle": "En espera", + "starting": "Iniciando", + "running": "En funcionamiento", + "paused": "En pausa", + "user_paused": "En pausa por el usuario", + "ending": "Finalizando", + "finished": "Finalizado", + "anti_wrinkle": "Antiarrugas", + "delay_wait": "Esperando para comenzar", + "interrupted": "Interrumpido", + "force_stopped": "Parada forzada", + "rinse": "Aclarado", + "unknown": "Desconocido", + "clean": "Limpio" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Tiempo restante" + }, + "total_duration": { + "name": "Duración total" + }, + "cycle_progress": { + "name": "Progreso" + }, + "current_power": { + "name": "Potencia actual" + }, + "elapsed_time": { + "name": "Tiempo transcurrido" + }, + "debug_info": { + "name": "Información de depuración" + }, + "match_confidence": { + "name": "Confianza de coincidencia" + }, + "top_candidates": { + "name": "Mejores candidatos", + "state": { + "none": "Ninguno" + } + }, + "current_phase": { + "name": "Fase actual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Recuento de ciclos del perfil {profile_name}", + "unit_of_measurement": "ciclos" + }, + "suggestions": { + "name": "Ajustes sugeridos disponibles" + }, + "pump_runs_today": { + "name": "Funcionamiento de la bomba (últimas 24 h)" + }, + "cycle_count": { + "name": "Conteo de ciclos" + } + }, + "select": { + "program_select": { + "name": "Programa del ciclo", + "state": { + "auto_detect": "Detección automática" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forzar fin de ciclo" + }, + "pause_cycle": { + "name": "Ciclo de pausa" + }, + "resume_cycle": { + "name": "Reanudar ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Se requiere un ID de dispositivo válido." + }, + "cycle_id_required": { + "message": "Se requiere un cycle_id válido." + }, + "profile_name_required": { + "message": "Se requiere un profile_name válido." + }, + "device_not_found": { + "message": "Dispositivo no encontrado." + }, + "no_config_entry": { + "message": "No se encontró ninguna entrada de configuración para el dispositivo." + }, + "integration_not_loaded": { + "message": "La integración no está cargada para este dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclo no encontrado o sin datos de potencia." + }, + "trim_failed_empty_window": { + "message": "Error de recorte: ciclo no encontrado, sin datos de potencia o la ventana resultante está vacía." + }, + "trim_invalid_range": { + "message": "trim_end_s debe ser mayor que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/es.json b/custom_components/ha_washdata/translations/es.json new file mode 100644 index 0000000..60ec609 --- /dev/null +++ b/custom_components/ha_washdata/translations/es.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuración de WashData", + "description": "Configura tu lavadora u otro electrodoméstico.\n\nSe requiere un sensor de potencia.\n\n**A continuación, se te preguntará si deseas crear tu primer perfil.**", + "data": { + "name": "Nombre del dispositivo", + "device_type": "Tipo de dispositivo", + "power_sensor": "Sensor de potencia", + "min_power": "Umbral mínimo de potencia (W)" + }, + "data_description": { + "name": "Un nombre descriptivo para este dispositivo (p. ej., «Lavadora», «Lavavajillas»).", + "device_type": "¿Qué tipo de electrodoméstico es este? Ayuda a personalizar la detección y el etiquetado.", + "power_sensor": "La entidad de sensor que informa el consumo de potencia en tiempo real (en vatios) desde tu enchufe inteligente.", + "min_power": "Las lecturas de potencia por encima de este umbral (en vatios) indican que el electrodoméstico está en funcionamiento. Empieza con 2 W para la mayoría de los dispositivos." + } + }, + "first_profile": { + "title": "Crear primer perfil", + "description": "Un **Ciclo** es una ejecución completa del electrodoméstico (p. ej., una carga de ropa). Un **Perfil** agrupa estos ciclos por tipo (p. ej., «Algodón», «Lavado rápido»). Crear un perfil ahora ayuda a la integración a estimar la duración de inmediato.\n\nPuedes seleccionar este perfil manualmente en los controles del dispositivo si no se detecta automáticamente.\n\n**Deja el «Nombre del perfil» vacío para omitir este paso.**", + "data": { + "profile_name": "Nombre del perfil (opcional)", + "manual_duration": "Duración estimada (minutos)" + } + } + }, + "error": { + "cannot_connect": "No se pudo conectar", + "invalid_auth": "Autenticación no válida", + "unknown": "Error inesperado" + }, + "abort": { + "already_configured": "El dispositivo ya está configurado" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revisar retroalimentación de aprendizaje", + "settings": "Ajustes", + "notifications": "Notificaciones", + "manage_cycles": "Gestionar ciclos", + "manage_profiles": "Gestionar perfiles", + "manage_phase_catalog": "Gestionar catálogo de fases", + "record_cycle": "Registrar ciclo (manual)", + "diagnostics": "Diagnóstico y mantenimiento" + } + }, + "apply_suggestions": { + "title": "Copiar valores sugeridos", + "description": "Esto copiará los valores sugeridos actuales en tu configuración guardada. Esta es una acción única y no habilita sobreescrituras automáticas.\n\nValores sugeridos para copiar:\n{suggested}", + "data": { + "confirm": "Confirmar copia" + } + }, + "apply_suggestions_confirm": { + "title": "Revisar cambios sugeridos", + "description": "WashData encontró **{pending_count}** valores sugeridos para aplicar.\n\nCambios:\n{changes}\n\n✅ Marque la casilla a continuación y haga clic en **Enviar** para aplicar y guardar estos cambios inmediatamente.\n❌ Déjalo sin marcar y haz clic en **Enviar** para cancelar y regresar.", + "data": { + "confirm_apply_suggestions": "Aplicar y guardar estos cambios." + } + }, + "settings": { + "title": "Ajustes", + "description": "{deprecation_warning}Ajusta los umbrales de detección, el aprendizaje y el comportamiento avanzado. Los valores predeterminados funcionan bien para la mayoría de las configuraciones.\n\nAjustes sugeridos disponibles actualmente: {suggestions_count}.\nSi es mayor que 0, abre Configuración avanzada y usa Aplicar valores sugeridos para revisar las recomendaciones.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos", + "device_type": "Tipo de dispositivo", + "power_sensor": "Entidad del sensor de potencia", + "min_power": "Potencia mínima (W)", + "off_delay": "Retraso de fin de ciclo (s)", + "notify_actions": "Acciones de notificación", + "notify_people": "Retrasar entrega hasta que estas personas estén en casa", + "notify_only_when_home": "Retrasar notificaciones hasta que la persona seleccionada esté en casa", + "notify_fire_events": "Disparar eventos de automatización", + "notify_start_services": "Inicio del ciclo: objetivos de notificación", + "notify_finish_services": "Fin del ciclo: objetivos de notificación", + "notify_live_services": "Progreso en vivo: objetivos de notificación", + "notify_before_end_minutes": "Notificación previa a la finalización (minutos antes del final)", + "notify_title": "Título de la notificación", + "notify_icon": "Icono de notificación", + "notify_start_message": "Formato del mensaje de inicio", + "notify_finish_message": "Formato del mensaje de finalización", + "notify_pre_complete_message": "Formato del mensaje previo a la finalización", + "show_advanced": "Editar configuración avanzada (siguiente paso)" + }, + "data_description": { + "apply_suggestions": "Marca esta casilla para copiar los valores sugeridos por el algoritmo de aprendizaje en el formulario. Revisa antes de guardar.\n\nSugerido (del aprendizaje):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selecciona el tipo de electrodoméstico (Lavadora, Secadora, Lavavajillas, Cafetera, Vehículo eléctrico). Esta etiqueta se almacena con cada ciclo para una mejor organización.", + "power_sensor": "La entidad de sensor que informa la potencia en tiempo real (en vatios). Puedes cambiarla en cualquier momento sin perder datos históricos, lo cual es útil si reemplazas un enchufe inteligente.", + "min_power": "Las lecturas de potencia por encima de este umbral (en vatios) indican que el electrodoméstico está en funcionamiento. Empieza con 2 W para la mayoría de los dispositivos.", + "off_delay": "Segundos que la potencia suavizada debe mantenerse por debajo del umbral de parada para marcar la finalización. Predeterminado: 120 s.", + "notify_actions": "Secuencia de acciones opcional de Home Assistant para notificaciones (scripts, notify, TTS, etc.). Si se configura, las acciones se ejecutan antes del servicio de notificación de reserva.", + "notify_people": "Entidades de personas usadas para control de presencia. Cuando está habilitado, la entrega de notificaciones se retrasa hasta que al menos una persona seleccionada esté en casa.", + "notify_only_when_home": "Si está habilitado, retrasa las notificaciones hasta que al menos una persona seleccionada esté en casa.", + "notify_fire_events": "Si está habilitado, dispara eventos de Home Assistant para el inicio y fin de ciclo (ha_washdata_cycle_started y ha_washdata_cycle_ended) para automatizaciones.", + "notify_start_services": "Uno o más servicios de notificación para alertar cuando comienza un ciclo. Déjelo vacío para omitir las notificaciones de inicio.", + "notify_finish_services": "Uno o más servicios de notificación para alertar cuando un ciclo finaliza o está a punto de completarse. Déjelo vacío para omitir las notificaciones de finalización.", + "notify_live_services": "Uno o más servicios de notificación para recibir actualizaciones de progreso en vivo mientras se ejecuta el ciclo (solo aplicación móvil complementaria). Déjelo vacío para omitir las actualizaciones en vivo.", + "notify_before_end_minutes": "Minutos antes del fin estimado del ciclo para activar una notificación. Pon 0 para deshabilitar. Predeterminado: 0.", + "notify_title": "Título personalizado para notificaciones. Admite el marcador {device}.", + "notify_icon": "Icono para notificaciones (p. ej., mdi:washing-machine). Deja vacío para enviar sin icono.", + "notify_start_message": "Mensaje enviado cuando comienza un ciclo. Admite el marcador {device}.", + "notify_finish_message": "Mensaje enviado cuando finaliza un ciclo. Admite los marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texto de las actualizaciones recurrentes del progreso en vivo mientras se ejecuta el ciclo. Admite marcadores de posición {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificaciones", + "description": "Configura los canales de notificación, las plantillas y las actualizaciones opcionales de progreso en vivo para móviles.", + "data": { + "notify_actions": "Acciones de notificación", + "notify_people": "Retrasar entrega hasta que estas personas estén en casa", + "notify_only_when_home": "Retrasar notificaciones hasta que la persona seleccionada esté en casa", + "notify_fire_events": "Disparar eventos de automatización", + "notify_start_services": "Inicio del ciclo: objetivos de notificación", + "notify_finish_services": "Fin del ciclo: objetivos de notificación", + "notify_live_services": "Progreso en vivo: objetivos de notificación", + "notify_before_end_minutes": "Notificación previa a la finalización (minutos antes del final)", + "notify_live_interval_seconds": "Intervalo de actualización en vivo (segundos)", + "notify_live_overrun_percent": "Margen de sobrepasar el tiempo estimado en actualizaciones en vivo (%)", + "notify_live_chronometer": "Temporizador de cuenta atrás del cronómetro", + "notify_title": "Título de la notificación", + "notify_icon": "Icono de notificación", + "notify_start_message": "Formato del mensaje de inicio", + "notify_finish_message": "Formato del mensaje de finalización", + "notify_pre_complete_message": "Formato del mensaje previo a la finalización", + "energy_price_entity": "Precio de la energía: entidad (opcional)", + "energy_price_static": "Precio de la energía: valor estático (opcional)", + "go_back": "Volver sin guardar", + "notify_reminder_message": "Formato de mensaje de recordatorio", + "notify_timeout_seconds": "Auto-desestimación Después (segundos)", + "notify_channel": "Canal de notificación (Estado/live/reminder)", + "notify_finish_channel": "Canal de notificación terminado" + }, + "data_description": { + "go_back": "Marca esto y haz clic en Enviar para descartar los cambios anteriores y volver al menú principal.", + "notify_actions": "Secuencia de acciones opcional de Home Assistant para notificaciones (scripts, notify, TTS, etc.). Si se configura, las acciones se ejecutan antes del servicio de notificación de reserva.", + "notify_people": "Entidades de personas usadas para control de presencia. Cuando está habilitado, la entrega de notificaciones se retrasa hasta que al menos una persona seleccionada esté en casa.", + "notify_only_when_home": "Si está habilitado, retrasa las notificaciones hasta que al menos una persona seleccionada esté en casa.", + "notify_fire_events": "Si está habilitado, dispara eventos de Home Assistant para el inicio y fin de ciclo (ha_washdata_cycle_started y ha_washdata_cycle_ended) para automatizaciones.", + "notify_start_services": "Uno o más servicios de notificación para avisar cuando comienza un ciclo (por ejemplo, notify.mobile_app_my_phone). Déjelo vacío para omitir las notificaciones de inicio.", + "notify_finish_services": "Uno o más servicios de notificación para alertar cuando un ciclo finaliza o está a punto de completarse. Déjelo vacío para omitir las notificaciones de finalización.", + "notify_live_services": "Uno o más servicios de notificación para recibir actualizaciones de progreso en vivo mientras se ejecuta el ciclo (solo aplicación móvil complementaria). Déjelo vacío para omitir las actualizaciones en vivo.", + "notify_before_end_minutes": "Minutos antes del fin estimado del ciclo para activar una notificación. Pon 0 para deshabilitar. Predeterminado: 0.", + "notify_live_interval_seconds": "Con qué frecuencia se envían actualizaciones de progreso durante el funcionamiento. Los valores más bajos son más responsivos pero consumen más notificaciones. Se aplica un mínimo de 30 segundos; los valores por debajo de 30 s se redondean automáticamente a 30 s.", + "notify_live_overrun_percent": "Margen de seguridad sobre el recuento estimado de actualizaciones para ciclos largos o que se extienden (p. ej., el 20% permite 1,2 veces las actualizaciones estimadas).", + "notify_live_chronometer": "Cuando está habilitada, cada actualización en vivo incluye una cuenta regresiva del cronómetro hasta el tiempo estimado de finalización. El temporizador de notificaciones avanza automáticamente en el dispositivo entre actualizaciones (solo aplicación complementaria de Android).", + "notify_title": "Título personalizado para notificaciones. Admite el marcador {device}.", + "notify_icon": "Icono para notificaciones (p. ej., mdi:washing-machine). Deja vacío para enviar sin icono.", + "notify_start_message": "Mensaje enviado cuando comienza un ciclo. Admite el marcador {device}.", + "notify_finish_message": "Mensaje enviado cuando finaliza un ciclo. Admite los marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Seleccione una entidad numérica que contenga el precio actual de la electricidad (por ejemplo, sensor.electricity_price, input_number.energy_rate). Cuando está configurado, habilita el marcador de posición {cost}. Tiene prioridad sobre el valor estático si ambos están configurados.", + "energy_price_static": "Precio fijo de la electricidad por kWh. Cuando está configurado, habilita el marcador de posición {cost}. Se ignora si una entidad está configurada arriba. Agregue su símbolo de moneda en la plantilla de mensaje, p. {cost}€.", + "notify_reminder_message": "Texto del recordatorio de una sola vez envió el número configurado de minutos antes del final estimado. Distinto de las actualizaciones en vivo recurrentes arriba. Apoyo {device}, {minutes}, {program} propietarios de puestos.", + "notify_timeout_seconds": "Descargue automáticamente las notificaciones después de estos muchos segundos (en adelante como la aplicación de acompañamiento 'timeout'). Se establece a 0 para mantenerlos hasta que sea despedido. Predeterminado: 0.", + "notify_channel": "Android canal de notificación de aplicaciones acompañantes para el estado, progreso en vivo y notificaciones de recordatorio. El sonido y la importancia de un canal se configuran en la aplicación compañera la primera vez que se utiliza el nombre del canal - WashData sólo establece el nombre del canal. Deja vacío para el defecto de la aplicación.", + "notify_finish_channel": "Separar Android canal para la alerta terminada (también utilizado por el recordatorio y el aviso persistente de espera de la colada) para que pueda tener su propio sonido. Deja vacío para reutilizar el canal de arriba.", + "notify_pre_complete_message": "Texto de las actualizaciones recurrentes del progreso en vivo mientras se ejecuta el ciclo. Admite marcadores de posición {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Configuración avanzada", + "description": "Ajusta los umbrales de detección, el aprendizaje y el comportamiento avanzado. Los valores predeterminados funcionan bien para la mayoría de las configuraciones.", + "sections": { + "suggestions_section": { + "name": "Ajustes sugeridos", + "description": "{suggestions_count} sugerencias aprendidas disponibles. Marca Aplicar valores sugeridos para revisar los cambios propuestos antes de guardar.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos" + }, + "data_description": { + "apply_suggestions": "Marca esta casilla para abrir un paso de revisión de los valores sugeridos por el algoritmo de aprendizaje. Nada se guarda automáticamente.\n\nSugerido (del aprendizaje):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detección y umbrales de potencia", + "description": "Cuándo se considera que el aparato está ENCENDIDO o APAGADO, umbrales de energía y temporización de las transiciones de estado.", + "data": { + "start_duration_threshold": "Duración de antirrebote de inicio (s)", + "start_energy_threshold": "Umbral de energía de inicio (Wh)", + "completion_min_seconds": "Tiempo mínimo de ejecución para completar (segundos)", + "start_threshold_w": "Umbral de inicio (W)", + "stop_threshold_w": "Umbral de parada (W)", + "end_energy_threshold": "Umbral de energía de fin (Wh)", + "running_dead_zone": "Zona muerta en ejecución (segundos)", + "end_repeat_count": "Recuento de repeticiones de fin", + "min_off_gap": "Brecha mínima entre ciclos (s)", + "sampling_interval": "Intervalo de muestreo (s)" + }, + "data_description": { + "start_duration_threshold": "Ignora los picos breves de potencia más cortos que esta duración (segundos). Filtra picos de arranque (p. ej., 60 W durante 2 s) antes del inicio real del ciclo. Predeterminado: 5 s.", + "start_energy_threshold": "El ciclo debe acumular al menos esta energía (Wh) durante la fase de INICIO para confirmarse. Predeterminado: 0,005 Wh.", + "completion_min_seconds": "Los ciclos más cortos se marcarán como «interrumpidos» incluso si finalizan de forma natural. Predeterminado: 600 s.", + "start_threshold_w": "La potencia debe superar ESTE umbral para INICIAR un ciclo. Configúralo por encima de tu potencia en espera. Predeterminado: min_power + 1 W.", + "stop_threshold_w": "La potencia debe caer POR DEBAJO de este umbral para FINALIZAR un ciclo. Configúralo justo por encima de cero para evitar que el ruido provoque finales falsos. Predeterminado: 0,6 × min_power.", + "end_energy_threshold": "El ciclo finaliza solo si la energía en la última ventana de retraso de apagado está por debajo de este valor (Wh). Evita finales falsos si la potencia fluctúa cerca de cero. Predeterminado: 0,05 Wh.", + "running_dead_zone": "Tras el inicio del ciclo, ignora las caídas de potencia durante estos segundos para evitar la detección falsa del final durante la fase de arranque. Pon 0 para deshabilitar. Predeterminado: 0 s.", + "end_repeat_count": "Número de veces que la condición de fin (potencia por debajo del umbral durante off_delay) debe cumplirse consecutivamente antes de finalizar el ciclo. Valores más altos evitan finales falsos durante las pausas. Predeterminado: 1.", + "min_off_gap": "Si un ciclo comienza dentro de estos segundos tras finalizar el anterior, se FUSIONARÁN en un único ciclo.", + "sampling_interval": "Intervalo mínimo de muestreo en segundos. Se ignorarán las actualizaciones más rápidas que esta velocidad. Valor predeterminado: 30 s (2 s para lavadoras, lavadoras-secadoras y lavavajillas)." + } + }, + "matching_section": { + "name": "Coincidencia de perfiles y aprendizaje", + "description": "Qué tan agresivamente WashData hace coincidir los ciclos en curso con los perfiles aprendidos y cuándo pedir comentarios.", + "data": { + "profile_match_min_duration_ratio": "Proporción mínima de duración para coincidencia de perfil (0,1-1,0)", + "profile_match_interval": "Intervalo de coincidencia de perfil (segundos)", + "profile_match_threshold": "Umbral de coincidencia de perfil", + "profile_unmatch_threshold": "Umbral de no coincidencia de perfil", + "auto_label_confidence": "Confianza de etiquetado automático (0-1)", + "learning_confidence": "Confianza mínima para solicitar retroalimentación (0-1)", + "suppress_feedback_notifications": "Suprimir notificaciones de comentarios", + "duration_tolerance": "Tolerancia de duración (0-0,5)", + "smoothing_window": "Ventana de suavizado (muestras)", + "profile_duration_tolerance": "Tolerancia de duración para coincidencia de perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Proporción mínima de duración para la coincidencia de perfil. El ciclo en ejecución debe ser al menos esta fracción de la duración del perfil para coincidir. Más bajo = coincidencia más temprana. Predeterminado: 0,50 (50%).", + "profile_match_interval": "Con qué frecuencia (en segundos) intentar la coincidencia de perfil durante un ciclo. Los valores más bajos dan una detección más rápida pero usan más CPU. Predeterminado: 300 s (5 minutos).", + "profile_match_threshold": "Puntuación mínima de similitud DTW (0,0–1,0) requerida para considerar un perfil como coincidencia. Más alto = coincidencia más estricta, menos falsos positivos. Predeterminado: 0,4.", + "profile_unmatch_threshold": "Puntuación de similitud DTW (0,0–1,0) por debajo de la cual se rechaza un perfil previamente coincidente. Debe ser inferior al umbral de coincidencia para evitar parpadeos. Predeterminado: 0,35.", + "auto_label_confidence": "Etiqueta automáticamente los ciclos con esta confianza o superior al finalizar (0,0–1,0).", + "learning_confidence": "Confianza mínima para solicitar verificación del usuario mediante un evento (0,0–1,0).", + "suppress_feedback_notifications": "Cuando está habilitado, WashData seguirá rastreando y solicitando comentarios internamente, pero no mostrará notificaciones persistentes pidiéndole que confirme los ciclos. Útil cuando los ciclos siempre se detectan correctamente y las indicaciones le distraen.", + "duration_tolerance": "Tolerancia para las estimaciones de tiempo restante durante una ejecución (0,0–0,5 corresponde a 0–50%).", + "smoothing_window": "Número de lecturas de potencia recientes usadas para el suavizado de media móvil.", + "profile_duration_tolerance": "Tolerancia usada por la coincidencia de perfil para la variación de duración total (0,0–0,5). Comportamiento predeterminado: ±25%." + } + }, + "timing_section": { + "name": "Tiempo, mantenimiento y depuración", + "description": "Watchdog, reinicio del progreso, mantenimiento automático y exposición de entidades de depuración.", + "data": { + "watchdog_interval": "Intervalo de vigilancia (segundos)", + "no_update_active_timeout": "Tiempo de espera sin actualización (s)", + "progress_reset_delay": "Retraso de restablecimiento del progreso (segundos)", + "auto_maintenance": "Habilitar mantenimiento automático", + "expose_debug_entities": "Exponer entidades de depuración", + "save_debug_traces": "Guardar trazas de depuración" + }, + "data_description": { + "watchdog_interval": "Segundos entre comprobaciones de vigilancia durante la ejecución. Predeterminado: 5 s. ADVERTENCIA: Asegúrate de que sea MAYOR que el intervalo de actualización de tu sensor para evitar paradas falsas (p. ej., si el sensor se actualiza cada 60 s, usa 65 s o más).", + "no_update_active_timeout": "Para sensores que publican solo al cambiar: fuerza el fin de un ciclo ACTIVO solo si no llegan actualizaciones durante estos segundos. La finalización de bajo consumo sigue usando el retraso de apagado.", + "progress_reset_delay": "Tras completar un ciclo (100%), espera estos segundos de inactividad antes de restablecer el progreso al 0%. Predeterminado: 300 s.", + "auto_maintenance": "Habilitar mantenimiento automático (reparar muestras, fusionar fragmentos).", + "expose_debug_entities": "Mostrar sensores avanzados (Confianza, Fase, Ambigüedad) para depuración.", + "save_debug_traces": "Almacenar datos detallados de clasificación y trazas de potencia en el historial (aumenta el uso de almacenamiento)." + } + }, + "anti_wrinkle_section": { + "name": "Escudo antiarrugas (secadoras)", + "description": "Ignora los giros del tambor tras el ciclo para evitar ciclos fantasma. Solo secadora / lavasecadora.", + "data": { + "anti_wrinkle_enabled": "Escudo antiarrugas (solo secadora/lavadora-secadora)", + "anti_wrinkle_max_power": "Potencia máxima antiarrugas (W) (solo secadora/lavadora-secadora)", + "anti_wrinkle_max_duration": "Duración máxima antiarrugas (s) (solo secadora/lavadora-secadora)", + "anti_wrinkle_exit_power": "Potencia de salida antiarrugas (W) (solo secadora/lavadora-secadora)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignora las breves rotaciones del tambor a baja potencia tras el fin de un ciclo para evitar ciclos fantasma (solo secadora/lavadora-secadora).", + "anti_wrinkle_max_power": "Las ráfagas a esta potencia o por debajo se tratan como rotaciones antiarrugas en lugar de nuevos ciclos. Solo se aplica cuando el Escudo antiarrugas está habilitado.", + "anti_wrinkle_max_duration": "Duración máxima de ráfaga para tratar como rotación antiarrugas. Solo se aplica cuando el Escudo antiarrugas está habilitado.", + "anti_wrinkle_exit_power": "Si la potencia cae por debajo de este nivel, sale del modo antiarrugas inmediatamente (apagado real). Solo se aplica cuando el Escudo antiarrugas está habilitado." + } + }, + "delay_start_section": { + "name": "Detección de inicio diferido", + "description": "Trata el modo de espera sostenido de baja potencia como 'Esperando para iniciar' hasta que el ciclo comience realmente.", + "data": { + "delay_start_detect_enabled": "Habilitar la detección de inicio retrasado", + "delay_confirm_seconds": "Inicio diferido: tiempo de confirmación en espera (s)", + "delay_timeout_hours": "Inicio retrasado: tiempo máximo de espera (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Cuando está habilitado, se detecta un breve pico de consumo de baja potencia seguido de energía de reserva sostenida como un inicio retrasado. El sensor de estado muestra \"Esperando para comenzar\" durante el retraso. No se realiza un seguimiento del tiempo ni del progreso del programa hasta que el ciclo realmente comienza. Requiere que start_threshold_w se establezca por encima de la potencia del pico de drenaje.", + "delay_confirm_seconds": "La potencia debe permanecer entre Umbral de parada (W) y Umbral de inicio (W) durante este número de segundos antes de que WashData pase a 'Esperando para iniciar'. Filtra picos breves al navegar por menús. Predeterminado: 60 s.", + "delay_timeout_hours": "Tiempo de espera de seguridad: si la máquina todavía está en 'Esperando para iniciar' después de tantas horas, WashData se restablece en Apagado. Predeterminado: 8 h." + } + }, + "external_triggers_section": { + "name": "Disparadores externos, puerta y pausa", + "description": "Sensor externo opcional de fin de ciclo, sensor de puerta para detección de pausa/limpio e interruptor de corte de energía al pausar.", + "data": { + "external_end_trigger_enabled": "Habilitar activador externo de fin de ciclo", + "external_end_trigger": "Sensor externo de fin de ciclo", + "external_end_trigger_inverted": "Invertir lógica de activación (activar en OFF)", + "door_sensor_entity": "Sensor de puerta", + "pause_cuts_power": "Cortar la energía al hacer una pausa", + "switch_entity": "Cambiar entidad (para pausar el corte de energía)", + "notify_unload_delay_minutes": "Retraso de notificación de espera de lavandería (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Habilitar la monitorización de un sensor binario externo para activar la finalización del ciclo.", + "external_end_trigger": "Selecciona una entidad de sensor binario. Cuando este sensor se activa, el ciclo actual finalizará con el estado «completado».", + "external_end_trigger_inverted": "De forma predeterminada, el activador se dispara cuando el sensor se enciende (ON). Marca esta casilla para disparar cuando el sensor se apague (OFF).", + "door_sensor_entity": "Sensor binario opcional para la puerta de la máquina (on = abierto, off = cerrado). Cuando la puerta se abre durante un ciclo activo, WashData confirmará que el ciclo está pausado intencionalmente. Una vez finalizado el ciclo, si la puerta aún está cerrada, el estado cambia a \"Limpiar\" hasta que se abre la puerta. Nota: cerrar la puerta NO reanuda automáticamente un ciclo en pausa; la reanudación debe activarse manualmente mediante el botón Reanudar ciclo o el servicio.", + "pause_cuts_power": "Al hacer una pausa mediante el botón o servicio Pause Cycle, también apague la entidad de conmutación configurada. Solo tiene efecto si se configura una entidad de conmutación para este dispositivo.", + "switch_entity": "La entidad de cambio para alternar al pausar o reanudar. Requerido cuando está habilitado 'Cortar energía al pausar'.", + "notify_unload_delay_minutes": "Envíe una notificación a través del canal de notificación de finalización después de que finalice el ciclo y la puerta no se haya abierto durante tantos minutos (requiere sensor de puerta). Establezca en 0 para desactivar. Predeterminado: 60 min." + } + }, + "pump_section": { + "name": "Monitor de bomba", + "description": "Solo para el tipo de dispositivo bomba. Dispara un evento si un ciclo de bomba supera esta duración.", + "data": { + "pump_stuck_duration": "Umbrales de alerta de bomba atascada (sólo bomba)" + }, + "data_description": { + "pump_stuck_duration": "Si un ciclo de bomba sigue ejecutándose después de tantos segundos, se activa una vez un evento ha_washdata_pump_stuck. Utilícelo para detectar un motor atascado (el funcionamiento típico de la bomba de sumidero es inferior a 60 s). Predeterminado: 1800 s (30 min). Solo tipo de dispositivo de bomba." + } + }, + "device_link_section": { + "name": "Enlace de dispositivo", + "description": "Opcionalmente vincula este dispositivo WashData a un dispositivo existente Home Assistant (por ejemplo, el plug inteligente o el propio aparato). El dispositivo WashData se muestra entonces como \"Connected via\" ese dispositivo. Deja vacío para mantener a WashData como un dispositivo independiente.", + "data": { + "linked_device": "Dispositivo vinculado" + }, + "data_description": { + "linked_device": "Seleccione el dispositivo para conectar WashData a. Esto agrega una referencia 'Connected via' en el registro del dispositivo; WashData mantiene su propia tarjeta de dispositivo y entidades. Limpiar la selección para eliminar el enlace." + } + } + } + }, + "diagnostics": { + "title": "Diagnóstico y mantenimiento", + "description": "Ejecuta acciones de mantenimiento como fusionar ciclos fragmentados o migrar datos almacenados.\n\n**Uso de almacenamiento**\n\n- Tamaño del archivo: {file_size_kb} KB\n- Ciclos: {cycle_count}\n- Perfiles: {profile_count}\n- Trazas de depuración: {debug_count}", + "menu_options": { + "reprocess_history": "Mantenimiento: reprocesar y optimizar datos", + "clear_debug_data": "Borrar datos de depuración (liberar espacio)", + "wipe_history": "Borrar TODOS los datos de este dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con ajustes (copiar/pegar)", + "menu_back": "← Volver" + } + }, + "clear_debug_data": { + "title": "Borrar datos de depuración", + "description": "¿Estás seguro de que deseas eliminar todas las trazas de depuración almacenadas? Esto liberará espacio pero eliminará la información detallada de clasificación de ciclos anteriores." + }, + "manage_cycles": { + "title": "Gestionar ciclos", + "description": "Ciclos recientes:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "select_cycle_to_label": "Etiquetar ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusión/división", + "trim_cycle_select": "Recortar datos del ciclo", + "menu_back": "← Volver" + } + }, + "manage_cycles_empty": { + "title": "Gestionar ciclos", + "description": "Todavía no se han registrado ciclos.", + "menu_options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "menu_back": "← Volver" + } + }, + "manage_profiles": { + "title": "Gestionar perfiles", + "description": "Resumen de perfiles:\n{profile_summary}", + "menu_options": { + "create_profile": "Crear nuevo perfil", + "edit_profile": "Editar/cambiar nombre del perfil", + "delete_profile_select": "Eliminar perfil", + "profile_stats": "Estadísticas de perfil", + "cleanup_profile": "Limpiar historial - Gráfico y eliminación", + "assign_profile_phases_select": "Asignar rangos de fase", + "menu_back": "← Volver" + } + }, + "manage_profiles_empty": { + "title": "Gestionar perfiles", + "description": "Todavía no se han creado perfiles.", + "menu_options": { + "create_profile": "Crear nuevo perfil", + "menu_back": "← Volver" + } + }, + "manage_phase_catalog": { + "title": "Gestionar catálogo de fases", + "description": "Catálogo de fases (predeterminados para este dispositivo + fases personalizadas):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Crear nueva fase", + "phase_catalog_edit_select": "Editar fase", + "phase_catalog_delete": "Eliminar fase", + "menu_back": "← Volver" + } + }, + "phase_catalog_create": { + "title": "Crear fase personalizada", + "description": "Crea un nombre y descripción de fase personalizados, y elige a qué tipo de dispositivo se aplica.", + "data": { + "target_device_type": "Tipo de dispositivo de destino", + "phase_name": "Nombre de la fase", + "phase_description": "Descripción de la fase" + } + }, + "phase_catalog_edit_select": { + "title": "Editar fase personalizada", + "description": "Selecciona una fase personalizada para editar.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "phase_catalog_edit": { + "title": "Editar fase personalizada", + "description": "Editando fase: {phase_name}", + "data": { + "phase_name": "Nombre de la fase", + "phase_description": "Descripción de la fase" + } + }, + "phase_catalog_delete": { + "title": "Eliminar fase personalizada", + "description": "Selecciona una fase personalizada para eliminar. Se eliminarán todos los rangos asignados que usen esta fase.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "assign_profile_phases": { + "title": "Asignar rangos de fase", + "description": "Vista previa de fase para el perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Rangos actuales:**\n{current_ranges}\n\nUsa las acciones de abajo para añadir, editar, eliminar o guardar rangos.", + "menu_options": { + "assign_profile_phases_add": "Añadir rango de fase", + "assign_profile_phases_edit_select": "Editar rango de fase", + "assign_profile_phases_delete": "Eliminar rango de fase", + "phase_ranges_clear": "Borrar todos los rangos", + "assign_profile_phases_auto_detect": "Detectar fases automáticamente", + "phase_ranges_save": "Guardar y volver", + "menu_back": "← Volver" + } + }, + "assign_profile_phases_add": { + "title": "Añadir rango de fase", + "description": "Añade un rango de fase al borrador actual.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto de fin" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editar rango de fase", + "description": "Selecciona un rango para editar.", + "data": { + "range_index": "Rango de fase" + } + }, + "assign_profile_phases_edit": { + "title": "Editar rango de fase", + "description": "Actualiza el rango de fase seleccionado.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto de fin" + } + }, + "assign_profile_phases_delete": { + "title": "Eliminar rango de fase", + "description": "Selecciona un rango para eliminar del borrador.", + "data": { + "range_index": "Rango de fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectadas automáticamente", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) detectadas automáticamente.** Elige una acción a continuación.", + "data": { + "action": "Acción" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nombrar fases detectadas", + "description": "Perfil: **{profile_name}**\n\n**{detected_count} fase(s) detectada(s):**\n{ranges_summary}\n\nAsigna un nombre a cada fase." + }, + "assign_profile_phases_select": { + "title": "Asignar rangos de fase", + "description": "Selecciona un perfil para configurar los rangos de fases.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estadísticas de perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Crear nuevo perfil", + "description": "Crea un nuevo perfil manualmente o a partir de un ciclo anterior.\n\nEjemplos de nombres de perfil: «Delicados», «Resistente», «Lavado rápido»", + "data": { + "profile_name": "Nombre del perfil", + "reference_cycle": "Ciclo de referencia (opcional)", + "manual_duration": "Duración manual (minutos)" + } + }, + "edit_profile": { + "title": "Editar perfil", + "description": "Selecciona un perfil para cambiar el nombre", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Editar detalles del perfil", + "description": "Perfil actual: {current_name}", + "data": { + "new_name": "Nombre del perfil", + "manual_duration": "Duración manual (minutos)" + } + }, + "delete_profile_select": { + "title": "Eliminar perfil", + "description": "Selecciona un perfil para eliminar", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirmar eliminación de perfil", + "description": "⚠️ Esto eliminará permanentemente el perfil: {profile_name}", + "data": { + "unlabel_cycles": "Quitar etiqueta de los ciclos que usan este perfil" + } + }, + "auto_label_cycles": { + "title": "Etiquetar automáticamente ciclos antiguos", + "description": "Se encontraron {total_count} ciclos en total. Perfiles: {profiles}", + "data": { + "confidence_threshold": "Confianza mínima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleccionar ciclo para etiquetar", + "description": "✓ = completado, ⚠ = reanudado, ✗ = interrumpido", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Eliminar ciclo", + "description": "⚠️ Esto eliminará permanentemente el ciclo seleccionado", + "data": { + "cycle_id": "Ciclo a eliminar" + } + }, + "label_cycle": { + "title": "Etiquetar ciclo", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Nuevo nombre de perfil (si se está creando)" + } + }, + "post_process": { + "title": "Procesar historial (fusionar/dividir)", + "description": "Limpie el historial de ciclos fusionando ejecuciones fragmentadas y dividiendo ciclos fusionados incorrectamente dentro de una ventana de tiempo. Ingrese el número de horas para mirar hacia atrás (use 999999 para todas).", + "data": { + "time_range": "Ventana de retrospección (horas)", + "gap_seconds": "Brecha para fusión/división (segundos)" + }, + "data_description": { + "time_range": "Número de horas para buscar ciclos fragmentados que se fusionarán. Utilice 999999 para procesar todos los datos históricos.", + "gap_seconds": "Umbral para decidir dividir o fusionar. Las brechas MÁS CORTAS que esto se fusionan. Las brechas MÁS LARGAS que esto se dividen. Reduce esto para forzar una división en un ciclo fusionado incorrectamente." + } + }, + "cleanup_profile": { + "title": "Limpiar historial - Seleccionar perfil", + "description": "Selecciona un perfil para visualizar. Esto generará un gráfico con todos los ciclos anteriores de este perfil para ayudar a identificar valores atípicos.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Limpiar historial - Gráfico y eliminación", + "description": "![Gráfico]({graph_url})\n\n**Visualizando {profile_name}**\n\nEl gráfico muestra ciclos individuales como líneas de colores. Identifica los valores atípicos (líneas alejadas del grupo) y busca su color en la lista de abajo para eliminarlos.\n\n**Selecciona ciclos para eliminar PERMANENTEMENTE:**", + "data": { + "cycles_to_delete": "Ciclos a eliminar (verifica los colores coincidentes)" + } + }, + "interactive_editor": { + "title": "Editor interactivo de fusión/división", + "description": "Selecciona una acción manual para realizar en tu historial de ciclos.", + "menu_options": { + "editor_split": "Dividir un ciclo (encontrar brechas)", + "editor_merge": "Fusionar ciclos (unir fragmentos)", + "editor_delete": "Eliminar ciclo(s)", + "menu_back": "← Volver" + } + }, + "editor_select": { + "title": "Seleccionar ciclos", + "description": "Selecciona los ciclos a procesar.\n\n{info_text}", + "data": { + "selected_cycles": "Ciclos" + } + }, + "editor_configure": { + "title": "Configurar y aplicar", + "description": "{preview_md}", + "data": { + "confirm_action": "Acción", + "merged_profile": "Perfil resultante", + "new_profile_name": "Nuevo nombre de perfil", + "confirm_commit": "Sí, confirmo esta acción", + "segment_0_profile": "Perfil del segmento 1", + "segment_1_profile": "Perfil del segmento 2", + "segment_2_profile": "Perfil del segmento 3", + "segment_3_profile": "Perfil del segmento 4", + "segment_4_profile": "Perfil del segmento 5", + "segment_5_profile": "Perfil del segmento 6", + "segment_6_profile": "Perfil del segmento 7", + "segment_7_profile": "Perfil del segmento 8", + "segment_8_profile": "Perfil del segmento 9", + "segment_9_profile": "Perfil del segmento 10" + } + }, + "editor_split_params": { + "title": "Configuración de división", + "description": "Ajusta la sensibilidad para dividir este ciclo. Cualquier pausa inactiva mayor que esta causará una división.", + "data": { + "split_mode": "Método de división" + } + }, + "editor_split_auto_params": { + "title": "Configuración de división automática", + "description": "Ajusta la sensibilidad para dividir este ciclo. Cualquier intervalo de inactividad más largo que este provocará una división.", + "data": { + "min_gap_seconds": "Umbral de intervalo de división (segundos)" + } + }, + "editor_split_manual_params": { + "title": "Marcas de tiempo de división manual", + "description": "{preview_md}Ventana del ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** el {cycle_date}.\n\nIngresa una o más marcas de tiempo de división (`HH:MM` o `HH:MM:SS`), una por línea. El ciclo se cortará en cada marca de tiempo — N marcas de tiempo producen N+1 segmentos.", + "data": { + "split_timestamps": "Marcas de tiempo de división" + } + }, + "reprocess_history": { + "title": "Mantenimiento: reprocesar y optimizar datos", + "description": "Realiza una limpieza profunda de los datos históricos:\n1. Elimina las lecturas de potencia cero (silencio).\n2. Corrige el tiempo/duración del ciclo.\n3. Recalcula todos los modelos estadísticos (envolventes).\n\n**Ejecuta esto para solucionar problemas de datos o aplicar nuevos algoritmos.**" + }, + "wipe_history": { + "title": "Borrar historial (solo para pruebas)", + "description": "⚠️ Esto eliminará TODOS los ciclos y perfiles almacenados para este dispositivo. ¡Esta acción no se puede deshacer!" + }, + "export_import": { + "title": "Exportar/Importar JSON", + "description": "Copia/pega ciclos, perfiles Y ajustes de este dispositivo. Exporta abajo o pega JSON para importar.", + "data": { + "mode": "Acción", + "json_payload": "Configuración completa JSON" + }, + "data_description": { + "mode": "Elige Exportar para copiar los datos y ajustes actuales, o Importar para pegar datos exportados desde otro dispositivo WashData.", + "json_payload": "Para exportar: copia este JSON (incluye ciclos, perfiles y todos los ajustes). Para importar: pega el JSON exportado desde WashData." + } + }, + "record_cycle": { + "title": "Registrar ciclo", + "description": "Registra manualmente un ciclo para crear un perfil limpio sin interferencias.\n\nEstado: **{status}**\nDuración: {duration} s\nMuestras: {samples}", + "menu_options": { + "record_refresh": "Actualizar estado", + "record_stop": "Detener grabación (guardar y procesar)", + "record_start": "Iniciar nueva grabación", + "record_process": "Procesar última grabación (recortar y guardar)", + "record_discard": "Descartar última grabación", + "menu_back": "← Volver" + } + }, + "record_process": { + "title": "Procesar grabación", + "description": "![Gráfico]({graph_url})\n\n**El gráfico muestra la grabación sin procesar.** Azul = Conservar, Rojo = Recorte propuesto.\n*El gráfico es estático y no se actualiza con los cambios del formulario.*\n\nLa grabación se detuvo. Los recortes están alineados con la frecuencia de muestreo detectada (~{sampling_rate} s).\n\nDuración sin procesar: {duration} s\nMuestras: {samples}", + "data": { + "head_trim": "Recorte de inicio (segundos)", + "tail_trim": "Recorte de fin (segundos)", + "save_mode": "Destino de guardado", + "profile_name": "Nombre del perfil" + } + }, + "learning_feedbacks": { + "title": "Retroalimentación de aprendizaje", + "description": "Comentarios pendientes: {count}\n\n{pending_table}\n\nSelecciona una solicitud de revisión de ciclo para procesar.", + "menu_options": { + "learning_feedbacks_pick": "Revisar un comentario pendiente", + "learning_feedbacks_dismiss_all": "Descartar todos los comentarios pendientes", + "menu_back": "← Volver" + } + }, + "learning_feedbacks_pick": { + "title": "Seleccionar retroalimentación para revisar", + "description": "Seleccione una solicitud de revisión de ciclo para abrir.", + "data": { + "selected_feedback": "Seleccionar ciclo para revisar" + } + }, + "learning_feedbacks_empty": { + "title": "Retroalimentación de aprendizaje", + "description": "No se encontraron solicitudes de retroalimentación pendientes.", + "menu_options": { + "menu_back": "← Volver" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Descartar todas las solicitudes de retroalimentación pendientes", + "description": "Está a punto de descartar **{count}** solicitud(es) de retroalimentación pendiente(s). Los ciclos descartados permanecen en su historial, pero no aportarán nueva señal de aprendizaje. Esto no se puede deshacer.\n\n✅ Marque la casilla a continuación y haga clic en **Enviar** para descartar todo.\n❌ Déjela sin marcar y haga clic en **Enviar** para cancelar y regresar.", + "data": { + "confirm_dismiss_all": "Confirmar: descartar todas las solicitudes de comentarios pendientes" + } + }, + "resolve_feedback": { + "title": "Resolver retroalimentación", + "description": "{comparison_img}{candidates_table}\n**Perfil detectado**: {detected_profile} ({confidence_pct}%)\n**Duración estimada**: {est_duration_min} min\n**Duración real**: {act_duration_min} min\n\n__Elige una acción a continuación:__", + "data": { + "action": "¿Qué te gustaría hacer?", + "corrected_profile": "Programa correcto (si se está corrigiendo)", + "corrected_duration": "Duración correcta en segundos (si se está corrigiendo)" + } + }, + "trim_cycle_select": { + "title": "Recortar ciclo - Seleccionar ciclo", + "description": "Selecciona un ciclo para recortar. ✓ = completado, ⚠ = reanudado, ✗ = interrumpido", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Recortar ciclo", + "description": "Ventana actual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acción" + } + }, + "trim_cycle_start": { + "title": "Establecer inicio del recorte", + "description": "Ventana de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInicio actual: **{current_wallclock}** (+{current_offset_min} min desde el inicio del ciclo)\n\nElija una nueva hora de inicio usando el selector de reloj a continuación.", + "data": { + "trim_start_time": "Nueva hora de inicio", + "trim_start_min": "Nuevo inicio (minutos desde el inicio del ciclo)" + } + }, + "trim_cycle_end": { + "title": "Establecer fin del recorte", + "description": "Ventana de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFin actual: **{current_wallclock}** (+{current_offset_min} min desde el inicio del ciclo)\n\nElija una nueva hora de finalización utilizando el selector de reloj a continuación.", + "data": { + "trim_end_time": "Nuevo tiempo de finalización", + "trim_end_min": "Nuevo fin (minutos desde el inicio del ciclo)" + } + } + }, + "error": { + "import_failed": "Error al importar. Pega un JSON de exportación de WashData válido.", + "select_exactly_one": "Selecciona exactamente un ciclo para esta acción.", + "select_at_least_one": "Selecciona al menos un ciclo para esta acción.", + "select_at_least_two": "Selecciona al menos dos ciclos para esta acción.", + "profile_exists": "Ya existe un perfil con este nombre", + "rename_failed": "No se pudo cambiar el nombre del perfil. Es posible que el perfil no exista o que el nuevo nombre ya esté en uso.", + "assignment_failed": "No se pudo asignar el perfil al ciclo", + "duplicate_phase": "Ya existe una fase con este nombre.", + "phase_not_found": "No se encontró la fase seleccionada.", + "invalid_phase_name": "El nombre de la fase no puede estar vacío.", + "phase_name_too_long": "El nombre de la fase es demasiado largo.", + "invalid_phase_range": "El rango de fase no es válido. El fin debe ser mayor que el inicio.", + "invalid_phase_timestamp": "La marca de tiempo no es válida. Usa el formato AAAA-MM-DD HH:MM o HH:MM.", + "overlapping_phase_ranges": "Los rangos de fase se solapan. Ajusta los rangos y vuelve a intentarlo.", + "incomplete_phase_row": "Cada fila de fase debe incluir una fase más los valores completos de inicio/fin para el modo seleccionado.", + "timestamp_mode_cycle_required": "Selecciona un ciclo de origen cuando uses el modo de marca de tiempo.", + "no_phase_ranges": "Todavía no hay rangos de fase disponibles para esa acción.", + "feedback_notification_title": "WashData: Verificar ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programa**: {program} ({confidence}% de confianza)\n**Hora**: {time}\n\nWashData necesita tu ayuda para verificar este ciclo detectado.\n\nVe a **Configuración > Dispositivos y servicios > WashData > Configurar > Retroalimentación de aprendizaje** para confirmar o corregir este resultado.", + "suggestions_ready_notification_title": "WashData: Ajustes sugeridos listos ({device})", + "suggestions_ready_notification_message": "El sensor de **Ajustes sugeridos** ahora reporta **{count}** recomendaciones aplicables.\n\nPara revisarlas y aplicarlas: **Configuración > Dispositivos y servicios > WashData > Configurar > Configuración avanzada > Aplicar valores sugeridos**.\n\nLas sugerencias son opcionales y se muestran para revisar antes de guardar.", + "trim_range_invalid": "El inicio debe ser anterior al fin. Por favor, ajusta los puntos de recorte.", + "trim_failed": "El recorte falló. Es posible que el ciclo ya no exista o que la ventana esté vacía.", + "invalid_split_timestamp": "La marca de tiempo no es válida o está fuera de la ventana del ciclo. Usa HH:MM o HH:MM:SS dentro de la ventana del ciclo.", + "no_split_segments_found": "No se pudieron generar segmentos válidos a partir de estas marcas de tiempo. Asegúrate de que cada marca de tiempo esté dentro de la ventana del ciclo y de que los segmentos duren al menos 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} ciclos fantasma detectados. Cambio de min_power sugerido: {current_min}W -> {new_min}W (no se aplica automáticamente).", + "auto_tune_title": "Ajuste automático de WashData", + "auto_tune_fallback": "{device_type} {device_title} ciclos fantasma detectados. Cambio de min_power sugerido: {current_min}W -> {new_min}W (no se aplica automáticamente)." + }, + "abort": { + "no_cycles_found": "No se encontraron ciclos", + "no_split_segments_found": "No se encontraron segmentos divisibles en el ciclo seleccionado.", + "cycle_not_found": "No se pudo cargar el ciclo seleccionado.", + "no_power_data": "No hay datos de traza de potencia disponibles para el ciclo seleccionado.", + "no_profiles_found": "No se encontraron perfiles. Crea un perfil primero.", + "no_profiles_for_matching": "No hay perfiles disponibles para la coincidencia. Crea perfiles primero.", + "no_unlabeled_cycles": "Todos los ciclos ya están etiquetados", + "no_suggestions": "Todavía no hay valores sugeridos disponibles. Ejecuta algunos ciclos y vuelve a intentarlo.", + "no_predictions": "Sin predicciones", + "no_custom_phases": "No se encontraron fases personalizadas.", + "reprocess_success": "{count} ciclos reprocesados correctamente.", + "debug_data_cleared": "Datos de depuración eliminados correctamente de {count} ciclos." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Inicio de ciclo", + "cycle_finish": "Fin de ciclo", + "cycle_live": "Progreso en vivo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sin etiquetar)", + "create_new_profile": "Crear nuevo perfil", + "remove_label": "Quitar etiqueta", + "no_reference_cycle": "(Sin ciclo de referencia)", + "all_device_types": "Todos los tipos de dispositivo", + "current_suffix": "(actual)", + "editor_select_info": "Selecciona 1 ciclo para dividir, o 2 o más ciclos para fusionar.", + "editor_select_info_split": "Selecciona 1 ciclo para dividir.", + "editor_select_info_merge": "Selecciona 2 o más ciclos para fusionar.", + "editor_select_info_delete": "Selecciona 1 o más ciclos para eliminar.", + "no_cycles_recorded": "Todavía no se han registrado ciclos.", + "profile_summary_line": "- **{name}**: {count} ciclos, promedio {avg} min", + "no_profiles_created": "Todavía no se han creado perfiles.", + "phase_preview": "Vista previa de fase", + "phase_preview_no_curve": "La curva de perfil promedio aún no está disponible. Ejecuta/etiqueta más ciclos para este perfil.", + "average_power_curve": "Curva de potencia promedio", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciclos, ~{duration} min de promedio)", + "profile_option_short_fmt": "{name} ({count} ciclos, ~{duration} min)", + "deleted_cycles_from_profile": "Se eliminaron {count} ciclos de {profile}.", + "not_enough_data_graph": "No hay suficientes datos para generar el gráfico.", + "no_profiles_found": "No se encontraron perfiles.", + "cycle_info_fmt": "Ciclo: {start}, {duration} min, Actual: {label}", + "top_candidates_header": "### Mejores candidatos", + "tbl_profile": "Perfil", + "tbl_confidence": "Confianza", + "tbl_correlation": "Correlación", + "tbl_duration_match": "Coincidencia de duración", + "detected_profile": "Perfil detectado", + "estimated_duration": "Duración estimada", + "actual_duration": "Duración real", + "choose_action": "__Elige una acción a continuación:__", + "tbl_count": "Recuento", + "tbl_avg": "Prom.", + "tbl_min": "Mín.", + "tbl_max": "Máx.", + "tbl_energy_avg": "Energía (prom.)", + "tbl_energy_total": "Energía (total)", + "tbl_consistency": "Consistencia", + "tbl_last_run": "Última ejecución", + "graph_legend_title": "Leyenda del gráfico", + "graph_legend_body": "La banda azul representa el rango mínimo y máximo de consumo de potencia observado. La línea muestra la curva de potencia promedio.", + "recording_preview": "Vista previa de grabación", + "trim_start": "Inicio de recorte", + "trim_end": "Fin de recorte", + "split_preview_title": "Vista previa de división", + "split_preview_found_fmt": "Se encontraron {count} segmentos.", + "split_preview_confirm_fmt": "Haz clic en Confirmar para dividir este ciclo en {count} ciclos separados.", + "merge_preview_title": "Vista previa de fusión", + "merge_preview_joining_fmt": "Uniendo {count} ciclos. Las brechas se rellenarán con lecturas de 0 W.", + "editor_delete_preview_title": "Vista previa de eliminación", + "editor_delete_preview_intro": "Los ciclos seleccionados se eliminarán permanentemente:", + "editor_delete_preview_confirm": "Haz clic en Confirmar para eliminar permanentemente estos registros de ciclos.", + "no_power_preview": "*No hay datos de potencia disponibles para vista previa.*", + "profile_comparison": "Comparación de perfiles", + "actual_cycle_label": "Este ciclo (real)", + "storage_usage_header": "Uso de almacenamiento", + "storage_file_size": "Tamaño del archivo", + "storage_cycles": "Ciclos", + "storage_profiles": "Perfiles", + "storage_debug_traces": "Trazas de depuración", + "overview_suffix": "Resumen", + "phase_preview_for_profile": "Vista previa de fase para perfil", + "current_ranges_header": "Rangos actuales", + "assign_phases_help": "Usa las acciones de abajo para añadir, editar, eliminar o guardar rangos.", + "profile_summary_header": "Resumen de perfiles", + "recent_cycles_header": "Ciclos recientes", + "trim_cycle_preview_title": "Vista previa de recorte de ciclo", + "trim_cycle_preview_no_data": "No hay datos de potencia disponibles para este ciclo.", + "trim_cycle_preview_kept_suffix": "conservado", + "table_program": "Programa", + "table_when": "Cuándo", + "table_length": "Duración", + "table_match": "Coincidencia", + "table_profile": "Perfil", + "table_cycles": "Ciclos", + "table_avg_length": "Duración prom.", + "table_last_run": "Última ejecución", + "table_avg_energy": "Energía prom.", + "table_detected_program": "Programa detectado", + "table_confidence": "Confianza", + "table_reported": "Informado", + "phase_builtin_suffix": "(Incorporado)", + "phase_none_available": "No hay fases disponibles.", + "settings_deprecation_warning": "⚠️ **Tipo de dispositivo obsoleto:** {device_type} está programado para eliminarse en una versión futura. El proceso de coincidencia de WashData no produce resultados confiables para esta clase de electrodomésticos. Su integración sigue funcionando durante el período de desuso; Para silenciar esta advertencia, cambie el **Tipo de dispositivo** a continuación a uno de los tipos admitidos (lavadora, secadora, combinación de lavadora y secadora, lavavajillas, freidora, máquina para hacer pan o bomba), o a **Otro (avanzado)** si su electrodoméstico no coincide con ninguno de los tipos admitidos. **Otro (Avanzado)** incluye valores predeterminados intencionalmente genéricos que no están ajustados para ningún dispositivo específico, por lo que deberá configurar los umbrales, los tiempos de espera y los parámetros coincidentes usted mismo; todas sus configuraciones existentes se conservan cuando cambia.", + "phase_other_device_types": "Otros tipos de dispositivos:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir un ciclo (encontrar brechas)", + "merge": "Fusionar ciclos (unir fragmentos)", + "delete": "Eliminar ciclo(s)" + } + }, + "split_mode": { + "options": { + "auto": "Detectar automáticamente huecos de inactividad", + "manual": "Marcas de tiempo manuales" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Mantenimiento: reprocesar y optimizar datos", + "clear_debug_data": "Borrar datos de depuración (liberar espacio)", + "wipe_history": "Borrar TODOS los datos de este dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con ajustes (copiar/pegar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportar todos los datos", + "import": "Importar/fusionar datos" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etiquetar automáticamente ciclos antiguos", + "select_cycle_to_label": "Etiquetar ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusión/división", + "trim_cycle": "Recortar datos del ciclo" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Crear nuevo perfil", + "edit_profile": "Editar/cambiar nombre del perfil", + "delete_profile": "Eliminar perfil", + "profile_stats": "Estadísticas de perfil", + "cleanup_profile": "Limpiar historial - gráfico y eliminación", + "assign_phases": "Asignar rangos de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Crear nueva fase", + "edit_custom_phase": "Editar fase", + "delete_custom_phase": "Eliminar fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Añadir rango de fase", + "edit_range": "Editar rango de fase", + "delete_range": "Eliminar rango de fase", + "clear_ranges": "Borrar todos los rangos", + "auto_detect_ranges": "Detectar fases automáticamente", + "save_ranges": "Guardar y volver" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Actualizar estado", + "stop_recording": "Detener grabación (guardar y procesar)", + "start_recording": "Iniciar nueva grabación", + "process_recording": "Procesar última grabación (recortar y guardar)", + "discard_recording": "Descartar última grabación" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Crear nuevo perfil", + "existing_profile": "Añadir al perfil existente", + "discard": "Descartar grabación" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - Detección correcta", + "correct": "Corregir - Elegir programa/duración", + "ignore": "Ignorar - Falso positivo/ciclo ruidoso", + "delete": "Eliminar - Quitar del historial" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nombrar y aplicar fases", + "cancel": "Volver sin cambios" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Establecer punto de inicio", + "set_end": "Establecer punto de fin", + "reset": "Restablecer a duración completa", + "apply": "Aplicar recorte", + "cancel": "Cancelar" + } + }, + "device_type": { + "options": { + "washing_machine": "Lavadora", + "dryer": "Secadora", + "washer_dryer": "Combinación de lavadora y secadora", + "dishwasher": "Lavavajillas", + "coffee_machine": "Máquina de café (obsoleta)", + "ev": "Vehículo eléctrico (obsoleto)", + "air_fryer": "Freidora de aire", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "fabricante de pan", + "pump": "Bomba / Bomba de sumidero", + "oven": "Horno (obsoleto)", + "other": "Otro (avanzado)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiquetar ciclo", + "description": "Asigna un perfil existente a un ciclo anterior, o quita la etiqueta.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a etiquetar." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo a etiquetar." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "El nombre de un perfil existente (crea perfiles en el menú Gestionar perfiles). Deja vacío para quitar la etiqueta." + } + } + }, + "create_profile": { + "name": "Crear perfil", + "description": "Crea un nuevo perfil (independiente o basado en un ciclo de referencia).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "Nombre para el nuevo perfil (p. ej., «Resistente», «Delicados»)." + }, + "reference_cycle_id": { + "name": "ID del ciclo de referencia", + "description": "ID de ciclo opcional en el que basar este perfil." + } + } + }, + "delete_profile": { + "name": "Eliminar perfil", + "description": "Elimina un perfil y, opcionalmente, quita las etiquetas de los ciclos que lo usan.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "profile_name": { + "name": "Nombre del perfil", + "description": "El perfil a eliminar." + }, + "unlabel_cycles": { + "name": "Quitar etiqueta de ciclos", + "description": "Quitar la etiqueta de perfil de los ciclos que usan este perfil." + } + } + }, + "auto_label_cycles": { + "name": "Etiquetar automáticamente ciclos antiguos", + "description": "Etiqueta retroactivamente ciclos sin etiquetar usando la coincidencia de perfiles.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "confidence_threshold": { + "name": "Umbral de confianza", + "description": "Confianza mínima de coincidencia (0,50-0,95) para aplicar etiquetas." + } + } + }, + "export_config": { + "name": "Exportar configuración", + "description": "Exporta los perfiles, ciclos y ajustes de este dispositivo a un archivo JSON (por dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a exportar." + }, + "path": { + "name": "Ruta", + "description": "Ruta de archivo absoluta opcional para escribir (por defecto: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importar configuración", + "description": "Importa perfiles, ciclos y ajustes para este dispositivo desde un archivo JSON de exportación (los ajustes pueden sobrescribirse).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData al que importar." + }, + "path": { + "name": "Ruta", + "description": "Ruta absoluta al archivo JSON de exportación a importar." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar retroalimentación del ciclo", + "description": "Confirma o corrige un programa detectado automáticamente tras un ciclo completado. Proporciona `entry_id` (avanzado) o `device_id` (recomendado).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData (recomendado en lugar de entry_id)." + }, + "entry_id": { + "name": "ID de entrada", + "description": "El ID de entrada de configuración del dispositivo (alternativa a device_id)." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo que aparece en la notificación de retroalimentación / registros." + }, + "user_confirmed": { + "name": "Confirmar programa detectado", + "description": "Establece verdadero si el programa detectado es correcto." + }, + "corrected_profile": { + "name": "Perfil corregido", + "description": "Si no se confirma, proporciona el nombre correcto del perfil/programa." + }, + "corrected_duration": { + "name": "Duración corregida (segundos)", + "description": "Duración corregida opcional en segundos." + }, + "notes": { + "name": "Notas", + "description": "Notas opcionales sobre este ciclo." + } + } + }, + "record_start": { + "name": "Iniciar grabación de ciclo", + "description": "Empieza a grabar manualmente un ciclo limpio (omite toda la lógica de coincidencia).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData a grabar." + } + } + }, + "record_stop": { + "name": "Detener grabación de ciclo", + "description": "Detiene la grabación manual.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData para detener la grabación." + } + } + }, + "trim_cycle": { + "name": "Recortar ciclo", + "description": "Recorta los datos de potencia de un ciclo anterior a una ventana de tiempo específica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData." + }, + "cycle_id": { + "name": "ID del ciclo", + "description": "El ID del ciclo a recortar." + }, + "trim_start_s": { + "name": "Inicio del recorte (segundos)", + "description": "Conservar datos desde este desplazamiento en segundos. Predeterminado: 0." + }, + "trim_end_s": { + "name": "Fin del recorte (segundos)", + "description": "Conservar datos hasta este desplazamiento en segundos. Predeterminado = duración completa." + } + } + }, + "pause_cycle": { + "name": "Ciclo de pausa", + "description": "Pausa el ciclo activo de un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData se pausará." + } + } + }, + "resume_cycle": { + "name": "Reanudar ciclo", + "description": "Reanudar un ciclo en pausa para un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "El dispositivo WashData para reanudar." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "En funcionamiento" + }, + "match_ambiguity": { + "name": "Ambigüedad de coincidencia" + } + }, + "sensor": { + "washer_state": { + "name": "Estado", + "state": { + "off": "Apagado", + "idle": "En espera", + "starting": "Iniciando", + "running": "En funcionamiento", + "paused": "En pausa", + "user_paused": "En pausa por el usuario", + "ending": "Finalizando", + "finished": "Finalizado", + "anti_wrinkle": "Antiarrugas", + "delay_wait": "Esperando para comenzar", + "interrupted": "Interrumpido", + "force_stopped": "Parada forzada", + "rinse": "Aclarado", + "unknown": "Desconocido", + "clean": "Limpio" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Tiempo restante" + }, + "total_duration": { + "name": "Duración total" + }, + "cycle_progress": { + "name": "Progreso" + }, + "current_power": { + "name": "Potencia actual" + }, + "elapsed_time": { + "name": "Tiempo transcurrido" + }, + "debug_info": { + "name": "Información de depuración" + }, + "match_confidence": { + "name": "Confianza de coincidencia" + }, + "top_candidates": { + "name": "Mejores candidatos", + "state": { + "none": "Ninguno" + } + }, + "current_phase": { + "name": "Fase actual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Recuento de ciclos del perfil {profile_name}", + "unit_of_measurement": "ciclos" + }, + "suggestions": { + "name": "Ajustes sugeridos disponibles" + }, + "pump_runs_today": { + "name": "Funcionamiento de la bomba (últimas 24 h)" + }, + "cycle_count": { + "name": "Conteo de ciclos" + } + }, + "select": { + "program_select": { + "name": "Programa del ciclo", + "state": { + "auto_detect": "Detección automática" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forzar fin de ciclo" + }, + "pause_cycle": { + "name": "Ciclo de pausa" + }, + "resume_cycle": { + "name": "Reanudar ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Se requiere un ID de dispositivo válido." + }, + "cycle_id_required": { + "message": "Se requiere un cycle_id válido." + }, + "profile_name_required": { + "message": "Se requiere un profile_name válido." + }, + "device_not_found": { + "message": "Dispositivo no encontrado." + }, + "no_config_entry": { + "message": "No se encontró ninguna entrada de configuración para el dispositivo." + }, + "integration_not_loaded": { + "message": "La integración no está cargada para este dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclo no encontrado o sin datos de potencia." + }, + "trim_failed_empty_window": { + "message": "Error de recorte: ciclo no encontrado, sin datos de potencia o la ventana resultante está vacía." + }, + "trim_invalid_range": { + "message": "trim_end_s debe ser mayor que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/et.json b/custom_components/ha_washdata/translations/et.json new file mode 100644 index 0000000..b9d0914 --- /dev/null +++ b/custom_components/ha_washdata/translations/et.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData häälestus", + "description": "Seadistage oma pesumasin või muu seade.\n\nVajalik on võimsusandur.\n\n**Järgmisena küsitakse teilt, kas soovite luua oma esimese profiili.**", + "data": { + "name": "Seadme nimi", + "device_type": "Seadme tüüp", + "power_sensor": "Võimsussensor", + "min_power": "Minimaalne võimsuslävi (W)" + }, + "data_description": { + "name": "Selle seadme sõbralik nimi (nt 'pesumasin', 'nõudepesumasin').", + "device_type": "Mis tüüpi seade see on? Aitab kohandada tuvastamist ja märgistamist.", + "power_sensor": "Anduri olem, mis teatab teie nutika pistiku reaalajas energiatarbimisest (vattides).", + "min_power": "Sellest läviväärtusest kõrgemad võimsusnäidud (vattides) näitavad, et seade töötab. Enamiku seadmete puhul alustage 2W-st." + } + }, + "first_profile": { + "title": "Loo esimene profiil", + "description": "**Tsükkel** on teie seadme täielik töökord (nt täis pesu). **Profiil** rühmitab need tsüklid tüübi järgi (nt 'puuvill', 'kiirpesu'). Profiili loomine aitab koheselt hinnata integratsiooni kestust.\n\nSaate selle profiili seadme juhtelementidest käsitsi valida, kui seda automaatselt ei tuvastata.\n\n**Selle sammu vahelejätmiseks jätke „Profiili nimi” tühjaks.**", + "data": { + "profile_name": "Profiili nimi (valikuline)", + "manual_duration": "Hinnanguline kestus (minutites)" + } + } + }, + "error": { + "cannot_connect": "Ühenduse loomine ebaõnnestus", + "invalid_auth": "Kehtetu autentimine", + "unknown": "Ootamatu viga" + }, + "abort": { + "already_configured": "Seade on juba konfigureeritud" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Vaadake läbi õppe tagasiside", + "settings": "Seaded", + "notifications": "Märguanded", + "manage_cycles": "Tsükleid hallata", + "manage_profiles": "Profiilide haldamine", + "manage_phase_catalog": "Halda faasikataloogi", + "record_cycle": "Tsükli salvestamine (käsitsi)", + "diagnostics": "Diagnostika ja hooldus" + } + }, + "apply_suggestions": { + "title": "Kopeerige soovitatud väärtused", + "description": "See kopeerib praegused soovitatud väärtused teie salvestatud sätetesse. See on ühekordne toiming ega võimalda automaatset ülekirjutamist.\n\nSoovitatud väärtused kopeerimiseks:\n{suggested}", + "data": { + "confirm": "Kopeerimistoimingu kinnitamine" + } + }, + "apply_suggestions_confirm": { + "title": "Vaadake üle soovitatud muudatused", + "description": "WashData leitud **{pending_count}** soovitatud väärtus(t) rakendada.\n\nMuudatused:\n{changes}\n\n✅ Muudatuste koheseks rakendamiseks ja salvestamiseks märkige allolev ruut ja klõpsake nuppu **Esita**.\n❌ Jätke see märkimata ja klõpsake tühistamiseks ja tagasi minemiseks nuppu **Esita**.", + "data": { + "confirm_apply_suggestions": "Rakendage ja salvestage need muudatused" + } + }, + "settings": { + "title": "Seaded", + "description": "{deprecation_warning}Tuvastamise lävede, õppimise ja täiustatud käitumise häälestamine. Vaikimisi töötavad enamiku seadistuste puhul hästi.\n\nPraegu saadaolevate soovituslike seadete arv: {suggestions_count}.\nKui see on suurem kui 0, ava Täpsemad seaded ja kasuta Rakenda soovituslikud väärtused soovituste ülevaatamiseks.", + "data": { + "apply_suggestions": "Rakendage soovitatud väärtused", + "device_type": "Seadme tüüp", + "power_sensor": "Toiteanduri üksus", + "min_power": "Minimaalne võimsus (W)", + "off_delay": "Tsükli lõpu viivitus (id)", + "notify_actions": "Teavitustoimingud", + "notify_people": "Viivitada kohaletoimetamisega, kuni need inimesed koju jõuavad", + "notify_only_when_home": "Viivitage teatised, kuni valitud inimene on kodus", + "notify_fire_events": "Automaatikaürituste käivitamine", + "notify_start_services": "Tsükli algus – teavituse sihtmärgid", + "notify_finish_services": "Tsükli lõpetamine – teavituse sihtmärgid", + "notify_live_services": "Reaalajas edenemine – teavituste sihtmärgid", + "notify_before_end_minutes": "Lõpetamiseelne teatis (minutid enne lõppu)", + "notify_title": "Teatise pealkiri", + "notify_icon": "Teavituse ikoon", + "notify_start_message": "Käivitussõnumi vorming", + "notify_finish_message": "Lõpusõnumi vorming", + "notify_pre_complete_message": "Lõpetamiseelne sõnumivorming", + "show_advanced": "Muuda täpsemaid seadeid (järgmine samm)" + }, + "data_description": { + "apply_suggestions": "Märkige see ruut, et kopeerida vormile õppealgoritmi soovitatud väärtused. Enne salvestamist vaadake üle.\n\nSoovitatav (õppimisest):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Vali seadme tüüp (pesumasin, kuivati, nõudepesumasin, kohvimasin, EV). See silt salvestatakse paremaks korraldamiseks iga tsükliga.", + "power_sensor": "Anduri olem, mis annab aru reaalajas võimsusest (vattides). Saate seda igal ajal muuta ilma ajaloolisi andmeid kaotamata – see on kasulik, kui vahetate nutika pistiku.", + "min_power": "Sellest läviväärtusest kõrgemad võimsusnäidud (vattides) näitavad, et seade töötab. Enamiku seadmete puhul alustage 2W-st.", + "off_delay": "Sekundite jooksul peab tasandatud võimsus jääma allapoole peatumisläve, et tähistada lõpetamist. Vaikimisi: 120 s.", + "notify_actions": "Valikuline koduabilise toimingujada märguannete jaoks (skriptid, teavitus, TTS jne). Kui see on määratud, käitatakse toimingud enne varuteatiste teenust.", + "notify_people": "Kohaloleku väravaks kasutatavad isikud. Kui see on lubatud, lükatakse teavituste edastamine edasi, kuni vähemalt üks valitud inimene on kodus.", + "notify_only_when_home": "Kui see on lubatud, viivitage märguanded, kuni vähemalt üks valitud inimene on kodus.", + "notify_fire_events": "Kui see on lubatud, käivitage koduabilise tsükli alguse ja lõpu sündmused (ha_washdata_cycle_started ja ha_washdata_cycle_ended), et juhtida automatiseerimist.", + "notify_start_services": "Üks või mitu teenust teavitavad tsükli algusest. Alustamise märguannete vahelejätmiseks jätke tühjaks.", + "notify_finish_services": "Üks või mitu teenust teavitavad, kui tsükkel lõppeb või läheneb lõpule. Lõpetamismärguannete vahelejätmiseks jätke tühjaks.", + "notify_live_services": "Üks või mitu teavitavat teenust, et saada tsükli ajal reaalajas edenemise värskendusi (ainult mobiilirakendus). Reaalajas värskenduste vahelejätmiseks jätke tühjaks.", + "notify_before_end_minutes": "Minutid enne tsükli hinnangulist lõppu, et käivitada teatis. Keelamiseks määrake 0. Vaikimisi: 0.", + "notify_title": "Kohandatud pealkiri teatiste jaoks. Toetab kohatäidet {device}.", + "notify_icon": "Märguannete jaoks kasutatav ikoon (nt mdi:pesumasin). Ikooni saatmata jätmiseks jätke tühjaks.", + "notify_start_message": "Sõnum saadetakse, kui tsükkel algab. Toetab kohatäidet {device}.", + "notify_finish_message": "Sõnum saadetakse pärast tsükli lõppemist. Toetab kohahoidjaid {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Korduva reaalajas edenemise tekst värskendatakse tsükli jooksul. Toetab kohahoidjaid {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Märguanded", + "description": "Seadistage teavituskanalid, mallid ja valikulised reaalajas mobiili edenemise värskendused.", + "data": { + "notify_actions": "Teavitustoimingud", + "notify_people": "Viivitada kohaletoimetamisega, kuni need inimesed koju jõuavad", + "notify_only_when_home": "Viivitage teatised, kuni valitud inimene on kodus", + "notify_fire_events": "Automaatikaürituste käivitamine", + "notify_start_services": "Tsükli algus – teavituse sihtmärgid", + "notify_finish_services": "Tsükli lõpetamine – teavituse sihtmärgid", + "notify_live_services": "Reaalajas edenemine – teavituste sihtmärgid", + "notify_before_end_minutes": "Lõpetamiseelne teatis (minutid enne lõppu)", + "notify_live_interval_seconds": "Reaalajas värskendamise intervall (sekundites)", + "notify_live_overrun_percent": "Reaalajas värskenduse ületamise luba (%)", + "notify_live_chronometer": "Kronomeetri Taimer", + "notify_title": "Teatise pealkiri", + "notify_icon": "Teavituse ikoon", + "notify_start_message": "Käivitussõnumi vorming", + "notify_finish_message": "Lõpusõnumi vorming", + "notify_pre_complete_message": "Lõpetamiseelne sõnumivorming", + "energy_price_entity": "Energiahind – üksus (valikuline)", + "energy_price_static": "Energia hind – staatiline väärtus (valikuline)", + "go_back": "Mine tagasi salvestamata", + "notify_reminder_message": "Meeldetuletuse kirja vorming", + "notify_timeout_seconds": "Automaatne loobumine pärast (sekundeid)", + "notify_channel": "Teavitamiskanal (staatus/otseülekanne/meeldetuletus)", + "notify_finish_channel": "Lõpetatud teavituskanal" + }, + "data_description": { + "go_back": "Märkige see ja klõpsake Esita, et ülaltoodud muudatused ära visata ja põhimenüüsse naasta.", + "notify_actions": "Valikuline koduabilise toimingujada märguannete jaoks (skriptid, teavitus, TTS jne). Kui see on määratud, käitatakse toimingud enne varuteatiste teenust.", + "notify_people": "Kohaloleku väravaks kasutatavad isikud. Kui see on lubatud, lükatakse teavituste edastamine edasi, kuni vähemalt üks valitud inimene on kodus.", + "notify_only_when_home": "Kui see on lubatud, viivitage märguanded, kuni vähemalt üks valitud inimene on kodus.", + "notify_fire_events": "Kui see on lubatud, käivitage koduabilise tsükli alguse ja lõpu sündmused (ha_washdata_cycle_started ja ha_washdata_cycle_ended), et juhtida automatiseerimist.", + "notify_start_services": "Üks või mitu teavitusteenust, mis hoiatavad tsükli alguses (nt notify.mobile_app_my_phone). Alustamise märguannete vahelejätmiseks jätke tühjaks.", + "notify_finish_services": "Üks või mitu teenust teavitavad, kui tsükkel lõppeb või läheneb lõpule. Lõpetamismärguannete vahelejätmiseks jätke tühjaks.", + "notify_live_services": "Üks või mitu teavitavat teenust, et saada tsükli ajal reaalajas edenemise värskendusi (ainult mobiilirakendus). Reaalajas värskenduste vahelejätmiseks jätke tühjaks.", + "notify_before_end_minutes": "Minutid enne tsükli hinnangulist lõppu, et käivitada teatis. Keelamiseks määrake 0. Vaikimisi: 0.", + "notify_live_interval_seconds": "Kui sageli edenemise värskendusi töötamise ajal saadetakse. Madalamad väärtused reageerivad paremini, kuid tarbivad rohkem märguandeid. Jõustatakse vähemalt 30 sekundit – alla 30 s väärtused ümardatakse automaatselt 30 sekundini.", + "notify_live_overrun_percent": "Ohutusvaru, mis ületab hinnangulist värskenduste arvu pikkade/ülejooksvate tsüklite korral (näiteks 20% võimaldab 1,2-kordset hinnangulist värskendust).", + "notify_live_chronometer": "Kui see on lubatud, sisaldab iga reaalajas värskendus kronomeetri pöördloendust hinnangulise lõpuajani. Teavituste taimer tiksub seadmes automaatselt värskenduste vahel (ainult Androidi kaasrakendus).", + "notify_title": "Kohandatud pealkiri teatiste jaoks. Toetab kohatäidet {device}.", + "notify_icon": "Märguannete jaoks kasutatav ikoon (nt mdi:pesumasin). Ikooni saatmata jätmiseks jätke tühjaks.", + "notify_start_message": "Sõnum saadetakse, kui tsükkel algab. Toetab kohatäidet {device}.", + "notify_finish_message": "Sõnum saadetakse pärast tsükli lõppemist. Toetab kohahoidjaid {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Valige numbriline olem, millel on praegune elektrihind (nt sensor.electricity_price, input_number.energy_rate). Kui see on määratud, lubab kohahoidja {cost}. Kui mõlemad on konfigureeritud, on see ülimuslik staatilise väärtuse suhtes.", + "energy_price_static": "Fikseeritud elektrihind kWh kohta. Kui see on määratud, lubab kohahoidja {cost}. Ignoreeritakse, kui olem on ülal konfigureeritud. Lisa oma valuuta sümbol sõnumimalli, nt. {cost} €.", + "notify_reminder_message": "Ühekordse meeldetuletuse tekst saatis seadistatud minutite arvu enne eeldatavat lõppu. Erinevus korduvatest live-uuendustest ülalpool. Toetab {device}, {minutes}, {program} kohahoidjaid.", + "notify_timeout_seconds": "Pärast seda mitu sekundit automaatselt teated välja jätta (edasi saadetud kui kaasrakenduse aegumine). Seadke 0, et hoida neid kuni vallandamiseni. Vaikimisi: 0.", + "notify_channel": "Android kaaslase rakenduse teavituskanal oleku, live progressi ja meeldetuletuste jaoks. Kanali heli ja tähtsus on konfigureeritud kaaslase rakenduses kanali nime esmakordsel kasutamisel - WashData määrab ainult kanali nime. Jäta rakenduse vaikeväärtus tühjaks.", + "notify_finish_channel": "Eraldi Android kanal lõpetatud hoiatuse jaoks (kasutab ka meeldetuletus ja pesu ootamise korduv meeldetuletus), et sellel oleks oma heli. Jäta tühjaks, et kanalit uuesti kasutada.", + "notify_pre_complete_message": "Korduva reaalajas edenemise tekst värskendatakse tsükli jooksul. Toetab kohahoidjaid {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Täpsemad seaded", + "description": "Tuvastamise lävede, õppimise ja täiustatud käitumise häälestamine. Vaikimisi seaded töötavad enamiku seadistuste puhul hästi.", + "sections": { + "suggestions_section": { + "name": "Soovituslikud seaded", + "description": "{suggestions_count} õpitud soovitust saadaval. Märkige Rakenda soovituslikud väärtused, et kavandatud muudatused enne salvestamist üle vaadata.", + "data": { + "apply_suggestions": "Rakendage soovitatud väärtused" + }, + "data_description": { + "apply_suggestions": "Märkige see ruut, et avada õppealgoritmi soovitatud väärtuste ülevaatamise etapp. Midagi ei salvestata automaatselt.\n\nSoovitatav (õppimisest):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Tuvastus ja võimsusläved", + "description": "Millal seadet loetakse SISSE või VÄLJA lülitatuks, energiapiirid ja olekuüleminekute ajastus.", + "data": { + "start_duration_threshold": "Tagasilükkamise alustamise kestus (id)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Minimaalne valmimisaeg (sekundites)", + "start_threshold_w": "Alguslävi (W)", + "stop_threshold_w": "Peatuslävi (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Jooksu surnud tsoon (sekundites)", + "end_repeat_count": "Lõpeta korduste loendus", + "min_off_gap": "Minimaalne vahe tsüklite vahel (s)", + "sampling_interval": "Proovivõtu intervall (id)" + }, + "data_description": { + "start_duration_threshold": "Ignoreerige sellest kestusest (sekundites) lühemaid lühiajalisi võimsuse hüppeid. Filtreerib algkäivitusnaelu (nt 60 W 2 sekundi jooksul) enne tegeliku tsükli algust. Vaikimisi: 5s.", + "start_energy_threshold": "Kinnitamiseks peab tsükkel START-faasi ajal koguma vähemalt nii palju energiat (Wh). Vaikimisi: 0,005 Wh.", + "completion_min_seconds": "Sellest lühemad tsüklid märgitakse katkestatuks, isegi kui need lõppevad loomulikult. Vaikimisi: 600 s.", + "start_threshold_w": "Tsükli alustamiseks peab võimsus tõusma ÜLE selle läve. Seadistage ooterežiimi võimsusest kõrgemaks. Vaikimisi: min_võimsus + 1 W.", + "stop_threshold_w": "Tsükli LÕPPEKS peab võimsus langema ALLALLA seda läve. Seadke nullist veidi kõrgemale, et vältida müra, mis käivitaks valeotste. Vaikimisi: 0,6 * min_võimsus.", + "end_energy_threshold": "Tsükkel lõpeb ainult siis, kui energia viimases Off-Delay aknas on sellest väärtusest (Wh) väiksem. Hoiab ära valeotsad, kui võimsus kõigub nulli lähedal. Vaikimisi: 0,05 Wh.", + "running_dead_zone": "Pärast tsükli algust ignoreerige võimsuse langusi nii paljude sekundite jooksul, et vältida vale lõpu tuvastamist esialgses pöörlemisfaasis. Keelamiseks määrake 0. Vaikimisi: 0s.", + "end_repeat_count": "Enne tsükli lõpetamist tuleb lõputingimuse (võimsus alla väljalülitusviivituse läve) järjestikuste täitmiste arv. Kõrgemad väärtused hoiavad ära valelõpud pauside ajal. Vaikimisi: 1.", + "min_off_gap": "Kui tsükkel algab selle mitme sekundi jooksul pärast eelmise lõppemist, LIIDEtakse need üheks tsükliks.", + "sampling_interval": "Minimaalne proovivõtu intervall sekundites. Sellest kiirusest kiiremaid värskendusi eiratakse. Vaikimisi: 30 s (2 s pesumasinate, pesumasin-kuivatite ja nõudepesumasinate jaoks)." + } + }, + "matching_section": { + "name": "Profiilide sobitamine ja õppimine", + "description": "Kui agressiivselt WashData sobitab käimasolevaid tsükleid õpitud profiilidega ja millal tagasisidet küsida.", + "data": { + "profile_match_min_duration_ratio": "Profiilivaste minimaalse kestuse suhe (0,1–1,0)", + "profile_match_interval": "Profiili vaste intervall (sekundites)", + "profile_match_threshold": "Profiili vaste lävi", + "profile_unmatch_threshold": "Profiili mittevastavuse lävi", + "auto_label_confidence": "Automaatsiltide usaldusväärsus (0-1)", + "learning_confidence": "Tagasisidetaotluse usaldus (0-1)", + "suppress_feedback_notifications": "Tagasiside märguannete väljalülitamine", + "duration_tolerance": "Kestuse tolerants (0–0,5)", + "smoothing_window": "Silumisaken (näidised)", + "profile_duration_tolerance": "Profiilivaste kestuse tolerants (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Profiili sobitamise minimaalne kestuse suhe. Töötsükkel peab vastamiseks olema vähemalt selle murdosa profiili kestusest. Madalam = varasem sobivus. Vaikimisi: 0,50 (50%).", + "profile_match_interval": "Kui sageli (sekundites) tsükli jooksul profiili sobitamist proovida. Madalamad väärtused tagavad kiirema tuvastamise, kuid kasutavad rohkem protsessorit. Vaikimisi: 300 s (5 minutit).", + "profile_match_threshold": "Minimaalne DTW sarnasusskoor (0,0–1,0), mis on vajalik profiili vastena käsitlemiseks. Kõrgem = rangem sobitamine, vähem valepositiivseid tulemusi. Vaikimisi: 0,4.", + "profile_unmatch_threshold": "DTW sarnasusskoor (0,0–1,0), millest madalamal varem sobitatud profiil tagasi lükatakse. Virvenduse vältimiseks peaks olema vaste läviväärtusest madalam. Vaikimisi: 0,35.", + "auto_label_confidence": "Märgista tsüklid automaatselt selle usaldusväärsusega või sellest kõrgemal pärast lõpetamist (0,0–1,0).", + "learning_confidence": "Minimaalne usaldus, et taotleda kasutaja kinnitust sündmuse kaudu (0,0–1,0).", + "suppress_feedback_notifications": "Kui see on lubatud, jälgib WashData endiselt sisemist tagasisidet ja küsib seda, kuid ei näita püsivaid teatisi, mis paluvad teil tsükleid kinnitada. Kasulik, kui tsüklid tuvastatakse alati õigesti ja juhised häirivad teie tähelepanu.", + "duration_tolerance": "Järelejäänud aja hinnangute tolerants jooksu ajal (0,0–0,5 vastab 0–50%).", + "smoothing_window": "Liikuva keskmise silumiseks kasutatud hiljutiste võimsusnäitude arv.", + "profile_duration_tolerance": "Tolerants, mida kasutatakse profiilide sobitamisel kogukestuse dispersiooni jaoks (0,0–0,5). Vaikimisi käitumine: ±25%." + } + }, + "timing_section": { + "name": "Ajastus, hooldus ja silumine", + "description": "Valveprotsess, edenemise lähtestamine, automaathooldus ja silumisolemite nähtavaks tegemine.", + "data": { + "watchdog_interval": "Valvekoera intervall (sekundites)", + "no_update_active_timeout": "Värskendusteta aeg (id)", + "progress_reset_delay": "Lähtestamise edenemise viivitus (sekundites)", + "auto_maintenance": "Luba automaatne hooldus", + "expose_debug_entities": "Avage silumisüksused", + "save_debug_traces": "Salvestage silumisjäljed" + }, + "data_description": { + "watchdog_interval": "Sekundid jooksmise ajal valvekoera kontrollide vahel. Vaikimisi: 5s. HOIATUS: veenduge, et see oleks KÕRGEM kui teie anduri värskendusintervall, et vältida valepeatusi (nt kui andurit värskendatakse iga 60 sekundi järel, kasutage 65+).", + "no_update_active_timeout": "Muutmisel avaldatavate andurite puhul: AKTIIVNE tsükkel sunnitud lõpetada ainult siis, kui selle sekundi jooksul värskendusi ei saabu. Madala võimsusega lõpetamine kasutab endiselt väljalülitusviivitust.", + "progress_reset_delay": "Pärast tsükli lõppemist (100%) oodake nii palju sekundeid jõudeolekut, enne kui lähtestate edenemise 0%-le. Vaikimisi: 300 s.", + "auto_maintenance": "Automaathoolduse lubamine (proovide parandamine, fragmentide liitmine).", + "expose_debug_entities": "Kuva silumiseks täiustatud andurid (usaldus, faas, mitmetähenduslikkus).", + "save_debug_traces": "Salvestage ajaloos üksikasjalikud järjestuse ja võimsuse jälgimise andmed (suurendab salvestusruumi kasutamist)." + } + }, + "anti_wrinkle_section": { + "name": "Kortsumisvastane kaitse (Kuivatid)", + "description": "Eira tsüklijärgseid trumlipöördeid, et vältida kummitustsükleid. Ainult kuivati / pesu-kuivati.", + "data": { + "anti_wrinkle_enabled": "Kortsudevastane kaitse (ainult kuivati)", + "anti_wrinkle_max_power": "Kortsudevastane maksimaalne võimsus (W)", + "anti_wrinkle_max_duration": "Kortsudevastane maksimaalne kestus (s)", + "anti_wrinkle_exit_power": "Kortsudevastane väljumisvõimsus (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignoreerige lühikesi väikese võimsusega trumli pöörlemisi pärast tsükli lõppu, et vältida kummitustsükliid (ainult kuivati/pesumasin-kuivati).", + "anti_wrinkle_max_power": "Selle võimsusega või sellest madalamal olevaid purskeid käsitletakse uute tsüklite asemel kortsudevastaste pöördetena. Kehtib ainult siis, kui kortsudevastane kaitse on sisse lülitatud.", + "anti_wrinkle_max_duration": "Maksimaalne katkestuspikkus, mida käsitleda kortsudevastase pöörlemisena. Kehtib ainult siis, kui kortsudevastane kaitse on sisse lülitatud.", + "anti_wrinkle_exit_power": "Kui võimsus langeb alla selle taseme, väljuge kohe kortsudevastasest funktsioonist (true off). Kehtib ainult siis, kui kortsudevastane kaitse on sisse lülitatud." + } + }, + "delay_start_section": { + "name": "Viitkäivituse tuvastus", + "description": "Käsitle püsivat madala võimsusega ooterežiimi kui 'Ootab käivitumist', kuni tsükkel tegelikult algab.", + "data": { + "delay_start_detect_enabled": "Luba viivitusega käivitamise tuvastamine", + "delay_confirm_seconds": "Viitkäivitus: ooterežiimi kinnitusaeg (s)", + "delay_timeout_hours": "Viitstart: maksimaalne ooteaeg (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kui see on lubatud, tuvastatakse viitkäivitusena lühike väikese võimsusega äravoolupiik, millele järgneb pidev ooterežiimi toide. Olekuandur näitab viivituse ajal teadet \"Ootab käivitumist\". Programmi aega ega edenemist ei jälgita enne, kui tsükkel tegelikult algab. Nõuab start_threshold_w seadistamist äravoolu võimsusest kõrgemale.", + "delay_confirm_seconds": "Võimsus peab püsima Stoppläve (W) ja Käivitusläve (W) vahel nii mitu sekundit, enne kui WashData läheb olekusse 'Ootab käivitumist'. Filtreerib välja lühikesed menüüliikumise tipud. Vaikimisi: 60 s.", + "delay_timeout_hours": "Ohutuse ajalõpp: kui masin on pärast seda mitu tundi endiselt režiimis „Ootab käivitumist”, lähtestatakse WashData olekusse Väljas. Vaikimisi: 8 h." + } + }, + "external_triggers_section": { + "name": "Välised päästikud, uks ja paus", + "description": "Valikuline väline lõputriggeri andur, ukseandur pausi/puhta oleku tuvastuseks ja pausi ajal voolu katkestav lüliti.", + "data": { + "external_end_trigger_enabled": "Luba väline tsükli lõpu päästik", + "external_end_trigger": "Väline tsükli lõpu andur", + "external_end_trigger_inverted": "Pöörake päästiku loogika ümber (päästik VÄLJAS-sündmusel)", + "door_sensor_entity": "Ukse andur", + "pause_cuts_power": "Katkesta voolu peatamise ajal", + "switch_entity": "Lüliti olem (toitekatkestuse peatamiseks)", + "notify_unload_delay_minutes": "Pesu ootamise teatise viivitus (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Lubage välise binaaranduri jälgimine, et käivitada tsükli lõpetamine.", + "external_end_trigger": "Valige binaarne anduri olem. Kui see andur käivitub, lõpeb praegune tsükkel olekuga \"lõpetatud\".", + "external_end_trigger_inverted": "Vaikimisi käivitub päästik, kui andur lülitub SISSE. Märkige see ruut, kui soovite selle asemel käivituda, kui andur lülitub VÄLJA.", + "door_sensor_entity": "Valikuline binaarandur masina ukse jaoks (sees = avatud, väljas = suletud). Kui uks aktiivse tsükli ajal avaneb, kinnitab WashData, et tsükkel on tahtlikult peatatud. Kui uks on endiselt suletud, muutub pärast tsükli lõppu olek \"Puhasta\", kuni uks avatakse. Märkus: ukse sulgemine EI jätka automaatselt peatatud tsüklit – jätkamine tuleb käivitada käsitsi nupu Resume Cycle või teenuse kaudu.", + "pause_cuts_power": "Kui peatate nupu Pause Cycle või teenuse kaudu, lülitage välja ka konfigureeritud lüliti olem. Jõuab ainult siis, kui selle seadme jaoks on konfigureeritud lüliti olem.", + "switch_entity": "Peatamisel või jätkamisel lülituv olem. Nõutav, kui 'Katkesta voolu peatamisel' on lubatud.", + "notify_unload_delay_minutes": "Kui tsükkel on lõppenud ja ust pole nii mitu minutit avatud, saatke teavitus finišiteatekanali kaudu (vajalik on ukseandur). Keelamiseks määrake 0. Vaikimisi: 60 min." + } + }, + "pump_section": { + "name": "Pumba jälgimine", + "description": "Ainult pumba seadmetüübile. Käivitab sündmuse, kui pumbatsükkel kestab sellest kauem.", + "data": { + "pump_stuck_duration": "Pumba kinnijäämise hoiatuslävi (ainult pumbad)" + }, + "data_description": { + "pump_stuck_duration": "Kui pumbatsükkel töötab ka pärast seda mitu sekundit, käivitatakse üks kord sündmus ha_washdata_pump_stuck. Kasutage seda kinnikiilunud mootori tuvastamiseks (tavaline karteripumba tööaeg on alla 60 s). Vaikimisi: 1800 s (30 min). Ainult pumba seadme tüüp." + } + }, + "device_link_section": { + "name": "Seadme link", + "description": "Lisavõimalusena ühendage see WashData seade olemasoleva Home Assistant seadmega (näiteks nutipistik või seade ise). Seejärel kuvatakse WashData seade selle seadme kaudu \"ühendatud\". Jäta tühjaks, et WashData jääks eraldiseisvaks seadmeks.", + "data": { + "linked_device": "Ühendatud seade" + }, + "data_description": { + "linked_device": "Valige seade WashData ühendamiseks. See lisab seadmeregistrisse viite 'ühendatud läbi'; WashData säilitab oma seadmekaardi ja olemid. Puhasta valik lingi eemaldamiseks." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika ja hooldus", + "description": "Käivitage hooldustoiminguid, nagu killustatud tsüklite liitmine või salvestatud andmete migreerimine.\n\n**Salvestusruumi kasutamine**\n\n- Faili suurus: {file_size_kb} KB\n- Tsüklid: {cycle_count}\n- Profiilid: {profile_count}\n- Silumisjäljed: {debug_count}", + "menu_options": { + "reprocess_history": "Hooldus: andmete ümbertöötlemine ja optimeerimine", + "clear_debug_data": "Silumisandmete kustutamine (ruumi vabastamine)", + "wipe_history": "Tühjendage selle seadme KÕIK andmed (pöördumatu)", + "export_import": "JSON-i eksportimine/importimine seadetega (kopeeri/kleebi)", + "menu_back": "← Tagasi" + } + }, + "clear_debug_data": { + "title": "Silumisandmete kustutamine", + "description": "Kas olete kindel, et soovite kustutada kõik salvestatud silumisjäljed? See vabastab ruumi, kuid eemaldab eelmistest tsüklitest üksikasjaliku järjestusteabe." + }, + "manage_cycles": { + "title": "Tsükleid hallata", + "description": "Viimased tsüklid:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Vanade tsüklite automaatne sildistamine", + "select_cycle_to_label": "Sildi konkreetne tsükkel", + "select_cycle_to_delete": "Kustuta tsükkel", + "interactive_editor": "Interaktiivne redaktori ühendamine/tükeldamine", + "trim_cycle_select": "Kärpimistsükli andmed", + "menu_back": "← Tagasi" + } + }, + "manage_cycles_empty": { + "title": "Tsükleid hallata", + "description": "Tsükleid pole veel salvestatud.", + "menu_options": { + "auto_label_cycles": "Vanade tsüklite automaatne sildistamine", + "menu_back": "← Tagasi" + } + }, + "manage_profiles": { + "title": "Profiilide haldamine", + "description": "Profiili kokkuvõte:\n{profile_summary}", + "menu_options": { + "create_profile": "Loo uus profiil", + "edit_profile": "Redigeeri/nimeta ümber profiili", + "delete_profile_select": "Kustuta profiil", + "profile_stats": "Profiili statistika", + "cleanup_profile": "Ajaloo puhastamine – graafik ja kustutamine", + "assign_profile_phases_select": "Määrake faasivahemikud", + "menu_back": "← Tagasi" + } + }, + "manage_profiles_empty": { + "title": "Profiilide haldamine", + "description": "Ühtegi profiili pole veel loodud.", + "menu_options": { + "create_profile": "Loo uus profiil", + "menu_back": "← Tagasi" + } + }, + "manage_phase_catalog": { + "title": "Halda faasikataloogi", + "description": "Faaside kataloog (selle seadme vaikeseaded + kohandatud faasid):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Loo uus faas", + "phase_catalog_edit_select": "Redigeeri faasi", + "phase_catalog_delete": "Kustuta faas", + "menu_back": "← Tagasi" + } + }, + "phase_catalog_create": { + "title": "Loo kohandatud faas", + "description": "Looge kohandatud faasi nimi ja kirjeldus ning valige, millist seadmetüüpi see kehtib.", + "data": { + "target_device_type": "Sihtseadme tüüp", + "phase_name": "Faasi nimi", + "phase_description": "Faasi kirjeldus" + } + }, + "phase_catalog_edit_select": { + "title": "Muuda kohandatud faasi", + "description": "Valige muutmiseks kohandatud faas.", + "data": { + "phase_name": "Kohandatud faas" + } + }, + "phase_catalog_edit": { + "title": "Muuda kohandatud faasi", + "description": "Redigeerimisfaas: {phase_name}", + "data": { + "phase_name": "Faasi nimi", + "phase_description": "Faasi kirjeldus" + } + }, + "phase_catalog_delete": { + "title": "Kustuta kohandatud faas", + "description": "Valige kustutamiseks kohandatud faas. Kõik seda faasi kasutavad määratud vahemikud eemaldatakse.", + "data": { + "phase_name": "Kohandatud faas" + } + }, + "assign_profile_phases": { + "title": "Määrake faasivahemikud", + "description": "Profiili faasi eelvaade: **{profile_name}**\n\n{timeline_svg}\n\n**Praegused vahemikud:**\n{current_ranges}\n\nKasutage allolevaid toiminguid vahemike lisamiseks, muutmiseks, kustutamiseks või salvestamiseks.", + "menu_options": { + "assign_profile_phases_add": "Lisa faasivahemik", + "assign_profile_phases_edit_select": "Redigeeri faasivahemikku", + "assign_profile_phases_delete": "Kustuta faasivahemik", + "phase_ranges_clear": "Kustuta kõik vahemikud", + "assign_profile_phases_auto_detect": "Faaside automaatne tuvastamine", + "phase_ranges_save": "Salvesta ja tagasta", + "menu_back": "← Tagasi" + } + }, + "assign_profile_phases_add": { + "title": "Lisa faasivahemik", + "description": "Lisage praegusele mustandile üks faasivahemik.", + "data": { + "phase_name": "Faas", + "start_min": "Alguse minut", + "end_min": "Lõpu minut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Redigeeri faasivahemikku", + "description": "Valige redigeeritav vahemik.", + "data": { + "range_index": "Faasi vahemik" + } + }, + "assign_profile_phases_edit": { + "title": "Redigeeri faasivahemikku", + "description": "Värskendage valitud faasivahemikku.", + "data": { + "phase_name": "Faas", + "start_min": "Alguse minut", + "end_min": "Lõpu minut" + } + }, + "assign_profile_phases_delete": { + "title": "Kustuta faasivahemik", + "description": "Valige mustandist eemaldatav vahemik.", + "data": { + "range_index": "Faasi vahemik" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automaatselt tuvastatud faasid", + "description": "Profiil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} faasi tuvastati automaatselt.** Valige allpool toiming.", + "data": { + "action": "Tegevus" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nimetage tuvastatud faasid", + "description": "Profiil: **{profile_name}**\n\n**Tuvastati {detected_count} faasi(t):**\n{ranges_summary}\n\nMäärake igale faasile nimi." + }, + "assign_profile_phases_select": { + "title": "Määrake faasivahemikud", + "description": "Valige faasivahemike konfigureerimiseks profiil.", + "data": { + "profile": "Profiil" + } + }, + "profile_stats": { + "title": "Profiili statistika", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Loo uus profiil", + "description": "Looge uus profiil käsitsi või eelmisest tsüklist.\n\nProfiilinimede näited: „Delicates”, „Heavy Duty”, „Quick Wash”", + "data": { + "profile_name": "Profiili nimi", + "reference_cycle": "Võrdlustsükkel (valikuline)", + "manual_duration": "Käsitsi kestus (minutites)" + } + }, + "edit_profile": { + "title": "Redigeeri profiili", + "description": "Valige ümbernimetamiseks profiil", + "data": { + "profile": "Profiil" + } + }, + "rename_profile": { + "title": "Redigeeri profiili üksikasju", + "description": "Praegune profiil: {current_name}", + "data": { + "new_name": "Profiili nimi", + "manual_duration": "Käsitsi kestus (minutites)" + } + }, + "delete_profile_select": { + "title": "Kustuta profiil", + "description": "Valige kustutamiseks profiil", + "data": { + "profile": "Profiil" + } + }, + "delete_profile_confirm": { + "title": "Kinnitage profiili kustutamine", + "description": "⚠️ See kustutab jäädavalt profiili: {profile_name}", + "data": { + "unlabel_cycles": "Eemaldage silt seda profiili kasutades tsüklitelt" + } + }, + "auto_label_cycles": { + "title": "Vanade tsüklite automaatne sildistamine", + "description": "Leiti kokku {total_count} tsüklit. Profiilid: {profiles}", + "data": { + "confidence_threshold": "Minimaalne usaldus (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Valige Tsükli märgistamiseks", + "description": "✓ = lõpetatud, ⚠ = jätkatud, ✗ = katkestatud", + "data": { + "cycle_id": "Tsükkel" + } + }, + "select_cycle_to_delete": { + "title": "Kustuta tsükkel", + "description": "⚠️ See kustutab valitud tsükli jäädavalt", + "data": { + "cycle_id": "Tsükli kustutamiseks" + } + }, + "label_cycle": { + "title": "Sildi tsükkel", + "description": "{cycle_info}", + "data": { + "profile_name": "Profiil", + "new_profile_name": "Uus profiilinimi (kui luuakse)" + } + }, + "post_process": { + "title": "Protsessi ajalugu (ühenda/jaga)", + "description": "Puhastage tsüklite ajalugu, ühendades killustatud jooksud ja jagades valesti ühendatud tsüklid ajaaknas. Sisestage tagasivaatamiseks kuluvate tundide arv (kasutage kõigi jaoks 999999).", + "data": { + "time_range": "Tagasivaate aken (tundides)", + "gap_seconds": "Ühenda/jaga vahe (sekundites)" + }, + "data_description": { + "time_range": "Tundide arv killustatud tsüklite ühendamiseks otsimiseks. Kasutage kõigi ajalooliste andmete töötlemiseks numbrit 999999.", + "gap_seconds": "Lõikamise ja ühendamise otsustamise künnis. Sellest LÜHEMEMAD lüngad liidetakse. Sellest PIKEMAD lüngad on jagatud. Valesti liidetud tsükli jagamise sundimiseks langetage seda." + } + }, + "cleanup_profile": { + "title": "Ajaloo puhastamine – valige profiil", + "description": "Valige visualiseerimiseks profiil. See loob graafiku, mis näitab selle profiili kõiki möödunud tsükleid, et aidata tuvastada kõrvalekaldeid.", + "data": { + "profile": "Profiil" + } + }, + "cleanup_select": { + "title": "Ajaloo puhastamine – graafik ja kustutamine", + "description": "![Graafik]({graph_url})\n\n**Visualiseerimine {profile_name}**\n\nGraafik näitab üksikuid tsükleid värviliste joontena. Nende kustutamiseks tuvastage allolevas loendis kõrvalekalded (grupist kaugel olevad jooned) ja sobitage nende värv.\n\n**Valige tsüklid, mida PÜDAVALT kustutada:**", + "data": { + "cycles_to_delete": "Tsükleid kustutamiseks (kontrollige sobivaid värve)" + } + }, + "interactive_editor": { + "title": "Interaktiivne redaktori ühendamine/tükeldamine", + "description": "Valige käsitsi toiming, mida oma tsükli ajaloos teha.", + "menu_options": { + "editor_split": "Tsükli poolitamine (lünkade leidmine)", + "editor_merge": "Tsüklite ühendamine (fragmentide ühendamine)", + "editor_delete": "Kustuta tsükkel(id)", + "menu_back": "← Tagasi" + } + }, + "editor_select": { + "title": "Valige Tsüklid", + "description": "Valige töödeldav(ad) tsükkel(id).\n\n{info_text}", + "data": { + "selected_cycles": "Tsüklid" + } + }, + "editor_configure": { + "title": "Seadista ja rakenda", + "description": "{preview_md}", + "data": { + "confirm_action": "Tegevus", + "merged_profile": "Tulemuseks olev profiil", + "new_profile_name": "Uus profiilinimi", + "confirm_commit": "Jah, ma kinnitan seda tegevust", + "segment_0_profile": "1. segmendi profiil", + "segment_1_profile": "2. segmendi profiil", + "segment_2_profile": "3. segmendi profiil", + "segment_3_profile": "4. segmendi profiil", + "segment_4_profile": "5. segmendi profiil", + "segment_5_profile": "6. segmendi profiil", + "segment_6_profile": "Segmendi 7 profiil", + "segment_7_profile": "8. segmendi profiil", + "segment_8_profile": "Segmendi 9 profiil", + "segment_9_profile": "Segmendi 10 profiil" + } + }, + "editor_split_params": { + "title": "Tükeldatud konfiguratsioon", + "description": "Reguleerige selle tsükli jagamise tundlikkust. Sellest pikem tühikäigu vahe põhjustab lõhenemist.", + "data": { + "split_mode": "Jagamismeetod" + } + }, + "editor_split_auto_params": { + "title": "Automaatse jagamise seadistus", + "description": "Kohandage selle tsükli jagamise tundlikkust. Iga sellest pikem jõudevahe põhjustab jagamise.", + "data": { + "min_gap_seconds": "Jagamisvahe lävi (sekundid)" + } + }, + "editor_split_manual_params": { + "title": "Käsitsi jagamise ajatemplid", + "description": "{preview_md}Tsükli aken: **{cycle_start_wallclock} → {cycle_end_wallclock}** kuupäeval {cycle_date}.\n\nSisestage üks või mitu jagamise ajatemplit (`HH:MM` või `HH:MM:SS`), üks rea kohta. Tsükkel lõigatakse iga ajatempli juures — N ajatemplit annavad N+1 segmenti.", + "data": { + "split_timestamps": "Jagamise ajatemplid" + } + }, + "reprocess_history": { + "title": "Hooldus: andmete ümbertöötlemine ja optimeerimine", + "description": "Teostab ajalooliste andmete sügavpuhastuse:\n1. Kärbib nullvõimsuse näitu (vaikus).\n2. Kinnitab tsükli ajastuse/kestuse.\n3. Arvutab ümber kõik statistilised mudelid (ümbrikud).\n\n**Käitage seda andmete probleemide lahendamiseks või uute algoritmide rakendamiseks.**" + }, + "wipe_history": { + "title": "Ajaloo kustutamine (ainult testimine)", + "description": "⚠️ See kustutab jäädavalt KÕIK selle seadme salvestatud tsüklid ja profiilid. Seda ei saa tagasi võtta!" + }, + "export_import": { + "title": "Ekspordi/impordi JSON", + "description": "Selle seadme tsüklite, profiilide JA seadete kopeerimine/kleepimine. Eksportige allpool või kleepige importimiseks JSON.", + "data": { + "mode": "Tegevus", + "json_payload": "JSON täielik konfigureerimine" + }, + "data_description": { + "mode": "Valige Ekspordi, et kopeerida praegused andmed ja seaded, või Import, et kleepida eksporditud andmed teisest WashData seadmest.", + "json_payload": "Ekspordiks: kopeerige see JSON (sisaldab tsükleid, profiile ja kõiki peenhäälestatud sätteid). Importimiseks: kleepige WashDatast eksporditud JSON." + } + }, + "record_cycle": { + "title": "Salvestustsükkel", + "description": "Salvestage tsükkel käsitsi, et luua häireteta puhas profiil.\n\nOlek: **{status}**\nKestus: {duration} s\nNäidised: {samples}", + "menu_options": { + "record_refresh": "Värskenda olekut", + "record_stop": "Peata salvestamine (salvesta ja töötle)", + "record_start": "Alusta uut salvestust", + "record_process": "Viimase salvestuse töötlemine (kärbi ja salvesta)", + "record_discard": "Loobu viimane salvestis", + "menu_back": "← Tagasi" + } + }, + "record_process": { + "title": "Protsessi salvestamine", + "description": "![Graafik]({graph_url})\n\n**Graafik näitab töötlemata salvestust.** Sinine = säilita, punane = kavandatud kärpimine.\n*Graafik on staatiline ja seda ei värskendata vormimuudatustega.*\n\nSalvestamine peatus. Kärbimised on joondatud tuvastatud diskreetimissagedusega (~{sampling_rate} s).\n\nToores kestus: {duration} s\nNäidised: {samples}", + "data": { + "head_trim": "Kärbi algus (sekundites)", + "tail_trim": "Kärbi lõpp (sekundites)", + "save_mode": "Salvesta sihtkoht", + "profile_name": "Profiili nimi" + } + }, + "learning_feedbacks": { + "title": "Õppimise tagasiside", + "description": "Ootel tagasiside: {count}\n\n{pending_table}\n\nValige töötlemiseks tsükli ülevaatuse taotlus.", + "menu_options": { + "learning_feedbacks_pick": "Vaata ootel tagasisidet", + "learning_feedbacks_dismiss_all": "Lükka kõik ootel tagasisided tagasi", + "menu_back": "← Tagasi" + } + }, + "learning_feedbacks_pick": { + "title": "Valige läbivaatamiseks tagasiside", + "description": "Valige avamiseks tsükli ülevaatuse taotlus.", + "data": { + "selected_feedback": "Valige läbivaatamiseks tsükkel" + } + }, + "learning_feedbacks_empty": { + "title": "Õppimise tagasiside", + "description": "Ootel tagasisidetaotlusi ei leitud.", + "menu_options": { + "menu_back": "← Tagasi" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Lükka kõik ootel tagasisided tagasi", + "description": "Te kavatsete tagasi lükata **{count}** ootel tagasisidetaotlust. Tagasi lükatud tsüklid jäävad teie ajalukku, kuid ei anna uut õppesignaali. Seda ei saa tagasi võtta.\n\n✅ Märkige allolev ruut ja klõpsake nuppu **Esita**, et kõik tagasi lükata.\n❌ Jätke see märkimata ja klõpsake nuppu **Esita**, et tühistada ja minna tagasi.", + "data": { + "confirm_dismiss_all": "Kinnita: lükka kõik ootel tagasisidetaotlused tagasi" + } + }, + "resolve_feedback": { + "title": "Lahendage tagasiside", + "description": "{comparison_img}{candidates_table}\n**Tuvastatud profiil**: {detected_profile} ({confidence_pct}%)\n**Eeldatav kestus**: {est_duration_min} min\n**Tegelik kestus**: {act_duration_min} min\n\n__Valige allpool toiming:__", + "data": { + "action": "Mida sa teha tahaksid?", + "corrected_profile": "Õige programm (kui parandate)", + "corrected_duration": "Õige kestus sekundites (kui parandate)" + } + }, + "trim_cycle_select": { + "title": "Kärpimistsükkel - valige Tsükkel", + "description": "Valige kärpimiseks tsükkel. ✓ = lõpetatud, ⚠ = jätkatud, ✗ = katkestatud", + "data": { + "cycle_id": "Tsükkel" + } + }, + "trim_cycle": { + "title": "Kärpimistsükkel", + "description": "Praegune aken: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Tegevus" + } + }, + "trim_cycle_start": { + "title": "Määra kärpimise algus", + "description": "Tsükli aken: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nPraegune algus: **{current_wallclock}** (+{current_offset_min} min tsükli algusest)\n\nValige alloleva kellavalija abil uus algusaeg.", + "data": { + "trim_start_time": "Uus algusaeg", + "trim_start_min": "Uus algus (minutid alates tsükli algusest)" + } + }, + "trim_cycle_end": { + "title": "Määra kärpimise lõpp", + "description": "Tsükli aken: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nPraegune lõpp: **{current_wallclock}** (+{current_offset_min} min tsükli algusest)\n\nValige alloleva kellavalija abil uus lõpuaeg.", + "data": { + "trim_end_time": "Uus lõpuaeg", + "trim_end_min": "Uus lõpp (minutid alates tsükli algusest)" + } + } + }, + "error": { + "import_failed": "Import ebaõnnestus. Kleepige kehtiv WashData ekspordi JSON.", + "select_exactly_one": "Valige selle toimingu jaoks täpselt üks tsükkel.", + "select_at_least_one": "Valige selle toimingu jaoks vähemalt üks tsükkel.", + "select_at_least_two": "Valige selle toimingu jaoks vähemalt kaks tsüklit.", + "profile_exists": "Selle nimega profiil on juba olemas", + "rename_failed": "Profiili ümbernimetamine ebaõnnestus. Profiili ei pruugi olla või uus nimi on juba võetud.", + "assignment_failed": "Tsüklile profiili määramine ebaõnnestus", + "duplicate_phase": "Selle nimega faas on juba olemas.", + "phase_not_found": "Valitud faasi ei leitud.", + "invalid_phase_name": "Faasi nimi ei tohi olla tühi.", + "phase_name_too_long": "Etapi nimi on liiga pikk.", + "invalid_phase_range": "Faasivahemik on kehtetu. Lõpp peab olema algusest suurem.", + "invalid_phase_timestamp": "Ajatempel on kehtetu. Kasutage vormingut AAAA-KK-PP HH:MM või HH:MM.", + "overlapping_phase_ranges": "Faasivahemikud kattuvad. Reguleerige vahemikke ja proovige uuesti.", + "incomplete_phase_row": "Iga faasirida peab sisaldama valitud režiimi faasi pluss täielikke algus-/lõpuväärtusi.", + "timestamp_mode_cycle_required": "Valige ajatemplirežiimi kasutamisel allika tsükkel.", + "no_phase_ranges": "Selle toimingu jaoks pole veel faasivahemikke saadaval.", + "feedback_notification_title": "WashData: kontrolli tsükkel ({device})", + "feedback_notification_message": "**Seade**: {device}\n**Programm**: {program} ({confidence}% usaldusväärsus)\n**Aeg**: {time}\n\nWashData vajab tuvastatud tsükli kontrollimiseks teie abi.\n\nSelle tulemuse kinnitamiseks või parandamiseks minge jaotisse **Seaded > Seadmed ja teenused > WashData > Seadistamine > Tagasiside õppimine**.", + "suggestions_ready_notification_title": "WashData: soovituslikud seaded on valmis ({device})", + "suggestions_ready_notification_message": "Andur **Soovitatud seaded** annab nüüd **{count}** rakendatavaid soovitusi.\n\nNende ülevaatamiseks ja rakendamiseks tehke järgmist. **Seaded > Seadmed ja teenused > WashData > Seadistamine > Täpsemad seaded > Rakenda soovitatud väärtused**.\n\nSoovitused on valikulised ja neid kuvatakse enne salvestamist ülevaatamiseks.", + "trim_range_invalid": "Algus peab olema enne lõppu. Palun kohandage oma trimmipunkte.", + "trim_failed": "Kärpimine ebaõnnestus. Tsüklit ei pruugi enam eksisteerida või aken on tühi.", + "invalid_split_timestamp": "Ajatempel on kehtetu või jääb tsükli ajavahemikust välja. Kasutage tsükli aknas vormingut HH:MM või HH:MM:SS.", + "no_split_segments_found": "Nendest ajatemplidest ei saanud ühtegi kehtivat segmenti luua. Veenduge, et iga ajatempel jääb tsükli aknasse ja segmendid on vähemalt 1 minuti pikkused.", + "auto_tune_suggestion": "{device_type} {device_title} tuvastas kummitustsüklit. Soovitatav min_võimsuse muutus: {current_min}W -> {new_min}W (ei rakendata automaatselt).", + "auto_tune_title": "WashData automaatne häälestus", + "auto_tune_fallback": "{device_type} {device_title} tuvastas kummitustsüklit. Soovitatav min_võimsuse muutus: {current_min}W -> {new_min}W (ei rakendata automaatselt)." + }, + "abort": { + "no_cycles_found": "Tsükleid ei leitud", + "no_split_segments_found": "Valitud tsüklist ei leitud ühtegi jagatavat segmenti.", + "cycle_not_found": "Valitud tsüklit ei saanud laadida.", + "no_power_data": "Valitud tsükli kohta pole võimsuse jälgimise andmeid saadaval.", + "no_profiles_found": "Ühtegi profiili ei leitud. Esmalt looge profiil.", + "no_profiles_for_matching": "Ühtegi profiili pole sobitamiseks saadaval. Esmalt looge profiilid.", + "no_unlabeled_cycles": "Kõik tsüklid on juba märgistatud", + "no_suggestions": "Soovitatud väärtusi pole veel saadaval. Käivitage paar tsüklit ja proovige uuesti.", + "no_predictions": "Ei mingeid ennustusi", + "no_custom_phases": "Kohandatud faase ei leitud.", + "reprocess_success": "{count} tsüklit edukalt uuesti töödeldud.", + "debug_data_cleared": "Silumisandmete kustutamine {count} tsüklist õnnestus." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Tsükli algus", + "cycle_finish": "Tsükli lõpetamine", + "cycle_live": "Otseülekanne" + } + }, + "common_text": { + "options": { + "unlabeled": "(Märgistamata)", + "create_new_profile": "Loo uus profiil", + "remove_label": "Eemalda silt", + "no_reference_cycle": "(Ei võrdlustsüklit)", + "all_device_types": "Kõik seadmetüübid", + "current_suffix": "(praegune)", + "editor_select_info": "Valige poolitamiseks 1 tsükkel või ühendamiseks 2+ tsüklit.", + "editor_select_info_split": "Jagamiseks valige 1 tsükkel.", + "editor_select_info_merge": "Ühendamiseks valige 2+ tsüklit.", + "editor_select_info_delete": "Kustutamiseks valige 1+ tsükkel(tsüklit).", + "no_cycles_recorded": "Tsükleid pole veel salvestatud.", + "profile_summary_line": "- **{name}**: {count} tsüklit, {avg} min keskm", + "no_profiles_created": "Ühtegi profiili pole veel loodud.", + "phase_preview": "Faasi eelvaade", + "phase_preview_no_curve": "Keskmine profiilikõver pole veel saadaval. Käivita/sildista selle profiili jaoks veel tsükleid.", + "average_power_curve": "Keskmine võimsuskõver", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} tsüklit, ~{duration} min keskmiselt)", + "profile_option_short_fmt": "{name} ({count} tsüklit, ~{duration} min)", + "deleted_cycles_from_profile": "Kustutatud {count} tsüklit domeenist {profile}.", + "not_enough_data_graph": "Graafiku loomiseks pole piisavalt andmeid.", + "no_profiles_found": "Ühtegi profiili ei leitud.", + "cycle_info_fmt": "Tsükkel: {start}, {duration} min, praegune: {label}", + "top_candidates_header": "### Parimad kandidaadid", + "tbl_profile": "Profiil", + "tbl_confidence": "Usaldus", + "tbl_correlation": "Korrelatsioon", + "tbl_duration_match": "Kestus Matš", + "detected_profile": "Tuvastatud profiil", + "estimated_duration": "Eeldatav kestus", + "actual_duration": "Tegelik kestus", + "choose_action": "__Valige allpool toiming:__", + "tbl_count": "Arv", + "tbl_avg": "Keskm", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (keskm.)", + "tbl_energy_total": "Energia (kokku)", + "tbl_consistency": "Järjepidevus", + "tbl_last_run": "Viimane jooks", + "graph_legend_title": "Graafiku legend", + "graph_legend_body": "Sinine riba tähistab täheldatud minimaalset ja maksimaalset energiatarbimise vahemikku. Joon näitab keskmist võimsuskõverat.", + "recording_preview": "Salvestuse eelvaade", + "trim_start": "Kärbi algus", + "trim_end": "Kärbi lõpp", + "split_preview_title": "Tükeldatud eelvaade", + "split_preview_found_fmt": "Leiti {count} segmenti.", + "split_preview_confirm_fmt": "Selle tsükli jagamiseks {count} eraldi tsükliks klõpsake nuppu Kinnita.", + "merge_preview_title": "Ühenda eelvaade", + "merge_preview_joining_fmt": "{count} tsükliga liitumine. Vahed täidetakse 0 W näiduga.", + "editor_delete_preview_title": "Kustutamise eelvaade", + "editor_delete_preview_intro": "Valitud tsüklid kustutatakse jäädavalt:", + "editor_delete_preview_confirm": "Klõpsake Kinnita, et need tsükli kirjed jäädavalt kustutada.", + "no_power_preview": "*Eelvaateks pole võimsusandmeid saadaval.*", + "profile_comparison": "Profiilide võrdlus", + "actual_cycle_label": "See tsükkel (tegelik)", + "storage_usage_header": "Salvestuskasutus", + "storage_file_size": "Faili suurus", + "storage_cycles": "Tsüklid", + "storage_profiles": "Profiilid", + "storage_debug_traces": "Silumisjäljed", + "overview_suffix": "Ülevaade", + "phase_preview_for_profile": "Profiili faasi eelvaade", + "current_ranges_header": "Praegused vahemikud", + "assign_phases_help": "Kasutage allolevaid toiminguid vahemike lisamiseks, muutmiseks, kustutamiseks või salvestamiseks.", + "profile_summary_header": "Profiili kokkuvõte", + "recent_cycles_header": "Viimased tsüklid", + "trim_cycle_preview_title": "Tsükli kärpimise eelvaade", + "trim_cycle_preview_no_data": "Selle tsükli võimsusandmed pole saadaval.", + "trim_cycle_preview_kept_suffix": "hoitud", + "table_program": "Programm", + "table_when": "Millal", + "table_length": "Kestus", + "table_match": "Sobivus", + "table_profile": "Profiil", + "table_cycles": "Tsüklid", + "table_avg_length": "Keskm. kestus", + "table_last_run": "Viimane jooks", + "table_avg_energy": "Keskm. energia", + "table_detected_program": "Tuvastatud programm", + "table_confidence": "Usaldus", + "table_reported": "Teatatud", + "phase_builtin_suffix": "(sisseehitatud)", + "phase_none_available": "Faase pole saadaval.", + "settings_deprecation_warning": "⚠️ **Tuginemata seadme tüüp:** {device_type} eemaldatakse tulevases versioonis. WashData sobituskonveier ei anna selle seadmeklassi jaoks usaldusväärseid tulemusi. Teie integratsioon toimib kogu aegumisperioodi jooksul; selle hoiatuse vaigistamiseks valige **Seadme tüüp** allpool ühele toetatud tüübile (pesumasin, kuivati, pesumasin-kuivati, nõudepesumasin, õhufritüür, leivamasin või pump) või valikule **Muu (täiustatud)**, kui teie seade ei vasta ühelegi toetatud tüübile. **Muu (täiustatud)** tarnib tahtlikult üldisi vaikeseadeid, mis ei ole häälestatud ühegi konkreetse seadme jaoks, seega peate läved, ajalõpud ja sobivad parameetrid ise konfigureerima; vahetamisel säilitatakse kõik teie olemasolevad seaded.", + "phase_other_device_types": "Muud seadmetüübid:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Tsükli poolitamine (lünkade leidmine)", + "merge": "Tsüklite ühendamine (fragmentide ühendamine)", + "delete": "Kustuta tsükkel(id)" + } + }, + "split_mode": { + "options": { + "auto": "Tuvasta jõudevahed automaatselt", + "manual": "Käsitsi ajatempel(id)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Hooldus: andmete ümbertöötlemine ja optimeerimine", + "clear_debug_data": "Silumisandmete kustutamine (ruumi vabastamine)", + "wipe_history": "Tühjendage selle seadme KÕIK andmed (pöördumatu)", + "export_import": "JSON-i eksportimine/importimine seadetega (kopeeri/kleebi)" + } + }, + "export_import_mode": { + "options": { + "export": "Ekspordi kõik andmed", + "import": "Andmete import/ühendamine" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Vanade tsüklite automaatne sildistamine", + "select_cycle_to_label": "Sildi konkreetne tsükkel", + "select_cycle_to_delete": "Kustuta tsükkel", + "interactive_editor": "Interaktiivne redaktori ühendamine/tükeldamine", + "trim_cycle": "Kärpimistsükli andmed" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Loo uus profiil", + "edit_profile": "Redigeeri/nimeta ümber profiili", + "delete_profile": "Kustuta profiil", + "profile_stats": "Profiili statistika", + "cleanup_profile": "Ajaloo puhastamine – graafik ja kustutamine", + "assign_phases": "Määrake faasivahemikud" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Loo uus faas", + "edit_custom_phase": "Redigeeri faasi", + "delete_custom_phase": "Kustuta faas" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Lisa faasivahemik", + "edit_range": "Redigeeri faasivahemikku", + "delete_range": "Kustuta faasivahemik", + "clear_ranges": "Kustuta kõik vahemikud", + "auto_detect_ranges": "Faaside automaatne tuvastamine", + "save_ranges": "Salvesta ja tagasta" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Värskenda olekut", + "stop_recording": "Peata salvestamine (salvesta ja töötle)", + "start_recording": "Alusta uut salvestust", + "process_recording": "Viimase salvestuse töötlemine (kärbi ja salvesta)", + "discard_recording": "Loobu viimane salvestis" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Loo uus profiil", + "existing_profile": "Lisa olemasolevale profiilile", + "discard": "Loobu salvestusest" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Kinnita – õige tuvastamine", + "correct": "Õige – valige Programm/Kestus", + "ignore": "Ignoreeri – valepositiivne/mürarikas tsükkel", + "delete": "Kustuta – eemalda ajaloost" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nimetage ja rakendage faase", + "cancel": "Minge tagasi ilma muudatusteta" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Määra alguspunkt", + "set_end": "Määra lõpp-punkt", + "reset": "Lähtestage täiskestusele", + "apply": "Rakenda kärpimine", + "cancel": "Tühista" + } + }, + "device_type": { + "options": { + "washing_machine": "Pesumasin", + "dryer": "Kuivati", + "washer_dryer": "Kombineeritud pesumasin-kuivati", + "dishwasher": "Nõudepesumasin", + "coffee_machine": "Kohvimasin (aegunud)", + "ev": "Elektrisõiduk (aegunud)", + "air_fryer": "Õhufritüür", + "heat_pump": "Soojuspump (aegunud)", + "bread_maker": "Leivaküpsetaja", + "pump": "Pump / Äravoolupump", + "oven": "Ahi (aegunud)", + "other": "Muu (täpsem)" + } + } + }, + "services": { + "label_cycle": { + "name": "Tsükli sildistamine", + "description": "Määrake olemasolev profiil eelmisele tsüklile või eemaldage silt.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade märgistada." + }, + "cycle_id": { + "name": "Tsükli ID", + "description": "Silditava tsükli ID." + }, + "profile_name": { + "name": "Profiili nimi", + "description": "Olemasoleva profiili nimi (profiilide loomine menüüs Profiilide haldamine). Sildi eemaldamiseks jätke tühjaks." + } + } + }, + "create_profile": { + "name": "Loo profiil", + "description": "Looge uus profiil (eraldi või võrdlustsükli alusel).", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade." + }, + "profile_name": { + "name": "Profiili nimi", + "description": "Uue profiili nimi (nt Heavy Duty, Delicates)." + }, + "reference_cycle_id": { + "name": "Võrdlustsükli ID", + "description": "Valikuline tsükli ID selle profiili aluseks." + } + } + }, + "delete_profile": { + "name": "Kustuta profiil", + "description": "Kustutage profiil ja soovi korral eemaldage selle abil tsüklite sildid.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade." + }, + "profile_name": { + "name": "Profiili nimi", + "description": "Kustutatav profiil." + }, + "unlabel_cycles": { + "name": "Tsüklite märgistuse tühistamine", + "description": "Eemaldage profiili silt seda profiili kasutavatelt tsüklitelt." + } + } + }, + "auto_label_cycles": { + "name": "Vanade tsüklite automaatne sildistamine", + "description": "Sildistada tagasiulatuvalt märgistamata tsüklid, kasutades profiili sobitamist.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade." + }, + "confidence_threshold": { + "name": "Usalduse lävi", + "description": "Minimaalne vaste usaldusväärsus (0,50–0,95) siltide rakendamiseks." + } + } + }, + "export_config": { + "name": "Ekspordi konfiguratsioon", + "description": "Eksportige selle seadme profiilid, tsüklid ja seaded JSON-faili (seadme kohta).", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade ekspordiks." + }, + "path": { + "name": "Tee", + "description": "Valikuline absoluutne failitee kirjutamiseks (vaikimisi /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Impordi konfiguratsioon", + "description": "Importige selle seadme profiilid, tsüklid ja seaded JSON-i ekspordifailist (seaded võidakse üle kirjutada).", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade, millesse importida." + }, + "path": { + "name": "Tee", + "description": "Imporditava JSON-faili ekspordi absoluutne tee." + } + } + }, + "submit_cycle_feedback": { + "name": "Esitage tsükli tagasiside", + "description": "Pärast tsükli lõppemist kinnitage või parandage automaatselt tuvastatud programm. Sisestage kas „entry_id” (täpsem) või „device_id” (soovitatav).", + "fields": { + "device_id": { + "name": "Seade", + "description": "Seade WashData (soovitatav sisestamise_id asemel)." + }, + "entry_id": { + "name": "Sisenemise ID", + "description": "Seadme konfiguratsioonikirje ID (alternatiiv seadme_id-le)." + }, + "cycle_id": { + "name": "Tsükli ID", + "description": "Tsükli ID, mis kuvatakse tagasiside teatises / logides." + }, + "user_confirmed": { + "name": "Kinnitage tuvastatud programm", + "description": "Määra tõene, kui tuvastatud programm on õige." + }, + "corrected_profile": { + "name": "Parandatud profiil", + "description": "Kui seda ei kinnitata, sisestage õige profiili/programmi nimi." + }, + "corrected_duration": { + "name": "Parandatud kestus (sekundites)", + "description": "Valikuline korrigeeritud kestus sekundites." + }, + "notes": { + "name": "Märkmed", + "description": "Valikulised märkused selle tsükli kohta." + } + } + }, + "record_start": { + "name": "Tsükli salvestamise alustamine", + "description": "Alustage puhta tsükli käsitsi salvestamist (läheb mööda kogu sobivast loogikast).", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade salvestamiseks." + } + } + }, + "record_stop": { + "name": "Tsükli salvestamise lõpetamine", + "description": "Peatage käsitsi salvestamine.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Pesumasina seade salvestamise peatamiseks." + } + } + }, + "trim_cycle": { + "name": "Tsükli kärpimine", + "description": "Kärbige möödunud tsükli võimsusandmeid kindlale ajaaknale.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Seade WashData." + }, + "cycle_id": { + "name": "Tsükli ID", + "description": "Kärbitava tsükli ID." + }, + "trim_start_s": { + "name": "Kärbi algus (sekundites)", + "description": "Säilitage andmed sellest nihkest sekundites. Vaikimisi 0." + }, + "trim_end_s": { + "name": "Kärbi lõpp (sekundites)", + "description": "Hoidke andmeid sekundites selle nihkeni. Vaikimisi = täiskestus." + } + } + }, + "pause_cycle": { + "name": "Peata tsükkel", + "description": "Peatage WashData seadme aktiivne tsükkel.", + "fields": { + "device_id": { + "name": "Seade", + "description": "Seade WashData peatamiseks." + } + } + }, + "resume_cycle": { + "name": "Tsükli jätkamine", + "description": "Jätkake WashData seadme peatatud tsüklit.", + "fields": { + "device_id": { + "name": "Seade", + "description": "WashData seade jätkamiseks." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Töötab" + }, + "match_ambiguity": { + "name": "Vaste ebamäärasus" + } + }, + "sensor": { + "washer_state": { + "name": "Olek", + "state": { + "off": "Väljas", + "idle": "Ootel", + "starting": "Käivitamine", + "running": "Töötab", + "paused": "Peatatud", + "user_paused": "Kasutaja peatatud", + "ending": "Lõpetamine", + "finished": "Valmis", + "anti_wrinkle": "Kortsudevastane", + "delay_wait": "Ootab algust", + "interrupted": "Katkestatud", + "force_stopped": "Sundpeatatud", + "rinse": "Loputamine", + "unknown": "Tundmatu", + "clean": "Puhas" + } + }, + "washer_program": { + "name": "Programm" + }, + "time_remaining": { + "name": "Järelejäänud aeg" + }, + "total_duration": { + "name": "Kogukestus" + }, + "cycle_progress": { + "name": "Edusammud" + }, + "current_power": { + "name": "Praegune võimsus" + }, + "elapsed_time": { + "name": "Kulunud aeg" + }, + "debug_info": { + "name": "Silumisinfo" + }, + "match_confidence": { + "name": "Vaste usaldusväärsus" + }, + "top_candidates": { + "name": "Parimad kandidaadid", + "state": { + "none": "Mitte ühtegi" + } + }, + "current_phase": { + "name": "Praegune faas" + }, + "wash_phase": { + "name": "Faas" + }, + "profile_cycle_count": { + "name": "Profiili {profile_name} arv", + "unit_of_measurement": "tsüklid" + }, + "suggestions": { + "name": "Saadaolevad soovituslikud seaded" + }, + "pump_runs_today": { + "name": "Pump töötab (viimased 24 tundi)" + }, + "cycle_count": { + "name": "Tsüklite arv" + } + }, + "select": { + "program_select": { + "name": "Tsükli programm", + "state": { + "auto_detect": "Automaatne tuvastamine" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Sundlõpeta tsükkel" + }, + "pause_cycle": { + "name": "Peata tsükkel" + }, + "resume_cycle": { + "name": "Tsükli jätkamine" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Nõutav on kehtiv device_id." + }, + "cycle_id_required": { + "message": "Nõutav on kehtiv cycle_id." + }, + "profile_name_required": { + "message": "Nõutav on kehtiv profile_name." + }, + "device_not_found": { + "message": "Seadet ei leitud." + }, + "no_config_entry": { + "message": "Seadme konfiguratsioonikirjet ei leitud." + }, + "integration_not_loaded": { + "message": "Selle seadme jaoks pole integreerimist laaditud." + }, + "cycle_not_found_or_no_power": { + "message": "Tsüklit ei leitud või sellel puuduvad toiteandmed." + }, + "trim_failed_empty_window": { + "message": "Kärpimine ebaõnnestus – tsüklit ei leitud, toiteandmeid pole või tulemuseks olev aken on tühi." + }, + "trim_invalid_range": { + "message": "trim_end_s peab olema suurem kui trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/eu.json b/custom_components/ha_washdata/translations/eu.json new file mode 100644 index 0000000..26c5dcd --- /dev/null +++ b/custom_components/ha_washdata/translations/eu.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData konfigurazioa", + "description": "Konfiguratu zure garbigailua edo beste tresna bat.\n\nPotentzia-sentsorea behar da.\n\n**Ondoren, zure lehen profila sortu nahi duzun galdetuko zaizu.**", + "data": { + "name": "Gailuaren izena", + "device_type": "Gailu mota", + "power_sensor": "Potentzia-sentsorea", + "min_power": "Potentzia minimoaren atalasea (W)" + }, + "data_description": { + "name": "Gailu honen izen atsegina (adibidez, 'Garbigailua', 'Ontzi-garbigailua').", + "device_type": "Zein tresna mota da hau? Neurrira detektatzen eta etiketatzen laguntzen du.", + "power_sensor": "Zure entxufe adimendunaren denbora errealeko energia-kontsumoa (wattetan) jakinarazten duen sentsore-entitatea.", + "min_power": "Atalase horretatik gorako potentzia-irakurketak (wattetan) aparatua martxan dagoela adierazten du. Hasi 2W gailu gehienetarako." + } + }, + "first_profile": { + "title": "Sortu lehen profila", + "description": "**Zikloa** zure aparatuaren funtzionamendu osoa da (adibidez, arropa zama bat). **Profilak** motaren arabera taldekatzen ditu ziklo hauek (adibidez, 'Kotoia', 'Garbiketa azkarra'). Orain profil bat sortzeak integrazioaren iraupena berehala kalkulatzen laguntzen du.\n\nProfil hau eskuz hauta dezakezu gailuaren kontroletan automatikoki hautematen ez bada.\n\n**Utzi 'Profilaren izena' hutsik urrats hau saltatzeko.**", + "data": { + "profile_name": "Profilaren izena (aukerakoa)", + "manual_duration": "Iraupena estimatua (minutuak)" + } + } + }, + "error": { + "cannot_connect": "Ezin izan da konektatu", + "invalid_auth": "Autentifikazio baliogabea", + "unknown": "Ustekabeko errorea" + }, + "abort": { + "already_configured": "Gailua dagoeneko konfiguratuta dago" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Berrikusi Ikaskuntzaren Iritziak", + "settings": "Ezarpenak", + "notifications": "Jakinarazpenak", + "manage_cycles": "Kudeatu Zikloak", + "manage_profiles": "Kudeatu profilak", + "manage_phase_catalog": "Kudeatu Faseen Katalogoa", + "record_cycle": "Zikloa erregistratu (eskuz)", + "diagnostics": "Diagnostikoa eta mantentze-lanak" + } + }, + "apply_suggestions": { + "title": "Kopiatu iradokitako balioak", + "description": "Honek uneko iradokitako balioak gordetako ezarpenetan kopiatuko ditu. Hau behin-behineko ekintza da eta ez du gaitzen automatikoki gainidazketa.\n\nKopiatzeko iradokitako balioak:\n{suggested}", + "data": { + "confirm": "Berretsi kopiatzeko ekintza" + } + }, + "apply_suggestions_confirm": { + "title": "Berrikusi Iradokitako Aldaketak", + "description": "WashData-k aplikatu beharreko iradokitako balio(k) **{pending_count}** aurkitu du.\n\nAldaketak:\n{changes}\n\n✅ Markatu beheko laukia eta egin klik **Bidali** aldaketa hauek berehala aplikatzeko eta gordetzeko.\n❌ Utzi markarik gabe eta egin klik **Bidali** bertan behera uzteko eta atzera egiteko.", + "data": { + "confirm_apply_suggestions": "Aplikatu eta gorde aldaketa hauek" + } + }, + "settings": { + "title": "Ezarpenak", + "description": "{deprecation_warning}Sintonizatu detektatzeko atalaseak, ikaskuntza eta portaera aurreratua. Lehenetsiek ondo funtzionatzen dute konfigurazio gehienetarako.\n\nEskuragarri dauden iradokizun-ezarpenen kopurua: {suggestions_count}.\n0 baino handiagoa bada, ireki Ezarpen aurreratuak eta erabili Aplikatu iradokitako balioak gomendazioak berrikusteko.", + "data": { + "apply_suggestions": "Aplikatu iradokitako balioak", + "device_type": "Gailu mota", + "power_sensor": "Potentzia Sentsore Entitatea", + "min_power": "Gutxieneko potentzia (W)", + "off_delay": "Zikloaren amaierako atzerapena (k)", + "notify_actions": "Jakinarazpen Ekintzak", + "notify_people": "Atzeratu Entrega Pertsona hauek Etxera arte", + "notify_only_when_home": "Atzeratu jakinarazpenak hautatutako pertsona etxean egon arte", + "notify_fire_events": "Automatizazio-gertaerak abiarazi", + "notify_start_services": "Zikloaren hasiera - Jakinarazpen-helburuak", + "notify_finish_services": "Zikloaren amaiera - Jakinarazpen-helburuak", + "notify_live_services": "Zuzeneko aurrerapena - Jakinarazpen-helburuak", + "notify_before_end_minutes": "Osatu aurretiko jakinarazpena (amaitu baino minutu batzuk lehenago)", + "notify_title": "Jakinarazpenaren izenburua", + "notify_icon": "Jakinarazpenen ikonoa", + "notify_start_message": "Hasi mezu formatua", + "notify_finish_message": "Amaitu mezuen formatua", + "notify_pre_complete_message": "Osatu aurretiko mezu formatua", + "show_advanced": "Editatu ezarpen aurreratuak (Hurrengo urratsa)" + }, + "data_description": { + "apply_suggestions": "Markatu lauki hau ikaskuntza-algoritmoak iradokitako balioak formulariora kopiatzeko. Berrikusi gorde aurretik.\n\nIradokitako (ikaskuntzatik):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Hautatu tresna mota (garbigailua, lehorgailua, ontzi-garbigailua, kafe-makina, EV). Etiketa hau ziklo bakoitzean gordetzen da hobeto antolatzeko.", + "power_sensor": "Denbora errealeko potentziaren berri ematen duen sentsore-entitatea (wattetan). Hau edonoiz alda dezakezu datu historikoak galdu gabe; erabilgarria entxufe adimenduna ordezkatzen baduzu.", + "min_power": "Atalase horretatik gorako potentzia-irakurketak (wattetan) aparatua martxan dagoela adierazten du. Hasi 2W gailu gehienetarako.", + "off_delay": "Segundotan, leundutako potentzia geldialdiaren atalasearen azpitik egon behar da amaitutakoa markatzeko. Lehenetsia: 120s.", + "notify_actions": "Home Assistant aukerako ekintza-sekuentzia jakinarazpenetarako exekutatzeko (scriptak, jakinarazpena, TTS, etab.). Ezarrita badago, ekintzak abiaraztearen jakinarazpen-zerbitzuaren aurretik egingo dira.", + "notify_people": "Presentzia ateratzeko erabiltzen diren pertsona-entitateak. Gaituta dagoenean, jakinarazpenak bidaltzea atzeratzen da, gutxienez hautatutako pertsona bat etxean egon arte.", + "notify_only_when_home": "Gaituta badago, atzeratu jakinarazpenak gutxienez hautatutako pertsona bat etxean egon arte.", + "notify_fire_events": "Gaituta badago, piztu Home Assistant-eko gertaerak zikloaren hasiera eta amaierarako (ha_washdata_cycle_started eta ha_washdata_cycle_ended) automatizazioak gidatzeko.", + "notify_start_services": "Zerbitzu bati edo gehiagori jakinaraztea ziklo bat hasten denean abisatzeko. Utzi hutsik hasierako jakinarazpenak saltatzeko.", + "notify_finish_services": "Zerbitzu bati edo gehiagori jakinaraztea ziklo bat amaitzen denean edo amaitzen denean abisatzeko. Utzi hutsik amaierako jakinarazpenak saltatzeko.", + "notify_live_services": "Zerbitzu bati edo gehiagori jakinarazpena eman diezaiokete zuzeneko aurrerapen-eguneratzeak zikloa martxan dagoen bitartean (mugikorretarako aplikazio osagarria soilik). Utzi hutsik zuzeneko eguneraketak saltatzeko.", + "notify_before_end_minutes": "Zikloaren amaiera estimatua baino minutu batzuk lehenago jakinarazpen bat abiarazteko. Ezarri 0 desgaitzeko. Lehenetsia: 0.", + "notify_title": "Jakinarazpenetarako izenburu pertsonalizatua. {device} leku-marka onartzen du.", + "notify_icon": "Jakinarazpenetarako erabiltzeko ikonoa (adibidez, mdi:washing-machine). Utzi hutsik ikonorik gabe bidaltzeko.", + "notify_start_message": "Ziklo bat hasten denean bidalitako mezua. {device} leku-marka onartzen du.", + "notify_finish_message": "Ziklo bat amaitzean bidalitako mezua. {device}, {duration}, {program}, {energy_kwh}, {cost} leku-markak onartzen ditu.", + "notify_pre_complete_message": "Zikloa exekutatzen den bitartean errepikatzen diren zuzeneko aurrerapenen eguneratzeen testua. {device}, {minutes}, {program} leku-markak onartzen ditu." + } + }, + "notifications": { + "title": "Jakinarazpenak", + "description": "Konfiguratu jakinarazpen-kanalak, txantiloiak eta mugikorren zuzeneko aukerako aurrerapen eguneraketak.", + "data": { + "notify_actions": "Jakinarazpen Ekintzak", + "notify_people": "Atzeratu Entrega Pertsona hauek Etxera arte", + "notify_only_when_home": "Atzeratu jakinarazpenak hautatutako pertsona etxean egon arte", + "notify_fire_events": "Automatizazio-gertaerak abiarazi", + "notify_start_services": "Zikloaren hasiera - Jakinarazpen-helburuak", + "notify_finish_services": "Zikloaren amaiera - Jakinarazpen-helburuak", + "notify_live_services": "Zuzeneko aurrerapena - Jakinarazpen-helburuak", + "notify_before_end_minutes": "Osatu aurretiko jakinarazpena (amaitu baino minutu batzuk lehenago)", + "notify_live_interval_seconds": "Zuzeneko eguneratze-tartea (segundoak)", + "notify_live_overrun_percent": "Zuzeneko eguneraketa gainditze hobaria (%)", + "notify_live_chronometer": "Kronometroa atzerako kontaketa tenporizadorea", + "notify_title": "Jakinarazpenaren izenburua", + "notify_icon": "Jakinarazpenen ikonoa", + "notify_start_message": "Hasi mezu formatua", + "notify_finish_message": "Amaitu mezuen formatua", + "notify_pre_complete_message": "Osatu aurretiko mezu formatua", + "energy_price_entity": "Energiaren prezioa - Entitatea (aukerakoa)", + "energy_price_static": "Energiaren prezioa - Balio estatikoa (aukerakoa)", + "go_back": "Itzuli gorde gabe", + "notify_reminder_message": "Mezuaren formatua", + "notify_timeout_seconds": "Auto-desmuntatu ondoren (segundoak)", + "notify_channel": "Jakinarazpen-kanala (egoera/bizitza/irakurlea)", + "notify_finish_channel": "Jakinarazpen-kanala amaituta" + }, + "data_description": { + "go_back": "Markatu hau eta egin klik Bidali aukeran goiko aldaketak baztertzeko eta menu nagusira itzultzeko.", + "notify_actions": "Home Assistant aukerako ekintza-sekuentzia jakinarazpenetarako exekutatzeko (scriptak, jakinarazpena, TTS, etab.). Ezarrita badago, ekintzak abiaraztearen jakinarazpen-zerbitzuaren aurretik egingo dira.", + "notify_people": "Presentzia ateratzeko erabiltzen diren pertsona-entitateak. Gaituta dagoenean, jakinarazpenak bidaltzea atzeratzen da, gutxienez hautatutako pertsona bat etxean egon arte.", + "notify_only_when_home": "Gaituta badago, atzeratu jakinarazpenak gutxienez hautatutako pertsona bat etxean egon arte.", + "notify_fire_events": "Gaituta badago, piztu Home Assistant-eko gertaerak zikloaren hasiera eta amaierarako (ha_washdata_cycle_started eta ha_washdata_cycle_ended) automatizazioak gidatzeko.", + "notify_start_services": "Ziklo bat hasten denean abisatzeko zerbitzu bat edo gehiago jakinarazteko (adibidez, notify.mobile_app_my_phone). Utzi hutsik hasierako jakinarazpenak saltatzeko.", + "notify_finish_services": "Zerbitzu bati edo gehiagori jakinaraztea ziklo bat amaitzen denean edo amaitzen denean abisatzeko. Utzi hutsik amaierako jakinarazpenak saltatzeko.", + "notify_live_services": "Zerbitzu bati edo gehiagori jakinarazpena eman diezaiokete zuzeneko aurrerapen-eguneratzeak zikloa martxan dagoen bitartean (mugikorretarako aplikazio osagarria soilik). Utzi hutsik zuzeneko eguneraketak saltatzeko.", + "notify_before_end_minutes": "Zikloaren amaiera estimatua baino minutu batzuk lehenago jakinarazpen bat abiarazteko. Ezarri 0 desgaitzeko. Lehenetsia: 0.", + "notify_live_interval_seconds": "Aurrerapen-eguneratzeak zenbateko maiztasuna bidaltzen diren exekutatzen ari zaren bitartean. Balio baxuagoek erantzun handiagoa dute baina jakinarazpen gehiago kontsumitzen dute. Gutxienez 30 segundo behar dira; 30 s-tik beherako balioak automatikoki 30 s-ra biribiltzen dira.", + "notify_live_overrun_percent": "Segurtasun-marjina zenbatetsitako eguneratze-kopurutik gorako ziklo luzeak/gainekotan (adibidez, % 20ak 1,2 aldiz zenbatetsitako eguneratzeak onartzen ditu).", + "notify_live_chronometer": "Gaituta dagoenean, zuzeneko eguneratze bakoitzak kronometroko atzerako kontaketa bat du estimatutako amaiera-orduraino. Jakinarazpen-tenporizadorea automatikoki deskargatzen da gailuan eguneratzeen artean (Android-en aplikazio osagarria soilik).", + "notify_title": "Jakinarazpenetarako izenburu pertsonalizatua. {device} leku-marka onartzen du.", + "notify_icon": "Jakinarazpenetarako erabiltzeko ikonoa (adibidez, mdi:washing-machine). Utzi hutsik ikonorik gabe bidaltzeko.", + "notify_start_message": "Ziklo bat hasten denean bidalitako mezua. {device} leku-marka onartzen du.", + "notify_finish_message": "Ziklo bat amaitzean bidalitako mezua. {device}, {duration}, {program}, {energy_kwh}, {cost} leku-markak onartzen ditu.", + "energy_price_entity": "Hautatu uneko elektrizitatearen prezioari eusten dion zenbakizko entitate bat (adibidez, sentsorea.electricity_price, input_number.energy_rate). Ezarritakoan, {cost} leku-marka gaitu. Balio estatikoari lehentasuna ematen dio biak konfiguratuta badaude.", + "energy_price_static": "Elektrizitatearen prezio finkoa kWh bakoitzeko. Ezarritakoan, {cost} leku-marka gaitu. Ez ikusi egingo da goiko entitate bat konfiguratuta badago. Gehitu zure moneta ikurra mezu-txantiloian, adibidez. {cost} €.", + "notify_reminder_message": "Ordu bateko oroigarriaren testuak aurreikusitako amaiera baino minutu batzuk lehenago bidali zuen konfiguratutako kopurua. Goiko zuzeneko eguneraketa errepikatuetatik aldendu. {device}, {minutes}, {program} leku-markak onartzen ditu.", + "notify_timeout_seconds": "Automatikoki baztertu jakinarazpenak segundo askoren ondoren (lagunaren aplikazioaren 'denbora-muga' gisa aurrera). '0' balioarekin itxi arte. Lehenetsia: 0.", + "notify_channel": "Android aplikazio lagunaren jakinarazpen-kanala egoerarako, aurrerapen bizietarako eta abisuetarako. Kanal baten soinua eta garrantzia kanalaren aplikazioan konfiguratzen dira kanalaren izena erabiltzen den lehen aldian - WashData-k kanalaren izena bakarrik ezartzen du. Utzi hutsik aplikazioa lehenetsi gisa.", + "notify_finish_channel": "Android kanala bereizia amaitutako alertarako (abisuak eta garbitegiaren zain dagoen nagak ere erabiltzen dute) bere soinua izan dezan. Utzi hutsik goiko kanala berrerabiltzeko.", + "notify_pre_complete_message": "Zikloa exekutatzen den bitartean errepikatzen diren zuzeneko aurrerapenen eguneratzeen testua. {device}, {minutes}, {program} leku-markak onartzen ditu." + } + }, + "advanced_settings": { + "title": "Ezarpen aurreratuak", + "description": "Doitu detekzio-atalaseak, ikasketa eta portaera aurreratua. Lehenetsiek ondo funtzionatzen dute konfigurazio gehienetan.", + "sections": { + "suggestions_section": { + "name": "Iradokitako ezarpenak", + "description": "{suggestions_count} ikasitako iradokizun daude erabilgarri. Markatu Aplikatu iradokitako balioak gorde aurretik proposatutako aldaketak berrikusteko.", + "data": { + "apply_suggestions": "Aplikatu iradokitako balioak" + }, + "data_description": { + "apply_suggestions": "Markatu lauki hau ikaskuntza-algoritmoan iradokitako balioen berrikuspen-urrats bat irekitzeko. Ezer ez da automatikoki gordetzen.\n\nIradokitako (ikaskuntzatik):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekzioa eta potentzia-atalaseak", + "description": "Noiz jotzen den gailua PIZTUTA edo ITZALITA dagoela, energia-atalaseak eta egoera-trantsizioen denborak.", + "data": { + "start_duration_threshold": "Hasi errebotearen iraupena (s)", + "start_energy_threshold": "Hasi Energy Gate (Wh)", + "completion_min_seconds": "Osatzeko gutxieneko exekuzio-denbora (segundoak)", + "start_threshold_w": "Hasierako atalasea (W)", + "stop_threshold_w": "Gelditzeko atalasea (W)", + "end_energy_threshold": "Amaierako energia-atea (Wh)", + "running_dead_zone": "Running Dead Zone (segundoak)", + "end_repeat_count": "Amaitu Errepikatu Zenbaketa", + "min_off_gap": "Zikloen arteko gutxieneko tartea (k)", + "sampling_interval": "Laginketa-tartea (k)" + }, + "data_description": { + "start_duration_threshold": "Ez ikusi iraupen hori (segundo) baino laburragoak diren potentzia-puntu laburrak. Abiarazte-puntak iragazten ditu (adibidez, 60 W 2 segundotan) ziklo erreala hasi baino lehen. Lehenetsia: 5s.", + "start_energy_threshold": "Zikloak gutxienez energia hori (Wh) metatu behar du HASIERA fasean baieztatzeko. Lehenetsia: 0,005 Wh.", + "completion_min_seconds": "Hau baino laburragoak diren zikloak \"eten\" gisa markatuko dira, modu naturalean amaitu arren. Lehenetsia: 600s.", + "start_threshold_w": "Potentzia atalase honetatik GAINetik igo behar da ziklo bat HASTEKO. Ezarri zure egonean dagoen potentzia baino handiagoa. Lehenetsia: min_potentzia + 1 W.", + "stop_threshold_w": "Potentzia atalase honetatik BEHERA jaitsi behar da ziklo bat AMAITZEKO. Ezarri zeroren gainetik zaratak amaiera faltsuak abiarazteko. Lehenetsia: 0,6 * min_potentzia.", + "end_energy_threshold": "Zikloa amaitzen da azken Off-Delay leihoko energia balio honen (Wh) azpitik badago. Bukaera faltsuak eragozten ditu potentzia zerotik gertu aldatzen bada. Lehenetsia: 0,05 Wh.", + "running_dead_zone": "Zikloa hasi ondoren, jaramonik egin segundo horrenbesteko potentzia-jauziak, hasierako biraketa fasean amaiera faltsuak hautemateko saihesteko. Ezarri 0 desgaitzeko. Lehenetsia: 0s.", + "end_repeat_count": "Amaiera-baldintza (off_delay-ren atalasearen azpiko potentzia) jarraian bete behar den zikloa amaitu aurretik. Balio altuagoek amaiera faltsuak saihesten dituzte pausaldietan. Lehenetsia: 1.", + "min_off_gap": "Ziklo bat aurrekoa amaitzen denetik segundo horren barruan hasten bada, ziklo bakarrean BATUA izango dira.", + "sampling_interval": "Laginketa-tarte minimoa segundotan. Tarifa hori baino azkarrago eguneratzeak ez dira aintzat hartuko. Lehenetsia: 30 s (2 s garbigailuetarako, garbigailu-lehorgailuetarako eta ontzi-garbigailuetarako)." + } + }, + "matching_section": { + "name": "Profilen parekatzea eta ikasketa", + "description": "WashData-k martxan dauden zikloak ikasitako profilekin zenbateraino parekatzen dituen eta noiz eskatu behar duen iritzia.", + "data": { + "profile_match_min_duration_ratio": "Profilaren bat-etortzeen gutxieneko iraupen-erlazioa (0,1-1,0)", + "profile_match_interval": "Profilaren bat-etortze-tartea (segundoak)", + "profile_match_threshold": "Profila bat etortzeko atalasea", + "profile_unmatch_threshold": "Profila bat ez datorren atalasea", + "auto_label_confidence": "Etiketa automatikoko konfiantza (0-1)", + "learning_confidence": "Iritzia eskatzeko konfiantza (0-1)", + "suppress_feedback_notifications": "Ezabatu Iritzien jakinarazpenak", + "duration_tolerance": "Iraupen-tolerantzia (0-0,5)", + "smoothing_window": "Leuntzeko leihoa (laginak)", + "profile_duration_tolerance": "Profilaren bat-etortzearen iraupen-perdoia (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Profila parekatzeko gutxieneko iraupen-ratioa. Korrika-zikloak profilaren iraupenaren zati hori izan behar du gutxienez bat etortzeko. Behea = lehenago bat etortzea. Lehenetsia: 0,50 (%50).", + "profile_match_interval": "Zenbateko maiztasuna (segundotan) ziklo batean profila parekatzen saiatzea. Balio baxuagoek detekzio azkarragoa eskaintzen dute baina CPU gehiago erabiltzen dute. Lehenetsia: 300s (5 minutu).", + "profile_match_threshold": "DTW antzekotasun puntuazio minimoa (0,0-1,0) behar da profil bat parekotzat hartzeko. Handiagoa = parekatze zorrotzagoa, positibo faltsu gutxiago. Lehenetsia: 0,4.", + "profile_unmatch_threshold": "DTW antzekotasun puntuazioa (0,0-1,0) eta horren azpitik aurrez parekatuta dagoen profil bat baztertzen da. Bat-etortzearen atalasea baino baxuagoa izan behar da keinurik ez egiteko. Lehenetsia: 0,35.", + "auto_label_confidence": "Automatikoki etiketa ezazu zikloak konfiantza hori amaitzean (0,0-1,0).", + "learning_confidence": "Erabiltzaileen egiaztapena gertaera baten bidez eskatzeko gutxieneko konfiantza (0.0-1.0).", + "suppress_feedback_notifications": "Gaituta dagoenean, WashData-k barnean jarraipena egingo du eta iritzia eskatuko du, baina ez du zikloak berresteko eskatzen dizuten jakinarazpen iraunkorrik erakutsiko. Erabilgarria zikloak beti behar bezala hautematen direnean eta oharrak distraitzen dizutenean.", + "duration_tolerance": "Korrika batean zehar geratzen den denboraren kalkuluekiko tolerantzia (0,0-0,5 % 0-50 da).", + "smoothing_window": "Batez besteko leuntzeko erabilitako azken potentzia-irakurketa kopurua.", + "profile_duration_tolerance": "Profilen bat etortzeak iraupen osoaren bariantzarako erabiltzen duen tolerantzia (0,0-0,5). Portaera lehenetsia: % ±25." + } + }, + "timing_section": { + "name": "Denborak, mantentzea eta arazketa", + "description": "Watchdog-a, aurrerapenaren berrezarpena, auto-mantentzea eta debug entitateen erakustea.", + "data": { + "watchdog_interval": "Watchdog tartea (segundoak)", + "no_update_active_timeout": "Eguneratzerik gabeko denbora-muga (k)", + "progress_reset_delay": "Aurrerapena berrezarri atzerapena (segundoak)", + "auto_maintenance": "Gaitu Mantentze automatikoa", + "expose_debug_entities": "Erakutsi arazketa-entitateak", + "save_debug_traces": "Gorde arazketa arrastoak" + }, + "data_description": { + "watchdog_interval": "Zailatzaileen egiaztapenen arteko segundoak korrika egiten ari zaren bitartean. Lehenetsia: 5s. OHARRA: Ziurtatu sentsorearen eguneratze-tartea baino HANDIAGOA dela geldialdi faltsuak ekiditeko (adibidez, sentsorea 60 segundotan behin eguneratzen bada, erabili 65 segundo).", + "no_update_active_timeout": "Argitaratze-aldaketako sentsoreetarako: behartu bakarrik amaitu ziklo AKTIBO bat eguneratzerik iristen ez bada segundo askotan. Potentzia gutxiko osatzeak itzaltzeko atzerapena erabiltzen du oraindik.", + "progress_reset_delay": "Ziklo bat amaitu ondoren (% 100), itxaron segundo asko inaktiboen aurrerapena % 0ra berrezarri aurretik. Lehenetsia: 300s.", + "auto_maintenance": "Gaitu mantentze-lan automatikoa (konponketa laginak, batu zatiak).", + "expose_debug_entities": "Erakutsi sentsore aurreratuak (konfiantza, fasea, anbiguotasuna) arazketarako.", + "save_debug_traces": "Gorde sailkapen zehatza eta potentzia-aztarna datu historian (biltegiratze-erabilera areagotzen du)." + } + }, + "anti_wrinkle_section": { + "name": "Zimurren aurkako babesa (Lehorgailuak)", + "description": "Ez ikusi ziklo osteko danbor-biraketei ziklo mamuak saihesteko. Lehorgailua / garbigailu-lehorgailua soilik.", + "data": { + "anti_wrinkle_enabled": "Zimurren aurkako babesa (lehorgailua soilik)", + "anti_wrinkle_max_power": "Zimurren aurkako potentzia maximoa (W)", + "anti_wrinkle_max_duration": "Zimurren aurkako gehienezko iraupena (k)", + "anti_wrinkle_exit_power": "Zimurren aurkako irteera-potentzia (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ez ikusi potentzia baxuko danborraren biraketa laburrak ziklo bat amaitu ondoren, ziklo mamuak saihesteko (lehorgailua/garbigailua-lehorgailua soilik).", + "anti_wrinkle_max_power": "Potentzia horren edo beheragoko leherketak zimurren aurkako biraketa gisa tratatzen dira, ziklo berrien ordez. Zimurren aurkako babesa gaituta dagoenean bakarrik aplikatzen da.", + "anti_wrinkle_max_duration": "Leherketaren gehienezko luzera zimurren aurkako biraketa gisa tratatzeko. Zimurren aurkako babesa gaituta dagoenean bakarrik aplikatzen da.", + "anti_wrinkle_exit_power": "Potentzia maila horretatik behera jaisten bada, irten zimurren aurkako berehala (egia desaktibatuta). Zimurren aurkako babesa gaituta dagoenean bakarrik aplikatzen da." + } + }, + "delay_start_section": { + "name": "Atzeratutako hasieraren detekzioa", + "description": "Tratatu potentzia baxuko standby iraunkorra 'Hasteko zain' gisa, zikloa benetan hasi arte.", + "data": { + "delay_start_detect_enabled": "Gaitu Abiatze atzeratua detektatzeko", + "delay_confirm_seconds": "Atzeratutako hasiera: standby berrespen-denbora (s)", + "delay_timeout_hours": "Hasiera atzeratua: Gehienezko itxaron-denbora (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Gaituta dagoenean, potentzia baxuko drainatze-puntu labur bat eta jarraian egonean dagoen potentziaren ondoren abiarazte atzeratu gisa hautematen da. Egoera-sentsoreak 'Hasteko zain' erakusten du atzerapenean. Ez da programaren denborarik edo aurrerapenik egiten zikloa benetan hasi arte. Start_threshold_w drainatze-potentziaren gainetik ezarri behar da.", + "delay_confirm_seconds": "Potentziak Gelditzeko atala (W) eta Hasteko atala (W) artean egon behar du segundo kopuru honetan WashData 'Hasteko zain' egoeran sartu aurretik. Menuetan nabigatzean sortzen diren gailur laburrak iragazten ditu. Lehenetsia: 60 s.", + "delay_timeout_hours": "Segurtasun-denbora-muga: ordu asko igaro ondoren makina oraindik 'Hasteko zain' moduan badago, WashData desaktibatuta berrezartzen da. Lehenetsia: 8 h." + } + }, + "external_triggers_section": { + "name": "Kanpoko abiarazleak, atea eta pausa", + "description": "Aukerako kanpoko amaiera-abiarazle sentsorea, ate-sentsorea pausa/garbi detektatzeko, eta pausatzean potentzia eteten duen etengailua.", + "data": { + "external_end_trigger_enabled": "Gaitu kanpoko ziklo amaierako abiarazlea", + "external_end_trigger": "Kanpoko Ziklo amaierako sentsorea", + "external_end_trigger_inverted": "Alderantzikatu Trigger Logika (Trigger aktibatuta)", + "door_sensor_entity": "Ate-sentsorea", + "pause_cuts_power": "Moztu energia pausatzean", + "switch_entity": "Aldatu entitatea (Pausatu energia mozterako)", + "notify_unload_delay_minutes": "Garbitegia zain jakinarazpenaren atzerapena (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Gaitu kanpoko sentsore bitar baten jarraipena zikloa osatzea abiarazteko.", + "external_end_trigger": "Hautatu sentsore entitate bitar bat. Sentsore hau abiaraztean, uneko zikloa 'osatu' egoerarekin amaituko da.", + "external_end_trigger_inverted": "Lehenespenez, abiarazleak sentsorea pizten denean pizten da. Markatu lauki hau sentsorea itzaltzen denean pizteko.", + "door_sensor_entity": "Makinaren atearen aukerako sentsore bitarra (aktibatuta = irekita, itzalita = itxita). Ziklo aktibo batean atea irekitzen denean, WashData-k zikloa nahita pausatu dela baieztatuko du. Zikloa amaitu ondoren, atea oraindik itxita badago, egoera 'Garbitu'-ra aldatzen da atea ireki arte. Oharra: atea ixteak EZ du pausatutako ziklo bat automatikoki berresten; eskuz abiarazi behar da berrezartzea Zikloa berrekin batera botoiaren edo zerbitzuaren bidez.", + "pause_cuts_power": "Etenaldiaren zikloa botoiaren edo zerbitzuaren bidez pausatzen duzunean, desaktibatu konfiguratutako etengailu-entitatea ere. Gailu honetarako aldatzeko entitate bat konfiguratuta badago bakarrik izango du eragina.", + "switch_entity": "Etenaldian edo berriro hastean aldatzeko entitatea. Beharrezkoa da \"Moztu energia pausatzean\" gaituta dagoenean.", + "notify_unload_delay_minutes": "Bidali jakinarazpena amaierako jakinarazpen-kanalaren bidez, zikloa amaitu eta atea minutu askotan ireki ez denean (ate-sentsorea behar da). Ezarri 0 desgaitzeko. Lehenetsia: 60 min." + } + }, + "pump_section": { + "name": "Ponparen monitorea", + "description": "Ponpa gailu motarentzat bakarrik. Gertaera bat jaurtitzen du ponparen zikloa iraupen hau baino gehiago luzatzen bada.", + "data": { + "pump_stuck_duration": "Ponpa trabatuta dagoen alerta-atalasea (k) (ponpa soilik)" + }, + "data_description": { + "pump_stuck_duration": "Segundo horren ondoren ponpaketa-ziklo bat martxan badago, ha_washdata_pump_stuck gertaera bat piztuko da behin. Erabili hau trabatuta dagoen motor bat detektatzeko (putzu-ponparen funtzionamendu arrunta 60 s baino gutxiagokoa da). Lehenetsia: 1800 s (30 min). Ponpa-gailu mota soilik." + } + }, + "device_link_section": { + "name": "Gailuaren esteka", + "description": "Aukeran, konektatu WashData gailu hau lehendik dagoen Home Assistant gailu batera (adibidez, entxufe azkarra edo aplikazioa bera). Orduan, WashData gailua gailu hori \"Konektatuta\" gisa erakusten da. Utzi hutsik WashData gailu bakar gisa mantentzeko.", + "data": { + "linked_device": "Konektatutako gailua" + }, + "data_description": { + "linked_device": "Hautatu gailua WashData konektatzeko. Honek \"Konektatutako bidez\" erreferentzia gehitzen du gailuen erregistroan; WashData-k bere gailu-txartela eta entitateak gordetzen ditu. Garbitu hautapena esteka kentzeko." + } + } + } + }, + "diagnostics": { + "title": "Diagnostikoa eta mantentze-lanak", + "description": "Exekutatu mantentze-ekintzak, esate baterako, zatitutako zikloak bateratzea edo gordetako datuak migratzea.\n\n**Biltegiratze erabilera**\n\n- Fitxategiaren tamaina: {file_size_kb} KB\n- Zikloak: {cycle_count}\n- Profilak: {profile_count}\n- Araztu arrastoak: {debug_count}", + "menu_options": { + "reprocess_history": "Mantentze-lanak: birprozesatu eta optimizatu datuak", + "clear_debug_data": "Garbitu arazketa-datuak (askatu espazioa)", + "wipe_history": "Garbitu gailu honen datu GUZTIAK (itzulezina)", + "export_import": "Esportatu/Inportatu JSON ezarpenekin (kopiatu/itsatsi)", + "menu_back": "← Atzera" + } + }, + "clear_debug_data": { + "title": "Garbitu arazketa-datuak", + "description": "Ziur gordetako arazketa-arrasto guztiak ezabatu nahi dituzula? Honek lekua askatuko du, baina iraganeko zikloetako sailkapenaren informazio zehatza kenduko du." + }, + "manage_cycles": { + "title": "Kudeatu Zikloak", + "description": "Azken zikloak:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-etiketatu Ziklo Zaharrak", + "select_cycle_to_label": "Etiketatu ziklo espezifiko bat", + "select_cycle_to_delete": "Ezabatu Zikloa", + "interactive_editor": "Batu/Zatitu Editore Interaktiboa", + "trim_cycle_select": "Moztu Zikloaren Datuak", + "menu_back": "← Atzera" + } + }, + "manage_cycles_empty": { + "title": "Kudeatu Zikloak", + "description": "Oraindik ez dago ziklorik erregistratu.", + "menu_options": { + "auto_label_cycles": "Auto-etiketatu Ziklo Zaharrak", + "menu_back": "← Atzera" + } + }, + "manage_profiles": { + "title": "Kudeatu profilak", + "description": "Profilaren laburpena:\n{profile_summary}", + "menu_options": { + "create_profile": "Sortu profil berria", + "edit_profile": "Editatu/Aldatu izena profila", + "delete_profile_select": "Ezabatu profila", + "profile_stats": "Profilen Estatistikak", + "cleanup_profile": "Garbitu historia - Grafikoa eta ezabatu", + "assign_profile_phases_select": "Esleitu fase-barrutiak", + "menu_back": "← Atzera" + } + }, + "manage_profiles_empty": { + "title": "Kudeatu profilak", + "description": "Ez dago profilik sortu oraindik.", + "menu_options": { + "create_profile": "Sortu profil berria", + "menu_back": "← Atzera" + } + }, + "manage_phase_catalog": { + "title": "Kudeatu Faseen Katalogoa", + "description": "Faseen katalogoa (gailu honen lehenetsiak + fase pertsonalizatuak):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Sortu Fase Berria", + "phase_catalog_edit_select": "Editatu Fasea", + "phase_catalog_delete": "Ezabatu Fasea", + "menu_back": "← Atzera" + } + }, + "phase_catalog_create": { + "title": "Sortu Fase pertsonalizatua", + "description": "Sortu fase-izen eta deskribapen pertsonalizatu bat eta aukeratu zein gailu motari aplikatzen zaion.", + "data": { + "target_device_type": "Helburuko gailu mota", + "phase_name": "Fasearen izena", + "phase_description": "Fasearen Deskribapena" + } + }, + "phase_catalog_edit_select": { + "title": "Editatu Fase pertsonalizatua", + "description": "Hautatu editatzeko fase pertsonalizatu bat.", + "data": { + "phase_name": "Fase pertsonalizatua" + } + }, + "phase_catalog_edit": { + "title": "Editatu Fase pertsonalizatua", + "description": "Edizio fasea: {phase_name}", + "data": { + "phase_name": "Fasearen izena", + "phase_description": "Fasearen Deskribapena" + } + }, + "phase_catalog_delete": { + "title": "Ezabatu Fase pertsonalizatua", + "description": "Hautatu ezabatzeko fase pertsonalizatu bat. Fase hau erabiliz esleitutako barrutiak kenduko dira.", + "data": { + "phase_name": "Fase pertsonalizatua" + } + }, + "assign_profile_phases": { + "title": "Esleitu fase-barrutiak", + "description": "Profilaren fasearen aurrebista: **{profile_name}**\n\n{timeline_svg}\n\n**Oraingo barrutiak:**\n{current_ranges}\n\nErabili beheko ekintzak barrutiak gehitzeko, editatzeko, ezabatzeko edo gordetzeko.", + "menu_options": { + "assign_profile_phases_add": "Gehitu Fase Barrutia", + "assign_profile_phases_edit_select": "Editatu fase-barrutia", + "assign_profile_phases_delete": "Ezabatu fase-barrutia", + "phase_ranges_clear": "Garbitu barruti guztiak", + "assign_profile_phases_auto_detect": "Auto-detektatzeko faseak", + "phase_ranges_save": "Gorde eta Itzuli", + "menu_back": "← Atzera" + } + }, + "assign_profile_phases_add": { + "title": "Gehitu Fase Barrutia", + "description": "Gehitu fase-barruti bat uneko zirriborroari.", + "data": { + "phase_name": "Fasea", + "start_min": "Hasierako minutua", + "end_min": "Amaiera Minutua" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editatu fase-barrutia", + "description": "Hautatu editatzeko barruti bat.", + "data": { + "range_index": "Fase barrutia" + } + }, + "assign_profile_phases_edit": { + "title": "Editatu fase-barrutia", + "description": "Eguneratu hautatutako fase-barrutia.", + "data": { + "phase_name": "Fasea", + "start_min": "Hasierako minutua", + "end_min": "Amaiera Minutua" + } + }, + "assign_profile_phases_delete": { + "title": "Ezabatu fase-barrutia", + "description": "Hautatu zirriborrotik kentzeko barrutia.", + "data": { + "range_index": "Fase barrutia" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detektaturiko faseak", + "description": "Profila: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(k) automatikoki detektatu dira.** Aukeratu ekintza bat behean.", + "data": { + "action": "Ekintza" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Izena detektaturiko faseak", + "description": "Profila: **{profile_name}**\n\n**{detected_count} fase(k) detektatu dira:**\n{ranges_summary}\n\nIzen bat eman fase bakoitzari." + }, + "assign_profile_phases_select": { + "title": "Esleitu fase-barrutiak", + "description": "Hautatu profil bat fase-barrutiak konfiguratzeko.", + "data": { + "profile": "Profila" + } + }, + "profile_stats": { + "title": "Profilen Estatistikak", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Sortu profil berria", + "description": "Sortu profil berri bat eskuz edo iraganeko ziklo batetik.\n\nProfilaren izenaren adibideak: 'Delikatuak', 'Erresistentzia gogorra', 'Garbiketa azkarra'", + "data": { + "profile_name": "Profilaren izena", + "reference_cycle": "Erreferentzia Zikloa (aukerakoa)", + "manual_duration": "Eskuzko iraupena (minutuak)" + } + }, + "edit_profile": { + "title": "Editatu profila", + "description": "Hautatu izena aldatzeko profil bat", + "data": { + "profile": "Profila" + } + }, + "rename_profile": { + "title": "Editatu profilaren xehetasunak", + "description": "Uneko profila: {current_name}", + "data": { + "new_name": "Profilaren izena", + "manual_duration": "Eskuzko iraupena (minutuak)" + } + }, + "delete_profile_select": { + "title": "Ezabatu profila", + "description": "Hautatu ezabatzeko profil bat", + "data": { + "profile": "Profila" + } + }, + "delete_profile_confirm": { + "title": "Berretsi ezabatu profila", + "description": "⚠️ Honek betiko ezabatuko du profila: {profile_name}", + "data": { + "unlabel_cycles": "Kendu etiketa zikloetatik profil hau erabiliz" + } + }, + "auto_label_cycles": { + "title": "Auto-etiketatu Ziklo Zaharrak", + "description": "Guztira {total_count} ziklo aurkitu dira. Profilak: {profiles}", + "data": { + "confidence_threshold": "Gutxieneko konfiantza (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Hautatu Zikloa etiketatzera", + "description": "✓ = amaitu, ⚠ = berriro hasi, ✗ = eten", + "data": { + "cycle_id": "Zikloa" + } + }, + "select_cycle_to_delete": { + "title": "Ezabatu Zikloa", + "description": "⚠️ Honek betiko ezabatuko du hautatutako zikloa", + "data": { + "cycle_id": "Ezabatzeko zikloa" + } + }, + "label_cycle": { + "title": "Etiketa Zikloa", + "description": "{cycle_info}", + "data": { + "profile_name": "Profila", + "new_profile_name": "Profilaren izen berria (sortuz gero)" + } + }, + "post_process": { + "title": "Prozesuaren historia (Bateatu/Zatitu)", + "description": "Garbitu zikloaren historia zatitutako exekuzioak batuz eta gaizki bateratutako zikloak denbora-leiho batean banatuz. Idatzi atzera begiratzeko ordu kopurua (erabili 999999 guztientzako).", + "data": { + "time_range": "Atzera begiratzeko leihoa (orduak)", + "gap_seconds": "Batu/Zatitu hutsunea (segundoak)" + }, + "data_description": { + "time_range": "Ziklo zatikatuak batzeko eskaneatzeko ordu kopurua. Erabili 999999 datu historiko guztiak prozesatzeko.", + "gap_seconds": "Banaketa eta batzea erabakitzeko atalasea. Hau baino LABURRATZEKO hutsuneak batzen dira. Hau baino LUZEAGOKO hutsuneak banatuta daude. Jaitsi hau gaizki batu den ziklo batean zatiketa bat behartzeko." + } + }, + "cleanup_profile": { + "title": "Garbitu historia - Hautatu profila", + "description": "Hautatu bistaratzeko profil bat. Honen bidez, grafiko bat sortuko da profil honen iraganeko ziklo guztiak erakusten dituena, kanpokoak identifikatzen laguntzeko.", + "data": { + "profile": "Profila" + } + }, + "cleanup_select": { + "title": "Garbitu historia - Grafikoa eta ezabatu", + "description": "![Grafikoa]({graph_url})\n\n**{profile_name}** bistaratzen\n\nGrafikoak ziklo indibidualak koloretako lerro gisa erakusten ditu. Identifikatu kanpokoak (taldetik urrun dauden lerroak) eta haien kolorea bat etortzea beheko zerrendan ezabatzeko.\n\n**Hautatu BEHIKO ezabatzeko zikloak:**", + "data": { + "cycles_to_delete": "Ezabatu beharreko zikloak (ikusi bat datozen koloreak)" + } + }, + "interactive_editor": { + "title": "Batu/Zatitu Editore Interaktiboa", + "description": "Hautatu eskuzko ekintza bat zure zikloaren historian egiteko.", + "menu_options": { + "editor_split": "Ziklo bat zatitu (aurkitu hutsuneak)", + "editor_merge": "Bateratu zikloak (Batu zatiak)", + "editor_delete": "Ezabatu zikloa(k)", + "menu_back": "← Atzera" + } + }, + "editor_select": { + "title": "Hautatu Zikloak", + "description": "Hautatu prozesatu beharreko zikloa(k).\n\n{info_text}", + "data": { + "selected_cycles": "Zikloak" + } + }, + "editor_configure": { + "title": "Konfiguratu eta Aplikatu", + "description": "{preview_md}", + "data": { + "confirm_action": "Ekintza", + "merged_profile": "Sortutako profila", + "new_profile_name": "Profilaren izen berria", + "confirm_commit": "Bai, ekintza hau berresten dut", + "segment_0_profile": "1. segmentuaren profila", + "segment_1_profile": "2. segmentuaren profila", + "segment_2_profile": "3. segmentuaren profila", + "segment_3_profile": "4. segmentuaren profila", + "segment_4_profile": "5. segmentuaren profila", + "segment_5_profile": "6. segmentuaren profila", + "segment_6_profile": "7. segmentuaren profila", + "segment_7_profile": "8. segmentuaren profila", + "segment_8_profile": "9. segmentua profila", + "segment_9_profile": "10. segmentuaren profila" + } + }, + "editor_split_params": { + "title": "Zatitu konfigurazioa", + "description": "Doitu ziklo hau zatitzeko sentikortasuna. Hau baino luzeagoa den hutsuneak zatiketa eragingo du.", + "data": { + "split_mode": "Zatitze metodoa" + } + }, + "editor_split_auto_params": { + "title": "Zatiketa automatikoaren konfigurazioa", + "description": "Doitu ziklo hau zatitzeko sentikortasuna. Hori baino luzeagoa den geldialdi-tarte orok zatiketa eragingo du.", + "data": { + "min_gap_seconds": "Zatiketa-tartearen atalasea (segundoak)" + } + }, + "editor_split_manual_params": { + "title": "Eskuzko zatiketa-denbora markak", + "description": "{preview_md}Zikloaren leihoa: **{cycle_start_wallclock} → {cycle_end_wallclock}** {cycle_date} egunean.\n\nSartu zatiketa-denbora marka bat edo gehiago (`HH:MM` edo `HH:MM:SS`), bat lerro bakoitzean. Zikloa denbora marka bakoitzean ebakiko da — N denbora markek N+1 segmentu sortzen dituzte.", + "data": { + "split_timestamps": "Zatiketa-denbora zigiluak" + } + }, + "reprocess_history": { + "title": "Mantentze-lanak: birprozesatu eta optimizatu datuak", + "description": "Datu historikoen garbiketa sakona egiten du:\n1. Zero potentziako irakurketak mozten ditu (isiltasuna).\n2. Zikloaren denbora/iraupena konpontzen du.\n3. Eredu estatistiko guztiak (kartazalak) birkalkulatzen ditu.\n\n**Exekutatu hau datu-arazoak konpontzeko edo algoritmo berriak aplikatzeko.**" + }, + "wipe_history": { + "title": "Garbitu historia (probak soilik)", + "description": "⚠️ Honek betiko ezabatuko ditu gailu honen gordetako ziklo eta profil GUZTIAK. Hau ezin da desegin!" + }, + "export_import": { + "title": "Esportatu/Inportatu JSON", + "description": "Kopiatu/itsatsi gailu honen zikloak, profilak ETA ezarpenak. Esportatu behean edo itsatsi JSON inportatzeko.", + "data": { + "mode": "Ekintza", + "json_payload": "Osatu JSON konfigurazioa" + }, + "data_description": { + "mode": "Aukeratu Esportatu uneko datuak eta ezarpenak kopiatzeko, edo Inportatu esportatutako datuak beste WashData gailu batetik itsasteko.", + "json_payload": "Esportatzeko: kopiatu JSON hau (zikloak, profilak eta doitutako ezarpen guztiak barne). Inportatzeko: itsatsi WashData-tik esportatutako JSON." + } + }, + "record_cycle": { + "title": "Erregistro Zikloa", + "description": "Grabatu eskuz ziklo bat interferentziarik gabe profil garbi bat sortzeko.\n\nEgoera: **{status}**\nIraupena: {duration} s\nLaginak: {samples}", + "menu_options": { + "record_refresh": "Freskatu egoera", + "record_stop": "Gelditu grabaketa (Gorde eta prozesatu)", + "record_start": "Hasi Grabaketa Berria", + "record_process": "Azken grabaketa prozesatu (moztu eta gorde)", + "record_discard": "Baztertu azken grabaketa", + "menu_back": "← Atzera" + } + }, + "record_process": { + "title": "Prozesuaren Grabaketa", + "description": "![Grafikoa]({graph_url})\n\n**Grafikoak grabaketa gordina erakusten du.** Urdina = Mantendu, Gorria = Proposatutako mozketa.\n* Grafikoa estatikoa da eta ez da inprimaki-aldaketekin eguneratzen.*\n\nGrabaketa gelditu da. Mozketak detektatutako laginketa-tasarekin (~{sampling_rate} s) lerrokatzen dira.\n\nIraupena gordina: {duration} s\nLaginak: {samples}", + "data": { + "head_trim": "Moztu hasiera (segundoak)", + "tail_trim": "Moztu amaiera (segundoak)", + "save_mode": "Gorde helmuga", + "profile_name": "Profilaren izena" + } + }, + "learning_feedbacks": { + "title": "Ikasteko Iritziak", + "description": "Zain dauden iritziak: {count}\n\n{pending_table}\n\nHautatu ziklo berrikusteko eskaera bat prozesatzeko.", + "menu_options": { + "learning_feedbacks_pick": "Berrikusi zain dagoen iritzi bat", + "learning_feedbacks_dismiss_all": "Baztertu zain dauden iritzi guztiak", + "menu_back": "← Atzera" + } + }, + "learning_feedbacks_pick": { + "title": "Berrikusteko iritziak aukeratu", + "description": "Irekitzeko zikloaren berrikuspen-eskaera bat hautatu.", + "data": { + "selected_feedback": "Berrikusteko zikloa hautatu" + } + }, + "learning_feedbacks_empty": { + "title": "Ikasteko Iritziak", + "description": "Ez da aurkitu zain dagoen iritzi-eskaerarik.", + "menu_options": { + "menu_back": "← Atzera" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Zain dauden iritzi guztiak baztertu", + "description": "**{count}** zain dauden iritzi-eskaera baztertzeko zorian zaude. Baztertutako zikloak zure historian geratuko dira, baina ez dute ikaskuntza-seinale berririk emango. Ezin da hau desegin.\n\n✅ Markatu beheko laukia eta egin klik **Bidali** guztiak baztertzeko.\n❌ Utzi markarik gabe eta egin klik **Bidali** bertan behera uzteko eta atzera egiteko.", + "data": { + "confirm_dismiss_all": "Berretsi: baztertu zain dauden iritzi-eskaera guztiak" + } + }, + "resolve_feedback": { + "title": "Ebatzi Iritzia", + "description": "{comparison_img}{candidates_table}\n**Hautsitako profila**: {detected_profile} (% {confidence_pct})\n**Gutxi gorabeherako iraupena**: {est_duration_min} min\n**Egiazko iraupena**: {act_duration_min} min\n\n__Aukeratu beheko ekintza bat:__", + "data": { + "action": "Zer egin nahiko zenuke?", + "corrected_profile": "Programa zuzena (zuzentzen bada)", + "corrected_duration": "Zuzena iraupena segundutan (zuzentzen bada)" + } + }, + "trim_cycle_select": { + "title": "Moztu Zikloa - Hautatu Zikloa", + "description": "Hautatu mozteko ziklo bat. ✓ = amaitu, ⚠ = berriro hasi, ✗ = eten", + "data": { + "cycle_id": "Zikloa" + } + }, + "trim_cycle": { + "title": "Moztu Zikloa", + "description": "Uneko leihoa: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Ekintza" + } + }, + "trim_cycle_start": { + "title": "Ezarri Moztu Hasiera", + "description": "Zikloaren leihoa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nUneko hasiera: **{current_wallclock}** (zikloaren hasieratik {current_offset_min} min)\n\nAukeratu hasiera-ordu berria beheko erloju-hautatzailea erabiliz.", + "data": { + "trim_start_time": "Hasiera ordu berria", + "trim_start_min": "Irteera berria (zikloaren hasieratik minutu batzuk)" + } + }, + "trim_cycle_end": { + "title": "Ezarri Moztu Amaiera", + "description": "Zikloaren leihoa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nUneko amaiera: **{current_wallclock}** (zikloaren hasieratik {current_offset_min} min)\n\nAukeratu amaiera ordu berri bat beheko erloju-hautatzailea erabiliz.", + "data": { + "trim_end_time": "Amaiera ordu berria", + "trim_end_min": "Amaiera berria (zikloaren hasieratik minutu batzuk)" + } + } + }, + "error": { + "import_failed": "Ezin izan da inportatu. Mesedez, itsatsi baliozko WashData esportazio JSON bat.", + "select_exactly_one": "Hautatu zehazki ziklo bat ekintza honetarako.", + "select_at_least_one": "Hautatu gutxienez ziklo bat ekintza honetarako.", + "select_at_least_two": "Hautatu gutxienez bi ziklo ekintza honetarako.", + "profile_exists": "Badago izen hau duen profil bat", + "rename_failed": "Ezin izan da profilaren izena aldatu. Baliteke profila ez egotea edo izen berria dagoeneko hartua izatea.", + "assignment_failed": "Ezin izan zaio profila esleitu zikloari", + "duplicate_phase": "Izen hau duen fase bat badago jada.", + "phase_not_found": "Hautatutako fasea ez da aurkitu.", + "invalid_phase_name": "Fasearen izena ezin da hutsik egon.", + "phase_name_too_long": "Fasearen izena luzeegia da.", + "invalid_phase_range": "Fase-barrutia baliogabea da. Amaierak hasiera baino handiagoa izan behar du.", + "invalid_phase_timestamp": "Denbora-zigilua baliogabea da. Erabili YYYY-MM-DD HH:MM edo HH:MM formatua.", + "overlapping_phase_ranges": "Fase barrutiak gainjartzen dira. Doitu barrutiak eta saiatu berriro.", + "incomplete_phase_row": "Fase errenkada bakoitzak fase bat gehi hasiera/amaiera balio osoak sartu behar ditu hautatutako modurako.", + "timestamp_mode_cycle_required": "Mesedez, hautatu iturburu-ziklo bat denbora-zigilu modua erabiltzean.", + "no_phase_ranges": "Oraindik ez dago fase-barrutirik erabilgarri ekintza horretarako.", + "feedback_notification_title": "WashData: Egiaztatu Zikloa ({device})", + "feedback_notification_message": "**Gailua**: {device}\n**Programa**: {program} (% {confidence} konfiantza)\n**Ordua**: {time}\n\nWashData-k zure laguntza behar du hautemandako ziklo hau egiaztatzeko.\n\nMesedez, joan **Ezarpenak > Gailuak eta zerbitzuak > WashData > Konfiguratu > Ikaskuntza-iritziak** atalera emaitza hau berresteko edo zuzentzeko.", + "suggestions_ready_notification_title": "WashData: iradokitako ezarpenak prest ({device})", + "suggestions_ready_notification_message": "**Iradokitako ezarpenak** sentsoreak **{count}** gomendio akziogarriak ematen ditu orain.\n\nHoriek berrikusteko eta aplikatzeko: **Ezarpenak > Gailuak eta zerbitzuak > WashData > Konfiguratu > Ezarpen aurreratuak > Aplikatu iradokitako balioak**.\n\nIradokizunak aukerakoak dira eta gorde aurretik berrikusteko erakusten dira.", + "trim_range_invalid": "Hasiera amaiera baino lehen izan behar da. Mesedez, egokitu zure mozketa-puntuak.", + "trim_failed": "Moztu huts egin du. Baliteke zikloa ez egotea edo leihoa hutsik egotea.", + "invalid_split_timestamp": "Denbora-zigilua baliogabea da edo zikloaren leihoaz kanpo dago. Erabili HH:MM edo HH:MM:SS zikloaren leihoaren barruan.", + "no_split_segments_found": "Ezin izan da baliozko segmenturik sortu denbora-zigilu hauetatik. Ziurtatu denbora-zigilu bakoitza zikloaren leihoaren barruan dagoela eta segmentuek gutxienez minutu 1 dutela.", + "auto_tune_suggestion": "{device_type} {device_title} mamu-zikloak detektatu ditu. Iradokitako potentzia minimoaren aldaketa: {current_min}W -> {new_min}W (ez da automatikoki aplikatu).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} mamu-zikloak detektatu ditu. Iradokitako potentzia minimoaren aldaketa: {current_min}W -> {new_min}W (ez da automatikoki aplikatu)." + }, + "abort": { + "no_cycles_found": "Ez da ziklorik aurkitu", + "no_split_segments_found": "Ez da segmentu zatigarririk aurkitu hautatutako zikloan.", + "cycle_not_found": "Ezin izan da hautatutako zikloa kargatu.", + "no_power_data": "Ez dago potentzia-aztarna daturik aukeratutako ziklorako.", + "no_profiles_found": "Ez da profilik aurkitu. Sortu profil bat lehenik.", + "no_profiles_for_matching": "Ez dago profilik bat etortzeko erabilgarri. Sortu profilak lehenik.", + "no_unlabeled_cycles": "Ziklo guztiak dagoeneko etiketatuta daude", + "no_suggestions": "Oraindik ez dago iradokitako baliorik eskuragarri. Exekutatu ziklo batzuk eta saiatu berriro.", + "no_predictions": "Iragarpenik ez", + "no_custom_phases": "Ez da fase pertsonalizaturik aurkitu.", + "reprocess_success": "Behar bezala birprozesatu dira {count} ziklo.", + "debug_data_cleared": "Behar bezala garbitu dira {count} zikloetako arazketa-datuak." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Zikloaren Hasiera", + "cycle_finish": "Zikloaren Amaiera", + "cycle_live": "Zuzeneko Aurrerapena" + } + }, + "common_text": { + "options": { + "unlabeled": "(Etiketarik gabe)", + "create_new_profile": "Sortu profil berria", + "remove_label": "Kendu etiketa", + "no_reference_cycle": "(Ez dago erreferentzia-ziklorik)", + "all_device_types": "Gailu mota guztiak", + "current_suffix": "(egungoa)", + "editor_select_info": "Hautatu 1 ziklo zatitzeko, edo 2 ziklo baino gehiago batzeko.", + "editor_select_info_split": "Hautatu zatitzeko 1 ziklo.", + "editor_select_info_merge": "Hautatu bateratzeko 2+ ziklo.", + "editor_select_info_delete": "Hautatu ezabatzeko 1+ ziklo.", + "no_cycles_recorded": "Oraindik ez dago ziklorik erregistratu.", + "profile_summary_line": "- **{name}**: {count} ziklo, {avg} min batez beste", + "no_profiles_created": "Ez dago profilik sortu oraindik.", + "phase_preview": "Fase Aurrebista", + "phase_preview_no_curve": "Profilaren batez besteko kurba ez dago eskuragarri oraindik. Exekutatu/etiketatu ziklo gehiago profil honetarako.", + "average_power_curve": "Batez besteko potentzia-kurba", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ziklo, ~{duration} min batez beste)", + "profile_option_short_fmt": "{name} ({count} ziklo, ~{duration} min)", + "deleted_cycles_from_profile": "{count} ziklo ezabatu dira {profile}tik.", + "not_enough_data_graph": "Ez dago datu nahikorik grafikoa sortzeko.", + "no_profiles_found": "Ez da profilik aurkitu.", + "cycle_info_fmt": "Zikloa: {start}, {duration} min, Etiketa: {label}", + "top_candidates_header": "### Hautagai nagusiak", + "tbl_profile": "Profila", + "tbl_confidence": "Konfiantza", + "tbl_correlation": "Korrelazioa", + "tbl_duration_match": "Iraupena bat-etortzea", + "detected_profile": "Detektaturiko profila", + "estimated_duration": "Iraupena estimatua", + "actual_duration": "Benetako Iraupena", + "choose_action": "__Aukeratu beheko ekintza bat:__", + "tbl_count": "Kopurua", + "tbl_avg": "Batez beste", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (batez bestekoa)", + "tbl_energy_total": "Energia (guztira)", + "tbl_consistency": "Koherentzia", + "tbl_last_run": "Azken exekuzioa", + "graph_legend_title": "Kondaira grafikoa", + "graph_legend_body": "Banda urdinak behatutako potentzia minimoa eta maximoa adierazten du. Lerroak batez besteko potentzia-kurba erakusten du.", + "recording_preview": "Grabazioaren aurrebista", + "trim_start": "Moztu Hasi", + "trim_end": "Moztu amaiera", + "split_preview_title": "Zatitutako aurrebista", + "split_preview_found_fmt": "{count} segmentu aurkitu dira.", + "split_preview_confirm_fmt": "Sakatu Berretsi ziklo hau {count} ziklo bereizietan banatzeko.", + "merge_preview_title": "Bateratu aurrebista", + "merge_preview_joining_fmt": "{count} ziklo batzen. Hutsuneak 0W-ko irakurketekin beteko dira.", + "editor_delete_preview_title": "Ezabaketaren aurrebista", + "editor_delete_preview_intro": "Hautatutako zikloak behin betiko ezabatuko dira:", + "editor_delete_preview_confirm": "Egin klik Berretsi aukeran ziklo-erregistro hauek betiko ezabatzeko.", + "no_power_preview": "*Ez dago energia-daturik eskuragarri aurrebistarako.*", + "profile_comparison": "Profilen Konparaketa", + "actual_cycle_label": "Ziklo hau (benetakoa)", + "storage_usage_header": "Biltegiratze erabilera", + "storage_file_size": "Fitxategiaren tamaina", + "storage_cycles": "Zikloak", + "storage_profiles": "Profilak", + "storage_debug_traces": "Araztu arrastoak", + "overview_suffix": "Ikuspegi orokorra", + "phase_preview_for_profile": "Profilaren fasearen aurrebista", + "current_ranges_header": "Egungo barrutiak", + "assign_phases_help": "Erabili beheko ekintzak barrutiak gehitzeko, editatzeko, ezabatzeko edo gordetzeko.", + "profile_summary_header": "Profilaren laburpena", + "recent_cycles_header": "Azken zikloak", + "trim_cycle_preview_title": "Zikloaren mozketaren aurrebista", + "trim_cycle_preview_no_data": "Ez dago energia-daturik eskuragarri ziklo honetarako.", + "trim_cycle_preview_kept_suffix": "mantendu", + "table_program": "Programa", + "table_when": "Noiz", + "table_length": "Iraupena", + "table_match": "Bat-etortzea", + "table_profile": "Profila", + "table_cycles": "Zikloak", + "table_avg_length": "Batez best. iraupena", + "table_last_run": "Azken exekuzioa", + "table_avg_energy": "Batez best. energia", + "table_detected_program": "Detektatutako programa", + "table_confidence": "Konfiantza", + "table_reported": "Jakinarazia", + "phase_builtin_suffix": "(Sortua)", + "phase_none_available": "Ez dago faserik eskuragarri.", + "settings_deprecation_warning": "⚠️ **Gailu mota zaharkitua:** {device_type} etorkizuneko bertsio batean kentzeko aurreikusita dago. WashData-ren bat datorren kanalizazioak ez du emaitza fidagarriak ematen tresna-klase honetarako. Zure integrazioak funtzionatzen jarraitzen du zaharkitze-aldian zehar; abisu hau isilarazteko, aldatu beheko **Gailu mota** onartzen den mota batera (garbigailua, lehorgailua, garbigailua-lehorgailua konbinatua, ontzi-garbigailua, aire frijigailua, ogi-egilea edo ponpa) edo **Besteak (aurreratua)** aukerara, zure gailuak onartzen ez badituzu. **Beste batzuk (Aurreratuak)** nahita inongo tresna zehatzetarako sintonizatuta ez dauden lehenespen generikoak bidaltzen ditu, beraz, zuk zeuk konfiguratu beharko dituzu atalaseak, denbora-muga eta bat datozen parametroak; Lehendik dituzun ezarpen guztiak gordetzen dira aldatzen zarenean.", + "phase_other_device_types": "Beste gailu mota batzuk:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Ziklo bat zatitu (aurkitu hutsuneak)", + "merge": "Bateratu zikloak (Batu zatiak)", + "delete": "Ezabatu zikloa(k)" + } + }, + "split_mode": { + "options": { + "auto": "Automatikoki detektatu geldialdi-tarteak", + "manual": "Eskuzko denbora-zigilua(k)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Mantentze-lanak: birprozesatu eta optimizatu datuak", + "clear_debug_data": "Garbitu arazketa-datuak (askatu espazioa)", + "wipe_history": "Garbitu gailu honen datu GUZTIAK (itzulezina)", + "export_import": "Esportatu/Inportatu JSON ezarpenekin (kopiatu/itsatsi)" + } + }, + "export_import_mode": { + "options": { + "export": "Esportatu datu guztiak", + "import": "Inportatu/Bateatu datuak" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Auto-etiketatu Ziklo Zaharrak", + "select_cycle_to_label": "Etiketatu ziklo espezifiko bat", + "select_cycle_to_delete": "Ezabatu Zikloa", + "interactive_editor": "Batu/Zatitu Editore Interaktiboa", + "trim_cycle": "Moztu Zikloaren Datuak" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Sortu profil berria", + "edit_profile": "Editatu/Aldatu izena profila", + "delete_profile": "Ezabatu profila", + "profile_stats": "Profilen Estatistikak", + "cleanup_profile": "Garbitu historia - Grafikoa eta ezabatu", + "assign_phases": "Esleitu fase-barrutiak" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Sortu Fase Berria", + "edit_custom_phase": "Editatu Fasea", + "delete_custom_phase": "Ezabatu Fasea" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Gehitu Fase Barrutia", + "edit_range": "Editatu fase-barrutia", + "delete_range": "Ezabatu fase-barrutia", + "clear_ranges": "Garbitu barruti guztiak", + "auto_detect_ranges": "Auto-detektatzeko faseak", + "save_ranges": "Gorde eta Itzuli" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Freskatu egoera", + "stop_recording": "Gelditu grabaketa (Gorde eta prozesatu)", + "start_recording": "Hasi Grabaketa Berria", + "process_recording": "Azken grabaketa prozesatu (moztu eta gorde)", + "discard_recording": "Baztertu azken grabaketa" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Sortu profil berria", + "existing_profile": "Gehitu lehendik dagoen profilean", + "discard": "Baztertu grabazioa" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Berretsi - Detekzio zuzena", + "correct": "Zuzena - Aukeratu Programa/Iraupena", + "ignore": "Ez ikusi egin - Positibo faltsu/Ziklo zaratatsua", + "delete": "Ezabatu - Kendu historiatik" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Faseak izendatu eta aplikatu", + "cancel": "Atzera egin aldaketarik gabe" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Ezarri Abiapuntua", + "set_end": "Ezarri amaiera-puntua", + "reset": "Berrezarri iraupen osoa", + "apply": "Aplikatu mozketa", + "cancel": "Utzi" + } + }, + "device_type": { + "options": { + "washing_machine": "Garbigailua", + "dryer": "Lehorgailua", + "washer_dryer": "Garbigailua-lehorgailua konbinazioa", + "dishwasher": "Ontzi-garbigailua", + "coffee_machine": "Kafe-makina (zaharkitua)", + "ev": "Ibilgailu elektrikoa (zaharkitua)", + "air_fryer": "Aire frijigailua", + "heat_pump": "Bero-ponpa (zaharkitua)", + "bread_maker": "Ogi Egilea", + "pump": "Ponpa", + "oven": "Labea (zaharkitua)", + "other": "Beste batzuk (aurreratua)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiketa Zikloa", + "description": "Esleitu lehendik dagoen profil bat iraganeko ziklo bati edo kendu etiketa.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Etiketatzeko garbigailuaren gailua." + }, + "cycle_id": { + "name": "Zikloaren IDa", + "description": "Etiketatu beharreko zikloaren IDa." + }, + "profile_name": { + "name": "Profilaren izena", + "description": "Lehendik dagoen profil baten izena (sortu profilak Kudeatu profilak menuan). Utzi hutsik etiketa kentzeko." + } + } + }, + "create_profile": { + "name": "Sortu profila", + "description": "Sortu profil berri bat (autonomia edo erreferentzia-ziklo batean oinarrituta).", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailuaren gailua." + }, + "profile_name": { + "name": "Profilaren izena", + "description": "Profil berriaren izena (adibidez, 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Erreferentzia Zikloaren IDa", + "description": "Ziklo ID aukerakoa profil hau oinarritzeko." + } + } + }, + "delete_profile": { + "name": "Ezabatu profila", + "description": "Ezabatu profil bat eta, aukeran, kendu etiketa hori erabiliz zikloak.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailuaren gailua." + }, + "profile_name": { + "name": "Profilaren izena", + "description": "Ezabatu beharreko profila." + }, + "unlabel_cycles": { + "name": "Desetiketatu Zikloak", + "description": "Kendu profilaren etiketa zikloetatik profil hau erabiliz." + } + } + }, + "auto_label_cycles": { + "name": "Auto-etiketatu Ziklo Zaharrak", + "description": "Etiketatu atzeraeraginez etiketarik gabeko zikloak profil bategitea erabiliz.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailuaren gailua." + }, + "confidence_threshold": { + "name": "Konfiantza Atalasea", + "description": "Etiketak aplikatzeko gutxieneko partida-konfiantza (0,50-0,95)." + } + } + }, + "export_config": { + "name": "Esportatu konfigurazioa", + "description": "Esportatu gailu honen profilak, zikloak eta ezarpenak JSON fitxategi batera (gailu bakoitzeko).", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailua esportatzeko gailua." + }, + "path": { + "name": "Bidea", + "description": "Idazteko aukerako fitxategi-bide absolutua (lehenetsia /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Inportatu konfigurazioa", + "description": "Inportatu gailu honen profilak, zikloak eta ezarpenak JSON esportazio fitxategi batetik (baliteke ezarpenak gainidatzita egotea).", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailuan inportatzeko gailua." + }, + "path": { + "name": "Bidea", + "description": "Inportatzeko JSON fitxategiaren esportaziorako bide absolutua." + } + } + }, + "submit_cycle_feedback": { + "name": "Bidali Zikloaren Iritzia", + "description": "Ziklo bat amaitu ondoren auto-detektatu den programa bat berretsi edo zuzendu. Eman `entry_id` (aurreratua) edo `device_id` (gomendatua).", + "fields": { + "device_id": { + "name": "Gailua", + "description": "WashData gailua (gomendatua entry_id-en ordez)." + }, + "entry_id": { + "name": "Sarrera ID", + "description": "Gailuaren konfigurazio-sarreraren IDa (device_id-en alternatiba)." + }, + "cycle_id": { + "name": "Zikloaren IDa", + "description": "Iritziaren jakinarazpenean / erregistroetan agertzen den zikloaren IDa." + }, + "user_confirmed": { + "name": "Berretsi detektatutako programa", + "description": "Ezarri egia detektatutako programa zuzena bada." + }, + "corrected_profile": { + "name": "Profila zuzendua", + "description": "Berresten ez bada, eman profil/programaren izen zuzena." + }, + "corrected_duration": { + "name": "Zuzendutako iraupena (segundoak)", + "description": "Aukerako zuzendutako iraupena segundotan." + }, + "notes": { + "name": "Oharrak", + "description": "Ziklo honi buruzko aukerako oharrak." + } + } + }, + "record_start": { + "name": "Grabatu Zikloaren Hasiera", + "description": "Hasi eskuz ziklo garbi bat grabatzen (bat datozen logika guztiak baztertzen ditu).", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailua grabatzeko gailua." + } + } + }, + "record_stop": { + "name": "Grabatu Zikloaren Geldialdia", + "description": "Gelditu eskuzko grabaketa.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Garbigailuaren gailua grabazioa gelditzeko." + } + } + }, + "trim_cycle": { + "name": "Moztu Zikloa", + "description": "Moztu iraganeko ziklo baten potentzia-datuak denbora-leiho zehatz batera.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "WashData gailua." + }, + "cycle_id": { + "name": "Zikloaren IDa", + "description": "Moztu beharreko zikloaren IDa." + }, + "trim_start_s": { + "name": "Moztu hasiera (segundoak)", + "description": "Gorde desplazamendu honen datuak segundotan. Lehenetsia 0." + }, + "trim_end_s": { + "name": "Moztu amaiera (segundoak)", + "description": "Mantendu datuak desplazamendu honetarako segundotan. Lehenetsia = iraupen osoa." + } + } + }, + "pause_cycle": { + "name": "Eten zikloa", + "description": "Pausatu WashData gailu baten ziklo aktiboa.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "WashData gailua pausatzeko." + } + } + }, + "resume_cycle": { + "name": "Zikloa berreskuratzea", + "description": "Berrekin pausatutako ziklo bati WashData gailu baterako.", + "fields": { + "device_id": { + "name": "Gailua", + "description": "Berrekiteko WashData gailua." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Martxan" + }, + "match_ambiguity": { + "name": "Bat-etortze anbiguotasuna" + } + }, + "sensor": { + "washer_state": { + "name": "Egoera", + "state": { + "off": "Desaktibatuta", + "idle": "Geldirik", + "starting": "Abiatzen", + "running": "Martxan", + "paused": "Pausatuta", + "user_paused": "Erabiltzaileak pausatuta", + "ending": "Amaitze bidean", + "finished": "Amaituta", + "anti_wrinkle": "Zimur-aurkakoa", + "delay_wait": "Hasteko zain", + "interrupted": "Etenduta", + "force_stopped": "Beharrez gelditu", + "rinse": "Garbiketa", + "unknown": "Ezezaguna", + "clean": "Garbitu" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Gelditzen den denbora" + }, + "total_duration": { + "name": "Iraupen osoa" + }, + "cycle_progress": { + "name": "Aurrerapena" + }, + "current_power": { + "name": "Egungo Potentzia" + }, + "elapsed_time": { + "name": "Iragandako Denbora" + }, + "debug_info": { + "name": "Arazketa informazioa" + }, + "match_confidence": { + "name": "Bat-etortze konfiantza" + }, + "top_candidates": { + "name": "Hautagai nagusiak", + "state": { + "none": "Bat ere ez" + } + }, + "current_phase": { + "name": "Egungo Fasea" + }, + "wash_phase": { + "name": "Fasea" + }, + "profile_cycle_count": { + "name": "{profile_name} profilaren ziklo kopurua", + "unit_of_measurement": "ziklo" + }, + "suggestions": { + "name": "Iradokitako ezarpen erabilgarriak" + }, + "pump_runs_today": { + "name": "Ponpa martxan (azken 24 orduak)" + }, + "cycle_count": { + "name": "Zikloen Zenbaketa" + } + }, + "select": { + "program_select": { + "name": "Zikloaren programa", + "state": { + "auto_detect": "Auto-detektatu" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Behartuta amaitu zikloa" + }, + "pause_cycle": { + "name": "Eten zikloa" + }, + "resume_cycle": { + "name": "Zikloa berreskuratzea" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Baliozko device_id behar da." + }, + "cycle_id_required": { + "message": "Baliozko cycle_id bat behar da." + }, + "profile_name_required": { + "message": "Baliozko profile_name bat behar da." + }, + "device_not_found": { + "message": "Ez da gailua aurkitu." + }, + "no_config_entry": { + "message": "Ez da aurkitu gailurako konfigurazio-sarrerarik." + }, + "integration_not_loaded": { + "message": "Ez da kargatu gailu honetarako integrazioa." + }, + "cycle_not_found_or_no_power": { + "message": "Zikloa ez da aurkitu edo ez du energia-daturik." + }, + "trim_failed_empty_window": { + "message": "Moztu huts egin du - zikloa ez da aurkitu, ez dago energia-daturik edo ondoriozko leihoa hutsik dago." + }, + "trim_invalid_range": { + "message": "trim_end_s trim_start_s baino handiagoa izan behar du." + } + } +} diff --git a/custom_components/ha_washdata/translations/fa.json b/custom_components/ha_washdata/translations/fa.json new file mode 100644 index 0000000..0ee04b7 --- /dev/null +++ b/custom_components/ha_washdata/translations/fa.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "راه اندازی WashData", + "description": "ماشین لباسشویی یا وسایل دیگر خود را پیکربندی کنید.\n\nسنسور قدرت مورد نیاز است.\n\n**بعد از شما پرسیده می شود که آیا می خواهید اولین نمایه خود را ایجاد کنید.**", + "data": { + "name": "نام دستگاه", + "device_type": "نوع دستگاه", + "power_sensor": "سنسور قدرت", + "min_power": "حداقل آستانه توان (W)" + }, + "data_description": { + "name": "نامی مناسب برای این دستگاه (به عنوان مثال، \"ماشین لباسشویی\"، \"ماشین ظرفشویی\").", + "device_type": "این چه نوع دستگاهی است؟ به تشخیص و برچسب زدن کمک می کند.", + "power_sensor": "موجودیت حسگری که مصرف برق بلادرنگ (بر حسب وات) را از دوشاخه هوشمند شما گزارش می دهد.", + "min_power": "خوانش های توان بالاتر از این آستانه (بر حسب وات) نشان می دهد که دستگاه در حال کار است. برای اکثر دستگاه ها با 2W شروع کنید." + } + }, + "first_profile": { + "title": "ایجاد اولین نمایه", + "description": "یک **چرخه** اجرای کامل دستگاه شما است (به عنوان مثال، یک بار لباسشویی). یک **نمایه** این چرخه‌ها را بر اساس نوع گروه‌بندی می‌کند (به عنوان مثال، «پنبه»، «شستشوی سریع»). ایجاد نمایه اکنون به یکپارچه‌سازی کمک می‌کند تا فوری مدت زمان را تخمین بزند.\n\nاگر به صورت خودکار شناسایی نشد، می‌توانید این نمایه را به‌صورت دستی در کنترل‌های دستگاه انتخاب کنید.\n\n**برای رد شدن از این مرحله، «نام نمایه» را خالی بگذارید.**", + "data": { + "profile_name": "نام نمایه (اختیاری)", + "manual_duration": "مدت زمان تخمینی (دقیقه)" + } + } + }, + "error": { + "cannot_connect": "اتصال ناموفق بود", + "invalid_auth": "احراز هویت نامعتبر است", + "unknown": "خطای غیرمنتظره" + }, + "abort": { + "already_configured": "دستگاه قبلاً پیکربندی شده است" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "بازخوردهای آموزشی را مرور کنید", + "settings": "تنظیمات", + "notifications": "اطلاعیه ها", + "manage_cycles": "چرخه ها را مدیریت کنید", + "manage_profiles": "مدیریت پروفایل ها", + "manage_phase_catalog": "مدیریت کاتالوگ فاز", + "record_cycle": "چرخه ضبط (دستی)", + "diagnostics": "تشخیص و نگهداری" + } + }, + "apply_suggestions": { + "title": "کپی کردن مقادیر پیشنهادی", + "description": "این مقادیر پیشنهادی فعلی را در تنظیمات ذخیره شده شما کپی می کند. این یک اقدام یک بار است و بازنویسی خودکار را فعال نمی کند.\n\nمقادیر پیشنهادی برای کپی کردن:\n{suggested}", + "data": { + "confirm": "اقدام کپی را تأیید کنید" + } + }, + "apply_suggestions_confirm": { + "title": "بررسی تغییرات پیشنهادی", + "description": "WashData **{pending_count}** مقدار(های) پیشنهادی برای اعمال پیدا شد.\n\nتغییرات:\n{changes}\n\n✅ کادر زیر را علامت بزنید و روی **ارسال** کلیک کنید تا این تغییرات اعمال شود و فوراً ذخیره شوند.\n❌ آن را بدون علامت بگذارید و برای لغو و بازگشت روی **ارسال** کلیک کنید.", + "data": { + "confirm_apply_suggestions": "این تغییرات را اعمال و ذخیره کنید" + } + }, + "settings": { + "title": "تنظیمات", + "description": "{deprecation_warning}آستانه تشخیص، یادگیری و رفتار پیشرفته را تنظیم کنید. پیش‌فرض‌ها برای اکثر تنظیمات به خوبی کار می‌کنند.\n\nتعداد تنظیمات پیشنهادی موجود: {suggestions_count}.\nاگر بزرگ‌تر از 0 است، تنظیمات پیشرفته را باز کنید و از «اعمال مقادیر پیشنهادی» برای بررسی توصیه‌ها استفاده کنید.", + "data": { + "apply_suggestions": "اعمال مقادیر پیشنهادی", + "device_type": "نوع دستگاه", + "power_sensor": "موجودیت سنسور قدرت", + "min_power": "حداقل توان (W)", + "off_delay": "تأخیر (های) پایان چرخه", + "notify_actions": "اقدامات اطلاع رسانی", + "notify_people": "تأخیر تحویل تا زمانی که این افراد در خانه هستند", + "notify_only_when_home": "تأخیر اعلان‌ها تا زمانی که فرد منتخب در خانه باشد", + "notify_fire_events": "فعال‌سازی رویدادهای اتوماسیون", + "notify_start_services": "شروع چرخه - اهداف اعلان", + "notify_finish_services": "چرخه پایان - اهداف اعلان", + "notify_live_services": "پیشرفت زنده - اهداف اعلان", + "notify_before_end_minutes": "اعلان پیش از اتمام (دقیقه قبل از پایان)", + "notify_title": "عنوان اطلاعیه", + "notify_icon": "نماد اعلان", + "notify_start_message": "فرمت پیام را شروع کنید", + "notify_finish_message": "فرمت پیام پایان", + "notify_pre_complete_message": "قالب پیام قبل از تکمیل", + "show_advanced": "ویرایش تنظیمات پیشرفته (مرحله بعد)" + }, + "data_description": { + "apply_suggestions": "این کادر را علامت بزنید تا مقادیر پیشنهاد شده توسط الگوریتم یادگیری را در فرم کپی کنید. قبل از ذخیره بررسی کنید.\n\nپیشنهادی (از یادگیری):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "نوع دستگاه (ماشین لباسشویی، خشک کن، ماشین ظرفشویی، قهوه ساز، EV) را انتخاب کنید. این تگ با هر چرخه برای سازماندهی بهتر ذخیره می شود.", + "power_sensor": "موجودیت حسگر که توان آنی را گزارش می‌کند (بر حسب وات). می‌توانید هرزمان خواستید بدون از دست دادن داده‌های تاریخی این مورد را تغییر دهید-در صورت تعویض دوشاخه هوشمند مفید است.", + "min_power": "خوانش های توان بالاتر از این آستانه (بر حسب وات) نشان می دهد که دستگاه در حال کار است. برای اکثر دستگاه ها با 2W شروع کنید.", + "off_delay": "چند ثانیه توان هموارسازی شده باید زیر آستانه توقف باقی بماند تا علامت اتمام باشد. پیش فرض: 120 ثانیه", + "notify_actions": "دنباله اقدام اختیاری Home Assistant برای اجرای اعلان ها (اسکریپت ها، اطلاع رسانی، TTS و غیره). در صورت تنظیم، اقدامات قبل از سرویس اطلاع رسانی مجدد اجرا می شود.", + "notify_people": "نهادهای افراد برای دروازه حضور استفاده می شود. در صورت فعال بودن، تحویل اعلان تا زمانی که حداقل یک نفر انتخاب شده در خانه باشد به تعویق می افتد.", + "notify_only_when_home": "در صورت فعال بودن، اعلان ها را تا زمانی که حداقل یک نفر انتخاب شده در خانه باشد به تاخیر بیاندازید.", + "notify_fire_events": "در صورت فعال بودن، رویدادهای Home Assistant را برای شروع و پایان چرخه (ha_washdata_cycle_started و ha_washdata_cycle_ended) برای رانندگی خودکار فعال کنید.", + "notify_start_services": "یک یا چند سرویس را برای هشدار در هنگام شروع یک چرخه مطلع می کند. برای رد شدن از اعلان‌های شروع، خالی بگذارید.", + "notify_finish_services": "یک یا چند سرویس را مطلع می کنند تا در صورت اتمام یا نزدیک شدن به اتمام یک چرخه هشدار دهند. برای رد شدن از اعلان‌های پایان، خالی بگذارید.", + "notify_live_services": "یک یا چند سرویس برای دریافت به‌روزرسانی‌های پیشرفت زنده در حین اجرای چرخه (فقط برنامه همراه همراه) اطلاع رسانی می‌کنند. برای رد شدن از به‌روزرسانی‌های زنده، خالی بگذارید.", + "notify_before_end_minutes": "چند دقیقه قبل از پایان تخمینی چرخه برای راه اندازی یک اعلان. برای غیرفعال کردن، روی 0 تنظیم کنید. پیش فرض: 0.", + "notify_title": "عنوان سفارشی برای اعلان ها. از {device} متغیری پشتیبانی می کند.", + "notify_icon": "نماد مورد استفاده برای اعلان‌ها (مانند mdi: ماشین لباسشویی). برای ارسال بدون نماد خالی بگذارید.", + "notify_start_message": "هنگام شروع یک چرخه پیام ارسال می شود. از {device} متغیری پشتیبانی می کند.", + "notify_finish_message": "پس از پایان یک چرخه، پیام ارسال می شود. از متغیرهای {device}، {duration}، {program}, {energy_kwh}, {cost} پشتیبانی می کند.", + "notify_pre_complete_message": "متن پیشرفت زنده مکرر به‌روزرسانی می‌شود در حالی که چرخه اجرا می‌شود. از متغیرهای {device}، {minutes}، {program} پشتیبانی می کند." + } + }, + "notifications": { + "title": "اطلاعیه ها", + "description": "کانال‌های اعلان، الگوها و به‌روزرسانی‌های پیشرفت زنده اختیاری تلفن همراه را پیکربندی کنید.", + "data": { + "notify_actions": "اقدامات اطلاع رسانی", + "notify_people": "تأخیر تحویل تا زمانی که این افراد در خانه هستند", + "notify_only_when_home": "تأخیر اعلان‌ها تا زمانی که فرد منتخب در خانه باشد", + "notify_fire_events": "فعال‌سازی رویدادهای اتوماسیون", + "notify_start_services": "شروع چرخه - اهداف اعلان", + "notify_finish_services": "چرخه پایان - اهداف اعلان", + "notify_live_services": "پیشرفت زنده - اهداف اعلان", + "notify_before_end_minutes": "اعلان پیش از اتمام (دقیقه قبل از پایان)", + "notify_live_interval_seconds": "فاصله به روز رسانی زنده (ثانیه)", + "notify_live_overrun_percent": "کمک هزینه به‌روزرسانی زنده (%)", + "notify_live_chronometer": "تایمر شمارش معکوس کرنومتر", + "notify_title": "عنوان اطلاعیه", + "notify_icon": "نماد اعلان", + "notify_start_message": "فرمت پیام را شروع کنید", + "notify_finish_message": "فرمت پیام پایان", + "notify_pre_complete_message": "قالب پیام قبل از تکمیل", + "energy_price_entity": "قیمت انرژی - نهاد (اختیاری)", + "energy_price_static": "قیمت انرژی - مقدار استاتیک (اختیاری)", + "go_back": "بازگشت بدون ذخیره", + "notify_reminder_message": "بایگانی برچسب ها", + "notify_timeout_seconds": "ماشین پس از (ثانیه)", + "notify_channel": "کانال اطلاع رسانی (وضعیت / زندگی / اقامت)", + "notify_finish_channel": "کانال اطلاع رسانی" + }, + "data_description": { + "go_back": "این را علامت بزنید و روی ارسال کلیک کنید تا هر تغییری در بالا کنار گذاشته شود و به منوی اصلی برگردید.", + "notify_actions": "دنباله اقدام اختیاری Home Assistant برای اجرای اعلان ها (اسکریپت ها، اطلاع رسانی، TTS و غیره). در صورت تنظیم، اقدامات قبل از سرویس اطلاع رسانی مجدد اجرا می شود.", + "notify_people": "نهادهای افراد برای دروازه حضور استفاده می شود. در صورت فعال بودن، تحویل اعلان تا زمانی که حداقل یک نفر انتخاب شده در خانه باشد به تعویق می افتد.", + "notify_only_when_home": "در صورت فعال بودن، اعلان ها را تا زمانی که حداقل یک نفر انتخاب شده در خانه باشد به تاخیر بیاندازید.", + "notify_fire_events": "در صورت فعال بودن، رویدادهای Home Assistant را برای شروع و پایان چرخه (ha_washdata_cycle_started و ha_washdata_cycle_ended) برای رانندگی خودکار فعال کنید.", + "notify_start_services": "یک یا چند سرویس اطلاع رسانی برای هشدار هنگام شروع یک چرخه (مانند notify.mobile_app_my_phone). برای رد شدن از اعلان‌های شروع، خالی بگذارید.", + "notify_finish_services": "یک یا چند سرویس را مطلع می کنند تا در صورت اتمام یا نزدیک شدن به اتمام یک چرخه هشدار دهند. برای رد شدن از اعلان‌های پایان، خالی بگذارید.", + "notify_live_services": "یک یا چند سرویس برای دریافت به‌روزرسانی‌های پیشرفت زنده در حین اجرای چرخه (فقط برنامه همراه همراه) اطلاع رسانی می‌کنند. برای رد شدن از به‌روزرسانی‌های زنده، خالی بگذارید.", + "notify_before_end_minutes": "چند دقیقه قبل از پایان تخمینی چرخه برای راه اندازی یک اعلان. برای غیرفعال کردن، روی 0 تنظیم کنید. پیش فرض: 0.", + "notify_live_interval_seconds": "هر چند وقت یک‌بار به‌روزرسانی‌های پیشرفت هنگام اجرا ارسال می‌شوند. مقادیر پایین تر پاسخگوتر هستند اما اعلان های بیشتری را مصرف می کنند. حداقل 30 ثانیه اعمال می شود - مقادیر کمتر از 30 ثانیه به طور خودکار به 30 ثانیه گرد می شوند.", + "notify_live_overrun_percent": "حاشیه ایمنی بالاتر از تعداد به‌روزرسانی‌های تخمینی برای چرخه‌های طولانی/بیش از حد مجاز است (به عنوان مثال 20٪ به 1.2 برابر به‌روزرسانی‌های تخمینی اجازه می‌دهد).", + "notify_live_chronometer": "وقتی فعال باشد، هر به‌روزرسانی زنده شامل شمارش معکوس کرنومتر تا زمان تخمینی پایان می‌شود. تایمر اعلان بین به‌روزرسانی‌ها به‌طور خودکار روی دستگاه خاموش می‌شود (فقط برنامه همراه اندروید).", + "notify_title": "عنوان سفارشی برای اعلان ها. از {device} متغیری پشتیبانی می کند.", + "notify_icon": "نماد مورد استفاده برای اعلان‌ها (مانند mdi: ماشین لباسشویی). برای ارسال بدون نماد خالی بگذارید.", + "notify_start_message": "هنگام شروع یک چرخه پیام ارسال می شود. از {device} متغیری پشتیبانی می کند.", + "notify_finish_message": "پس از پایان یک چرخه، پیام ارسال می شود. از متغیرهای {device}، {duration}، {program}, {energy_kwh}, {cost} پشتیبانی می کند.", + "energy_price_entity": "یک موجود عددی را انتخاب کنید که دارای قیمت فعلی برق باشد (به عنوان مثال sensor.electricity_price، input_number.energy_rate). وقتی تنظیم شود، مکان‌دار {cost} را فعال می‌کند. اگر هر دو پیکربندی شده باشند، بر مقدار استاتیک اولویت دارد.", + "energy_price_static": "قیمت ثابت برق به ازای هر کیلووات ساعت وقتی تنظیم شود، مکان‌دار {cost} را فعال می‌کند. اگر موجودی در بالا پیکربندی شده باشد نادیده گرفته می شود. نماد ارز خود را در قالب پیام اضافه کنید، به عنوان مثال. {cost} یورو.", + "notify_reminder_message": "متن یادآوری یک‌باره، تعداد دقیقه‌های پیکربندی‌شده را قبل از پایان تخمینی ارسال کرد. متمایز از به‌روزرسانی‌های زنده مکرر بالا. از متغیرهای {device}، {minutes}، {program} پشتیبانی می کند.", + "notify_timeout_seconds": "به طور خودکار اعلان ها را پس از این چند ثانیه رد کنید (به عنوان برنامه \"زمان\" به جلو بروید). به سمت صفر بروید تا آنها را تا زمانی که اخراج شوند، نگه دارید. پیش‌فرض: 0", + "notify_channel": "Android کانال اطلاع رسانی برنامه همراه برای وضعیت، پیشرفت زنده و اعلان های یادآوری. صدا و اهمیت یک کانال در برنامه همراه برای اولین بار که نام کانال استفاده می شود پیکربندی می شود - WashData فقط نام کانال را تنظیم می کند. خالی را برای پیش فرض برنامه بگذارید.", + "notify_finish_channel": "کانال برای هشدار نهایی (همچنین توسط یادآوری و یادآور مکرر انتظار لباسشویی استفاده می شود) بنابراین می تواند صدای خود را داشته باشد. برای استفاده از کانال بالا خالی بگذارید.", + "notify_pre_complete_message": "متن پیشرفت زنده مکرر به‌روزرسانی می‌شود در حالی که چرخه اجرا می‌شود. از متغیرهای {device}، {minutes}، {program} پشتیبانی می کند." + } + }, + "advanced_settings": { + "title": "تنظیمات پیشرفته", + "description": "آستانه‌های تشخیص، یادگیری و رفتار پیشرفته را تنظیم کنید. پیش‌فرض‌ها برای اکثر تنظیمات به خوبی کار می‌کنند.", + "sections": { + "suggestions_section": { + "name": "تنظیمات پیشنهادی", + "description": "{suggestions_count} پیشنهادِ آموخته‌شده در دسترس است. برای بررسی تغییرات پیشنهادی پیش از ذخیره، «اعمال مقادیر پیشنهادی» را علامت بزنید.", + "data": { + "apply_suggestions": "اعمال مقادیر پیشنهادی" + }, + "data_description": { + "apply_suggestions": "این کادر را علامت بزنید تا یک مرحله بررسی برای مقادیر پیشنهادی از الگوریتم یادگیری باز شود. هیچ چیزی به طور خودکار ذخیره نمی شود.\n\nپیشنهادی (از یادگیری):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "تشخیص و آستانه‌های توان", + "description": "اینکه چه زمانی دستگاه روشن یا خاموش در نظر گرفته می‌شود، دروازه‌های انرژی، و زمان‌بندی انتقال وضعیت‌ها.", + "data": { + "start_duration_threshold": "شروع مدت زمان (های) Debounce", + "start_energy_threshold": "راه اندازی دروازه انرژی (Wh)", + "completion_min_seconds": "حداقل زمان اجرا (ثانیه)", + "start_threshold_w": "آستانه شروع (W)", + "stop_threshold_w": "آستانه توقف (W)", + "end_energy_threshold": "دروازه انرژی پایانی (Wh)", + "running_dead_zone": "در حال اجرا منطقه مرده (ثانیه)", + "end_repeat_count": "پایان تعداد تکرار", + "min_off_gap": "حداقل فاصله بین چرخه ها", + "sampling_interval": "فاصله (های) نمونه برداری" + }, + "data_description": { + "start_duration_threshold": "نوک کوتاه‌تر از این مدت (ثانیه) را نادیده بگیرید. قبل از شروع چرخه واقعی، نوک های بوت را فیلتر می کند (مثلاً 60 وات برای 2 ثانیه). پیش فرض: 5 ثانیه", + "start_energy_threshold": "چرخه باید حداقل این مقدار انرژی (Wh) را در مرحله استارت جمع کند تا تایید شود. پیش فرض: 0.005 وات", + "completion_min_seconds": "چرخه‌های کوتاه‌تر از این به‌عنوان «مقطع» علامت‌گذاری می‌شوند، حتی اگر به طور طبیعی تمام شوند. پیش فرض: 600s.", + "start_threshold_w": "برای شروع یک چرخه، توان باید بالاتر از این آستانه باشد. بالاتر از قدرت آماده به کار خود تنظیم کنید. پیش فرض: min_power + 1 W.", + "stop_threshold_w": "برای پایان یک چرخه، توان باید زیر این آستانه باشد. برای جلوگیری از سر و صدایی که باعث ایجاد انتهای کاذب می شود، کمی بالای صفر تنظیم کنید. پیش فرض: 0.6 * min_power.", + "end_energy_threshold": "چرخه فقط در صورتی به پایان می رسد که انرژی در آخرین پنجره Off-Delay کمتر از این مقدار (Wh) باشد. در صورت نوسانات برق نزدیک به صفر از پایان های کاذب جلوگیری می کند. پیش فرض: 0.05 وات", + "running_dead_zone": "پس از شروع چرخه، برای جلوگیری از تشخیص انتهای کاذب در مرحله اولیه چرخش، افت برق را برای این چند ثانیه نادیده بگیرید. برای غیرفعال کردن، روی 0 تنظیم کنید. پیش فرض: 0 ثانیه", + "end_repeat_count": "تعداد دفعاتی که شرط پایان (قدرت زیر آستانه برای off_delay) باید به طور متوالی قبل از پایان چرخه برآورده شود. مقادیر بالاتر از پایان های نادرست در طول مکث جلوگیری می کند. پیش فرض: 1.", + "min_off_gap": "اگر یک چرخه در این چند ثانیه از پایان چرخه قبلی شروع شود، آنها در یک چرخه واحد ادغام می شوند.", + "sampling_interval": "حداقل فاصله نمونه برداری بر حسب ثانیه به‌روزرسانی‌های سریع‌تر از این نرخ نادیده گرفته می‌شوند. پیش‌فرض: 30 ثانیه (2 ثانیه برای ماشین‌های لباسشویی، ماشین لباسشویی و خشک‌کن و ماشین ظرفشویی)." + } + }, + "matching_section": { + "name": "تطبیق پروفایل و یادگیری", + "description": "اینکه WashData با چه شدتی چرخه‌های در حال اجرا را با پروفایل‌های آموخته‌شده تطبیق می‌دهد و چه زمانی بازخورد درخواست می‌کند.", + "data": { + "profile_match_min_duration_ratio": "نسبت حداقل مدت زمان مطابقت نمایه (0.1-1.0)", + "profile_match_interval": "فاصله تطبیق نمایه (ثانیه)", + "profile_match_threshold": "آستانه مطابقت نمایه", + "profile_unmatch_threshold": "آستانه عدم تطابق نمایه", + "auto_label_confidence": "برچسب خودکار اطمینان (0-1)", + "learning_confidence": "درخواست بازخورد اطمینان (0-1)", + "suppress_feedback_notifications": "سرکوب اعلان‌های بازخورد", + "duration_tolerance": "تحمل مدت (0-0.5)", + "smoothing_window": "صاف کردن پنجره (نمونه)", + "profile_duration_tolerance": "تحمل مدت زمان مطابقت نمایه (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "حداقل نسبت مدت زمان برای تطبیق پروفایل. چرخه دویدن باید حداقل این کسری از مدت پروفیل باشد تا مطابقت داشته باشد. پایین تر = تطبیق زودتر پیش فرض: 0.50 (50%).", + "profile_match_interval": "هر چند وقت یکبار (در چند ثانیه) در طول یک چرخه تلاش برای تطبیق نمایه. مقادیر پایین‌تر تشخیص سریع‌تری را ارائه می‌کنند اما از CPU بیشتری استفاده می‌کنند. پیش‌فرض: 300 ثانیه (5 دقیقه).", + "profile_match_threshold": "حداقل امتیاز شباهت DTW (0.0-1.0) برای در نظر گرفتن یک نمایه به عنوان یک تطابق لازم است. بالاتر = تطبیق دقیق تر، مثبت کاذب کمتر. پیش فرض: 0.4.", + "profile_unmatch_threshold": "امتیاز شباهت DTW (0.0-1.0) که زیر آن یک نمایه قبلا مطابقت داده شده رد می شود. برای جلوگیری از سوسو زدن باید کمتر از آستانه مسابقه باشد. پیش فرض: 0.35.", + "auto_label_confidence": "چرخه‌ها را به‌طور خودکار پس از تکمیل (0.0-1.0) با این اطمینان یا بالاتر از آن برچسب‌گذاری کنید.", + "learning_confidence": "حداقل اطمینان برای درخواست تأیید کاربر از طریق یک رویداد (0.0–1.0).", + "suppress_feedback_notifications": "وقتی WashData فعال باشد، همچنان به صورت داخلی بازخورد را ردیابی و درخواست می‌کند، اما اعلان‌های دائمی که از شما می‌خواهند چرخه‌ها را تأیید کنید، نشان نمی‌دهد. زمانی مفید است که چرخه ها همیشه به درستی تشخیص داده می شوند و پیام ها را منحرف می کند.", + "duration_tolerance": "تحمل تخمین های باقیمانده زمان در طول اجرا (0.0-0.5 مربوط به 0-50٪ است).", + "smoothing_window": "تعداد قرائت‌های اخیر توان مورد استفاده برای هموارسازی میانگین متحرک.", + "profile_duration_tolerance": "تحمل استفاده شده توسط تطبیق پروفایل برای واریانس کل مدت (0.0-0.5). رفتار پیش فرض: ± 25%." + } + }, + "timing_section": { + "name": "زمان‌بندی، نگهداری و اشکال‌زدایی", + "description": "واچ‌داگ، بازنشانی پیشرفت، نگهداری خودکار و نمایش موجودیت‌های اشکال‌زدایی.", + "data": { + "watchdog_interval": "فاصله سگ نگهبان (ثانیه)", + "no_update_active_timeout": "مهلت زمانی بدون به‌روزرسانی", + "progress_reset_delay": "تاخیر بازنشانی پیشرفت (ثانیه)", + "auto_maintenance": "Auto-Maintenance را فعال کنید", + "expose_debug_entities": "موجودیت های اشکال زدایی را در معرض نمایش قرار دهید", + "save_debug_traces": "ذخیره ردیابی اشکال زدایی" + }, + "data_description": { + "watchdog_interval": "ثانيه بين بازرسي هاي نگهبان در حين دويدن. پیش فرض: 5 ثانیه هشدار: برای جلوگیری از توقف های کاذب، مطمئن شوید که این فاصله بالاتر از فاصله به روز رسانی سنسور شما است (به عنوان مثال، اگر سنسور هر 60 ثانیه به روز می شود، از 65 ثانیه به بالا استفاده کنید).", + "no_update_active_timeout": "برای حسگرهای انتشار در هنگام تغییر: فقط در صورتی که به‌روزرسانی برای این چند ثانیه صورت نگیرد، چرخه ACTIVE را به اجبار پایان دهید. تکمیل کم مصرف همچنان از تاخیر خاموش استفاده می کند.", + "progress_reset_delay": "پس از تکمیل یک چرخه (100%)، قبل از تنظیم مجدد پیشرفت به 0٪، این چند ثانیه در حالت بیکار صبر کنید. پیش فرض: 300s.", + "auto_maintenance": "تعمیر و نگهداری خودکار را فعال کنید (نمونه‌ها را تعمیر کنید، قطعات را ادغام کنید).", + "expose_debug_entities": "نمایش سنسورهای پیشرفته (اطمینان، فاز، ابهام) برای رفع اشکال.", + "save_debug_traces": "داده های رتبه بندی دقیق و ردیابی قدرت را در تاریخچه ذخیره کنید (استفاده از ذخیره سازی را افزایش می دهد)." + } + }, + "anti_wrinkle_section": { + "name": "سپر ضدچروک (خشک‌کن‌ها)", + "description": "چرخش‌های دیگ پس از پایان چرخه را نادیده بگیرید تا از چرخه‌های شبح جلوگیری شود. فقط برای خشک‌کن / واشر-خشک‌کن.", + "data": { + "anti_wrinkle_enabled": "سپر ضد چروک (فقط خشک کن)", + "anti_wrinkle_max_power": "حداکثر توان ضد چروک (W)", + "anti_wrinkle_max_duration": "حداکثر مدت زمان ضد چروک (s)", + "anti_wrinkle_exit_power": "قدرت خروج ضد چروک (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "چرخش های کوتاه درام کم مصرف را پس از پایان یک چرخه نادیده بگیرید تا از چرخه های ارواح جلوگیری کنید (فقط خشک کن/واشر-خشک کن).", + "anti_wrinkle_max_power": "انفجارهای با این توان یا کمتر از آن به عنوان چرخش های ضد چروک به جای چرخه های جدید در نظر گرفته می شوند. فقط زمانی اعمال می شود که سپر ضد چروک فعال باشد.", + "anti_wrinkle_max_duration": "حداکثر طول ترکیدگی برای درمان به عنوان چرخش ضد چروک. فقط زمانی اعمال می شود که سپر ضد چروک فعال باشد.", + "anti_wrinkle_exit_power": "اگر قدرت به زیر این سطح رسید، فوراً از ضد چروک خارج شوید (خاموش واقعی). فقط زمانی اعمال می شود که سپر ضد چروک فعال باشد." + } + }, + "delay_start_section": { + "name": "تشخیص شروع با تأخیر", + "description": "حالت آماده‌به‌کار کم‌مصرفِ پایدار را تا زمانی که چرخه واقعاً شروع شود، به‌عنوان 'در انتظار شروع' در نظر بگیرید.", + "data": { + "delay_start_detect_enabled": "تشخیص شروع تاخیری را فعال کنید", + "delay_confirm_seconds": "شروع با تأخیر: زمان تأیید حالت آماده‌به‌کار (ثانیه)", + "delay_timeout_hours": "شروع تاخیر: حداکثر زمان انتظار (ساعت)" + }, + "data_description": { + "delay_start_detect_enabled": "هنگامی که فعال می شود، یک جهش کوتاه تخلیه کم مصرف و به دنبال آن قدرت آماده به کار پایدار به عنوان شروع تاخیری تشخیص داده می شود. حسگر حالت \"در انتظار شروع\" را در طول تاخیر نشان می دهد. هیچ زمان یا پیشرفت برنامه تا زمانی که چرخه واقعاً شروع نشود ردیابی نمی شود. به start_threshold_w نیاز دارد که بالاتر از توان لوله تخلیه تنظیم شود.", + "delay_confirm_seconds": "پیش از اینکه WashData وارد وضعیت 'در انتظار شروع' شود، توان باید به مدت این تعداد ثانیه بین آستانه توقف (W) و آستانه شروع (W) بماند. این کار اوج‌های کوتاه ناشی از جابه‌جایی در منو را فیلتر می‌کند. پیش‌فرض: 60 ثانیه.", + "delay_timeout_hours": "مهلت زمانی ایمنی: اگر دستگاه پس از این چند ساعت همچنان در «در انتظار راه‌اندازی» باشد، WashData به حالت خاموش بازنشانی می‌شود. پیش‌فرض: 8 ساعت" + } + }, + "external_triggers_section": { + "name": "محرک‌های خارجی، در و مکث", + "description": "سنسور اختیاری محرک پایان خارجی، سنسور در برای تشخیص مکث/تمیز، و کلید قطع توان هنگام مکث.", + "data": { + "external_end_trigger_enabled": "فعال‌سازی محرک پایان خارجی", + "external_end_trigger": "سنسور پایان سیکل خارجی", + "external_end_trigger_inverted": "معکوس کردن منطق ماشه (تریگر خاموش است)", + "door_sensor_entity": "سنسور درب", + "pause_cuts_power": "قطع برق هنگام مکث", + "switch_entity": "سوئیچ نهاد (برای توقف موقت قطع برق)", + "notify_unload_delay_minutes": "تأخیر اعلان انتظار لباسشویی (دقیقه)" + }, + "data_description": { + "external_end_trigger_enabled": "نظارت بر حسگر باینری خارجی را برای شروع تکمیل چرخه فعال کنید.", + "external_end_trigger": "یک موجودیت حسگر باینری را انتخاب کنید. هنگامی که این سنسور فعال می شود، چرخه فعلی با وضعیت \"تکمیل\" به پایان می رسد.", + "external_end_trigger_inverted": "به طور پیش فرض، هنگامی که سنسور روشن می شود، ماشه فعال می شود. این کادر را علامت بزنید تا زمانی که سنسور خاموش می شود، روشن شود.", + "door_sensor_entity": "سنسور باینری اختیاری برای درب دستگاه (روشن = باز، خاموش = بسته). هنگامی که در در طول یک چرخه فعال باز می شود، WashData چرخه را به عنوان توقف عمدی تایید می کند. پس از پایان چرخه، اگر درب همچنان بسته باشد، وضعیت به \"تمیز\" تغییر می کند تا درب باز شود. توجه: بستن در، چرخه توقف موقت را به طور خودکار از سر نمی گیرد - رزومه باید به صورت دستی از طریق دکمه یا سرویس چرخه از سرگیری فعال شود.", + "pause_cuts_power": "هنگام مکث از طریق دکمه یا سرویس چرخه مکث، موجودیت سوئیچ پیکربندی شده را نیز خاموش کنید. فقط در صورتی اعمال می شود که یک موجودیت سوئیچ برای این دستگاه پیکربندی شده باشد.", + "switch_entity": "موجودیت سوئیچ برای تغییر هنگام توقف یا از سرگیری. زمانی که «قطع برق هنگام مکث» فعال باشد، لازم است.", + "notify_unload_delay_minutes": "پس از پایان چرخه و باز نشدن درب برای این چند دقیقه، از طریق کانال اعلان پایان، اعلان ارسال کنید (نیاز به سنسور درب دارد). برای غیرفعال کردن، روی 0 تنظیم کنید. پیش‌فرض: 60 دقیقه" + } + }, + "pump_section": { + "name": "پایش پمپ", + "description": "فقط برای نوع دستگاه پمپ. اگر چرخه پمپ از این مدت طولانی‌تر شود، یک رویداد ارسال می‌کند.", + "data": { + "pump_stuck_duration": "آستانه هشدار گیر کردن پمپ (فقط پمپ)" + }, + "data_description": { + "pump_stuck_duration": "اگر چرخه پمپ پس از این چند ثانیه همچنان در حال اجرا باشد، یک رویداد ha_washdata_pump_stuck یک بار اجرا می شود. از این برای تشخیص موتور گیر کرده استفاده کنید (معمولاً پمپ کارکرد زیر 60 ثانیه است). پیش‌فرض: 1800 ثانیه (30 دقیقه). فقط نوع دستگاه پمپ" + } + }, + "device_link_section": { + "name": "دستگاه Link", + "description": "به صورت اختیاری این دستگاه شستشو را به یک دستگاه موجود (برای مثال پلاگین هوشمند یا خود دستگاه) پیوند دهید. سپس دستگاه WashData به عنوان “Connected Through” نشان داده می شود. برای نگه داشتن اطلاعات به عنوان یک دستگاه مستقل خالی بگذارید.", + "data": { + "linked_device": "دستگاه متصل" + }, + "data_description": { + "linked_device": "دستگاه را برای اتصال WashData انتخاب کنید. این یک مرجع “Connected Through” را در رجیستر دستگاه اضافه می کند؛ WashData کارت و دستگاه خود را نگه می دارد. انتخاب را برای حذف لینک روشن کنید." + } + } + } + }, + "diagnostics": { + "title": "تشخیص و نگهداری", + "description": "اقدامات تعمیر و نگهداری مانند ادغام چرخه های تکه تکه یا انتقال داده های ذخیره شده را اجرا کنید.\n\n**استفاده از فضای ذخیره سازی**\n\n- حجم فایل: {file_size_kb} کیلوبایت\n- چرخه ها: {cycle_count}\n- نمایه ها: {profile_count}\n- ردیابی اشکال: {debug_count}", + "menu_options": { + "reprocess_history": "نگهداری: پردازش مجدد و بهینه سازی داده ها", + "clear_debug_data": "پاک کردن داده های اشکال زدایی (آزاد کردن فضا)", + "wipe_history": "پاک کردن تمام داده‌های این دستگاه (غیرقابل برگشت)", + "export_import": "صادرات/وارد کردن JSON با تنظیمات (کپی/پیست)", + "menu_back": "← بازگشت" + } + }, + "clear_debug_data": { + "title": "پاک کردن دیباگ داده ها", + "description": "آیا مطمئن هستید که می خواهید همه ردیابی اشکال زدایی ذخیره شده را حذف کنید؟ این کار فضا را آزاد می کند اما اطلاعات رتبه بندی دقیق را از چرخه های گذشته حذف می کند." + }, + "manage_cycles": { + "title": "چرخه ها را مدیریت کنید", + "description": "چرخه های اخیر:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "برچسب‌گذاری خودکار چرخه‌های قدیمی", + "select_cycle_to_label": "چرخه خاص برچسب", + "select_cycle_to_delete": "چرخه را حذف کنید", + "interactive_editor": "ادغام / تقسیم ویرایشگر تعاملی", + "trim_cycle_select": "داده های چرخه برش", + "menu_back": "← بازگشت" + } + }, + "manage_cycles_empty": { + "title": "چرخه ها را مدیریت کنید", + "description": "هنوز هیچ چرخه ای ثبت نشده است.", + "menu_options": { + "auto_label_cycles": "برچسب‌گذاری خودکار چرخه‌های قدیمی", + "menu_back": "← بازگشت" + } + }, + "manage_profiles": { + "title": "مدیریت پروفایل ها", + "description": "خلاصه نمایه:\n{profile_summary}", + "menu_options": { + "create_profile": "ایجاد نمایه جدید", + "edit_profile": "ویرایش/تغییر نام نمایه", + "delete_profile_select": "حذف نمایه", + "profile_stats": "آمار پروفایل", + "cleanup_profile": "پاک کردن تاریخچه - نمودار و حذف", + "assign_profile_phases_select": "محدوده های فاز را تعیین کنید", + "menu_back": "← بازگشت" + } + }, + "manage_profiles_empty": { + "title": "مدیریت پروفایل ها", + "description": "هنوز هیچ پروفایلی ایجاد نشده است.", + "menu_options": { + "create_profile": "ایجاد نمایه جدید", + "menu_back": "← بازگشت" + } + }, + "manage_phase_catalog": { + "title": "مدیریت کاتالوگ فاز", + "description": "کاتالوگ فاز (پیش‌فرض این دستگاه + مراحل سفارشی):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "فاز جدید ایجاد کنید", + "phase_catalog_edit_select": "فاز ویرایش", + "phase_catalog_delete": "حذف فاز", + "menu_back": "← بازگشت" + } + }, + "phase_catalog_create": { + "title": "ایجاد فاز سفارشی", + "description": "یک نام فاز و شرح سفارشی ایجاد کنید و نوع دستگاه را انتخاب کنید.", + "data": { + "target_device_type": "نوع دستگاه مورد نظر", + "phase_name": "نام فاز", + "phase_description": "شرح فاز" + } + }, + "phase_catalog_edit_select": { + "title": "ویرایش فاز سفارشی", + "description": "یک فاز سفارشی را برای ویرایش انتخاب کنید.", + "data": { + "phase_name": "فاز سفارشی" + } + }, + "phase_catalog_edit": { + "title": "ویرایش فاز سفارشی", + "description": "مرحله ویرایش: {phase_name}", + "data": { + "phase_name": "نام فاز", + "phase_description": "شرح فاز" + } + }, + "phase_catalog_delete": { + "title": "حذف فاز سفارشی", + "description": "یک فاز سفارشی را برای حذف انتخاب کنید. هر محدوده اختصاص داده شده با استفاده از این فاز حذف خواهد شد.", + "data": { + "phase_name": "فاز سفارشی" + } + }, + "assign_profile_phases": { + "title": "محدوده های فاز را تعیین کنید", + "description": "پیش نمایش فاز برای نمایه: **{profile_name}**\n\n{timeline_svg}\n\n**محدوده های فعلی:**\n{current_ranges}\n\nاز اقدامات زیر برای افزودن، ویرایش، حذف یا ذخیره محدوده استفاده کنید.", + "menu_options": { + "assign_profile_phases_add": "محدوده فاز را اضافه کنید", + "assign_profile_phases_edit_select": "ویرایش محدوده فاز", + "assign_profile_phases_delete": "حذف محدوده فاز", + "phase_ranges_clear": "پاک کردن همه محدوده ها", + "assign_profile_phases_auto_detect": "تشخیص خودکار فازها", + "phase_ranges_save": "ذخیره و برگردانید", + "menu_back": "← بازگشت" + } + }, + "assign_profile_phases_add": { + "title": "محدوده فاز را اضافه کنید", + "description": "یک محدوده فاز را به پیش نویس فعلی اضافه کنید.", + "data": { + "phase_name": "فاز", + "start_min": "دقیقه شروع", + "end_min": "دقیقه پایانی" + } + }, + "assign_profile_phases_edit_select": { + "title": "ویرایش محدوده فاز", + "description": "محدوده ای را برای ویرایش انتخاب کنید.", + "data": { + "range_index": "محدوده فاز" + } + }, + "assign_profile_phases_edit": { + "title": "ویرایش محدوده فاز", + "description": "محدوده فاز انتخابی را به روز کنید.", + "data": { + "phase_name": "فاز", + "start_min": "دقیقه شروع", + "end_min": "دقیقه پایانی" + } + }, + "assign_profile_phases_delete": { + "title": "حذف محدوده فاز", + "description": "محدوده ای را برای حذف از پیش نویس انتخاب کنید.", + "data": { + "range_index": "محدوده فاز" + } + }, + "assign_profile_phases_auto_detect": { + "title": "فازهای شناسایی خودکار", + "description": "نمایه: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} مرحله (های) به طور خودکار شناسایی شد.** اقدامی را در زیر انتخاب کنید.", + "data": { + "action": "اقدام" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "نام فازهای شناسایی شده", + "description": "نمایه: **{profile_name}**\n\n**{detected_count} مرحله شناسایی شد:**\n{ranges_summary}\n\nبرای هر مرحله یک نام تعیین کنید." + }, + "assign_profile_phases_select": { + "title": "محدوده های فاز را تعیین کنید", + "description": "یک نمایه را برای پیکربندی محدوده فاز انتخاب کنید.", + "data": { + "profile": "نمایه" + } + }, + "profile_stats": { + "title": "آمار پروفایل", + "description": "{stats_table}" + }, + "create_profile": { + "title": "ایجاد نمایه جدید", + "description": "یک نمایه جدید به صورت دستی یا از یک چرخه گذشته ایجاد کنید.\n\nنمونه‌های نام نمایه: 'ظریف'، 'سنگین'، 'شستشوی سریع'", + "data": { + "profile_name": "نام نمایه", + "reference_cycle": "چرخه مرجع (اختیاری)", + "manual_duration": "مدت زمان دستی (دقیقه)" + } + }, + "edit_profile": { + "title": "ویرایش نمایه", + "description": "نمایه ای را برای تغییر نام انتخاب کنید", + "data": { + "profile": "نمایه" + } + }, + "rename_profile": { + "title": "ویرایش جزئیات نمایه", + "description": "نمایه فعلی: {current_name}", + "data": { + "new_name": "نام نمایه", + "manual_duration": "مدت زمان دستی (دقیقه)" + } + }, + "delete_profile_select": { + "title": "حذف نمایه", + "description": "نمایه ای را برای حذف انتخاب کنید", + "data": { + "profile": "نمایه" + } + }, + "delete_profile_confirm": { + "title": "حذف نمایه را تایید کنید", + "description": "⚠️ با این کار نمایه برای همیشه حذف می شود: {profile_name}", + "data": { + "unlabel_cycles": "برچسب را از چرخه ها با استفاده از این نمایه حذف کنید" + } + }, + "auto_label_cycles": { + "title": "برچسب‌گذاری خودکار چرخه‌های قدیمی", + "description": "کل {total_count} چرخه پیدا شد. نمایه ها: {profiles}", + "data": { + "confidence_threshold": "حداقل اطمینان (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Cycle to Label را انتخاب کنید", + "description": "✓ = کامل شد، ⚠ = از سر گرفته شد، ✗ = قطع شد", + "data": { + "cycle_id": "چرخه" + } + }, + "select_cycle_to_delete": { + "title": "چرخه را حذف کنید", + "description": "⚠️ با این کار چرخه انتخاب شده برای همیشه حذف می شود", + "data": { + "cycle_id": "چرخه برای حذف" + } + }, + "label_cycle": { + "title": "چرخه برچسب", + "description": "{cycle_info}", + "data": { + "profile_name": "نمایه", + "new_profile_name": "نام نمایه جدید (در صورت ایجاد)" + } + }, + "post_process": { + "title": "تاریخچه فرآیند (ادغام / تقسیم)", + "description": "با ادغام اجراهای تکه تکه شده و تقسیم چرخه های ادغام شده نادرست در یک پنجره زمانی، تاریخچه چرخه را پاک کنید. تعداد ساعات نگاه کردن به گذشته را وارد کنید (برای همه از 999999 استفاده کنید).", + "data": { + "time_range": "پنجره نگاه (ساعت)", + "gap_seconds": "ادغام/تقسیم شکاف (ثانیه)" + }, + "data_description": { + "time_range": "تعداد ساعات اسکن برای ادغام چرخه های تکه تکه شده. از 999999 برای پردازش تمام داده های تاریخی استفاده کنید.", + "gap_seconds": "آستانه برای تصمیم گیری تقسیم در مقابل ادغام. شکاف های کوتاه تر از این ادغام می شوند. شکاف های طولانی تر از این تقسیم می شوند. این را پایین بیاورید تا چرخه ای که به اشتباه ادغام شده است، تقسیم شود." + } + }, + "cleanup_profile": { + "title": "پاک کردن تاریخچه - نمایه را انتخاب کنید", + "description": "نمایه ای را برای تجسم انتخاب کنید. این یک نمودار را ایجاد می کند که تمام چرخه های گذشته را برای این نمایه نشان می دهد تا به شناسایی نقاط پرت کمک کند.", + "data": { + "profile": "نمایه" + } + }, + "cleanup_select": { + "title": "پاک کردن تاریخچه - نمودار و حذف", + "description": "![گراف]({graph_url})\n\n**تجسم {profile_name}**\n\nنمودار چرخه های جداگانه را به صورت خطوط رنگی نشان می دهد. خطوط پرت (خطوط دور از گروه) و مطابقت با رنگ آنها را در لیست زیر شناسایی کنید تا آنها را حذف کنید.\n\n** چرخه هایی را برای حذف دائمی انتخاب کنید:**", + "data": { + "cycles_to_delete": "چرخه‌های حذف (تطابق رنگ‌ها را بررسی کنید)" + } + }, + "interactive_editor": { + "title": "ادغام / تقسیم ویرایشگر تعاملی", + "description": "یک اقدام دستی را برای انجام در تاریخچه چرخه خود انتخاب کنید.", + "menu_options": { + "editor_split": "تقسیم یک چرخه (یافتن شکاف ها)", + "editor_merge": "چرخه های ادغام (پیوستن قطعات)", + "editor_delete": "حذف چرخه(های)", + "menu_back": "← بازگشت" + } + }, + "editor_select": { + "title": "چرخه ها را انتخاب کنید", + "description": "چرخه(های) مورد پردازش را انتخاب کنید.\n\n{info_text}", + "data": { + "selected_cycles": "چرخه ها" + } + }, + "editor_configure": { + "title": "پیکربندی و اعمال", + "description": "{preview_md}", + "data": { + "confirm_action": "اقدام", + "merged_profile": "نمایه نتیجه", + "new_profile_name": "نام نمایه جدید", + "confirm_commit": "بله، من این اقدام را تایید می کنم", + "segment_0_profile": "نمایه بخش 1", + "segment_1_profile": "نمایه بخش 2", + "segment_2_profile": "نمایه بخش 3", + "segment_3_profile": "نمایه بخش 4", + "segment_4_profile": "نمایه بخش 5", + "segment_5_profile": "نمایه بخش 6", + "segment_6_profile": "نمایه بخش 7", + "segment_7_profile": "نمایه بخش 8", + "segment_8_profile": "نمایه بخش 9", + "segment_9_profile": "نمایه بخش 10" + } + }, + "editor_split_params": { + "title": "پیکربندی تقسیم", + "description": "حساسیت را برای تقسیم این چرخه تنظیم کنید. هر فاصله بیکار طولانی تر از این باعث شکاف می شود.", + "data": { + "split_mode": "روش تقسیم" + } + }, + "editor_split_auto_params": { + "title": "پیکربندی تقسیم خودکار", + "description": "حساسیت تقسیم این چرخه را تنظیم کنید. هر فاصلهٔ بیکاری طولانی‌تر از این باعث تقسیم خواهد شد.", + "data": { + "min_gap_seconds": "آستانهٔ فاصلهٔ تقسیم (ثانیه)" + } + }, + "editor_split_manual_params": { + "title": "مهرهای زمانی تقسیم دستی", + "description": "{preview_md}بازهٔ چرخه: **{cycle_start_wallclock} → {cycle_end_wallclock}** در {cycle_date}.\n\nیک یا چند مهر زمانی تقسیم (`HH:MM` یا `HH:MM:SS`) را وارد کنید، هر کدام در یک خط. چرخه در هر مهر زمانی بریده می‌شود — N مهر زمانی N+1 بخش تولید می‌کند.", + "data": { + "split_timestamps": "تقسیم مهرهای زمانی" + } + }, + "reprocess_history": { + "title": "نگهداری: پردازش مجدد و بهینه سازی داده ها", + "description": "پاکسازی عمیق داده های تاریخی را انجام می دهد:\n1. خوانش های توان صفر (سکوت) را کاهش می دهد.\n2. زمان / مدت چرخه را رفع می کند.\n3. همه مدل های آماری (پاکت) را دوباره محاسبه می کند.\n\n**این را برای رفع مشکلات داده یا اعمال الگوریتم های جدید اجرا کنید.**" + }, + "wipe_history": { + "title": "پاک کردن تاریخچه (فقط آزمایش)", + "description": "⚠️ با این کار همه چرخه ها و نمایه های ذخیره شده برای این دستگاه برای همیشه حذف می شود. این قابل واگرد نیست!" + }, + "export_import": { + "title": "صادرات/وارد کردن JSON", + "description": "چرخه‌ها، نمایه‌ها، و تنظیمات و کپی/پیست کردن برای این دستگاه. زیر را صادر کنید یا برای وارد کردن JSON جای‌گذاری کنید.", + "data": { + "mode": "اقدام", + "json_payload": "پیکربندی کامل JSON" + }, + "data_description": { + "mode": "برای کپی کردن داده‌ها و تنظیمات فعلی، صادرات را انتخاب کنید، یا برای جای‌گذاری داده‌های صادر شده از دستگاه WashData دیگری، Import را انتخاب کنید.", + "json_payload": "برای صادرات: این JSON را کپی کنید (شامل چرخه‌ها، نمایه‌ها و تمام تنظیمات دقیق‌شده). برای واردات: JSON صادر شده از WashData را بچسبانید." + } + }, + "record_cycle": { + "title": "چرخه ضبط", + "description": "یک چرخه را به صورت دستی ضبط کنید تا یک نمایه تمیز بدون تداخل ایجاد کنید.\n\nوضعیت: **{status}**\nمدت زمان: {duration} ثانیه\nنمونه ها: {samples}", + "menu_options": { + "record_refresh": "وضعیت تازه کردن", + "record_stop": "توقف ضبط (ذخیره و پردازش)", + "record_start": "شروع ضبط جدید", + "record_process": "پردازش آخرین ضبط (برش و ذخیره)", + "record_discard": "حذف آخرین ضبط", + "menu_back": "← بازگشت" + } + }, + "record_process": { + "title": "فرآیند ضبط", + "description": "![گراف]({graph_url})\n\n** نمودار ضبط خام را نشان می دهد.** آبی = نگه دارید، قرمز = برش پیشنهادی.\n*گراف ثابت است و با تغییرات فرم به روز نمی شود.*\n\nضبط متوقف شد. برش‌ها با نرخ نمونه‌برداری شناسایی‌شده (~{sampling_rate}s) تراز می‌شوند.\n\nمدت زمان خام: {duration} ثانیه\nنمونه ها: {samples}", + "data": { + "head_trim": "برش شروع (ثانیه)", + "tail_trim": "پایان برش (ثانیه)", + "save_mode": "ذخیره مقصد", + "profile_name": "نام نمایه" + } + }, + "learning_feedbacks": { + "title": "بازخوردهای یادگیری", + "description": "بازخوردهای معلق: {count}\n\n{pending_table}\n\nیک درخواست بررسی چرخه را برای پردازش انتخاب کنید.", + "menu_options": { + "learning_feedbacks_pick": "بررسی یک بازخورد معلق", + "learning_feedbacks_dismiss_all": "رد کردن همه بازخوردهای معلق", + "menu_back": "← بازگشت" + } + }, + "learning_feedbacks_pick": { + "title": "بازخورد برای بررسی را انتخاب کنید", + "description": "یک درخواست بررسی چرخه را برای باز کردن انتخاب کنید.", + "data": { + "selected_feedback": "چرخه را برای بررسی انتخاب کنید" + } + }, + "learning_feedbacks_empty": { + "title": "بازخوردهای یادگیری", + "description": "هیچ درخواست بازخورد معلقی پیدا نشد.", + "menu_options": { + "menu_back": "← بازگشت" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "رد کردن همه بازخوردهای در انتظار", + "description": "شما در آستانهٔ رد کردن **{count}** درخواست بازخورد در انتظار هستید. چرخه‌های ردشده در سابقهٔ شما باقی می‌مانند، اما سیگنال یادگیری جدیدی اضافه نخواهند کرد. این کار قابل بازگشت نیست.\n\n✅ کادر زیر را علامت بزنید و روی **ارسال** کلیک کنید تا همه رد شوند.\n❌ آن را بدون علامت بگذارید و روی **ارسال** کلیک کنید تا لغو شود و برگردید.", + "data": { + "confirm_dismiss_all": "تأیید: رد کردن همه درخواست‌های بازخورد معلق" + } + }, + "resolve_feedback": { + "title": "بازخورد را حل کنید", + "description": "{comparison_img}{candidates_table}\n**نمایه شناسایی شده**: {detected_profile} ({confidence_pct}%)\n**مدت زمان تخمینی**: {est_duration_min} دقیقه\n**مدت زمان واقعی **: {act_duration_min} دقیقه\n\n__یک اقدام زیر را انتخاب کنید:__", + "data": { + "action": "دوست دارید چه کار کنید؟", + "corrected_profile": "برنامه صحیح (در صورت تصحیح)", + "corrected_duration": "مدت زمان صحیح بر حسب ثانیه (در صورت تصحیح)" + } + }, + "trim_cycle_select": { + "title": "چرخه برش - چرخه را انتخاب کنید", + "description": "یک چرخه را برای برش انتخاب کنید. ✓ = کامل شد، ⚠ = از سر گرفته شد، ✗ = قطع شد", + "data": { + "cycle_id": "چرخه" + } + }, + "trim_cycle": { + "title": "چرخه برش", + "description": "پنجره فعلی: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "اقدام" + } + }, + "trim_cycle_start": { + "title": "تنظیم نقطه شروع کوتاه‌سازی", + "description": "پنجره چرخه: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nشروع فعلی: **{current_wallclock}** (+{current_offset_min} دقیقه از شروع چرخه)\n\nبا استفاده از انتخابگر ساعت زیر، زمان شروع جدیدی را انتخاب کنید.", + "data": { + "trim_start_time": "زمان شروع جدید", + "trim_start_min": "شروع جدید (دقیقه از شروع چرخه)" + } + }, + "trim_cycle_end": { + "title": "تنظیم نقطه پایان کوتاه‌سازی", + "description": "پنجره چرخه: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nپایان فعلی: **{current_wallclock}** (+{current_offset_min} دقیقه از شروع چرخه)\n\nبا استفاده از انتخابگر ساعت زیر، زمان پایان جدیدی را انتخاب کنید.", + "data": { + "trim_end_time": "زمان پایان جدید", + "trim_end_min": "پایان جدید (دقیقه از شروع چرخه)" + } + } + }, + "error": { + "import_failed": "واردات انجام نشد. لطفاً یک JSON صادرکننده WashData معتبر جای‌گذاری کنید.", + "select_exactly_one": "برای این اقدام، دقیقاً یک چرخه را انتخاب کنید.", + "select_at_least_one": "برای این اقدام، حداقل یک چرخه را انتخاب کنید.", + "select_at_least_two": "برای این اقدام، حداقل دو چرخه را انتخاب کنید.", + "profile_exists": "نمایه ای با این نام از قبل وجود دارد", + "rename_failed": "تغییر نام نمایه انجام نشد. ممکن است نمایه وجود نداشته باشد یا نام جدیدی از قبل گرفته شده باشد.", + "assignment_failed": "تخصیص نمایه به چرخه انجام نشد", + "duplicate_phase": "فازی با این نام از قبل وجود دارد.", + "phase_not_found": "فاز انتخابی یافت نشد.", + "invalid_phase_name": "نام فاز نمی تواند خالی باشد.", + "phase_name_too_long": "نام فاز خیلی طولانی است.", + "invalid_phase_range": "محدوده فاز نامعتبر است. پایان باید بزرگتر از شروع باشد.", + "invalid_phase_timestamp": "مهر زمانی نامعتبر است. از قالب YYYY-MM-DD HH:MM یا HH:MM استفاده کنید.", + "overlapping_phase_ranges": "محدوده فازها همپوشانی دارند. محدوده ها را تنظیم کنید و دوباره امتحان کنید.", + "incomplete_phase_row": "هر ردیف فاز باید شامل یک فاز به اضافه مقادیر شروع/پایان کامل برای حالت انتخاب شده باشد.", + "timestamp_mode_cycle_required": "لطفاً هنگام استفاده از حالت مهر زمانی، یک چرخه منبع را انتخاب کنید.", + "no_phase_ranges": "هنوز هیچ محدوده فازی برای آن اقدام موجود نیست.", + "feedback_notification_title": "WashData: چرخه تأیید ({device})", + "feedback_notification_message": "**دستگاه**: {device}\n**برنامه**: {program} ({confidence}٪ اطمینان)\n**زمان**: {time}\n\nWashData برای تأیید این چرخه شناسایی شده به کمک شما نیاز دارد.\n\nلطفاً برای تأیید یا تصحیح این نتیجه به **تنظیمات > دستگاه‌ها و خدمات > WashData > پیکربندی > بازخوردهای یادگیری** بروید.", + "suggestions_ready_notification_title": "WashData: تنظیمات پیشنهادی آماده ({device})", + "suggestions_ready_notification_message": "حسگر **تنظیمات پیشنهادی** اکنون **{count}** توصیه های قابل اجرا را گزارش می دهد.\n\nبرای بررسی و اعمال آنها: **تنظیمات > دستگاه‌ها و خدمات > WashData > پیکربندی > تنظیمات پیشرفته > اعمال مقادیر پیشنهادی**.\n\nپیشنهادات اختیاری هستند و قبل از ذخیره برای بررسی نشان داده می شوند.", + "trim_range_invalid": "شروع باید قبل از پایان باشد. لطفا نقاط برش خود را تنظیم کنید.", + "trim_failed": "برش انجام نشد. ممکن است چرخه دیگر وجود نداشته باشد یا پنجره خالی باشد.", + "invalid_split_timestamp": "مهر زمانی نامعتبر است یا خارج از پنجره چرخه است. از HH:MM یا HH:MM:SS در محدوده پنجره چرخه استفاده کنید.", + "no_split_segments_found": "هیچ بخش معتبری از این مهرهای زمانی تولید نشد. مطمئن شوید هر مهر زمانی داخل پنجره چرخه است و هر بخش دست‌کم 1 دقیقه طول دارد.", + "auto_tune_suggestion": "{device_type} {device_title} چرخه های ارواح را شناسایی کرد. حداقل تغییر توان پیشنهادی: {current_min}W -> {new_min}W (به طور خودکار اعمال نمی شود).", + "auto_tune_title": "تنظیم خودکار WashData", + "auto_tune_fallback": "{device_type} {device_title} چرخه های ارواح را شناسایی کرد. حداقل تغییر توان پیشنهادی: {current_min}W -> {new_min}W (به طور خودکار اعمال نمی شود)." + }, + "abort": { + "no_cycles_found": "هیچ چرخه ای یافت نشد", + "no_split_segments_found": "هیچ بخش قابل تقسیم در چرخه انتخاب‌شده پیدا نشد.", + "cycle_not_found": "بارگذاری چرخه انتخاب‌شده ممکن نشد.", + "no_power_data": "هیچ داده ردیابی نیرو برای چرخه انتخابی موجود نیست.", + "no_profiles_found": "هیچ پروفایلی پیدا نشد ابتدا یک نمایه ایجاد کنید.", + "no_profiles_for_matching": "هیچ نمایه ای برای مطابقت در دسترس نیست. ابتدا پروفایل ایجاد کنید", + "no_unlabeled_cycles": "همه چرخه ها قبلاً برچسب گذاری شده اند", + "no_suggestions": "هنوز مقادیر پیشنهادی موجود نیست. چند چرخه را اجرا کنید و دوباره امتحان کنید.", + "no_predictions": "بدون پیش بینی", + "no_custom_phases": "هیچ فاز سفارشی یافت نشد.", + "reprocess_success": "{count} چرخه با موفقیت دوباره پردازش شد.", + "debug_data_cleared": "داده‌های اشکال‌زدایی از چرخه‌های {count} با موفقیت پاک شد." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "شروع چرخه", + "cycle_finish": "چرخه پایان", + "cycle_live": "پیشرفت زنده" + } + }, + "common_text": { + "options": { + "unlabeled": "(بدون برچسب)", + "create_new_profile": "ایجاد نمایه جدید", + "remove_label": "حذف برچسب", + "no_reference_cycle": "(بدون چرخه مرجع)", + "all_device_types": "همه انواع دستگاه", + "current_suffix": "(جاری)", + "editor_select_info": "1 چرخه را برای تقسیم یا 2+ چرخه را برای ادغام انتخاب کنید.", + "editor_select_info_split": "برای تقسیم، 1 چرخه را انتخاب کنید.", + "editor_select_info_merge": "برای ادغام، 2+ چرخه را انتخاب کنید.", + "editor_select_info_delete": "برای حذف، 1+ چرخه را انتخاب کنید.", + "no_cycles_recorded": "هنوز هیچ چرخه ای ثبت نشده است.", + "profile_summary_line": "- **{name}**: {count} چرخه، میانگین {avg} دقیقه", + "no_profiles_created": "هنوز هیچ پروفایلی ایجاد نشده است.", + "phase_preview": "پیش نمایش فاز", + "phase_preview_no_curve": "منحنی نمایه متوسط ​​هنوز در دسترس نیست. چرخه های بیشتری را برای این نمایه اجرا کنید/برچسب بزنید.", + "average_power_curve": "منحنی توان متوسط", + "unit_min": "دقیقه", + "profile_option_fmt": "{name} ({count} چرخه، ~{duration} دقیقه به طور میانگین)", + "profile_option_short_fmt": "{name} ({count} چرخه، ~{duration} دقیقه)", + "deleted_cycles_from_profile": "{count} چرخه از {profile} حذف شد.", + "not_enough_data_graph": "داده های کافی برای تولید نمودار وجود ندارد.", + "no_profiles_found": "هیچ پروفایلی پیدا نشد", + "cycle_info_fmt": "چرخه: {start}، {duration} دقیقه، فعلی: {label}", + "top_candidates_header": "### نامزدهای برتر", + "tbl_profile": "نمایه", + "tbl_confidence": "اطمینان", + "tbl_correlation": "همبستگی", + "tbl_duration_match": "تطابق مدت", + "detected_profile": "نمایه شناسایی شده", + "estimated_duration": "مدت زمان تخمینی", + "actual_duration": "مدت زمان واقعی", + "choose_action": "__یک اقدام زیر را انتخاب کنید:__", + "tbl_count": "تعداد", + "tbl_avg": "میانگین", + "tbl_min": "حداقل", + "tbl_max": "حداکثر", + "tbl_energy_avg": "انرژی (میانگین)", + "tbl_energy_total": "انرژی (کل)", + "tbl_consistency": "یکنواختی", + "tbl_last_run": "آخرین اجرا", + "graph_legend_title": "افسانه نمودار", + "graph_legend_body": "نوار آبی نشان دهنده حداقل و حداکثر بازه مصرف توان مشاهده شده است. خط منحنی توان متوسط ​​را نشان می دهد.", + "recording_preview": "پیش نمایش ضبط", + "trim_start": "برش شروع", + "trim_end": "پایان را برش دهید", + "split_preview_title": "پیش نمایش تقسیم", + "split_preview_found_fmt": "{count} بخش پیدا شد.", + "split_preview_confirm_fmt": "روی تأیید کلیک کنید تا این چرخه به {count} چرخه جداگانه تقسیم شود.", + "merge_preview_title": "پیش نمایش ادغام", + "merge_preview_joining_fmt": "پیوستن به {count} چرخه. شکاف ها با قرائت های 0W پر می شوند.", + "editor_delete_preview_title": "پیش‌نمایش حذف", + "editor_delete_preview_intro": "چرخه‌های انتخاب‌شده برای همیشه حذف خواهند شد:", + "editor_delete_preview_confirm": "برای حذف دائمی این رکوردهای چرخه، روی تأیید کلیک کنید.", + "no_power_preview": "*داده برق برای پیش نمایش موجود نیست.*", + "profile_comparison": "مقایسه پروفایل", + "actual_cycle_label": "این چرخه (واقعی)", + "storage_usage_header": "استفاده از فضای ذخیره سازی", + "storage_file_size": "اندازه فایل", + "storage_cycles": "چرخه ها", + "storage_profiles": "پروفایل ها", + "storage_debug_traces": "ردیابی اشکال زدایی", + "overview_suffix": "نمای کلی", + "phase_preview_for_profile": "پیش نمایش فاز برای نمایه", + "current_ranges_header": "محدوده های فعلی", + "assign_phases_help": "از اقدامات زیر برای افزودن، ویرایش، حذف یا ذخیره محدوده استفاده کنید.", + "profile_summary_header": "خلاصه نمایه", + "recent_cycles_header": "چرخه های اخیر", + "trim_cycle_preview_title": "پیش نمایش برش چرخه", + "trim_cycle_preview_no_data": "هیچ داده توان برای این چرخه در دسترس نیست.", + "trim_cycle_preview_kept_suffix": "نگهداری می شود", + "table_program": "برنامه", + "table_when": "زمان", + "table_length": "مدت", + "table_match": "تطابق", + "table_profile": "نمایه", + "table_cycles": "چرخه ها", + "table_avg_length": "میانگین مدت", + "table_last_run": "آخرین اجرا", + "table_avg_energy": "میانگین انرژی", + "table_detected_program": "برنامه شناسایی‌شده", + "table_confidence": "اطمینان", + "table_reported": "گزارش‌شده", + "phase_builtin_suffix": "(توکار)", + "phase_none_available": "هیچ فازی در دسترس نیست", + "settings_deprecation_warning": "⚠️ **نوع دستگاه منسوخ شده:** {device_type} برای حذف در نسخه بعدی برنامه ریزی شده است. خط لوله تطبیق WashData نتایج قابل اعتمادی برای این کلاس لوازم خانگی ایجاد نمی کند. ادغام شما در طول دوره انحلال به کار خود ادامه می دهد. برای خاموش کردن این هشدار، **نوع دستگاه** زیر را به یکی از انواع پشتیبانی شده (ماشین لباسشویی، خشک کن، ماشین لباسشویی و خشک کن ترکیبی، ماشین ظرفشویی، سرخ کن هوا، نان ساز یا پمپ) یا اگر دستگاه شما با هیچ یک از انواع پشتیبانی شده مطابقت ندارد، به **سایر (پیشرفته)** تغییر دهید. **سایر (پیشرفته)** پیش‌فرض‌های عمدی عمومی را ارسال می‌کند که برای هیچ دستگاه خاصی تنظیم نشده‌اند، بنابراین باید آستانه‌ها، زمان‌بندی‌ها و پارامترهای مطابقت را خودتان پیکربندی کنید. تمام تنظیمات موجود شما هنگام تغییر حفظ می شود.", + "phase_other_device_types": "انواع دیگر دستگاه:" + } + }, + "interactive_editor_action": { + "options": { + "split": "تقسیم یک چرخه (یافتن شکاف ها)", + "merge": "چرخه های ادغام (پیوستن قطعات)", + "delete": "حذف چرخه(های)" + } + }, + "split_mode": { + "options": { + "auto": "شکاف‌های بیکاری را خودکار تشخیص دهید", + "manual": "مهر زمانی دستی" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "نگهداری: پردازش مجدد و بهینه سازی داده ها", + "clear_debug_data": "پاک کردن داده های اشکال زدایی (آزاد کردن فضا)", + "wipe_history": "پاک کردن تمام داده‌های این دستگاه (غیرقابل برگشت)", + "export_import": "صادرات/وارد کردن JSON با تنظیمات (کپی/پیست)" + } + }, + "export_import_mode": { + "options": { + "export": "صادرات همه داده ها", + "import": "وارد کردن/ادغام داده ها" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "برچسب‌گذاری خودکار چرخه‌های قدیمی", + "select_cycle_to_label": "چرخه خاص برچسب", + "select_cycle_to_delete": "چرخه را حذف کنید", + "interactive_editor": "ادغام / تقسیم ویرایشگر تعاملی", + "trim_cycle": "داده های چرخه برش" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "ایجاد نمایه جدید", + "edit_profile": "ویرایش/تغییر نام نمایه", + "delete_profile": "حذف نمایه", + "profile_stats": "آمار پروفایل", + "cleanup_profile": "پاک کردن تاریخچه - نمودار و حذف", + "assign_phases": "محدوده های فاز را تعیین کنید" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "فاز جدید ایجاد کنید", + "edit_custom_phase": "فاز ویرایش", + "delete_custom_phase": "حذف فاز" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "محدوده فاز را اضافه کنید", + "edit_range": "ویرایش محدوده فاز", + "delete_range": "حذف محدوده فاز", + "clear_ranges": "پاک کردن همه محدوده ها", + "auto_detect_ranges": "تشخیص خودکار فازها", + "save_ranges": "ذخیره و برگردانید" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "وضعیت تازه کردن", + "stop_recording": "توقف ضبط (ذخیره و پردازش)", + "start_recording": "شروع ضبط جدید", + "process_recording": "پردازش آخرین ضبط (برش و ذخیره)", + "discard_recording": "حذف آخرین ضبط" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "ایجاد نمایه جدید", + "existing_profile": "به نمایه موجود اضافه کنید", + "discard": "حذف ضبط" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "تأیید - تشخیص صحیح", + "correct": "درست - برنامه/مدت را انتخاب کنید", + "ignore": "نادیده گرفتن - چرخه مثبت کاذب/نویز", + "delete": "حذف - حذف از تاریخچه" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "نام و اعمال مراحل", + "cancel": "بدون تغییر به عقب برگردید" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "نقطه شروع را تنظیم کنید", + "set_end": "نقطه پایان را تنظیم کنید", + "reset": "بازنشانی به مدت زمان کامل", + "apply": "Trim را اعمال کنید", + "cancel": "لغو کنید" + } + }, + "device_type": { + "options": { + "washing_machine": "ماشین لباسشویی", + "dryer": "خشک کن", + "washer_dryer": "ترکیب لباسشویی و خشک کن", + "dishwasher": "ماشین ظرفشویی", + "coffee_machine": "دستگاه قهوه ساز (منسوخ شده)", + "ev": "خودروی برقی (منسوخ شده)", + "air_fryer": "سرخ کن هوا", + "heat_pump": "پمپ حرارتی (منسوخ شده)", + "bread_maker": "نان ساز", + "pump": "پمپ / پمپ پمپ", + "oven": "فر (منسوخ شده)", + "other": "سایر (پیشرفته)" + } + } + }, + "services": { + "label_cycle": { + "name": "چرخه برچسب", + "description": "یک نمایه موجود را به چرخه گذشته اختصاص دهید یا برچسب را بردارید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی برای برچسب زدن." + }, + "cycle_id": { + "name": "شناسه چرخه", + "description": "شناسه چرخه برای برچسب زدن." + }, + "profile_name": { + "name": "نام نمایه", + "description": "نام یک نمایه موجود (ایجاد نمایه ها در منوی Manage Profiles). برای حذف برچسب خالی بگذارید." + } + } + }, + "create_profile": { + "name": "ایجاد نمایه", + "description": "یک نمایه جدید (به تنهایی یا بر اساس یک چرخه مرجع) ایجاد کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی." + }, + "profile_name": { + "name": "نام نمایه", + "description": "نام نمایه جدید (مثلاً \"Heavy Duty\"، \"Delicates\")." + }, + "reference_cycle_id": { + "name": "شناسه چرخه مرجع", + "description": "شناسه چرخه اختیاری برای استناد به این نمایه." + } + } + }, + "delete_profile": { + "name": "حذف نمایه", + "description": "یک نمایه را حذف کنید و به صورت اختیاری چرخه ها را با استفاده از آن لغو برچسب کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی." + }, + "profile_name": { + "name": "نام نمایه", + "description": "نمایه برای حذف" + }, + "unlabel_cycles": { + "name": "چرخه ها را بدون برچسب", + "description": "با استفاده از این نمایه، برچسب نمایه را از چرخه‌ها حذف کنید." + } + } + }, + "auto_label_cycles": { + "name": "برچسب‌گذاری خودکار چرخه‌های قدیمی", + "description": "با استفاده از تطبیق نمایه، چرخه‌های بدون برچسب را به‌عنوان ماسبق برچسب‌گذاری کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی." + }, + "confidence_threshold": { + "name": "آستانه اعتماد", + "description": "حداقل اطمینان مطابقت (0.50-0.95) برای اعمال برچسب ها." + } + } + }, + "export_config": { + "name": "صادر کردن پیکربندی", + "description": "نمایه‌ها، چرخه‌ها و تنظیمات این دستگاه را به یک فایل JSON (در هر دستگاه) صادر کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی برای صادرات." + }, + "path": { + "name": "مسیر", + "description": "مسیر فایل مطلق اختیاری برای نوشتن (پیش‌فرض /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "پیکربندی را وارد کنید", + "description": "نمایه‌ها، چرخه‌ها و تنظیمات این دستگاه را از یک فایل صادراتی JSON وارد کنید (تنظیمات ممکن است بازنویسی شوند).", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی برای وارد کردن." + }, + "path": { + "name": "مسیر", + "description": "مسیر مطلق فایل JSON صادراتی برای وارد کردن." + } + } + }, + "submit_cycle_feedback": { + "name": "ارسال بازخورد چرخه", + "description": "پس از یک چرخه کامل، یک برنامه شناسایی خودکار را تأیید یا تصحیح کنید. «entry_id» (پیشرفته) یا «device_id» (توصیه می‌شود) را ارائه دهید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه WashData (به جای entry_id توصیه می شود)." + }, + "entry_id": { + "name": "شناسه ورودی", + "description": "شناسه ورودی پیکربندی برای دستگاه (جایگزین device_id)." + }, + "cycle_id": { + "name": "شناسه چرخه", + "description": "شناسه چرخه نشان داده شده در اعلان بازخورد / سیاهههای مربوط." + }, + "user_confirmed": { + "name": "برنامه شناسایی شده را تأیید کنید", + "description": "اگر برنامه شناسایی شده درست است، true را تنظیم کنید." + }, + "corrected_profile": { + "name": "نمایه تصحیح شده", + "description": "اگر تأیید نشد، نام پروفایل/برنامه صحیح را وارد کنید." + }, + "corrected_duration": { + "name": "مدت زمان تصحیح شده (ثانیه)", + "description": "مدت زمان تصحیح اختیاری در ثانیه." + }, + "notes": { + "name": "یادداشت ها", + "description": "یادداشت های اختیاری در مورد این چرخه." + } + } + }, + "record_start": { + "name": "شروع چرخه ضبط", + "description": "ضبط دستی یک چرخه تمیز را شروع کنید (کلیه منطق منطبق را دور می زند).", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی برای ضبط." + } + } + }, + "record_stop": { + "name": "توقف چرخه ضبط", + "description": "ضبط دستی را متوقف کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه ماشین لباسشویی ضبط را متوقف می کند." + } + } + }, + "trim_cycle": { + "name": "چرخه برش", + "description": "داده های قدرت یک چرخه گذشته را در یک پنجره زمانی خاص برش دهید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه WashData." + }, + "cycle_id": { + "name": "شناسه چرخه", + "description": "شناسه چرخه برای اصلاح." + }, + "trim_start_s": { + "name": "برش شروع (ثانیه)", + "description": "داده های این افست را در چند ثانیه نگه دارید. پیش فرض 0." + }, + "trim_end_s": { + "name": "پایان برش (ثانیه)", + "description": "داده ها را در عرض چند ثانیه تا این میزان افست نگه دارید. پیش فرض = مدت زمان کامل." + } + } + }, + "pause_cycle": { + "name": "چرخه مکث", + "description": "چرخه فعال دستگاه WashData را متوقف کنید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه WashData برای مکث." + } + } + }, + "resume_cycle": { + "name": "چرخه از سرگیری", + "description": "یک چرخه متوقف شده را برای دستگاه WashData از سر بگیرید.", + "fields": { + "device_id": { + "name": "دستگاه", + "description": "دستگاه WashData از سر گرفته می شود." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "در حال کار" + }, + "match_ambiguity": { + "name": "ابهام مطابقت" + } + }, + "sensor": { + "washer_state": { + "name": "وضعیت", + "state": { + "off": "خاموش", + "idle": "در انتظار", + "starting": "در حال شروع", + "running": "در حال کار", + "paused": "مکث کرد", + "user_paused": "توسط کاربر مکث شد", + "ending": "پایان یافتن", + "finished": "تمام شد", + "anti_wrinkle": "ضد چروک", + "delay_wait": "در انتظار شروع", + "interrupted": "قطع شد", + "force_stopped": "اجباری متوقف شد", + "rinse": "آبکشی", + "unknown": "ناشناس", + "clean": "تمیز" + } + }, + "washer_program": { + "name": "برنامه" + }, + "time_remaining": { + "name": "زمان باقی مانده" + }, + "total_duration": { + "name": "مدت زمان کل" + }, + "cycle_progress": { + "name": "پیشرفت" + }, + "current_power": { + "name": "قدرت فعلی" + }, + "elapsed_time": { + "name": "زمان سپری شده" + }, + "debug_info": { + "name": "اطلاعات اشکال زدایی" + }, + "match_confidence": { + "name": "اطمینان تطابق" + }, + "top_candidates": { + "name": "نامزدهای برتر", + "state": { + "none": "هیچ کدام" + } + }, + "current_phase": { + "name": "فاز فعلی" + }, + "wash_phase": { + "name": "فاز" + }, + "profile_cycle_count": { + "name": "تعداد چرخه‌های نمایه {profile_name}", + "unit_of_measurement": "چرخه ها" + }, + "suggestions": { + "name": "تنظیمات پیشنهادی موجود" + }, + "pump_runs_today": { + "name": "کارکرد پمپ (24 ساعت گذشته)" + }, + "cycle_count": { + "name": "تعداد چرخه" + } + }, + "select": { + "program_select": { + "name": "برنامه چرخه", + "state": { + "auto_detect": "تشخیص خودکار" + } + } + }, + "button": { + "force_end_cycle": { + "name": "پایان اجباری چرخه" + }, + "pause_cycle": { + "name": "چرخه مکث" + }, + "resume_cycle": { + "name": "چرخه از سرگیری" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "یک device_id معتبر مورد نیاز است." + }, + "cycle_id_required": { + "message": "یک cycle_id معتبر مورد نیاز است." + }, + "profile_name_required": { + "message": "یک profile_name معتبر لازم است." + }, + "device_not_found": { + "message": "دستگاه پیدا نشد" + }, + "no_config_entry": { + "message": "هیچ ورودی پیکربندی برای دستگاه یافت نشد." + }, + "integration_not_loaded": { + "message": "ادغام برای این دستگاه بارگیری نشده است." + }, + "cycle_not_found_or_no_power": { + "message": "چرخه یافت نشد یا داده برق ندارد." + }, + "trim_failed_empty_window": { + "message": "برش انجام نشد - چرخه یافت نشد، هیچ داده برق یا پنجره حاصل خالی است." + }, + "trim_invalid_range": { + "message": "trim_end_s باید بزرگتر از trim_start_s باشد." + } + } +} diff --git a/custom_components/ha_washdata/translations/fi.json b/custom_components/ha_washdata/translations/fi.json new file mode 100644 index 0000000..fdcf04f --- /dev/null +++ b/custom_components/ha_washdata/translations/fi.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-asetukset", + "description": "Määritä pesukoneesi tai muu laite.\n\nTehoanturi tarvitaan.\n\n**Seuraavaksi sinulta kysytään, haluatko luoda ensimmäisen profiilisi.**", + "data": { + "name": "Laitteen nimi", + "device_type": "Laitteen tyyppi", + "power_sensor": "Tehon anturi", + "min_power": "Minimitehokynnys (W)" + }, + "data_description": { + "name": "Ystävällinen nimi tälle laitteelle (esim. 'pesukone', 'astianpesukone').", + "device_type": "Minkä tyyppinen laite tämä on? Auttaa räätälöimään havaitsemista ja merkitsemistä.", + "power_sensor": "Anturientiteetti, joka raportoi reaaliaikaisen virrankulutuksen (watteina) älypistokkeesta.", + "min_power": "Tämän kynnyksen ylittävät teholukemat (watteina) osoittavat, että laite on käynnissä. Aloita 2 watilla useimmissa laitteissa." + } + }, + "first_profile": { + "title": "Luo ensimmäinen profiili", + "description": "**Sykli** on laitteesi täydellinen suoritus (esim. yksi pyykkikerta). **Profiili** ryhmittelee nämä syklit tyypin mukaan (esim. \"Puuvilla\", \"Pikapesu\"). Profiilin luominen auttaa integraatiota arvioimaan keston välittömästi.\n\nVoit valita tämän profiilin manuaalisesti laitteen ohjaimista, jos sitä ei tunnisteta automaattisesti.\n\n**Jätä \"Profiilin nimi\" tyhjäksi ohittaaksesi tämän vaiheen.**", + "data": { + "profile_name": "Profiilin nimi (valinnainen)", + "manual_duration": "Arvioitu kesto (minuutteja)" + } + } + }, + "error": { + "cannot_connect": "Yhteyden muodostaminen epäonnistui", + "invalid_auth": "Virheellinen todennus", + "unknown": "Odottamaton virhe" + }, + "abort": { + "already_configured": "Laite on jo määritetty" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Tutustu oppimispalautteisiin", + "settings": "Asetukset", + "notifications": "Ilmoitukset", + "manage_cycles": "Hallitse syklejä", + "manage_profiles": "Hallitse profiileja", + "manage_phase_catalog": "Hallitse vaiheluetteloa", + "record_cycle": "Tallennusjakso (manuaalinen)", + "diagnostics": "Diagnostiikka ja huolto" + } + }, + "apply_suggestions": { + "title": "Kopioi ehdotetut arvot", + "description": "Tämä kopioi nykyiset ehdotetut arvot tallennettuihin asetuksiin. Tämä on kertaluonteinen toiminto, eikä automaattista päällekirjoitusta salli.\n\nEhdotetut arvot kopioitavaksi:\n{suggested}", + "data": { + "confirm": "Vahvista kopiointitoiminto" + } + }, + "apply_suggestions_confirm": { + "title": "Tarkista ehdotetut muutokset", + "description": "WashData löytyi **{pending_count}** ehdotettua arvoa käytettäväksi.\n\nMuutokset:\n{changes}\n\n✅ Valitse alla oleva ruutu ja napsauta **Lähetä** ottaaksesi muutokset käyttöön ja tallentaaksesi nämä muutokset välittömästi.\n❌ Jätä se valitsematta ja napsauta **Lähetä** peruuttaaksesi ja palataksesi takaisin.", + "data": { + "confirm_apply_suggestions": "Ota nämä muutokset käyttöön ja tallenna ne" + } + }, + "settings": { + "title": "Asetukset", + "description": "{deprecation_warning}Viritä tunnistuskynnykset, oppiminen ja edistynyt toiminta. Oletusasetukset toimivat hyvin useimmissa asetuksissa.\n\nNykyiset saatavilla olevat suositelut asetukset: {suggestions_count}.\nJos suurempi kuin 0, avaa Lisäasetukset ja käytä Käytä suositeltuja arvoja tarkastellaksesi suosituksia.", + "data": { + "apply_suggestions": "Käytä ehdotettuja arvoja", + "device_type": "Laitteen tyyppi", + "power_sensor": "Tehoanturin kokonaisuus", + "min_power": "Vähimmäisteho (W)", + "off_delay": "Jakson päättymisviive (s)", + "notify_actions": "Ilmoitustoiminnot", + "notify_people": "Viivästyy toimitusta, kunnes nämä ihmiset ovat kotona", + "notify_only_when_home": "Viivästyttää ilmoituksia, kunnes valittu henkilö on kotona", + "notify_fire_events": "Automaatiotapahtumien käynnistäminen", + "notify_start_services": "Syklin aloitus - Ilmoituskohteet", + "notify_finish_services": "Syklin lopetus - Ilmoituskohteet", + "notify_live_services": "Reaaliaikainen edistyminen - Ilmoituskohteet", + "notify_before_end_minutes": "Ilmoitus ennen valmistumista (minuuttia ennen loppua)", + "notify_title": "Ilmoituksen otsikko", + "notify_icon": "Ilmoituskuvake", + "notify_start_message": "Aloitusviestin muoto", + "notify_finish_message": "Lopetusviesti muoto", + "notify_pre_complete_message": "Esivalmistumisviestin muoto", + "show_advanced": "Muokkaa lisäasetuksia (seuraava vaihe)" + }, + "data_description": { + "apply_suggestions": "Valitse tämä valintaruutu kopioidaksesi oppimisalgoritmin ehdottamat arvot lomakkeeseen. Tarkista ennen tallentamista.\n\nSuositeltu (oppimisesta):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Valitse laitetyyppi (pesukone, kuivausrumpu, astianpesukone, kahvinkeitin, sähköauto). Tämä tunniste tallennetaan jokaisen jakson yhteydessä paremman järjestelyn vuoksi.", + "power_sensor": "Anturientiteetti, joka raportoi reaaliaikaisen tehon (watteina). Voit muuttaa tätä milloin tahansa menettämättä historiallisia tietoja – hyödyllistä, jos vaihdat älypistokkeen.", + "min_power": "Tämän kynnyksen ylittävät teholukemat (watteina) osoittavat, että laite on käynnissä. Aloita 2 watilla useimmissa laitteissa.", + "off_delay": "Sekunneissa tasoitetun tehon on pysyttävä pysäytyskynnyksen alapuolella, jotta se merkitsee valmistumista. Oletus: 120s.", + "notify_actions": "Valinnainen Home Assistant -toimintosarja, joka suoritetaan ilmoituksia varten (skriptit, ilmoitus, TTS jne.). Jos asetettu, toiminnot suoritetaan ennen varailmoituspalvelua.", + "notify_people": "Henkilökokonaisuudet, joita käytetään läsnäolon portittamiseen. Kun tämä on käytössä, ilmoitusten toimitus viivästyy, kunnes vähintään yksi valittu henkilö on kotona.", + "notify_only_when_home": "Jos tämä on käytössä, viivytä ilmoituksia, kunnes vähintään yksi valittu henkilö on kotona.", + "notify_fire_events": "Jos käytössä, käynnistä Home Assistant -tapahtumat syklin alkaessa ja lopussa (ha_washdata_cycle_started ja ha_washdata_cycle_ended) automaation ohjaamiseksi.", + "notify_start_services": "Yksi tai useampi palvelu ilmoittaa syklin alkamisesta. Jätä tyhjäksi ohittaaksesi aloitusilmoitukset.", + "notify_finish_services": "Yksi tai useampi ilmoittaa palveluille, kun sykli päättyy tai lähestyy loppuaan. Jätä tyhjäksi ohittaaksesi lopetusilmoitukset.", + "notify_live_services": "Vähintään yksi ilmoitus palveluista saa reaaliaikaisia ​​edistymispäivityksiä syklin aikana (vain mobiilisovellus). Jätä tyhjäksi ohittaaksesi live-päivitykset.", + "notify_before_end_minutes": "Minuuttia ennen jakson arvioitua loppua ilmoituksen käynnistämiseksi. Aseta arvoksi 0 poistaaksesi käytöstä. Oletus: 0.", + "notify_title": "Mukautettu otsikko ilmoituksille. Tukee paikkamerkkiä {device}.", + "notify_icon": "Ilmoituksiin käytettävä kuvake (esim. mdi:washing-machine). Jätä tyhjäksi lähettääksesi ilman kuvaketta.", + "notify_start_message": "Viesti lähetetään, kun sykli alkaa. Tukee paikkamerkkiä {device}.", + "notify_finish_message": "Viesti lähetetään, kun jakso on päättynyt. Tukee paikkamerkkejä {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Toistuvan live-edistyksen teksti päivittyy syklin aikana. Tukee paikkamerkkejä {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Ilmoitukset", + "description": "Määritä ilmoituskanavia, malleja ja valinnaisia ​​reaaliaikaisia ​​mobiili edistymispäivityksiä.", + "data": { + "notify_actions": "Ilmoitustoiminnot", + "notify_people": "Viivästyy toimitusta, kunnes nämä ihmiset ovat kotona", + "notify_only_when_home": "Viivästyttää ilmoituksia, kunnes valittu henkilö on kotona", + "notify_fire_events": "Automaatiotapahtumien käynnistäminen", + "notify_start_services": "Syklin aloitus - Ilmoituskohteet", + "notify_finish_services": "Syklin lopetus - Ilmoituskohteet", + "notify_live_services": "Reaaliaikainen edistyminen - Ilmoituskohteet", + "notify_before_end_minutes": "Ilmoitus ennen valmistumista (minuuttia ennen loppua)", + "notify_live_interval_seconds": "Live-päivitysväli (sekuntia)", + "notify_live_overrun_percent": "Live-päivityksen ylityskorvaus (%)", + "notify_live_chronometer": "Kronometrin ajastin", + "notify_title": "Ilmoituksen otsikko", + "notify_icon": "Ilmoituskuvake", + "notify_start_message": "Käynnistä Viestimuoto", + "notify_finish_message": "Viimeistele viestin muoto", + "notify_pre_complete_message": "Esitäytetty viestin muoto", + "energy_price_entity": "Energian hinta – kokonaisuus (valinnainen)", + "energy_price_static": "Energian hinta – staattinen arvo (valinnainen)", + "go_back": "Palaa takaisin tallentamatta", + "notify_reminder_message": "Muistutusviestin muoto", + "notify_timeout_seconds": "Automaattinen poisto jälkeen (sekuntia)", + "notify_channel": "Ilmoituskanava (status/live/reminder)", + "notify_finish_channel": "Päätetty ilmoituskanava" + }, + "data_description": { + "go_back": "Valitse tämä ja napsauta Lähetä hylätäksesi yllä olevat muutokset ja palataksesi päävalikkoon.", + "notify_actions": "Valinnainen Home Assistant -toimintosarja, joka suoritetaan ilmoituksia varten (skriptit, ilmoitus, TTS jne.). Jos asetettu, toiminnot suoritetaan ennen varailmoituspalvelua.", + "notify_people": "Henkilökokonaisuudet, joita käytetään läsnäolon portittamiseen. Kun tämä on käytössä, ilmoitusten toimitus viivästyy, kunnes vähintään yksi valittu henkilö on kotona.", + "notify_only_when_home": "Jos tämä on käytössä, viivytä ilmoituksia, kunnes vähintään yksi valittu henkilö on kotona.", + "notify_fire_events": "Jos käytössä, käynnistä Home Assistant -tapahtumat syklin alkaessa ja lopussa (ha_washdata_cycle_started ja ha_washdata_cycle_ended) automaation ohjaamiseksi.", + "notify_start_services": "Yksi tai useampi ilmoituspalvelu ilmoittaa syklin alkamisesta (esim. notify.mobile_app_my_phone). Jätä tyhjäksi ohittaaksesi aloitusilmoitukset.", + "notify_finish_services": "Yksi tai useampi ilmoittaa palveluille, kun sykli päättyy tai lähestyy loppuaan. Jätä tyhjäksi ohittaaksesi lopetusilmoitukset.", + "notify_live_services": "Vähintään yksi ilmoitus palveluista saa reaaliaikaisia ​​edistymispäivityksiä syklin aikana (vain mobiilisovellus). Jätä tyhjäksi ohittaaksesi live-päivitykset.", + "notify_before_end_minutes": "Minuuttia ennen jakson arvioitua loppua ilmoituksen käynnistämiseksi. Aseta arvoksi 0 poistaaksesi käytöstä. Oletus: 0.", + "notify_live_interval_seconds": "Kuinka usein edistymispäivityksiä lähetetään suorituksen aikana. Pienemmät arvot reagoivat paremmin, mutta kuluttavat enemmän ilmoituksia. Vähintään 30 sekuntia pakotetaan – alle 30 sekuntia olevat arvot pyöristetään automaattisesti 30 sekuntiin.", + "notify_live_overrun_percent": "Turvamarginaali yli arvioidut päivitysmäärät pitkille/ylitysjaksoille (esimerkiksi 20 % sallii 1,2-kertaiset arvioidut päivitykset).", + "notify_live_chronometer": "Kun tämä on käytössä, jokainen live-päivitys sisältää kronometrin lähtölaskennan arvioituun päättymisaikaan. Ilmoitusajastin tikittää automaattisesti laitteeseen päivitysten välillä (vain Android-kumppanisovellus).", + "notify_title": "Mukautettu otsikko ilmoituksille. Tukee paikkamerkkiä {device}.", + "notify_icon": "Ilmoituksiin käytettävä kuvake (esim. mdi:washing-machine). Jätä tyhjäksi lähettääksesi ilman kuvaketta.", + "notify_start_message": "Viesti lähetetään, kun sykli alkaa. Tukee paikkamerkkiä {device}.", + "notify_finish_message": "Viesti lähetetään, kun jakso on päättynyt. Tukee paikkamerkkejä {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Valitse numeerinen kokonaisuus, jolla on nykyinen sähkön hinta (esim. sensor.electricity_price, input_number.energy_rate). Kun asetettu, ottaa käyttöön paikkamerkin {cost}. Se on staattisen arvon edelle, jos molemmat on määritetty.", + "energy_price_static": "Kiinteä sähkön hinta per kWh. Kun asetettu, ottaa paikkamerkin {cost} käyttöön. Ohitetaan, jos entiteetti on määritetty yllä. Lisää viestipohjaan valuuttasymboli, esim. {cost} €.", + "notify_reminder_message": "Kertamuistimuistutuksen teksti lähetti vahvistetun minuuttimäärän ennen arvioitua loppua. Erona yllä olevista toistuvista päivityksistä. Tukee {device}, {minutes}, {program} paikkaa.", + "notify_timeout_seconds": "Hylätkää ilmoitukset automaattisesti näin monen sekunnin jälkeen (lähetetty kumppanisovelluksena \"timeout\"). Nolla pitää heidät poissa. Oletus: 0.", + "notify_channel": "Android kumppanisovelluksen ilmoituskanava tila, live etenemistä, ja muistutus ilmoitukset. Kanavan ääni ja merkitys määritetään kumppanisovelluksessa, kun ensimmäistä kertaa käytetään kanavan nimeä - WashData asettaa vain kanavan nimen. Jätä tyhjäksi sovelluksen oletusarvoon.", + "notify_finish_channel": "Erillinen Android-kanava valmiille hälytykselle (käytetään myös muistutuksessa ja pyykkiä odottavassa nalkussa), jotta sillä voi olla oma ääni. Jätä tyhjäksi käyttääksesi kanavaa yllä.", + "notify_pre_complete_message": "Toistuvan live-edistyksen teksti päivittyy syklin aikana. Tukee paikkamerkkejä {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Lisäasetukset", + "description": "Viritä tunnistuskynnykset, oppiminen ja edistynyt toiminta. Oletusasetukset toimivat hyvin useimmissa asetuksissa.", + "sections": { + "suggestions_section": { + "name": "Suositellut asetukset", + "description": "{suggestions_count} opittua ehdotusta saatavilla. Valitse Käytä suositeltuja arvoja tarkistaaksesi ehdotetut muutokset ennen tallennusta.", + "data": { + "apply_suggestions": "Käytä ehdotettuja arvoja" + }, + "data_description": { + "apply_suggestions": "Valitse tämä valintaruutu avataksesi tarkistusvaiheen oppimisalgoritmin ehdotettujen arvojen tarkastelulle. Mitään ei tallenneta automaattisesti.\n\nSuositeltu (oppimisesta):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Tunnistus ja tehorajat", + "description": "Milloin laite katsotaan päällä- tai pois päältä -tilaan, energiarajat ja tilasiirtymien ajoitus.", + "data": { + "start_duration_threshold": "Aloita palautuksen kesto (s)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Valmistumisen vähimmäiskesto (sekuntia)", + "start_threshold_w": "Aloituskynnys (W)", + "stop_threshold_w": "Pysähdyskynnys (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Running Dead Zone (sekuntia)", + "end_repeat_count": "Lopeta toistojen laskenta", + "min_off_gap": "Minimiväli syklien välillä (s)", + "sampling_interval": "Näytteenottoväli (s)" + }, + "data_description": { + "start_duration_threshold": "Ohita lyhyet tehopiikit, jotka ovat lyhyempiä tätä kestoa (sekunteina). Suodattaa pois käynnistyspiikit (esim. 60 W 2 sekuntia) ennen todellisen jakson alkamista. Oletus: 5s.", + "start_energy_threshold": "Jakson on kerättävä vähintään näin paljon energiaa (Wh) START-vaiheen aikana, jotta se vahvistetaan. Oletus: 0,005 Wh.", + "completion_min_seconds": "Tätä lyhyemmät syklit merkitään \"keskeytetyiksi\", vaikka ne päättyisivät luonnollisesti. Oletus: 600s.", + "start_threshold_w": "Tehon on noustava YLI tämän kynnyksen syklin ALOITTAMISEKSI. Aseta korkeampi kuin valmiustilan teho. Oletus: min_teho + 1 W.", + "stop_threshold_w": "Tehon on laskettava ALLE tämän kynnyksen, jotta jakso LOPETA. Aseta hieman nollan yläpuolelle, jotta kohina ei aiheuta vääriä loppuja. Oletus: 0,6 * min_teho.", + "end_energy_threshold": "Jakso päättyy vain, jos energia viimeisessä Off-Delay-ikkunassa on tämän arvon (Wh) alapuolella. Estää vääriä loppuja, jos teho vaihtelee lähellä nollaa. Oletus: 0,05 Wh.", + "running_dead_zone": "Kun sykli on alkanut, jätä tehohäviöt huomioimatta näin monen sekunnin ajan estääksesi väärän lopputunnistuksen alkupyöritysvaiheen aikana. Aseta arvoksi 0 poistaaksesi käytöstä. Oletus: 0s.", + "end_repeat_count": "Kuinka monta kertaa lopetusehto (teho alle off_delay-kynnyksen) on täytyttävä peräkkäin ennen jakson lopettamista. Suuremmat arvot estävät vääriä loppuja taukojen aikana. Oletus: 1.", + "min_off_gap": "Jos sykli alkaa näin monen sekunnin sisällä edellisen päättymisestä, ne YHDISTÄÄN yhdeksi sykliksi.", + "sampling_interval": "Vähimmäisnäytteenottoväli sekunneissa. Tätä nopeammat päivitykset ohitetaan. Oletus: 30 s (2 s pesukoneille, pesukone-kuivauskoneille ja astianpesukoneille)." + } + }, + "matching_section": { + "name": "Profiilien täsmäytys ja oppiminen", + "description": "Kuinka aggressiivisesti WashData täsmäyttää käynnissä olevia syklejä opittuihin profiileihin ja milloin pyytää palautetta.", + "data": { + "profile_match_min_duration_ratio": "Profiilin osuman vähimmäiskeston suhde (0,1–1,0)", + "profile_match_interval": "Profiilin otteluväli (sekuntia)", + "profile_match_threshold": "Profiilin vastaavuuskynnys", + "profile_unmatch_threshold": "Profile Unmatch Threshold", + "auto_label_confidence": "Automaattisen etiketin luottamus (0-1)", + "learning_confidence": "Palautepyynnön luottamus (0-1)", + "suppress_feedback_notifications": "Estä palauteilmoitukset", + "duration_tolerance": "Keston toleranssi (0-0,5)", + "smoothing_window": "Tasoitusikkuna (esimerkkejä)", + "profile_duration_tolerance": "Profiiliottelun keston toleranssi (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Profiilin sovituksen vähimmäiskestosuhde. Ajojakson on oltava vähintään tämä osa profiilin kestosta, jotta se täsmää. Alempi = aikaisempi haku. Oletus: 0,50 (50 %).", + "profile_match_interval": "Kuinka usein (sekunneissa) profiilin täsmäämistä yritetään syklin aikana. Pienemmät arvot mahdollistavat nopeamman havaitsemisen, mutta käyttävät enemmän prosessoria. Oletus: 300 s (5 minuuttia).", + "profile_match_threshold": "Minimi DTW-samankaltaisuuspisteet (0,0–1,0), jotka vaaditaan profiilin katsomiseksi osumana. Korkeampi = tiukempi vastaavuus, vähemmän vääriä positiivisia. Oletus: 0.4.", + "profile_unmatch_threshold": "DTW-samankaltaisuuspisteet (0,0–1,0), jonka alapuolella aiemmin sovittu profiili hylätään. Pitäisi olla alle vastaavuuskynnyksen välkkymisen estämiseksi. Oletusarvo: 0,35.", + "auto_label_confidence": "Merkitse jaksot automaattisesti tällä luotettavuudella tai sen yläpuolella, kun se on valmis (0,0–1,0).", + "learning_confidence": "Vähimmäisluottamus pyytää käyttäjän vahvistusta tapahtuman kautta (0,0–1,0).", + "suppress_feedback_notifications": "Kun WashData on käytössä, se seuraa edelleen ja pyytää palautetta sisäisesti, mutta ei näytä jatkuvia ilmoituksia, joissa pyydetään vahvistamaan jaksot. Hyödyllinen, kun syklit tunnistetaan aina oikein ja kehotteet häiritsevät.", + "duration_tolerance": "Toleranssi ajon aikana jäljellä oleville arvioille (0,0–0,5 vastaa 0–50 %).", + "smoothing_window": "Liikkuvan keskiarvon tasoittamiseen käytettyjen viimeaikaisten teholukemien määrä.", + "profile_duration_tolerance": "Toleranssia käytetään profiilin sovituksessa kokonaiskestovarianssiin (0,0–0,5). Oletuskäyttäytyminen: ±25 %." + } + }, + "timing_section": { + "name": "Ajoitus, ylläpito ja vianmääritys", + "description": "Watchdog, edistymisen nollaus, automaattinen ylläpito ja vianmääritysentiteettien näyttäminen.", + "data": { + "watchdog_interval": "Watchdog Interval (sekuntia)", + "no_update_active_timeout": "Ei päivityksen aikakatkaisu(t)", + "progress_reset_delay": "Edistymisen nollausviive (sekuntia)", + "auto_maintenance": "Ota automaattinen huolto käyttöön", + "expose_debug_entities": "Paljasta Debug Entities", + "save_debug_traces": "Tallenna virheenkorjausjäljet" + }, + "data_description": { + "watchdog_interval": "Sekuntia vahtikoiran tarkastusten välillä juoksun aikana. Oletus: 5s. VAROITUS: Varmista, että tämä on KORKEAMPI kuin anturin päivitysväli, jotta vältytään vääriltä pysähdyksiltä (esim. jos anturi päivittyy 60 sekunnin välein, käytä yli 65 sekuntia).", + "no_update_active_timeout": "Julkaise muutoksen yhteydessä -anturit: pakota lopettamaan ACTIVE-jakso vain, jos päivityksiä ei tule näin moneen sekuntiin. Pienitehoinen viimeistely käyttää edelleen virrankatkaisuviivettä.", + "progress_reset_delay": "Kun sykli on päättynyt (100 %), odota näin monta sekuntia tyhjäkäyntiä ennen kuin nollaat edistymisen 0 prosenttiin. Oletus: 300 s.", + "auto_maintenance": "Ota automaattinen huolto käyttöön (korjaa näytteitä, yhdistä fragmentteja).", + "expose_debug_entities": "Näytä edistyneet anturit (luottamus, vaihe, epäselvyys) virheenkorjausta varten.", + "save_debug_traces": "Tallenna yksityiskohtaiset sijoitukset ja tehon seurantatiedot historiaan (lisää tallennustilan käyttöä)." + } + }, + "anti_wrinkle_section": { + "name": "Rypynestosuoja (kuivausrummut)", + "description": "Ohita rummun pyörähdykset syklin jälkeen, jotta haamusyklit vältetään. Vain kuivausrummulle / pesukuivaajalle.", + "data": { + "anti_wrinkle_enabled": "Ryppyjä ehkäisevä suoja (vain kuivausrumpu)", + "anti_wrinkle_max_power": "Anti-Wrinkle Max Power (W)", + "anti_wrinkle_max_duration": "Anti-ryppyjen enimmäiskesto (s)", + "anti_wrinkle_exit_power": "Ryppyjä estävä poistumisteho (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Jätä huomioimatta lyhyet pienitehoiset rummun kierrokset jakson päätyttyä estääksesi haamujaksot (vain kuivausrumpu/pesukone-kuivausrumpu).", + "anti_wrinkle_max_power": "Tällä teholla tai sen alapuolella olevia purskeita käsitellään ryppyjä ehkäisevinä kierroksina uusien jaksojen sijaan. Koskee vain, kun Anti-Wrinkle Shield on käytössä.", + "anti_wrinkle_max_duration": "Suurin purskeen pituus, jota voidaan pitää ryppyjä estävänä kiertona. Koskee vain, kun Anti-Wrinkle Shield on käytössä.", + "anti_wrinkle_exit_power": "Jos teho putoaa tämän tason alapuolelle, poistu välittömästi ryppyjä vastaan ​​(true off). Koskee vain, kun Anti-Wrinkle Shield on käytössä." + } + }, + "delay_start_section": { + "name": "Viivästetyn käynnistyksen tunnistus", + "description": "Käsittele jatkuvaa matalatehoista valmiustilaa tilana 'Odottaa käynnistystä', kunnes sykli todella alkaa.", + "data": { + "delay_start_detect_enabled": "Ota viivästetun käynnistyksen tunnistus käyttöön", + "delay_confirm_seconds": "Viivästetty käynnistys: valmiustilan vahvistusaika (s)", + "delay_timeout_hours": "Viivästetty aloitus: maksimi odotusaika (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kun tämä on käytössä, lyhyt virrankulutuspiikki, jota seuraa jatkuva valmiustila, havaitaan viivästettynä käynnistyksenä. Tila-anturi näyttää 'Odottaa käynnistystä' viiveen aikana. Ohjelman aikaa tai edistymistä ei seurata ennen kuin sykli todella alkaa. Edellyttää start_threshold_w-arvon asettamisen tyhjennyspiikin tehon yläpuolelle.", + "delay_confirm_seconds": "Tehon on pysyttävä Pysäytysrajan (W) ja Käynnistysrajan (W) välissä näin monta sekuntia ennen kuin WashData siirtyy tilaan 'Odottaa käynnistystä'. Suodattaa lyhyet valikkonavigoinnin piikit. Oletus: 60 s.", + "delay_timeout_hours": "Turvaaikakatkaisu: jos kone on edelleen \"Odottaa käynnistystä\" -tilassa tämän monen tunnin jälkeen, WashData palautuu pois päältä. Oletus: 8 h." + } + }, + "external_triggers_section": { + "name": "Ulkoiset liipaisimet, ovi ja tauko", + "description": "Valinnainen ulkoinen syklin päättymisanturi, ovianturi tauko-/puhdas-tunnistukseen ja kytkin virran katkaisuun tauon ajaksi.", + "data": { + "external_end_trigger_enabled": "Ota käyttöön ulkoinen syklin lopetustriggeri", + "external_end_trigger": "Ulkoinen jakson päättymisanturi", + "external_end_trigger_inverted": "Käänteinen laukaisulogiikka (liipaisu päällä OFF)", + "door_sensor_entity": "Oven anturi", + "pause_cuts_power": "Katkaise virta tauon aikana", + "switch_entity": "Kytkinentiteetti (keskeytä virtakatkos)", + "notify_unload_delay_minutes": "Pyykin odotusilmoituksen viive (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Ota käyttöön ulkoisen binaarisensorin valvonta käynnistääksesi syklin valmistumisen.", + "external_end_trigger": "Valitse binäärinen anturikokonaisuus. Kun tämä anturi laukeaa, nykyinen jakso päättyy tilaan \"valmis\".", + "external_end_trigger_inverted": "Oletuksena laukaisu laukeaa, kun anturi kytkeytyy PÄÄLLE. Valitse tämä ruutu, jos haluat sen sijaan laukeaa, kun anturi sammuu.", + "door_sensor_entity": "Valinnainen binäärianturi koneen ovelle (päällä = auki, pois = kiinni). Kun luukku avautuu aktiivisen jakson aikana, WashData vahvistaa ohjelman tahallisesti keskeytetyksi. Jakson päätyttyä, jos ovi on edelleen kiinni, tila muuttuu 'Clean'-tilaan, kunnes ovi avataan. Huomautus: luukun sulkeminen EI jatka automaattisesti keskeytettyä jaksoa – jatkaminen on käynnistettävä manuaalisesti Resume Cycle -painikkeen tai palvelun kautta.", + "pause_cuts_power": "Kun keskeytät Pause Cycle -painikkeen tai palvelun kautta, sammuta myös määritetty kytkinkokonaisuus. Tulee voimaan vain, jos tälle laitteelle on määritetty kytkinyksikkö.", + "switch_entity": "Vaihtoyksikkö, joka vaihtaa, kun keskeytetään tai jatketaan. Vaaditaan, kun \"Katkaise virta tauon aikana\" on käytössä.", + "notify_unload_delay_minutes": "Lähetä ilmoitus lopetusilmoituskanavan kautta, kun jakso on päättynyt ja ovea ei ole avattu näin moneen minuuttiin (vaatii ovitunnistimen). Aseta arvoksi 0 poistaaksesi käytöstä. Oletus: 60 min." + } + }, + "pump_section": { + "name": "Pumpun valvonta", + "description": "Vain pumpun laitetyypille. Laukaisee tapahtuman, jos pumpun sykli kestää tätä pidempään.", + "data": { + "pump_stuck_duration": "Pumpun jumiutuneen hälytyskynnys (vain pumppu)" + }, + "data_description": { + "pump_stuck_duration": "Jos pumppujakso on edelleen käynnissä tämän monen sekunnin jälkeen, ha_washdata_pump_stuck -tapahtuma käynnistyy kerran. Käytä tätä havaitaksesi juuttunut moottori (tyypillinen öljypohjapumpun käynti on alle 60 s). Oletus: 1800 s (30 min). Vain pumppulaitetyyppi." + } + }, + "device_link_section": { + "name": "Laitelinkki", + "description": "Valinnaisesti liitä tämä WashData-laite olemassa olevaan Home Assistant laitteeseen (esimerkiksi älytulppaan tai itse laitteeseen). Tämän jälkeen WashData-laite näytetään \"Connected via\" -laitteena. Jätä tyhjäksi, jotta WashData pysyy erillisenä laitteena.", + "data": { + "linked_device": "Yhdistetty laite" + }, + "data_description": { + "linked_device": "Valitse laite, johon WashData liitetään. Tämä lisää \"Connected via\" -viittauksen laiterekisteriin; WashData säilyttää oman laitekorttinsa ja yksikkönsä. Tyhjennä valinta linkin poistamiseksi." + } + } + } + }, + "diagnostics": { + "title": "Diagnostiikka ja huolto", + "description": "Suorita ylläpitotoimia, kuten pirstoutuneiden syklien yhdistäminen tai tallennettujen tietojen siirto.\n\n**Tallennustilan käyttö**\n\n- Tiedoston koko: {file_size_kb} kt\n- Pyörät: {cycle_count}\n- Profiilit: {profile_count}\n- Virheenkorjausjäljet: {debug_count}", + "menu_options": { + "reprocess_history": "Ylläpito: Käsittele ja optimoi tiedot uudelleen", + "clear_debug_data": "Tyhjennä virheenkorjaustiedot (vapauta tilaa)", + "wipe_history": "Tyhjennä KAIKKI tämän laitteen tiedot (peruuttamaton)", + "export_import": "Vie/tuo JSON asetuksilla (kopioi/liitä)", + "menu_back": "← Takaisin" + } + }, + "clear_debug_data": { + "title": "Tyhjennä virheenkorjaustiedot", + "description": "Haluatko varmasti poistaa kaikki tallennetut virheenkorjausjäljet? Tämä vapauttaa tilaa, mutta poistaa yksityiskohtaiset sijoitustiedot aiemmista jaksoista." + }, + "manage_cycles": { + "title": "Hallitse syklejä", + "description": "Viimeaikaiset syklit:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Merkitse vanhat syklit automaattisesti", + "select_cycle_to_label": "Merkitse tietty sykli", + "select_cycle_to_delete": "Poista sykli", + "interactive_editor": "Yhdistä/jakaa interaktiivinen editori", + "trim_cycle_select": "Trimmausjakson tiedot", + "menu_back": "← Takaisin" + } + }, + "manage_cycles_empty": { + "title": "Hallitse syklejä", + "description": "Syklejä ei ole vielä tallennettu.", + "menu_options": { + "auto_label_cycles": "Merkitse vanhat syklit automaattisesti", + "menu_back": "← Takaisin" + } + }, + "manage_profiles": { + "title": "Hallitse profiileja", + "description": "Profiilin yhteenveto:\n{profile_summary}", + "menu_options": { + "create_profile": "Luo uusi profiili", + "edit_profile": "Muokkaa/nimeä profiilia uudelleen", + "delete_profile_select": "Poista profiili", + "profile_stats": "Profiilitilastot", + "cleanup_profile": "Puhdista historia - piirrä ja poista", + "assign_profile_phases_select": "Määritä vaihealueet", + "menu_back": "← Takaisin" + } + }, + "manage_profiles_empty": { + "title": "Hallitse profiileja", + "description": "Profiilia ei ole vielä luotu.", + "menu_options": { + "create_profile": "Luo uusi profiili", + "menu_back": "← Takaisin" + } + }, + "manage_phase_catalog": { + "title": "Hallitse vaiheluetteloa", + "description": "Vaiheluettelo (oletusasetukset tälle laitteelle + mukautetut vaiheet):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Luo uusi vaihe", + "phase_catalog_edit_select": "Muokkaa vaihetta", + "phase_catalog_delete": "Poista vaihe", + "menu_back": "← Takaisin" + } + }, + "phase_catalog_create": { + "title": "Luo mukautettu vaihe", + "description": "Luo mukautettu vaiheen nimi ja kuvaus ja valitse, mitä laitetyyppiä se koskee.", + "data": { + "target_device_type": "Kohdelaitetyyppi", + "phase_name": "Vaiheen nimi", + "phase_description": "Vaiheen kuvaus" + } + }, + "phase_catalog_edit_select": { + "title": "Muokkaa mukautettua vaihetta", + "description": "Valitse muokattava vaihe.", + "data": { + "phase_name": "Mukautettu vaihe" + } + }, + "phase_catalog_edit": { + "title": "Muokkaa mukautettua vaihetta", + "description": "Muokkausvaihe: {phase_name}", + "data": { + "phase_name": "Vaiheen nimi", + "phase_description": "Vaiheen kuvaus" + } + }, + "phase_catalog_delete": { + "title": "Poista mukautettu vaihe", + "description": "Valitse poistettava mukautettu vaihe. Kaikki tätä vaihetta käyttävät määritetyt alueet poistetaan.", + "data": { + "phase_name": "Mukautettu vaihe" + } + }, + "assign_profile_phases": { + "title": "Määritä vaihealueet", + "description": "Profiilin vaiheen esikatselu: **{profile_name}**\n\n{timeline_svg}\n\n**Nykyiset vaihteluvälit:**\n{current_ranges}\n\nKäytä alla olevia toimintoja alueiden lisäämiseen, muokkaamiseen, poistamiseen tai tallentamiseen.", + "menu_options": { + "assign_profile_phases_add": "Lisää vaihealue", + "assign_profile_phases_edit_select": "Muokkaa vaihealuetta", + "assign_profile_phases_delete": "Poista vaihealue", + "phase_ranges_clear": "Tyhjennä kaikki alueet", + "assign_profile_phases_auto_detect": "Tunnista vaiheet automaattisesti", + "phase_ranges_save": "Tallenna ja palauta", + "menu_back": "← Takaisin" + } + }, + "assign_profile_phases_add": { + "title": "Lisää vaihealue", + "description": "Lisää yksi vaihealue nykyiseen luonnokseen.", + "data": { + "phase_name": "Vaihe", + "start_min": "Aloitus minuutti", + "end_min": "Loppuminuutti" + } + }, + "assign_profile_phases_edit_select": { + "title": "Muokkaa vaihealuetta", + "description": "Valitse muokattava alue.", + "data": { + "range_index": "Vaihealue" + } + }, + "assign_profile_phases_edit": { + "title": "Muokkaa vaihealuetta", + "description": "Päivitä valittu vaihealue.", + "data": { + "phase_name": "Vaihe", + "start_min": "Aloitus minuutti", + "end_min": "Loppuminuutti" + } + }, + "assign_profile_phases_delete": { + "title": "Poista vaihealue", + "description": "Valitse luonnoksesta poistettava alue.", + "data": { + "range_index": "Vaihealue" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automaattisesti tunnistetut vaiheet", + "description": "Profiili: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} vaihetta tunnistettu automaattisesti.** Valitse toiminto alta.", + "data": { + "action": "Toiminta" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nimeä havaitut vaiheet", + "description": "Profiili: **{profile_name}**\n\n**{detected_count} vaihetta havaittu:**\n{ranges_summary}\n\nAnna jokaiselle vaiheelle nimi." + }, + "assign_profile_phases_select": { + "title": "Määritä vaihealueet", + "description": "Valitse profiili määrittääksesi vaihealueet.", + "data": { + "profile": "Profiili" + } + }, + "profile_stats": { + "title": "Profiilitilastot", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Luo uusi profiili", + "description": "Luo uusi profiili manuaalisesti tai aiemmasta jaksosta.\n\nEsimerkkejä profiilin nimistä: \"Delicates\", \"Heavy Duty\", \"Quick Wash\"", + "data": { + "profile_name": "Profiilin nimi", + "reference_cycle": "Viitesykli (valinnainen)", + "manual_duration": "Manuaalinen kesto (minuutteja)" + } + }, + "edit_profile": { + "title": "Muokkaa profiilia", + "description": "Valitse uudelleennimettävä profiili", + "data": { + "profile": "Profiili" + } + }, + "rename_profile": { + "title": "Muokkaa profiilin tietoja", + "description": "Nykyinen profiili: {current_name}", + "data": { + "new_name": "Profiilin nimi", + "manual_duration": "Manuaalinen kesto (minuutteja)" + } + }, + "delete_profile_select": { + "title": "Poista profiili", + "description": "Valitse poistettava profiili", + "data": { + "profile": "Profiili" + } + }, + "delete_profile_confirm": { + "title": "Vahvista profiilin poistaminen", + "description": "⚠️ Tämä poistaa pysyvästi profiilin: {profile_name}", + "data": { + "unlabel_cycles": "Poista etiketti syklistä käyttämällä tätä profiilia" + } + }, + "auto_label_cycles": { + "title": "Merkitse vanhat syklit automaattisesti", + "description": "Löytyi yhteensä {total_count} sykliä. Profiilit: {profiles}", + "data": { + "confidence_threshold": "Vähimmäisluottamus (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Valitse Cycle to Label", + "description": "✓ = valmis, ⚠ = jatkunut, ✗ = keskeytetty", + "data": { + "cycle_id": "Kierrä" + } + }, + "select_cycle_to_delete": { + "title": "Poista sykli", + "description": "⚠️ Tämä poistaa valitun syklin pysyvästi", + "data": { + "cycle_id": "Valitse Poista" + } + }, + "label_cycle": { + "title": "Label Cycle", + "description": "{cycle_info}", + "data": { + "profile_name": "Profiili", + "new_profile_name": "Uusi profiilin nimi (jos luodaan)" + } + }, + "post_process": { + "title": "Prosessihistoria (yhdistä/jakaa)", + "description": "Puhdista syklihistoria yhdistämällä hajanaiset ajot ja jakamalla väärin yhdistetyt jaksot aikaikkunassa. Anna, kuinka monta tuntia haluat katsoa taaksepäin (käytä 999999 kaikille).", + "data": { + "time_range": "Katsausikkuna (tuntia)", + "gap_seconds": "Yhdistä/jakaa väli (sekuntia)" + }, + "data_description": { + "time_range": "Tuntimäärä, jonka aikana hajautettujen syklien etsiminen yhdistetään. Käytä numeroa 999999 kaikkien historiallisten tietojen käsittelemiseen.", + "gap_seconds": "Kynnys päättää jakamisesta vs yhdistämisestä. Tätä LYHEMMÄT aukot yhdistetään. Tätä PIDEMMÄT aukot jaetaan. Laske tätä pakottaaksesi jakamaan syklin, joka on yhdistetty väärin." + } + }, + "cleanup_profile": { + "title": "Puhdista historia - Valitse profiili", + "description": "Valitse visualisoitava profiili. Tämä luo kaavion, joka näyttää kaikki tämän profiilin menneet jaksot, mikä auttaa tunnistamaan poikkeamat.", + "data": { + "profile": "Profiili" + } + }, + "cleanup_select": { + "title": "Puhdista historia - piirrä ja poista", + "description": "![Kaavio]({graph_url})\n\n**Visualisointi {profile_name}**\n\nKaavio näyttää yksittäiset jaksot värillisinä viivoina. Tunnista poikkeamat (viivat kaukana ryhmästä) ja vastaa niiden väriä alla olevasta luettelosta poistaaksesi ne.\n\n**Valitse jaksot, jotka haluat poistaa PYSYVÄSTI:**", + "data": { + "cycles_to_delete": "Jaksoi poistaa (tarkista vastaavat värit)" + } + }, + "interactive_editor": { + "title": "Yhdistä/jakaa interaktiivinen editori", + "description": "Valitse manuaalinen toimenpide, joka suoritetaan syklihistoriallesi.", + "menu_options": { + "editor_split": "Jaa sykli (etsi aukkoja)", + "editor_merge": "Yhdistä syklit (liitä fragmentit)", + "editor_delete": "Poista sykli(t)", + "menu_back": "← Takaisin" + } + }, + "editor_select": { + "title": "Valitse Cycles", + "description": "Valitse käsiteltävä(t) sykli(t).\n\n{info_text}", + "data": { + "selected_cycles": "Pyörät" + } + }, + "editor_configure": { + "title": "Määritä ja käytä", + "description": "{preview_md}", + "data": { + "confirm_action": "Toiminta", + "merged_profile": "Tuloksena oleva profiili", + "new_profile_name": "Uusi profiilinimi", + "confirm_commit": "Kyllä, vahvistan tämän toimenpiteen", + "segment_0_profile": "Segmentin 1 profiili", + "segment_1_profile": "Segmentin 2 profiili", + "segment_2_profile": "Segmentin 3 profiili", + "segment_3_profile": "Segmentin 4 profiili", + "segment_4_profile": "Segmentin 5 profiili", + "segment_5_profile": "Segmentin 6 profiili", + "segment_6_profile": "Segmentin 7 profiili", + "segment_7_profile": "Segmentin 8 profiili", + "segment_8_profile": "Segmentin 9 profiili", + "segment_9_profile": "Segmentin 10 profiili" + } + }, + "editor_split_params": { + "title": "Jaettu kokoonpano", + "description": "Säädä tämän jakson herkkyys. Mikä tahansa tätä pidempi tyhjäkäyntiväli aiheuttaa halkeaman.", + "data": { + "split_mode": "Jakomenetelmä" + } + }, + "editor_split_auto_params": { + "title": "Automaattisen jaon määritys", + "description": "Säädä tämän syklin jakamisen herkkyyttä. Tätä pidempi toimettomuusväli aiheuttaa jaon.", + "data": { + "min_gap_seconds": "Jaon välikynnys (sekuntia)" + } + }, + "editor_split_manual_params": { + "title": "Manuaaliset jakamisen aikaleimat", + "description": "{preview_md}Syklin ikkuna: **{cycle_start_wallclock} → {cycle_end_wallclock}** päivänä {cycle_date}.\n\nAnna yksi tai useampi jakamisen aikaleima (`HH:MM` tai `HH:MM:SS`), yksi per rivi. Sykli katkaistaan jokaisen aikaleiman kohdalta — N aikaleimaa tuottaa N+1 segmenttiä.", + "data": { + "split_timestamps": "Jakoaikaleimat" + } + }, + "reprocess_history": { + "title": "Ylläpito: Käsittele ja optimoi tiedot uudelleen", + "description": "Suorittaa historiallisten tietojen syväpuhdistuksen:\n1. Leikkaa nollateholukemat (hiljaisuus).\n2. Korjaa syklin ajoituksen/keston.\n3. Laskee uudelleen kaikki tilastolliset mallit (kirjekuoret).\n\n**Korjaa tietoongelmia tai käytä uusia algoritmeja suorittamalla tämä.**" + }, + "wipe_history": { + "title": "Pyyhi historia (vain testaus)", + "description": "⚠️ Tämä poistaa pysyvästi KAIKKI tämän laitteen tallennetut jaksot ja profiilit. Tätä ei voi kumota!" + }, + "export_import": { + "title": "Vie/tuo JSON", + "description": "Kopioi/liitä tämän laitteen jaksot, profiilit ja asetukset. Vie alla tai liitä JSON tuontia varten.", + "data": { + "mode": "Toiminta", + "json_payload": "Täydellinen määritys JSON" + }, + "data_description": { + "mode": "Valitse Vie, jos haluat kopioida nykyiset tiedot ja asetukset, tai Tuo, jos haluat liittää viedyt tiedot toisesta WashData-laitteesta.", + "json_payload": "Vientiä varten: kopioi tämä JSON (sisältää syklit, profiilit ja kaikki hienosäädetyt asetukset). Tuontia varten: liitä WashDatasta viety JSON." + } + }, + "record_cycle": { + "title": "Record Cycle", + "description": "Tallenna jakso manuaalisesti puhtaan profiilin luomiseksi ilman häiriöitä.\n\nTila: **{status}**\nKesto: {duration} s\nNäytteet: {samples}", + "menu_options": { + "record_refresh": "Päivitä tila", + "record_stop": "Lopeta tallennus (tallenna ja käsittele)", + "record_start": "Aloita uusi tallennus", + "record_process": "Käsittele viimeinen tallennus (leikkaa ja tallenna)", + "record_discard": "Hylkää viimeinen tallennus", + "menu_back": "← Takaisin" + } + }, + "record_process": { + "title": "Prosessin tallennus", + "description": "![Kaavio]({graph_url})\n\n**Kaavio näyttää raakatallennuksen.** Sininen = Säilytä, Punainen = Ehdotettu leikkaus.\n*Kaavio on staattinen eikä päivity lomakemuutoksilla.*\n\nTallennus lopetettiin. Trimmit kohdistetaan havaittuun näytteenottotaajuuteen (~{sampling_rate} s).\n\nRaaka-aika: {duration} s\nNäytteet: {samples}", + "data": { + "head_trim": "Trimmauksen aloitus (sekuntia)", + "tail_trim": "Trimmaus loppu (sekuntia)", + "save_mode": "Tallenna kohde", + "profile_name": "Profiilin nimi" + } + }, + "learning_feedbacks": { + "title": "Palautteiden oppiminen", + "description": "Odottava palaute: {count}\n\n{pending_table}\n\nValitse käsiteltävä syklin tarkistuspyyntö.", + "menu_options": { + "learning_feedbacks_pick": "Tarkista odottava palaute", + "learning_feedbacks_dismiss_all": "Hylkää kaikki odottavat palautteet", + "menu_back": "← Takaisin" + } + }, + "learning_feedbacks_pick": { + "title": "Valitse tarkistettava palaute", + "description": "Valitse avattava syklin tarkistuspyyntö.", + "data": { + "selected_feedback": "Valitse tarkistettava sykli" + } + }, + "learning_feedbacks_empty": { + "title": "Palautteiden oppiminen", + "description": "Odottavia palautepyyntöjä ei löytynyt.", + "menu_options": { + "menu_back": "← Takaisin" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Hylkää kaikki odottavat palautteet", + "description": "Olet hylkäämässä **{count}** odottavaa palautepyyntöä. Hylätyt syklit jäävät historiaasi, mutta ne eivät tuota uutta oppimissignaalia. Tätä ei voi kumota.\n\n✅ Valitse alla oleva ruutu ja napsauta **Lähetä** hylätäksesi kaikki.\n❌ Jätä se valitsematta ja napsauta **Lähetä** peruuttaaksesi ja palataksesi takaisin.", + "data": { + "confirm_dismiss_all": "Vahvista: hylkää kaikki odottavat palautepyynnöt" + } + }, + "resolve_feedback": { + "title": "Ratkaise palaute", + "description": "{comparison_img}{candidates_table}\n**Havaittu profiili**: {detected_profile} ({confidence_pct} %)\n**Arvioitu kesto**: {est_duration_min} min\n**Todellinen kesto**: {act_duration_min} min\n\n__Valitse toiminto alta:__", + "data": { + "action": "Mitä haluaisit tehdä?", + "corrected_profile": "Oikea ohjelma (jos korjataan)", + "corrected_duration": "Oikea kesto sekunteina (jos korjataan)" + } + }, + "trim_cycle_select": { + "title": "Trimmausjakso - Valitse sykli", + "description": "Valitse trimmattava sykli. ✓ = valmis, ⚠ = jatkunut, ✗ = keskeytetty", + "data": { + "cycle_id": "Syklin tunnus" + } + }, + "trim_cycle": { + "title": "Trimmaussykli", + "description": "Nykyinen ikkuna: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Toimenpide" + } + }, + "trim_cycle_start": { + "title": "Aseta trimmauksen aloitus", + "description": "Kierrä ikkuna: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNykyinen aloitus: **{current_wallclock}** (+{current_offset_min} min syklin alusta)\n\nValitse uusi aloitusaika alla olevasta kellonvalitsimesta.", + "data": { + "trim_start_time": "Uusi aloitusaika", + "trim_start_min": "Uusi aloitus (minuutteja syklin alusta)" + } + }, + "trim_cycle_end": { + "title": "Aseta trimmauksen loppu", + "description": "Kierrä ikkuna: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNykyinen loppu: **{current_wallclock}** (+{current_offset_min} min syklin alusta)\n\nValitse uusi lopetusaika alla olevalla kellonvalitsimella.", + "data": { + "trim_end_time": "Uusi lopetusaika", + "trim_end_min": "Uusi loppu (minuutteja syklin alusta)" + } + } + }, + "error": { + "import_failed": "Tuonti epäonnistui. Liitä kelvollinen WashData-vienti JSON.", + "select_exactly_one": "Valitse täsmälleen yksi sykli tätä toimintoa varten.", + "select_at_least_one": "Valitse vähintään yksi sykli tätä toimintoa varten.", + "select_at_least_two": "Valitse vähintään kaksi sykliä tätä toimintoa varten.", + "profile_exists": "Tämän niminen profiili on jo olemassa", + "rename_failed": "Profiilin uudelleennimeäminen epäonnistui. Profiilia ei ehkä ole olemassa tai uusi nimi on jo varattu.", + "assignment_failed": "Profiilin määrittäminen syklille epäonnistui", + "duplicate_phase": "Tämän niminen vaihe on jo olemassa.", + "phase_not_found": "Valittua vaihetta ei löytynyt.", + "invalid_phase_name": "Vaiheen nimi ei voi olla tyhjä.", + "phase_name_too_long": "Vaiheen nimi on liian pitkä.", + "invalid_phase_range": "Vaihealue on virheellinen. Lopun on oltava suurempi kuin alku.", + "invalid_phase_timestamp": "Aikaleima on virheellinen. Käytä VVVV-KK-PP HH:MM- tai HH:MM-muotoa.", + "overlapping_phase_ranges": "Vaihealueet menevät päällekkäin. Säädä alueita ja yritä uudelleen.", + "incomplete_phase_row": "Jokaisella vaiherivillä on oltava vaihe sekä valitun tilan täydelliset aloitus-/loppuarvot.", + "timestamp_mode_cycle_required": "Valitse lähdejakso, kun käytät aikaleimatilaa.", + "no_phase_ranges": "Vaihealueita ei ole vielä saatavilla tälle toiminnolle.", + "feedback_notification_title": "WashData: Tarkista sykli ({device})", + "feedback_notification_message": "**Laite**: {device}\n**Ohjelma**: {program} ({confidence} %:n luottamus)\n**Aika**: {time}\n\nWashData tarvitsee apuasi tämän havaitun jakson vahvistamiseksi.\n\nSiirry kohtaan **Asetukset > Laitteet ja palvelut > WashData > Configure > Learning Feedbacks** vahvistaaksesi tai korjataksesi tämän tuloksen.", + "suggestions_ready_notification_title": "WashData: Ehdotetut asetukset valmiina ({device})", + "suggestions_ready_notification_message": "**Ehdotetut asetukset** -anturi raportoi nyt **{count}** toimivia suosituksia.\n\nTarkista ja ota ne käyttöön seuraavasti: **Asetukset > Laitteet ja palvelut > WashData > Määritä > Lisäasetukset > Käytä ehdotettuja arvoja**.\n\nEhdotukset ovat valinnaisia, ja ne näytetään tarkistettavaksi ennen tallentamista.", + "trim_range_invalid": "Aloitus on oltava ennen loppua. Säädä leikkauspisteitäsi.", + "trim_failed": "Leikkaus epäonnistui. Jaksoa ei ehkä enää ole tai ikkuna on tyhjä.", + "invalid_split_timestamp": "Aikaleima on virheellinen tai syklin aikaikkunan ulkopuolella. Käytä muotoa HH:MM tai HH:MM:SS syklin aikaikkunan sisällä.", + "no_split_segments_found": "Näistä aikaleimoista ei voitu muodostaa kelvollisia segmenttejä. Varmista, että jokainen aikaleima on syklin aikaikkunan sisällä ja että segmentit ovat vähintään 1 minuutin pituisia.", + "auto_tune_suggestion": "{device_type} {device_title} havaitsi haamujaksoja. Ehdotettu minimitehon muutos: {current_min}W -> {new_min}W (ei käytössä automaattisesti).", + "auto_tune_title": "WashData automaattinen viritys", + "auto_tune_fallback": "{device_type} {device_title} havaitsi haamujaksoja. Ehdotettu minimitehon muutos: {current_min}W -> {new_min}W (ei käytössä automaattisesti)." + }, + "abort": { + "no_cycles_found": "Syklejä ei löytynyt", + "no_split_segments_found": "Valitusta syklistä ei löytynyt jaettavia segmenttejä.", + "cycle_not_found": "Valittua sykliä ei voitu ladata.", + "no_power_data": "Valitulle jaksolle ei ole saatavilla tehojäljitystietoja.", + "no_profiles_found": "Profiilia ei löytynyt. Luo ensin profiili.", + "no_profiles_for_matching": "Vastaavia profiileja ei ole saatavilla. Luo ensin profiilit.", + "no_unlabeled_cycles": "Kaikki syklit on jo merkitty", + "no_suggestions": "Ehdotettuja arvoja ei ole vielä saatavilla. Suorita muutama sykli ja yritä uudelleen.", + "no_predictions": "Ei ennusteita", + "no_custom_phases": "Mukautettuja vaiheita ei löytynyt.", + "reprocess_success": "Uudelleenkäsitelty {count} jaksoa.", + "debug_data_cleared": "Virheenkorjaustiedot tyhjennettiin {count} jaksosta." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Syklin alku", + "cycle_finish": "Syklin loppu", + "cycle_live": "Reaaliaikainen edistyminen" + } + }, + "common_text": { + "options": { + "unlabeled": "(Merkitsemätön)", + "create_new_profile": "Luo uusi profiili", + "remove_label": "Poista etiketti", + "no_reference_cycle": "(Ei vertailujaksoa)", + "all_device_types": "Kaikki laitetyypit", + "current_suffix": "(nykyinen)", + "editor_select_info": "Valitse 1 jakso jakaaksesi tai 2+ jaksoa yhdistääksesi.", + "editor_select_info_split": "Valitse 1 jaettava sykli.", + "editor_select_info_merge": "Valitse yhdistettäväksi 2 tai useampi sykli.", + "editor_select_info_delete": "Valitse poistettavaksi 1 tai useampi sykli.", + "no_cycles_recorded": "Syklejä ei ole vielä tallennettu.", + "profile_summary_line": "- **{name}**: {count} sykliä, {avg} min keskim", + "no_profiles_created": "Profiilia ei ole vielä luotu.", + "phase_preview": "Vaiheen esikatselu", + "phase_preview_no_curve": "Keskimääräistä profiilikäyrää ei ole vielä saatavilla. Suorita/merkitä lisää syklejä tälle profiilille.", + "average_power_curve": "Keskimääräinen tehokäyrä", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} sykliä, ~{duration} min keskiarvo)", + "profile_option_short_fmt": "{name} ({count} sykliä, ~{duration} min)", + "deleted_cycles_from_profile": "Poistettu {count} jaksoa kohteesta {profile}.", + "not_enough_data_graph": "Ei tarpeeksi dataa kaavion luomiseen.", + "no_profiles_found": "Profiilia ei löytynyt.", + "cycle_info_fmt": "Sykli: {start}, {duration} min, nykyinen: {label}", + "top_candidates_header": "### Parhaat ehdokkaat", + "tbl_profile": "Profiili", + "tbl_confidence": "Luottamus", + "tbl_correlation": "Korrelaatio", + "tbl_duration_match": "Keston vastaavuus", + "detected_profile": "Havaittu profiili", + "estimated_duration": "Arvioitu kesto", + "actual_duration": "Todellinen kesto", + "choose_action": "__Valitse toiminto alta:__", + "tbl_count": "Lukumäärä", + "tbl_avg": "Keskim", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (kesk.)", + "tbl_energy_total": "Energia (yhteensä)", + "tbl_consistency": "Johdonmukaisuus", + "tbl_last_run": "Viimeisin suoritus", + "graph_legend_title": "Kaavion selitys", + "graph_legend_body": "Sininen nauha edustaa havaittua pienintä ja suurinta tehonottoaluetta. Viiva näyttää keskimääräisen tehokäyrän.", + "recording_preview": "Tallennuksen esikatselu", + "trim_start": "Trimmauksen alku", + "trim_end": "Trimmauksen loppu", + "split_preview_title": "Jaettu esikatselu", + "split_preview_found_fmt": "Löytyi {count} segmenttiä.", + "split_preview_confirm_fmt": "Napsauta Vahvista jakaaksesi tämän syklin {count} erilliseen jaksoon.", + "merge_preview_title": "Yhdistä esikatselu", + "merge_preview_joining_fmt": "Liitytään {count} sykliin. Aukot täytetään 0 W lukemilla.", + "editor_delete_preview_title": "Poiston esikatselu", + "editor_delete_preview_intro": "Valitut syklit poistetaan pysyvästi:", + "editor_delete_preview_confirm": "Napsauta Vahvista poistaaksesi nämä syklitiedot pysyvästi.", + "no_power_preview": "*Tehotietoja ei ole saatavilla esikatseluun.*", + "profile_comparison": "Profiilien vertailu", + "actual_cycle_label": "Tämä sykli (todellinen)", + "storage_usage_header": "Varastoinnin käyttö", + "storage_file_size": "Tiedoston koko", + "storage_cycles": "Syklit", + "storage_profiles": "Profiilit", + "storage_debug_traces": "Virheenkorjauksen jäljet", + "overview_suffix": "Yleiskatsaus", + "phase_preview_for_profile": "Profiilin vaiheen esikatselu", + "current_ranges_header": "Nykyiset alueet", + "assign_phases_help": "Käytä alla olevia toimintoja alueiden lisäämiseen, muokkaamiseen, poistamiseen tai tallentamiseen.", + "profile_summary_header": "Profiilin yhteenveto", + "recent_cycles_header": "Viimeaikaiset syklit", + "trim_cycle_preview_title": "Sykli - trimmauksen esikatselu", + "trim_cycle_preview_no_data": "Tälle jaksolle ei ole saatavilla tehotietoja.", + "trim_cycle_preview_kept_suffix": "pidetty", + "table_program": "Ohjelma", + "table_when": "Milloin", + "table_length": "Pituus", + "table_match": "Vastaavuus", + "table_profile": "Profiili", + "table_cycles": "Syklit", + "table_avg_length": "Keskim. pituus", + "table_last_run": "Viimeisin suoritus", + "table_avg_energy": "Keskim. energia", + "table_detected_program": "Tunnistettu ohjelma", + "table_confidence": "Luottamus", + "table_reported": "Ilmoitettu", + "phase_builtin_suffix": "(Sisäänrakennettu)", + "phase_none_available": "Vaiheita ei ole saatavilla.", + "settings_deprecation_warning": "⚠️ **Käytöstä poistettu laitetyyppi:** {device_type} on ajoitettu poistettavaksi tulevassa julkaisussa. WashDatan täsmäysputki ei tuota luotettavia tuloksia tälle laiteluokalle. Integraatiosi toimii edelleen poistojakson ajan; vaimentaa tämä varoitus valitsemalla alla oleva **Laitetyyppi** jollekin tuetuista tyypeistä (pesukone, kuivausrumpu, pesukone-kuivausrumpu, astianpesukone, ilmakeitin, leipäkone tai pumppu) tai **Muu (Lisäasetukset)** -kohtaan, jos laitteesi ei vastaa mitään tuetuista tyypeistä. **Other (Advanced)** toimittaa tarkoituksella yleisiä oletusasetuksia, joita ei ole viritetty millekään tietylle laitteelle, joten sinun on määritettävä kynnykset, aikakatkaisut ja vastaavat parametrit itse. kaikki nykyiset asetuksesi säilyvät, kun vaihdat.", + "phase_other_device_types": "Muut laitetyypit:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Jaa sykli (etsi aukkoja)", + "merge": "Yhdistä syklit (liitä fragmentit)", + "delete": "Poista sykli(t)" + } + }, + "split_mode": { + "options": { + "auto": "Tunnista joutokäyntivälit automaattisesti", + "manual": "Manuaaliset aikaleimat" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Ylläpito: Käsittele ja optimoi tiedot uudelleen", + "clear_debug_data": "Tyhjennä virheenkorjaustiedot (vapauta tilaa)", + "wipe_history": "Tyhjennä KAIKKI tämän laitteen tiedot (peruuttamaton)", + "export_import": "Vie/tuo JSON asetuksilla (kopioi/liitä)" + } + }, + "export_import_mode": { + "options": { + "export": "Vie kaikki tiedot", + "import": "Tuo/Yhdistä tiedot" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Merkitse vanhat syklit automaattisesti", + "select_cycle_to_label": "Merkitse tietty sykli", + "select_cycle_to_delete": "Poista sykli", + "interactive_editor": "Yhdistä/jakaa interaktiivinen editori", + "trim_cycle": "Trimmausjakson tiedot" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Luo uusi profiili", + "edit_profile": "Muokkaa/nimeä profiilia uudelleen", + "delete_profile": "Poista profiili", + "profile_stats": "Profiilitilastot", + "cleanup_profile": "Puhdista historia - piirrä ja poista", + "assign_phases": "Määritä vaihealueet" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Luo uusi vaihe", + "edit_custom_phase": "Muokkaa vaihetta", + "delete_custom_phase": "Poista vaihe" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Lisää vaihealue", + "edit_range": "Muokkaa vaihealuetta", + "delete_range": "Poista vaihealue", + "clear_ranges": "Tyhjennä kaikki alueet", + "auto_detect_ranges": "Tunnista vaiheet automaattisesti", + "save_ranges": "Tallenna ja palauta" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Päivitä tila", + "stop_recording": "Lopeta tallennus (tallenna ja käsittele)", + "start_recording": "Aloita uusi tallennus", + "process_recording": "Käsittele viimeinen tallennus (leikkaa ja tallenna)", + "discard_recording": "Hylkää viimeinen tallennus" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Luo uusi profiili", + "existing_profile": "Lisää olemassa olevaan profiiliin", + "discard": "Hylkää tallennus" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Vahvista - Oikea tunnistus", + "correct": "Oikein - Valitse Ohjelma/kesto", + "ignore": "Ohita - Väärä positiivinen/kohinainen sykli", + "delete": "Poista - Poista historiasta" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nimeä ja käytä vaiheita", + "cancel": "Palaa takaisin ilman muutoksia" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Aseta aloituspiste", + "set_end": "Aseta päätepiste", + "reset": "Palauta täysi kesto", + "apply": "Käytä trimmaa", + "cancel": "Peruuttaa" + } + }, + "device_type": { + "options": { + "washing_machine": "Pesukone", + "dryer": "Kuivausrumpu", + "washer_dryer": "Pesukone-kuivausrumpu yhdistelmä", + "dishwasher": "Astianpesukone", + "coffee_machine": "Kahvinkeitin (poistettu käytöstä)", + "ev": "Sähköajoneuvo (vanhentunut)", + "air_fryer": "Air Fryer", + "heat_pump": "Lämpöpumppu (vanhentunut)", + "bread_maker": "Leipäkone", + "pump": "Pumppu / sumpupumppu", + "oven": "Uuni (poistettu käytöstä)", + "other": "Muu (edistynyt)" + } + } + }, + "services": { + "label_cycle": { + "name": "Merkitse sykli", + "description": "Määritä olemassa oleva profiili menneeseen jaksoon tai poista tarra.", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite etikettiin." + }, + "cycle_id": { + "name": "Syklin tunnus", + "description": "Merkittävän syklin tunnus." + }, + "profile_name": { + "name": "Profiilin nimi", + "description": "Olemassa olevan profiilin nimi (luo profiilit Profiilien hallinta -valikossa). Jätä tyhjäksi poistaaksesi tarran." + } + } + }, + "create_profile": { + "name": "Luo profiili", + "description": "Luo uusi profiili (erillinen tai referenssisykliin perustuva).", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite." + }, + "profile_name": { + "name": "Profiilin nimi", + "description": "Uuden profiilin nimi (esim. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Viitesyklin tunnus", + "description": "Valinnainen syklitunnus tämän profiilin perustana." + } + } + }, + "delete_profile": { + "name": "Poista profiili", + "description": "Poista profiili ja valinnaisesti poista jaksojen tunnisteet sen avulla.", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite." + }, + "profile_name": { + "name": "Profiilin nimi", + "description": "Poistettava profiili." + }, + "unlabel_cycles": { + "name": "Poista syklien merkinnät", + "description": "Poista profiilimerkki syklistä käyttämällä tätä profiilia." + } + } + }, + "auto_label_cycles": { + "name": "Merkitse vanhat syklit automaattisesti", + "description": "Merkitse merkitsemättömät syklit takautuvasti käyttämällä profiilin täsmäyttämistä.", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite." + }, + "confidence_threshold": { + "name": "Luottamuskynnys", + "description": "Vähimmäisosumavarmuus (0,50–0,95) tunnisteiden lisäämistä varten." + } + } + }, + "export_config": { + "name": "Vie kokoonpano", + "description": "Vie tämän laitteen profiilit, syklit ja asetukset JSON-tiedostoon (laitetta kohti).", + "fields": { + "device_id": { + "name": "Laite", + "description": "Vietävä pesukonelaite." + }, + "path": { + "name": "Polku", + "description": "Valinnainen absoluuttinen kirjoitettava tiedostopolku (oletus on /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Tuo kokoonpano", + "description": "Tuo tämän laitteen profiilit, syklit ja asetukset JSON-vientitiedostosta (asetukset voidaan korvata).", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pyykinpesukone, johon tuodaan." + }, + "path": { + "name": "Polku", + "description": "Absoluuttinen polku tuotavaan JSON-vientitiedostoon." + } + } + }, + "submit_cycle_feedback": { + "name": "Lähetä syklipalaute", + "description": "Vahvista tai korjaa automaattisesti tunnistettu ohjelma suoritetun jakson jälkeen. Anna joko \"entry_id\" (lisäasetukset) tai \"device_id\" (suositus).", + "fields": { + "device_id": { + "name": "Laite", + "description": "WashData-laite (suositellaan entry_id:n sijaan)." + }, + "entry_id": { + "name": "Sisääntulotunnus", + "description": "Laitteen konfigurointimerkinnän tunnus (vaihtoehto laite_id:lle)." + }, + "cycle_id": { + "name": "Syklin tunnus", + "description": "Palauteilmoituksessa / lokeissa näkyvä syklitunnus." + }, + "user_confirmed": { + "name": "Vahvista havaittu ohjelma", + "description": "Aseta tosi, jos havaittu ohjelma on oikea." + }, + "corrected_profile": { + "name": "Korjattu profiili", + "description": "Jos et vahvista, anna oikea profiilin/ohjelman nimi." + }, + "corrected_duration": { + "name": "Korjattu kesto (sekuntia)", + "description": "Valinnainen korjattu kesto sekunneissa." + }, + "notes": { + "name": "Huomautuksia", + "description": "Valinnaisia ​​huomautuksia tästä syklistä." + } + } + }, + "record_start": { + "name": "Aloita syklin tallennus", + "description": "Aloita puhtaan syklin tallentaminen manuaalisesti (ohittaa kaiken vastaavan logiikan).", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite tallentaa." + } + } + }, + "record_stop": { + "name": "Lopeta syklin tallennus", + "description": "Lopeta manuaalinen tallennus.", + "fields": { + "device_id": { + "name": "Laite", + "description": "Pesukone laite lopettaa tallennuksen." + } + } + }, + "trim_cycle": { + "name": "Trimmaussykli", + "description": "Leikkaa edellisen jakson tehotiedot tiettyyn aikaikkunaan.", + "fields": { + "device_id": { + "name": "Laite", + "description": "WashData-laite." + }, + "cycle_id": { + "name": "Syklin tunnus", + "description": "Trimmoitavan syklin tunnus." + }, + "trim_start_s": { + "name": "Trimmauksen aloitus (sekuntia)", + "description": "Säilytä tiedot tästä siirtymästä sekunneissa. Oletusarvo 0." + }, + "trim_end_s": { + "name": "Trimmaus loppu (sekuntia)", + "description": "Säilytä tiedot tässä poikkeamassa sekunneissa. Oletus = koko kesto." + } + } + }, + "pause_cycle": { + "name": "Keskeytä sykli", + "description": "Keskeytä WashData-laitteen aktiivinen sykli.", + "fields": { + "device_id": { + "name": "Laite", + "description": "WashData-laite keskeytettäväksi." + } + } + }, + "resume_cycle": { + "name": "Jatka sykliä", + "description": "Jatka keskeytettyä jaksoa WashData-laitteella.", + "fields": { + "device_id": { + "name": "Laite", + "description": "WashData-laite jatkaa." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Käynnissä" + }, + "match_ambiguity": { + "name": "Vastaavuuden epäselvyys" + } + }, + "sensor": { + "washer_state": { + "name": "Tila", + "state": { + "off": "Pois päältä", + "idle": "Odottaa", + "starting": "Käynnistyy", + "running": "Käynnissä", + "paused": "Keskeytetty", + "user_paused": "Keskeytetty käyttäjän toimesta", + "ending": "Loppuu", + "finished": "Valmis", + "anti_wrinkle": "Ryppyjenesto", + "delay_wait": "Odotetaan aloitusta", + "interrupted": "Keskeytynyt", + "force_stopped": "Pakotettu pysäytys", + "rinse": "Huuhtelu", + "unknown": "Tuntematon", + "clean": "Puhdas" + } + }, + "washer_program": { + "name": "Ohjelma" + }, + "time_remaining": { + "name": "Aikaa jäljellä" + }, + "total_duration": { + "name": "Kokonaiskesto" + }, + "cycle_progress": { + "name": "Edistyminen" + }, + "current_power": { + "name": "Nykyinen teho" + }, + "elapsed_time": { + "name": "Kulunut aika" + }, + "debug_info": { + "name": "Virheenkorjaustiedot" + }, + "match_confidence": { + "name": "Vastaavuuden varmuus" + }, + "top_candidates": { + "name": "Parhaat ehdokkaat", + "state": { + "none": "Ei mitään" + } + }, + "current_phase": { + "name": "Nykyinen vaihe" + }, + "wash_phase": { + "name": "Vaihe" + }, + "profile_cycle_count": { + "name": "Profiilin {profile_name} syklimäärä", + "unit_of_measurement": "syklit" + }, + "suggestions": { + "name": "Saatavilla olevat suositellut asetukset" + }, + "pump_runs_today": { + "name": "Pumppu käy (viimeiset 24 tuntia)" + }, + "cycle_count": { + "name": "Syklien määrä" + } + }, + "select": { + "program_select": { + "name": "Syklin ohjelma", + "state": { + "auto_detect": "Automaattinen tunnistus" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Pakota syklin lopetus" + }, + "pause_cycle": { + "name": "Keskeytä sykli" + }, + "resume_cycle": { + "name": "Jatka sykliä" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Kelvollinen laitetunnus vaaditaan." + }, + "cycle_id_required": { + "message": "Kelvollinen cycle_id vaaditaan." + }, + "profile_name_required": { + "message": "Kelvollinen profile_name vaaditaan." + }, + "device_not_found": { + "message": "Laitetta ei löydy." + }, + "no_config_entry": { + "message": "Laitteelle ei löytynyt asetusmerkintää." + }, + "integration_not_loaded": { + "message": "Integraatiota ei ole ladattu tälle laitteelle." + }, + "cycle_not_found_or_no_power": { + "message": "Jaksoa ei löydy tai siinä ei ole virtatietoja." + }, + "trim_failed_empty_window": { + "message": "Trimmaus epäonnistui – sykliä ei löydy, ei virtatietoja tai tuloksena oleva ikkuna on tyhjä." + }, + "trim_invalid_range": { + "message": "trim_end_s on oltava suurempi kuin trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/fr.json b/custom_components/ha_washdata/translations/fr.json new file mode 100644 index 0000000..607fe36 --- /dev/null +++ b/custom_components/ha_washdata/translations/fr.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuration des données de lavage", + "description": "Configurez votre lave-linge ou autre appareil.\n\nUn capteur de puissance est requis.\n\n**Ensuite, il vous sera demandé si vous souhaitez créer votre premier profil.**", + "data": { + "name": "Nom de l'appareil", + "device_type": "Type d'appareil", + "power_sensor": "Capteur de puissance", + "min_power": "Seuil de puissance minimum (W)" + }, + "data_description": { + "name": "Un nom convivial pour cet appareil (par exemple, « Machine à laver », « Lave-vaisselle »).", + "device_type": "De quel type d'appareil s'agit-il ? Aide à adapter la détection et l’étiquetage.", + "power_sensor": "L'entité de capteur qui signale la consommation d'énergie en temps réel (en watts) de votre prise intelligente.", + "min_power": "Les lectures de puissance supérieures à ce seuil (en watts) indiquent que l'appareil est en marche. Commencez avec 2 W pour la plupart des appareils." + } + }, + "first_profile": { + "title": "Créer un premier profil", + "description": "Un **Cycle** est un fonctionnement complet de votre appareil (par exemple, une charge de linge). Un **Profil** regroupe ces cycles par type (par exemple, « Coton », « Lavage rapide »). La création d'un profil permet désormais d'estimer immédiatement la durée de l'intégration.\n\nVous pouvez sélectionner manuellement ce profil dans les contrôles de l'appareil s'il n'est pas détecté automatiquement.\n\n**Laissez « Nom du profil » vide pour ignorer cette étape.**", + "data": { + "profile_name": "Nom du profil (facultatif)", + "manual_duration": "Durée estimée (minutes)" + } + } + }, + "error": { + "cannot_connect": "Échec de la connexion", + "invalid_auth": "Authentification invalide", + "unknown": "Erreur inattendue" + }, + "abort": { + "already_configured": "L'appareil est déjà configuré" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Examiner les retours d’apprentissage", + "settings": "Paramètres", + "notifications": "Notifications", + "manage_cycles": "Gérer les cycles", + "manage_profiles": "Gérer les profils", + "manage_phase_catalog": "Gérer le catalogue de phases", + "record_cycle": "Enregistrement de cycle (manuel)", + "diagnostics": "Diagnostic et entretien" + } + }, + "apply_suggestions": { + "title": "Copier les valeurs suggérées", + "description": "Cela copiera les valeurs suggérées actuelles dans vos paramètres enregistrés. Il s'agit d'une action unique qui ne permet pas les écrasements automatiques.\n\nValeurs suggérées à copier :\n{suggested}", + "data": { + "confirm": "Confirmer l'action de copie" + } + }, + "apply_suggestions_confirm": { + "title": "Examiner les modifications suggérées", + "description": "WashData a trouvé **{pending_count}** valeur(s) suggérée(s) à appliquer.\n\nModifications :\n{changes}\n\n✅ Cochez la case ci-dessous et cliquez sur **Soumettre** pour appliquer et enregistrer ces modifications immédiatement.\n❌ Laissez-la décochée et cliquez sur **Soumettre** pour annuler et revenir en arrière.", + "data": { + "confirm_apply_suggestions": "Appliquer et enregistrer ces modifications" + } + }, + "settings": { + "title": "Paramètres", + "description": "{deprecation_warning}Ajustez les seuils de détection, l’apprentissage et le comportement avancé. Les valeurs par défaut fonctionnent bien pour la plupart des configurations.\n\nParamètres suggérés actuellement disponibles : {suggestions_count}.\nS’il est supérieur à 0, ouvrez les Paramètres avancés et utilisez Appliquer les valeurs suggérées.", + "data": { + "apply_suggestions": "Appliquer les valeurs suggérées", + "device_type": "Type d’appareil", + "power_sensor": "Entité du capteur de puissance", + "min_power": "Puissance minimale (W)", + "off_delay": "Délai(s) de fin de cycle", + "notify_actions": "Actions de notification", + "notify_people": "Retarder la livraison jusqu'à ce que ces personnes soient à la maison", + "notify_only_when_home": "Retarder les notifications jusqu'à ce que la personne sélectionnée soit à la maison", + "notify_fire_events": "Déclencher les événements d'automatisation", + "notify_start_services": "Début du cycle – Cibles de notification", + "notify_finish_services": "Fin du cycle – Cibles de notification", + "notify_live_services": "Progression en direct – Objectifs de notification", + "notify_before_end_minutes": "Notification préalable à l'achèvement (minutes avant la fin)", + "notify_title": "Titre de la notification", + "notify_icon": "Icône de notification", + "notify_start_message": "Format du message de démarrage", + "notify_finish_message": "Format du message de fin", + "notify_pre_complete_message": "Format du message de pré-achèvement", + "show_advanced": "Modifier les paramètres avancés (étape suivante)" + }, + "data_description": { + "apply_suggestions": "Cochez cette case pour copier les valeurs suggérées par l'algorithme d'apprentissage dans le formulaire. Vérifiez avant de sauvegarder.\n\nSuggéré (issu de l'apprentissage) :\n- min_power : {suggested_min_power} W\n- off_delay : {suggested_off_delay} s\n- watchdog_interval : {suggested_watchdog_interval} s\n- no_update_active_timeout : {suggested_no_update_active_timeout} s\n- profile_match_interval : {suggested_profile_match_interval} s\n- auto_label_confidence : {suggested_auto_label_confidence}\n- duration_tolerance : {suggested_duration_tolerance}\n- profile_duration_tolerance : {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio : {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio : {suggested_profile_match_max_duration_ratio}\n- start_threshold_w : {suggested_start_threshold_w} W\n- stop_threshold_w : {suggested_stop_threshold_w} W\n- end_energy_threshold : {suggested_end_energy_threshold} Wh\n- running_dead_zone : {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Sélectionnez le type d'appareil (Lave-linge, Sèche-linge, Lave-vaisselle, Machine à café, EV). Cette balise est stockée à chaque cycle pour une meilleure organisation.", + "power_sensor": "Entité du capteur signalant la puissance en temps réel (en watts). Vous pouvez modifier cela à tout moment sans perdre les données historiques, ce qui est utile si vous remplacez une prise intelligente.", + "min_power": "Les lectures de puissance supérieures à ce seuil (en watts) indiquent que l'appareil est en marche. Commencez avec 2 W pour la plupart des appareils.", + "off_delay": "Pendant quelques secondes, la puissance lissée doit rester en dessous du seuil d'arrêt pour marquer la fin. Par défaut : 120 s.", + "notify_actions": "Séquence d'actions facultative de Home Assistant à exécuter pour les notifications (scripts, notification, TTS, etc.). Si cette option est définie, les actions sont exécutées avant le service de notification de secours.", + "notify_people": "Entités de personnes utilisées pour le contrôle de présence. Lorsqu'elle est activée, l'envoi des notifications est retardé jusqu'à ce qu'au moins une personne sélectionnée soit à la maison.", + "notify_only_when_home": "Si cette option est activée, retardez les notifications jusqu'à ce qu'au moins une personne sélectionnée soit à la maison.", + "notify_fire_events": "Si activé, déclenchez les événements Home Assistant pour le début et la fin du cycle (ha_washdata_cycle_started et ha_washdata_cycle_ended) pour piloter les automatisations.", + "notify_start_services": "Un ou plusieurs services de notification pour alerter lorsqu'un cycle commence. Laissez vide pour ignorer les notifications de démarrage.", + "notify_finish_services": "Un ou plusieurs services de notification pour alerter lorsqu'un cycle se termine ou est sur le point de se terminer. Laissez vide pour ignorer les notifications de fin.", + "notify_live_services": "Un ou plusieurs services de notification pour recevoir des mises à jour de progression en direct pendant l'exécution du cycle (application mobile compagnon uniquement). Laissez vide pour ignorer les mises à jour en direct.", + "notify_before_end_minutes": "Quelques minutes avant la fin estimée du cycle pour déclencher une notification. Réglez sur 0 pour désactiver. Par défaut : 0.", + "notify_title": "Titre personnalisé pour les notifications. Prend en charge l'espace réservé {device}.", + "notify_icon": "Icône à utiliser pour les notifications (par exemple mdi:washing-machine). Laissez vide pour envoyer sans icône.", + "notify_start_message": "Message envoyé au démarrage d'un cycle. Prend en charge l'espace réservé {device}.", + "notify_finish_message": "Message envoyé lorsqu'un cycle se termine. Prend en charge les espaces réservés {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texte des mises à jour récurrentes de la progression en direct pendant l'exécution du cycle. Prend en charge les espaces réservés {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notifications", + "description": "Configurez les canaux de notification, les modèles et les mises à jour facultatives de progression mobile en direct.", + "data": { + "notify_actions": "Actions de notification", + "notify_people": "Retarder la livraison jusqu'à ce que ces personnes soient à la maison", + "notify_only_when_home": "Retarder les notifications jusqu'à ce que la personne sélectionnée soit à la maison", + "notify_fire_events": "Déclencher les événements d'automatisation", + "notify_start_services": "Début du cycle – Cibles de notification", + "notify_finish_services": "Fin du cycle – Cibles de notification", + "notify_live_services": "Progression en direct – Objectifs de notification", + "notify_before_end_minutes": "Notification préalable à l'achèvement (minutes avant la fin)", + "notify_live_interval_seconds": "Intervalle de mise à jour en direct (secondes)", + "notify_live_overrun_percent": "Allocation de dépassement de mise à jour en direct (%)", + "notify_live_chronometer": "Chronomètre Compte à rebours", + "notify_title": "Titre de la notification", + "notify_icon": "Icône de notification", + "notify_start_message": "Format du message de démarrage", + "notify_finish_message": "Format du message de fin", + "notify_pre_complete_message": "Format du message de pré-achèvement", + "energy_price_entity": "Prix ​​de l'énergie - Entité (facultatif)", + "energy_price_static": "Prix ​​de l'énergie - Valeur statique (facultatif)", + "go_back": "Revenir sans enregistrer", + "notify_reminder_message": "Format du message de rappel", + "notify_timeout_seconds": "Rejet automatique après (secondes)", + "notify_channel": "Voie de notification (statut/vivant/rappel)", + "notify_finish_channel": "Voie de notification terminée" + }, + "data_description": { + "go_back": "Cochez ceci et cliquez sur Envoyer pour annuler les modifications ci-dessus et revenir au menu principal.", + "notify_actions": "Séquence d'actions facultative de Home Assistant à exécuter pour les notifications (scripts, notification, TTS, etc.). Si cette option est définie, les actions sont exécutées avant le service de notification de secours.", + "notify_people": "Entités de personnes utilisées pour le contrôle de présence. Lorsqu'elle est activée, l'envoi des notifications est retardé jusqu'à ce qu'au moins une personne sélectionnée soit à la maison.", + "notify_only_when_home": "Si cette option est activée, retardez les notifications jusqu'à ce qu'au moins une personne sélectionnée soit à la maison.", + "notify_fire_events": "Si activé, déclenchez les événements Home Assistant pour le début et la fin du cycle (ha_washdata_cycle_started et ha_washdata_cycle_ended) pour piloter les automatisations.", + "notify_start_services": "Un ou plusieurs services de notification pour alerter lorsqu'un cycle commence (par exemple notify.mobile_app_my_phone). Laissez vide pour ignorer les notifications de démarrage.", + "notify_finish_services": "Un ou plusieurs services de notification pour alerter lorsqu'un cycle se termine ou est sur le point de se terminer. Laissez vide pour ignorer les notifications de fin.", + "notify_live_services": "Un ou plusieurs services de notification pour recevoir des mises à jour de progression en direct pendant l'exécution du cycle (application mobile compagnon uniquement). Laissez vide pour ignorer les mises à jour en direct.", + "notify_before_end_minutes": "Quelques minutes avant la fin estimée du cycle pour déclencher une notification. Réglez sur 0 pour désactiver. Par défaut : 0.", + "notify_live_interval_seconds": "À quelle fréquence les mises à jour de progression sont envoyées pendant l'exécution. Les valeurs inférieures sont plus réactives mais consomment plus de notifications. Un minimum de 30 secondes est appliqué - les valeurs inférieures à 30 s sont automatiquement arrondies à 30 s.", + "notify_live_overrun_percent": "Marge de sécurité au-dessus du nombre de mises à jour estimé pour les cycles longs/dépassants (par exemple, 20 % autorisent 1,2 fois les mises à jour estimées).", + "notify_live_chronometer": "Lorsqu'elle est activée, chaque mise à jour en direct comprend un compte à rebours du chronomètre jusqu'à l'heure d'arrivée estimée. Le minuteur de notification s'écoule automatiquement sur l'appareil entre les mises à jour (application compagnon Android uniquement).", + "notify_title": "Titre personnalisé pour les notifications. Prend en charge l'espace réservé {device}.", + "notify_icon": "Icône à utiliser pour les notifications (par exemple mdi:washing-machine). Laissez vide pour envoyer sans icône.", + "notify_start_message": "Message envoyé au démarrage d'un cycle. Prend en charge l'espace réservé {device}.", + "notify_finish_message": "Message envoyé lorsqu'un cycle se termine. Prend en charge les espaces réservés {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Sélectionnez une entité numérique qui contient le prix actuel de l'électricité (par exemple, sensor.electricity_price, input_number.energy_rate). Lorsqu'il est défini, active l'espace réservé {cost}. A priorité sur la valeur statique si les deux sont configurées.", + "energy_price_static": "Prix ​​fixe de l'électricité par kWh. Lorsqu'il est défini, active l'espace réservé {cost}. Ignoré si une entité est configurée ci-dessus. Ajoutez votre symbole monétaire dans le modèle de message, par ex. {cost} €.", + "notify_reminder_message": "Le texte du rappel unique a envoyé le nombre de minutes configuré avant la fin estimée. Distinct des mises à jour récurrentes ci-dessus. Prend en charge {device}, {minutes}, {program} placeholders.", + "notify_timeout_seconds": "Rejeter automatiquement les notifications après cela plusieurs secondes (transféré comme l'application compagne 'timeout'). Réglez à 0 pour les garder jusqu'à ce qu'ils soient renvoyés. Par défaut : 0.", + "notify_channel": "Android canal de notification de l'application compagnon pour l'état, le progrès en direct et les notifications de rappel. Le son et l'importance d'un canal sont configurés dans l'application compagnon la première fois que le nom du canal est utilisé - WashData ne définit que le nom du canal. Laisser vide pour l'application par défaut.", + "notify_finish_channel": "Séparer le canal Android pour l'alerte terminée (également utilisé par le rappel et la relance d'attente de linge) afin qu'il puisse avoir son propre son. Laissez vide pour réutiliser le canal ci-dessus.", + "notify_pre_complete_message": "Texte des mises à jour récurrentes de la progression en direct pendant l'exécution du cycle. Prend en charge les espaces réservés {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Paramètres avancés", + "description": "Ajustez les seuils de détection, l’apprentissage et le comportement avancé. Les valeurs par défaut fonctionnent bien pour la plupart des configurations.", + "sections": { + "suggestions_section": { + "name": "Paramètres suggérés", + "description": "{suggestions_count} suggestions issues de l’apprentissage disponibles. Cochez Appliquer les valeurs suggérées pour examiner les modifications proposées avant d’enregistrer.", + "data": { + "apply_suggestions": "Appliquer les valeurs suggérées" + }, + "data_description": { + "apply_suggestions": "Cochez cette case pour ouvrir une étape de révision des valeurs suggérées par l'algorithme d'apprentissage. Rien n'est enregistré automatiquement.\n\nSuggéré (issu de l'apprentissage) :\n- min_power : {suggested_min_power} W\n- off_delay : {suggested_off_delay} s\n- watchdog_interval : {suggested_watchdog_interval} s\n- no_update_active_timeout : {suggested_no_update_active_timeout} s\n- profile_match_interval : {suggested_profile_match_interval} s\n- auto_label_confidence : {suggested_auto_label_confidence}\n- duration_tolerance : {suggested_duration_tolerance}\n- profile_duration_tolerance : {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio : {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio : {suggested_profile_match_max_duration_ratio}\n- start_threshold_w : {suggested_start_threshold_w} W\n- stop_threshold_w : {suggested_stop_threshold_w} W\n- end_energy_threshold : {suggested_end_energy_threshold} Wh\n- running_dead_zone : {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Détection et seuils de puissance", + "description": "Quand l’appareil est considéré comme actif ou inactif, seuils d’énergie et temporisation des transitions d’état.", + "data": { + "start_duration_threshold": "Démarrer la durée anti-rebond (s)", + "start_energy_threshold": "Démarrer le portail énergétique (Wh)", + "completion_min_seconds": "Durée d'exécution minimale d'achèvement (secondes)", + "start_threshold_w": "Seuil de démarrage (W)", + "stop_threshold_w": "Seuil d'arrêt (W)", + "end_energy_threshold": "Fin du portail énergétique (Wh)", + "running_dead_zone": "Zone morte en cours d'exécution (secondes)", + "end_repeat_count": "Fin du décompte des répétitions", + "min_off_gap": "Écart minimum entre les cycles (s)", + "sampling_interval": "Intervalle(s) d'échantillonnage" + }, + "data_description": { + "start_duration_threshold": "Ignorez les brèves pointes de puissance inférieures à cette durée (secondes). Filtre les pics de démarrage (par exemple, 60 W pendant 2 s) avant le début du cycle réel. Par défaut : 5 s.", + "start_energy_threshold": "Le cycle doit accumuler au moins autant d’énergie (Wh) pendant la phase START pour être confirmé. Par défaut : 0,005 Wh.", + "completion_min_seconds": "Les cycles plus courts seront marqués comme « interrompus » même s’ils se terminent naturellement. Par défaut : 600 s.", + "start_threshold_w": "La puissance doit augmenter AU-DESSUS de ce seuil pour DÉMARRER un cycle. Réglez-le à un niveau supérieur à votre consommation en veille. Par défaut : min_power + 1 W.", + "stop_threshold_w": "La puissance doit descendre EN DESSOUS de ce seuil pour TERMINER un cycle. Réglez-le juste au-dessus de zéro pour éviter que le bruit ne déclenche de fausses extrémités. Par défaut : 0,6 * min_power.", + "end_energy_threshold": "Le cycle se termine uniquement si l'énergie dans la dernière fenêtre Off-Delay est inférieure à cette valeur (Wh). Empêche les fausses fins si la puissance fluctue près de zéro. Par défaut : 0,05 Wh.", + "running_dead_zone": "Après le démarrage du cycle, ignorez les baisses de puissance pendant ce nombre de secondes afin d'éviter toute détection de fausse fin pendant la phase de démarrage initiale. Réglez sur 0 pour désactiver. Par défaut : 0s.", + "end_repeat_count": "Nombre de fois où la condition de fin (puissance inférieure au seuil pour off_delay) doit être remplie consécutivement avant de terminer le cycle. Des valeurs plus élevées évitent les fausses fins pendant les pauses. Par défaut : 1.", + "min_off_gap": "Si un cycle commence dans ce nombre de secondes après la fin du précédent, ils seront FUSIONNÉS en un seul cycle.", + "sampling_interval": "Intervalle d'échantillonnage minimum en secondes. Les mises à jour plus rapides que ce taux seront ignorées. Par défaut : 30 s (2 s pour les lave-linge, les lave-linge séchants et les lave-vaisselle)." + } + }, + "matching_section": { + "name": "Correspondance de profils et apprentissage", + "description": "À quel point WashData associe agressivement les cycles en cours aux profils appris et quand demander un retour.", + "data": { + "profile_match_min_duration_ratio": "Ratio de durée minimale de correspondance de profil (0,1-1,0)", + "profile_match_interval": "Intervalle de correspondance de profil (secondes)", + "profile_match_threshold": "Seuil de correspondance de profil", + "profile_unmatch_threshold": "Seuil de non-correspondance du profil", + "auto_label_confidence": "Confiance de l'étiquetage automatique (0-1)", + "learning_confidence": "Demande de commentaires Confiance (0-1)", + "suppress_feedback_notifications": "Supprimer les notifications de commentaires", + "duration_tolerance": "Tolérance de durée (0-0,5)", + "smoothing_window": "Fenêtre de lissage (exemples)", + "profile_duration_tolerance": "Tolérance de durée de correspondance de profil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Ratio de durée minimum pour la correspondance de profil. Le cycle d’exécution doit correspondre au moins à cette fraction de la durée du profil pour correspondre. Inférieur = correspondance antérieure. Par défaut : 0,50 (50 %).", + "profile_match_interval": "À quelle fréquence (en secondes) tenter de faire correspondre le profil au cours d’un cycle. Des valeurs inférieures permettent une détection plus rapide mais utilisent plus de CPU. Par défaut : 300 s (5 minutes).", + "profile_match_threshold": "Score de similarité DTW minimum (0,0–1,0) requis pour considérer un profil comme une correspondance. Plus élevé = correspondance plus stricte, moins de faux positifs. Par défaut : 0,4.", + "profile_unmatch_threshold": "Score de similarité DTW (0,0–1,0) en dessous duquel un profil précédemment apparié est rejeté. Doit être inférieur au seuil de correspondance pour éviter le scintillement. Par défaut : 0,35.", + "auto_label_confidence": "Étiquetez automatiquement les cycles égaux ou supérieurs à ce niveau de confiance à la fin (0,0–1,0).", + "learning_confidence": "Confiance minimale pour demander la vérification de l'utilisateur via un événement (0,0–1,0).", + "suppress_feedback_notifications": "Lorsqu'il est activé, WashData suivra et demandera toujours des commentaires en interne, mais n'affichera pas de notifications persistantes vous demandant de confirmer les cycles. Utile lorsque les cycles sont toujours détectés correctement et que vous trouvez les invites distrayantes.", + "duration_tolerance": "Tolérance pour les estimations du temps restant au cours d'une exécution (0,0 à 0,5 correspond à 0 à 50 %).", + "smoothing_window": "Nombre de relevés de puissance récents utilisés pour le lissage de la moyenne mobile.", + "profile_duration_tolerance": "Tolérance utilisée par la correspondance de profil pour la variance totale de la durée (0,0–0,5). Comportement par défaut : ±25 %." + } + }, + "timing_section": { + "name": "Temporisation, maintenance et débogage", + "description": "Watchdog, réinitialisation de la progression, maintenance automatique et exposition des entités de débogage.", + "data": { + "watchdog_interval": "Intervalle de surveillance (secondes)", + "no_update_active_timeout": "Délai (s) d'absence de mise à jour", + "progress_reset_delay": "Délai de réinitialisation de la progression (secondes)", + "auto_maintenance": "Activer la maintenance automatique", + "expose_debug_entities": "Exposer les entités de débogage", + "save_debug_traces": "Enregistrer les traces de débogage" + }, + "data_description": { + "watchdog_interval": "Secondes entre les vérifications du chien de garde pendant l'exécution. Par défaut : 5 s. AVERTISSEMENT : assurez-vous qu'il est SUPÉRIEUR à l'intervalle de mise à jour de votre capteur pour éviter les faux arrêts (par exemple, si le capteur se met à jour toutes les 60 s, utilisez 65 s+).", + "no_update_active_timeout": "Pour les capteurs de publication sur modification : forcez la fin d'un cycle ACTIF uniquement si aucune mise à jour n'arrive pendant ce nombre de secondes. L'achèvement à faible consommation utilise toujours le délai d'arrêt.", + "progress_reset_delay": "Une fois un cycle terminé (100 %), attendez autant de secondes d'inactivité avant de réinitialiser la progression à 0 %. Par défaut : 300 s.", + "auto_maintenance": "Activer la maintenance automatique (réparer les échantillons, fusionner les fragments).", + "expose_debug_entities": "Afficher les capteurs avancés (Confiance, Phase, Ambiguïté) pour le débogage.", + "save_debug_traces": "Stockez les données détaillées de classement et de trace de puissance dans l'historique (augmente l'utilisation du stockage)." + } + }, + "anti_wrinkle_section": { + "name": "Bouclier anti-froissage (sèche-linge)", + "description": "Ignore les rotations du tambour après le cycle pour éviter les cycles fantômes. Sèche-linge / lave-linge séchant uniquement.", + "data": { + "anti_wrinkle_enabled": "Bouclier anti-rides (sèche-linge uniquement)", + "anti_wrinkle_max_power": "Puissance maximale anti-rides (W)", + "anti_wrinkle_max_duration": "Durée Max Anti-Rides (s)", + "anti_wrinkle_exit_power": "Puissance de sortie anti-rides (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorez les courtes rotations du tambour à faible puissance après la fin d'un cycle pour éviter les cycles fantômes (sécheuse/laveuse-sécheuse uniquement).", + "anti_wrinkle_max_power": "Les rafales égales ou inférieures à cette puissance sont traitées comme des rotations antirides au lieu de nouveaux cycles. S'applique uniquement lorsque le bouclier anti-rides est activé.", + "anti_wrinkle_max_duration": "Longueur maximale de rafale à traiter comme rotation anti-rides. S'applique uniquement lorsque le bouclier anti-rides est activé.", + "anti_wrinkle_exit_power": "Si la puissance descend en dessous de ce niveau, quittez immédiatement l'anti-rides (true off). S'applique uniquement lorsque le bouclier anti-rides est activé." + } + }, + "delay_start_section": { + "name": "Détection du départ différé", + "description": "Traitez une veille prolongée à faible consommation comme 'En attente de démarrage' jusqu’au début réel du cycle.", + "data": { + "delay_start_detect_enabled": "Activer la détection de démarrage différé", + "delay_confirm_seconds": "Départ différé : durée de confirmation de veille (s)", + "delay_timeout_hours": "Démarrage différé : temps d'attente maximum (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Lorsqu'il est activé, un bref pic de consommation de faible consommation suivi d'une alimentation de veille soutenue est détecté comme un démarrage différé. Le capteur d'état affiche « En attente de démarrage » pendant le délai. Aucune durée ni progression du programme n'est suivie jusqu'à ce que le cycle commence réellement. Nécessite que start_threshold_w soit défini au-dessus de la puissance de pointe de drain.", + "delay_confirm_seconds": "La puissance doit rester entre Seuil d’arrêt (W) et Seuil de démarrage (W) pendant ce nombre de secondes avant que WashData passe à 'En attente de démarrage'. Filtre les brèves pointes dues à la navigation dans les menus. Par défaut : 60 s.", + "delay_timeout_hours": "Délai d'expiration de sécurité : si la machine est toujours en « En attente de démarrage » après ce nombre d'heures, WashData se réinitialise sur Off. Par défaut : 8 heures." + } + }, + "external_triggers_section": { + "name": "Déclencheurs externes, porte et pause", + "description": "Capteur externe facultatif de fin de cycle, capteur de porte pour la détection pause/propre et interrupteur de coupure d’alimentation en pause.", + "data": { + "external_end_trigger_enabled": "Activer le déclencheur de fin de cycle externe", + "external_end_trigger": "Capteur de fin de cycle externe", + "external_end_trigger_inverted": "Inverser la logique de déclenchement (déclenchement sur OFF)", + "door_sensor_entity": "Capteur de porte", + "pause_cuts_power": "Couper l'alimentation lors d'une pause", + "switch_entity": "Entité de commutation (pour pause de coupure de courant)", + "notify_unload_delay_minutes": "Délai de notification d'attente de blanchisserie (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Activer la surveillance d’un capteur binaire externe pour déclencher l’achèvement du cycle.", + "external_end_trigger": "Sélectionnez une entité de capteur binaire. Lorsque ce capteur se déclenche, le cycle en cours se terminera avec l'état « terminé ».", + "external_end_trigger_inverted": "Par défaut, le déclencheur se déclenche lorsque le capteur s'allume. Cochez cette case pour qu'elle se déclenche lorsque le capteur s'éteint à la place.", + "door_sensor_entity": "Capteur binaire en option pour la porte de la machine (on = ouvert, off = fermé). Lorsque la porte s'ouvre pendant un cycle actif, WashData confirmera que le cycle est intentionnellement mis en pause. Une fois le cycle terminé, si la porte est toujours fermée, l'état passe à « Nettoyer » jusqu'à ce que la porte soit ouverte. Remarque : la fermeture de la porte ne reprend PAS automatiquement un cycle en pause ; la reprise doit être déclenchée manuellement via le bouton ou le service Reprendre le cycle.", + "pause_cuts_power": "Lors d'une pause via le bouton ou le service Pause Cycle, désactivez également l'entité de commutation configurée. Ne prend effet que si une entité de commutation est configurée pour cet appareil.", + "switch_entity": "L'entité de commutation à activer lors de la pause ou de la reprise. Requis lorsque « Couper l'alimentation en pause » est activé.", + "notify_unload_delay_minutes": "Envoyez une notification via le canal de notification de fin une fois le cycle terminé et que la porte n'a pas été ouverte pendant ce nombre de minutes (nécessite un capteur de porte). Réglez sur 0 pour désactiver. Par défaut : 60 min." + } + }, + "pump_section": { + "name": "Surveillance de pompe", + "description": "Type d’appareil pompe uniquement. Déclenche un événement si un cycle de pompe dépasse cette durée.", + "data": { + "pump_stuck_duration": "Seuil(s) d'alerte de blocage de pompe (pompe uniquement)" + }, + "data_description": { + "pump_stuck_duration": "Si un cycle de pompe est toujours en cours après ce nombre de secondes, un événement ha_washdata_pump_stuck est déclenché une fois. Utilisez-le pour détecter un moteur bloqué (le fonctionnement typique de la pompe de puisard est inférieur à 60 s). Par défaut : 1 800 s (30 min). Type de dispositif de pompe uniquement." + } + }, + "device_link_section": { + "name": "Lien du périphérique", + "description": "En option, liez cet appareil WashData à un appareil existant Home Assistant (par exemple la prise intelligente ou l'appareil lui-même). L'appareil WashData est alors affiché comme \"Connected via\" cet appareil. Laissez vide pour garder WashData comme un appareil autonome.", + "data": { + "linked_device": "Appareil relié" + }, + "data_description": { + "linked_device": "Sélectionnez l'appareil pour connecter WashData à. Cela ajoute une référence « Connected via » dans le registre des appareils ; WashData conserve sa propre carte de périphérique et ses propres entités. Effacer la sélection pour supprimer le lien." + } + } + } + }, + "diagnostics": { + "title": "Diagnostic et entretien", + "description": "Exécutez des actions de maintenance telles que la fusion de cycles fragmentés ou la migration des données stockées.\n\n**Utilisation du stockage**\n\n- Taille du fichier : {file_size_kb} Ko\n-Cycles : {cycle_count}\n- Profils : {profile_count}\n- Traces de débogage : {debug_count}", + "menu_options": { + "reprocess_history": "Maintenance : retraiter et optimiser les données", + "clear_debug_data": "Effacer les données de débogage (libérer de l'espace)", + "wipe_history": "Effacer TOUTES les données de cet appareil (irréversible)", + "export_import": "Exporter/Importer JSON avec paramètres (copier/coller)", + "menu_back": "← Retour" + } + }, + "clear_debug_data": { + "title": "Effacer les données de débogage", + "description": "Êtes-vous sûr de vouloir supprimer toutes les traces de débogage stockées ? Cela libérera de l'espace mais supprimera les informations de classement détaillées des cycles précédents." + }, + "manage_cycles": { + "title": "Gérer les cycles", + "description": "Cycles récents :\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Étiquetage automatique des anciens cycles", + "select_cycle_to_label": "Étiqueter un cycle spécifique", + "select_cycle_to_delete": "Supprimer le cycle", + "interactive_editor": "Fusionner/diviser l'éditeur interactif", + "trim_cycle_select": "Rogner les données de cycle", + "menu_back": "← Retour" + } + }, + "manage_cycles_empty": { + "title": "Gérer les cycles", + "description": "Aucun cycle enregistré pour l'instant.", + "menu_options": { + "auto_label_cycles": "Étiquetage automatique des anciens cycles", + "menu_back": "← Retour" + } + }, + "manage_profiles": { + "title": "Gérer les profils", + "description": "Résumé du profil :\n{profile_summary}", + "menu_options": { + "create_profile": "Créer un nouveau profil", + "edit_profile": "Modifier/Renommer le profil", + "delete_profile_select": "Supprimer le profil", + "profile_stats": "Statistiques du profil", + "cleanup_profile": "Nettoyer l'historique – Graphiquer et supprimer", + "assign_profile_phases_select": "Attribuer des plages de phases", + "menu_back": "← Retour" + } + }, + "manage_profiles_empty": { + "title": "Gérer les profils", + "description": "Aucun profil créé pour l'instant.", + "menu_options": { + "create_profile": "Créer un nouveau profil", + "menu_back": "← Retour" + } + }, + "manage_phase_catalog": { + "title": "Gérer le catalogue de phases", + "description": "Catalogue de phases (valeurs par défaut pour cet appareil + phases personnalisées) :\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Créer une nouvelle phase", + "phase_catalog_edit_select": "Phase d'édition", + "phase_catalog_delete": "Supprimer la phase", + "menu_back": "← Retour" + } + }, + "phase_catalog_create": { + "title": "Créer une phase personnalisée", + "description": "Créez un nom et une description de phase personnalisés, puis choisissez le type d'appareil auquel ils s'appliquent.", + "data": { + "target_device_type": "Type de périphérique cible", + "phase_name": "Nom de la phase", + "phase_description": "Description des phases" + } + }, + "phase_catalog_edit_select": { + "title": "Modifier une phase personnalisée", + "description": "Sélectionnez une phase personnalisée à modifier.", + "data": { + "phase_name": "Phase personnalisée" + } + }, + "phase_catalog_edit": { + "title": "Modifier une phase personnalisée", + "description": "Phase de modification : {phase_name}", + "data": { + "phase_name": "Nom de la phase", + "phase_description": "Description des phases" + } + }, + "phase_catalog_delete": { + "title": "Supprimer la phase personnalisée", + "description": "Sélectionnez une phase personnalisée à supprimer. Toutes les plages attribuées utilisant cette phase seront supprimées.", + "data": { + "phase_name": "Phase personnalisée" + } + }, + "assign_profile_phases": { + "title": "Attribuer des plages de phases", + "description": "Aperçu de la phase pour le profil : **{profile_name}**\n\n{timeline_svg}\n\n**Gammes actuelles :**\n{current_ranges}\n\nUtilisez les actions ci-dessous pour ajouter, modifier, supprimer ou enregistrer des plages.", + "menu_options": { + "assign_profile_phases_add": "Ajouter une plage de phases", + "assign_profile_phases_edit_select": "Modifier la plage de phases", + "assign_profile_phases_delete": "Supprimer la plage de phases", + "phase_ranges_clear": "Effacer toutes les plages", + "assign_profile_phases_auto_detect": "Détection automatique des phases", + "phase_ranges_save": "Enregistrer et retourner", + "menu_back": "← Retour" + } + }, + "assign_profile_phases_add": { + "title": "Ajouter une plage de phases", + "description": "Ajoutez une plage de phases au brouillon actuel.", + "data": { + "phase_name": "Phase", + "start_min": "Minute de début", + "end_min": "Minute de fin" + } + }, + "assign_profile_phases_edit_select": { + "title": "Modifier la plage de phases", + "description": "Sélectionnez une plage à modifier.", + "data": { + "range_index": "Plage de phases" + } + }, + "assign_profile_phases_edit": { + "title": "Modifier la plage de phases", + "description": "Mettez à jour la plage de phases sélectionnée.", + "data": { + "phase_name": "Phase", + "start_min": "Minute de début", + "end_min": "Minute de fin" + } + }, + "assign_profile_phases_delete": { + "title": "Supprimer la plage de phases", + "description": "Sélectionnez une plage à supprimer du brouillon.", + "data": { + "range_index": "Plage de phases" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Phases détectées automatiquement", + "description": "Profil : **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} phase(s) détectée(s) automatiquement.** Choisissez une action ci-dessous.", + "data": { + "action": "Action" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nommer les phases détectées", + "description": "Profil : **{profile_name}**\n\n**{detected_count} phase(s) détectée(s) :**\n{ranges_summary}\n\nAttribuez un nom à chaque phase." + }, + "assign_profile_phases_select": { + "title": "Attribuer des plages de phases", + "description": "Sélectionnez un profil pour configurer les plages de phases.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistiques du profil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Créer un nouveau profil", + "description": "Créez un nouveau profil manuellement ou à partir d'un cycle passé.\n\nExemples de noms de profil : \"Delicates\", \"Heavy Duty\", \"Quick Wash\"", + "data": { + "profile_name": "Nom du profil", + "reference_cycle": "Cycle de référence (facultatif)", + "manual_duration": "Durée manuelle (minutes)" + } + }, + "edit_profile": { + "title": "Modifier le profil", + "description": "Sélectionnez un profil à renommer", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Modifier les détails du profil", + "description": "Profil actuel : {current_name}", + "data": { + "new_name": "Nom du profil", + "manual_duration": "Durée manuelle (minutes)" + } + }, + "delete_profile_select": { + "title": "Supprimer le profil", + "description": "Sélectionnez un profil à supprimer", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Confirmer la suppression du profil", + "description": "⚠️ Cela supprimera définitivement le profil : {profile_name}", + "data": { + "unlabel_cycles": "Supprimer l'étiquette des cycles utilisant ce profil" + } + }, + "auto_label_cycles": { + "title": "Étiquetage automatique des anciens cycles", + "description": "J'ai trouvé {total_count} cycles au total. Profils : {profiles}", + "data": { + "confidence_threshold": "Confiance minimale (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Sélectionnez Cycle pour étiqueter", + "description": "✓ = terminé, ⚠ = repris, ✗ = interrompu", + "data": { + "cycle_id": "Cycle" + } + }, + "select_cycle_to_delete": { + "title": "Supprimer le cycle", + "description": "⚠️ Cela supprimera définitivement le cycle sélectionné", + "data": { + "cycle_id": "Cycle pour supprimer" + } + }, + "label_cycle": { + "title": "Cycle d'étiquetage", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nouveau nom de profil (en cas de création)" + } + }, + "post_process": { + "title": "Historique des processus (fusion/fractionnement)", + "description": "Nettoyez l’historique des cycles en fusionnant les exécutions fragmentées et en divisant les cycles mal fusionnés dans une fenêtre de temps. Entrez le nombre d'heures pour regarder en arrière (utilisez 999999 pour tout).", + "data": { + "time_range": "Période d'analyse (heures)", + "gap_seconds": "Fusionner/diviser l'espace (secondes)" + }, + "data_description": { + "time_range": "Nombre d'heures nécessaires à l'analyse des cycles fragmentés à fusionner. Utilisez 999999 pour traiter toutes les données historiques.", + "gap_seconds": "Seuil pour décider de la scission ou de la fusion. Les espaces PLUS COURTS sont fusionnés. Les espaces PLUS LONGS que cela sont divisés. Réduisez-le pour forcer une division sur un cycle qui n’a pas été fusionné correctement." + } + }, + "cleanup_profile": { + "title": "Nettoyer l'historique – Sélectionner un profil", + "description": "Sélectionnez un profil à visualiser. Cela générera un graphique montrant tous les cycles passés pour ce profil afin d'aider à identifier les valeurs aberrantes.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Nettoyer l'historique – Graphiquer et supprimer", + "description": "![Graphique]({graph_url})\n\n**Visualisation de {profile_name}**\n\nLe graphique montre les cycles individuels sous forme de lignes colorées. Identifiez les valeurs aberrantes (lignes éloignées du groupe) et faites correspondre leur couleur dans la liste ci-dessous pour les supprimer.\n\n**Sélectionnez les cycles à supprimer définitivement :**", + "data": { + "cycles_to_delete": "Cycles à supprimer (vérifier les couleurs correspondantes)" + } + }, + "interactive_editor": { + "title": "Fusionner/diviser l'éditeur interactif", + "description": "Sélectionnez une action manuelle à effectuer sur votre historique de cycle.", + "menu_options": { + "editor_split": "Diviser un cycle (trouver des lacunes)", + "editor_merge": "Fusionner les cycles (joindre des fragments)", + "editor_delete": "Supprimer des cycles", + "menu_back": "← Retour" + } + }, + "editor_select": { + "title": "Sélectionnez les cycles", + "description": "Sélectionnez le(s) cycle(s) à traiter.\n\n{info_text}", + "data": { + "selected_cycles": "Cycles" + } + }, + "editor_configure": { + "title": "Configurer et appliquer", + "description": "{preview_md}", + "data": { + "confirm_action": "Action", + "merged_profile": "Profil résultant", + "new_profile_name": "Nouveau nom de profil", + "confirm_commit": "Oui, je confirme cette action", + "segment_0_profile": "Profil du segment 1", + "segment_1_profile": "Profil du segment 2", + "segment_2_profile": "Profil du segment 3", + "segment_3_profile": "Profil du segment 4", + "segment_4_profile": "Profil du segment 5", + "segment_5_profile": "Profil du segment 6", + "segment_6_profile": "Profil du segment 7", + "segment_7_profile": "Profil du segment 8", + "segment_8_profile": "Profil du segment 9", + "segment_9_profile": "Profil du segment 10" + } + }, + "editor_split_params": { + "title": "Configuration divisée", + "description": "Ajustez la sensibilité pour diviser ce cycle. Tout intervalle d'inactivité plus long entraînera une scission.", + "data": { + "split_mode": "Méthode de division" + } + }, + "editor_split_auto_params": { + "title": "Configuration du découpage automatique", + "description": "Ajustez la sensibilité de découpage de ce cycle. Tout intervalle d'inactivité supérieur à cette valeur provoquera un découpage.", + "data": { + "min_gap_seconds": "Seuil d'intervalle de découpage (secondes)" + } + }, + "editor_split_manual_params": { + "title": "Horodatages de découpage manuel", + "description": "{preview_md}Fenêtre du cycle : **{cycle_start_wallclock} → {cycle_end_wallclock}** le {cycle_date}.\n\nSaisissez un ou plusieurs horodatages de découpage (`HH:MM` ou `HH:MM:SS`), un par ligne. Le cycle sera coupé à chaque horodatage — N horodatages produisent N+1 segments.", + "data": { + "split_timestamps": "Horodatages de séparation" + } + }, + "reprocess_history": { + "title": "Maintenance : retraiter et optimiser les données", + "description": "Effectue un nettoyage en profondeur des données historiques :\n1. Coupe les lectures de puissance nulle (silence).\n2. Corrige le timing/la durée du cycle.\n3. Recalcule tous les modèles statistiques (enveloppes).\n\n**Exécutez ceci pour résoudre les problèmes de données ou appliquer de nouveaux algorithmes.**" + }, + "wipe_history": { + "title": "Effacer l'historique (tests uniquement)", + "description": "⚠️ Cela supprimera définitivement TOUS les cycles et profils stockés pour cet appareil. Cela ne peut pas être annulé !" + }, + "export_import": { + "title": "Exporter/Importer JSON", + "description": "Copiez/collez les cycles, les profils ET les paramètres de cet appareil. Exportez ci-dessous ou collez JSON pour importer.", + "data": { + "mode": "Action", + "json_payload": "Configuration complète JSON" + }, + "data_description": { + "mode": "Choisissez Exporter pour copier les données et paramètres actuels, ou Importer pour coller les données exportées à partir d'un autre appareil WashData.", + "json_payload": "Pour l'exportation : copiez ce JSON (inclut les cycles, les profils et tous les paramètres affinés). Pour l'importation : collez le JSON exporté depuis WashData." + } + }, + "record_cycle": { + "title": "Cycle d'enregistrement", + "description": "Enregistrez manuellement un cycle pour créer un profil propre sans interférence.\n\nStatut : **{status}**\nDurée : {duration}s\nÉchantillons : {samples}", + "menu_options": { + "record_refresh": "Actualiser le statut", + "record_stop": "Arrêter l'enregistrement (Enregistrer et traiter)", + "record_start": "Démarrer un nouvel enregistrement", + "record_process": "Traiter le dernier enregistrement (couper et enregistrer)", + "record_discard": "Supprimer le dernier enregistrement", + "menu_back": "← Retour" + } + }, + "record_process": { + "title": "Enregistrement du processus", + "description": "![Graphique]({graph_url})\n\n**Le graphique montre l'enregistrement brut.** Bleu = Conserver, Rouge = Découpage proposé.\n*Le graphique est statique et ne se met pas à jour avec les modifications du formulaire.*\n\nL'enregistrement s'est arrêté. Les trims sont alignés sur le taux d'échantillonnage détecté (~ {sampling_rate} s).\n\nDurée brute : {duration} s\nÉchantillons : {samples}", + "data": { + "head_trim": "Démarrage du trim (secondes)", + "tail_trim": "Fin de coupe (secondes)", + "save_mode": "Enregistrer la destination", + "profile_name": "Nom du profil" + } + }, + "learning_feedbacks": { + "title": "Commentaires d'apprentissage", + "description": "Retours en attente : {count}\n\n{pending_table}\n\nSélectionnez une demande de révision de cycle à traiter.", + "menu_options": { + "learning_feedbacks_pick": "Examiner un retour en attente", + "learning_feedbacks_dismiss_all": "Ignorer tous les retours en attente", + "menu_back": "← Retour" + } + }, + "learning_feedbacks_pick": { + "title": "Choisir un retour à examiner", + "description": "Sélectionnez une demande de révision de cycle à ouvrir.", + "data": { + "selected_feedback": "Sélectionner le cycle à examiner" + } + }, + "learning_feedbacks_empty": { + "title": "Commentaires d'apprentissage", + "description": "Aucune demande de commentaires en attente trouvée.", + "menu_options": { + "menu_back": "← Retour" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Ignorer tous les retours en attente", + "description": "Vous êtes sur le point d'ignorer **{count}** demande(s) de retour en attente. Les cycles ignorés restent dans votre historique mais n'apporteront aucun nouveau signal d'apprentissage. Cette action est irréversible.\n\n✅ Cochez la case ci-dessous et cliquez sur **Soumettre** pour tout ignorer.\n❌ Laissez-la décochée et cliquez sur **Soumettre** pour annuler et revenir en arrière.", + "data": { + "confirm_dismiss_all": "Confirmer : ignorer toutes les demandes de retour en attente" + } + }, + "resolve_feedback": { + "title": "Résoudre les commentaires", + "description": "{comparison_img}{candidates_table}\n**Profil détecté** : {detected_profile} ({confidence_pct} %)\n**Durée estimée** : {est_duration_min} minutes\n**Durée réelle** : {act_duration_min} minutes\n\n__Choisissez une action ci-dessous :__", + "data": { + "action": "Qu'aimeriez-vous faire ?", + "corrected_profile": "Programme correct (si correction)", + "corrected_duration": "Durée correcte en secondes (si correction)" + } + }, + "trim_cycle_select": { + "title": "Cycle de coupe - Sélectionnez le cycle", + "description": "Sélectionnez un cycle à couper. ✓ = terminé, ⚠ = repris, ✗ = interrompu", + "data": { + "cycle_id": "Cycle" + } + }, + "trim_cycle": { + "title": "Cycle de coupe", + "description": "Fenêtre actuelle : {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Action" + } + }, + "trim_cycle_start": { + "title": "Définir le début du découpage", + "description": "Fenêtre de cycle : {cycle_start_wallclock} → {cycle_end_wallclock}\n\nDémarrage actuel : **{current_wallclock}** (+{current_offset_min} minutes à partir du début du cycle)\n\nChoisissez une nouvelle heure de début à l'aide du sélecteur d'horloge ci-dessous.", + "data": { + "trim_start_time": "Nouvelle heure de début", + "trim_start_min": "Nouveau démarrage (minutes après le début du cycle)" + } + }, + "trim_cycle_end": { + "title": "Définir la fin de la coupe", + "description": "Fenêtre de cycle : {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFin actuelle : **{current_wallclock}** (+{current_offset_min} min depuis le début du cycle)\n\nChoisissez une nouvelle heure de fin à l'aide du sélecteur d'horloge ci-dessous.", + "data": { + "trim_end_time": "Nouvelle heure de fin", + "trim_end_min": "Nouvelle fin (minutes depuis le début du cycle)" + } + } + }, + "error": { + "import_failed": "L'importation a échoué. Veuillez coller un JSON d'exportation WashData valide.", + "select_exactly_one": "Sélectionnez exactement un cycle pour cette action.", + "select_at_least_one": "Sélectionnez au moins un cycle pour cette action.", + "select_at_least_two": "Sélectionnez au moins deux cycles pour cette action.", + "profile_exists": "Un profil portant ce nom existe déjà", + "rename_failed": "Échec du renommage du profil. Le profil n'existe peut-être pas ou le nouveau nom est déjà pris.", + "assignment_failed": "Échec de l'attribution du profil au cycle", + "duplicate_phase": "Une phase portant ce nom existe déjà.", + "phase_not_found": "La phase sélectionnée n'a pas été trouvée.", + "invalid_phase_name": "Le nom de la phase ne peut pas être vide.", + "phase_name_too_long": "Le nom de la phase est trop long.", + "invalid_phase_range": "La plage de phases n'est pas valide. La fin doit être supérieure au début.", + "invalid_phase_timestamp": "L'horodatage n'est pas valide. Utilisez le format AAAA-MM-JJ HH:MM ou HH:MM.", + "overlapping_phase_ranges": "Les plages de phases se chevauchent. Ajustez les plages et réessayez.", + "incomplete_phase_row": "Chaque ligne de phase doit inclure une phase ainsi que les valeurs complètes de début/fin pour le mode sélectionné.", + "timestamp_mode_cycle_required": "Veuillez sélectionner un cycle source lorsque vous utilisez le mode horodatage.", + "no_phase_ranges": "Aucune plage de phases n’est encore disponible pour cette action.", + "feedback_notification_title": "WashData : Vérifier le cycle ({device})", + "feedback_notification_message": "**Appareil** : {device}\n**Programme** : {program} ({confidence} % de confiance)\n**Heure** : {time}\n\nWashData a besoin de votre aide pour vérifier ce cycle détecté.\n\nVeuillez accéder à **Paramètres > Appareils et services > WashData > Configurer > Commentaires d'apprentissage** pour confirmer ou corriger ce résultat.", + "suggestions_ready_notification_title": "WashData : Paramètres suggérés prêts ({device})", + "suggestions_ready_notification_message": "Le capteur **Paramètres suggérés** signale désormais **{count}** recommandations exploitables.\n\nPour les consulter et les appliquer : **Paramètres > Appareils et services > WashData > Configurer > Paramètres avancés > Appliquer les valeurs suggérées**.\n\nLes suggestions sont facultatives et affichées pour examen avant d’enregistrer.", + "trim_range_invalid": "Le début doit être avant la fin. Veuillez ajuster vos points de coupe.", + "trim_failed": "Le découpage a échoué. Le cycle n'existe peut-être plus ou la fenêtre est vide.", + "invalid_split_timestamp": "L’horodatage est invalide ou hors de la fenêtre du cycle. Utilisez HH:MM ou HH:MM:SS dans la fenêtre du cycle.", + "no_split_segments_found": "Aucun segment valide n’a pu être produit à partir de ces horodatages. Assurez-vous que chaque horodatage se trouve dans la fenêtre du cycle et que les segments durent au moins 1 minute.", + "auto_tune_suggestion": "{device_type} {device_title} cycles fantômes détectés. Modification de min_power suggérée : {current_min}W -> {new_min}W (non appliqué automatiquement).", + "auto_tune_title": "Réglage automatique des données WashData", + "auto_tune_fallback": "{device_type} {device_title} cycles fantômes détectés. Modification de min_power suggérée : {current_min}W -> {new_min}W (non appliqué automatiquement)." + }, + "abort": { + "no_cycles_found": "Aucun cycle trouvé", + "no_split_segments_found": "Aucun segment divisible n’a été trouvé dans le cycle sélectionné.", + "cycle_not_found": "Le cycle sélectionné n’a pas pu être chargé.", + "no_power_data": "Aucune donnée de trace de puissance disponible pour le cycle sélectionné.", + "no_profiles_found": "Aucun profil trouvé. Créez d’abord un profil.", + "no_profiles_for_matching": "Aucun profil disponible pour la correspondance. Créez d’abord des profils.", + "no_unlabeled_cycles": "Tous les cycles sont déjà étiquetés", + "no_suggestions": "Aucune valeur suggérée disponible pour l'instant. Exécutez quelques cycles et réessayez.", + "no_predictions": "Aucune prédiction", + "no_custom_phases": "Aucune phase personnalisée trouvée.", + "reprocess_success": "{count} cycles ont été retraités avec succès.", + "debug_data_cleared": "Les données de débogage de {count} cycles ont été effacées avec succès." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Démarrage du cycle", + "cycle_finish": "Fin du cycle", + "cycle_live": "Progrès en direct" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sans étiquette)", + "create_new_profile": "Créer un nouveau profil", + "remove_label": "Supprimer l'étiquette", + "no_reference_cycle": "(Pas de cycle de référence)", + "all_device_types": "Tous les types d'appareils", + "current_suffix": "(actuel)", + "editor_select_info": "Sélectionnez 1 cycle à diviser ou plus de 2 cycles à fusionner.", + "editor_select_info_split": "Sélectionnez 1 cycle à diviser.", + "editor_select_info_merge": "Sélectionnez 2 cycles ou plus à fusionner.", + "editor_select_info_delete": "Sélectionnez 1 cycle ou plus à supprimer.", + "no_cycles_recorded": "Aucun cycle enregistré pour l'instant.", + "profile_summary_line": "- **{name}** : {count} cycles, {avg} min en moyenne", + "no_profiles_created": "Aucun profil créé pour l'instant.", + "phase_preview": "Aperçu des phases", + "phase_preview_no_curve": "La courbe de profil moyenne n'est pas encore disponible. Exécutez/étiquetez plus de cycles pour ce profil.", + "average_power_curve": "Courbe de puissance moyenne", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cycles, ~{duration} min en moyenne)", + "profile_option_short_fmt": "{name} ({count} cycles, ~{duration} min)", + "deleted_cycles_from_profile": "{count} cycles supprimés de {profile}.", + "not_enough_data_graph": "Pas assez de données pour générer un graphique.", + "no_profiles_found": "Aucun profil trouvé.", + "cycle_info_fmt": "Cycle : {start}, {duration} min, Étiquette : {label}", + "top_candidates_header": "### Meilleurs candidats", + "tbl_profile": "Profil", + "tbl_confidence": "Confiance", + "tbl_correlation": "Corrélation", + "tbl_duration_match": "Correspondance de durée", + "detected_profile": "Profil détecté", + "estimated_duration": "Durée estimée", + "actual_duration": "Durée réelle", + "choose_action": "__Choisissez une action ci-dessous :__", + "tbl_count": "Nombre", + "tbl_avg": "Moy.", + "tbl_min": "Min.", + "tbl_max": "Max.", + "tbl_energy_avg": "Énergie (moyenne)", + "tbl_energy_total": "Énergie (totale)", + "tbl_consistency": "Cohérence", + "tbl_last_run": "Dernière exécution", + "graph_legend_title": "Légende du graphique", + "graph_legend_body": "La bande bleue représente la plage de consommation électrique minimale et maximale observée. La ligne montre la courbe de puissance moyenne.", + "recording_preview": "Aperçu de l'enregistrement", + "trim_start": "Début de coupe", + "trim_end": "Fin de coupe", + "split_preview_title": "Aperçu fractionné", + "split_preview_found_fmt": "{count} segments trouvés.", + "split_preview_confirm_fmt": "Cliquez sur Confirmer pour diviser ce cycle en {count} cycles distincts.", + "merge_preview_title": "Aperçu de la fusion", + "merge_preview_joining_fmt": "Rejoindre {count} cycles. Les lacunes seront comblées par des lectures de 0 W.", + "editor_delete_preview_title": "Aperçu de suppression", + "editor_delete_preview_intro": "Les cycles sélectionnés seront supprimés définitivement :", + "editor_delete_preview_confirm": "Cliquez sur Confirmer pour supprimer définitivement ces enregistrements de cycle.", + "no_power_preview": "*Aucune donnée de puissance disponible pour l'aperçu.*", + "profile_comparison": "Comparaison de profils", + "actual_cycle_label": "Ce cycle (réel)", + "storage_usage_header": "Utilisation du stockage", + "storage_file_size": "Taille du fichier", + "storage_cycles": "Cycles", + "storage_profiles": "Profils", + "storage_debug_traces": "Traces de débogage", + "overview_suffix": "Aperçu", + "phase_preview_for_profile": "Aperçu de la phase pour le profil", + "current_ranges_header": "Gammes actuelles", + "assign_phases_help": "Utilisez les actions ci-dessous pour ajouter, modifier, supprimer ou enregistrer des plages.", + "profile_summary_header": "Résumé du profil", + "recent_cycles_header": "Cycles récents", + "trim_cycle_preview_title": "Aperçu du découpage du cycle", + "trim_cycle_preview_no_data": "Aucune donnée de puissance disponible pour ce cycle.", + "trim_cycle_preview_kept_suffix": "gardé", + "table_program": "Programme", + "table_when": "Quand", + "table_length": "Durée", + "table_match": "Correspondance", + "table_profile": "Profil", + "table_cycles": "Cycles", + "table_avg_length": "Durée moy.", + "table_last_run": "Dernière exécution", + "table_avg_energy": "Énergie moy.", + "table_detected_program": "Programme détecté", + "table_confidence": "Confiance", + "table_reported": "Signalé", + "phase_builtin_suffix": "(Intégré)", + "phase_none_available": "Aucune phase disponible.", + "settings_deprecation_warning": "⚠️ **Type d'appareil obsolète :** {device_type} devrait être supprimé dans une prochaine version. Le pipeline de correspondance de WashData ne produit pas de résultats fiables pour cette classe d'appliances. Votre intégration continue de fonctionner pendant la période de dépréciation ; pour faire taire cet avertissement, basculez **Type d'appareil** ci-dessous sur l'un des types pris en charge (machine à laver, sèche-linge, lave-linge/sèche-linge combiné, lave-vaisselle, friteuse à air, machine à pain ou pompe), ou sur **Autre (avancé)** si votre appareil ne correspond à aucun des types pris en charge. **Autre (Avancé)** fournit intentionnellement des valeurs par défaut génériques qui ne sont adaptées à aucune appliance spécifique. Vous devrez donc configurer vous-même les seuils, les délais d'attente et les paramètres de correspondance ; tous vos paramètres existants sont conservés lorsque vous changez.", + "phase_other_device_types": "Autres types d'appareils :" + } + }, + "interactive_editor_action": { + "options": { + "split": "Diviser un cycle (trouver des lacunes)", + "merge": "Fusionner les cycles (joindre des fragments)", + "delete": "Supprimer des cycles" + } + }, + "split_mode": { + "options": { + "auto": "Détecter automatiquement les périodes d’inactivité", + "manual": "Horodatage(s) manuel(s)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Maintenance : retraiter et optimiser les données", + "clear_debug_data": "Effacer les données de débogage (libérer de l'espace)", + "wipe_history": "Effacer TOUTES les données de cet appareil (irréversible)", + "export_import": "Exporter/Importer JSON avec paramètres (copier/coller)" + } + }, + "export_import_mode": { + "options": { + "export": "Exporter toutes les données", + "import": "Importer/Fusionner des données" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Étiquetage automatique des anciens cycles", + "select_cycle_to_label": "Étiqueter un cycle spécifique", + "select_cycle_to_delete": "Supprimer le cycle", + "interactive_editor": "Fusionner/diviser l'éditeur interactif", + "trim_cycle": "Rogner les données de cycle" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Créer un nouveau profil", + "edit_profile": "Modifier/Renommer le profil", + "delete_profile": "Supprimer le profil", + "profile_stats": "Statistiques du profil", + "cleanup_profile": "Nettoyer l'historique – Graphiquer et supprimer", + "assign_phases": "Attribuer des plages de phases" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Créer une nouvelle phase", + "edit_custom_phase": "Phase d'édition", + "delete_custom_phase": "Supprimer la phase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Ajouter une plage de phases", + "edit_range": "Modifier la plage de phases", + "delete_range": "Supprimer la plage de phases", + "clear_ranges": "Effacer toutes les plages", + "auto_detect_ranges": "Détection automatique des phases", + "save_ranges": "Enregistrer et retourner" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Actualiser le statut", + "stop_recording": "Arrêter l'enregistrement (Enregistrer et traiter)", + "start_recording": "Démarrer un nouvel enregistrement", + "process_recording": "Traiter le dernier enregistrement (couper et enregistrer)", + "discard_recording": "Supprimer le dernier enregistrement" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Créer un nouveau profil", + "existing_profile": "Ajouter au profil existant", + "discard": "Supprimer l'enregistrement" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmer - Détection correcte", + "correct": "Correct - Choisissez le programme/la durée", + "ignore": "Ignorer - Cycle faux positif/bruyant", + "delete": "Supprimer - Supprimer de l'historique" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nommer et appliquer les phases", + "cancel": "Revenir sans modifications" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Définir le point de départ", + "set_end": "Définir le point final", + "reset": "Réinitialiser à la durée complète", + "apply": "Appliquer le découpage", + "cancel": "Annuler" + } + }, + "device_type": { + "options": { + "washing_machine": "Machine à laver", + "dryer": "Séchoir", + "washer_dryer": "Combo laveuse-sécheuse", + "dishwasher": "Lave-vaisselle", + "coffee_machine": "Machine à café (obsolète)", + "ev": "Véhicule électrique (obsolète)", + "air_fryer": "Friteuse à air", + "heat_pump": "Pompe à chaleur (obsolète)", + "bread_maker": "Machine à pain", + "pump": "Pompe / Pompe de puisard", + "oven": "Four (obsolète)", + "other": "Autre (avancé)" + } + } + }, + "services": { + "label_cycle": { + "name": "Cycle d'étiquetage", + "description": "Attribuez un profil existant à un cycle passé ou supprimez l'étiquette.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "L'appareil de la machine à laver à étiqueter." + }, + "cycle_id": { + "name": "Identifiant du cycle", + "description": "L’ID du cycle à étiqueter." + }, + "profile_name": { + "name": "Nom du profil", + "description": "Le nom d'un profil existant (créer des profils dans le menu Gérer les profils). Laissez vide pour supprimer l’étiquette." + } + } + }, + "create_profile": { + "name": "Créer un profil", + "description": "Créez un nouveau profil (autonome ou basé sur un cycle de référence).", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le dispositif de la machine à laver." + }, + "profile_name": { + "name": "Nom du profil", + "description": "Nom du nouveau profil (par exemple « Heavy Duty », « Delicates »)." + }, + "reference_cycle_id": { + "name": "ID du cycle de référence", + "description": "ID de cycle facultatif sur lequel baser ce profil." + } + } + }, + "delete_profile": { + "name": "Supprimer le profil", + "description": "Supprimez un profil et éventuellement désétiquetez les cycles qui l'utilisent.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le dispositif de la machine à laver." + }, + "profile_name": { + "name": "Nom du profil", + "description": "Le profil à supprimer." + }, + "unlabel_cycles": { + "name": "Annuler l'étiquetage des cycles", + "description": "Supprimez l’étiquette de profil des cycles utilisant ce profil." + } + } + }, + "auto_label_cycles": { + "name": "Étiquetage automatique des anciens cycles", + "description": "Étiquetez rétroactivement les cycles non étiquetés à l’aide de la correspondance de profil.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le dispositif de la machine à laver." + }, + "confidence_threshold": { + "name": "Seuil de confiance", + "description": "Confiance de correspondance minimale (0,50-0,95) pour appliquer les étiquettes." + } + } + }, + "export_config": { + "name": "Exporter la configuration", + "description": "Exportez les profils, cycles et paramètres de cet appareil vers un fichier JSON (par appareil).", + "fields": { + "device_id": { + "name": "Appareil", + "description": "L'appareil de machine à laver à exporter." + }, + "path": { + "name": "Chemin", + "description": "Chemin de fichier absolu facultatif à écrire (par défaut /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importer la configuration", + "description": "Importez des profils, des cycles et des paramètres pour cet appareil à partir d'un fichier d'exportation JSON (les paramètres peuvent être écrasés).", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Appareil de machine à laver dans lequel importer." + }, + "path": { + "name": "Chemin", + "description": "Chemin absolu vers le fichier JSON d’exportation à importer." + } + } + }, + "submit_cycle_feedback": { + "name": "Soumettre des commentaires sur le cycle", + "description": "Confirmez ou corrigez un programme détecté automatiquement après un cycle terminé. Fournissez soit `entry_id` (avancé) ou `device_id` (recommandé).", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le périphérique WashData (recommandé à la place de Entry_id)." + }, + "entry_id": { + "name": "ID d'entrée", + "description": "L’ID d’entrée de configuration du périphérique (alternative à device_id)." + }, + "cycle_id": { + "name": "Identifiant du cycle", + "description": "L'identifiant du cycle affiché dans la notification de retour/les journaux." + }, + "user_confirmed": { + "name": "Confirmer le programme détecté", + "description": "Définissez true si le programme détecté est correct." + }, + "corrected_profile": { + "name": "Profil corrigé", + "description": "S’il n’est pas confirmé, fournissez le nom de profil/programme correct." + }, + "corrected_duration": { + "name": "Durée corrigée (secondes)", + "description": "Durée corrigée facultative en secondes." + }, + "notes": { + "name": "Remarques", + "description": "Notes facultatives sur ce cycle." + } + } + }, + "record_start": { + "name": "Début du cycle d’enregistrement", + "description": "Commencez à enregistrer manuellement un cycle de nettoyage (contourne toute logique de correspondance).", + "fields": { + "device_id": { + "name": "Appareil", + "description": "L'appareil de la machine à laver à enregistrer." + } + } + }, + "record_stop": { + "name": "Arrêt du cycle d'enregistrement", + "description": "Arrêtez l'enregistrement manuel.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le dispositif de la machine à laver pour arrêter l'enregistrement." + } + } + }, + "trim_cycle": { + "name": "Cycle de coupe", + "description": "Ajustez les données de puissance d'un cycle passé à une fenêtre de temps spécifique.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le périphérique WashData." + }, + "cycle_id": { + "name": "Identifiant du cycle", + "description": "L’ID du cycle à découper." + }, + "trim_start_s": { + "name": "Démarrage du trim (secondes)", + "description": "Conservez les données de ce décalage en secondes. Par défaut 0." + }, + "trim_end_s": { + "name": "Fin de coupe (secondes)", + "description": "Conservez les données jusqu'à ce décalage en secondes. Par défaut = durée complète." + } + } + }, + "pause_cycle": { + "name": "Cycle de pause", + "description": "Suspendez le cycle actif d’un appareil WashData.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "L'appareil WashData à mettre en pause." + } + } + }, + "resume_cycle": { + "name": "Reprendre le cycle", + "description": "Reprendre un cycle en pause pour un appareil WashData.", + "fields": { + "device_id": { + "name": "Appareil", + "description": "Le périphérique WashData à reprendre." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "En cours d'exécution" + }, + "match_ambiguity": { + "name": "Ambiguïté des correspondances" + } + }, + "sensor": { + "washer_state": { + "name": "État", + "state": { + "off": "Désactivé", + "idle": "Inactif", + "starting": "Démarrage", + "running": "En cours d'exécution", + "paused": "En pause", + "user_paused": "En pause par l'utilisateur", + "ending": "En cours d'arrêt", + "finished": "Terminé", + "anti_wrinkle": "Anti-faux plis", + "delay_wait": "En attendant de commencer", + "interrupted": "Interrompu", + "force_stopped": "Arrêt forcé", + "rinse": "Rinçage", + "unknown": "Inconnu", + "clean": "Propre" + } + }, + "washer_program": { + "name": "Programme" + }, + "time_remaining": { + "name": "Temps restant" + }, + "total_duration": { + "name": "Durée totale" + }, + "cycle_progress": { + "name": "Progrès" + }, + "current_power": { + "name": "Puissance actuelle" + }, + "elapsed_time": { + "name": "Temps écoulé" + }, + "debug_info": { + "name": "Informations de débogage" + }, + "match_confidence": { + "name": "Niveau de confiance" + }, + "top_candidates": { + "name": "Meilleurs candidats", + "state": { + "none": "Aucun" + } + }, + "current_phase": { + "name": "Phase actuelle" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Nombre de cycles du profil {profile_name}", + "unit_of_measurement": "cycles" + }, + "suggestions": { + "name": "Paramètres suggérés" + }, + "pump_runs_today": { + "name": "La pompe fonctionne (dernières 24 heures)" + }, + "cycle_count": { + "name": "Nombre de cycles" + } + }, + "select": { + "program_select": { + "name": "Programme des cycles", + "state": { + "auto_detect": "Détection automatique" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forcer la fin du cycle" + }, + "pause_cycle": { + "name": "Cycle de pause" + }, + "resume_cycle": { + "name": "Reprendre le cycle" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Un device_id valide est requis." + }, + "cycle_id_required": { + "message": "Un cycle_id valide est requis." + }, + "profile_name_required": { + "message": "Un nom_profil valide est requis." + }, + "device_not_found": { + "message": "Appareil introuvable." + }, + "no_config_entry": { + "message": "Aucune entrée de configuration trouvée pour l'appareil." + }, + "integration_not_loaded": { + "message": "Intégration non chargée pour cet appareil." + }, + "cycle_not_found_or_no_power": { + "message": "Cycle introuvable ou ne comportant aucune donnée de puissance." + }, + "trim_failed_empty_window": { + "message": "Échec du trim : cycle introuvable, aucune donnée d'alimentation ou la fenêtre résultante est vide." + }, + "trim_invalid_range": { + "message": "trim_end_s doit être supérieur à trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/fy.json b/custom_components/ha_washdata/translations/fy.json new file mode 100644 index 0000000..4e3c352 --- /dev/null +++ b/custom_components/ha_washdata/translations/fy.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData ynstelle", + "description": "Konfigurearje jo waskmasine of oare apparaat.\n\nPower sensor is nedich.\n\n**Dêrnei wurdt jo frege oft jo jo earste profyl meitsje wolle.**", + "data": { + "name": "Apparaat Namme", + "device_type": "Apparaat Type", + "power_sensor": "Power Sensor", + "min_power": "Minimum macht drompel (W)" + }, + "data_description": { + "name": "In freonlike namme foar dit apparaat (bgl. 'Waskmasine', 'ôfwaskmasine').", + "device_type": "Hokker type apparaat is dit? Helpt deteksje en labeling op maat.", + "power_sensor": "De sensor-entiteit dy't realtime enerzjyferbrûk (yn watt) rapporteart fan jo smart plug.", + "min_power": "Stromlêzingen boppe dizze drompel (yn watt) jouwe oan dat it apparaat wurket. Begjin mei 2W foar de measte apparaten." + } + }, + "first_profile": { + "title": "Meitsje earste profyl", + "description": "In ** Cycle ** is in folsleine run fan jo apparaat (bgl. in lading wask). In **Profyl** groepearret dizze syklusen op type (bgl. 'Katoen', 'Fluchwaskje'). It oanmeitsjen fan in profyl helpt no direkt de yntegraasje skatting doer.\n\nJo kinne dit profyl manuell selektearje yn 'e apparaatkontrôles as it net automatysk ûntdutsen wurdt.\n\n**Lit 'Profylnamme' leech om dizze stap oer te slaan.**", + "data": { + "profile_name": "Profylnamme (opsjoneel)", + "manual_duration": "Ynschatte doer (minuten)" + } + } + }, + "error": { + "cannot_connect": "Ferbine mislearre", + "invalid_auth": "Unjildige autentikaasje", + "unknown": "Unferwachte flater" + }, + "abort": { + "already_configured": "Apparaat is al konfigurearre" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Learfeedbacks beoardielje", + "settings": "Ynstellings", + "notifications": "Notifikaasjes", + "manage_cycles": "Behear Cycles", + "manage_profiles": "Beheare Profilen", + "manage_phase_catalog": "Fasekatalogus beheare", + "record_cycle": "Syklus opnimme (hânmjittich)", + "diagnostics": "Diagnostyk en ûnderhâld" + } + }, + "apply_suggestions": { + "title": "Foarstelde wearden kopiearje", + "description": "Dit sil de aktuele foarstelde wearden kopiearje nei jo bewarre ynstellings. Dit is in ienmalige aksje en makket auto-oerskriuwingen net ynskeakele.\n\nFoarstelde wearden om te kopiearjen:\n{suggested}", + "data": { + "confirm": "Befêstigje kopiearjen aksje" + } + }, + "apply_suggestions_confirm": { + "title": "Foarstelde feroarings beoardielje", + "description": "WashData fûn **{pending_count}** foarstelde wearde(n) om oan te passen.\n\nFeroarings:\n{changes}\n\n✅ Kontrolearje it fakje hjirûnder en klikje op **Ferstjoere** om dizze wizigingen direkt oan te passen en op te slaan.\n❌ Lit it net útskeakele en klikje op **Submit** om te annulearjen en werom te gean.", + "data": { + "confirm_apply_suggestions": "Tapasse en bewarje dizze wizigingen" + } + }, + "settings": { + "title": "Ynstellings", + "description": "{deprecation_warning}Tune detection drompels, learen, en avansearre gedrach. Standerts wurkje goed foar de measte opset.\n\nFoarstelde ynstellings beskikber: {suggestions_count}.\nAs dit boppe 0 is, iepenje Avansearre ynstellings en brûk Oanbefelle wearden tapasse.", + "data": { + "apply_suggestions": "Oanbefelle wearden tapasse", + "device_type": "Apparaat Type", + "power_sensor": "Power Sensor Entiteit", + "min_power": "Minimum macht (W)", + "off_delay": "Syklus einfertraging (s)", + "notify_actions": "Notifikaasje Aksjes", + "notify_people": "Fertrage levering oant dizze minsken thús binne", + "notify_only_when_home": "Notifikaasjes fertrage oant selekteare persoan thús is", + "notify_fire_events": "Automatisearring eveneminten triggerje", + "notify_start_services": "Syklusbegjin - Notifikaasjedoelen", + "notify_finish_services": "Syklus foltôgje - Notifikaasjedoelen", + "notify_live_services": "Live foarútgong - Notifikaasjedoelen", + "notify_before_end_minutes": "Notifikaasje foar foltôging (minuten foar ein)", + "notify_title": "Notifikaasje titel", + "notify_icon": "Notifikaasje Ikoan", + "notify_start_message": "Begjin berjochtformaat", + "notify_finish_message": "Berjochtformaat foltôgje", + "notify_pre_complete_message": "Pre-foltôging berjochtformaat", + "show_advanced": "Avansearre ynstellings bewurkje (Folgjende stap)" + }, + "data_description": { + "apply_suggestions": "Selektearje dit fakje om wearden foarsteld troch it learalgoritme nei it formulier te kopiearjen. Kontrolearje foardat jo bewarje.\n\nOanbefelle (fan learen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selektearje it type apparaat (waskmasine, droeger, ôfwaskmasine, kofjemasine, EV). Dizze tag wurdt opslein mei elke syklus foar bettere organisaasje.", + "power_sensor": "De sensor-entiteit rapportearret real-time macht (yn watt). Jo kinne dit op elk momint feroarje sûnder histoaryske gegevens te ferliezen - nuttich as jo in smart plug ferfange.", + "min_power": "Stromlêzingen boppe dizze drompel (yn watt) jouwe oan dat it apparaat wurket. Begjin mei 2W foar de measte apparaten.", + "off_delay": "Sekonden moat de glêde krêft ûnder de stopdrompel bliuwe om foltôging te markearjen. Standert: 120s.", + "notify_actions": "Opsjonele aksjesekwinsje fan Home Assistant om te rinnen foar notifikaasjes (skripts, notify, TTS, ensfh.). As ynsteld, rinne aksjes foardat fallback notify tsjinst.", + "notify_people": "Minsken entiteiten brûkt foar oanwêzigens gating. As ynskeakele, wurdt de levering fan notifikaasjes fertrage oant op syn minst ien selektearre persoan thús is.", + "notify_only_when_home": "As ynskeakele, fertrage notifikaasjes oant op syn minst ien selektearre persoan thús is.", + "notify_fire_events": "As ynskeakele, trigger Home Assistant-eveneminten foar syklusbegjin en -ein (ha_washdata_cycle_started en ha_washdata_cycle_ended) om automatisaasjes te riden.", + "notify_start_services": "Ien of mear notifikaasjetsjinsten om te warskôgjen as in syklus begjint. Lit leech om startnotifikaasjes oer te slaan.", + "notify_finish_services": "Ien of mear notifikaasjetsjinsten om te warskôgjen as in syklus einiget of tichtby foltôging. Lit leech om finishnotifikaasjes oer te slaan.", + "notify_live_services": "Ien of mear notifikaasjetsjinsten om live foarútgongsupdates te ûntfangen wylst de syklus rint (allinich mobile begelieder-app). Lit leech om live updates oer te slaan.", + "notify_before_end_minutes": "Minuten foar it rûsde ein fan 'e syklus om in notifikaasje te triggerjen. Stel op 0 om út te skeakeljen. Standert: 0.", + "notify_title": "Oanpaste titel foar notifikaasjes. Unterstützt {device} plakhâlder.", + "notify_icon": "Ikoan om te brûken foar notifikaasjes (bgl. mdi:wasmachine). Lit leech om sûnder ikoan te stjoeren.", + "notify_start_message": "Berjocht ferstjoerd as in syklus begjint. Unterstützt {device} plakhâlder.", + "notify_finish_message": "Berjocht ferstjoerd as in syklus einiget. Unterstützt {device}, {duration}, {program}, {energy_kwh}, {cost} plakhâlders.", + "notify_pre_complete_message": "Tekst fan de weromkommende live foarútgong updates wylst de syklus rint. Unterstützt {device}, {minutes}, {program} plakhâlders." + } + }, + "notifications": { + "title": "Notifikaasjes", + "description": "Konfigurearje notifikaasjekanalen, sjabloanen en opsjonele live updates foar mobile foarútgong.", + "data": { + "notify_actions": "Notifikaasje Aksjes", + "notify_people": "Fertrage levering oant dizze minsken thús binne", + "notify_only_when_home": "Notifikaasjes fertrage oant selekteare persoan thús is", + "notify_fire_events": "Automatisearring eveneminten triggerje", + "notify_start_services": "Syklusbegjin - Notifikaasjedoelen", + "notify_finish_services": "Syklus foltôgje - Notifikaasjedoelen", + "notify_live_services": "Live foarútgong - Notifikaasjedoelen", + "notify_before_end_minutes": "Notifikaasje foar foltôging (minuten foar ein)", + "notify_live_interval_seconds": "Live Update-ynterval (sekonden)", + "notify_live_overrun_percent": "Live Update Overrun Allowance (%)", + "notify_live_chronometer": "Chronometer ôfteltimer", + "notify_title": "Notifikaasje titel", + "notify_icon": "Notifikaasje Ikoan", + "notify_start_message": "Begjin berjochtformaat", + "notify_finish_message": "Berjochtformaat foltôgje", + "notify_pre_complete_message": "Pre-foltôging berjochtformaat", + "energy_price_entity": "Enerzjypriis - Entiteit (opsjoneel)", + "energy_price_static": "Enerzjypriis - Statyske wearde (opsjoneel)", + "go_back": "Gean werom sûnder op te slaan", + "notify_reminder_message": "Herinneringsberjochtformaat", + "notify_timeout_seconds": "Auto-dismiss Nei (sekonden)", + "notify_channel": "Notifikaasjekanaal (status / live / herinnering)", + "notify_finish_channel": "Foltôge Notifikaasjekanaal" + }, + "data_description": { + "go_back": "Tik dit oan en klik op Yntsjinje om alle boppesteande wizigingen fuort te smiten en werom te gean nei it haadmenu.", + "notify_actions": "Opsjonele aksjesekwinsje fan Home Assistant om te rinnen foar notifikaasjes (skripts, notify, TTS, ensfh.). As ynsteld, rinne aksjes foardat fallback notify tsjinst.", + "notify_people": "Minsken entiteiten brûkt foar oanwêzigens gating. As ynskeakele, wurdt de levering fan notifikaasjes fertrage oant op syn minst ien selektearre persoan thús is.", + "notify_only_when_home": "As ynskeakele, fertrage notifikaasjes oant op syn minst ien selektearre persoan thús is.", + "notify_fire_events": "As ynskeakele, trigger Home Assistant-eveneminten foar syklusbegjin en -ein (ha_washdata_cycle_started en ha_washdata_cycle_ended) om automatisaasjes te riden.", + "notify_start_services": "Ien of mear notifikaasjetsjinsten om te warskôgjen as in syklus begjint (bgl. notify.mobile_app_my_phone). Lit leech om startnotifikaasjes oer te slaan.", + "notify_finish_services": "Ien of mear notifikaasjetsjinsten om te warskôgjen as in syklus einiget of tichtby foltôging. Lit leech om finishnotifikaasjes oer te slaan.", + "notify_live_services": "Ien of mear notifikaasjetsjinsten om live foarútgongsupdates te ûntfangen wylst de syklus rint (allinich mobile begelieder-app). Lit leech om live updates oer te slaan.", + "notify_before_end_minutes": "Minuten foar it rûsde ein fan 'e syklus om in notifikaasje te triggerjen. Stel op 0 om út te skeakeljen. Standert: 0.", + "notify_live_interval_seconds": "Hoe faak foarútgong updates wurde ferstjoerd wylst rinnen. Legere wearden binne responsiver, mar konsumearje mear notifikaasjes. In minimum fan 30 sekonden wurdt hanthavene - wearden ûnder 30 s wurde automatysk ôfrûn nei 30 s.", + "notify_live_overrun_percent": "Feiligens marzje boppe de rûsde update count foar lange / oerrinnende syklusen (bygelyks 20% tastean 1.2x skatte updates).", + "notify_live_chronometer": "As ynskeakele, omfettet elke live update in chronometer countdown nei de rûsde eintiid. De notifikaasjetimer tikt automatysk del op it apparaat tusken updates (allinich Android-begelieder-app).", + "notify_title": "Oanpaste titel foar notifikaasjes. Unterstützt {device} plakhâlder.", + "notify_icon": "Ikoan om te brûken foar notifikaasjes (bgl. mdi:wasmachine). Lit leech om sûnder ikoan te stjoeren.", + "notify_start_message": "Berjocht ferstjoerd as in syklus begjint. Unterstützt {device} plakhâlder.", + "notify_finish_message": "Berjocht ferstjoerd as in syklus einiget. Unterstützt {device}, {duration}, {program}, {energy_kwh}, {cost} plakhâlders.", + "energy_price_entity": "Selektearje in numerike entiteit dy't de hjoeddeistige elektrisiteitspriis hâldt (bygelyks sensor.electricity_price, input_number.energy_rate). As ynsteld, aktivearret de {cost} plakhâlder. Nimt foarrang boppe de statyske wearde as beide binne konfigurearre.", + "energy_price_static": "Fêste elektrisiteitspriis per kWh. As ynsteld, aktivearret de {cost} plakhâlder. Negearre as in entiteit hjirboppe ynsteld is. Foegje jo falutasymboal ta yn it berjochtsjabloan, f.eks. {cost} €.", + "notify_reminder_message": "Tekst fan 'e ienmalige herinnering stjoerde it ynstelde oantal minuten foar it rûsde ein. Ûnderskieden fan de weromkommende live updates hjirboppe. Unterstützt {device}, {minutes}, {program} plakhâlders.", + "notify_timeout_seconds": "Ferwiderje notifikaasjes automatysk nei dizze protte sekonden (trochstjoerd as 'timeout' fan 'e begelieder-app). Stel op 0 om se te hâlden oant se wurde wegere. Standert: 0.", + "notify_channel": "Android begeliedend app-notifikaasjekanaal foar notifikaasjes oer status, live foarútgong en herinnerings. It lûd en it belang fan in kanaal wurde konfigureare yn 'e begelieder-app de earste kear dat de kanaalnamme wurdt brûkt - WashData stelt allinich de kanaalnamme yn. Lit leech foar de app standert.", + "notify_finish_channel": "Separate Android kanaal foar de kleare warskôging (ek brûkt troch de herinnering en it wachtsjen fan 'e wask) sadat it in eigen lûd kin hawwe. Lit leech om it boppesteande kanaal opnij te brûken.", + "notify_pre_complete_message": "Tekst fan de weromkommende live foarútgong updates wylst de syklus rint. Unterstützt {device}, {minutes}, {program} plakhâlders." + } + }, + "advanced_settings": { + "title": "Avansearre ynstellings", + "description": "Stel deteksjedrompels, learen en avansearre gedrach ôf. Standertwearden wurkje goed foar de measte ynstellings.", + "sections": { + "suggestions_section": { + "name": "Foarstelde ynstellings", + "description": "{suggestions_count} oanlearde suggestjes beskikber. Tik Oanbefelle wearden tapasse oan om foarstelde wizigingen foar it bewarjen te besjen.", + "data": { + "apply_suggestions": "Oanbefelle wearden tapasse" + }, + "data_description": { + "apply_suggestions": "Selektearje dit fakje om in resinsjestap te iepenjen foar foarstelde wearden út it learalgoritme. Neat wurdt automatysk bewarre.\n\nOanbefelle (fan learen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Deteksje en krêftdrompels", + "description": "Wannear't it apparaat as OAN of ÚT beskôge wurdt, enerzjygrinzen en timing foar statusoergongen.", + "data": { + "start_duration_threshold": "Start debounce Duration (s)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Minimale foltôgingstiid (sekonden)", + "start_threshold_w": "Startdrempel (W)", + "stop_threshold_w": "Stopdrempel (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Rint-dea-sône (sekonden)", + "end_repeat_count": "End Repeat Count", + "min_off_gap": "Min Gap Between Cycles (s)", + "sampling_interval": "Sampling ynterval (s)" + }, + "data_description": { + "start_duration_threshold": "Negearje koarte krêftspikes koarter dan dizze doer (sekonden). Filtert bootspikes út (bgl. 60W foar 2s) foardat de echte syklus begjint. Standert: 5s.", + "start_energy_threshold": "Syklus moat op syn minst safolle enerzjy (Wh) sammelje yn 'e START-faze om te befêstigjen. Standert: 0,005 Wh.", + "completion_min_seconds": "Cycles dy't koarter binne as dit sille wurde markearre as 'ûnderbrutsen' sels as se natuerlik einigje. Standert: 600s.", + "start_threshold_w": "Macht moat boppe dizze drompel opkomme om in syklus te STARTEN. Stel heger dan jo standby-krêft. Standert: min_power + 1 W.", + "stop_threshold_w": "Power moat falle Ûnder dizze drompel foar END in syklus. Stel krekt boppe nul om lûd te foarkommen dat falske eintsjes trigger. Standert: 0,6 * min_power.", + "end_energy_threshold": "Syklus einiget allinnich as enerzjy yn it lêste Off-Delay finster is ûnder dizze wearde (Wh). Foarkomt falske einen as macht fluktuearret tichtby nul. Standert: 0,05 Wh.", + "running_dead_zone": "Neidat syklus begjint, negearje macht dips foar dizze protte sekonden om foar te kommen falske ein deteksje tidens de earste spin-up faze. Stel op 0 om út te skeakeljen. Standert: 0s.", + "end_repeat_count": "Oantal kearen moat de einbetingst (krêft ûnder drompel foar off_delay) efterinoar foldien wurde foardat de syklus einiget. Hegere wearden foarkomme falske eintsjes tidens pauzes. Standert: 1.", + "min_off_gap": "As in syklus begjint binnen safolle sekonden fan 'e foarige ein, sille se wurde gearfoege yn ien syklus.", + "sampling_interval": "Minimum sampling ynterval yn sekonden. Updates rapper dan dit taryf sille wurde negearre. Standert: 30s (2s foar waskmasines, waskmasine-droegers, en ôfwaskmasines)." + } + }, + "matching_section": { + "name": "Profylôfstimming en learen", + "description": "Hoe agressyf WashData rinnende syklussen oanlearde profilen mei-inoar fergeliket en wannear't om feedback frege wurdt.", + "data": { + "profile_match_min_duration_ratio": "Profylwedstriid Min Duration Ratio (0.1-1.0)", + "profile_match_interval": "Profylwedstriid ynterval (sekonden)", + "profile_match_threshold": "Profyl oerienkomstdrompel", + "profile_unmatch_threshold": "Profyl net-oerienkomstdrompel", + "auto_label_confidence": "Auto-label fertrouwen (0-1)", + "learning_confidence": "Feedback fersyk fertrouwen (0-1)", + "suppress_feedback_notifications": "Underdrukke feedbacknotifikaasjes", + "duration_tolerance": "Duration Tolerance (0-0,5)", + "smoothing_window": "Smoothing Window (samples)", + "profile_duration_tolerance": "Tolerânsje fan profylwedstriiddoer (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum doer ratio foar profyl matching. Running syklus moat op syn minst dizze fraksje fan profyl doer te oerien. Leger = earder matching. Standert: 0,50 (50%).", + "profile_match_interval": "Hoe faak (yn sekonden) besykje profyl oerienkomst tidens in syklus. Legere wearden jouwe rappere deteksje, mar brûke mear CPU. Standert: 300s (5 minuten).", + "profile_match_threshold": "Minimale DTW-likensskoare (0.0–1.0) fereaske om in profyl as in wedstriid te beskôgjen. Heger = strangere matching, minder falske positives. Standert: 0.4.", + "profile_unmatch_threshold": "DTW-oerienkomstskoare (0.0-1.0) wêrûnder in earder oerienkommen profyl wurdt ôfwiisd. Moat leger wêze as de wedstriiddrompel om flikkerjen te foarkommen. Standert: 0,35.", + "auto_label_confidence": "Label syklusen automatysk by of boppe dit fertrouwen by foltôging (0.0–1.0).", + "learning_confidence": "Minimale fertrouwen om brûkersferifikaasje te freegjen fia in evenemint (0.0–1.0).", + "suppress_feedback_notifications": "As ynskeakele, sil WashData noch yntern feedback folgje en freegje, mar sil gjin persistente notifikaasjes sjen litte dy't jo freegje om syklusen te befêstigjen. Nuttich as syklusen altyd goed wurde ûntdutsen en jo fine dat de prompts ôfliedend fine.", + "duration_tolerance": "Tolerânsje foar oerbleaune skattings tidens in run (0,0-0,5 komt oerien mei 0-50%).", + "smoothing_window": "Oantal resinte krêftlêzingen brûkt foar ferpleatse-gemiddelde glêdens.", + "profile_duration_tolerance": "Tolerânsje brûkt troch profyl oerienkomst foar totale duration fariânsje (0.0-0.5). Standert gedrach: ± 25%." + } + }, + "timing_section": { + "name": "Timing, ûnderhâld en debug", + "description": "Watchdog, reset fan fuortgong, automatysk ûnderhâld en it toanen fan debug-entiteiten.", + "data": { + "watchdog_interval": "Watchdog-ynterval (sekonden)", + "no_update_active_timeout": "Gjin bywurkingstiid (s)", + "progress_reset_delay": "Fertraging weromsette foarútgong (sekonden)", + "auto_maintenance": "Auto-ûnderhâld ynskeakelje", + "expose_debug_entities": "Debug-entiteiten eksposearje", + "save_debug_traces": "Debug-spoaren bewarje" + }, + "data_description": { + "watchdog_interval": "Sekonden tusken watchdog kontrôles wylst rinnen. Standert: 5s. WAARSKUWING: Soargje dat dit HEGER is dan it bywurkingsynterval fan jo sensor om falske stops te foarkommen (bygelyks as de sensor elke 60s bywurket, brûk dan 65s+).", + "no_update_active_timeout": "Foar sensors foar publisearje-op-feroaring: twinge allinich in AKTIVE syklus ôf as gjin updates oankomme foar safolle sekonden. Foltôging mei lege krêft brûkt noch de Off Delay.", + "progress_reset_delay": "Nei in syklus foltôge (100%), wachtsje safolle sekonden fan idle foardat jo de foarútgong weromsette nei 0%. Standert: 300s.", + "auto_maintenance": "Automatysk ûnderhâld ynskeakelje (monsters reparearje, fragminten gearfoegje).", + "expose_debug_entities": "Lit avansearre sensoren sjen (fertrouwen, faze, dûbelsinnigens) foar debuggen.", + "save_debug_traces": "Bewarje detaillearre ranglist en krêftspoargegevens yn skiednis (fergruttet opslachgebrûk)." + } + }, + "anti_wrinkle_section": { + "name": "Kreukbeskerming (Drûgers)", + "description": "Negearje trommelrotaasjes nei de syklus om spûksyklussen foar te kommen. Allinnich drûger / wask-drûger.", + "data": { + "anti_wrinkle_enabled": "Anti-rimpelskerm (allinich droeger)", + "anti_wrinkle_max_power": "Anti-rimpel Max Power (W)", + "anti_wrinkle_max_duration": "Anti-rimpel Max Duration (s)", + "anti_wrinkle_exit_power": "Anti-rimpel útgongskrêft (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Negearje koarte trommelrotaasjes mei lege macht nei't in syklus einiget om spooksyklusen te foarkommen (allinich droeger / waskmasine-droeger).", + "anti_wrinkle_max_power": "Bursts op of ûnder dizze krêft wurde behannele as anty-rimpelrotaasjes ynstee fan nije syklusen. Allinnich jildt as Anti-Wrinkle Shield is ynskeakele.", + "anti_wrinkle_max_duration": "Maksimum burst lingte te behanneljen as anty-rimpel rotaasje. Allinnich jildt as Anti-Wrinkle Shield is ynskeakele.", + "anti_wrinkle_exit_power": "As macht sakket ûnder dit nivo, útgean anty-rimpel fuortendaliks (wier út). Allinnich jildt as Anti-Wrinkle Shield is ynskeakele." + } + }, + "delay_start_section": { + "name": "Fertrage startdeteksje", + "description": "Behannelje oanhâldende standby mei leech fermogen as 'Wachtet om te starten' oant de syklus echt begjint.", + "data": { + "delay_start_detect_enabled": "Fertrage startdeteksje ynskeakelje", + "delay_confirm_seconds": "Fertrage start: standby-befêstigingstiid (s)", + "delay_timeout_hours": "Fertrage start: Max Wachttiid (h)" + }, + "data_description": { + "delay_start_detect_enabled": "As it ynskeakele is, wurdt in koarte drainspike mei leech krêft folge troch oanhâldende standby-krêft ûntdutsen as in fertrage start. De tastânsensor lit 'Wachtsjen om te begjinnen' sjen tidens de fertraging. Gjin programma tiid of foarútgong wurdt folge oant de syklus eins begjint. Fereasket start_threshold_w boppe de drain spike macht ynsteld wurde.", + "delay_confirm_seconds": "It fermogen moat safolle sekonden tusken Stopdrompel (W) en Startdrompel (W) bliuwe foardat WashData yn 'Wachtet om te starten' komt. Filteret koarte pieken by menu-navigaasje fuort. Standert: 60 s.", + "delay_timeout_hours": "Feiligens-time-out: as de masine nei dizze protte oeren noch yn 'Wachtsjen om te begjinnen' stiet, set WashData werom nei Off. Standert: 8 h." + } + }, + "external_triggers_section": { + "name": "Eksterne triggers, doar en pauze", + "description": "Opsjonele eksterne ein-trigger-sensor, doar-sensor foar pauze/skjin-deteksje, en skeakel foar stroom-út by pauze.", + "data": { + "external_end_trigger_enabled": "Ynskeakelje External Cycle End Trigger", + "external_end_trigger": "Eksterne Cycle End Sensor", + "external_end_trigger_inverted": "Invert Trigger Logic (Trigger op OFF)", + "door_sensor_entity": "Door Sensor", + "pause_cuts_power": "Snij de macht by it pauze", + "switch_entity": "Switch Entity (foar Pause Power Cut)", + "notify_unload_delay_minutes": "Wasserij Wacht-notifikaasje fertraging (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Skeakelje tafersjoch fan in eksterne binêre sensor yn om syklusfoltôging te triggerjen.", + "external_end_trigger": "Selektearje in binêre sensor-entiteit. As dizze sensor trigger, sil de hjoeddeistige syklus einigje mei de status 'foltôge'.", + "external_end_trigger_inverted": "Standert, de trekker fjoer as de sensor draait ON. Selektearje dit fakje om te sjitten as de sensor ynstee útskeakelt.", + "door_sensor_entity": "Opsjonele binêre sensor foar de masine doar (oan = iepen, út = sluten). As de doar iepenet tidens in aktive syklus, sil WashData de syklus befêstigje as opsetlik ûnderbrutsen. Nei't de syklus einiget, as de doar noch ticht is, feroaret de steat nei 'Skjin' oant de doar iepene wurdt. Opmerking: it sluten fan 'e doar sil NET in pauze syklus automatysk ferfetsje - ferfetsje moat manuell wurde aktivearre fia de knop Resume Cycle of tsjinst.", + "pause_cuts_power": "As jo ​​​​pauze fia de Pause Cycle knop of tsjinst, útsette ek de ynstelde switch entiteit. Allinnich effekt as in switch entiteit is konfigurearre foar dit apparaat.", + "switch_entity": "De switch-entiteit om te wikseljen by pauze of trochstart. Fereaske as 'Stromje besunigje by pauze' ynskeakele is.", + "notify_unload_delay_minutes": "Stjoer in notifikaasje fia it finish-notifikaasjekanaal nei't de syklus einiget en de doar foar dizze protte minuten net iepene is (fereasket Door Sensor). Stel op 0 om út te skeakeljen. Standert: 60 min." + } + }, + "pump_section": { + "name": "Pompmonitor", + "description": "Allinnich foar pompapparaattype. Fjoert in barren as in pompsyklus langer as dizze doer rint.", + "data": { + "pump_stuck_duration": "Pomp fêst warskôgingsdrempel (s) (allinich pomp)" + }, + "data_description": { + "pump_stuck_duration": "As in pomp syklus noch rint nei dit protte sekonden, in ha_washdata_pump_stuck evenemint wurdt ûntslein ien kear. Brûk dit om in blokkearre motor te detektearjen (typyske sumppomp is ûnder 60 s). Standert: 1800 s (30 min). Allinnich type pompapparaat." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Keppelje dit WashData-apparaat opsjoneel oan in besteand Home Assistant-apparaat (bygelyks de smart plug of it apparaat sels). It WashData-apparaat wurdt dan werjûn as \"Ferbûn fia\" dat apparaat. Lit leech om WashData as in standalone apparaat te hâlden.", + "data": { + "linked_device": "Keppele apparaat" + }, + "data_description": { + "linked_device": "Selektearje it apparaat om WashData oan te ferbinen. Dit foeget in 'Ferbûn fia' referinsje ta yn it apparaatregister; WashData hâldt har eigen apparaatkaart en entiteiten. Wiskje de seleksje om de keppeling te ferwiderjen." + } + } + } + }, + "diagnostics": { + "title": "Diagnostyk en ûnderhâld", + "description": "Utfiere ûnderhâldsaksjes lykas it gearfoegjen fan fragmintele syklusen of it migrearjen fan bewarre gegevens.\n\n**Opslachgebrûk**\n\n- Bestânsgrutte: {file_size_kb} KB\n- Syklusen: {cycle_count}\n- Profilen: {profile_count}\n- Debug Spoaren: {debug_count}", + "menu_options": { + "reprocess_history": "Underhâld: Gegevens opnij ferwurkje en optimalisearje", + "clear_debug_data": "Debuggegevens wiskje (romte frijmeitsje)", + "wipe_history": "Wiskje ALLE gegevens foar dit apparaat (ûnomkearber)", + "export_import": "JSON eksportearje / ymportearje mei ynstellings (kopiearje / plakke)", + "menu_back": "← Werom" + } + }, + "clear_debug_data": { + "title": "Wiskje debuggegevens", + "description": "Binne jo wis dat jo alle bewarre debug-spoaren wiskje wolle? Dit sil romte frijmeitsje, mar detaillearre ranglistynformaasje fuortsmite fan ferline syklusen." + }, + "manage_cycles": { + "title": "Behear syklusen", + "description": "Resinte syklusen:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-label âlde syklusen", + "select_cycle_to_label": "Spesifike syklus labelen", + "select_cycle_to_delete": "Wiskje syklus", + "interactive_editor": "Ynteraktive bewurker gearfoegje / splitsen", + "trim_cycle_select": "Syklusgegevens trimme", + "menu_back": "← Werom" + } + }, + "manage_cycles_empty": { + "title": "Behear syklusen", + "description": "Noch gjin syklusen opnommen.", + "menu_options": { + "auto_label_cycles": "Auto-label âlde syklusen", + "menu_back": "← Werom" + } + }, + "manage_profiles": { + "title": "Beheare Profilen", + "description": "Profyl gearfetting:\n{profile_summary}", + "menu_options": { + "create_profile": "Meitsje nij profyl", + "edit_profile": "Profyl bewurkje/omneame", + "delete_profile_select": "Wiskje profyl", + "profile_stats": "Profylstatistiken", + "cleanup_profile": "Skiednis skjinmeitsje - Grafyk en wiskje", + "assign_profile_phases_select": "Fasebereiken tawize", + "menu_back": "← Werom" + } + }, + "manage_profiles_empty": { + "title": "Beheare Profilen", + "description": "Gjin profilen makke noch.", + "menu_options": { + "create_profile": "Meitsje nij profyl", + "menu_back": "← Werom" + } + }, + "manage_phase_catalog": { + "title": "Fasekatalogus beheare", + "description": "Fasekatalogus (standert foar dit apparaat + oanpaste fazen):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Meitsje Nije Fase", + "phase_catalog_edit_select": "Fase bewurkje", + "phase_catalog_delete": "Fase wiskje", + "menu_back": "← Werom" + } + }, + "phase_catalog_create": { + "title": "Oanpaste faze oanmeitsje", + "description": "Meitsje in oanpaste faze namme en beskriuwing, en kies hokker apparaat type it jildt foar.", + "data": { + "target_device_type": "Target Device Type", + "phase_name": "Fase Namme", + "phase_description": "Fase Beskriuwing" + } + }, + "phase_catalog_edit_select": { + "title": "Oanpaste faze bewurkje", + "description": "Selektearje in oanpaste faze om te bewurkjen.", + "data": { + "phase_name": "Oanpaste Fase" + } + }, + "phase_catalog_edit": { + "title": "Oanpaste faze bewurkje", + "description": "Bewurkingsfaze: {phase_name}", + "data": { + "phase_name": "Fase Namme", + "phase_description": "Fase Beskriuwing" + } + }, + "phase_catalog_delete": { + "title": "Wiskje Oanpaste Fase", + "description": "Selektearje in oanpaste faze om te wiskjen. Elke tawiisde berik dy't dizze faze brûke sil wurde fuortsmiten.", + "data": { + "phase_name": "Oanpaste Fase" + } + }, + "assign_profile_phases": { + "title": "Fasebereiken tawize", + "description": "Fasefoarbyld foar profyl: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuele berik:**\n{current_ranges}\n\nBrûk de aksjes hjirûnder om berikken ta te foegjen, te bewurkjen, te wiskjen of op te slaan.", + "menu_options": { + "assign_profile_phases_add": "Faseberik tafoegje", + "assign_profile_phases_edit_select": "Faseberik bewurkje", + "assign_profile_phases_delete": "Faseberik wiskje", + "phase_ranges_clear": "Alle berikken wiskje", + "assign_profile_phases_auto_detect": "Fazen automatysk ûntdekke", + "phase_ranges_save": "Bewarje en werom", + "menu_back": "← Werom" + } + }, + "assign_profile_phases_add": { + "title": "Faseberik tafoegje", + "description": "Foegje ien fazeberik ta oan it aktuele ûntwerp.", + "data": { + "phase_name": "Faze", + "start_min": "Start minút", + "end_min": "Eind minút" + } + }, + "assign_profile_phases_edit_select": { + "title": "Faseberik bewurkje", + "description": "Selektearje in berik om te bewurkjen.", + "data": { + "range_index": "Fase Range" + } + }, + "assign_profile_phases_edit": { + "title": "Faseberik bewurkje", + "description": "Update it selektearre fazeberik.", + "data": { + "phase_name": "Faze", + "start_min": "Start minút", + "end_min": "Eind minút" + } + }, + "assign_profile_phases_delete": { + "title": "Faseberik wiskje", + "description": "Selektearje in berik om út it konsept te ferwiderjen.", + "data": { + "range_index": "Fase Range" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detekteare fazen", + "description": "Profyl: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} faze(s) automatysk ûntdutsen.** Kies in aksje hjirûnder.", + "data": { + "action": "Aksje" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Namme ûntdutsen fazen", + "description": "Profyl: **{profile_name}**\n\n**{detected_count} faze(s) ûntdutsen:**\n{ranges_summary}\n\nJou elke faze in namme ta." + }, + "assign_profile_phases_select": { + "title": "Fasebereiken tawize", + "description": "Selektearje in profyl om fazeberiken te konfigurearjen.", + "data": { + "profile": "Profyl" + } + }, + "profile_stats": { + "title": "Profylstatistiken", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Meitsje nij profyl", + "description": "Meitsje in nij profyl mei de hân of út in ferline syklus.\n\nFoarbylden fan profylnammen: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Profylnamme", + "reference_cycle": "Referinsjesyklus (opsjoneel)", + "manual_duration": "Manual Duration (minuten)" + } + }, + "edit_profile": { + "title": "Profyl bewurkje", + "description": "Selektearje in profyl om omneame", + "data": { + "profile": "Profyl" + } + }, + "rename_profile": { + "title": "Profyldetails bewurkje", + "description": "Aktueel profyl: {current_name}", + "data": { + "new_name": "Profylnamme", + "manual_duration": "Manual Duration (minuten)" + } + }, + "delete_profile_select": { + "title": "Wiskje profyl", + "description": "Selektearje in profyl om te wiskjen", + "data": { + "profile": "Profyl" + } + }, + "delete_profile_confirm": { + "title": "Befêstigje wiskje profyl", + "description": "⚠️ Dit sil profyl permanint wiskje: {profile_name}", + "data": { + "unlabel_cycles": "Label fuortsmite fan syklusen mei dit profyl" + } + }, + "auto_label_cycles": { + "title": "Auto-label âlde syklusen", + "description": "Fûn {total_count} totale syklusen. Profilen: {profiles}", + "data": { + "confidence_threshold": "Minimum fertrouwen (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Selektearje syklus om te labelen", + "description": "✓ = foltôge, ⚠ = opnij, ✗ = ûnderbrutsen", + "data": { + "cycle_id": "Syklus" + } + }, + "select_cycle_to_delete": { + "title": "Wiskje syklus", + "description": "⚠️ Dit sil de selekteare syklus permanint wiskje", + "data": { + "cycle_id": "Syklus om te wiskjen" + } + }, + "label_cycle": { + "title": "Syklus labelen", + "description": "{cycle_info}", + "data": { + "profile_name": "Profyl", + "new_profile_name": "Nije profylnamme (as oanmakke)" + } + }, + "post_process": { + "title": "Prosesskiednis (Fergearje / Splitsen)", + "description": "Syklusskiednis skjinmeitsje troch fersnippere runen te fusearjen en ferkeard gearfoege syklusen binnen in tiidfinster te splitsen. Fier it oantal oeren yn om werom te sjen (brûk 999999 foar allegear).", + "data": { + "time_range": "Oersjochfinster (oeren)", + "gap_seconds": "Fuortsmite / Split Gap (sekonden)" + }, + "data_description": { + "time_range": "Oantal oeren om te scannen foar fragmintele syklusen om te fusearjen. Brûk 999999 om alle histoaryske gegevens te ferwurkjen.", + "gap_seconds": "Drompel om te besluten split vs gearfoegje. Gaps KORTER as dit wurde gearfoege. Gaps LANGER as dit wurde splitst. Ferleegje dit om in split te twingen op in syklus dy't ferkeard gearfoege is." + } + }, + "cleanup_profile": { + "title": "Skiednis skjinmeitsje - Selektearje profyl", + "description": "Selektearje in profyl om te visualisearjen. Dit sil in grafyk generearje dy't alle ferline syklusen foar dit profyl toant om te helpen by it identifisearjen fan outliers.", + "data": { + "profile": "Profyl" + } + }, + "cleanup_select": { + "title": "Skiednis skjinmeitsje - Grafyk en wiskje", + "description": "![Graph]({graph_url})\n\n**Visualisearjen {profile_name}**\n\nDe grafyk toant yndividuele syklusen as kleurde rigels. Identifisearje outliers (rigels fier fan 'e groep) en oerienkomme mei har kleur yn 'e list hjirûnder om se te wiskjen.\n\n**Selektearje syklusen om PERMANENT te wiskjen:**", + "data": { + "cycles_to_delete": "Cycles om te wiskjen (kontrolearje oerienkommende kleuren)" + } + }, + "interactive_editor": { + "title": "Ynteraktive bewurker gearfoegje / splitsen", + "description": "Selektearje in hânmjittige aksje om út te fieren op jo syklusskiednis.", + "menu_options": { + "editor_split": "Split in syklus (Fyn gatten)", + "editor_merge": "Cycles gearfoegje (Join fragminten)", + "editor_delete": "Syklus(en) wiskje", + "menu_back": "← Werom" + } + }, + "editor_select": { + "title": "Selektearje Cycles", + "description": "Selektearje de syklus(en) om te ferwurkjen.\n\n{info_text}", + "data": { + "selected_cycles": "Cycles" + } + }, + "editor_configure": { + "title": "Konfigurearje & tapasse", + "description": "{preview_md}", + "data": { + "confirm_action": "Aksje", + "merged_profile": "Resultaat profyl", + "new_profile_name": "Nije profylnamme", + "confirm_commit": "Ja, ik befêstigje dizze aksje", + "segment_0_profile": "Segment 1 Profile", + "segment_1_profile": "Segment 2 Profile", + "segment_2_profile": "Segment 3 Profile", + "segment_3_profile": "Segment 4 Profile", + "segment_4_profile": "Segment 5 Profile", + "segment_5_profile": "Segment 6 Profile", + "segment_6_profile": "Segment 7 Profile", + "segment_7_profile": "Segment 8 Profile", + "segment_8_profile": "Segment 9 Profile", + "segment_9_profile": "Segment 10 Profile" + } + }, + "editor_split_params": { + "title": "Split konfiguraasje", + "description": "Pas de gefoelichheid oan foar it splitsen fan dizze syklus. Elke idle gat langer dan dit sil in splitsing feroarsaakje.", + "data": { + "split_mode": "Splitmetoade" + } + }, + "editor_split_auto_params": { + "title": "Auto-splitskonfiguraasje", + "description": "Pas de gefoelichheid oan foar it splitsen fan dizze syklus. Elke ynaktive pauze dy't langer is as dit sil in splitsing feroarsaakje.", + "data": { + "min_gap_seconds": "Drompel foar splitsingsgat (sekonden)" + } + }, + "editor_split_manual_params": { + "title": "Manuele splitsingstiidstimpels", + "description": "{preview_md}Syklusfinster: **{cycle_start_wallclock} → {cycle_end_wallclock}** op {cycle_date}.\n\nFier ien of mear splitsingstiidstimpels yn (`HH:MM` of `HH:MM:SS`), ien per rigel. De syklus wurdt by elke tiidstimpel knipt — N tiidstimpels leverje N+1 segminten op.", + "data": { + "split_timestamps": "Split-tiidstimpels" + } + }, + "reprocess_history": { + "title": "Underhâld: Gegevens opnij ferwurkje en optimalisearje", + "description": "Fiert djippe skjinmeitsjen fan histoaryske gegevens út:\n1. Trims nul-power lêzingen (stilte).\n2. Fixes syklus timing / doer.\n3. Recalculates alle statistyske modellen (envelopes).\n\n** Dit útfiere om gegevensproblemen op te lossen of nije algoritmen oan te passen.**" + }, + "wipe_history": { + "title": "Skiednis wiskje (allinich testen)", + "description": "⚠️ Dit sil ALLE opsleine syklusen en profilen foar dit apparaat permanint wiskje. Dit kin net ûngedien makke wurde!" + }, + "export_import": { + "title": "JSON eksportearje / ymportearje", + "description": "Kopiearje / plakke syklusen, profilen, EN ynstellings foar dit apparaat. Eksportearje hjirûnder of plakke JSON om te ymportearjen.", + "data": { + "mode": "Aksje", + "json_payload": "Folsleine konfiguraasje JSON" + }, + "data_description": { + "mode": "Kies Eksportearje om hjoeddeistige gegevens en ynstellings te kopiearjen, of ymportearje om eksportearre gegevens fan in oar WashData-apparaat te plakjen.", + "json_payload": "Foar Eksportearje: kopiearje dizze JSON (omfettet syklusen, profilen en alle fine-tuned ynstellings). Foar ymportearje: plak JSON eksportearre fan WashData." + } + }, + "record_cycle": { + "title": "Syklus opnimme", + "description": "Meitsje in syklus manuell op om in skjin profyl te meitsjen sûnder ynterferinsje.\n\nStatus: **{status}**\nDuration: {duration}s\nFoarbylden: {samples}", + "menu_options": { + "record_refresh": "Ferfarskje Status", + "record_stop": "Opname stopje (Bewarje en ferwurkje)", + "record_start": "Start Nije opname", + "record_process": "Lêste opname ferwurkje (Trim & Save)", + "record_discard": "Ferwiderje lêste opname", + "menu_back": "← Werom" + } + }, + "record_process": { + "title": "Opname ferwurkje", + "description": "![Graph]({graph_url})\n\n** Grafyk toant rau opname. ** Blau = Keep, Red = Foarstelde Trim.\n*De grafyk is statysk en wurdt net bywurke mei formulierwizigingen.*\n\nOpname stoppe. Trimmen wurde ôfstimd op de ûntdutsen sampling rate (~{sampling_rate}s).\n\nRaw Duration: {duration}s\nFoarbylden: {samples}", + "data": { + "head_trim": "Trimstart (sekonden)", + "tail_trim": "Trim ein (sekonden)", + "save_mode": "Bewarje bestimming", + "profile_name": "Profylnamme" + } + }, + "learning_feedbacks": { + "title": "Learfeedbacks", + "description": "Wachtsjende feedback: {count}\n\n{pending_table}\n\nSelektearje in fersyk foar syklusbeoardieling om te ferwurkjen.", + "menu_options": { + "learning_feedbacks_pick": "Besjoch wachtsjende feedback", + "learning_feedbacks_dismiss_all": "Wys alle wachtsjende feedback ôf", + "menu_back": "← Werom" + } + }, + "learning_feedbacks_pick": { + "title": "Selektearje feedback om te beoardieljen", + "description": "Selektearje in syklusbeoardielingsfersyk om te iepenjen.", + "data": { + "selected_feedback": "Selektearje syklus om te beoardieljen" + } + }, + "learning_feedbacks_empty": { + "title": "Learfeedbacks", + "description": "Gjin wachtsjende feedbackoanfragen fûn.", + "menu_options": { + "menu_back": "← Werom" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Wys alle wachtsjende feedback ôf", + "description": "Jo steane op it punt om **{count}** wachtsjende feedbackfersiken ôf te wizen. Ofwiisde syklussen bliuwe yn jo skiednis, mar sille gjin nij learsinjaal bydrage. Dit kin net ûngedien makke wurde.\n\n✅ Kontrolearje it fakje hjirûnder en klik op **Ferstjoere** om alles ôf te wizen.\n❌ Lit it net oankrúst en klik op **Ferstjoere** om te annulearjen en werom te gean.", + "data": { + "confirm_dismiss_all": "Befêstigje: wiis alle wachtsjende feedbackfersiken ôf" + } + }, + "resolve_feedback": { + "title": "Feedback oplosse", + "description": "{comparison_img}{candidates_table}\n**Profyl ûntdutsen**: {detected_profile} ({confidence_pct}%)\n** Geschatte doer**: {est_duration_min} min\n**Wirklike doer**: {act_duration_min} min\n\n__Kies in aksje hjirûnder:__", + "data": { + "action": "Wat wolle jo dwaan?", + "corrected_profile": "Korrekte programma (as korrigearje)", + "corrected_duration": "Korrekte doer yn sekonden (as korreksje)" + } + }, + "trim_cycle_select": { + "title": "Syklus trimme - Selektearje Syklus", + "description": "Selektearje in syklus om te trimmen. ✓ = foltôge, ⚠ = opnij, ✗ = ûnderbrutsen", + "data": { + "cycle_id": "Syklus" + } + }, + "trim_cycle": { + "title": "Syklus trimme", + "description": "Aktueel finster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aksje" + } + }, + "trim_cycle_start": { + "title": "Trimstart ynstelle", + "description": "Syklusfinster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuele start: **{current_wallclock}** (+{current_offset_min} min fan syklusstart)\n\nKies in nije starttiid mei de klokkiezer hjirûnder.", + "data": { + "trim_start_time": "Nije starttiid", + "trim_start_min": "Nije start (minuten fan syklusstart)" + } + }, + "trim_cycle_end": { + "title": "Trimein ynstelle", + "description": "Syklusfinster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuele ein: **{current_wallclock}** (+{current_offset_min} min fan syklusbegjin)\n\nKies in nije eintiid mei de klokkiezer hjirûnder.", + "data": { + "trim_end_time": "Nije eintiid", + "trim_end_min": "Nije ein (minuten fan syklusbegjin)" + } + } + }, + "error": { + "import_failed": "Ymport mislearre. Plak asjebleaft in jildige WashData eksport JSON.", + "select_exactly_one": "Selektearje krekt ien syklus foar dizze aksje.", + "select_at_least_one": "Selektearje teminsten ien syklus foar dizze aksje.", + "select_at_least_two": "Selektearje teminsten twa syklussen foar dizze aksje.", + "profile_exists": "In profyl mei dizze namme bestiet al", + "rename_failed": "Omneame profyl mislearre. It profyl bestiet mooglik net of in nije namme is al nommen.", + "assignment_failed": "It tawizen fan profyl oan syklus is net slagge", + "duplicate_phase": "In faze mei dizze namme bestiet al.", + "phase_not_found": "Selekteare faze waard net fûn.", + "invalid_phase_name": "Fazenamme kin net leech wêze.", + "phase_name_too_long": "Fazenamme is te lang.", + "invalid_phase_range": "Faseberik is ûnjildich. Ein moat grutter wêze as begjin.", + "invalid_phase_timestamp": "Tiidstempel is ûnjildich. Brûk JJJJ-MM-DD UU:MM of UU:MM opmaak.", + "overlapping_phase_ranges": "Faseberiken oerlaapje. Pas de berikken oan en besykje it nochris.", + "incomplete_phase_row": "Eltse faze rige moat befetsje in faze plus folsleine start / ein wearden foar de selektearre modus.", + "timestamp_mode_cycle_required": "Selektearje asjebleaft in boarnesyklus by it brûken fan tiidstempelmodus.", + "no_phase_ranges": "Der binne noch gjin fazebereiken beskikber foar dy aksje.", + "feedback_notification_title": "WashData: ferifiearje syklus ({device})", + "feedback_notification_message": "**Apparaat**: {device}\n**Programma**: {program} ({confidence}% fertrouwen)\n**Tiid**: {time}\n\nWashData hat jo help nedich om dizze ûntdekte syklus te ferifiearjen.\n\nGean asjebleaft nei **Ynstellings> Apparaten en tsjinsten> WashData> Konfigurearje> Learning Feedbacks** om dit resultaat te befêstigjen of te korrigearjen.", + "suggestions_ready_notification_title": "WashData: Foarstelde ynstellings klear ({device})", + "suggestions_ready_notification_message": "De sensor **Foarstelde ynstellings** rapportearret no **{count}** aksjebere oanbefellings.\n\nOm se te besjen en ta te passen: **Ynstellings> Apparaten en tsjinsten> WashData> Konfigurearje> Avansearre ynstellings> Oanbefelle wearden tapasse**.\n\nSuggestjes binne opsjoneel en werjûn foar beoardieling foardat jo bewarje.", + "trim_range_invalid": "Start moat foar ein wêze. Pas jo trimpunten asjebleaft oan.", + "trim_failed": "Trim mislearre. De syklus kin net mear bestean of it finster is leech.", + "invalid_split_timestamp": "Tiidstimpel is ûnjildich of falt bûten it syklusfinster. Brûk HH:MM of HH:MM:SS binnen it syklusfinster.", + "no_split_segments_found": "Der koene gjin jildige segminten makke wurde út dizze tiidstimpels. Soargje derfoar dat elke tiidstimpel binnen it syklusfinster falt en segminten teminsten 1 minút lang binne.", + "auto_tune_suggestion": "{device_type} {device_title} spoeksyklusen ûntdutsen. Foarstelde min_power feroaring: {current_min}W -> {new_min}W (net automatysk tapast).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} spoeksyklusen ûntdutsen. Foarstelde min_power feroaring: {current_min}W -> {new_min}W (net automatysk tapast)." + }, + "abort": { + "no_cycles_found": "Gjin cycles fûn", + "no_split_segments_found": "Gjin splitbere segminten fûn yn de selektearre syklus.", + "cycle_not_found": "De selektearre syklus koe net laden wurde.", + "no_power_data": "Gjin macht trace gegevens beskikber foar de selektearre syklus.", + "no_profiles_found": "Gjin profilen fûn. Meitsje earst in profyl.", + "no_profiles_for_matching": "Gjin profilen beskikber foar matching. Meitsje earst profilen.", + "no_unlabeled_cycles": "Alle syklusen binne al markearre", + "no_suggestions": "Der binne noch gjin foarstelde wearden beskikber. Rinne in pear syklussen en besykje it nochris.", + "no_predictions": "Gjin foarsizzings", + "no_custom_phases": "Gjin oanpaste fazen fûn.", + "reprocess_success": "Mei sukses ferwurke {count} syklusen.", + "debug_data_cleared": "Debuggegevens fan {count} syklusen mei súkses wiske." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Syklus begjin", + "cycle_finish": "Syklus ein", + "cycle_live": "Live foarútgong" + } + }, + "common_text": { + "options": { + "unlabeled": "(Net-label)", + "create_new_profile": "Meitsje nij profyl", + "remove_label": "Fuortsmite Label", + "no_reference_cycle": "(Gjin referinsjesyklus)", + "all_device_types": "Alle apparaattypen", + "current_suffix": "(aktueel)", + "editor_select_info": "Selektearje 1 syklus om te splitsen, of 2+ syklusen om te fusearjen.", + "editor_select_info_split": "Selektearje 1 syklus om te splitsen.", + "editor_select_info_merge": "Selektearje 2+ syklussen om gear te foegjen.", + "editor_select_info_delete": "Selektearje 1+ syklus(sen) om te wiskjen.", + "no_cycles_recorded": "Noch gjin syklusen opnommen.", + "profile_summary_line": "- **{name}**: {count} syklusen, {avg} min gem.", + "no_profiles_created": "Gjin profilen makke noch.", + "phase_preview": "Fasefoarbyld", + "phase_preview_no_curve": "Gemiddelde profylkromme is noch net beskikber. Run/labelje mear syklusen foar dit profyl.", + "average_power_curve": "Gemiddelde Power Curve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} syklusen, ~{duration} min gem.)", + "profile_option_short_fmt": "{name} ({count} syklusen, ~{duration} min)", + "deleted_cycles_from_profile": "Wiske {count} syklusen fan {profile}.", + "not_enough_data_graph": "Net genôch gegevens om grafyk te generearjen.", + "no_profiles_found": "Gjin profilen fûn.", + "cycle_info_fmt": "Syklus: {start}, {duration} min, Etiket: {label}", + "top_candidates_header": "### Topkandidaten", + "tbl_profile": "Profyl", + "tbl_confidence": "Betrouwen", + "tbl_correlation": "Korrelaasje", + "tbl_duration_match": "Doer oerienkomst", + "detected_profile": "Detektearre profyl", + "estimated_duration": "Rûsde doer", + "actual_duration": "Eigentlike doer", + "choose_action": "__Kies in aksje hjirûnder:__", + "tbl_count": "Oantal", + "tbl_avg": "Gem", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Enerzjy (Gem.)", + "tbl_energy_total": "Enerzjy (totaal)", + "tbl_consistency": "Konsistinsje", + "tbl_last_run": "Lêste syklus", + "graph_legend_title": "Grafyk Legend", + "graph_legend_body": "De blauwe bân stiet foar it waarnommen minimale en maksimale power draw berik. De line toant de gemiddelde krêftkurve.", + "recording_preview": "Opname Preview", + "trim_start": "Trim begjin", + "trim_end": "Trim ein", + "split_preview_title": "Split Preview", + "split_preview_found_fmt": "Fûn {count} segminten.", + "split_preview_confirm_fmt": "Klik Befêstigje om dizze syklus te splitsen yn {count} aparte syklusen.", + "merge_preview_title": "Foarbyld gearfoegje", + "merge_preview_joining_fmt": "Doch mei oan {count} syklusen. Hiaten sille wurde fol mei 0W lêzingen.", + "editor_delete_preview_title": "Wiskfoarbyld", + "editor_delete_preview_intro": "De selektearre syklussen sille permanint wiske wurde:", + "editor_delete_preview_confirm": "Klik op Befêstigje om dizze syklusrecords permanint te wiskjen.", + "no_power_preview": "*Gjin machtgegevens beskikber foar foarbyld.*", + "profile_comparison": "Profyl fergeliking", + "actual_cycle_label": "Dizze syklus (werklik)", + "storage_usage_header": "Opslach gebrûk", + "storage_file_size": "Triemgrutte", + "storage_cycles": "Syklusen", + "storage_profiles": "Profilen", + "storage_debug_traces": "Debug-spoaren", + "overview_suffix": "Oersicht", + "phase_preview_for_profile": "Fasefoarbyld foar profyl", + "current_ranges_header": "Aktuele berik", + "assign_phases_help": "Brûk de aksjes hjirûnder om berikken ta te foegjen, te bewurkjen, te wiskjen of op te slaan.", + "profile_summary_header": "Profyl gearfetting", + "recent_cycles_header": "Resinte syklusen", + "trim_cycle_preview_title": "Foarbyld fan syklus trimme", + "trim_cycle_preview_no_data": "Gjin macht gegevens beskikber foar dizze syklus.", + "trim_cycle_preview_kept_suffix": "hâlden", + "table_program": "Programma", + "table_when": "Wannear", + "table_length": "Lingte", + "table_match": "Oerienkomst", + "table_profile": "Profyl", + "table_cycles": "Syklusen", + "table_avg_length": "Gem. lingte", + "table_last_run": "Lêste syklus", + "table_avg_energy": "Gem. enerzjy", + "table_detected_program": "Detektearre programma", + "table_confidence": "Betrouwen", + "table_reported": "Rapportearre", + "phase_builtin_suffix": "(Ynboud)", + "phase_none_available": "Gjin fazen beskikber.", + "settings_deprecation_warning": "⚠️ ** Ferâldere apparaattype:** {device_type} is pland foar ferwidering yn in takomstige release. De oerienkommende pipeline fan WashData produseart gjin betroubere resultaten foar dizze apparaatklasse. Jo yntegraasje bliuwt wurkje troch de ôfskriuwingsperioade; om dizze warskôging stil te meitsjen, oerskeakelje **Apparaattype** hjirûnder nei ien fan 'e stipe typen (waskmasine, droeger, waskmasine-droegerkombinaasje, ôfwaskmasine, loftfriteuse, breamakker, of pomp), of nei **Oar (Avansearre)** as jo apparaat net oerienkomt mei ien fan 'e stipe typen. **Oar (Avansearre)** ferstjoert mei opsetsin algemiene standerts dy't net binne ôfstimd foar in spesifyk apparaat, dus jo moatte sels drompels, timeouts en oerienkommende parameters ynstelle; al jo besteande ynstellings wurde bewarre as jo wikselje.", + "phase_other_device_types": "Oare soarten apparaten:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Split in syklus (Fyn gatten)", + "merge": "Cycles gearfoegje (Join fragminten)", + "delete": "Syklus(en) wiskje" + } + }, + "split_mode": { + "options": { + "auto": "Automatysk stille gatten detektearje", + "manual": "Hânmjittige tiidstimpel(s)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Underhâld: Gegevens opnij ferwurkje en optimalisearje", + "clear_debug_data": "Debuggegevens wiskje (romte frijmeitsje)", + "wipe_history": "Wiskje ALLE gegevens foar dit apparaat (ûnomkearber)", + "export_import": "JSON eksportearje / ymportearje mei ynstellings (kopiearje / plakke)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksportearje alle gegevens", + "import": "Gegevens ymportearje / gearfoegje" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Âlde syklusen auto-labelje", + "select_cycle_to_label": "Spesifike syklus labelen", + "select_cycle_to_delete": "Syklus wiskje", + "interactive_editor": "Ynteraktive bewurker gearfoegje / splitsen", + "trim_cycle": "Syklusgegevens trimme" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Meitsje nij profyl", + "edit_profile": "Profyl bewurkje/omneame", + "delete_profile": "Wiskje profyl", + "profile_stats": "Profylstatistiken", + "cleanup_profile": "Skiednis skjinmeitsje - Grafyk en wiskje", + "assign_phases": "Fasebereiken tawize" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Meitsje Nije Fase", + "edit_custom_phase": "Fase bewurkje", + "delete_custom_phase": "Fase wiskje" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Faseberik tafoegje", + "edit_range": "Faseberik bewurkje", + "delete_range": "Faseberik wiskje", + "clear_ranges": "Alle berikken wiskje", + "auto_detect_ranges": "Fazen automatysk ûntdekke", + "save_ranges": "Bewarje en werom" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Ferfarskje Status", + "stop_recording": "Opname stopje (Bewarje en ferwurkje)", + "start_recording": "Start Nije opname", + "process_recording": "Lêste opname ferwurkje (Trim & Save)", + "discard_recording": "Ferwiderje lêste opname" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Meitsje nij profyl", + "existing_profile": "Taheakje oan besteande profyl", + "discard": "Opname fuortsmite" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Befêstigje - Korrekte deteksje", + "correct": "Korrekt - Kies programma / Duration", + "ignore": "Negearje - False Positive / Noisy Cycle", + "delete": "Wiskje - Fuortsmite út Skiednis" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Namme & tapasse fazen", + "cancel": "Gean werom sûnder feroarings" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Begjinpunt ynstelle", + "set_end": "Einpunt ynstelle", + "reset": "Weromsette nei Folsleine Duration", + "apply": "Trim tapasse", + "cancel": "Ofbrekke" + } + }, + "device_type": { + "options": { + "washing_machine": "Wasmachine", + "dryer": "Droger", + "washer_dryer": "Washer-Dryer Combo", + "dishwasher": "Dishwasher", + "coffee_machine": "Kofjemasine (ferâldere)", + "ev": "Elektrysk auto (ferâldere)", + "air_fryer": "Air Fryer", + "heat_pump": "Warmtepomp (ferâldere)", + "bread_maker": "Bread Maker", + "pump": "Pomp / Sump Pomp", + "oven": "Oven (ferâldere)", + "other": "Oare (Avansearre)" + } + } + }, + "services": { + "label_cycle": { + "name": "Syklus labelen", + "description": "Tawize in besteand profyl oan in ferline syklus, of fuortsmite it label.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat te label." + }, + "cycle_id": { + "name": "Syklus-ID", + "description": "De ID fan 'e syklus om te labeljen." + }, + "profile_name": { + "name": "Profylnamme", + "description": "De namme fan in besteand profyl (profylen meitsje yn it menu Profilen beheare). Lit leech om label te ferwiderjen." + } + } + }, + "create_profile": { + "name": "Meitsje profyl", + "description": "Meitsje in nij profyl (standalone of basearre op in referinsjesyklus).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat." + }, + "profile_name": { + "name": "Profylnamme", + "description": "Namme foar it nije profyl (bgl. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Referinsjesyklus-ID", + "description": "Opsjonele syklus-ID om dit profyl op te basearjen." + } + } + }, + "delete_profile": { + "name": "Wiskje profyl", + "description": "Wiskje in profyl en opsjoneel unlabel syklusen mei help fan it.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat." + }, + "profile_name": { + "name": "Profylnamme", + "description": "It profyl om te wiskjen." + }, + "unlabel_cycles": { + "name": "Label fuortsmite fan syklusen", + "description": "Ferwiderje profyllabel út syklusen mei dit profyl." + } + } + }, + "auto_label_cycles": { + "name": "Âlde syklusen auto-labelje", + "description": "Labelje net-labele syklusen mei retroaktyf mei profyloanpassing.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat." + }, + "confidence_threshold": { + "name": "Betrouwensdrempel", + "description": "Minimale wedstriidfertrouwen (0,50-0,95) om labels oan te passen." + } + } + }, + "export_config": { + "name": "Konfiguraasje eksportearje", + "description": "Eksportearje de profilen, syklusen en ynstellings fan dit apparaat nei in JSON-bestân (per apparaat).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat te eksportearjen." + }, + "path": { + "name": "Paad", + "description": "Opsjoneel absolút triempaad om te skriuwen (standert foar /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Konfiguraasje ymportearje", + "description": "Ymportearje profilen, syklusen, en ynstellings foar dit apparaat út in JSON eksport triem (ynstellings kinne wurde oerskreaun).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "It apparaat foar de waskmasine om yn te ymportearjen." + }, + "path": { + "name": "Paad", + "description": "Absolute paad nei it eksportearjen fan JSON-bestân om te ymportearjen." + } + } + }, + "submit_cycle_feedback": { + "name": "Syklusfeedback yntsjinje", + "description": "Befêstigje of korrigearje in automatysk ûntdutsen programma nei in foltôge syklus. Jou of `entry_id` (avansearre) of `device_id` (oanrikkemandearre).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "It WashData-apparaat (oanrikkemandearre ynstee fan entry_id)." + }, + "entry_id": { + "name": "Entry ID", + "description": "De konfiguraasje-ynfier-id foar it apparaat (alternatyf foar apparaat_id)." + }, + "cycle_id": { + "name": "Syklus-ID", + "description": "De syklus-id werjûn yn 'e feedbacknotifikaasje / logs." + }, + "user_confirmed": { + "name": "Ûntdutsen programma befêstigje", + "description": "Stel wier as it ûntdekte programma goed is." + }, + "corrected_profile": { + "name": "Korrigearre profyl", + "description": "As net befêstige, jou dan de juste profyl-/programmanamme." + }, + "corrected_duration": { + "name": "Korrigearre doer (sekonden)", + "description": "Opsjoneel korrizjearre doer yn sekonden." + }, + "notes": { + "name": "Notysjes", + "description": "Opsjonele notysjes oer dizze syklus." + } + } + }, + "record_start": { + "name": "Syklus opname starte", + "description": "Begjinne mei it manuell opnimmen fan in skjinne syklus (omgiet alle oerienkommende logika).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat op te nimmen." + } + } + }, + "record_stop": { + "name": "Syklus opname stopje", + "description": "Stopje hânmjittich opnimmen.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "De waskmasine apparaat te stopjen opname." + } + } + }, + "trim_cycle": { + "name": "Syklus trimme", + "description": "Trim de krêftgegevens fan in ferline syklus nei in spesifyk tiidfinster.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "It WashData-apparaat." + }, + "cycle_id": { + "name": "Syklus-ID", + "description": "De ID fan 'e syklus om te trimmen." + }, + "trim_start_s": { + "name": "Trimstart (sekonden)", + "description": "Hâld gegevens fan dizze offset yn sekonden. Standert 0." + }, + "trim_end_s": { + "name": "Trim ein (sekonden)", + "description": "Hâld gegevens oant dizze offset yn sekonden. Standert = folsleine doer." + } + } + }, + "pause_cycle": { + "name": "Syklus pausearje", + "description": "Pause de aktive syklus foar in WashData apparaat.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "It WashData-apparaat om te pausen." + } + } + }, + "resume_cycle": { + "name": "Syklus ferfetsje", + "description": "Ferfetsje in pauze syklus foar in WashData-apparaat.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "It WashData-apparaat om troch te gean." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Oan it rinnen" + }, + "match_ambiguity": { + "name": "Oerienkomst ûndúdlikheid" + } + }, + "sensor": { + "washer_state": { + "name": "Tastân", + "state": { + "off": "Út", + "idle": "Rêstend", + "starting": "Opstarten", + "running": "Oan it rinnen", + "paused": "Pauzearre", + "user_paused": "Troch brûker op pauze set", + "ending": "Oan it einigjen", + "finished": "Klear", + "anti_wrinkle": "Anti-rimpel", + "delay_wait": "Wachtsje op Start", + "interrupted": "Underbrutsen", + "force_stopped": "Twongen stoppe", + "rinse": "Spoelje", + "unknown": "Ûnbekend", + "clean": "Skjin" + } + }, + "washer_program": { + "name": "Programma" + }, + "time_remaining": { + "name": "Tiid oerbleaun" + }, + "total_duration": { + "name": "Totale doer" + }, + "cycle_progress": { + "name": "Foarútgong" + }, + "current_power": { + "name": "Aktueel fermogen" + }, + "elapsed_time": { + "name": "Ferline tiid" + }, + "debug_info": { + "name": "Debug ynformaasje" + }, + "match_confidence": { + "name": "Oerienkomst fertrouwen" + }, + "top_candidates": { + "name": "Topkandidaten", + "state": { + "none": "Gjin" + } + }, + "current_phase": { + "name": "Aktuele faze" + }, + "wash_phase": { + "name": "Faze" + }, + "profile_cycle_count": { + "name": "Profyl {profile_name} syklustel", + "unit_of_measurement": "syklusen" + }, + "suggestions": { + "name": "Foarstelde ynstellings" + }, + "pump_runs_today": { + "name": "Pomp rint (lêste 24 h)" + }, + "cycle_count": { + "name": "Syklustal" + } + }, + "select": { + "program_select": { + "name": "Syklusprogramma", + "state": { + "auto_detect": "Auto-detect" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Syklus twongen beeinigje" + }, + "pause_cycle": { + "name": "Syklus pausearje" + }, + "resume_cycle": { + "name": "Syklus ferfetsje" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "In jildich device_id is fereaske." + }, + "cycle_id_required": { + "message": "In jildich cycle_id is fereaske." + }, + "profile_name_required": { + "message": "In jildige profylnamme is fereaske." + }, + "device_not_found": { + "message": "Apparaat net fûn." + }, + "no_config_entry": { + "message": "Gjin konfiguraasje-yngong fûn foar apparaat." + }, + "integration_not_loaded": { + "message": "Yntegraasje net laden foar dit apparaat." + }, + "cycle_not_found_or_no_power": { + "message": "Syklus net fûn of hat gjin macht gegevens." + }, + "trim_failed_empty_window": { + "message": "Trimmen mislearre - syklus net fûn, gjin macht gegevens, of resultearjend finster is leech." + }, + "trim_invalid_range": { + "message": "trim_end_s moat grutter wêze as trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ga.json b/custom_components/ha_washdata/translations/ga.json new file mode 100644 index 0000000..9794a94 --- /dev/null +++ b/custom_components/ha_washdata/translations/ga.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Socrú WashData", + "description": "Cumraigh do mheaisín níocháin nó fearas eile.\n\nTá braiteoir cumhachta ag teastáil.\n\n**Ar aghaidh, fiafrófar díot an bhfuil fonn ort do chéad phróifíl a chruthú.**", + "data": { + "name": "Ainm an Ghléis", + "device_type": "Cineál Gléas", + "power_sensor": "Braiteoir Cumhachta", + "min_power": "Tairseach Cumhachta Íosta (W)" + }, + "data_description": { + "name": "Ainm cairdiúil don ghléas seo (m.sh., 'Meaisín Níocháin', 'Miasniteoir').", + "device_type": "Cén cineál fearais é seo? Cuidíonn sé le brath agus lipéadú a chur in oiriúint.", + "power_sensor": "An t-eintiteas braiteora a thuairiscíonn tomhaltas cumhachta fíor-ama (i vata) ó do bhreiseán cliste.", + "min_power": "Léiríonn léamha cumhachta os cionn na tairsí seo (i vata) go bhfuil an fearas ag rith. Tosaigh le 2W don chuid is mó de na feistí." + } + }, + "first_profile": { + "title": "Cruthaigh An Chéad Phróifíl", + "description": "Rith iomlán de d'fhearas is ea **Timthriall** (m.sh. ualach níocháin). Déanann **Próifíl** na timthriallta seo a ghrúpáil de réir cineáil (m.sh., 'Cadás', 'Sciobghnímh Níocháin'). Cuidíonn próifíl a chruthú anois leis an ré a mheas láithreach.\n\nIs féidir leat an phróifíl seo a roghnú de láimh i rialuithe an ghléis mura n-aimsítear go huathoibríoch í.\n\n**Fág 'Ainm Próifíl' folamh chun an chéim seo a scipeáil.**", + "data": { + "profile_name": "Ainm Próifíl (Roghnach)", + "manual_duration": "Fad Measta (nóiméid)" + } + } + }, + "error": { + "cannot_connect": "Theip ar nascadh", + "invalid_auth": "Fíordheimhniú neamhbhailí", + "unknown": "Earráid gan choinne" + }, + "abort": { + "already_configured": "Tá an gléas cumraithe cheana féin" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Athbhreithniú a dhéanamh ar Aiseolas Foghlama", + "settings": "Socruithe", + "notifications": "Fógraí", + "manage_cycles": "Bainistigh Timthriallta", + "manage_profiles": "Bainistigh Próifílí", + "manage_phase_catalog": "Bainistigh Catalóg Chéim", + "record_cycle": "Timthriall a Thaifeadadh (Lámhleabhar)", + "diagnostics": "Diagnóisic & Cothabháil" + } + }, + "apply_suggestions": { + "title": "Cóipeáil Luachanna Molta", + "description": "Déanfaidh sé seo na luachanna molta reatha a chóipeáil isteach i do Shocruithe sábháilte. Is gníomh aonuaire é seo agus ní chumasaíonn sé uathfhorscríobh.\n\nLuachanna molta le cóipeáil:\n{suggested}", + "data": { + "confirm": "Deimhnigh gníomh cóipe" + } + }, + "apply_suggestions_confirm": { + "title": "Athbhreithnigh Athruithe Molta", + "description": "Aimsíodh WashData **{pending_count}** luach(anna) molta le cur i bhfeidhm.\n\nAthruithe:\n{changes}\n\n✅ Ticeáil an bosca thíos agus cliceáil **Cuir isteach** chun iarratas a dhéanamh agus sábháil na hathruithe seo láithreach.\n❌ Fág gan é a sheiceáil agus cliceáil **Submit** chun é a chur ar ceal agus dul ar ais.", + "data": { + "confirm_apply_suggestions": "Cuir na hathruithe seo i bhfeidhm agus sábháil iad" + } + }, + "settings": { + "title": "Socruithe", + "description": "{deprecation_warning}Mionghléas tairseacha braite, foghlaim, agus iompar ardleibhéil. Oibríonn réamhshocraithe go maith don chuid is mó de na socruithe.\n\nSocruithe Molta ar fáil faoi láthair: {suggestions_count}.\nMá tá sé seo os cionn 0, oscail Ardsocruithe agus úsáid Cuir Luachanna Molta i bhFeidhm chun moltaí a athbhreithniú.", + "data": { + "apply_suggestions": "Cuir Luachanna Molta i bhfeidhm", + "device_type": "Cineál Gléas", + "power_sensor": "Eintiteas Braiteora Cumhachta", + "min_power": "Cumhacht Íosta (W)", + "off_delay": "Moill Deireadh Timthrialla (s)", + "notify_actions": "Gníomhartha Fógra", + "notify_people": "Moill ar Sheachadadh Go dtí go Bhfuil Na Daoine Seo Abhaile", + "notify_only_when_home": "Moill ar Fhógraí Go dtí go mbeidh an Duine Roghnaithe sa Bhaile", + "notify_fire_events": "Imeachtaí Uathoibrithe a Loisceadh", + "notify_start_services": "Tús na Rothaíochta - Spriocanna Fógra", + "notify_finish_services": "Críoch na Rothaíochta - Spriocanna Fógra", + "notify_live_services": "Dul Chun Cinn Beo - Spriocanna Fógraí", + "notify_before_end_minutes": "Fógra Réamhchríochnaithe (nóiméad roimh dheireadh)", + "notify_title": "Teideal an Fhógra", + "notify_icon": "Deilbhín Fógraí", + "notify_start_message": "Formáid Teachtaireachta Tosaigh", + "notify_finish_message": "Formáid Teachtaireachta Críochnaithe", + "notify_pre_complete_message": "Formáid Teachtaireachta Réamh-Chríochnaithe", + "show_advanced": "Cuir Ardsocruithe in Eagar (An Chéad Chéim Eile)" + }, + "data_description": { + "apply_suggestions": "Ticeáil an bosca seo chun na luachanna a mhol an t-algartam foghlama a chóipeáil isteach san fhoirm. Déan athbhreithniú roimh shábháil.\n\nMolta (ó fhoghlaim):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Roghnaigh an cineál fearais (Meaisín Níocháin, Triomadóir, Miasniteoir, Meaisín Caife, EV). Stóráiltear an chlib seo le gach timthriall le haghaidh eagrú níos fearr.", + "power_sensor": "An t-eintiteas braiteora a thuairiscíonn cumhacht fíor-ama (i vata). Is féidir leat é seo a athrú am ar bith gan sonraí stairiúla a chailliúint - úsáideach má athsholáthróidh tú breiseán cliste.", + "min_power": "Léiríonn léamha cumhachta os cionn na tairsí seo (i vata) go bhfuil an fearas ag rith. Tosaigh le 2W don chuid is mó de na feistí.", + "off_delay": "Soicindí ní mór don chumhacht slíoctha fanacht faoi bhun na tairsí stoptha chun críochnú a mharcáil. Réamhshocrú: 120s.", + "notify_actions": "Seicheamh gníomhaíochta roghnach Home Assistant le rith le haghaidh fógraí (scripteanna, fógairt, TTS, etc.). Má tá sé socraithe, ritheann gníomhartha roimh an tseirbhís fógra cúltaca.", + "notify_people": "Aonáin daoine a úsáidtear le haghaidh geataí láithreachta. Nuair a bhíonn sé cumasaithe, cuirtear moill ar sheachadadh an fhógra go dtí go mbíonn duine roghnaithe amháin ar a laghad sa bhaile.", + "notify_only_when_home": "Má tá sé cumasaithe, cuir moill ar fhógraí go dtí go mbeidh duine roghnaithe amháin ar a laghad sa bhaile.", + "notify_fire_events": "Má tá sé cumasaithe, loiscigh imeachtaí Home Assistant le haghaidh tús agus deireadh timthrialla (ha_washdata_cycle_started agus ha_washdata_cycle_ended) chun uathoibrithe a thiomáint.", + "notify_start_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó chun foláireamh a thabhairt nuair a thosaíonn timthriall. Fág folamh chun fógraí tosaithe a scipeáil.", + "notify_finish_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó chun foláireamh a thabhairt nuair a chríochnaíonn timthriall nó nuair a thagann deireadh le timthriall. Fág folamh chun fógraí críochnaithe a scipeáil.", + "notify_live_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó chun nuashonruithe beo ar dhul chun cinn a fháil fad a bheidh an timthriall ar siúl (aip shoghluaiste compánach amháin). Fág folamh chun nuashonruithe beo a scipeáil.", + "notify_before_end_minutes": "Nóiméid roimh dheireadh measta an timthrialla chun fógra a spreagadh. Socraigh ar 0 chun é a dhíchumasú. Réamhshocrú: 0.", + "notify_title": "Teideal saincheaptha le haghaidh fógraí. Tacaíonn sé le {device} ionadsealbhóir.", + "notify_icon": "Deilbhín le húsáid le haghaidh fógraí (m.sh. mdi:washing-machine). Fág folamh chun seoladh gan deilbhín.", + "notify_start_message": "Teachtaireacht seolta nuair a thosaíonn timthriall. Tacaíonn sé le {device} ionadsealbhóir.", + "notify_finish_message": "Seoltar teachtaireacht nuair a chríochnaíonn timthriall. Tacaíonn sé le {device}, {duration}, {program}, {energy_kwh}, {cost} ionadsealbhóirí.", + "notify_pre_complete_message": "Téacs an nuashonraithe dul chun cinn beo athfhillteach agus an timthriall ar siúl. Tacaíonn {device}, {minutes}, {program} sealbhóirí áite." + } + }, + "notifications": { + "title": "Fógraí", + "description": "Cumraigh bealaí fógra, teimpléid, agus nuashonruithe roghnacha beo ar dhul chun cinn soghluaiste.", + "data": { + "notify_actions": "Gníomhartha Fógra", + "notify_people": "Moill ar Sheachadadh Go dtí go Bhfuil Na Daoine Seo Abhaile", + "notify_only_when_home": "Moill ar Fhógraí Go dtí go mbeidh an Duine Roghnaithe sa Bhaile", + "notify_fire_events": "Imeachtaí Uathoibrithe a Loisceadh", + "notify_start_services": "Tús na Rothaíochta - Spriocanna Fógra", + "notify_finish_services": "Críoch na Rothaíochta - Spriocanna Fógra", + "notify_live_services": "Dul Chun Cinn Beo - Spriocanna Fógraí", + "notify_before_end_minutes": "Fógra Réamhchríochnaithe (nóiméad roimh dheireadh)", + "notify_live_interval_seconds": "Eatramh Nuashonraithe Beo (soicindí)", + "notify_live_overrun_percent": "Liúntas Forríthe Nuashonraithe Beo (%)", + "notify_live_chronometer": "Uaineadóir Comhaireamh Síos Chronometer", + "notify_title": "Teideal an Fhógra", + "notify_icon": "Deilbhín Fógraí", + "notify_start_message": "Formáid Teachtaireachta Tosaigh", + "notify_finish_message": "Formáid Teachtaireachta Críochnaithe", + "notify_pre_complete_message": "Formáid Teachtaireachta Réamh-Chríochnaithe", + "energy_price_entity": "Praghas Fuinnimh - Aonán (roghnach)", + "energy_price_static": "Praghas Fuinnimh - Luach statach (roghnach)", + "go_back": "Téigh ar ais gan sábháil", + "notify_reminder_message": "Aiseolas Formáid Teachtaireacht", + "notify_timeout_seconds": "Auto-dismiss Tar éis (soicindí)", + "notify_channel": "Fógra Channel (gaireas/beo/meabhrúchán)", + "notify_finish_channel": "Fógra Channel Críochnaithe" + }, + "data_description": { + "go_back": "Cuir tic leis seo agus cliceáil Cuir Isteach chun aon athruithe thuas a chaitheamh uait agus filleadh ar an bpríomhroghchlár.", + "notify_actions": "Seicheamh gníomhaíochta roghnach Home Assistant le rith le haghaidh fógraí (scripteanna, fógairt, TTS, etc.). Má tá sé socraithe, ritheann gníomhartha roimh an tseirbhís fógra cúltaca.", + "notify_people": "Aonáin daoine a úsáidtear le haghaidh geataí láithreachta. Nuair a bhíonn sé cumasaithe, cuirtear moill ar sheachadadh an fhógra go dtí go mbíonn duine roghnaithe amháin ar a laghad sa bhaile.", + "notify_only_when_home": "Má tá sé cumasaithe, cuir moill ar fhógraí go dtí go mbeidh duine roghnaithe amháin ar a laghad sa bhaile.", + "notify_fire_events": "Má tá sé cumasaithe, loiscigh imeachtaí Home Assistant le haghaidh tús agus deireadh timthrialla (ha_washdata_cycle_started agus ha_washdata_cycle_ended) chun uathoibrithe a thiomáint.", + "notify_start_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó le foláireamh a thabhairt nuair a thosaíonn timthriall (m.sh. notify.mobile_app_my_phone). Fág folamh chun fógraí tosaithe a scipeáil.", + "notify_finish_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó chun foláireamh a thabhairt nuair a chríochnaíonn timthriall nó nuair a thagann deireadh le timthriall. Fág folamh chun fógraí críochnaithe a scipeáil.", + "notify_live_services": "Fógra a thabhairt do sheirbhís amháin nó níos mó chun nuashonruithe beo ar dhul chun cinn a fháil fad a bheidh an timthriall ar siúl (aip shoghluaiste compánach amháin). Fág folamh chun nuashonruithe beo a scipeáil.", + "notify_before_end_minutes": "Nóiméid roimh dheireadh measta an timthrialla chun fógra a spreagadh. Socraigh ar 0 chun é a dhíchumasú. Réamhshocrú: 0.", + "notify_live_interval_seconds": "Cé chomh minic is a sheoltar nuashonruithe ar dhul chun cinn agus tú ag rith. Tá luachanna níos ísle níos freagraí ach ídíonn siad níos mó fógraí. Cuirtear 30 soicind ar a laghad i bhfeidhm - déantar luachanna faoi 30 s a shlánú go huathoibríoch suas go 30 s.", + "notify_live_overrun_percent": "Lamháil sábháilteachta os cionn an chomhairimh nuashonraithe mheasta do thimthriallta fada nó forríofa (mar shampla ceadaíonn 20% 1.2x nuashonruithe measta).", + "notify_live_chronometer": "Nuair a bheidh sé cumasaithe, cuimsíonn gach nuashonrú beo comhaireamh síos chronometer go dtí an t-am críochnaithe measta. Ticeáil an t-amadóir fógra síos go huathoibríoch ar an ngléas idir nuashonruithe (aip chompánach Android amháin).", + "notify_title": "Teideal saincheaptha le haghaidh fógraí. Tacaíonn sé le {device} ionadsealbhóir.", + "notify_icon": "Deilbhín le húsáid le haghaidh fógraí (m.sh. mdi:washing-machine). Fág folamh chun seoladh gan deilbhín.", + "notify_start_message": "Teachtaireacht seolta nuair a thosaíonn timthriall. Tacaíonn sé le {device} ionadsealbhóir.", + "notify_finish_message": "Seoltar teachtaireacht nuair a chríochnaíonn timthriall. Tacaíonn sé le {device}, {duration}, {program}, {energy_kwh}, {cost} ionadsealbhóirí.", + "energy_price_entity": "Roghnaigh aonán uimhriúil a bhfuil an praghas leictreachais reatha aige (m.sh. sensor.electricity_price, ionchur_number.energy_rate). Nuair a bheidh sé socraithe, cumasaigh an coinneálaí {cost}. Bíonn tosaíocht aige ar an luach statach má tá an dá cheann cumraithe.", + "energy_price_static": "Praghas seasta leictreachais in aghaidh an kWh. Nuair a bheidh sé socraithe, cumasaigh an coinneálaí {cost}. Neamhaird má tá aonán cumraithe thuas. Cuir do shiombail airgeadra leis sa teimpléad teachtaireachta, e.g. {cost} €.", + "notify_reminder_message": "Téacs an meabhrúcháin aon-uaire sheoladh an líon cumraithe de nóiméad roimh an deireadh measta. Deireadh ó na nuashonruithe beo athfhillteach thuas. Tacaíochtaí {device}, {minutes}, {program} sealbhóirí áite.", + "notify_timeout_seconds": "Go huathoibríoch fógraí a dhíbhe tar éis an soicind go leor (ar aghaidh mar an app chompánach 'timeout'). Socraigh go 0 iad a choinneáil go dtí go dhíbhe. Réamhshocrú: 0.", + "notify_channel": "Android cainéal fógra app compánach do stádas, dul chun cinn beo, agus fógraí i gcuimhne. An fhuaim agus tábhacht a bhaineann le cainéal atá cumraithe sa app compánach an chéad uair an t-ainm cainéal a úsáidtear - NighData Leagann ach an t-ainm cainéal. Fág folamh don mhainneachtain app.", + "notify_finish_channel": "Ar leithligh Android cainéal don airdeall críochnaithe (a úsáidtear freisin ag an meabhrúchán agus an meabhrúchán leanúnach fanachta níocháin) ionas gur féidir é a fuaime féin. Fág folamh chun an cainéal thuas a athúsáid.", + "notify_pre_complete_message": "Téacs an nuashonraithe dul chun cinn beo athfhillteach agus an timthriall ar siúl. Tacaíonn {device}, {minutes}, {program} sealbhóirí áite." + } + }, + "advanced_settings": { + "title": "Ardsocruithe", + "description": "Mionchoigeartaigh tairseacha braite, foghlaim agus iompar ardleibhéil. Oibríonn na réamhshocruithe go maith don chuid is mó de na socruithe.", + "sections": { + "suggestions_section": { + "name": "Socruithe Molta", + "description": "{suggestions_count} moladh foghlamtha ar fáil. Cuir tic le Cuir Luachanna Molta i bhFeidhm chun na hathruithe beartaithe a athbhreithniú sula sábhálann tú.", + "data": { + "apply_suggestions": "Cuir Luachanna Molta i bhfeidhm" + }, + "data_description": { + "apply_suggestions": "Ticeáil an bosca seo chun céim athbhreithnithe a oscailt le haghaidh luachanna molta ón algartam foghlama. Ní dhéantar aon rud a shábháil go huathoibríoch.\n\nMolta (ó fhoghlaim):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Braite agus Tairseacha Cumhachta", + "description": "Cathain a mheastar go bhfuil an gléas AIR nó AS, geataí fuinnimh, agus uainiú d'aistrithe staid.", + "data": { + "start_duration_threshold": "Tréimhse Athluaile Tosaigh (s)", + "start_energy_threshold": "Geata Fuinnimh Tosaigh (Wh)", + "completion_min_seconds": "Íosamthréimhse Rite Críochnaithe (soicindí)", + "start_threshold_w": "Tairseach Tosaigh (W)", + "stop_threshold_w": "Tairseach Stop (W)", + "end_energy_threshold": "Geata Fuinnimh Deiridh (Wh)", + "running_dead_zone": "Crios Marbh Rith (soicindí)", + "end_repeat_count": "Comhaireamh Athdhéanaimh Deiridh", + "min_off_gap": "Bearna Íosta idir Timthriallta (s)", + "sampling_interval": "Eatramh Samplála (s)" + }, + "data_description": { + "start_duration_threshold": "Déan neamhaird de spící cumhachta gearra atá níos giorra ná an ré seo (soicindí). Scagann sé spící tosaithe (m.sh., 60W ar feadh 2s) sula dtosaíonn an fíor-thimthriall. Réamhshocrú: 5s.", + "start_energy_threshold": "Ní mór don timthriall an méid fuinnimh seo (Wh) ar a laghad a charnadh le linn na céime TOSAIGH atá le deimhniú. Réamhshocrú: 0.005 Wh.", + "completion_min_seconds": "Marcálfar timthriallta níos giorra ná seo mar 'idirbhriste' fiú má chríochnaíonn siad go nádúrtha. Réamhshocrú: 600s.", + "start_threshold_w": "Ní mór don chumhacht ardú OS CIONN na tairsí seo chun timthriall a THOSÚ. Socraigh níos airde ná do chumhacht fuireachais. Réamhshocrú: min_power + 1 W.", + "stop_threshold_w": "Ní mór don chumhacht titim FAOI BHUN na tairsí seo chun timthriall a CHRÍOCHNÚ. Socraigh díreach os cionn náid chun deireadh bréagach ó thorann a sheachaint. Réamhshocrú: 0.6 * min_power.", + "end_energy_threshold": "Ní chríochnaíonn an timthriall ach amháin má tá fuinneamh sa fhuinneog Moill-Mhúchta dheireanach faoi bhun an luacha seo (Wh). Coscann deireadh bréagach má athraíonn an chumhacht gar do nialas. Réamhshocrú: 0.05 Wh.", + "running_dead_zone": "Tar éis tús a chur leis an timthriall, déan neamhaird de laghduithe cumhachta ar feadh an méid soicindí seo chun cosc a chur ar bhrath deiridh bréagach le linn na céime tosaigh. Socraigh ar 0 chun é a dhíchumasú. Réamhshocrú: 0s.", + "end_repeat_count": "Líon uaireanta ní mór an coinníoll deiridh (cumhacht faoi bhun na tairsí ar feadh off_delay) a shásamh as a chéile sula gcuirfear deireadh leis an timthriall. Coscann luachanna níos airde deireadh bréagach le linn sosanna. Réamhshocrú: 1.", + "min_off_gap": "Má thosaíonn timthriall laistigh den méid soicindí seo ón gceann roimhe sin ag críochnú, déanfar iad a CHUMASC ina timthriall amháin.", + "sampling_interval": "Íos-eatramh samplála i soicindí. Ní thabharfar aird ar nuashonruithe níos tapúla ná an ráta seo. Réamhshocrú: 30s (2s le haghaidh meaisíní níocháin, triomadóirí níocháin, agus miasniteoirí)." + } + }, + "matching_section": { + "name": "Meaitseáil Próifíle agus Foghlaim", + "description": "Cé chomh ionsaitheach is a mheaitseálann WashData timthriallta reatha le próifílí foghlamtha agus cathain ba chóir aiseolas a iarraidh.", + "data": { + "profile_match_min_duration_ratio": "Cóimheas Íosta Faid Meaitseála Próifíle (0.1-1.0)", + "profile_match_interval": "Eatramh Meaitseála Próifíle (soicindí)", + "profile_match_threshold": "Tairseach Meaitseála Próifíle", + "profile_unmatch_threshold": "Tairseach Neamhmeaitseála Próifíle", + "auto_label_confidence": "Muinín Uath-Lipéadaithe (0-1)", + "learning_confidence": "Muinín Iarratais Aiseolais (0-1)", + "suppress_feedback_notifications": "Faoi chois Fógraí Aiseolais", + "duration_tolerance": "Caoinfhulaingt Faid (0-0.5)", + "smoothing_window": "Fuinneog Slíoctha (samplaí)", + "profile_duration_tolerance": "Caoinfhulaingt Faid Meaitseála Próifíle (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Cóimheas íosfhaid le haghaidh meaitseála próifíle. Caithfidh an timthriall reatha a bheith ar a laghad an codán seo d'fhad na próifíle chun meaitseáil. Níos ísle = meaitseáil níos luaithe. Réamhshocrú: 0.50 (50%).", + "profile_match_interval": "Cé chomh minic (i soicindí) a ndéantar iarracht meaitseáil próifíle le linn timthrialla. Soláthraíonn luachanna níos ísle braite níos tapúla ach úsáideann siad níos mó CPU. Réamhshocrú: 300s (5 nóiméad).", + "profile_match_threshold": "Íosscór cosúlachta DTW (0.0–1.0) ag teastáil chun próifíl a mheas mar mheaitseáil. Níos airde = meaitseáil níos déine, níos lú dearfacha bréagacha. Réamhshocrú: 0.4.", + "profile_unmatch_threshold": "Scór cosúlachta DTW (0.0–1.0) faoina ndiúltaítear do phróifíl a bhí meaitseáilte roimhe seo. Ba chóir go mbeadh sé níos ísle ná an tairseach meaitseála chun caochaíl a chosc. Réamhshocrú: 0.35.", + "auto_label_confidence": "Lipéadaigh timthriallta go huathoibríoch ag an muinín seo nó os a chionn nuair a bheidh sé críochnaithe (0.0–1.0).", + "learning_confidence": "Íos-mhuinín chun fíorú úsáideora a iarraidh trí imeacht (0.0–1.0).", + "suppress_feedback_notifications": "Nuair a bheidh sé cumasaithe, déanfaidh WashData aiseolas a rianú agus a iarraidh go hinmheánach go fóill ach ní thaispeánfar fógraí leanúnacha ag iarraidh ort timthriallta a dhearbhú. Tá sé úsáideach nuair a aimsítear timthriallta i gceart i gcónaí agus nuair a bhraitheann tú go mbíonn na leideanna ag cur as d’aird.", + "duration_tolerance": "Caoinfhulaingt do mheastacháin ama fágtha le linn rith (0.0–0.5 ionann le 0–50%).", + "smoothing_window": "Líon na léamha cumhachta le déanaí a úsáidtear le haghaidh slíochadh meán-ghluaiste.", + "profile_duration_tolerance": "Caoinfhulaingt arna úsáid ag meaitseáil próifíle le haghaidh athraithe ré iomlán (0.0–0.5). Iompar réamhshocraithe: ±25%." + } + }, + "timing_section": { + "name": "Uainiú, Cothabháil agus Dífhabhtú", + "description": "Faireoir, athshocrú dul chun cinn, uathchothabháil, agus nochtadh eintiteas dífhabhtaithe.", + "data": { + "watchdog_interval": "Eatramh Faire (soicindí)", + "no_update_active_timeout": "Teorainn Ama Gan Nuashonrú (s)", + "progress_reset_delay": "Moill ar Athshocrú Dul Chun Cinn (soicindí)", + "auto_maintenance": "Cumasaigh Cothabháil Uathoibríoch", + "expose_debug_entities": "Nocht Eintitis Dífhabhtaithe", + "save_debug_traces": "Sábháil Rianta Dífhabhtaithe" + }, + "data_description": { + "watchdog_interval": "Soicindí idir seiceálacha faire agus tú ag rith. Réamhshocrú: 5s. RABHADH: Cinntigh go bhfuil sé seo NÍOS ARD ná eatramh nuashonraithe do bhraiteora chun stopanna bréagacha a sheachaint (m.sh. má nuashonraíonn braiteoir gach 60s, bain úsáid as 65s+).", + "no_update_active_timeout": "Maidir le braiteoirí foilsiú-ar-athrú: ná cuir deireadh le timthriall GNÍOMHACH ach amháin mura dtagann aon nuashonrú ar feadh an méid soicindí seo. Úsáideann críochnú ísealchumhachta fós an Moill Múchta.", + "progress_reset_delay": "Tar éis timthriall a bheith críochnaithe (100%), fan an méid soicindí díomhaoin seo sula n-athshocraíonn tú dul chun cinn go 0%. Réamhshocrú: 300s.", + "auto_maintenance": "Cumasaigh cothabháil uathoibríoch (samplaí a dheisiú, blúirí a chumasc).", + "expose_debug_entities": "Taispeáin braiteoirí ardleibhéil (Muinín, Céim, Athbhrí) le haghaidh dífhabhtaithe.", + "save_debug_traces": "Stóráil rangú mionsonraithe agus sonraí rian cumhachta sa stair (Méadaíonn úsáid stórála)." + } + }, + "anti_wrinkle_section": { + "name": "Sciath Frithroic (Triomadóirí)", + "description": "Déan neamhaird de rothluithe an druma tar éis an timthrialla chun taibhtimthriallta a chosc. Triomadóir / meaisín níocháin-triomadóir amháin.", + "data": { + "anti_wrinkle_enabled": "Sciath Frith-Roc (Triomadóir/Meaisín Níocháin-Triomadóir Amháin)", + "anti_wrinkle_max_power": "Uaschumhacht Frith-Roc (W) (Triomadóir/Meaisín Níocháin-Triomadóir Amháin)", + "anti_wrinkle_max_duration": "Uasfhad Frith-Roc (s) (Triomadóir/Meaisín Níocháin-Triomadóir Amháin)", + "anti_wrinkle_exit_power": "Cumhacht Scoir Frith-Roc (W) (Triomadóir/Meaisín Níocháin-Triomadóir Amháin)" + }, + "data_description": { + "anti_wrinkle_enabled": "Déan neamhaird de rothlaithe druma gearra ísealchumhachta tar éis do thimthriall críochnú chun timthriallta taibhse a chosc (triomadóir/meaisín níocháin-triomadóir amháin).", + "anti_wrinkle_max_power": "Déileáiltear le pléascanna ag an gcumhacht seo nó faoina bhun mar rothlaithe frith-roc in ionad timthriallta nua. Ní bhaineann sé ach nuair atá Sciath Frith-Roc cumasaithe.", + "anti_wrinkle_max_duration": "Fad uasta pléasc a chóireáil mar rothlú frith-roc. Ní bhaineann sé ach nuair atá Sciath Frith-Roc cumasaithe.", + "anti_wrinkle_exit_power": "Má thiteann an chumhacht faoi bhun an leibhéil seo, scoir an frith-roc láithreach (múchta go fírinneach). Ní bhaineann sé ach nuair atá Sciath Frith-Roc cumasaithe." + } + }, + "delay_start_section": { + "name": "Braite Tosaithe Moillithe", + "description": "Caith le fuireachas leanúnach ísealchumhachta mar 'Ag fanacht le tosú' go dtí go dtosaíonn an timthriall i ndáiríre.", + "data": { + "delay_start_detect_enabled": "Cumasaigh Brath Tosaigh Moillithe", + "delay_confirm_seconds": "Tús Moillithe: Am Deimhnithe Fuireachais (s)", + "delay_timeout_hours": "Tús Moillithe: Uasmhéid Am Feithimh (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Nuair a bhíonn sé cumasaithe, braitear spíc draenála ísealchumhachta agus cumhacht fuireachais marthanach mar thús moillithe. Taispeánann an braiteoir stáit 'Fan le Tosaigh' le linn na moille. Ní rianaítear am cláir ná dul chun cinn ar bith go dtí go dtosóidh an timthriall iarbhír. Éilíonn start_threshold_w a shocrú os cionn an spike draein cumhachta.", + "delay_confirm_seconds": "Caithfidh an chumhacht fanacht idir an Tairseach Stoptha (W) agus an Tairseach Tosaithe (W) ar feadh an oiread seo soicind sula dtéann WashData isteach i staid 'Ag fanacht le tosú'. Scagann sé seo buaiceanna gearra nascleanúna roghchláir amach. Réamhshocrú: 60 s.", + "delay_timeout_hours": "Teorainn ama sábháilteachta: má tá an meaisín fós in 'Fan le Tosú' tar éis na n-uaireanta fada seo, athshocróidh WashData go Off. Réamhshocrú: 8 h." + } + }, + "external_triggers_section": { + "name": "Spreagthóirí Seachtracha, Doras agus Sos", + "description": "Braiteoir spreagtha seachtrach roghnach don deireadh, braiteoir dorais le haghaidh sos/glan a bhrath, agus lasc gearrtha cumhachta le linn sosa.", + "data": { + "external_end_trigger_enabled": "Cumasaigh Truicear Seachtrach Deiridh Timthrialla", + "external_end_trigger": "Braiteoir Seachtrach Deiridh Timthrialla", + "external_end_trigger_inverted": "Inbhéartaigh Loighic Truicéara (Truicear ar MHÚCHADH)", + "door_sensor_entity": "Braiteoir Doras", + "pause_cuts_power": "Gearr Cumhacht Agus Sos", + "switch_entity": "Aonán Athraigh (chun Power Cut a chur ar sos)", + "notify_unload_delay_minutes": "Moill ar Fhógra Feithimh Níocháin (nóiméad)" + }, + "data_description": { + "external_end_trigger_enabled": "Cumasaigh monatóireacht ar bhraiteoir dénártha seachtrach chun críochnú timthrialla a spreagadh.", + "external_end_trigger": "Roghnaigh eintiteas braiteora dénártha. Nuair a spreagann an braiteoir seo, cuirfear deireadh leis an timthriall reatha le stádas 'críochnaithe'.", + "external_end_trigger_inverted": "De réir réamhshocraithe, lasann an truicear nuair a chasann an braiteoir AIR. Ticeáil an bosca seo chun é a loisceadh nuair a mhúchtar an braiteoir ina ionad.", + "door_sensor_entity": "Braiteoir dénártha roghnach do dhoras an mheaisín (ar = oscailte, as = dúnta). Nuair a osclaíonn an doras le linn timthriall gníomhach, deimhneoidh WashData go bhfuil an timthriall ar sos d’aon ghnó. Tar éis don timthriall críochnú, má tá an doras fós dúnta, athraíonn an stát go 'Glan' go dtí go n-osclaítear an doras. Tabhair faoi deara: NÍ dúnadh an doras go huathoibríoch-atosú ar thimthriall ar sos - ní mór atosú a thosú de láimh tríd an gcnaipe nó an tseirbhís Lean ar aghaidh ar an Rothaíocht.", + "pause_cuts_power": "Nuair a bheidh tú ar sos tríd an gcnaipe nó an tseirbhís Sos Cycle, múch an t-aonán lasc cumraithe freisin. Ní thiocfaidh sé i bhfeidhm ach amháin má tá aonán lasc cumraithe don ghléas seo.", + "switch_entity": "An t-aonán lasc le scoránú agus é ag sos nó ag atosú. Ag teastáil nuair atá 'Gearr Cumhacht Agus Sos' cumasaithe.", + "notify_unload_delay_minutes": "Seol fógra tríd an gcainéal fógartha bailchríoch tar éis don timthriall críochnú agus níor osclaíodh an doras le fada an lá seo (tá Braiteoir Doras ag teastáil). Socraigh ar 0 chun é a dhíchumasú. Réamhshocrú: 60 min." + } + }, + "pump_section": { + "name": "Monatóir Caidéil", + "description": "Le haghaidh cineál gléis caidéil amháin. Seolann sé imeacht má ritheann timthriall caidéil thar an tréimhse seo.", + "data": { + "pump_stuck_duration": "Tairseach(í) Foláirimh Caidéil (Caidéil Amháin)" + }, + "data_description": { + "pump_stuck_duration": "Má tá timthriall caidéil fós ar siúl tar éis an iliomad soicind seo, cuirtear imeacht ha_washdata_pump_stuck amach uair amháin. Bain úsáid as seo chun mótair jammed a bhrath (tá gnáth-rith caidéil sump faoi bhun 60 s). Réamhshocrú: 1800 s (30 min). Cineál gléas caidéil amháin." + } + }, + "device_link_section": { + "name": "Gléas Nasc", + "description": "Optionally nasc an gléas WashData le gléas atá ann cheana Home Assistant (mar shampla an breiseán cliste nó an fearas féin). Tá an gléas WashData léirithe ansin mar \"Connected via\" an gléas sin. Fág folamh a choinneáil mar fheiste standalone.", + "data": { + "linked_device": "Gléas Nasctha" + }, + "data_description": { + "linked_device": "Roghnaigh an gléas a nascadh NighData go. Cuireann sé seo tagairt 'Connected via' sa chlár gléas; Coinníonn WashData a chárta agus eintitis gléas féin. Glan an roghnú a bhaint as an nasc." + } + } + } + }, + "diagnostics": { + "title": "Diagnóisic & Cothabháil", + "description": "Déan gníomhartha cothabhála ar nós timthriallta ilroinnte a chumasc nó sonraí stóráilte a aistriú.\n\n**Úsáid Stórála**\n\n- Méid Comhaid: {file_size_kb} KB\n- Timthriallta: {cycle_count}\n- Próifílí: {profile_count}\n- Rianta Dífhabhtaithe: {debug_count}", + "menu_options": { + "reprocess_history": "Cothabháil: Athphróiseáil & Optamaigh Sonraí", + "clear_debug_data": "Glan Sonraí Dífhabhtaithe (Saor spás)", + "wipe_history": "Scrios GACH sonraí don ghléas seo (do-inchúlghairthe)", + "export_import": "Easpórtáil/Iompórtáil JSON le socruithe (cóipeáil/greamaigh)", + "menu_back": "← Ar ais" + } + }, + "clear_debug_data": { + "title": "Glan Sonraí Dífhabhtaithe", + "description": "An bhfuil tú cinnte gur mian leat gach rian dífhabhtaithe stóráilte a scriosadh? Saorfaidh sé seo spás ach bainfidh sé faisnéis rangúcháin mhionsonraithe ó thimthriallta roimhe seo." + }, + "manage_cycles": { + "title": "Bainistigh Timthriallta", + "description": "Timthriallta le déanaí:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Uath-Lipéadaigh Seantimthriallta", + "select_cycle_to_label": "Lipéadaigh Timthriall Sonrach", + "select_cycle_to_delete": "Scrios Timthriall", + "interactive_editor": "Eagarthóir Idirghníomhach Cumaisc/Scoilte", + "trim_cycle_select": "Bearr Sonraí Timthrialla", + "menu_back": "← Ar ais" + } + }, + "manage_cycles_empty": { + "title": "Bainistigh Timthriallta", + "description": "Níl aon timthriallta taifeadta fós.", + "menu_options": { + "auto_label_cycles": "Uath-Lipéadaigh Seantimthriallta", + "menu_back": "← Ar ais" + } + }, + "manage_profiles": { + "title": "Bainistigh Próifílí", + "description": "Achoimre Próifíle:\n{profile_summary}", + "menu_options": { + "create_profile": "Cruthaigh Próifíl Nua", + "edit_profile": "Cuir Próifíl in Eagar/Athainmnigh", + "delete_profile_select": "Scrios Próifíl", + "profile_stats": "Staitisticí Próifíle", + "cleanup_profile": "Glan Suas Stair - Graf & Scrios", + "assign_profile_phases_select": "Sann Raonta Céime", + "menu_back": "← Ar ais" + } + }, + "manage_profiles_empty": { + "title": "Bainistigh Próifílí", + "description": "Níl próifílí cruthaithe fós.", + "menu_options": { + "create_profile": "Cruthaigh Próifíl Nua", + "menu_back": "← Ar ais" + } + }, + "manage_phase_catalog": { + "title": "Bainistigh Catalóg Chéime", + "description": "Catalóg céimeanna (réamhshocraithe don ghléas seo + céimeanna saincheaptha):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Cruthaigh Céim Nua", + "phase_catalog_edit_select": "Cuir Céim in Eagar", + "phase_catalog_delete": "Scrios Céim", + "menu_back": "← Ar ais" + } + }, + "phase_catalog_create": { + "title": "Cruthaigh Céim Saincheaptha", + "description": "Cruthaigh ainm céime saincheaptha agus cur síos, agus roghnaigh cén cineál gléis a mbaineann sé leis.", + "data": { + "target_device_type": "Cineál Gléis Sprice", + "phase_name": "Ainm Céime", + "phase_description": "Cur Síos ar Chéim" + } + }, + "phase_catalog_edit_select": { + "title": "Cuir Céim Saincheaptha in Eagar", + "description": "Roghnaigh céim shaincheaptha le cur in eagar.", + "data": { + "phase_name": "Céim Saincheaptha" + } + }, + "phase_catalog_edit": { + "title": "Cuir Céim Saincheaptha in Eagar", + "description": "Céim á heagarthóireacht: {phase_name}", + "data": { + "phase_name": "Ainm Céime", + "phase_description": "Cur Síos ar Chéim" + } + }, + "phase_catalog_delete": { + "title": "Scrios Céim Saincheaptha", + "description": "Roghnaigh céim shaincheaptha le scriosadh. Bainfear aon raonta sannta a úsáideann an chéim seo.", + "data": { + "phase_name": "Céim Saincheaptha" + } + }, + "assign_profile_phases": { + "title": "Sann Raonta Céime", + "description": "Réamhamharc céime don phróifíl: **{profile_name}**\n\n{timeline_svg}\n\n**Raonta reatha:**\n{current_ranges}\n\nÚsáid na gníomhartha thíos chun raonta a chur leis, a chur in eagar, a scriosadh nó a shábháil.", + "menu_options": { + "assign_profile_phases_add": "Cuir Raon Céime leis", + "assign_profile_phases_edit_select": "Cuir Raon Céime in Eagar", + "assign_profile_phases_delete": "Scrios Raon Céime", + "phase_ranges_clear": "Glan Gach Raon", + "assign_profile_phases_auto_detect": "Braith Céimeanna go Huathoibríoch", + "phase_ranges_save": "Sábháil agus Fill ar Ais", + "menu_back": "← Ar ais" + } + }, + "assign_profile_phases_add": { + "title": "Cuir Raon Céime leis", + "description": "Cuir raon céime amháin leis an dréacht reatha.", + "data": { + "phase_name": "Céim", + "start_min": "Nóiméad Tosaigh", + "end_min": "Nóiméad Deiridh" + } + }, + "assign_profile_phases_edit_select": { + "title": "Cuir Raon Céime in Eagar", + "description": "Roghnaigh raon le cur in eagar.", + "data": { + "range_index": "Raon Céime" + } + }, + "assign_profile_phases_edit": { + "title": "Cuir Raon Céime in Eagar", + "description": "Nuashonraigh an raon céime roghnaithe.", + "data": { + "phase_name": "Céim", + "start_min": "Nóiméad Tosaigh", + "end_min": "Nóiméad Deiridh" + } + }, + "assign_profile_phases_delete": { + "title": "Scrios Raon Céime", + "description": "Roghnaigh raon le baint den dréacht.", + "data": { + "range_index": "Raon Céime" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Céimeanna Uathbhraite", + "description": "Próifíl: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} céim(eanna) aimsithe go huathoibríoch.** Roghnaigh gníomh thíos.", + "data": { + "action": "Gníomh" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Ainmnigh Céimeanna Braite", + "description": "Próifíl: **{profile_name}**\n\n**{detected_count} céim(eanna) braite:**\n{ranges_summary}\n\nTabhair ainm do gach céim." + }, + "assign_profile_phases_select": { + "title": "Sann Raonta Céime", + "description": "Roghnaigh próifíl chun raonta na gcéimeanna a chumrú.", + "data": { + "profile": "Próifíl" + } + }, + "profile_stats": { + "title": "Staitisticí Próifíle", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Cruthaigh Próifíl Nua", + "description": "Cruthaigh próifíl nua de láimh nó ó thimthriall a chuaigh thart.\n\nSamplaí ainmneacha próifíle: 'Ábhair Mhínchnis', 'Ualach Trom', 'Sciobghnímh Níocháin'", + "data": { + "profile_name": "Ainm Próifíle", + "reference_cycle": "Timthriall Tagartha (roghnach)", + "manual_duration": "Fad Láimhe (nóiméid)" + } + }, + "edit_profile": { + "title": "Cuir Próifíl in Eagar", + "description": "Roghnaigh próifíl le hathainmniú", + "data": { + "profile": "Próifíl" + } + }, + "rename_profile": { + "title": "Cuir Sonraí Próifíle in Eagar", + "description": "Próifíl reatha: {current_name}", + "data": { + "new_name": "Ainm Próifíle", + "manual_duration": "Fad Láimhe (nóiméid)" + } + }, + "delete_profile_select": { + "title": "Scrios Próifíl", + "description": "Roghnaigh próifíl le scriosadh", + "data": { + "profile": "Próifíl" + } + }, + "delete_profile_confirm": { + "title": "Deimhnigh Scriosadh Próifíle", + "description": "⚠️ Scriosfaidh sé seo an phróifíl seo go buan: {profile_name}", + "data": { + "unlabel_cycles": "Bain lipéad ó thimthriallta ag baint úsáid as an bpróifíl seo" + } + }, + "auto_label_cycles": { + "title": "Uath-Lipéadaigh Seantimthriallta", + "description": "Aimsíodh {total_count} timthriall iomlán. Próifílí: {profiles}", + "data": { + "confidence_threshold": "Íos-Mhuinín (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "Roghnaigh Timthriall le Lipéadú", + "description": "✓ = críochnaithe, ⚠ = atosaithe, ✗ = idirbhriste", + "data": { + "cycle_id": "Timthriall" + } + }, + "select_cycle_to_delete": { + "title": "Scrios Timthriall", + "description": "⚠️ Scriosfaidh sé seo an timthriall roghnaithe go buan", + "data": { + "cycle_id": "Timthriall le Scriosadh" + } + }, + "label_cycle": { + "title": "Lipéadaigh Timthriall", + "description": "{cycle_info}", + "data": { + "profile_name": "Próifíl", + "new_profile_name": "Ainm Próifíle Nua (má chruthaítear é)" + } + }, + "post_process": { + "title": "Próiseáil Staire (Cumasc/Scoilt)", + "description": "Glan suas stair timthriallta trí rití ilroinnte a chumasc agus trí thimthriallta atá cumasctha go mícheart a roinnt laistigh d'fhuinneog ama. Cuir isteach líon na n-uaireanta chun breathnú siar (úsáid 999999 do chách).", + "data": { + "time_range": "Fuinneog Amhairc Siar (uaireanta)", + "gap_seconds": "Bearna Cumaisc/Scoilte (soicindí)" + }, + "data_description": { + "time_range": "Líon uaireanta le scanadh le haghaidh timthriallta ilroinnte le cumasc. Úsáid 999999 chun na sonraí stairiúla go léir a phróiseáil.", + "gap_seconds": "Tairseach chun scoilt vs cumasc a chinneadh. CUMASCTAR bearnaí NÍOS GIORRA ná seo. SCOILTEAR bearnaí NÍOS FAIDE ná seo. Laghdaigh é seo chun scoilt a bhrú ar thimthriall a cumascadh go mícheart." + } + }, + "cleanup_profile": { + "title": "Glan Suas Stair - Roghnaigh Próifíl", + "description": "Roghnaigh próifíl lena léirshamhlú. Ginfidh sé seo graf a thaispeánfaidh na timthriallta go léir a chuaigh thart don phróifíl seo chun cuidiú le cinn amuigh a shainaithint.", + "data": { + "profile": "Próifíl" + } + }, + "cleanup_select": { + "title": "Glan Suas Stair - Graf & Scrios", + "description": "![Graf]({graph_url})\n\n**Léirshamhlú {profile_name}**\n\nTaispeánann an graf timthriallta aonair mar línte daite. Sainaithin cinn amuigh (línte i bhfad ón ngrúpa) agus meaitseáil a ndath sa liosta thíos chun iad a scriosadh.\n\n**Roghnaigh timthriallta le scriosadh GO BUan:**", + "data": { + "cycles_to_delete": "Timthriallta le Scriosadh (seiceáil dathanna meaitseála)" + } + }, + "interactive_editor": { + "title": "Eagarthóir Idirghníomhach Cumaisc/Scoilte", + "description": "Roghnaigh gníomh láimhe le déanamh ar stair do thimthriallta.", + "menu_options": { + "editor_split": "Scoilt Timthriall (Aimsigh bearnaí)", + "editor_merge": "Cumasc Timthriallta (Glac blúirí isteach)", + "editor_delete": "Scrios Timthriall(ta)", + "menu_back": "← Ar ais" + } + }, + "editor_select": { + "title": "Roghnaigh Timthriallta", + "description": "Roghnaigh an timthriall/na timthriallta le próiseáil.\n\n{info_text}", + "data": { + "selected_cycles": "Timthriallta" + } + }, + "editor_configure": { + "title": "Cumraigh & Cuir i bhFeidhm", + "description": "{preview_md}", + "data": { + "confirm_action": "Gníomh", + "merged_profile": "Próifíl Thorthaí", + "new_profile_name": "Ainm Próifíle Nua", + "confirm_commit": "Sea, deimhním an gníomh seo", + "segment_0_profile": "Próifíl Deighleog 1", + "segment_1_profile": "Próifíl Deighleog 2", + "segment_2_profile": "Próifíl Deighleog 3", + "segment_3_profile": "Próifíl Deighleog 4", + "segment_4_profile": "Próifíl Deighleog 5", + "segment_5_profile": "Próifíl Deighleog 6", + "segment_6_profile": "Próifíl Deighleog 7", + "segment_7_profile": "Próifíl Deighleog 8", + "segment_8_profile": "Próifíl Deighleog 9", + "segment_9_profile": "Próifíl Deighleog 10" + } + }, + "editor_split_params": { + "title": "Cumraíocht Scoilte", + "description": "Coigeartaigh an íogaireacht chun an timthriall seo a scoilteadh. Beidh aon bhearna díomhaoin níos faide ná seo ina chúis le scoilt.", + "data": { + "split_mode": "Modh scoilte" + } + }, + "editor_split_auto_params": { + "title": "Cumraíocht Uathroinnte", + "description": "Coigeartaigh an íogaireacht chun an timthriall seo a roinnt. Cuirfidh aon bhearna díomhaoin níos faide ná seo scoilt i bhfeidhm.", + "data": { + "min_gap_seconds": "Tairseach bearna scoilte (soicindí)" + } + }, + "editor_split_manual_params": { + "title": "Stampaí ama scoilte láimhe", + "description": "{preview_md}Fuinneog an timthrialla: **{cycle_start_wallclock} → {cycle_end_wallclock}** ar {cycle_date}.\n\nIontráil ceann amháin nó níos mó de stampaí ama scoilte (`HH:MM` nó `HH:MM:SS`), ceann amháin in aghaidh na líne. Gearrfar an timthriall ag gach stampa ama — táirgeann N stampa ama N+1 deighleog.", + "data": { + "split_timestamps": "Stampaí Ama Roinnte" + } + }, + "reprocess_history": { + "title": "Cothabháil: Athphróiseáil & Optamaigh Sonraí", + "description": "Déanann sé glanadh domhain ar shonraí stairiúla:\n1. Bearrann léamha nialaschumhachta (ciúnas).\n2. Socraíonn sé am/fad an timthrialla.\n3. Déanann sé gach múnla staidrimh (clúdaigh) a athríomh.\n\n**Rith é seo chun fadhbanna sonraí a réiteach nó chun algartaim nua a chur i bhfeidhm.**" + }, + "wipe_history": { + "title": "Scrios an Stair (Tástáil Amháin)", + "description": "⚠️ Scriosfaidh sé seo GACH timthriall agus próifíl stóráilte don ghléas seo go buan. Ní féidir é seo a chur ar ceal!" + }, + "export_import": { + "title": "Easpórtáil/Iompórtáil JSON", + "description": "Cóipeáil/greamaigh timthriallta, próifílí, AGUS socruithe don ghléas seo. Easpórtáil thíos nó greamaigh JSON lena iompórtáil.", + "data": { + "mode": "Gníomh", + "json_payload": "Cumraíocht Iomlán JSON" + }, + "data_description": { + "mode": "Roghnaigh Easpórtáil chun sonraí agus socruithe reatha a chóipeáil, nó Iompórtáil chun sonraí easpórtáilte a ghreamú ó ghléas WashData eile.", + "json_payload": "Le haghaidh Easpórtála: cóipeáil an JSON seo (lena n-áirítear timthriallta, próifílí, agus gach socrú mionghléasta). Le hIompórtáil: greamaigh JSON easpórtáilte ó WashData." + } + }, + "record_cycle": { + "title": "Taifeadadh Timthrialla", + "description": "Taifeadaigh timthriall de láimh chun próifíl ghlan a chruthú gan cur isteach.\n\nStádas: **{status}**\nFad: {duration}s\nSamplaí: {samples}", + "menu_options": { + "record_refresh": "Nuashonraigh Stádas", + "record_stop": "Stop Taifeadadh (Sábháil & Próiseáil)", + "record_start": "Tosaigh Taifeadadh Nua", + "record_process": "Próiseáil an Taifeadadh Deireanach (Bearr & Sábháil)", + "record_discard": "Caith Uait an Taifeadadh Deireanach", + "menu_back": "← Ar ais" + } + }, + "record_process": { + "title": "Próiseáil Taifeadta", + "description": "![Graf]({graph_url})\n\n**Taispeánann an graf an taifeadadh amh.** Gorm = Coinneáil, Dearg = Bearradh Molta.\n*Tá an graf statach agus ní nuashonraítear é le hathruithe foirme.*\n\nStopadh an taifeadadh. Tá bearrthaí ailínithe leis an ráta samplála braite (~{sampling_rate}s).\n\nFad Amh: {duration}s\nSamplaí: {samples}", + "data": { + "head_trim": "Bearradh Tosaigh (soicindí)", + "tail_trim": "Bearradh Deiridh (soicindí)", + "save_mode": "Ceann Scríbe Sábhála", + "profile_name": "Ainm Próifíle" + } + }, + "learning_feedbacks": { + "title": "Aiseolas Foghlama", + "description": "Aiseolas ar feitheamh: {count}\n\n{pending_table}\n\nRoghnaigh iarratas ar athbhreithniú timthrialla le próiseáil.", + "menu_options": { + "learning_feedbacks_pick": "Athbhreithnigh aiseolas ar feitheamh", + "learning_feedbacks_dismiss_all": "Cuir deireadh le gach aiseolas ar feitheamh", + "menu_back": "← Ar ais" + } + }, + "learning_feedbacks_pick": { + "title": "Roghnaigh aiseolas le hathbhreithniú", + "description": "Roghnaigh iarratas athbhreithnithe timthrialla le hoscailt.", + "data": { + "selected_feedback": "Roghnaigh timthriall le hathbhreithniú" + } + }, + "learning_feedbacks_empty": { + "title": "Aiseolas Foghlama", + "description": "Níor aimsíodh aon iarratas aiseolais ar feitheamh.", + "menu_options": { + "menu_back": "← Ar ais" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Diúltaigh gach aiseolas atá ar feitheamh", + "description": "Tá tú ar tí **{count}** iarratas aiseolais atá ar feitheamh a dhiúltú. Fanfaidh timthriallta diúltaithe i do stair ach ní chuirfidh siad comhartha foghlama nua leis. Ní féidir é seo a chealú.\n\n✅ Ticeáil an bosca thíos agus cliceáil **Cuir isteach** chun gach ceann a dhiúltú.\n❌ Fág gan é a sheiceáil agus cliceáil **Cuir isteach** chun cealú agus dul ar ais.", + "data": { + "confirm_dismiss_all": "Deimhnigh: cuir deireadh le gach iarratas aiseolais ar feitheamh" + } + }, + "resolve_feedback": { + "title": "Réitigh Aiseolas", + "description": "{comparison_img}{candidates_table}\n**Próifíl Braite**: {detected_profile} ({confidence_pct}%)\n**Fad Measta**: {est_duration_min} nóim\n**Fíorfhad**: {act_duration_min} nóim\n\n__Roghnaigh gníomh thíos:__", + "data": { + "action": "Cad ba mhaith leat a dhéanamh?", + "corrected_profile": "Clár Ceartaithe (má tá sé á cheartú)", + "corrected_duration": "Fad Ceartaithe i soicindí (má tá sé á cheartú)" + } + }, + "trim_cycle_select": { + "title": "Bearradh Timthrialla - Roghnaigh Timthriall", + "description": "Roghnaigh timthriall le bearradh. ✓ = críochnaithe, ⚠ = atosaithe, ✗ = idirbhriste", + "data": { + "cycle_id": "Timthriall" + } + }, + "trim_cycle": { + "title": "Bearradh Timthrialla", + "description": "Fuinneog reatha: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Gníomh" + } + }, + "trim_cycle_start": { + "title": "Socraigh Tús Bearrtha", + "description": "Fuinneog rothaíochta: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTús reatha: **{current_wallclock}** (+{current_offset_min} nóim ó thús an timthrialla)\n\nRoghnaigh am tosaithe nua ag baint úsáide as an roghnóir clog thíos.", + "data": { + "trim_start_time": "Am tosaithe nua", + "trim_start_min": "Tús nua (nóiméad ó thús an timthrialla)" + } + }, + "trim_cycle_end": { + "title": "Socraigh Deireadh Bearrtha", + "description": "Fuinneog rothaíochta: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAn deireadh reatha: **{current_wallclock}** (+{current_offset_min} nóim ó thús an timthrialla)\n\nRoghnaigh am deiridh nua ag baint úsáide as an roghnóir clog thíos.", + "data": { + "trim_end_time": "Am deiridh nua", + "trim_end_min": "Deireadh nua (nóiméad ó thús an timthrialla)" + } + } + }, + "error": { + "import_failed": "Theip ar an iompórtáil. Greamaigh JSON bailí ó easpórtáil WashData.", + "select_exactly_one": "Roghnaigh go díreach timthriall amháin don ghníomh seo.", + "select_at_least_one": "Roghnaigh timthriall amháin ar a laghad don ghníomh seo.", + "select_at_least_two": "Roghnaigh dhá thimthriall ar a laghad don ghníomh seo.", + "profile_exists": "Tá próifíl leis an ainm seo ann cheana", + "rename_failed": "Theip ar phróifíl a athainmniú. Seans nach bhfuil an phróifíl ann nó go bhfuil an t-ainm nua glactha cheana féin.", + "assignment_failed": "Theip ar phróifíl a shannadh don timthriall", + "duplicate_phase": "Tá céim leis an ainm seo ann cheana féin.", + "phase_not_found": "Níor aimsíodh an chéim roghnaithe.", + "invalid_phase_name": "Ní féidir ainm na céime a bheith folamh.", + "phase_name_too_long": "Tá ainm na céime ró-fhada.", + "invalid_phase_range": "Tá raon céime neamhbhailí. Ní mór an deireadh a bheith níos mó ná an tús.", + "invalid_phase_timestamp": "Tá stampa ama neamhbhailí. Úsáid formáid YYYY-MM-DD HH:MM nó HH:MM.", + "overlapping_phase_ranges": "Forluíonn raonta céime. Coigeartaigh na raonta agus bain triail eile as.", + "incomplete_phase_row": "Ní mór céim chomh maith le luachanna tosaigh/deiridh iomlána don mhód roghnaithe a bheith i ngach ró céime.", + "timestamp_mode_cycle_required": "Roghnaigh timthriall foinse nuair atá mód stampa ama in úsáid.", + "no_phase_ranges": "Níl aon raonta céime ar fáil don ghníomh sin fós.", + "feedback_notification_title": "WashData: Fíoraigh Timthriall ({device})", + "feedback_notification_message": "**Gléas**: {device}\n**Clár**: {program} ({confidence}% muinín)\n**Am**: {time}\n\nTeastaíonn do chabhair ó WashData chun an timthriall braite seo a fhíorú.\n\nTéigh go **Socruithe > Gléasanna & Seirbhísí > WashData > Cumraigh > Aiseolas Foghlama** chun an toradh seo a dhearbhú nó a cheartú.", + "suggestions_ready_notification_title": "WashData: Socruithe Molta Réidh ({device})", + "suggestions_ready_notification_message": "Tuairiscíonn an braiteoir **Socruithe Molta** anois **{count}** molta inghníomhaithe.\n\nChun iad a athbhreithniú agus a chur i bhfeidhm: **Socruithe > Gléasanna & Seirbhísí > WashData > Cumraigh > Ardsocruithe > Cuir Luachanna Molta i bhFeidhm**.\n\nTá na moltaí roghnach agus taispeántar iad le haghaidh athbhreithniú sula sábhálann tú.", + "trim_range_invalid": "Ní mór tús a bheith roimh dheireadh. Coigeartaigh do phointí bearrtha.", + "trim_failed": "Theip ar an mbearradh. Seans nach bhfuil an timthriall ann a thuilleadh nó go bhfuil an fhuinneog folamh.", + "invalid_split_timestamp": "Tá an stampa ama neamhbhailí nó lasmuigh d'fhuinneog an timthrialla. Úsáid HH:MM nó HH:MM:SS laistigh d'fhuinneog an timthrialla.", + "no_split_segments_found": "Níorbh fhéidir aon deighleoga bailí a chruthú ó na stampaí ama seo. Cinntigh go bhfuil gach stampa ama laistigh d'fhuinneog an timthrialla agus go bhfuil deighleoga 1 nóiméad ar a laghad ar fad.", + "auto_tune_suggestion": "{device_type} {device_title} timthriallta taibhsí braite. Athrú molta min_power: {current_min}W -> {new_min}W (gan chur i bhfeidhm go huathoibríoch).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} timthriallta taibhsí braite. Athrú molta min_power: {current_min}W -> {new_min}W (gan chur i bhfeidhm go huathoibríoch)." + }, + "abort": { + "no_cycles_found": "Níor aimsíodh aon timthriallta", + "no_split_segments_found": "Níor aimsíodh aon deighleoga inroinnte sa timthriall roghnaithe.", + "cycle_not_found": "Níorbh fhéidir an timthriall roghnaithe a luchtú.", + "no_power_data": "Níl aon sonraí rian cumhachta ar fáil don timthriall roghnaithe.", + "no_profiles_found": "Níor aimsíodh próifílí. Cruthaigh próifíl ar dtús.", + "no_profiles_for_matching": "Níl próifílí ar fáil le meaitseáil. Cruthaigh próifílí ar dtús.", + "no_unlabeled_cycles": "Tá gach timthriall lipéadaithe cheana féin", + "no_suggestions": "Níl aon luachanna molta ar fáil fós. Rith cúpla timthriall agus bain triail eile as.", + "no_predictions": "Gan tuar", + "no_custom_phases": "Níor aimsíodh aon chéimeanna saincheaptha.", + "reprocess_success": "Athphróiseáladh {count} timthriall go rathúil.", + "debug_data_cleared": "Glanadh sonraí dífhabhtaithe go rathúil ó {count} timthriall." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Tús Timthrialla", + "cycle_finish": "Críoch Timthrialla", + "cycle_live": "Dul Chun Cinn Beo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Gan Lipéad)", + "create_new_profile": "Cruthaigh Próifíl Nua", + "remove_label": "Bain Lipéad", + "no_reference_cycle": "(Gan timthriall tagartha)", + "all_device_types": "Gach Cineál Gléis", + "current_suffix": "(reatha)", + "editor_select_info": "Roghnaigh 1 timthriall le scoilteadh, nó 2+ timthriall le cumasc.", + "editor_select_info_split": "Roghnaigh 1 timthriall le scoilt.", + "editor_select_info_merge": "Roghnaigh 2+ thimthriall le cumasc.", + "editor_select_info_delete": "Roghnaigh 1+ timthriall le scriosadh.", + "no_cycles_recorded": "Níl aon timthriallta taifeadta fós.", + "profile_summary_line": "- **{name}**: {count} timthriall, {avg}m meán", + "no_profiles_created": "Níl próifílí cruthaithe fós.", + "phase_preview": "Réamhamharc Céime", + "phase_preview_no_curve": "Níl an meánchuar próifíle ar fáil fós. Rith/lipéadaigh níos mó timthriallta don phróifíl seo.", + "average_power_curve": "Cuar Meánchumhachta", + "unit_min": "nóim", + "profile_option_fmt": "{name} ({count} timthriall, ~{duration}m meán)", + "profile_option_short_fmt": "{name} ({count} timthriall, ~{duration}m)", + "deleted_cycles_from_profile": "Scriosadh {count} timthriall ó {profile}.", + "not_enough_data_graph": "Níl go leor sonraí ann chun graf a ghiniúint.", + "no_profiles_found": "Níor aimsíodh próifílí.", + "cycle_info_fmt": "Timthriall: {start}, {duration}m, Reatha: {label}", + "top_candidates_header": "### Na hIarrthóirí is Fearr", + "tbl_profile": "Próifíl", + "tbl_confidence": "Muinín", + "tbl_correlation": "Comhghaol", + "tbl_duration_match": "Meaitseáil Faid", + "detected_profile": "Próifíl Braite", + "estimated_duration": "Fad Measta", + "actual_duration": "Fíorfhad", + "choose_action": "__Roghnaigh gníomh thíos:__", + "tbl_count": "Áireamh", + "tbl_avg": "Meán", + "tbl_min": "Íos", + "tbl_max": "Uas", + "tbl_energy_avg": "Fuinneamh (Meán)", + "tbl_energy_total": "Fuinneamh (Iomlán)", + "tbl_consistency": "Comhsheasmh.", + "tbl_last_run": "Rith Deireanach", + "graph_legend_title": "Finscéal Graif", + "graph_legend_body": "Léiríonn an banna gorm an raon tarraingthe cumhachta íosta agus uasta a breathnaíodh. Taispeánann an líne an cuar meánchumhachta.", + "recording_preview": "Réamhamharc Taifeadta", + "trim_start": "Tús Bearrtha", + "trim_end": "Deireadh Bearrtha", + "split_preview_title": "Réamhamharc Scoilte", + "split_preview_found_fmt": "Aimsíodh {count} mír.", + "split_preview_confirm_fmt": "Cliceáil Deimhnigh chun an timthriall seo a roinnt ina {count} timthriall ar leith.", + "merge_preview_title": "Réamhamharc Cumaisc", + "merge_preview_joining_fmt": "Ag cumasc {count} timthriall. Líonfar bearnaí le léamha 0W.", + "editor_delete_preview_title": "Réamhamharc Scriosta", + "editor_delete_preview_intro": "Scriosfar na timthriallta roghnaithe go buan:", + "editor_delete_preview_confirm": "Cliceáil Deimhnigh chun na taifid timthrialla seo a scriosadh go buan.", + "no_power_preview": "*Níl aon sonraí cumhachta ar fáil le haghaidh réamhamhairc.*", + "profile_comparison": "Comparáid Próifíle", + "actual_cycle_label": "An timthriall seo (iarbhír)", + "storage_usage_header": "Úsáid Stórála", + "storage_file_size": "Méid Comhaid", + "storage_cycles": "Timthriallta", + "storage_profiles": "Próifílí", + "storage_debug_traces": "Rianta Dífhabhtaithe", + "overview_suffix": "Forbhreathnú", + "phase_preview_for_profile": "Réamhamharc céime don phróifíl", + "current_ranges_header": "Raonta reatha", + "assign_phases_help": "Úsáid na gníomhartha thíos chun raonta a chur leis, a chur in eagar, a scriosadh nó a shábháil.", + "profile_summary_header": "Achoimre Próifíle", + "recent_cycles_header": "Timthriallta le déanaí", + "trim_cycle_preview_title": "Réamhamharc Bearrtha Timthrialla", + "trim_cycle_preview_no_data": "Níl aon sonraí cumhachta ar fáil don timthriall seo.", + "trim_cycle_preview_kept_suffix": "coinnithe", + "table_program": "Clár", + "table_when": "Cathain", + "table_length": "Fad", + "table_match": "Meaitseáil", + "table_profile": "Próifíl", + "table_cycles": "Timthriallta", + "table_avg_length": "Fad Meán.", + "table_last_run": "Rith Deireanach", + "table_avg_energy": "Fuinneamh Meán.", + "table_detected_program": "Clár Braite", + "table_confidence": "Muinín", + "table_reported": "Tuairiscithe", + "phase_builtin_suffix": "(Tógtha isteach)", + "phase_none_available": "Níl aon chéimeanna ar fáil.", + "settings_deprecation_warning": "⚠️ **Cineál gléis astaithe:** Tá {device_type} sceidealaithe lena bhaint in eisiúint amach anseo. Ní thugann píblíne meaitseála WashData torthaí iontaofa don aicme fearais seo. Coinníonn do chomhtháthú ag obair tríd an tréimhse dímheasa; chun an rabhadh seo a chur ina thost, athraigh **Cineál Gléas** thíos go ceann de na cineálacha tacaithe (Meaisín Níocháin, Triomadóir, Teaglama Níochán-Triomadóir, Miasniteoir, Aer-frithire, Déantóir Aráin, nó Caidéal), nó go **Eile (Ardleibhéal)** mura bhfuil d’fhearas ag teacht le haon cheann de na cineálacha a dtacaítear leis. Déanann **Eile (Ardleibhéal)** mainneachtainí cineálacha d’aon ghnó nach bhfuil tiúnta d’aon fhearas ar leith á loingsiú acu, mar sin beidh ort tairseacha, teorainn ama agus paraiméadair mheaitseála a chumrú tú féin; coinnítear na socruithe atá agat cheana féin nuair a athraíonn tú.", + "phase_other_device_types": "Cineálacha gléasanna eile:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Scoilt Timthriall (Aimsigh bearnaí)", + "merge": "Cumasc Timthriallta (Glac blúirí isteach)", + "delete": "Scrios Timthriall(ta)" + } + }, + "split_mode": { + "options": { + "auto": "Braith bearnaí díomhaoin go huathoibríoch", + "manual": "Stampa(í) ama de láimh" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Cothabháil: Athphróiseáil & Optamaigh Sonraí", + "clear_debug_data": "Glan Sonraí Dífhabhtaithe (Saor spás)", + "wipe_history": "Scrios GACH sonraí don ghléas seo (do-inchúlghairthe)", + "export_import": "Easpórtáil/Iompórtáil JSON le socruithe (cóipeáil/greamaigh)" + } + }, + "export_import_mode": { + "options": { + "export": "Easpórtáil Gach Sonra", + "import": "Iompórtáil/Cumasc Sonraí" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Uath-Lipéadaigh Seantimthriallta", + "select_cycle_to_label": "Lipéadaigh Timthriall Sonrach", + "select_cycle_to_delete": "Scrios Timthriall", + "interactive_editor": "Eagarthóir Idirghníomhach Cumaisc/Scoilte", + "trim_cycle": "Bearr Sonraí Timthrialla" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Cruthaigh Próifíl Nua", + "edit_profile": "Cuir Próifíl in Eagar/Athainmnigh", + "delete_profile": "Scrios Próifíl", + "profile_stats": "Staitisticí Próifíle", + "cleanup_profile": "Glan Suas Stair - Graf & Scrios", + "assign_phases": "Sann Raonta Céime" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Cruthaigh Céim Nua", + "edit_custom_phase": "Cuir Céim in Eagar", + "delete_custom_phase": "Scrios Céim" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Cuir Raon Céime leis", + "edit_range": "Cuir Raon Céime in Eagar", + "delete_range": "Scrios Raon Céime", + "clear_ranges": "Glan Gach Raon", + "auto_detect_ranges": "Braith Céimeanna go Huathoibríoch", + "save_ranges": "Sábháil agus Fill ar Ais" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Nuashonraigh Stádas", + "stop_recording": "Stop Taifeadadh (Sábháil & Próiseáil)", + "start_recording": "Tosaigh Taifeadadh Nua", + "process_recording": "Próiseáil an Taifeadadh Deireanach (Bearr & Sábháil)", + "discard_recording": "Caith Uait an Taifeadadh Deireanach" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Cruthaigh Próifíl Nua", + "existing_profile": "Cuir le Próifíl Atá ann", + "discard": "Caith Uait Taifeadadh" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Deimhnigh - Brath Ceart", + "correct": "Ceartaigh - Roghnaigh Clár/Fad", + "ignore": "Déan Neamhaird - Timthriall Bréagach Dearfach/Torannaigh", + "delete": "Scrios - Bain as an Stair" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Ainmnigh & cuir na céimeanna i bhfeidhm", + "cancel": "Téigh ar ais gan athruithe" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Socraigh Pointe Tosaigh", + "set_end": "Socraigh Deireadhphointe", + "reset": "Athshocraigh go Fad Iomlán", + "apply": "Cuir Bearradh i bhFeidhm", + "cancel": "Cealaigh" + } + }, + "device_type": { + "options": { + "washing_machine": "Meaisín Níochán", + "dryer": "Triomadóir", + "washer_dryer": "Teaglama Washer-Triomadóir", + "dishwasher": "Miasniteoir", + "coffee_machine": "Meaisín Caife (neamhcheadaithe)", + "ev": "Feithicil Leictreach (neamhlaghdaithe)", + "air_fryer": "Fryer Aeir", + "heat_pump": "Caidéal Teasa (neamhlaghdaithe)", + "bread_maker": "Déantóir Arán", + "pump": "Caidéal / Sump Caidéal", + "oven": "oigheann (neamhlaghdaithe)", + "other": "Eile (Ardleibhéal)" + } + } + }, + "services": { + "label_cycle": { + "name": "Lipéadaigh Timthriall", + "description": "Sann próifíl atá ann cheana féin do thimthriall san am atá thart, nó bain an lipéad.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le lipéadú." + }, + "cycle_id": { + "name": "ID Timthrialla", + "description": "ID an timthrialla le lipéadú." + }, + "profile_name": { + "name": "Ainm Próifíle", + "description": "Ainm próifíle atá ann cheana féin (cruthaigh próifílí sa roghchlár Bainistigh Próifílí). Fág folamh chun an lipéad a bhaint." + } + } + }, + "create_profile": { + "name": "Cruthaigh Próifíl", + "description": "Cruthaigh próifíl nua (neamhspleách nó bunaithe ar thimthriall tagartha).", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData." + }, + "profile_name": { + "name": "Ainm Próifíle", + "description": "Ainm na próifíle nua (m.sh. 'Ualach Trom', 'Ábhair Mhínchnis')." + }, + "reference_cycle_id": { + "name": "ID Timthrialla Tagartha", + "description": "ID timthrialla roghnach chun an phróifíl seo a bhunú air." + } + } + }, + "delete_profile": { + "name": "Scrios Próifíl", + "description": "Scrios próifíl agus dílipéadaigh go roghnach timthriallta a úsáideann í.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData." + }, + "profile_name": { + "name": "Ainm Próifíle", + "description": "An phróifíl le scriosadh." + }, + "unlabel_cycles": { + "name": "Dílipéadaigh Timthriallta", + "description": "Bain lipéad próifíle de na timthriallta a úsáideann an phróifíl seo." + } + } + }, + "auto_label_cycles": { + "name": "Uath-Lipéadaigh Seantimthriallta", + "description": "Lipéadaigh go cúlghníomhach timthriallta gan lipéad ag baint úsáide as meaitseáil próifíle.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData." + }, + "confidence_threshold": { + "name": "Tairseach Muiníne", + "description": "Íos-mhuinín meaitseála (0.50-0.95) chun lipéid a chur i bhfeidhm." + } + } + }, + "export_config": { + "name": "Easpórtáil Cumraíochta", + "description": "Easpórtáil próifílí, timthriallta agus socruithe an ghléis seo go comhad JSON (in aghaidh gléis).", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le heaspórtáil." + }, + "path": { + "name": "Conair", + "description": "Conair iomlán roghnach comhaid le scríobh (réamhshocraithe go /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Iompórtáil Cumraíochta", + "description": "Iompórtáil próifílí, timthriallta agus socruithe don ghléas seo ó chomhad easpórtála JSON (seans go bhforscríobhfar socruithe).", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le hiompórtáil isteach." + }, + "path": { + "name": "Conair", + "description": "Conair iomlán chuig an gcomhad easpórtála JSON le hiompórtáil." + } + } + }, + "submit_cycle_feedback": { + "name": "Cuir Isteach Aiseolas Timthrialla", + "description": "Deimhnigh nó ceartaigh clár uathbhraite tar éis timthriall críochnaithe. Tabhair `entry_id` (ardleibhéil) nó `device_id` (molta).", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData (molta in ionad entry_id)." + }, + "entry_id": { + "name": "ID Iontrála", + "description": "ID na hiontrála cumraíochta don ghléas (mar mhalairt ar device_id)." + }, + "cycle_id": { + "name": "ID Timthrialla", + "description": "ID an timthrialla a thaispeántar san fhógra aiseolais/logaí." + }, + "user_confirmed": { + "name": "Deimhnigh Clár Braite", + "description": "Socraigh fíor má tá an clár braite ceart." + }, + "corrected_profile": { + "name": "Próifíl Cheartaithe", + "description": "Mura bhfuil sé deimhnithe, tabhair ainm ceart na próifíle/an chláir." + }, + "corrected_duration": { + "name": "Fad Ceartaithe (soicindí)", + "description": "Fad ceartaithe roghnach i soicindí." + }, + "notes": { + "name": "Nótaí", + "description": "Nótaí roghnacha faoin timthriall seo." + } + } + }, + "record_start": { + "name": "Tosaigh Taifeadadh Timthrialla", + "description": "Tosaigh timthriall glan a thaifeadadh de láimh (seachnaíonn gach loighic meaitseála).", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le taifeadadh." + } + } + }, + "record_stop": { + "name": "Stop Taifeadta Timthrialla", + "description": "Stop taifeadadh láimhe.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData chun taifeadadh a stopadh." + } + } + }, + "trim_cycle": { + "name": "Bearr Timthriall", + "description": "Bearr sonraí cumhachta timthrialla a chuaigh thart go fuinneog ama ar leith.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData." + }, + "cycle_id": { + "name": "ID Timthrialla", + "description": "ID an timthrialla le bearradh." + }, + "trim_start_s": { + "name": "Tús Bearrtha (soicindí)", + "description": "Coinnigh sonraí ón bhfritháireamh seo i soicindí. Réamhshocrú 0." + }, + "trim_end_s": { + "name": "Deireadh Bearrtha (soicindí)", + "description": "Coinnigh sonraí suas go dtí an fritháireamh seo i soicindí. Réamhshocrú = fad iomlán." + } + } + }, + "pause_cycle": { + "name": "Sos Timthriall", + "description": "Cuir an timthriall gníomhach do ghléas WashData ar sos.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le sos." + } + } + }, + "resume_cycle": { + "name": "Atosaigh Timthriall", + "description": "Lean ar aghaidh le timthriall sosaithe le haghaidh gléas WashData.", + "fields": { + "device_id": { + "name": "Gléas", + "description": "An gléas WashData le atosú." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Ag Rith" + }, + "match_ambiguity": { + "name": "Athbhrí Meaitseála" + } + }, + "sensor": { + "washer_state": { + "name": "Staid", + "state": { + "off": "As", + "idle": "Díomhaoin", + "starting": "Ag Tosú", + "running": "Ag Rith", + "paused": "Ar Sos", + "user_paused": "Curtha ar sos ag an úsáideoir", + "ending": "Ag Críochnú", + "finished": "Críochnaithe", + "anti_wrinkle": "Frith-Roc", + "delay_wait": "Ag feitheamh le Tosaigh", + "interrupted": "Idirbhriste", + "force_stopped": "Stopadh Éignithe", + "rinse": "Sruthladh", + "unknown": "Anaithnid", + "clean": "Glan" + } + }, + "washer_program": { + "name": "Clár" + }, + "time_remaining": { + "name": "Am Fágtha" + }, + "total_duration": { + "name": "Fad Iomlán" + }, + "cycle_progress": { + "name": "Dul Chun Cinn" + }, + "current_power": { + "name": "Cumhacht Reatha" + }, + "elapsed_time": { + "name": "Am Caite" + }, + "debug_info": { + "name": "Eolas Dífhabhtaithe" + }, + "match_confidence": { + "name": "Muinín Meaitseála" + }, + "top_candidates": { + "name": "Iarrthóirí is Fearr", + "state": { + "none": "Dada" + } + }, + "current_phase": { + "name": "An Chéim Reatha" + }, + "wash_phase": { + "name": "Céim" + }, + "profile_cycle_count": { + "name": "Áireamh Próifíle {profile_name}", + "unit_of_measurement": "timthriallta" + }, + "suggestions": { + "name": "Socruithe Molta ar Fáil" + }, + "pump_runs_today": { + "name": "Rití Caidéil (24 u.)" + }, + "cycle_count": { + "name": "Comhaireamh Rothaíochta" + } + }, + "select": { + "program_select": { + "name": "Clár Timthrialla", + "state": { + "auto_detect": "Uathbhrath" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Cuir Deireadh Éignithe le Timthriall" + }, + "pause_cycle": { + "name": "Sos Timthriall" + }, + "resume_cycle": { + "name": "Lean an Rothaíocht" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Teastaíonn device_id bailí." + }, + "cycle_id_required": { + "message": "Tá cycle_id bailí ag teastáil." + }, + "profile_name_required": { + "message": "Tá profile_name bailí ag teastáil." + }, + "device_not_found": { + "message": "Gléas gan aimsiú." + }, + "no_config_entry": { + "message": "Níor aimsíodh aon iontráil cumraíochta don ghléas." + }, + "integration_not_loaded": { + "message": "Níl an comhtháthú luchtaithe don ghléas seo." + }, + "cycle_not_found_or_no_power": { + "message": "Timthriall gan aimsiú nó níl aon sonraí cumhachta ann." + }, + "trim_failed_empty_window": { + "message": "Theip ar an mbearradh - níor aimsíodh an timthriall, níl aon sonraí cumhachta ann, nó tá an fhuinneog mar thoradh folamh." + }, + "trim_invalid_range": { + "message": "Caithfidh trim_end_s a bheith níos mó ná trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/gl.json b/custom_components/ha_washdata/translations/gl.json new file mode 100644 index 0000000..b7f7414 --- /dev/null +++ b/custom_components/ha_washdata/translations/gl.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuración de WashData", + "description": "Configura a túa lavadora ou outro aparello.\n\nRequírese un sensor de potencia.\n\n**A continuación, preguntarache se queres crear o teu primeiro perfil.**", + "data": { + "name": "Nome do dispositivo", + "device_type": "Tipo de dispositivo", + "power_sensor": "Sensor de potencia", + "min_power": "Limiar de potencia mínima (W)" + }, + "data_description": { + "name": "Un nome sinxelo para este dispositivo (por exemplo, 'Lavadora', 'Lavalouzas').", + "device_type": "Que tipo de aparello é este? Axuda a personalizar a detección e a etiquetaxe.", + "power_sensor": "A entidade sensora que informa o consumo de enerxía en tempo real (en vatios) do teu enchufe intelixente.", + "min_power": "As lecturas de potencia por riba deste limiar (en vatios) indican que o aparello está funcionando. Comeza con 2 W para a maioría dos dispositivos." + } + }, + "first_profile": { + "title": "Crear o primeiro perfil", + "description": "Un **ciclo** é unha execución completa do teu aparello (por exemplo, unha carga de roupa). Un **Perfil** agrupa estes ciclos por tipo (por exemplo, \"Algodón\", \"Lavado rápido\"). A creación dun perfil agora axuda a estimar a duración da integración inmediatamente.\n\nPodes seleccionar este perfil manualmente nos controis do dispositivo se non se detecta automaticamente.\n\n**Deixe \"Nome do perfil\" baleiro para omitir este paso.**", + "data": { + "profile_name": "Nome do perfil (opcional)", + "manual_duration": "Duración estimada (minutos)" + } + } + }, + "error": { + "cannot_connect": "Produciuse un erro ao conectar", + "invalid_auth": "Autenticación non válida", + "unknown": "Erro inesperado" + }, + "abort": { + "already_configured": "O dispositivo xa está configurado" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revisar comentarios de aprendizaxe", + "settings": "Configuración", + "notifications": "Notificacións", + "manage_cycles": "Xestionar Ciclos", + "manage_profiles": "Xestionar perfís", + "manage_phase_catalog": "Xestionar o catálogo de fases", + "record_cycle": "Ciclo de gravación (manual)", + "diagnostics": "Diagnóstico e Mantemento" + } + }, + "apply_suggestions": { + "title": "Copiar valores suxeridos", + "description": "Isto copiará os valores suxeridos actuais na túa configuración gardada. Esta é unha acción única e non activa as sobreescrituras automáticas.\n\nValores suxeridos para copiar:\n{suggested}", + "data": { + "confirm": "Confirme a acción de copiar" + } + }, + "apply_suggestions_confirm": { + "title": "Revisa os cambios suxeridos", + "description": "WashData atopou **{pending_count}** valor(s) suxerido(s) para aplicar.\n\nCambios:\n{changes}\n\n✅ Marca a caixa de abaixo e fai clic en **Enviar** para aplicar e gardar estes cambios inmediatamente.\n❌ Déixao desmarcado e fai clic en **Enviar** para cancelar e volver.", + "data": { + "confirm_apply_suggestions": "Aplica e garda estes cambios" + } + }, + "settings": { + "title": "Configuración", + "description": "{deprecation_warning}Axusta os limiares de detección, a aprendizaxe e o comportamento avanzado. Os valores predeterminados funcionan ben para a maioría das configuracións.\n\nConfiguración suxerida dispoñible actualmente: {suggestions_count}.\nSe isto supera 0, abre a Configuración avanzada e usa Aplicar valores suxeridos para revisar as recomendacións.", + "data": { + "apply_suggestions": "Aplicar os valores suxeridos", + "device_type": "Tipo de dispositivo", + "power_sensor": "Entidade sensor de potencia", + "min_power": "Potencia mínima (W)", + "off_delay": "Retraso de fin de ciclo (s)", + "notify_actions": "Accións de notificación", + "notify_people": "Atrasa a entrega ata que estas persoas estean na casa", + "notify_only_when_home": "Atrasa as notificacións ata que a persoa seleccionada estea na casa", + "notify_fire_events": "Lanzar eventos de automatización", + "notify_start_services": "Inicio do ciclo - Obxectivos de notificación", + "notify_finish_services": "Finalización do ciclo - Obxectivos de notificación", + "notify_live_services": "Progreso en directo - Obxectivos de notificación", + "notify_before_end_minutes": "Notificación previa á finalización (minutos antes do final)", + "notify_title": "Título da notificación", + "notify_icon": "Icona de notificación", + "notify_start_message": "Iniciar o formato da mensaxe", + "notify_finish_message": "Finalizar o formato da mensaxe", + "notify_pre_complete_message": "Formato de mensaxe precompletado", + "show_advanced": "Editar configuración avanzada (paso seguinte)" + }, + "data_description": { + "apply_suggestions": "Marque esta caixa para copiar os valores suxeridos polo algoritmo de aprendizaxe no formulario. Revisa antes de gardar.\n\nSuxerido (de aprendizaxe):\n- Potencia_mín: {suggested_min_power} W\n- retardo_desactivación: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- tolerancia_duración: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- límite_enerxético_final: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Seleccione o tipo de electrodoméstico (lavadora, secadora, lavalouzas, cafetera, EV). Esta etiqueta gárdase con cada ciclo para unha mellor organización.", + "power_sensor": "A entidade sensora que informa da potencia en tempo real (en vatios). Podes cambiar isto en calquera momento sen perder os datos históricos; é útil se substitúes un enchufe intelixente.", + "min_power": "As lecturas de potencia por riba deste limiar (en vatios) indican que o aparello está funcionando. Comeza con 2 W para a maioría dos dispositivos.", + "off_delay": "Segundos, a potencia suavizada debe permanecer por debaixo do limiar de parada para marcar a finalización. Predeterminado: 120 s.", + "notify_actions": "Secuencia de acción opcional do Home Assistant para executar para notificacións (scripts, notificación, TTS, etc.). Se se establece, as accións execútanse antes do servizo de notificación alternativa.", + "notify_people": "Entidades de persoas utilizadas para o control de presenza. Cando se activa, a entrega de notificacións atrasase ata que polo menos unha persoa seleccionada estea na casa.", + "notify_only_when_home": "Se está activado, atrasa as notificacións ata que polo menos unha persoa seleccionada estea na casa.", + "notify_fire_events": "Se está activado, activa os eventos do Home Assistant para o inicio e o final do ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para impulsar as automatizacións.", + "notify_start_services": "Un ou varios servizos de notificación para alertar cando comeza un ciclo. Deixa o campo baleiro para saltar as notificacións de inicio.", + "notify_finish_services": "Un ou máis servizos de notificación para alertar cando un ciclo remata ou se está preto da súa finalización. Deixa o campo baleiro para saltar as notificacións de finalización.", + "notify_live_services": "Un ou máis servizos de notificación para recibir actualizacións de progreso en directo mentres se executa o ciclo (só a aplicación complementaria para móbiles). Deixa o campo baleiro para saltar as actualizacións en directo.", + "notify_before_end_minutes": "Minutos antes do final estimado do ciclo para activar unha notificación. Establécese en 0 para desactivar. Por defecto: 0.", + "notify_title": "Título personalizado para notificacións. Admite o marcador de posición {device}.", + "notify_icon": "Icona para usar para notificacións (por exemplo, mdi:washing-machine). Deixa en branco para enviar sen icona.", + "notify_start_message": "Mensaxe enviada cando comeza un ciclo. Admite o marcador de posición {device}.", + "notify_finish_message": "Mensaxe enviada cando remata un ciclo. Admite os marcadores de posición {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texto das actualizacións recorrentes do progreso en directo mentres se executa o ciclo. Admite os marcadores de posición {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificacións", + "description": "Configura as canles de notificación, os modelos e as actualizacións opcionais do progreso móbil en directo.", + "data": { + "notify_actions": "Accións de notificación", + "notify_people": "Atrasa a entrega ata que estas persoas estean na casa", + "notify_only_when_home": "Atrasa as notificacións ata que a persoa seleccionada estea na casa", + "notify_fire_events": "Lanzar eventos de automatización", + "notify_start_services": "Inicio do ciclo - Obxectivos de notificación", + "notify_finish_services": "Finalización do ciclo - Obxectivos de notificación", + "notify_live_services": "Progreso en directo - Obxectivos de notificación", + "notify_before_end_minutes": "Notificación previa á finalización (minutos antes do final)", + "notify_live_interval_seconds": "Intervalo de actualización en directo (segundos)", + "notify_live_overrun_percent": "Subsidio de excedencia de actualizacións en directo (%)", + "notify_live_chronometer": "Temporizador de conta atrás do cronómetro", + "notify_title": "Título da notificación", + "notify_icon": "Icona de notificación", + "notify_start_message": "Iniciar o formato da mensaxe", + "notify_finish_message": "Finalizar o formato da mensaxe", + "notify_pre_complete_message": "Formato de mensaxe precompletado", + "energy_price_entity": "Prezo da enerxía - Entidade (opcional)", + "energy_price_static": "Prezo da enerxía: valor estático (opcional)", + "go_back": "Volver sen gardar", + "notify_reminder_message": "Formato de mensaxe", + "notify_timeout_seconds": "Auto-desistencia (segundos)", + "notify_channel": "Canle de notificación (estado/live/reminder)", + "notify_finish_channel": "Canle de notificación finalizada" + }, + "data_description": { + "go_back": "Marca isto e fai clic en Enviar para descartar os cambios anteriores e volver ao menú principal.", + "notify_actions": "Secuencia de acción opcional do Home Assistant para executar para notificacións (scripts, notificación, TTS, etc.). Se se establece, as accións execútanse antes do servizo de notificación alternativa.", + "notify_people": "Entidades de persoas utilizadas para o control de presenza. Cando se activa, a entrega de notificacións atrasase ata que polo menos unha persoa seleccionada estea na casa.", + "notify_only_when_home": "Se está activado, atrasa as notificacións ata que polo menos unha persoa seleccionada estea na casa.", + "notify_fire_events": "Se está activado, activa os eventos do Home Assistant para o inicio e o final do ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para impulsar as automatizacións.", + "notify_start_services": "Un ou máis servizos de notificación para alertar cando comeza un ciclo (por exemplo, notify.mobile_app_my_phone). Deixa o campo baleiro para saltar as notificacións de inicio.", + "notify_finish_services": "Un ou máis servizos de notificación para alertar cando un ciclo remata ou se está preto da súa finalización. Deixa o campo baleiro para saltar as notificacións de finalización.", + "notify_live_services": "Un ou máis servizos de notificación para recibir actualizacións de progreso en directo mentres se executa o ciclo (só a aplicación complementaria para móbiles). Deixa o campo baleiro para saltar as actualizacións en directo.", + "notify_before_end_minutes": "Minutos antes do final estimado do ciclo para activar unha notificación. Establécese en 0 para desactivar. Por defecto: 0.", + "notify_live_interval_seconds": "Con que frecuencia se envían as actualizacións de progreso durante a execución. Os valores máis baixos responden máis pero consomen máis notificacións. Aplícase un mínimo de 30 segundos; os valores inferiores a 30 s redondéanse automaticamente a 30 s.", + "notify_live_overrun_percent": "A marxe de seguridade por riba do reconto de actualizacións estimado para ciclos longos ou superados (por exemplo, un 20 % permite actualizacións 1,2 veces estimadas).", + "notify_live_chronometer": "Cando está activada, cada actualización en directo inclúe unha conta atrás do cronómetro ata a hora de finalización estimada. O temporizador de notificacións desactiva automaticamente no dispositivo entre as actualizacións (só a aplicación complementaria de Android).", + "notify_title": "Título personalizado para notificacións. Admite o marcador de posición {device}.", + "notify_icon": "Icona para usar para notificacións (por exemplo, mdi:washing-machine). Deixa en branco para enviar sen icona.", + "notify_start_message": "Mensaxe enviada cando comeza un ciclo. Admite o marcador de posición {device}.", + "notify_finish_message": "Mensaxe enviada cando remata un ciclo. Admite os marcadores de posición {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Seleccione unha entidade numérica que conteña o prezo actual da electricidade (por exemplo, sensor.electricity_price, input_number.energy_rate). Cando se define, activa o marcador de posición {cost}. Ten prioridade sobre o valor estático se ambos están configurados.", + "energy_price_static": "Prezo fixo da electricidade por kWh. Cando se define, activa o marcador de posición {cost}. Ignorase se unha entidade está configurada anteriormente. Engade o teu símbolo de moeda no modelo de mensaxe, por exemplo. {cost} €.", + "notify_reminder_message": "O texto do recordatorio único enviou o número configurado de minutos antes do final estimado. Distinción das actualizacións en directo recorrentes anteriores. Admite os marcadores de posición {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Desestima automaticamente as notificacións despois de tantos segundos (previado como a aplicación compañeira \"tempo de espera\"). 0 para mantelos ata que sexan eliminados. Por defecto: 0.", + "notify_channel": "Android canle de notificación de estado, progreso en directo e notificacións de recordatorios. O son e a importancia dunha canle configúrase na aplicación compañeira a primeira vez que se usa o nome da canle: WashData só establece o nome da canle. Deixe baleiro para a aplicación por defecto.", + "notify_finish_channel": "Canle de Android separada para a alerta de finalización (tamén usada polo recordatorio e o aviso de espera da roupa) para que poida ter o seu propio son. Deixar baleiro para reutilizar a canle de arriba.", + "notify_pre_complete_message": "Texto das actualizacións recorrentes do progreso en directo mentres se executa o ciclo. Admite os marcadores de posición {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Configuración avanzada", + "description": "Axusta os limiares de detección, a aprendizaxe e o comportamento avanzado. Os valores predeterminados funcionan ben para a maioría das configuracións.", + "sections": { + "suggestions_section": { + "name": "Configuracións suxeridas", + "description": "{suggestions_count} suxestións aprendidas dispoñibles. Marca Aplicar valores suxeridos para revisar os cambios propostos antes de gardar.", + "data": { + "apply_suggestions": "Aplicar os valores suxeridos" + }, + "data_description": { + "apply_suggestions": "Marque esta caixa para abrir un paso de revisión dos valores suxeridos do algoritmo de aprendizaxe. Non se garda nada automaticamente.\n\nSuxerido (de aprendizaxe):\n- Potencia_mín: {suggested_min_power} W\n- retardo_desactivación: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- tolerancia_duración: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- límite_enerxético_final: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detección e limiares de potencia", + "description": "Cando o aparello se considera acendido ou apagado, límites de enerxía e temporización das transicións de estado.", + "data": { + "start_duration_threshold": "Duración do inicio do rebote (s)", + "start_energy_threshold": "Iniciar Energy Gate (Wh)", + "completion_min_seconds": "Tempo de execución mínimo de finalización (segundos)", + "start_threshold_w": "Limiar de inicio (W)", + "stop_threshold_w": "Limiar de parada (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Zona morta de execución (segundos)", + "end_repeat_count": "Finalizar o reconto de repeticións", + "min_off_gap": "Distancia mínima entre ciclos (s)", + "sampling_interval": "Intervalo (s) de mostraxe" + }, + "data_description": { + "start_duration_threshold": "Ignora os picos de potencia breves máis curtos que esta duración (segundos). Filtra os picos de arranque (por exemplo, 60 W durante 2 s) antes de que comece o ciclo real. Predeterminado: 5 s.", + "start_energy_threshold": "O ciclo debe acumular polo menos esta enerxía (Wh) durante a fase de INICIO para ser confirmado. Predeterminado: 0,005 Wh.", + "completion_min_seconds": "Os ciclos máis curtos que este marcaranse como \"interrompidos\" aínda que rematen de forma natural. Predeterminado: 600 s.", + "start_threshold_w": "A potencia debe subir por riba deste limiar para INICIAR un ciclo. Establece unha potencia superior á túa en espera. Valor predeterminado: min_power + 1 W.", + "stop_threshold_w": "A potencia debe caer POR BAIXO deste limiar para FINALIZAR un ciclo. Establece un pouco por riba de cero para evitar que o ruído desencadee falsos extremos. Valor predeterminado: 0,6 * min_power.", + "end_energy_threshold": "O ciclo remata só se a enerxía na última xanela de retardo de apagado está por debaixo deste valor (Wh). Evita falsos extremos se a potencia fluctúa preto de cero. Predeterminado: 0,05 Wh.", + "running_dead_zone": "Despois de comezar o ciclo, ignore as caídas de enerxía durante estes segundos para evitar a detección de final falso durante a fase de arranque inicial. Establécese en 0 para desactivar. Predeterminado: 0s.", + "end_repeat_count": "Número de veces que se debe cumprir consecutivamente a condición de final (potencia por debaixo do limiar para off_delay) antes de finalizar o ciclo. Os valores máis altos evitan finalidades falsas durante as pausas. Por defecto: 1.", + "min_off_gap": "Se un ciclo comeza dentro destes segundos desde que remate o anterior, uniranse nun único ciclo.", + "sampling_interval": "Intervalo mínimo de mostraxe en segundos. Ignoraranse as actualizacións máis rápidas que esta taxa. Predeterminado: 30 s (2 s para lavadoras, lavadoras-secadoras e lavalouzas)." + } + }, + "matching_section": { + "name": "Correspondencia de perfís e aprendizaxe", + "description": "Con que agresividade WashData fai coincidir os ciclos en execución cos perfís aprendidos e cando pedir comentarios.", + "data": { + "profile_match_min_duration_ratio": "Relación de duración mínima de coincidencia de perfil (0,1-1,0)", + "profile_match_interval": "Intervalo de coincidencia de perfil (segundos)", + "profile_match_threshold": "Limiar de coincidencia de perfil", + "profile_unmatch_threshold": "Limiar de non coincidencia do perfil", + "auto_label_confidence": "Etiqueta automática de confianza (0-1)", + "learning_confidence": "Confianza da solicitude de comentarios (0-1)", + "suppress_feedback_notifications": "Suprimir as notificacións de comentarios", + "duration_tolerance": "Tolerancia de duración (0-0,5)", + "smoothing_window": "Fiestra de suavización (mostras)", + "profile_duration_tolerance": "Tolerancia de duración da coincidencia do perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Relación de duración mínima para a correspondencia do perfil. O ciclo de execución debe ser polo menos esta fracción da duración do perfil para que coincida. Inferior = coincidencia anterior. Por defecto: 0,50 (50%).", + "profile_match_interval": "Con que frecuencia (en segundos) tentar a coincidencia de perfil durante un ciclo. Os valores máis baixos proporcionan unha detección máis rápida pero usan máis CPU. Predeterminado: 300 s (5 minutos).", + "profile_match_threshold": "Puntuación mínima de semellanza DTW (0,0–1,0) necesaria para considerar un perfil como coincidencia. Maior = coincidencia máis estrita, menos falsos positivos. Por defecto: 0.4.", + "profile_unmatch_threshold": "Puntuación de semellanza DTW (0,0–1,0) por debaixo da cal se rexeita un perfil previamente coincidente. Debe ser inferior ao limiar de coincidencia para evitar o parpadeo. Por defecto: 0,35.", + "auto_label_confidence": "Etiqueta automaticamente os ciclos igual ou superior a esta confianza ao finalizar (0,0–1,0).", + "learning_confidence": "Confianza mínima para solicitar a verificación do usuario mediante un evento (0,0–1,0).", + "suppress_feedback_notifications": "Cando está activado, WashData seguirá facendo un seguimento e solicitará comentarios internamente, pero non mostrará notificacións persistentes que che soliciten que confirmes os ciclos. Útil cando os ciclos sempre se detectan correctamente e atopas que as indicacións distraen.", + "duration_tolerance": "Tolerancia para as estimacións do tempo restante durante unha carreira (0,0-0,5 corresponde ao 0-50%).", + "smoothing_window": "Número de lecturas recentes de potencia utilizadas para a suavización da media móbil.", + "profile_duration_tolerance": "Tolerancia utilizada pola coincidencia de perfil para a variación da duración total (0,0–0,5). Comportamento por defecto: ±25%." + } + }, + "timing_section": { + "name": "Temporización, mantemento e depuración", + "description": "Watchdog, reinicio do progreso, mantemento automático e exposición de entidades de depuración.", + "data": { + "watchdog_interval": "Intervalo de control (segundos)", + "no_update_active_timeout": "Tempo de espera sen actualización (s)", + "progress_reset_delay": "Demora de restablecemento do progreso (segundos)", + "auto_maintenance": "Activar o mantemento automático", + "expose_debug_entities": "Expoñer entidades de depuración", + "save_debug_traces": "Gardar trazos de depuración" + }, + "data_description": { + "watchdog_interval": "Segundos entre comprobacións de control durante a execución. Predeterminado: 5 s. ADVERTENCIA: Asegúrate de que este é MAIOR que o intervalo de actualización do teu sensor para evitar falsas paradas (por exemplo, se o sensor se actualiza cada 60 segundos, utiliza 65 segundos ou máis).", + "no_update_active_timeout": "Para sensores de publicación ao cambio: só forza a finalizar un ciclo ACTIVO se non chegan actualizacións durante tantos segundos. A finalización de baixa potencia aínda usa o retardo de apagado.", + "progress_reset_delay": "Despois de completar un ciclo (100 %), agarde tantos segundos de inactividade antes de restablecer o progreso ao 0 %. Predeterminado: 300 s.", + "auto_maintenance": "Activar o mantemento automático (reparar mostras, combinar fragmentos).", + "expose_debug_entities": "Mostra sensores avanzados (confianza, fase, ambigüidade) para a depuración.", + "save_debug_traces": "Almacena datos detallados de clasificación e rastrexo de enerxía no historial (Aumenta o uso de almacenamento)." + } + }, + "anti_wrinkle_section": { + "name": "Escudo antiarrugas (secadoras)", + "description": "Ignora as rotacións do tambor despois do ciclo para evitar ciclos fantasma. Só secadora / lavasecadora.", + "data": { + "anti_wrinkle_enabled": "Protector antiengurras (só secadora/lavadora-secadora)", + "anti_wrinkle_max_power": "Potencia máxima antiengurras (W) (só secadora/lavadora-secadora)", + "anti_wrinkle_max_duration": "Duración máxima antiengurras (s) (só secadora/lavadora-secadora)", + "anti_wrinkle_exit_power": "Potencia de saída antiengurras (W) (só secadora/lavadora-secadora)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignora as rotacións curtas de tambor de baixa potencia despois de que remate un ciclo para evitar ciclos fantasma (só para secadora/lavadora e secadora).", + "anti_wrinkle_max_power": "As explosións a esta potencia ou por debaixo trátanse como rotacións antiengurras en lugar de novos ciclos. Só se aplica cando o protector antiengurras está activado.", + "anti_wrinkle_max_duration": "Lonxitude máxima de explosión para tratar como rotación antiengurras. Só se aplica cando o protector antiengurras está activado.", + "anti_wrinkle_exit_power": "Se a potencia cae por debaixo deste nivel, saia inmediatamente da función antiengurras (desactivada). Só se aplica cando o protector antiengurras está activado." + } + }, + "delay_start_section": { + "name": "Detección de inicio diferido", + "description": "Trata a espera sostida de baixa potencia como 'Agardando para iniciar' ata que o ciclo comece de verdade.", + "data": { + "delay_start_detect_enabled": "Activar a detección de inicio retardado", + "delay_confirm_seconds": "Inicio diferido: tempo de confirmación en espera (s)", + "delay_timeout_hours": "Inicio diferido: tempo de espera máximo (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Cando está activado, detéctase como un inicio atrasado un breve pico de drenaxe de baixa potencia seguido dunha potencia en espera sostida. O sensor de estado mostra \"Esperando para comezar\" durante o atraso. Non se fai un seguimento do tempo nin do progreso do programa ata que comeza realmente o ciclo. Require que start_threshold_w se configure por riba da potencia do pico de drenaxe.", + "delay_confirm_seconds": "A potencia debe permanecer entre o Limiar de parada (W) e o Limiar de inicio (W) durante este número de segundos antes de que WashData entre en 'Agardando para iniciar'. Filtra os picos breves da navegación polos menús. Predeterminado: 60 s.", + "delay_timeout_hours": "Tempo de espera de seguridade: se a máquina aínda está en \"Esperando para iniciar\" despois de tantas horas, WashData restablecerá a Desactivado. Por defecto: 8 h." + } + }, + "external_triggers_section": { + "name": "Disparadores externos, porta e pausa", + "description": "Sensor externo opcional de fin de ciclo, sensor de porta para detectar pausa/limpo e interruptor de corte de enerxía en pausa.", + "data": { + "external_end_trigger_enabled": "Activar o disparador de fin de ciclo externo", + "external_end_trigger": "Sensor externo de fin de ciclo", + "external_end_trigger_inverted": "Inverter lóxica de disparo (disparador activado)", + "door_sensor_entity": "Sensor de porta", + "pause_cuts_power": "Cortar a enerxía ao facer unha pausa", + "switch_entity": "Cambiar a entidade (para o corte de enerxía en pausa)", + "notify_unload_delay_minutes": "Atraso da notificación de espera de lavandería (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Activa a monitorización dun sensor binario externo para activar a finalización do ciclo.", + "external_end_trigger": "Seleccione unha entidade de sensor binario. Cando este sensor se activa, o ciclo actual finalizará co estado \"completado\".", + "external_end_trigger_inverted": "De forma predeterminada, o gatillo dispara cando o sensor se acende. Marca esta caixa para disparar cando o sensor se apague.", + "door_sensor_entity": "Sensor binario opcional para a porta da máquina (encendido = aberto, apagado = pechado). Cando a porta se abre durante un ciclo activo, WashData confirmará o ciclo como pausado intencionalmente. Despois de finalizar o ciclo, se a porta aínda está pechada, o estado cambia a \"Limpar\" ata que se abra a porta. Nota: pechar a porta NON reanuda automaticamente un ciclo en pausa; a continuación debe activarse manualmente mediante o botón Reanudar o ciclo ou o servizo.", + "pause_cuts_power": "Ao facer unha pausa mediante o botón ou o servizo Pause Cycle, tamén desactiva a entidade de conmutación configurada. Só ten efecto se se configura unha entidade de cambio para este dispositivo.", + "switch_entity": "A entidade de conmutación para alternar ao pausar ou retomar. Necesario cando se activa \"Cortar a enerxía ao facer unha pausa\".", + "notify_unload_delay_minutes": "Envía unha notificación a través da canle de notificación de finalización despois de que o ciclo remate e a porta non se abriu durante tantos minutos (require sensor de porta). Establécese en 0 para desactivar. Por defecto: 60 min." + } + }, + "pump_section": { + "name": "Monitor de bomba", + "description": "Só para o tipo de dispositivo bomba. Dispara un evento se un ciclo da bomba supera esta duración.", + "data": { + "pump_stuck_duration": "Limiar (s) de alerta de bomba atascada (só bomba)" + }, + "data_description": { + "pump_stuck_duration": "Se un ciclo de bomba aínda está en execución despois destes segundos, desenvólvese unha vez un evento ha_washdata_pump_stuck. Use isto para detectar un motor atascado (o funcionamento típico da bomba de sumidoiro é inferior a 60 s). Predeterminado: 1800 s (30 min). Só tipo de dispositivo de bomba." + } + }, + "device_link_section": { + "name": "Dispositivo Link", + "description": "Facultativamente vincular este dispositivo a un dispositivo existente (por exemplo, o enchufe intelixente ou o propio electrodomésticos). O dispositivo WashData móstrase entón como \"conectado a través\" do dispositivo. Deixar baleiro para manter os datos como un dispositivo autónomo.", + "data": { + "linked_device": "Dispositivo conectado" + }, + "data_description": { + "linked_device": "Seleccione o dispositivo para conectar WashData. Isto engade unha referencia \"conectada vía\" no rexistro de dispositivos; WashData mantén a súa propia tarxeta de dispositivo e entidades. Borrar a selección para eliminar o enlace." + } + } + } + }, + "diagnostics": { + "title": "Diagnóstico e Mantemento", + "description": "Executa accións de mantemento como fusionar ciclos fragmentados ou migrar datos almacenados.\n\n**Uso de almacenamento**\n\n- Tamaño do ficheiro: {file_size_kb} KB\n- Ciclos: {cycle_count}\n- Perfís: {profile_count}\n- Trazos de depuración: {debug_count}", + "menu_options": { + "reprocess_history": "Mantemento: reprocesamento e optimización de datos", + "clear_debug_data": "Borrar datos de depuración (liberar espazo)", + "wipe_history": "Borrar TODOS os datos deste dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con configuración (copiar/pegar)", + "menu_back": "← Volver" + } + }, + "clear_debug_data": { + "title": "Borrar datos de depuración", + "description": "Estás seguro de que queres eliminar todos os rastros de depuración almacenados? Isto liberará espazo pero eliminará a información detallada da clasificación dos ciclos anteriores." + }, + "manage_cycles": { + "title": "Xestionar Ciclos", + "description": "Últimos ciclos:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etiqueta automática de ciclos antigos", + "select_cycle_to_label": "Ciclo específico da etiqueta", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Combinar/dividir Editor interactivo", + "trim_cycle_select": "Datos do ciclo de recorte", + "menu_back": "← Volver" + } + }, + "manage_cycles_empty": { + "title": "Xestionar Ciclos", + "description": "Aínda non se rexistrou ningún ciclo.", + "menu_options": { + "auto_label_cycles": "Etiqueta automática de ciclos antigos", + "menu_back": "← Volver" + } + }, + "manage_profiles": { + "title": "Xestionar perfís", + "description": "Resumo do perfil:\n{profile_summary}", + "menu_options": { + "create_profile": "Crear novo perfil", + "edit_profile": "Editar/Renomear o perfil", + "delete_profile_select": "Eliminar perfil", + "profile_stats": "Estatísticas do perfil", + "cleanup_profile": "Limpar o historial - Gráfica e elimina", + "assign_profile_phases_select": "Asignar intervalos de fase", + "menu_back": "← Volver" + } + }, + "manage_profiles_empty": { + "title": "Xestionar perfís", + "description": "Aínda non se creou ningún perfil.", + "menu_options": { + "create_profile": "Crear novo perfil", + "menu_back": "← Volver" + } + }, + "manage_phase_catalog": { + "title": "Xestionar o catálogo de fases", + "description": "Catálogo de fases (valores predeterminados para este dispositivo + fases personalizadas):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Crear unha nova fase", + "phase_catalog_edit_select": "Fase de edición", + "phase_catalog_delete": "Eliminar fase", + "menu_back": "← Volver" + } + }, + "phase_catalog_create": { + "title": "Crear fase personalizada", + "description": "Crea un nome e unha descrición de fase personalizados e escolla a que tipo de dispositivo se aplica.", + "data": { + "target_device_type": "Tipo de dispositivo de destino", + "phase_name": "Nome da fase", + "phase_description": "Descrición da fase" + } + }, + "phase_catalog_edit_select": { + "title": "Editar Fase Personalizada", + "description": "Seleccione unha fase personalizada para editar.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "phase_catalog_edit": { + "title": "Editar Fase Personalizada", + "description": "Fase de edición: {phase_name}", + "data": { + "phase_name": "Nome da fase", + "phase_description": "Descrición da fase" + } + }, + "phase_catalog_delete": { + "title": "Eliminar Fase Personalizada", + "description": "Seleccione unha fase personalizada para eliminar. Eliminaranse os intervalos asignados que utilicen esta fase.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "assign_profile_phases": { + "title": "Asignar intervalos de fase", + "description": "Vista previa da fase para o perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalos actuais:**\n{current_ranges}\n\nUse as accións seguintes para engadir, editar, eliminar ou gardar intervalos.", + "menu_options": { + "assign_profile_phases_add": "Engadir intervalo de fases", + "assign_profile_phases_edit_select": "Editar intervalo de fases", + "assign_profile_phases_delete": "Eliminar intervalo de fases", + "phase_ranges_clear": "Borrar todos os intervalos", + "assign_profile_phases_auto_detect": "Fases de detección automática", + "phase_ranges_save": "Garda e Volve", + "menu_back": "← Volver" + } + }, + "assign_profile_phases_add": { + "title": "Engadir intervalo de fases", + "description": "Engade un intervalo de fase ao borrador actual.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto Final" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editar intervalo de fases", + "description": "Seleccione un intervalo para editar.", + "data": { + "range_index": "Rango de fases" + } + }, + "assign_profile_phases_edit": { + "title": "Editar intervalo de fases", + "description": "Actualiza o intervalo de fase seleccionado.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de inicio", + "end_min": "Minuto Final" + } + }, + "assign_profile_phases_delete": { + "title": "Eliminar intervalo de fases", + "description": "Selecciona un intervalo para eliminar do borrador.", + "data": { + "range_index": "Rango de fases" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectadas automaticamente", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Detectáronse {detected_count} fases automaticamente.** Escolle unha acción a continuación.", + "data": { + "action": "Acción" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nome das fases detectadas", + "description": "Perfil: **{profile_name}**\n\n**Detectáronse {detected_count} fases:**\n{ranges_summary}\n\nAsignar un nome a cada fase." + }, + "assign_profile_phases_select": { + "title": "Asignar intervalos de fase", + "description": "Seleccione un perfil para configurar intervalos de fase.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estatísticas do perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Crear novo perfil", + "description": "Crea un novo perfil manualmente ou a partir dun ciclo anterior.\n\nExemplos de nomes de perfil: 'Delicados', 'Resistente', 'Lavado rápido'", + "data": { + "profile_name": "Nome do perfil", + "reference_cycle": "Ciclo de referencia (opcional)", + "manual_duration": "Duración manual (minutos)" + } + }, + "edit_profile": { + "title": "Editar perfil", + "description": "Seleccione un perfil para renomear", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Editar detalles do perfil", + "description": "Perfil actual: {current_name}", + "data": { + "new_name": "Nome do perfil", + "manual_duration": "Duración manual (minutos)" + } + }, + "delete_profile_select": { + "title": "Eliminar perfil", + "description": "Seleccione un perfil para eliminar", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirma Eliminar perfil", + "description": "⚠️ Isto eliminará permanentemente o perfil: {profile_name}", + "data": { + "unlabel_cycles": "Elimina a etiqueta dos ciclos usando este perfil" + } + }, + "auto_label_cycles": { + "title": "Etiqueta automática de ciclos antigos", + "description": "Atopáronse {total_count} ciclos totais. Perfís: {profiles}", + "data": { + "confidence_threshold": "Confianza mínima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleccione Ciclo para etiquetar", + "description": "✓ = completado, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Eliminar ciclo", + "description": "⚠️ Isto eliminará permanentemente o ciclo seleccionado", + "data": { + "cycle_id": "Ciclo para eliminar" + } + }, + "label_cycle": { + "title": "Ciclo de etiquetas", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Novo nome de perfil (se se está a crear)" + } + }, + "post_process": { + "title": "Historial do proceso (fusionar/dividir)", + "description": "Limpa o historial de ciclos fusionando execucións fragmentadas e dividindo ciclos combinados incorrectamente nunha xanela de tempo. Introduza o número de horas para mirar atrás (use 999999 para todos).", + "data": { + "time_range": "Ventá retrospectiva (horas)", + "gap_seconds": "Combinar/dividir o intervalo (segundos)" + }, + "data_description": { + "time_range": "Número de horas para buscar ciclos fragmentados para fusionar. Use 999999 para procesar todos os datos históricos.", + "gap_seconds": "Limiar para decidir división vs fusión. Os espazos máis curtos que este únense. Os espazos máis longos que este están divididos. Baixa isto para forzar unha división nun ciclo que se fusionou incorrectamente." + } + }, + "cleanup_profile": { + "title": "Limpar o historial: seleccione o perfil", + "description": "Seleccione un perfil para visualizar. Isto xerará un gráfico que mostra todos os ciclos pasados ​​deste perfil para axudar a identificar os valores atípicos.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Limpar o historial - Gráfica e elimina", + "description": "![Gráfica]({graph_url})\n\n**Visualización de {profile_name}**\n\nO gráfico mostra os ciclos individuais como liñas de cores. Identifica os valores atípicos (liñas afastadas do grupo) e fai coincidir a súa cor na lista de abaixo para eliminalos.\n\n**Selecciona ciclos para eliminar PERMANENTE:**", + "data": { + "cycles_to_delete": "Ciclos para eliminar (comproba as cores coincidentes)" + } + }, + "interactive_editor": { + "title": "Combinar/dividir Editor interactivo", + "description": "Seleccione unha acción manual para realizar no seu historial de ciclos.", + "menu_options": { + "editor_split": "Dividir un ciclo (Buscar ocos)", + "editor_merge": "Combinar ciclos (unir fragmentos)", + "editor_delete": "Eliminar ciclo(s)", + "menu_back": "← Volver" + } + }, + "editor_select": { + "title": "Seleccione Ciclos", + "description": "Seleccione o(s) ciclo(s) a procesar.\n\n{info_text}", + "data": { + "selected_cycles": "Ciclos" + } + }, + "editor_configure": { + "title": "Configurar e aplicar", + "description": "{preview_md}", + "data": { + "confirm_action": "Acción", + "merged_profile": "Perfil resultante", + "new_profile_name": "Novo nome de perfil", + "confirm_commit": "Si, confirmo esta acción", + "segment_0_profile": "Perfil do segmento 1", + "segment_1_profile": "Perfil do segmento 2", + "segment_2_profile": "Perfil do segmento 3", + "segment_3_profile": "Perfil do segmento 4", + "segment_4_profile": "Perfil do segmento 5", + "segment_5_profile": "Perfil do segmento 6", + "segment_6_profile": "Perfil do segmento 7", + "segment_7_profile": "Perfil do segmento 8", + "segment_8_profile": "Perfil do segmento 9", + "segment_9_profile": "Perfil do segmento 10" + } + }, + "editor_split_params": { + "title": "Configuración dividida", + "description": "Axuste a sensibilidade para dividir este ciclo. Calquera espazo inactivo máis longo que isto provocará unha división.", + "data": { + "split_mode": "Método de división" + } + }, + "editor_split_auto_params": { + "title": "Configuración da división automática", + "description": "Axusta a sensibilidade para dividir este ciclo. Calquera intervalo de inactividade máis longo ca este provocará unha división.", + "data": { + "min_gap_seconds": "Limiar do intervalo de división (segundos)" + } + }, + "editor_split_manual_params": { + "title": "Marcas de tempo de división manual", + "description": "{preview_md}Xanela do ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** o {cycle_date}.\n\nIntroduce unha ou máis marcas de tempo de división (`HH:MM` ou `HH:MM:SS`), unha por liña. O ciclo cortarase en cada marca de tempo — N marcas de tempo producen N+1 segmentos.", + "data": { + "split_timestamps": "Marcas temporais de división" + } + }, + "reprocess_history": { + "title": "Mantemento: reprocesamento e optimización de datos", + "description": "Realiza unha limpeza profunda de datos históricos:\n1. Recorta as lecturas de potencia cero (silencio).\n2. Corrixe o tempo/duración do ciclo.\n3. Recalcula todos os modelos estatísticos (sobres).\n\n**Executa isto para solucionar problemas de datos ou aplicar novos algoritmos.**" + }, + "wipe_history": { + "title": "Borrar o historial (só probas)", + "description": "⚠️ Isto eliminará permanentemente TODOS os ciclos e perfís almacenados para este dispositivo. Isto non se pode desfacer!" + }, + "export_import": { + "title": "Exportar/Importar JSON", + "description": "Copiar/pegar ciclos, perfís e configuracións para este dispositivo. Exporta a continuación ou pega JSON para importar.", + "data": { + "mode": "Acción", + "json_payload": "Configuración completa JSON" + }, + "data_description": { + "mode": "Escolla Exportar para copiar os datos e a configuración actuais ou Importar para pegar os datos exportados desde outro dispositivo WashData.", + "json_payload": "Para exportar: copia este JSON (inclúe ciclos, perfís e todas as configuracións afinadas). Para importar: pegue JSON exportado desde WashData." + } + }, + "record_cycle": { + "title": "Ciclo de rexistro", + "description": "Grava manualmente un ciclo para crear un perfil limpo sen interferencias.\n\nEstado: **{status}**\nDuración: {duration} s\nMostras: {samples}", + "menu_options": { + "record_refresh": "Estado de actualización", + "record_stop": "Deter a gravación (gardar e procesar)", + "record_start": "Iniciar unha nova gravación", + "record_process": "Procesar a última gravación (recortar e gardar)", + "record_discard": "Descartar a última gravación", + "menu_back": "← Volver" + } + }, + "record_process": { + "title": "Gravación do proceso", + "description": "![Gráfica]({graph_url})\n\n**O gráfico mostra a gravación en bruto.** Azul = Manter, Vermello = Recorte proposto.\n*O gráfico é estático e non se actualiza cos cambios de formulario.*\n\nDetívose a gravación. Os recortes están aliñados coa frecuencia de mostraxe detectada (~{sampling_rate}s).\n\nDuración bruta: {duration} s\nMostras: {samples}", + "data": { + "head_trim": "Inicio de recorte (segundos)", + "tail_trim": "Fin de recorte (segundos)", + "save_mode": "Gardar destino", + "profile_name": "Nome do perfil" + } + }, + "learning_feedbacks": { + "title": "Comentarios de aprendizaxe", + "description": "Comentarios pendentes: {count}\n\n{pending_table}\n\nSelecciona unha solicitude de revisión de ciclo para procesar.", + "menu_options": { + "learning_feedbacks_pick": "Revisar un feedback pendente", + "learning_feedbacks_dismiss_all": "Descartar todo o feedback pendente", + "menu_back": "← Volver" + } + }, + "learning_feedbacks_pick": { + "title": "Seleccionar comentarios para revisar", + "description": "Seleccione unha solicitude de revisión de ciclo para abrir.", + "data": { + "selected_feedback": "Seleccionar ciclo para revisar" + } + }, + "learning_feedbacks_empty": { + "title": "Comentarios de aprendizaxe", + "description": "Non se atoparon solicitudes de comentarios pendentes.", + "menu_options": { + "menu_back": "← Volver" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Descartar todos os comentarios pendentes", + "description": "Está a piques de descartar **{count}** solicitudes de comentarios pendentes. Os ciclos descartados permanecerán no seu historial pero non achegarán ningún novo sinal de aprendizaxe. Isto non se pode desfacer.\n\n✅ Marca a caixa de abaixo e fai clic en **Enviar** para descartalos todos.\n❌ Déixaa sen marcar e fai clic en **Enviar** para cancelar e volver.", + "data": { + "confirm_dismiss_all": "Confirmar: descartar todas as solicitudes de feedback pendentes" + } + }, + "resolve_feedback": { + "title": "Resolver comentarios", + "description": "{comparison_img}{candidates_table}\n**Perfil detectado**: {detected_profile} ({confidence_pct}%)\n**Duración estimada**: {est_duration_min} min\n**Duración real**: {act_duration_min} min\n\n__Escolle unha acción a continuación:__", + "data": { + "action": "Que che gustaría facer?", + "corrected_profile": "Programa correcto (se está corrixindo)", + "corrected_duration": "Duración correcta en segundos (se se corrixe)" + } + }, + "trim_cycle_select": { + "title": "Ciclo de recorte - Seleccione Ciclo", + "description": "Seleccione un ciclo para cortar. ✓ = completado, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Ciclo de corte", + "description": "Fiestra actual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acción" + } + }, + "trim_cycle_start": { + "title": "Establece o inicio de recorte", + "description": "Ventá de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInicio actual: **{current_wallclock}** (+{current_offset_min} min desde o inicio do ciclo)\n\nEscolle unha nova hora de inicio usando o selector de reloxos que aparece a continuación.", + "data": { + "trim_start_time": "Nova hora de inicio", + "trim_start_min": "Novo inicio (minutos desde o inicio do ciclo)" + } + }, + "trim_cycle_end": { + "title": "Establecer o final do recorte", + "description": "Ventá de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFin actual: **{current_wallclock}** (+{current_offset_min} min desde o inicio do ciclo)\n\nEscolle unha nova hora de finalización usando o selector de reloxos que aparece a continuación.", + "data": { + "trim_end_time": "Nova hora de finalización", + "trim_end_min": "Novo fin (minutos desde o inicio do ciclo)" + } + } + }, + "error": { + "import_failed": "Produciuse un erro na importación. Pega un JSON de exportación de WashData válido.", + "select_exactly_one": "Selecciona exactamente un ciclo para esta acción.", + "select_at_least_one": "Selecciona polo menos un ciclo para esta acción.", + "select_at_least_two": "Selecciona polo menos dous ciclos para esta acción.", + "profile_exists": "Xa existe un perfil con este nome", + "rename_failed": "Produciuse un erro ao cambiar o nome do perfil. É posible que o perfil non exista ou que xa se tome un novo nome.", + "assignment_failed": "Produciuse un erro ao asignar o perfil ao ciclo", + "duplicate_phase": "Xa existe unha fase con este nome.", + "phase_not_found": "Non se atopou a fase seleccionada.", + "invalid_phase_name": "O nome da fase non pode estar baleiro.", + "phase_name_too_long": "O nome da fase é demasiado longo.", + "invalid_phase_range": "O intervalo de fases non é válido. O final debe ser maior que o inicio.", + "invalid_phase_timestamp": "A marca de tempo non é válida. Use o formato AAAA-MM-DD HH:MM ou HH:MM.", + "overlapping_phase_ranges": "Os intervalos de fase se solapan. Axusta os intervalos e téntao de novo.", + "incomplete_phase_row": "Cada fila de fase debe incluír unha fase máis os valores de inicio/fin completos para o modo seleccionado.", + "timestamp_mode_cycle_required": "Seleccione un ciclo de orixe cando use o modo de marca de tempo.", + "no_phase_ranges": "Aínda non hai intervalos de fase dispoñibles para esa acción.", + "feedback_notification_title": "WashData: verificar o ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programa**: {program} ({confidence} % de confianza)\n**Hora**: {time}\n\nWashData precisa da túa axuda para verificar este ciclo detectado.\n\nVaia a **Configuración > Dispositivos e servizos > WashData > Configurar > Comentarios de aprendizaxe** para confirmar ou corrixir este resultado.", + "suggestions_ready_notification_title": "WashData: configuracións suxeridas listas ({device})", + "suggestions_ready_notification_message": "O sensor de **Configuración suxerida** agora informa de **{count}** recomendacións útiles.\n\nPara revisalos e aplicalos: **Configuración > Dispositivos e servizos > WashData > Configurar > Configuración avanzada > Aplicar valores suxeridos**.\n\nAs suxestións son opcionais e móstranse para revisar antes de gardar.", + "trim_range_invalid": "O comezo debe ser antes do final. Axusta os teus puntos de recorte.", + "trim_failed": "Fallou o recorte. É posible que o ciclo xa non exista ou que a xanela estea baleira.", + "invalid_split_timestamp": "A marca temporal é inválida ou está fóra da xanela do ciclo. Usa HH:MM ou HH:MM:SS dentro da xanela do ciclo.", + "no_split_segments_found": "Non se puideron producir segmentos válidos a partir destas marcas temporais. Asegúrate de que cada marca temporal estea dentro da xanela do ciclo e de que os segmentos duren polo menos 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} detectaron ciclos pantasmas. Cambio de min_power suxerido: {current_min}W -> {new_min}W (non se aplica automaticamente).", + "auto_tune_title": "Sintonización automática de WashData", + "auto_tune_fallback": "{device_type} {device_title} detectaron ciclos pantasmas. Cambio de min_power suxerido: {current_min}W -> {new_min}W (non se aplica automaticamente)." + }, + "abort": { + "no_cycles_found": "Non se atoparon ciclos", + "no_split_segments_found": "Non se atoparon segmentos divisibles no ciclo seleccionado.", + "cycle_not_found": "Non se puido cargar o ciclo seleccionado.", + "no_power_data": "Non hai datos de rastrexo de enerxía dispoñibles para o ciclo seleccionado.", + "no_profiles_found": "Non se atoparon perfís. Crea primeiro un perfil.", + "no_profiles_for_matching": "Non hai perfís dispoñibles para coincidir. Crea primeiro perfís.", + "no_unlabeled_cycles": "Todos os ciclos xa están etiquetados", + "no_suggestions": "Aínda non hai valores suxeridos dispoñibles. Executa algúns ciclos e téntao de novo.", + "no_predictions": "Sen previsións", + "no_custom_phases": "Non se atopou ningunha fase personalizada.", + "reprocess_success": "Reprocesáronse correctamente {count} ciclos.", + "debug_data_cleared": "Borráronse correctamente os datos de depuración de {count} ciclos." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Inicio de ciclo", + "cycle_finish": "Finalización do ciclo", + "cycle_live": "Progreso en directo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sen etiqueta)", + "create_new_profile": "Crear novo perfil", + "remove_label": "Eliminar a etiqueta", + "no_reference_cycle": "(Sen ciclo de referencia)", + "all_device_types": "Todos os tipos de dispositivos", + "current_suffix": "(actual)", + "editor_select_info": "Selecciona 1 ciclo para dividir ou máis de 2 ciclos para combinar.", + "editor_select_info_split": "Selecciona 1 ciclo para dividir.", + "editor_select_info_merge": "Selecciona 2 ou máis ciclos para fusionar.", + "editor_select_info_delete": "Selecciona 1 ou máis ciclos para eliminar.", + "no_cycles_recorded": "Aínda non se rexistrou ningún ciclo.", + "profile_summary_line": "- **{name}**: {count} ciclos, {avg}m de media", + "no_profiles_created": "Aínda non se creou ningún perfil.", + "phase_preview": "Vista previa de fases", + "phase_preview_no_curve": "A curva de perfil media aínda non está dispoñible. Executa/etiqueta máis ciclos para este perfil.", + "average_power_curve": "Curva de potencia media", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciclos, ~{duration} m de media)", + "profile_option_short_fmt": "{name} ({count} ciclos, ~{duration} m)", + "deleted_cycles_from_profile": "Elimináronse {count} ciclos de {profile}.", + "not_enough_data_graph": "Non hai datos suficientes para xerar o gráfico.", + "no_profiles_found": "Non se atoparon perfís.", + "cycle_info_fmt": "Ciclo: {start}, {duration}m, Actual: {label}", + "top_candidates_header": "### Principais candidatos", + "tbl_profile": "Perfil", + "tbl_confidence": "Confianza", + "tbl_correlation": "Correlación", + "tbl_duration_match": "Coincidencia de duración", + "detected_profile": "Perfil detectado", + "estimated_duration": "Duración estimada", + "actual_duration": "Duración real", + "choose_action": "__Escolle unha acción a continuación:__", + "tbl_count": "Conta", + "tbl_avg": "Prom", + "tbl_min": "Min", + "tbl_max": "Máx", + "tbl_energy_avg": "Enerxía (media)", + "tbl_energy_total": "Enerxía (Total)", + "tbl_consistency": "Consistir.", + "tbl_last_run": "Última carreira", + "graph_legend_title": "Lenda gráfica", + "graph_legend_body": "A banda azul representa o rango de consumo de potencia mínimo e máximo observado. A liña mostra a curva de potencia media.", + "recording_preview": "Vista previa da gravación", + "trim_start": "Inicio de recorte", + "trim_end": "Recortar final", + "split_preview_title": "Vista previa dividida", + "split_preview_found_fmt": "Atopáronse {count} segmentos.", + "split_preview_confirm_fmt": "Fai clic en Confirmar para dividir este ciclo en {count} ciclos separados.", + "merge_preview_title": "Vista previa de combinación", + "merge_preview_joining_fmt": "Unindo {count} ciclos. As lagoas cubriranse con lecturas de 0W.", + "editor_delete_preview_title": "Vista previa de eliminación", + "editor_delete_preview_intro": "Os ciclos seleccionados eliminaranse permanentemente:", + "editor_delete_preview_confirm": "Fai clic en Confirmar para eliminar permanentemente estes rexistros de ciclos.", + "no_power_preview": "*Non hai datos de enerxía dispoñibles para a vista previa.*", + "profile_comparison": "Comparación de perfiles", + "actual_cycle_label": "Este ciclo (real)", + "storage_usage_header": "Uso de almacenamento", + "storage_file_size": "Tamaño do ficheiro", + "storage_cycles": "Ciclos", + "storage_profiles": "Perfís", + "storage_debug_traces": "Rastros de depuración", + "overview_suffix": "Visión xeral", + "phase_preview_for_profile": "Vista previa da fase para o perfil", + "current_ranges_header": "Rangos actuais", + "assign_phases_help": "Use as accións seguintes para engadir, editar, eliminar ou gardar intervalos.", + "profile_summary_header": "Resumo do perfil", + "recent_cycles_header": "Ciclos recentes", + "trim_cycle_preview_title": "Vista previa de recorte do ciclo", + "trim_cycle_preview_no_data": "Non hai datos de enerxía dispoñibles para este ciclo.", + "trim_cycle_preview_kept_suffix": "mantido", + "table_program": "Programa", + "table_when": "Cando", + "table_length": "Duración", + "table_match": "Coincidencia", + "table_profile": "Perfil", + "table_cycles": "Ciclos", + "table_avg_length": "Duración media", + "table_last_run": "Última carreira", + "table_avg_energy": "Enerxía media", + "table_detected_program": "Programa detectado", + "table_confidence": "Confianza", + "table_reported": "Informado", + "phase_builtin_suffix": "(Incorporado)", + "phase_none_available": "Non hai fases dispoñibles.", + "settings_deprecation_warning": "⚠️ **Tipo de dispositivo en desuso:** {device_type} está programado para a súa eliminación nunha versión futura. A canalización de coincidencias de WashData non produce resultados fiables para esta clase de dispositivo. A súa integración segue funcionando durante o período de desuso; para silenciar esta advertencia, cambia o **Tipo de dispositivo** a continuación a un dos tipos admitidos (lavadora, secadora, lavadora-secadora combinada, lavavajillas, freidora de aire, máquina de pan ou bomba) ou a **Outro (avanzado)** se o teu aparello non coincide con ningún dos tipos admitidos. **Outro (Avanzado)** envía valores predeterminados xenéricos intencionadamente que non están axustados para ningún dispositivo específico, polo que terás que configurar ti mesmo os limiares, os tempos de espera e os parámetros coincidentes; toda a súa configuración existente consérvase cando cambia.", + "phase_other_device_types": "Outros tipos de dispositivos:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir un ciclo (Buscar ocos)", + "merge": "Combinar ciclos (unir fragmentos)", + "delete": "Eliminar ciclo(s)" + } + }, + "split_mode": { + "options": { + "auto": "Detectar automaticamente os ocos de inactividade", + "manual": "Marcas temporais manuais" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Mantemento: reprocesamento e optimización de datos", + "clear_debug_data": "Borrar datos de depuración (liberar espazo)", + "wipe_history": "Borrar TODOS os datos deste dispositivo (irreversible)", + "export_import": "Exportar/Importar JSON con configuración (copiar/pegar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportar todos os datos", + "import": "Importar/Combinar datos" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etiqueta automática de ciclos antigos", + "select_cycle_to_label": "Ciclo específico da etiqueta", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Combinar/dividir Editor interactivo", + "trim_cycle": "Datos do ciclo de recorte" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Crear novo perfil", + "edit_profile": "Editar/Renomear o perfil", + "delete_profile": "Eliminar perfil", + "profile_stats": "Estatísticas do perfil", + "cleanup_profile": "Limpar o historial - Gráfica e elimina", + "assign_phases": "Asignar intervalos de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Crear unha nova fase", + "edit_custom_phase": "Fase de edición", + "delete_custom_phase": "Eliminar fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Engadir intervalo de fases", + "edit_range": "Editar intervalo de fases", + "delete_range": "Eliminar intervalo de fases", + "clear_ranges": "Borrar todos os intervalos", + "auto_detect_ranges": "Fases de detección automática", + "save_ranges": "Garda e Volve" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Estado de actualización", + "stop_recording": "Deter a gravación (gardar e procesar)", + "start_recording": "Iniciar unha nova gravación", + "process_recording": "Procesar a última gravación (recortar e gardar)", + "discard_recording": "Descartar a última gravación" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Crear novo perfil", + "existing_profile": "Engadir ao perfil existente", + "discard": "Descartar a gravación" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - Detección correcta", + "correct": "Correcto - Escolla Programa/Duración", + "ignore": "Ignorar - Ciclo falso positivo/ruidoso", + "delete": "Eliminar - Eliminar do historial" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nomear e aplicar as fases", + "cancel": "Volver sen cambios" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Establecer o punto de inicio", + "set_end": "Establecer punto final", + "reset": "Restablecer a duración completa", + "apply": "Aplicar Trim", + "cancel": "Cancelar" + } + }, + "device_type": { + "options": { + "washing_machine": "Lavadora", + "dryer": "Secador", + "washer_dryer": "Combo lavadora-secadora", + "dishwasher": "Lavalouza", + "coffee_machine": "Máquina de café (obsoleta)", + "ev": "Vehículo eléctrico (obsoleto)", + "air_fryer": "Freidora de aire", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "Máquina de Pan", + "pump": "Bomba / Bomba de sumidoiro", + "oven": "Forno (obsoleto)", + "other": "Outros (Avanzado)" + } + } + }, + "services": { + "label_cycle": { + "name": "Ciclo de etiquetas", + "description": "Asigna un perfil existente a un ciclo anterior ou elimina a etiqueta.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora para etiquetar." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a etiquetar." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O nome dun perfil existente (crear perfís no menú Xestionar perfís). Deixa en branco para eliminar a etiqueta." + } + } + }, + "create_profile": { + "name": "Crear perfil", + "description": "Crea un novo perfil (autónomo ou baseado nun ciclo de referencia).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "Nome para o novo perfil (por exemplo, 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "ID do ciclo de referencia", + "description": "ID de ciclo opcional para basear este perfil." + } + } + }, + "delete_profile": { + "name": "Eliminar perfil", + "description": "Elimina un perfil e, opcionalmente, elimina a etiqueta dos ciclos que o usan.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O perfil a eliminar." + }, + "unlabel_cycles": { + "name": "Desetiquetar ciclos", + "description": "Elimina a etiqueta do perfil dos ciclos usando este perfil." + } + } + }, + "auto_label_cycles": { + "name": "Etiqueta automática de ciclos antigos", + "description": "Etiqueta de forma retroactiva os ciclos sen etiquetas mediante a coincidencia de perfil.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora." + }, + "confidence_threshold": { + "name": "Limiar de confianza", + "description": "Confianza de coincidencia mínima (0,50-0,95) para aplicar etiquetas." + } + } + }, + "export_config": { + "name": "Exportar configuración", + "description": "Exporta os perfís, ciclos e configuracións deste dispositivo a un ficheiro JSON (por dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora para exportar." + }, + "path": { + "name": "Camiño", + "description": "Ruta de ficheiro absoluta opcional para escribir (por omisión /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importar configuración", + "description": "Importa perfís, ciclos e configuracións para este dispositivo desde un ficheiro de exportación JSON (a configuración pode sobrescribirse).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora no que importar." + }, + "path": { + "name": "Camiño", + "description": "Ruta absoluta ao ficheiro JSON de exportación para importar." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar comentarios sobre o ciclo", + "description": "Confirme ou corrixa un programa detectado automaticamente despois dun ciclo completo. Proporcione `entry_id` (avanzado) ou `device_id` (recomendado).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData (recomendado en lugar de entry_id)." + }, + "entry_id": { + "name": "ID de entrada", + "description": "O ID da entrada de configuración para o dispositivo (alternativa a device_id)." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo que se mostra na notificación/rexistros de comentarios." + }, + "user_confirmed": { + "name": "Confirmar programa detectado", + "description": "Establecer true se o programa detectado é correcto." + }, + "corrected_profile": { + "name": "Perfil corrixido", + "description": "Se non se confirma, proporcione o nome correcto do perfil/programa." + }, + "corrected_duration": { + "name": "Duración corrixida (segundos)", + "description": "Duración correxida opcional en segundos." + }, + "notes": { + "name": "Notas", + "description": "Notas opcionais sobre este ciclo." + } + } + }, + "record_start": { + "name": "Inicio do ciclo de gravación", + "description": "Comeza a gravar manualmente un ciclo limpo (omite toda a lóxica coincidente).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora para gravar." + } + } + }, + "record_stop": { + "name": "Parada do ciclo de gravación", + "description": "Deter a gravación manual.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da lavadora para deter a gravación." + } + } + }, + "trim_cycle": { + "name": "Ciclo de corte", + "description": "Recorta os datos de enerxía dun ciclo pasado a unha xanela de tempo específica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a recortar." + }, + "trim_start_s": { + "name": "Inicio de recorte (segundos)", + "description": "Mantén os datos desta compensación en segundos. Predeterminado 0." + }, + "trim_end_s": { + "name": "Fin de recorte (segundos)", + "description": "Mantén os datos ata esta compensación en segundos. Predeterminado = duración total." + } + } + }, + "pause_cycle": { + "name": "Ciclo de pausa", + "description": "Pausa o ciclo activo para un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData para pausar." + } + } + }, + "resume_cycle": { + "name": "Reanudar Ciclo", + "description": "Retomar un ciclo en pausa para un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData para retomar." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Correndo" + }, + "match_ambiguity": { + "name": "Ambigüidade de coincidencia" + } + }, + "sensor": { + "washer_state": { + "name": "Estado", + "state": { + "off": "Desactivado", + "idle": "Inactivo", + "starting": "Comezando", + "running": "Correndo", + "paused": "En pausa", + "user_paused": "En pausa polo usuario", + "ending": "Finalización", + "finished": "Rematou", + "anti_wrinkle": "Anti-engurras", + "delay_wait": "Agardando para comezar", + "interrupted": "Interrompido", + "force_stopped": "Forza detida", + "rinse": "Enxágüe", + "unknown": "Descoñecido", + "clean": "Limpo" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Tempo Restante" + }, + "total_duration": { + "name": "Duración total" + }, + "cycle_progress": { + "name": "Progreso" + }, + "current_power": { + "name": "Potencia actual" + }, + "elapsed_time": { + "name": "Tempo transcorrido" + }, + "debug_info": { + "name": "Información de depuración" + }, + "match_confidence": { + "name": "Confianza de coincidencia" + }, + "top_candidates": { + "name": "Principais candidatos", + "state": { + "none": "Ningún" + } + }, + "current_phase": { + "name": "Fase actual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Conta do perfil {profile_name}", + "unit_of_measurement": "ciclos" + }, + "suggestions": { + "name": "Configuración suxerida dispoñible" + }, + "pump_runs_today": { + "name": "Funcións da bomba (últimas 24 h)" + }, + "cycle_count": { + "name": "Conta de ciclos" + } + }, + "select": { + "program_select": { + "name": "Programa Ciclo", + "state": { + "auto_detect": "Detección automática" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forzar ciclo final" + }, + "pause_cycle": { + "name": "Ciclo de pausa" + }, + "resume_cycle": { + "name": "Reanudar Ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Requírese un device_id válido." + }, + "cycle_id_required": { + "message": "Requírese un cycle_id válido." + }, + "profile_name_required": { + "message": "Requírese un profile_name válido." + }, + "device_not_found": { + "message": "Non se atopou o dispositivo." + }, + "no_config_entry": { + "message": "Non se atopou ningunha entrada de configuración para o dispositivo." + }, + "integration_not_loaded": { + "message": "Non se cargou a integración para este dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Non se atopou o ciclo ou non ten datos de potencia." + }, + "trim_failed_empty_window": { + "message": "Fallou o recorte: non se atopou o ciclo, non hai datos de enerxía ou a xanela resultante está baleira." + }, + "trim_invalid_range": { + "message": "trim_end_s debe ser maior que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/gsw.json b/custom_components/ha_washdata/translations/gsw.json new file mode 100644 index 0000000..07fc0eb --- /dev/null +++ b/custom_components/ha_washdata/translations/gsw.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-Setup", + "description": "Konfigurieren Sie Ihre Waschmaschine oder ein anderes Gerät.\n\nLeistungssensor ist erforderlich.\n\n**Als nächstes werden Sie gefragt, ob Sie Ihr erstes Profil erstellen möchten.**", + "data": { + "name": "Gerätename", + "device_type": "Gerätetyp", + "power_sensor": "Leistungssensor", + "min_power": "Mindestleistungsschwelle (W)" + }, + "data_description": { + "name": "Ein benutzerfreundlicher Name für dieses Gerät (z. B. „Waschmaschine“, „Geschirrspüler“).", + "device_type": "Was für ein Gerät ist das? Hilft bei der individuellen Erkennung und Kennzeichnung.", + "power_sensor": "Die Sensoreinheit, die den Stromverbrauch (in Watt) Ihres Smart Plugs in Echtzeit meldet.", + "min_power": "Leistungswerte über diesem Schwellenwert (in Watt) zeigen an, dass das Gerät läuft. Beginnen Sie bei den meisten Geräten mit 2 W." + } + }, + "first_profile": { + "title": "Erstellen Sie das erste Profil", + "description": "Ein **Zyklus** ist ein vollständiger Durchlauf Ihres Geräts (z. B. eine Ladung Wäsche). Ein **Profil** gruppiert diese Programme nach Typ (z. B. „Baumwolle“, „Schnellwäsche“). Wenn Sie jetzt ein Profil erstellen, kann die Integrationsdauer sofort geschätzt werden.\n\nSie können dieses Profil in der Gerätesteuerung manuell auswählen, wenn es nicht automatisch erkannt wird.\n\n**Lassen Sie „Profilname“ leer, um diesen Schritt zu überspringen.**", + "data": { + "profile_name": "Profilname (optional)", + "manual_duration": "Geschätzte Dauer (Minuten)" + } + } + }, + "error": { + "cannot_connect": "Verbindung konnte nicht hergestellt werden", + "invalid_auth": "Ungültige Authentifizierung", + "unknown": "Unerwarteter Fehler" + }, + "abort": { + "already_configured": "Gerät ist bereits konfiguriert" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Überprüfen Sie die Lernrückmeldungen", + "settings": "Einstellungen", + "notifications": "Benachrichtigungen", + "manage_cycles": "Zyklen verwalten", + "manage_profiles": "Profile verwalten", + "manage_phase_catalog": "Phasenkatalog verwalten", + "record_cycle": "Aufnahmezyklus (manuell)", + "diagnostics": "Diagnose und Wartung" + } + }, + "apply_suggestions": { + "title": "Vorgeschlagene Werte kopieren", + "description": "Dadurch werden die aktuellen Vorschlagswerte in Ihre gespeicherten Einstellungen kopiert. Dies ist eine einmalige Aktion und ermöglicht keine automatischen Überschreibungen.\n\nVorgeschlagene Werte zum Kopieren:\n{suggested}", + "data": { + "confirm": "Bestätigen Sie den Kopiervorgang" + } + }, + "apply_suggestions_confirm": { + "title": "Überprüfen Sie die vorgeschlagenen Änderungen", + "description": "WashData hat **{pending_count}** vorgeschlagene(n) Wert(e) gefunden, die angewendet werden sollen.\n\nÄnderungen:\n{changes}\n\n✅ Aktivieren Sie das Kontrollkästchen unten und klicken Sie auf **Senden**, um diese Änderungen sofort zu übernehmen und zu speichern.\n❌ Lassen Sie es deaktiviert und klicken Sie auf **Senden**, um den Vorgang abzubrechen und zurückzukehren.", + "data": { + "confirm_apply_suggestions": "Übernehmen und speichern Sie diese Änderungen" + } + }, + "settings": { + "title": "Einstellungen", + "description": "{deprecation_warning}Optimieren Sie Erkennungsschwellenwerte, Lernen und erweitertes Verhalten. Die Standardeinstellungen funktionieren für die meisten Setups gut.\n\nAktuell verfügbare Einstellungsvorschläge: {suggestions_count}.\nFalls dieser Wert über 0 liegt, öffnen Sie die Erweiterten Einstellungen und nutzen Sie „Vorgeschlagene Werte anwenden”, um die Empfehlungen zu prüfen.", + "data": { + "apply_suggestions": "Vorgeschlagene Werte anwenden", + "device_type": "Gerätetyp", + "power_sensor": "Leistungssensoreinheit", + "min_power": "Mindestleistung (W)", + "off_delay": "Zyklusendeverzögerung (en)", + "notify_actions": "Benachrichtigungsaktionen", + "notify_people": "Verzögern Sie die Lieferung, bis diese Personen zu Hause sind", + "notify_only_when_home": "Benachrichtigungen verzögern, bis die ausgewählte Person zu Hause ist", + "notify_fire_events": "Automatisierungsereignisse auslösen", + "notify_start_services": "Zyklusstart – Benachrichtigungsziele", + "notify_finish_services": "Zyklusende – Benachrichtigungsziele", + "notify_live_services": "Live-Fortschritt – Benachrichtigungsziele", + "notify_before_end_minutes": "Benachrichtigung vor Fertigstellung (Minuten vor Ende)", + "notify_title": "Titel der Benachrichtigung", + "notify_icon": "Benachrichtigungssymbol", + "notify_start_message": "Starten Sie das Nachrichtenformat", + "notify_finish_message": "Beenden Sie das Nachrichtenformat", + "notify_pre_complete_message": "Nachrichtenformat vor Abschluss", + "show_advanced": "Erweiterte Einstellungen bearbeiten (nächster Schritt)" + }, + "data_description": { + "apply_suggestions": "Aktivieren Sie dieses Kontrollkästchen, um vom Lernalgorithmus vorgeschlagene Werte in das Formular zu kopieren. Vor dem Speichern überprüfen.\n\nEmpfohlen (vom Lernen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Wählen Sie den Gerätetyp (Waschmaschine, Trockner, Geschirrspüler, Kaffeemaschine, Elektroauto). Dieses Tag wird zur besseren Organisation bei jedem Zyklus gespeichert.", + "power_sensor": "Die Sensoreinheit meldet die Leistung in Echtzeit (in Watt). Sie können dies jederzeit ändern, ohne historische Daten zu verlieren – nützlich, wenn Sie einen Smart Plug austauschen.", + "min_power": "Leistungswerte über diesem Schwellenwert (in Watt) zeigen an, dass das Gerät läuft. Beginnen Sie bei den meisten Geräten mit 2 W.", + "off_delay": "Sekunden lang muss die geglättete Leistung unter dem Stoppschwellenwert bleiben, um den Abschluss zu markieren. Standard: 120s.", + "notify_actions": "Optionale Home Assistant-Aktionssequenz zur Ausführung für Benachrichtigungen (Skripte, Benachrichtigung, TTS usw.). Wenn festgelegt, werden Aktionen vor dem Fallback-Benachrichtigungsdienst ausgeführt.", + "notify_people": "Personenentitäten, die für das Anwesenheits-Gating verwendet werden. Wenn diese Option aktiviert ist, wird die Zustellung der Benachrichtigung verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_only_when_home": "Wenn diese Option aktiviert ist, werden Benachrichtigungen verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_fire_events": "Wenn aktiviert, werden Home Assistant-Ereignisse für Zyklusstart und -ende (ha_washdata_cycle_started und ha_washdata_cycle_ended) ausgelöst, um Automatisierungen voranzutreiben.", + "notify_start_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus beginnt. Leer lassen, um Startbenachrichtigungen zu überspringen.", + "notify_finish_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus endet oder kurz vor dem Abschluss steht. Lassen Sie das Feld leer, um Abschlussbenachrichtigungen zu überspringen.", + "notify_live_services": "Ein oder mehrere Benachrichtigungsdienste, um während der Ausführung des Zyklus Live-Fortschrittsaktualisierungen zu erhalten (nur mobile Begleit-App). Leer lassen, um Live-Updates zu überspringen.", + "notify_before_end_minutes": "Minuten vor dem voraussichtlichen Ende des Zyklus, um eine Benachrichtigung auszulösen. Zum Deaktivieren auf 0 setzen. Standard: 0.", + "notify_title": "Benutzerdefinierter Titel für Benachrichtigungen. Unterstützt den Platzhalter {device}.", + "notify_icon": "Symbol zur Verwendung für Benachrichtigungen (z. B. mdi:washing-machine). Lassen Sie das Feld leer, um ohne Symbol zu senden.", + "notify_start_message": "Nachricht wird gesendet, wenn ein Zyklus beginnt. Unterstützt den Platzhalter {device}.", + "notify_finish_message": "Nachricht wird gesendet, wenn ein Zyklus beendet ist. Unterstützt die Platzhalter {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Text der wiederkehrenden Live-Fortschrittsaktualisierungen während der Ausführung des Zyklus. Unterstützt die Platzhalter {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Benachrichtigungen", + "description": "Konfigurieren Sie Benachrichtigungskanäle, Vorlagen und optionale Live-Fortschrittsaktualisierungen für Mobilgeräte.", + "data": { + "notify_actions": "Benachrichtigungsaktionen", + "notify_people": "Verzögern Sie die Lieferung, bis diese Personen zu Hause sind", + "notify_only_when_home": "Benachrichtigungen verzögern, bis die ausgewählte Person zu Hause ist", + "notify_fire_events": "Automatisierungsereignisse auslösen", + "notify_start_services": "Zyklusstart – Benachrichtigungsziele", + "notify_finish_services": "Zyklusende – Benachrichtigungsziele", + "notify_live_services": "Live-Fortschritt – Benachrichtigungsziele", + "notify_before_end_minutes": "Benachrichtigung vor Fertigstellung (Minuten vor Ende)", + "notify_live_interval_seconds": "Live-Update-Intervall (Sekunden)", + "notify_live_overrun_percent": "Live-Update-Überschreitungszuschlag (%)", + "notify_live_chronometer": "Chronometer-Countdown-Timer", + "notify_title": "Titel der Benachrichtigung", + "notify_icon": "Benachrichtigungssymbol", + "notify_start_message": "Starten Sie das Nachrichtenformat", + "notify_finish_message": "Beenden Sie das Nachrichtenformat", + "notify_pre_complete_message": "Nachrichtenformat vor Abschluss", + "energy_price_entity": "Energiepreis – Entität (optional)", + "energy_price_static": "Energiepreis – Statischer Wert (optional)", + "go_back": "Ohne Speichern zurück", + "notify_reminder_message": "Format der Erinnerungsnachricht", + "notify_timeout_seconds": "Automatisch schließen nach (Sekunden)", + "notify_channel": "Benachrichtigungskanal (Status/Live/Erinnerung)", + "notify_finish_channel": "Fertiger Benachrichtigungskanal" + }, + "data_description": { + "go_back": "Aktivieren Sie dies und klicken Sie auf Senden, um alle obigen Änderungen zu verwerfen und zum Hauptmenü zurückzukehren.", + "notify_actions": "Optionale Home Assistant-Aktionssequenz zur Ausführung für Benachrichtigungen (Skripte, Benachrichtigung, TTS usw.). Wenn festgelegt, werden Aktionen vor dem Fallback-Benachrichtigungsdienst ausgeführt.", + "notify_people": "Personenentitäten, die für das Anwesenheits-Gating verwendet werden. Wenn diese Option aktiviert ist, wird die Zustellung der Benachrichtigung verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_only_when_home": "Wenn diese Option aktiviert ist, werden Benachrichtigungen verzögert, bis mindestens eine ausgewählte Person zu Hause ist.", + "notify_fire_events": "Wenn aktiviert, werden Home Assistant-Ereignisse für Zyklusstart und -ende (ha_washdata_cycle_started und ha_washdata_cycle_ended) ausgelöst, um Automatisierungen voranzutreiben.", + "notify_start_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus beginnt (z. B. notify.mobile_app_my_phone). Leer lassen, um Startbenachrichtigungen zu überspringen.", + "notify_finish_services": "Ein oder mehrere Benachrichtigungsdienste, die benachrichtigen, wenn ein Zyklus endet oder kurz vor dem Abschluss steht. Lassen Sie das Feld leer, um Abschlussbenachrichtigungen zu überspringen.", + "notify_live_services": "Ein oder mehrere Benachrichtigungsdienste, um während der Ausführung des Zyklus Live-Fortschrittsaktualisierungen zu erhalten (nur mobile Begleit-App). Leer lassen, um Live-Updates zu überspringen.", + "notify_before_end_minutes": "Minuten vor dem voraussichtlichen Ende des Zyklus, um eine Benachrichtigung auszulösen. Zum Deaktivieren auf 0 setzen. Standard: 0.", + "notify_live_interval_seconds": "Wie oft Fortschrittsaktualisierungen während der Ausführung gesendet werden. Niedrigere Werte führen zu einer schnelleren Reaktion, verbrauchen jedoch mehr Benachrichtigungen. Es wird ein Minimum von 30 Sekunden erzwungen – Werte unter 30 Sekunden werden automatisch auf 30 Sekunden aufgerundet.", + "notify_live_overrun_percent": "Sicherheitsmarge über der geschätzten Aktualisierungsanzahl für lange/überlaufende Zyklen (z. B. 20 % ermöglichen das 1,2-fache der geschätzten Aktualisierungen).", + "notify_live_chronometer": "Wenn diese Option aktiviert ist, beinhaltet jedes Live-Update einen Chronometer-Countdown bis zur voraussichtlichen Zielzeit. Der Benachrichtigungstimer läuft auf dem Gerät zwischen Aktualisierungen automatisch ab (nur Android-Begleit-App).", + "notify_title": "Benutzerdefinierter Titel für Benachrichtigungen. Unterstützt den Platzhalter {device}.", + "notify_icon": "Symbol zur Verwendung für Benachrichtigungen (z. B. mdi:washing-machine). Lassen Sie das Feld leer, um ohne Symbol zu senden.", + "notify_start_message": "Nachricht wird gesendet, wenn ein Zyklus beginnt. Unterstützt den Platzhalter {device}.", + "notify_finish_message": "Nachricht wird gesendet, wenn ein Zyklus beendet ist. Unterstützt die Platzhalter {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Wählen Sie eine numerische Entität aus, die den aktuellen Strompreis enthält (z. B. sensor.electricity_price, input_number.energy_rate). Wenn festgelegt, wird der Platzhalter {cost} aktiviert. Hat Vorrang vor dem statischen Wert, wenn beide konfiguriert sind.", + "energy_price_static": "Fester Strompreis pro kWh. Wenn festgelegt, wird der Platzhalter {cost} aktiviert. Wird ignoriert, wenn oben eine Entität konfiguriert ist. Fügen Sie Ihr Währungssymbol in die Nachrichtenvorlage ein, z. B. {cost} €.", + "notify_reminder_message": "Text der einmaligen Erinnerung, der die konfigurierte Anzahl von Minuten vor dem voraussichtlichen Ende gesendet hat. Unterscheidet sich von den oben genannten wiederkehrenden Live-Updates. Unterstützt die Platzhalter {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Benachrichtigungen werden nach dieser Anzahl von Sekunden automatisch verworfen (weitergeleitet als „Timeout“ der Begleit-App). Auf 0 setzen, um sie bis zur Entlassung beizubehalten. Standard: 0.", + "notify_channel": "Android Begleit-App-Benachrichtigungskanal für Status, Live-Fortschritt und Erinnerungsbenachrichtigungen. Der Ton und die Wichtigkeit eines Kanals werden bei der ersten Verwendung des Kanalnamens in der Begleit-App konfiguriert – WashData legt nur den Kanalnamen fest. Für die App-Standardeinstellung leer lassen.", + "notify_finish_channel": "Separater Android-Kanal für die fertige Warnung (wird auch von der Erinnerung und dem Wäsche-Warten-Meckern verwendet), damit er seinen eigenen Ton haben kann. Lassen Sie das Feld leer, um den Kanal oben wiederzuverwenden.", + "notify_pre_complete_message": "Text der wiederkehrenden Live-Fortschrittsaktualisierungen während der Ausführung des Zyklus. Unterstützt die Platzhalter {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Erweiterte Einstellungen", + "description": "Passen Sie Erkennungsschwellen, Lernen und erweitertes Verhalten an. Die Standardeinstellungen funktionieren für die meisten Setups gut.", + "sections": { + "suggestions_section": { + "name": "Vorgeschlagene Einstellungen", + "description": "{suggestions_count} gelernte Vorschläge verfügbar. Aktivieren Sie „Vorgeschlagene Werte anwenden“, um die vorgeschlagenen Änderungen vor dem Speichern zu prüfen.", + "data": { + "apply_suggestions": "Vorgeschlagene Werte anwenden" + }, + "data_description": { + "apply_suggestions": "Aktivieren Sie dieses Kontrollkästchen, um einen Überprüfungsschritt für vorgeschlagene Werte vom Lernalgorithmus zu öffnen. Es wird nichts automatisch gespeichert.\n\nEmpfohlen (vom Lernen):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Erkennung & Leistungsschwellen", + "description": "Wann das Gerät als EIN oder AUS gilt, Energieschwellen und Zeitsteuerung für Zustandsübergänge.", + "data": { + "start_duration_threshold": "Start-Entprellungsdauer (s)", + "start_energy_threshold": "Startenergietor (Wh)", + "completion_min_seconds": "Mindestlaufzeit für den Abschluss (Sekunden)", + "start_threshold_w": "Startschwelle (W)", + "stop_threshold_w": "Stoppschwelle (W)", + "end_energy_threshold": "Endenergietor (Wh)", + "running_dead_zone": "Toter Bereich beim Start (Sekunden)", + "end_repeat_count": "Anzahl der Wiederholungen beenden", + "min_off_gap": "Min. Abstand zwischen Zyklen (s)", + "sampling_interval": "Abtastintervall (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorieren Sie kurze Leistungsspitzen, die kürzer als diese Dauer (Sekunden) sind. Filtert Startspitzen (z. B. 60 W für 2 Sekunden) heraus, bevor der eigentliche Zyklus beginnt. Standard: 5s.", + "start_energy_threshold": "Um bestätigt zu werden, muss der Zyklus während der START-Phase mindestens so viel Energie (Wh) akkumulieren. Standard: 0,005 Wh.", + "completion_min_seconds": "Zyklen, die kürzer sind, werden als „unterbrochen“ markiert, auch wenn sie natürlich enden. Standard: 600s.", + "start_threshold_w": "Die Leistung muss ÜBER diesen Schwellenwert ansteigen, um einen Zyklus zu starten. Höher einstellen als Ihre Standby-Leistung. Standard: min_power + 1 W.", + "stop_threshold_w": "Die Leistung muss UNTER diesen Schwellenwert fallen, um einen Zyklus zu BEENDEN. Stellen Sie den Wert knapp über Null ein, um zu vermeiden, dass Rauschen falsche Enden auslöst. Standard: 0,6 * min_power.", + "end_energy_threshold": "Der Zyklus endet nur, wenn die Energie im letzten Ausschaltverzögerungsfenster unter diesem Wert (Wh) liegt. Verhindert falsche Enden, wenn die Leistung nahe Null schwankt. Standard: 0,05 Wh.", + "running_dead_zone": "Ignorieren Sie nach dem Start des Zyklus Stromeinbrüche für diese Anzahl von Sekunden, um eine falsche Enderkennung während der anfänglichen Hochlaufphase zu verhindern. Zum Deaktivieren auf 0 setzen. Standard: 0s.", + "end_repeat_count": "Gibt an, wie oft die Endbedingung (Leistung unter dem Schwellenwert für Ausschaltverzögerung) nacheinander erfüllt sein muss, bevor der Zyklus beendet wird. Höhere Werte verhindern falsches Ende während Pausen. Standard: 1.", + "min_off_gap": "Wenn ein Zyklus innerhalb dieser Anzahl von Sekunden nach dem Ende des vorherigen beginnt, werden sie zu einem einzigen Zyklus zusammengeführt.", + "sampling_interval": "Mindestabtastintervall in Sekunden. Aktualisierungen, die schneller als diese Rate sind, werden ignoriert. Standard: 30 Sek. (2 Sek. für Waschmaschinen, Waschtrockner und Geschirrspüler)." + } + }, + "matching_section": { + "name": "Profilabgleich & Lernen", + "description": "Wie aggressiv WashData laufende Zyklen mit gelernten Profilen abgleicht und wann um Rückmeldung gebeten wird.", + "data": { + "profile_match_min_duration_ratio": "Verhältnis der Mindestdauer der Profilübereinstimmung (0,1–1,0)", + "profile_match_interval": "Profil-Match-Intervall (Sekunden)", + "profile_match_threshold": "Schwellenwert für Profilübereinstimmung", + "profile_unmatch_threshold": "Schwellenwert für nicht übereinstimmendes Profil", + "auto_label_confidence": "Auto-Label-Konfidenz (0-1)", + "learning_confidence": "Vertrauen der Feedback-Anfrage (0-1)", + "suppress_feedback_notifications": "Feedback-Benachrichtigungen unterdrücken", + "duration_tolerance": "Dauertoleranz (0–0,5)", + "smoothing_window": "Glättungsfenster (Beispiele)", + "profile_duration_tolerance": "Toleranz der Profilübereinstimmungsdauer (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Mindestdauerverhältnis für den Profilabgleich. Damit der Laufzyklus übereinstimmt, muss er mindestens diesen Bruchteil der Profildauer betragen. Niedriger = frühere Übereinstimmung. Standard: 0,50 (50 %).", + "profile_match_interval": "Wie oft (in Sekunden) der Profilabgleich während eines Zyklus versucht werden soll. Niedrigere Werte ermöglichen eine schnellere Erkennung, beanspruchen jedoch mehr CPU. Standard: 300s (5 Minuten).", + "profile_match_threshold": "Mindest-DTW-Ähnlichkeitswert (0,0–1,0), der erforderlich ist, um ein Profil als Übereinstimmung zu betrachten. Höher = strengere Übereinstimmung, weniger Fehlalarme. Standard: 0,4.", + "profile_unmatch_threshold": "DTW-Ähnlichkeitswert (0,0–1,0), unterhalb dessen ein zuvor übereinstimmendes Profil abgelehnt wird. Sollte niedriger als der Übereinstimmungsschwellenwert sein, um ein Flackern zu verhindern. Standard: 0,35.", + "auto_label_confidence": "Beschriften Sie Zyklen mit oder über diesem Konfidenzniveau nach Abschluss automatisch (0,0–1,0).", + "learning_confidence": "Mindestkonfidenz für die Anforderung einer Benutzerverifizierung über ein Ereignis (0,0–1,0).", + "suppress_feedback_notifications": "Wenn diese Option aktiviert ist, verfolgt WashData weiterhin intern Feedback und fordert es an, zeigt jedoch keine dauerhaften Benachrichtigungen an, in denen Sie zur Bestätigung von Zyklen aufgefordert werden. Nützlich, wenn Zyklen immer korrekt erkannt werden und Sie die Eingabeaufforderungen als störend empfinden.", + "duration_tolerance": "Toleranz für Schätzungen der verbleibenden Zeit während eines Laufs (0,0–0,5 entspricht 0–50 %).", + "smoothing_window": "Anzahl der letzten Leistungsmesswerte, die für die Glättung des gleitenden Durchschnitts verwendet werden.", + "profile_duration_tolerance": "Vom Profilabgleich verwendete Toleranz für die Gesamtdauervarianz (0,0–0,5). Standardverhalten: ±25 %." + } + }, + "timing_section": { + "name": "Zeitsteuerung, Wartung & Debug", + "description": "Watchdog, Fortschrittsrücksetzung, automatische Wartung und Sichtbarkeit von Debug-Entitäten.", + "data": { + "watchdog_interval": "Watchdog-Intervall (Sekunden)", + "no_update_active_timeout": "Zeitüberschreitung bei fehlender Aktualisierung (s)", + "progress_reset_delay": "Verzögerung beim Zurücksetzen des Fortschritts (Sekunden)", + "auto_maintenance": "Aktivieren Sie die automatische Wartung", + "expose_debug_entities": "Debug-Entitäten verfügbar machen", + "save_debug_traces": "Speichern Sie Debug-Traces" + }, + "data_description": { + "watchdog_interval": "Sekunden zwischen Watchdog-Prüfungen während der Ausführung. Standard: 5s. WARNUNG: Stellen Sie sicher, dass dieser Wert HÖHER ist als das Aktualisierungsintervall Ihres Sensors, um falsche Stopps zu vermeiden (z. B. wenn der Sensor alle 60 Sekunden aktualisiert wird, verwenden Sie 65 Sekunden+).", + "no_update_active_timeout": "Für Publish-on-Change-Sensoren: Erzwingen Sie das Beenden eines ACTIVE-Zyklus nur, wenn für diese viele Sekunden keine Aktualisierungen eintreffen. Beim Abschluss mit geringem Stromverbrauch wird weiterhin die Ausschaltverzögerung verwendet.", + "progress_reset_delay": "Nachdem ein Zyklus abgeschlossen ist (100 %), warten Sie so viele Sekunden im Leerlauf, bevor Sie den Fortschritt auf 0 % zurücksetzen. Standard: 300s.", + "auto_maintenance": "Aktivieren Sie die automatische Wartung (Beispiele reparieren, Fragmente zusammenführen).", + "expose_debug_entities": "Zeigen Sie erweiterte Sensoren (Konfidenz, Phase, Mehrdeutigkeit) zum Debuggen an.", + "save_debug_traces": "Speichern Sie detaillierte Ranking- und Stromverfolgungsdaten im Verlauf (erhöht die Speichernutzung)." + } + }, + "anti_wrinkle_section": { + "name": "Knitterschutz (Trockner)", + "description": "Ignorieren Sie Trommeldrehungen nach Zyklusende, um Geisterzyklen zu verhindern. Nur Trockner / Waschtrockner.", + "data": { + "anti_wrinkle_enabled": "Anti-Falten-Schutz (nur Trockner)", + "anti_wrinkle_max_power": "Anti-Falten-Maximalleistung (W)", + "anti_wrinkle_max_duration": "Maximale Anti-Falten-Dauer (s)", + "anti_wrinkle_exit_power": "Anti-Falten-Ausgangsleistung (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorieren Sie kurze Trommelumdrehungen mit geringer Leistung nach Programmende, um Geisterzyklen zu vermeiden (nur Trockner/Waschtrockner).", + "anti_wrinkle_max_power": "Ausbrüche bei oder unter dieser Leistung werden als Anti-Falten-Rotationen und nicht als neue Zyklen behandelt. Gilt nur, wenn Anti-Falten-Schutz aktiviert ist.", + "anti_wrinkle_max_duration": "Maximale Burst-Länge zur Behandlung als Anti-Falten-Rotation. Gilt nur, wenn Anti-Falten-Schutz aktiviert ist.", + "anti_wrinkle_exit_power": "Wenn die Leistung unter diesen Wert fällt, beenden Sie die Anti-Falten-Funktion sofort (echt aus). Gilt nur, wenn Anti-Falten-Schutz aktiviert ist." + } + }, + "delay_start_section": { + "name": "Erkennung verzögerter Starts", + "description": "Behandeln Sie anhaltenden Standby mit niedriger Leistung als „Wartet auf Start“, bis der Zyklus tatsächlich beginnt.", + "data": { + "delay_start_detect_enabled": "Aktivieren Sie die verzögerte Starterkennung", + "delay_confirm_seconds": "Verzögerter Start: Bestätigungszeit für Standby (s)", + "delay_timeout_hours": "Verzögerter Start: Maximale Wartezeit (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Wenn diese Option aktiviert ist, wird eine kurze Leistungsspitze bei geringem Stromverbrauch, gefolgt von einer anhaltenden Standby-Leistung, als verzögerter Start erkannt. Der Statussensor zeigt während der Verzögerung „Warten auf Start“ an. Es werden weder Programmzeit noch Fortschritt verfolgt, bis der Zyklus tatsächlich beginnt. Erfordert, dass start_threshold_w über der Drain-Spike-Leistung eingestellt wird.", + "delay_confirm_seconds": "Die Leistung muss so viele Sekunden zwischen Stoppschwelle (W) und Startschwelle (W) bleiben, bevor WashData in den Zustand „Wartet auf Start“ wechselt. Filtert kurze Spitzen bei der Menünavigation heraus. Standard: 60 s.", + "delay_timeout_hours": "Sicherheits-Timeout: Wenn sich die Maschine nach dieser Anzahl von Stunden immer noch im Zustand „Warten auf Start“ befindet, wird WashData auf „Aus“ zurückgesetzt. Standard: 8 Stunden." + } + }, + "external_triggers_section": { + "name": "Externe Auslöser, Tür & Pause", + "description": "Optionaler externer Endauslöser-Sensor, Türsensor für Pause/sauber-Erkennung und Schalter zum Abschalten der Stromversorgung bei Pause.", + "data": { + "external_end_trigger_enabled": "Aktivieren Sie den externen Zyklusende-Trigger", + "external_end_trigger": "Externer Zyklusendesensor", + "external_end_trigger_inverted": "Triggerlogik umkehren (Trigger auf AUS)", + "door_sensor_entity": "Türsensor", + "pause_cuts_power": "Unterbrechen Sie die Stromversorgung, wenn Sie pausieren", + "switch_entity": "Entität wechseln (für Stromausfall pausieren)", + "notify_unload_delay_minutes": "Verzögerung der Wäsche-Warten-Benachrichtigung (Min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktivieren Sie die Überwachung eines externen Binärsensors, um den Abschluss des Zyklus auszulösen.", + "external_end_trigger": "Wählen Sie eine binäre Sensoreinheit aus. Wenn dieser Sensor auslöst, endet der aktuelle Zyklus mit dem Status „abgeschlossen“.", + "external_end_trigger_inverted": "Standardmäßig wird der Auslöser ausgelöst, wenn der Sensor eingeschaltet wird. Aktivieren Sie dieses Kontrollkästchen, um stattdessen auszulösen, wenn der Sensor ausgeschaltet wird.", + "door_sensor_entity": "Optionaler binärer Sensor für die Maschinentür (ein = offen, aus = geschlossen). Wenn sich die Tür während eines aktiven Zyklus öffnet, bestätigt WashData, dass der Zyklus absichtlich angehalten wurde. Wenn der Zyklus beendet ist und die Tür noch geschlossen ist, wechselt der Zustand zu „Reinigen“, bis die Tür geöffnet wird. Hinweis: Durch das Schließen der Tür wird ein angehaltener Zyklus NICHT automatisch fortgesetzt. Die Wiederaufnahme muss manuell über die Schaltfläche „Zyklus fortsetzen“ oder den entsprechenden Dienst ausgelöst werden.", + "pause_cuts_power": "Wenn Sie über die Schaltfläche „Zyklus anhalten“ oder den Dienst anhalten, schalten Sie auch die konfigurierte Schaltereinheit aus. Wird nur wirksam, wenn für dieses Gerät eine Switch-Entität konfiguriert ist.", + "switch_entity": "Die Schalterentität, die beim Anhalten oder Fortsetzen umgeschaltet wird. Erforderlich, wenn „Stromversorgung bei Pause abschalten“ aktiviert ist.", + "notify_unload_delay_minutes": "Senden Sie eine Benachrichtigung über den Abschlussbenachrichtigungskanal, nachdem der Zyklus beendet ist und die Tür so viele Minuten lang nicht geöffnet wurde (erfordert Türsensor). Zum Deaktivieren auf 0 setzen. Standard: 60 Min." + } + }, + "pump_section": { + "name": "Pumpenüberwachung", + "description": "Nur für Gerätetyp Pumpe. Löst ein Ereignis aus, wenn ein Pumpenzyklus länger als diese Dauer läuft.", + "data": { + "pump_stuck_duration": "Schwellenwert(e) für Warnung „Pumpe blockiert“ (nur Pumpe)" + }, + "data_description": { + "pump_stuck_duration": "Wenn nach dieser Anzahl von Sekunden immer noch ein Pumpenzyklus läuft, wird einmalig ein ha_washdata_pump_stuck-Ereignis ausgelöst. Verwenden Sie dies, um einen blockierten Motor zu erkennen (die typische Laufzeit der Sumpfpumpe beträgt weniger als 60 s). Standard: 1800 s (30 min). Nur Pumpengerätetyp." + } + }, + "device_link_section": { + "name": "Geräteverbindung", + "description": "Verknüpfen Sie dieses WashData-Gerät optional mit einem vorhandenen Home Assistant-Gerät (z. B. dem Smart Plug oder dem Gerät selbst). Das WashData-Gerät wird dann als „Verbunden über“ angezeigt. Lassen Sie das Feld leer, um WashData als eigenständiges Gerät beizubehalten.", + "data": { + "linked_device": "Verknüpftes Gerät" + }, + "data_description": { + "linked_device": "Wählen Sie das Gerät aus, mit dem WashData verbunden werden soll. Dadurch wird in der Geräteregistrierung ein Verweis „Verbunden über“ hinzugefügt. WashData behält seine eigene Gerätekarte und Entitäten. Löschen Sie die Auswahl, um den Link zu entfernen." + } + } + } + }, + "diagnostics": { + "title": "Diagnose und Wartung", + "description": "Führen Sie Wartungsaktionen durch, z. B. das Zusammenführen fragmentierter Zyklen oder das Migrieren gespeicherter Daten.\n\n**Speichernutzung**\n\n- Dateigröße: {file_size_kb} KB\n- Zyklen: {cycle_count}\n- Profile: {profile_count}\n- Debug-Traces: {debug_count}", + "menu_options": { + "reprocess_history": "Wartung: Daten erneut verarbeiten und optimieren", + "clear_debug_data": "Debug-Daten löschen (Speicherplatz freigeben)", + "wipe_history": "ALLE Daten für dieses Gerät löschen (unwiderruflich)", + "export_import": "JSON mit Einstellungen exportieren/importieren (Kopieren/Einfügen)", + "menu_back": "← Zurück" + } + }, + "clear_debug_data": { + "title": "Debug-Daten löschen", + "description": "Sind Sie sicher, dass Sie alle gespeicherten Debug-Traces löschen möchten? Dadurch wird Speicherplatz frei, aber detaillierte Ranking-Informationen aus vergangenen Zyklen werden entfernt." + }, + "manage_cycles": { + "title": "Zyklen verwalten", + "description": "Letzte Zyklen:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Alte Zyklen automatisch kennzeichnen", + "select_cycle_to_label": "Beschriften Sie den spezifischen Zyklus", + "select_cycle_to_delete": "Zyklus löschen", + "interactive_editor": "Interaktiver Editor zum Zusammenführen/Teilen", + "trim_cycle_select": "Trimmzyklusdaten", + "menu_back": "← Zurück" + } + }, + "manage_cycles_empty": { + "title": "Zyklen verwalten", + "description": "Noch keine Zyklen aufgezeichnet.", + "menu_options": { + "auto_label_cycles": "Alte Zyklen automatisch kennzeichnen", + "menu_back": "← Zurück" + } + }, + "manage_profiles": { + "title": "Profile verwalten", + "description": "Profilzusammenfassung:\n{profile_summary}", + "menu_options": { + "create_profile": "Neues Profil erstellen", + "edit_profile": "Profil bearbeiten/umbenennen", + "delete_profile_select": "Profil löschen", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Verlauf bereinigen – grafisch darstellen und löschen", + "assign_profile_phases_select": "Phasenbereiche zuweisen", + "menu_back": "← Zurück" + } + }, + "manage_profiles_empty": { + "title": "Profile verwalten", + "description": "Noch keine Profile erstellt.", + "menu_options": { + "create_profile": "Neues Profil erstellen", + "menu_back": "← Zurück" + } + }, + "manage_phase_catalog": { + "title": "Phasenkatalog verwalten", + "description": "Phasenkatalog (Standardeinstellungen für dieses Gerät + benutzerdefinierte Phasen):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Neue Phase erstellen", + "phase_catalog_edit_select": "Phase bearbeiten", + "phase_catalog_delete": "Phase löschen", + "menu_back": "← Zurück" + } + }, + "phase_catalog_create": { + "title": "Erstellen Sie eine benutzerdefinierte Phase", + "description": "Erstellen Sie einen benutzerdefinierten Phasennamen und eine Beschreibung und wählen Sie aus, für welchen Gerätetyp sie gelten sollen.", + "data": { + "target_device_type": "Zielgerätetyp", + "phase_name": "Phasenname", + "phase_description": "Phasenbeschreibung" + } + }, + "phase_catalog_edit_select": { + "title": "Benutzerdefinierte Phase bearbeiten", + "description": "Wählen Sie eine benutzerdefinierte Phase zum Bearbeiten aus.", + "data": { + "phase_name": "Benutzerdefinierte Phase" + } + }, + "phase_catalog_edit": { + "title": "Benutzerdefinierte Phase bearbeiten", + "description": "Bearbeitungsphase: {phase_name}", + "data": { + "phase_name": "Phasenname", + "phase_description": "Phasenbeschreibung" + } + }, + "phase_catalog_delete": { + "title": "Benutzerdefinierte Phase löschen", + "description": "Wählen Sie eine benutzerdefinierte Phase zum Löschen aus. Alle zugewiesenen Bereiche, die diese Phase verwenden, werden entfernt.", + "data": { + "phase_name": "Benutzerdefinierte Phase" + } + }, + "assign_profile_phases": { + "title": "Phasenbereiche zuweisen", + "description": "Phasenvorschau für Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuelle Bereiche:**\n{current_ranges}\n\nVerwenden Sie die folgenden Aktionen, um Bereiche hinzuzufügen, zu bearbeiten, zu löschen oder zu speichern.", + "menu_options": { + "assign_profile_phases_add": "Phasenbereich hinzufügen", + "assign_profile_phases_edit_select": "Phasenbereich bearbeiten", + "assign_profile_phases_delete": "Phasenbereich löschen", + "phase_ranges_clear": "Alle Bereiche löschen", + "assign_profile_phases_auto_detect": "Phasen automatisch erkennen", + "phase_ranges_save": "Speichern und zurückgeben", + "menu_back": "← Zurück" + } + }, + "assign_profile_phases_add": { + "title": "Phasenbereich hinzufügen", + "description": "Fügen Sie dem aktuellen Entwurf einen Phasenbereich hinzu.", + "data": { + "phase_name": "Phase", + "start_min": "Startminute", + "end_min": "Endminute" + } + }, + "assign_profile_phases_edit_select": { + "title": "Phasenbereich bearbeiten", + "description": "Wählen Sie einen Bereich zum Bearbeiten aus.", + "data": { + "range_index": "Phasenbereich" + } + }, + "assign_profile_phases_edit": { + "title": "Phasenbereich bearbeiten", + "description": "Aktualisieren Sie den ausgewählten Phasenbereich.", + "data": { + "phase_name": "Phase", + "start_min": "Startminute", + "end_min": "Endminute" + } + }, + "assign_profile_phases_delete": { + "title": "Phasenbereich löschen", + "description": "Wählen Sie einen Bereich aus, der aus dem Entwurf entfernt werden soll.", + "data": { + "range_index": "Phasenbereich" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatisch erkannte Phasen", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} Phase(n) automatisch erkannt.** Wählen Sie unten eine Aktion aus.", + "data": { + "action": "Aktion" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Benennen Sie die erkannten Phasen", + "description": "Profil: **{profile_name}**\n\n**{detected_count} Phase(n) erkannt:**\n{ranges_summary}\n\nWeisen Sie jeder Phase einen Namen zu." + }, + "assign_profile_phases_select": { + "title": "Phasenbereiche zuweisen", + "description": "Wählen Sie ein Profil aus, um Phasenbereiche zu konfigurieren.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profilstatistik", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Neues Profil erstellen", + "description": "Erstellen Sie manuell oder aus einem vergangenen Zyklus ein neues Profil.\n\nBeispiele für Profilnamen: „Feinwäsche“, „Heavy Duty“, „Schnellwäsche“", + "data": { + "profile_name": "Profilname", + "reference_cycle": "Referenzzyklus (optional)", + "manual_duration": "Manuelle Dauer (Minuten)" + } + }, + "edit_profile": { + "title": "Profil bearbeiten", + "description": "Wählen Sie ein Profil zum Umbenennen aus", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Profildetails bearbeiten", + "description": "Aktuelles Profil: {current_name}", + "data": { + "new_name": "Profilname", + "manual_duration": "Manuelle Dauer (Minuten)" + } + }, + "delete_profile_select": { + "title": "Profil löschen", + "description": "Wählen Sie ein Profil zum Löschen aus", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Bestätigen Sie „Profil löschen“.", + "description": "⚠️ Dadurch wird das Profil dauerhaft gelöscht: {profile_name}", + "data": { + "unlabel_cycles": "Entfernen Sie mit diesem Profil Etiketten von Zyklen" + } + }, + "auto_label_cycles": { + "title": "Alte Zyklen automatisch kennzeichnen", + "description": "Insgesamt {total_count} Zyklen gefunden. Profile: {profiles}", + "data": { + "confidence_threshold": "Minimales Vertrauen (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Zyklus zum Beschriften auswählen", + "description": "✓ = abgeschlossen, ⚠ = fortgesetzt, ✗ = unterbrochen", + "data": { + "cycle_id": "Zyklus" + } + }, + "select_cycle_to_delete": { + "title": "Zyklus löschen", + "description": "⚠️ Dadurch wird der ausgewählte Zyklus dauerhaft gelöscht", + "data": { + "cycle_id": "Zu löschender Zyklus" + } + }, + "label_cycle": { + "title": "Zyklus beschriften", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Neuer Profilname (falls erstellt)" + } + }, + "post_process": { + "title": "Prozessverlauf (Zusammenführen/Aufteilen)", + "description": "Bereinigen Sie den Zyklusverlauf, indem Sie fragmentierte Läufe zusammenführen und falsch zusammengeführte Zyklen innerhalb eines Zeitfensters aufteilen. Geben Sie die Anzahl der Stunden ein, in denen Sie zurückblicken möchten (verwenden Sie 999999 für alle).", + "data": { + "time_range": "Lookback-Fenster (Stunden)", + "gap_seconds": "Zusammenführungs-/Aufteilungslücke (Sekunden)" + }, + "data_description": { + "time_range": "Anzahl der Stunden, die nach fragmentierten Zyklen zum Zusammenführen gescannt werden sollen. Verwenden Sie 999999, um alle historischen Daten zu verarbeiten.", + "gap_seconds": "Schwellenwert für die Entscheidung über Aufteilung oder Zusammenführung. Lücken, die kürzer sind, werden zusammengeführt. Längere Lücken werden geteilt. Verringern Sie diesen Wert, um eine Teilung eines Zyklus zu erzwingen, der falsch zusammengeführt wurde." + } + }, + "cleanup_profile": { + "title": "Verlauf bereinigen – Profil auswählen", + "description": "Wählen Sie ein Profil zur Visualisierung aus. Dadurch wird ein Diagramm erstellt, das alle vergangenen Zyklen für dieses Profil zeigt, um Ausreißer zu identifizieren.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Verlauf bereinigen – grafisch darstellen und löschen", + "description": "![Grafik]({graph_url})\n\n**Visualisierung von {profile_name}**\n\nDie Grafik zeigt einzelne Zyklen als farbige Linien. Identifizieren Sie Ausreißer (Linien, die weit von der Gruppe entfernt sind) und gleichen Sie ihre Farbe in der Liste unten ab, um sie zu löschen.\n\n**Zyklen auswählen, die DAUERHAFT gelöscht werden sollen:**", + "data": { + "cycles_to_delete": "Zu löschende Zyklen (passende Farben prüfen)" + } + }, + "interactive_editor": { + "title": "Interaktiver Editor zum Zusammenführen/Teilen", + "description": "Wählen Sie eine manuelle Aktion aus, die Sie an Ihrem Zyklusverlauf durchführen möchten.", + "menu_options": { + "editor_split": "Einen Zyklus aufteilen (Lücken finden)", + "editor_merge": "Zyklen zusammenführen (Fragmente verbinden)", + "editor_delete": "Zyklus(e) löschen", + "menu_back": "← Zurück" + } + }, + "editor_select": { + "title": "Wählen Sie Zyklen", + "description": "Wählen Sie die zu verarbeitenden Zyklen aus.\n\n{info_text}", + "data": { + "selected_cycles": "Zyklen" + } + }, + "editor_configure": { + "title": "Konfigurieren und anwenden", + "description": "{preview_md}", + "data": { + "confirm_action": "Aktion", + "merged_profile": "Resultierendes Profil", + "new_profile_name": "Neuer Profilname", + "confirm_commit": "Ja, ich bestätige diese Aktion", + "segment_0_profile": "Segment 1-Profil", + "segment_1_profile": "Segment 2-Profil", + "segment_2_profile": "Segment 3-Profil", + "segment_3_profile": "Segment 4-Profil", + "segment_4_profile": "Segment 5-Profil", + "segment_5_profile": "Segment 6-Profil", + "segment_6_profile": "Segment 7-Profil", + "segment_7_profile": "Segment 8-Profil", + "segment_8_profile": "Segment 9-Profil", + "segment_9_profile": "Segment 10-Profil" + } + }, + "editor_split_params": { + "title": "Split-Konfiguration", + "description": "Passen Sie die Empfindlichkeit für die Aufteilung dieses Zyklus an. Jede längere Leerlauflücke führt zu einer Spaltung.", + "data": { + "split_mode": "Split-Methode" + } + }, + "editor_split_auto_params": { + "title": "Automatische Aufteilung konfigurieren", + "description": "Passen Sie die Empfindlichkeit für das Aufteilen dieses Zyklus an. Jede Leerlaufpause, die länger als dieser Wert ist, führt zu einer Aufteilung.", + "data": { + "min_gap_seconds": "Schwellwert für Aufteilungslücke (Sekunden)" + } + }, + "editor_split_manual_params": { + "title": "Manuelle Zeitstempel für die Aufteilung", + "description": "{preview_md}Zyklusfenster: **{cycle_start_wallclock} → {cycle_end_wallclock}** am {cycle_date}.\n\nGeben Sie einen oder mehrere Zeitstempel für die Aufteilung ein (`HH:MM` oder `HH:MM:SS`), einen pro Zeile. Der Zyklus wird an jedem Zeitstempel geteilt — N Zeitstempel erzeugen N+1 Segmente.", + "data": { + "split_timestamps": "Zeitstempel zum Teilen" + } + }, + "reprocess_history": { + "title": "Wartung: Daten erneut verarbeiten und optimieren", + "description": "Führt eine gründliche Bereinigung historischer Daten durch:\n1. Reduziert Nullleistungswerte (Stille).\n2. Korrigiert Zykluszeitpunkt/-dauer.\n3. Berechnet alle statistischen Modelle (Hüllkurven) neu.\n\n**Führen Sie dies aus, um Datenprobleme zu beheben oder neue Algorithmen anzuwenden.**" + }, + "wipe_history": { + "title": "Verlauf löschen (nur zum Testen)", + "description": "⚠️ Dadurch werden ALLE gespeicherten Zyklen und Profile für dieses Gerät dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden!" + }, + "export_import": { + "title": "JSON exportieren/importieren", + "description": "Kopieren/Einfügen von Zyklen, Profilen und Einstellungen für dieses Gerät. Exportieren Sie unten oder fügen Sie JSON zum Importieren ein.", + "data": { + "mode": "Aktion", + "json_payload": "Komplette Konfiguration JSON" + }, + "data_description": { + "mode": "Wählen Sie „Exportieren“, um aktuelle Daten und Einstellungen zu kopieren, oder „Importieren“, um exportierte Daten von einem anderen WashData-Gerät einzufügen.", + "json_payload": "Für den Export: Kopieren Sie diesen JSON (einschließlich Zyklen, Profilen und allen fein abgestimmten Einstellungen). Für den Import: JSON einfügen, das aus WashData exportiert wurde." + } + }, + "record_cycle": { + "title": "Aufnahmezyklus", + "description": "Zeichnen Sie einen Zyklus manuell auf, um ein sauberes Profil ohne Störungen zu erstellen.\n\nStatus: **{status}**\nDauer: {duration}s\nBeispiele: {samples}", + "menu_options": { + "record_refresh": "Status aktualisieren", + "record_stop": "Aufnahme beenden (Speichern und verarbeiten)", + "record_start": "Neue Aufnahme starten", + "record_process": "Letzte Aufnahme verarbeiten (Zuschneiden und Speichern)", + "record_discard": "Letzte Aufnahme verwerfen", + "menu_back": "← Zurück" + } + }, + "record_process": { + "title": "Prozessaufzeichnung", + "description": "![Grafik]({graph_url})\n\n**Das Diagramm zeigt die Rohaufzeichnung.** Blau = Behalten, Rot = Vorgeschlagener Zuschnitt.\n*Das Diagramm ist statisch und wird bei Formularänderungen nicht aktualisiert.*\n\nDie Aufnahme wurde gestoppt. Zuschnitte werden an der erkannten Abtastrate (~{sampling_rate}s) ausgerichtet.\n\nRohdauer: {duration}s\nBeispiele: {samples}", + "data": { + "head_trim": "Trimmstart (Sekunden)", + "tail_trim": "Trimmende (Sekunden)", + "save_mode": "Ziel speichern", + "profile_name": "Profilname" + } + }, + "learning_feedbacks": { + "title": "Rückmeldungen lernen", + "description": "Ausstehende Rückmeldungen: {count}\n\n{pending_table}\n\nWählen Sie eine Anfrage zur Zyklusüberprüfung zur Bearbeitung aus.", + "menu_options": { + "learning_feedbacks_pick": "Ausstehende Rückmeldung prüfen", + "learning_feedbacks_dismiss_all": "Alle ausstehenden Rückmeldungen verwerfen", + "menu_back": "← Zurück" + } + }, + "learning_feedbacks_pick": { + "title": "Feedback zur Überprüfung auswählen", + "description": "Wählen Sie eine Zyklus-Überprüfungsanfrage zum Öffnen aus.", + "data": { + "selected_feedback": "Zu überprüfenden Zyklus auswählen" + } + }, + "learning_feedbacks_empty": { + "title": "Rückmeldungen lernen", + "description": "Es wurden keine ausstehenden Feedbackanfragen gefunden.", + "menu_options": { + "menu_back": "← Zurück" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Alle ausstehenden Feedbacks verwerfen", + "description": "Sie sind dabei, **{count}** ausstehende Feedback-Anfrage(n) zu verwerfen. Verworfene Zyklen bleiben in Ihrem Verlauf, tragen jedoch kein neues Lernsignal mehr bei. Dies kann nicht rückgängig gemacht werden.\n\n✅ Aktivieren Sie das Kontrollkästchen unten und klicken Sie auf **Senden**, um alle zu verwerfen.\n❌ Lassen Sie es deaktiviert und klicken Sie auf **Senden**, um den Vorgang abzubrechen und zurückzukehren.", + "data": { + "confirm_dismiss_all": "Bestätigen: alle ausstehenden Rückmeldungsanfragen verwerfen" + } + }, + "resolve_feedback": { + "title": "Feedback auflösen", + "description": "{comparison_img}{candidates_table}\n**Erkanntes Profil**: {detected_profile} ({confidence_pct} %)\n**Geschätzte Dauer**: {est_duration_min} Min\n**Tatsächliche Dauer**: {act_duration_min} Min\n\n__Wählen Sie unten eine Aktion aus:__", + "data": { + "action": "Was möchten Sie tun?", + "corrected_profile": "Richtiges Programm (bei Korrektur)", + "corrected_duration": "Korrekte Dauer in Sekunden (bei Korrektur)" + } + }, + "trim_cycle_select": { + "title": "Trimmzyklus – Wählen Sie Zyklus aus", + "description": "Wählen Sie einen Zyklus zum Trimmen aus. ✓ = abgeschlossen, ⚠ = fortgesetzt, ✗ = unterbrochen", + "data": { + "cycle_id": "Zyklus" + } + }, + "trim_cycle": { + "title": "Trimmzyklus", + "description": "Aktuelles Fenster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aktion" + } + }, + "trim_cycle_start": { + "title": "Legen Sie den Trim-Start fest", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktueller Start: **{current_wallclock}** (+{current_offset_min} Min. ab Zyklusstart)\n\nWählen Sie mit der Uhrauswahl unten eine neue Startzeit aus.", + "data": { + "trim_start_time": "Neue Startzeit", + "trim_start_min": "Neustart (Minuten ab Zyklusstart)" + } + }, + "trim_cycle_end": { + "title": "Trimmende festlegen", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuelles Ende: **{current_wallclock}** (+{current_offset_min} Min. ab Zyklusstart)\n\nWählen Sie mit der Uhrauswahl unten eine neue Endzeit aus.", + "data": { + "trim_end_time": "Neue Endzeit", + "trim_end_min": "Neues Ende (Minuten ab Zyklusbeginn)" + } + } + }, + "error": { + "import_failed": "Der Import ist fehlgeschlagen. Bitte fügen Sie einen gültigen WashData-Export-JSON ein.", + "select_exactly_one": "Wählen Sie genau einen Zyklus für diese Aktion aus.", + "select_at_least_one": "Wählen Sie mindestens einen Zyklus für diese Aktion aus.", + "select_at_least_two": "Wählen Sie mindestens zwei Zyklen für diese Aktion aus.", + "profile_exists": "Ein Profil mit diesem Namen existiert bereits", + "rename_failed": "Profil konnte nicht umbenannt werden. Das Profil existiert möglicherweise nicht oder der neue Name ist bereits vergeben.", + "assignment_failed": "Profil konnte dem Zyklus nicht zugewiesen werden", + "duplicate_phase": "Eine Phase mit diesem Namen existiert bereits.", + "phase_not_found": "Die ausgewählte Phase wurde nicht gefunden.", + "invalid_phase_name": "Der Phasenname darf nicht leer sein.", + "phase_name_too_long": "Der Phasenname ist zu lang.", + "invalid_phase_range": "Der Phasenbereich ist ungültig. Ende muss größer als Anfang sein.", + "invalid_phase_timestamp": "Der Zeitstempel ist ungültig. Verwenden Sie das Format JJJJ-MM-TT HH:MM oder HH:MM.", + "overlapping_phase_ranges": "Phasenbereiche überschneiden sich. Passen Sie die Bereiche an und versuchen Sie es erneut.", + "incomplete_phase_row": "Jede Phasenzeile muss eine Phase sowie vollständige Start-/Endwerte für den ausgewählten Modus enthalten.", + "timestamp_mode_cycle_required": "Bitte wählen Sie einen Quellzyklus aus, wenn Sie den Zeitstempelmodus verwenden.", + "no_phase_ranges": "Für diese Aktion sind noch keine Phasenbereiche verfügbar.", + "feedback_notification_title": "WashData: Zyklus überprüfen ({device})", + "feedback_notification_message": "**Gerät**: {device}\n**Programm**: {program} ({confidence} % Konfidenz)\n**Zeit**: {time}\n\nWashData benötigt Ihre Hilfe, um diesen erkannten Zyklus zu überprüfen.\n\nBitte gehen Sie zu **Einstellungen > Geräte & Dienste > WashData > Konfigurieren > Lern-Feedbacks**, um dieses Ergebnis zu bestätigen oder zu korrigieren.", + "suggestions_ready_notification_title": "WashData: Vorgeschlagene Einstellungen bereit ({device})", + "suggestions_ready_notification_message": "Der Sensor **Vorgeschlagene Einstellungen** meldet jetzt **{count}** umsetzbare Empfehlungen.\n\nUm sie zu überprüfen und anzuwenden: **Einstellungen > Geräte & Dienste > WashData > Konfigurieren > Erweiterte Einstellungen > Vorgeschlagene Werte anwenden**.\n\nVorschläge sind optional und werden vor dem Speichern zur Überprüfung angezeigt.", + "trim_range_invalid": "Der Start muss vor dem Ende liegen. Bitte passen Sie Ihre Trimmpunkte an.", + "trim_failed": "Das Trimmen ist fehlgeschlagen. Möglicherweise existiert der Zyklus nicht mehr oder das Fenster ist leer.", + "invalid_split_timestamp": "Zeitstempel ist ungültig oder liegt außerhalb des Zyklusfensters. Verwenden Sie HH:MM oder HH:MM:SS innerhalb des Zyklusfensters.", + "no_split_segments_found": "Aus diesen Zeitstempeln konnten keine gültigen Segmente erzeugt werden. Stellen Sie sicher, dass jeder Zeitstempel innerhalb des Zyklusfensters liegt und die Segmente mindestens 1 Minute lang sind.", + "auto_tune_suggestion": "{device_type} {device_title} hat Geisterzyklen erkannt. Vorgeschlagene Änderung der Mindestleistung: {current_min}W -> {new_min}W (wird nicht automatisch angewendet).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} hat Geisterzyklen erkannt. Vorgeschlagene Änderung der Mindestleistung: {current_min}W -> {new_min}W (wird nicht automatisch angewendet)." + }, + "abort": { + "no_cycles_found": "Keine Zyklen gefunden", + "no_split_segments_found": "Im ausgewählten Zyklus wurden keine teilbaren Segmente gefunden.", + "cycle_not_found": "Der ausgewählte Zyklus konnte nicht geladen werden.", + "no_power_data": "Für den ausgewählten Zyklus sind keine Leistungsverfolgungsdaten verfügbar.", + "no_profiles_found": "Keine Profile gefunden. Erstellen Sie zunächst ein Profil.", + "no_profiles_for_matching": "Keine Profile zum Abgleich verfügbar. Erstellen Sie zunächst Profile.", + "no_unlabeled_cycles": "Alle Zyklen sind bereits beschriftet", + "no_suggestions": "Noch keine Wertevorschläge verfügbar. Führen Sie einige Zyklen durch und versuchen Sie es erneut.", + "no_predictions": "Keine Vorhersagen", + "no_custom_phases": "Keine benutzerdefinierten Phasen gefunden.", + "reprocess_success": "{count} Zyklen erfolgreich erneut verarbeitet.", + "debug_data_cleared": "Debug-Daten aus {count} Zyklen wurden erfolgreich gelöscht." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Zyklusstart", + "cycle_finish": "Zyklusende", + "cycle_live": "Live-Fortschritt" + } + }, + "common_text": { + "options": { + "unlabeled": "(Unbeschriftet)", + "create_new_profile": "Neues Profil erstellen", + "remove_label": "Etikett entfernen", + "no_reference_cycle": "(Kein Referenzzyklus)", + "all_device_types": "Alle Gerätetypen", + "current_suffix": "(aktuell)", + "editor_select_info": "Wählen Sie 1 Zyklus zum Teilen oder 2+ Zyklen zum Zusammenführen aus.", + "editor_select_info_split": "1 Zyklus zum Teilen auswählen.", + "editor_select_info_merge": "2+ Zyklen zum Zusammenführen auswählen.", + "editor_select_info_delete": "1+ Zyklus/Zyklen zum Löschen auswählen.", + "no_cycles_recorded": "Noch keine Zyklen aufgezeichnet.", + "profile_summary_line": "- **{name}**: {count} Zyklen, {avg}m durchschnittlich", + "no_profiles_created": "Noch keine Profile erstellt.", + "phase_preview": "Phasenvorschau", + "phase_preview_no_curve": "Die durchschnittliche Profilkurve ist noch nicht verfügbar. Weitere Zyklen für dieses Profil ausführen/kennzeichnen.", + "average_power_curve": "Durchschnittliche Leistungskurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} Zyklen, ~{duration}m Durchschnitt)", + "profile_option_short_fmt": "{name} ({count} Zyklen, ~{duration}m)", + "deleted_cycles_from_profile": "{count} Zyklen von {profile} gelöscht.", + "not_enough_data_graph": "Nicht genügend Daten, um ein Diagramm zu erstellen.", + "no_profiles_found": "Keine Profile gefunden.", + "cycle_info_fmt": "Zyklus: {start}, {duration}m, Strom: {label}", + "top_candidates_header": "### Top-Kandidaten", + "tbl_profile": "Profil", + "tbl_confidence": "Vertrauen", + "tbl_correlation": "Korrelation", + "tbl_duration_match": "Dauer-Match", + "detected_profile": "Erkanntes Profil", + "estimated_duration": "Geschätzte Dauer", + "actual_duration": "Tatsächliche Dauer", + "choose_action": "__Wählen Sie unten eine Aktion aus:__", + "tbl_count": "Zählen", + "tbl_avg": "Durchschn", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energie (Durchschn.)", + "tbl_energy_total": "Energie (Gesamt)", + "tbl_consistency": "Bestehen.", + "tbl_last_run": "Letzter Lauf", + "graph_legend_title": "Diagrammlegende", + "graph_legend_body": "Das blaue Band stellt den beobachteten minimalen und maximalen Leistungsaufnahmebereich dar. Die Linie zeigt die durchschnittliche Leistungskurve.", + "recording_preview": "Aufnahmevorschau", + "trim_start": "Trimmen beginnen", + "trim_end": "Ende abschneiden", + "split_preview_title": "Geteilte Vorschau", + "split_preview_found_fmt": "{count} Segmente gefunden.", + "split_preview_confirm_fmt": "Klicken Sie auf „Bestätigen“, um diesen Zyklus in {count} separate Zyklen aufzuteilen.", + "merge_preview_title": "Vorschau zusammenführen", + "merge_preview_joining_fmt": "Tritt {count} Zyklen bei. Lücken werden mit 0W-Messwerten gefüllt.", + "editor_delete_preview_title": "Löschvorschau", + "editor_delete_preview_intro": "Die ausgewählten Zyklen werden dauerhaft gelöscht:", + "editor_delete_preview_confirm": "Klicken Sie auf Bestätigen, um diese Zyklusdatensätze dauerhaft zu löschen.", + "no_power_preview": "*Keine Leistungsdaten für die Vorschau verfügbar.*", + "profile_comparison": "Profilvergleich", + "actual_cycle_label": "Dieser Zyklus (aktuell)", + "storage_usage_header": "Speichernutzung", + "storage_file_size": "Dateigröße", + "storage_cycles": "Zyklen", + "storage_profiles": "Profile", + "storage_debug_traces": "Debug-Traces", + "overview_suffix": "Überblick", + "phase_preview_for_profile": "Phasenvorschau für Profil", + "current_ranges_header": "Aktuelle Bereiche", + "assign_phases_help": "Verwenden Sie die folgenden Aktionen, um Bereiche hinzuzufügen, zu bearbeiten, zu löschen oder zu speichern.", + "profile_summary_header": "Profilzusammenfassung", + "recent_cycles_header": "Aktuelle Zyklen", + "trim_cycle_preview_title": "Zyklustrimmvorschau", + "trim_cycle_preview_no_data": "Für diesen Zyklus sind keine Leistungsdaten verfügbar.", + "trim_cycle_preview_kept_suffix": "gehalten", + "table_program": "Programm", + "table_when": "Wann", + "table_length": "Länge", + "table_match": "Treffer", + "table_profile": "Profil", + "table_cycles": "Zyklen", + "table_avg_length": "Ø Länge", + "table_last_run": "Letzter Lauf", + "table_avg_energy": "Ø Energie", + "table_detected_program": "Erkanntes Programm", + "table_confidence": "Vertrauen", + "table_reported": "Gemeldet", + "phase_builtin_suffix": "(Eingebaut)", + "phase_none_available": "Keine Phasen verfügbar.", + "settings_deprecation_warning": "⚠️ **Veralteter Gerätetyp:** {device_type} soll in einer zukünftigen Version entfernt werden. Die Matching-Pipeline von WashData liefert für diese Appliance-Klasse keine zuverlässigen Ergebnisse. Ihre Integration funktioniert während des veralteten Zeitraums weiterhin. Um diese Warnung auszuschalten, schalten Sie **Gerätetyp** unten auf einen der unterstützten Typen um (Waschmaschine, Trockner, Wasch-Trockner-Kombi, Geschirrspüler, Heißluftfritteuse, Brotbackautomat oder Pumpe) oder auf **Andere (Erweitert)**, wenn Ihr Gerät keinem der unterstützten Typen entspricht. **Andere (Erweiterte)** liefert absichtlich generische Standardeinstellungen, die nicht auf eine bestimmte Appliance abgestimmt sind, sodass Sie Schwellenwerte, Zeitüberschreitungen und passende Parameter selbst konfigurieren müssen; Alle Ihre bestehenden Einstellungen bleiben beim Wechsel erhalten.", + "phase_other_device_types": "Weitere Gerätetypen:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Einen Zyklus aufteilen (Lücken finden)", + "merge": "Zyklen zusammenführen (Fragmente verbinden)", + "delete": "Zyklus(e) löschen" + } + }, + "split_mode": { + "options": { + "auto": "Leerlauflücken automatisch erkennen", + "manual": "Manuelle Zeitstempel" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Wartung: Daten erneut verarbeiten und optimieren", + "clear_debug_data": "Debug-Daten löschen (Speicherplatz freigeben)", + "wipe_history": "ALLE Daten für dieses Gerät löschen (unwiderruflich)", + "export_import": "JSON mit Einstellungen exportieren/importieren (Kopieren/Einfügen)" + } + }, + "export_import_mode": { + "options": { + "export": "Alle Daten exportieren", + "import": "Daten importieren/zusammenführen" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Alte Zyklen automatisch kennzeichnen", + "select_cycle_to_label": "Beschriften Sie den spezifischen Zyklus", + "select_cycle_to_delete": "Zyklus löschen", + "interactive_editor": "Interaktiver Editor zum Zusammenführen/Teilen", + "trim_cycle": "Trimmzyklusdaten" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Neues Profil erstellen", + "edit_profile": "Profil bearbeiten/umbenennen", + "delete_profile": "Profil löschen", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Verlauf bereinigen – grafisch darstellen und löschen", + "assign_phases": "Phasenbereiche zuweisen" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Neue Phase erstellen", + "edit_custom_phase": "Phase bearbeiten", + "delete_custom_phase": "Phase löschen" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Phasenbereich hinzufügen", + "edit_range": "Phasenbereich bearbeiten", + "delete_range": "Phasenbereich löschen", + "clear_ranges": "Alle Bereiche löschen", + "auto_detect_ranges": "Phasen automatisch erkennen", + "save_ranges": "Speichern und zurückgeben" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Status aktualisieren", + "stop_recording": "Aufnahme beenden (Speichern und verarbeiten)", + "start_recording": "Neue Aufnahme starten", + "process_recording": "Letzte Aufnahme verarbeiten (Zuschneiden und Speichern)", + "discard_recording": "Letzte Aufnahme verwerfen" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Neues Profil erstellen", + "existing_profile": "Zum vorhandenen Profil hinzufügen", + "discard": "Aufnahme verwerfen" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bestätigen – korrekte Erkennung", + "correct": "Richtig – Wählen Sie Programm/Dauer", + "ignore": "Ignorieren – falsch positiver/verrauschter Zyklus", + "delete": "Löschen – Aus dem Verlauf entfernen" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Phasen benennen und anwenden", + "cancel": "Gehen Sie ohne Änderungen zurück" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Startpunkt festlegen", + "set_end": "Endpunkt festlegen", + "reset": "Auf volle Dauer zurücksetzen", + "apply": "Trimmen anwenden", + "cancel": "Stornieren" + } + }, + "device_type": { + "options": { + "washing_machine": "Waschmaschine", + "dryer": "Trockner", + "washer_dryer": "Waschmaschine-Trockner-Kombination", + "dishwasher": "Spülmaschine", + "coffee_machine": "Kaffeemaschine (veraltet)", + "ev": "Elektrofahrzeug (veraltet)", + "air_fryer": "Luftfritteuse", + "heat_pump": "Wärmepumpe (veraltet)", + "bread_maker": "Brotbackautomat", + "pump": "Pumpe / Sumpfpumpe", + "oven": "Ofen (veraltet)", + "other": "Andere (Fortgeschrittene)" + } + } + }, + "services": { + "label_cycle": { + "name": "Zyklus beschriften", + "description": "Ordnen Sie ein vorhandenes Profil einem vergangenen Zyklus zu oder entfernen Sie die Bezeichnung.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät zum Beschriften." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die ID des zu kennzeichnenden Zyklus." + }, + "profile_name": { + "name": "Profilname", + "description": "Der Name eines vorhandenen Profils (Profile erstellen im Menü „Profile verwalten“). Lassen Sie das Feld leer, um das Etikett zu entfernen." + } + } + }, + "create_profile": { + "name": "Profil erstellen", + "description": "Erstellen Sie ein neues Profil (eigenständig oder basierend auf einem Referenzzyklus).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät." + }, + "profile_name": { + "name": "Profilname", + "description": "Name für das neue Profil (z. B. „Heavy Duty“, „Delicates“)." + }, + "reference_cycle_id": { + "name": "Referenzzyklus-ID", + "description": "Optionale Zyklus-ID, auf der dieses Profil basieren soll." + } + } + }, + "delete_profile": { + "name": "Profil löschen", + "description": "Löschen Sie ein Profil und heben Sie optional die Kennzeichnung von Zyklen auf, die es verwenden.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät." + }, + "profile_name": { + "name": "Profilname", + "description": "Das zu löschende Profil." + }, + "unlabel_cycles": { + "name": "Zyklen entkennzeichnen", + "description": "Entfernen Sie die Profilbezeichnung von Zyklen, die dieses Profil verwenden." + } + } + }, + "auto_label_cycles": { + "name": "Alte Zyklen automatisch kennzeichnen", + "description": "Beschriften Sie unbeschriftete Zyklen rückwirkend mithilfe des Profilabgleichs.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät." + }, + "confidence_threshold": { + "name": "Vertrauensschwelle", + "description": "Mindestübereinstimmungskonfidenz (0,50–0,95) zum Anwenden von Beschriftungen." + } + } + }, + "export_config": { + "name": "Konfiguration exportieren", + "description": "Exportieren Sie die Profile, Zyklen und Einstellungen dieses Geräts in eine JSON-Datei (pro Gerät).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das zu exportierende Waschmaschinengerät." + }, + "path": { + "name": "Weg", + "description": "Optionaler absoluter Dateipfad zum Schreiben (standardmäßig /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Konfiguration importieren", + "description": "Importieren Sie Profile, Zyklen und Einstellungen für dieses Gerät aus einer JSON-Exportdatei (Einstellungen werden möglicherweise überschrieben).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät, in das importiert werden soll." + }, + "path": { + "name": "Weg", + "description": "Absoluter Pfad zur zu importierenden JSON-Exportdatei." + } + } + }, + "submit_cycle_feedback": { + "name": "Senden Sie Feedback zum Zyklus", + "description": "Bestätigen oder korrigieren Sie ein automatisch erkanntes Programm nach einem abgeschlossenen Zyklus. Geben Sie entweder „entry_id“ (erweitert) oder „device_id“ (empfohlen) an.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät (empfohlen anstelle von „entry_id“)." + }, + "entry_id": { + "name": "Eintrags-ID", + "description": "Die Konfigurationseintrags-ID für das Gerät (Alternative zu device_id)." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die in der Feedback-Benachrichtigung/in den Protokollen angezeigte Zyklus-ID." + }, + "user_confirmed": { + "name": "Bestätigen Sie das erkannte Programm", + "description": "Auf „true“ setzen, wenn das erkannte Programm korrekt ist." + }, + "corrected_profile": { + "name": "Korrigiertes Profil", + "description": "Wenn dies nicht bestätigt wird, geben Sie den korrekten Profil-/Programmnamen an." + }, + "corrected_duration": { + "name": "Korrigierte Dauer (Sekunden)", + "description": "Optionale korrigierte Dauer in Sekunden." + }, + "notes": { + "name": "Notizen", + "description": "Optionale Hinweise zu diesem Zyklus." + } + } + }, + "record_start": { + "name": "Zyklusstart aufzeichnen", + "description": "Starten Sie die manuelle Aufzeichnung eines Reinigungszyklus (umgeht alle passenden Logiken).", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das Waschmaschinengerät zum Aufnehmen." + } + } + }, + "record_stop": { + "name": "Aufnahmezyklusstopp", + "description": "Stoppen Sie die manuelle Aufnahme.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Die Waschmaschine stoppt die Aufnahme." + } + } + }, + "trim_cycle": { + "name": "Trimmzyklus", + "description": "Trimmen Sie die Leistungsdaten eines vergangenen Zyklus auf ein bestimmtes Zeitfenster.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät." + }, + "cycle_id": { + "name": "Zyklus-ID", + "description": "Die ID des zu trimmenden Zyklus." + }, + "trim_start_s": { + "name": "Trimmstart (Sekunden)", + "description": "Behalten Sie die Daten von diesem Offset in Sekunden bei. Standard 0." + }, + "trim_end_s": { + "name": "Trimmende (Sekunden)", + "description": "Halten Sie die Daten in Sekunden auf diesem Offset. Standard = volle Dauer." + } + } + }, + "pause_cycle": { + "name": "Zyklus anhalten", + "description": "Unterbrechen Sie den aktiven Zyklus für ein WashData-Gerät.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät, das angehalten werden soll." + } + } + }, + "resume_cycle": { + "name": "Zyklus fortsetzen", + "description": "Setzen Sie einen angehaltenen Zyklus für ein WashData-Gerät fort.", + "fields": { + "device_id": { + "name": "Gerät", + "description": "Das WashData-Gerät, das fortgesetzt werden soll." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Läuft" + }, + "match_ambiguity": { + "name": "Übereinstimmungsmehrdeutigkeit" + } + }, + "sensor": { + "washer_state": { + "name": "Zustand", + "state": { + "off": "Aus", + "idle": "Leerlauf", + "starting": "Beginnt", + "running": "Läuft", + "paused": "Angehalten", + "user_paused": "Vum Benutzer pausiert", + "ending": "Ende", + "finished": "Fertig", + "anti_wrinkle": "Anti-Falten", + "delay_wait": "Warten auf den Start", + "interrupted": "Unterbrochen", + "force_stopped": "Kraft gestoppt", + "rinse": "Spülen", + "unknown": "Unbekannt", + "clean": "Sauber" + } + }, + "washer_program": { + "name": "Programm" + }, + "time_remaining": { + "name": "Verbleibende Zeit" + }, + "total_duration": { + "name": "Gesamtdauer" + }, + "cycle_progress": { + "name": "Fortschritt" + }, + "current_power": { + "name": "Aktuelle Leistung" + }, + "elapsed_time": { + "name": "Verstrichene Zeit" + }, + "debug_info": { + "name": "Debug-Info" + }, + "match_confidence": { + "name": "Match-Vertrauen" + }, + "top_candidates": { + "name": "Top-Kandidaten", + "state": { + "none": "Keiner" + } + }, + "current_phase": { + "name": "Aktuelle Phase" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Anzahl", + "unit_of_measurement": "Zyklen" + }, + "suggestions": { + "name": "Empfohlene Einstellungen" + }, + "pump_runs_today": { + "name": "Pumpenläufe (letzte 24 Stunden)" + }, + "cycle_count": { + "name": "Zyklusanzahl" + } + }, + "select": { + "program_select": { + "name": "Zyklusprogramm", + "state": { + "auto_detect": "Automatische Erkennung" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Zyklusende erzwingen" + }, + "pause_cycle": { + "name": "Zyklus anhalten" + }, + "resume_cycle": { + "name": "Zyklus fortsetzen" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Eine gültige Geräte-ID ist erforderlich." + }, + "cycle_id_required": { + "message": "Eine gültige Cycle_ID ist erforderlich." + }, + "profile_name_required": { + "message": "Ein gültiger Profilname ist erforderlich." + }, + "device_not_found": { + "message": "Gerät nicht gefunden." + }, + "no_config_entry": { + "message": "Für das Gerät wurde kein Konfigurationseintrag gefunden." + }, + "integration_not_loaded": { + "message": "Die Integration wurde für dieses Gerät nicht geladen." + }, + "cycle_not_found_or_no_power": { + "message": "Zyklus nicht gefunden oder hat keine Leistungsdaten." + }, + "trim_failed_empty_window": { + "message": "Trimmen fehlgeschlagen – Zyklus nicht gefunden, keine Leistungsdaten oder resultierendes Fenster ist leer." + }, + "trim_invalid_range": { + "message": "trim_end_s muss größer als trim_start_s sein." + } + } +} diff --git a/custom_components/ha_washdata/translations/he.json b/custom_components/ha_washdata/translations/he.json new file mode 100644 index 0000000..98441cb --- /dev/null +++ b/custom_components/ha_washdata/translations/he.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "הגדרת WashData", + "description": "הגדר את מכונת הכביסה או מכשיר אחר.\n\nנדרש חיישן כוח.\n\n**לאחר מכן, תישאל אם ברצונך ליצור את הפרופיל הראשון שלך.**", + "data": { + "name": "שם המכשיר", + "device_type": "סוג מכשיר", + "power_sensor": "חיישן כוח", + "min_power": "סף הספק מינימלי (W)" + }, + "data_description": { + "name": "שם ידידותי למכשיר זה (למשל, 'מכונת כביסה', 'מדיח כלים').", + "device_type": "איזה סוג של מכשיר זה? עוזר להתאים את הזיהוי והתיוג.", + "power_sensor": "ישות החיישן שמדווחת על צריכת חשמל בזמן אמת (בוואט) מהתקע החכם שלך.", + "min_power": "קריאות הספק מעל סף זה (בוואט) מצביעות על כך שהמכשיר פועל. התחל עם 2W עבור רוב המכשירים." + } + }, + "first_profile": { + "title": "צור פרופיל ראשון", + "description": "**מחזור** הוא הפעלה שלמה של המכשיר שלך (למשל, עומס של כביסה). **פרופיל** מקבץ את המחזורים הללו לפי סוג (למשל, 'כותנה', 'כביסה מהירה'). יצירת פרופיל עוזרת כעת לאמוד את משך האינטגרציה באופן מיידי.\n\nאתה יכול לבחור פרופיל זה באופן ידני בפקדי המכשיר אם הוא לא מזוהה אוטומטית.\n\n**השאר את 'שם פרופיל' ריק כדי לדלג על שלב זה.**", + "data": { + "profile_name": "שם פרופיל (אופציונלי)", + "manual_duration": "משך זמן משוער (דקות)" + } + } + }, + "error": { + "cannot_connect": "החיבור נכשל", + "invalid_auth": "אימות לא חוקי", + "unknown": "שגיאה לא צפויה" + }, + "abort": { + "already_configured": "המכשיר כבר מוגדר" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "עיין במשובי למידה", + "settings": "הגדרות", + "notifications": "התראות", + "manage_cycles": "נהל מחזורים", + "manage_profiles": "נהל פרופילים", + "manage_phase_catalog": "נהל את קטלוג השלבים", + "record_cycle": "מחזור הקלטות (ידני)", + "diagnostics": "אבחון ותחזוקה" + } + }, + "apply_suggestions": { + "title": "העתק ערכים מוצעים", + "description": "פעולה זו תעתיק את הערכים המוצעים הנוכחיים להגדרות השמורות שלך. זוהי פעולה חד פעמית ואינה מאפשרת החלפה אוטומטית.\n\nערכים מוצעים להעתקה:\n{suggested}", + "data": { + "confirm": "אשר את פעולת ההעתקה" + } + }, + "apply_suggestions_confirm": { + "title": "סקור שינויים מוצעים", + "description": "נמצאו **{pending_count}** ערכים מוצעים ליישום WashData.\n\nשינויים:\n{changes}\n\n✅ סמן את התיבה למטה ולחץ על **שלח** כדי להחיל ולשמור שינויים אלה באופן מיידי.\n❌ השאר אותו לא מסומן ולחץ על **שלח** כדי לבטל ולחזור.", + "data": { + "confirm_apply_suggestions": "החל ושמור שינויים אלה" + } + }, + "settings": { + "title": "הגדרות", + "description": "{deprecation_warning}כוונון ספי זיהוי, למידה והתנהגות מתקדמת. ברירת המחדל עובדת היטב עבור רוב ההגדרות.\n\nהגדרות מוצעות הזמינות כעת: {suggestions_count}.\nאם המספר גדול מ-0, פתח הגדרות מתקדמות ובחר 'החל ערכים מוצעים' לסקירת ההמלצות.", + "data": { + "apply_suggestions": "החל ערכים מוצעים", + "device_type": "סוג מכשיר", + "power_sensor": "ישות חיישן כוח", + "min_power": "הספק מינימלי (W)", + "off_delay": "עיכוב סיום מחזור (ים)", + "notify_actions": "פעולות הודעה", + "notify_people": "דחו את המשלוח עד שהאנשים האלה יהיו בבית", + "notify_only_when_home": "עיכוב הודעות עד שהאדם הנבחר יהיה בבית", + "notify_fire_events": "הפעלת אירועי אוטומציה", + "notify_start_services": "התחלת מחזור - יעדי הודעה", + "notify_finish_services": "סיום מחזור - יעדי הודעה", + "notify_live_services": "התקדמות חיה - יעדי התראות", + "notify_before_end_minutes": "הודעה מוקדמת על סיום (דקות לפני סיום)", + "notify_title": "כותרת ההודעה", + "notify_icon": "סמל הודעה", + "notify_start_message": "התחל פורמט הודעה", + "notify_finish_message": "סיים את פורמט ההודעה", + "notify_pre_complete_message": "פורמט הודעת טרום השלמה", + "show_advanced": "ערוך הגדרות מתקדמות (השלב ​​הבא)" + }, + "data_description": { + "apply_suggestions": "סמן תיבה זו כדי להעתיק ערכים שהוצעו על ידי אלגוריתם הלמידה לטופס. בדוק לפני השמירה.\n\nהצעה (מלמידה):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} שניות\n- watchdog_interval: {suggested_watchdog_interval} שניות\n- no_update_active_timeout: {suggested_no_update_active_timeout} שניות\n- profile_match_interval: {suggested_profile_match_interval} שניות\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} שניות\n{suggested_reason}", + "device_type": "בחר את סוג המכשיר (מכונת כביסה, מייבש, מדיח כלים, מכונת קפה, EV). תג זה מאוחסן עם כל מחזור לארגון טוב יותר.", + "power_sensor": "ישות החיישן מדווחת על הספק בזמן אמת (בוואט). אתה יכול לשנות זאת בכל עת מבלי לאבד נתונים היסטוריים - שימושי אם אתה מחליף תקע חכם.", + "min_power": "קריאות הספק מעל סף זה (בוואט) מצביעות על כך שהמכשיר פועל. התחל עם 2W עבור רוב המכשירים.", + "off_delay": "שניות הכוח המוחלק חייב להישאר מתחת לסף העצירה כדי לסמן את ההשלמה. ברירת מחדל: 120 שניות.", + "notify_actions": "רצף פעולה אופציונלי של Home Assistant להפעלת התראות (סקריפטים, התראה, TTS וכו'). אם הוגדר, הפעולות פועלות לפני שירות התראה חוזר.", + "notify_people": "ישויות של אנשים המשמשות עבור שער נוכחות. כאשר מופעל, מסירת ההתראה מתעכבת עד שלפחות אדם נבחר אחד יהיה בבית.", + "notify_only_when_home": "אם מופעל, דחה הודעות עד שלפחות אדם נבחר אחד יהיה בבית.", + "notify_fire_events": "אם מופעל, הפעל אירועי Home Assistant עבור התחלה וסיום של מחזור (ha_washdata_cycle_started ו-ha_washdata_cycle_ended) כדי להניע אוטומציות.", + "notify_start_services": "שירות הודעה אחד או יותר להתריע כאשר מחזור מתחיל. השאר ריק כדי לדלג על התראות התחלה.", + "notify_finish_services": "שירות הודעה אחד או יותר להתריע כאשר מחזור מסתיים או מתקרב לסיומו. השאר ריק כדי לדלג על הודעות סיום.", + "notify_live_services": "שירות הודעה אחד או יותר כדי לקבל עדכוני התקדמות בזמן שהמחזור פועל (אפליקציה נלווית לנייד בלבד). השאר ריק כדי לדלג על עדכונים חיים.", + "notify_before_end_minutes": "דקות לפני סיום המחזור המשוער כדי להפעיל הודעה. הגדר ל-0 כדי להשבית. ברירת מחדל: 0.", + "notify_title": "כותרת מותאמת אישית עבור התראות. תומך ב-{device} מציין מיקום.", + "notify_icon": "סמל לשימוש עבור התראות (למשל mdi:washing-machine). השאר ריק כדי לשלוח ללא סמל.", + "notify_start_message": "הודעה נשלחת כאשר מחזור מתחיל. תומך ב-{device} מציין מיקום.", + "notify_finish_message": "הודעה נשלחת כאשר מחזור מסתיים. תומך במצייני מיקום של {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "טקסט של עדכוני ההתקדמות החיים שחוזרים על עצמם בזמן שהמחזור פועל. תומך במצייני מיקום של {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "התראות", + "description": "הגדר ערוצי התראות, תבניות ועדכוני התקדמות אופציונליים לנייד.", + "data": { + "notify_actions": "פעולות הודעה", + "notify_people": "דחו את המשלוח עד שהאנשים האלה יהיו בבית", + "notify_only_when_home": "עיכוב הודעות עד שהאדם הנבחר יהיה בבית", + "notify_fire_events": "הפעלת אירועי אוטומציה", + "notify_start_services": "התחלת מחזור - יעדי הודעה", + "notify_finish_services": "סיום מחזור - יעדי הודעה", + "notify_live_services": "התקדמות חיה - יעדי התראות", + "notify_before_end_minutes": "הודעה מוקדמת על סיום (דקות לפני סיום)", + "notify_live_interval_seconds": "מרווח עדכון חי (שניות)", + "notify_live_overrun_percent": "קצבת חריגת עדכונים בזמן אמת (%)", + "notify_live_chronometer": "טיימר ספירה לאחור כרונומטר", + "notify_title": "כותרת ההודעה", + "notify_icon": "סמל הודעה", + "notify_start_message": "התחל פורמט הודעה", + "notify_finish_message": "סיים את פורמט ההודעה", + "notify_pre_complete_message": "פורמט הודעת טרום השלמה", + "energy_price_entity": "מחיר אנרגיה - ישות (אופציונלי)", + "energy_price_static": "מחיר אנרגיה - ערך סטטי (אופציונלי)", + "go_back": "חזור בלי לשמור", + "notify_reminder_message": "תגית: Message Format", + "notify_timeout_seconds": "Auto-dismiss After (שניים)", + "notify_channel": "ערוץ Notification (status/Life/reminder)", + "notify_finish_channel": "ערוץ Notification" + }, + "data_description": { + "go_back": "סמן זאת ולחץ על שלח כדי לבטל כל שינוי למעלה ולחזור לתפריט הראשי.", + "notify_actions": "רצף פעולה אופציונלי של Home Assistant להפעלת התראות (סקריפטים, התראה, TTS וכו'). אם הוגדר, הפעולות פועלות לפני שירות התראה חוזר.", + "notify_people": "ישויות של אנשים המשמשות עבור שער נוכחות. כאשר מופעל, מסירת ההתראה מתעכבת עד שלפחות אדם נבחר אחד יהיה בבית.", + "notify_only_when_home": "אם מופעל, דחה הודעות עד שלפחות אדם נבחר אחד יהיה בבית.", + "notify_fire_events": "אם מופעל, הפעל אירועי Home Assistant עבור התחלה וסיום של מחזור (ha_washdata_cycle_started ו-ha_washdata_cycle_ended) כדי להניע אוטומציות.", + "notify_start_services": "שירות התראה אחד או יותר כדי להתריע כאשר מחזור מתחיל (למשל, notify.mobile_app_my_phone). השאר ריק כדי לדלג על התראות התחלה.", + "notify_finish_services": "שירות הודעה אחד או יותר להתריע כאשר מחזור מסתיים או מתקרב לסיומו. השאר ריק כדי לדלג על הודעות סיום.", + "notify_live_services": "שירות הודעה אחד או יותר כדי לקבל עדכוני התקדמות בזמן שהמחזור פועל (אפליקציה נלווית לנייד בלבד). השאר ריק כדי לדלג על עדכונים חיים.", + "notify_before_end_minutes": "דקות לפני סיום המחזור המשוער כדי להפעיל הודעה. הגדר ל-0 כדי להשבית. ברירת מחדל: 0.", + "notify_live_interval_seconds": "באיזו תדירות נשלחים עדכוני התקדמות בזמן הריצה. ערכים נמוכים יותר מגיבים יותר אך צורכים יותר התראות. מינימום של 30 שניות נאכף - ערכים מתחת ל-30 שניות מעוגלים אוטומטית כלפי מעלה ל-30 שניות.", + "notify_live_overrun_percent": "מרווח בטיחות מעל ספירת העדכונים המשוערת עבור מחזורים ארוכים/חורגים (לדוגמה 20% מאפשרים עדכונים משוערים פי 1.2).", + "notify_live_chronometer": "כאשר מופעל, כל עדכון חי כולל ספירה לאחור של כרונומטר לזמן הסיום המשוער. טיימר ההתראות מתקתק אוטומטית במכשיר בין עדכונים (אפליקציה נלווית לאנדרואיד בלבד).", + "notify_title": "כותרת מותאמת אישית עבור התראות. תומך ב-{device} מציין מיקום.", + "notify_icon": "סמל לשימוש עבור התראות (למשל mdi:washing-machine). השאר ריק כדי לשלוח ללא סמל.", + "notify_start_message": "הודעה נשלחת כאשר מחזור מתחיל. תומך ב-{device} מציין מיקום.", + "notify_finish_message": "הודעה נשלחת כאשר מחזור מסתיים. תומך במצייני מיקום של {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "בחר ישות מספרית שמחזיקה את מחיר החשמל הנוכחי (למשל sensor.electricity_price, input_number.energy_rate). כאשר מוגדר, מפעיל את מציין המיקום {cost}. מקבל עדיפות על הערך הסטטי אם שניהם מוגדרים.", + "energy_price_static": "מחיר חשמל קבוע לקוט\"ש. כאשר מוגדר, מפעיל את מציין המיקום {cost}. התעלמות אם ישות מוגדרת למעלה. הוסף את סמל המטבע שלך בתבנית ההודעה, למשל. {cost} יורו.", + "notify_reminder_message": "הטקסט של התזכורת החד-פעמית שלח את מספר הדקות המוגדר לפני הסוף המשוער. להבדיל מהעדכונים החיים החוזרים ונשנים למעלה. תומך במצייני מיקום של {device}, {minutes}, {program}.", + "notify_timeout_seconds": "לבטל הודעות באופן אוטומטי לאחר שניות רבות אלה (הופנה מהדף \"Timeout\"). הגדר 0 כדי לשמור אותם עד שפוטר. תגית: 0.", + "notify_channel": "[0] ערוץ התראה של אפליקציה עבור מעמד, התקדמות חיה והודעות תזכורת. הצליל והחשיבות של ערוץ מוגדרים באפליקציית המלווים בפעם הראשונה שמו של הערוץ משמש - שטיפת מידע רק קובעת את שם הערוץ. להשאיר ריק עבור היישום ברירת מחדל.", + "notify_finish_channel": "ערוץ נפרד לכוננות גמורה (המכונה גם על ידי התזכורת והטנגו המחץ כביסה) כך שהוא יכול להיות קול משלו. השאירו ריק להשתמש בערוץ למעלה.", + "notify_pre_complete_message": "טקסט של עדכוני ההתקדמות החיים שחוזרים על עצמם בזמן שהמחזור פועל. תומך במצייני מיקום של {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "הגדרות מתקדמות", + "description": "כוונון ספי זיהוי, למידה והתנהגות מתקדמת. ברירת המחדל עובדת היטב עבור רוב ההגדרות.", + "sections": { + "suggestions_section": { + "name": "הגדרות מוצעות", + "description": "יש {suggestions_count} הצעות שנלמדו זמינות. סמן 'החל ערכים מוצעים' כדי לבדוק את השינויים המוצעים לפני השמירה.", + "data": { + "apply_suggestions": "החל ערכים מוצעים" + }, + "data_description": { + "apply_suggestions": "סמן תיבה זו כדי לפתוח שלב סקירה עבור ערכים מוצעים מאלגוריתם הלמידה. שום דבר לא נשמר אוטומטית.\n\nהצעה (מלמידה):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} שניות\n- watchdog_interval: {suggested_watchdog_interval} שניות\n- no_update_active_timeout: {suggested_no_update_active_timeout} שניות\n- profile_match_interval: {suggested_profile_match_interval} שניות\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} שניות\n{suggested_reason}" + } + }, + "detection_section": { + "name": "זיהוי וספי הספק", + "description": "מתי המכשיר נחשב לפועל או כבוי, שערי אנרגיה ותזמון מעברי מצב.", + "data": { + "start_duration_threshold": "משך התחלת יציאה מדף כניסה (ים)", + "start_energy_threshold": "התחל שער אנרגיה (Wh)", + "completion_min_seconds": "זמן ריצה מינימלי (שניות)", + "start_threshold_w": "סף התחלה (W)", + "stop_threshold_w": "סף עצירה (W)", + "end_energy_threshold": "שער אנרגיית סיום (Wh)", + "running_dead_zone": "אזור מת בהפעלה (שניות)", + "end_repeat_count": "סיום ספירת חזרות", + "min_off_gap": "פער מינימלי בין מחזורים (ים)", + "sampling_interval": "מרווח דגימה (ים)" + }, + "data_description": { + "start_duration_threshold": "התעלם משיאי כוח קצרים קצרים ממשך זה (שניות). מסנן קוצים לאתחול (למשל, 60W למשך 2 שניות) לפני שהמחזור האמיתי מתחיל. ברירת מחדל: 5 שניות.", + "start_energy_threshold": "המחזור חייב לצבור לפחות כמות כזו של אנרגיה (Wh) במהלך שלב ההתחלה כדי לאשר. ברירת מחדל: 0.005 וואט.", + "completion_min_seconds": "מחזורים קצרים מזה יסומנו כ'מופסקים' גם אם הם מסתיימים באופן טבעי. ברירת מחדל: 600.", + "start_threshold_w": "הכוח חייב לעלות מעל הסף הזה כדי להתחיל מחזור. הגדר גבוה יותר מעוצמת המתנה שלך. ברירת מחדל: min_power + 1 W.", + "stop_threshold_w": "הכוח חייב לרדת מתחת לסף הזה כדי לסיים מחזור. הגדר מעט מעל אפס כדי למנוע רעש המעורר קצוות כוזבים. ברירת מחדל: 0.6 * min_power.", + "end_energy_threshold": "המחזור מסתיים רק אם האנרגיה בחלון ההשהיה האחרון מתחת לערך זה (Wh). מונע קצוות כוזבים אם הספק משתנה קרוב לאפס. ברירת מחדל: 0.05 וואט.", + "running_dead_zone": "לאחר התחלת המחזור, התעלם מירידה בכוח למשך שניות רבות כל כך כדי למנוע זיהוי קצה כוזב במהלך שלב הספין-אפ הראשוני. הגדר ל-0 כדי להשבית. ברירת מחדל: 0s.", + "end_repeat_count": "מספר הפעמים שתנאי הסיום (הספק מתחת לסף עבור off_delay) חייבים להתקיים ברציפות לפני סיום המחזור. ערכים גבוהים יותר מונעים קצוות כוזבים במהלך הפסקות. ברירת מחדל: 1.", + "min_off_gap": "אם מחזור מתחיל בתוך שניות רבות כל כך מהסיום הקודם, הם ימוזגו למחזור בודד.", + "sampling_interval": "מרווח דגימה מינימלי בשניות. עדכונים מהירים מקצב זה יתעלמו. ברירת מחדל: 30 שניות (2 שניות למכונות כביסה, מכונת כביסה-מייבש ומדיח כלים)." + } + }, + "matching_section": { + "name": "התאמת פרופילים ולמידה", + "description": "עד כמה WashData מתאימה מחזורים פעילים לפרופילים שנלמדו ומתי לבקש משוב.", + "data": { + "profile_match_min_duration_ratio": "יחס משך זמן מינימלי של התאמה לפרופיל (0.1-1.0)", + "profile_match_interval": "מרווח התאמה לפרופיל (שניות)", + "profile_match_threshold": "סף התאמה לפרופיל", + "profile_unmatch_threshold": "פרופיל לא תואם סף", + "auto_label_confidence": "ביטחון תווית אוטומטי (0-1)", + "learning_confidence": "בטחון בקשת משוב (0-1)", + "suppress_feedback_notifications": "דחק הודעות משוב", + "duration_tolerance": "סובלנות משך זמן (0-0.5)", + "smoothing_window": "החלקת חלון (דוגמאות)", + "profile_duration_tolerance": "סובלנות משך התאמה לפרופיל (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "יחס משך מינימלי עבור התאמת פרופיל. מחזור הריצה חייב להיות לפחות חלק זה ממשך הפרופיל כדי להתאים. תחתון = התאמה מוקדמת יותר. ברירת מחדל: 0.50 (50%).", + "profile_match_interval": "באיזו תדירות (בשניות) לנסות התאמת פרופיל במהלך מחזור. ערכים נמוכים יותר מספקים זיהוי מהיר יותר אך משתמשים במעבד רב יותר. ברירת מחדל: 300 שניות (5 דקות).", + "profile_match_threshold": "ציון דמיון מינימלי ב-DTW (0.0–1.0) נדרש כדי להתייחס לפרופיל כהתאמה. גבוה יותר = התאמה קפדנית יותר, פחות תוצאות חיוביות שגויות. ברירת מחדל: 0.4.", + "profile_unmatch_threshold": "ציון דמיון DTW (0.0–1.0) שמתחתיו נדחה פרופיל תואם בעבר. צריך להיות נמוך מסף ההתאמה כדי למנוע הבהוב. ברירת מחדל: 0.35.", + "auto_label_confidence": "תווית אוטומטית מחזורים ברמת הביטחון הזה או מעליו בסיומם (0.0-1.0).", + "learning_confidence": "ביטחון מינימלי לבקשת אימות משתמש באמצעות אירוע (0.0–1.0).", + "suppress_feedback_notifications": "כאשר מופעל, WashData עדיין תעקוב ותבקש משוב באופן פנימי, אך לא תציג התראות מתמשכות המבקשות ממך לאשר מחזורים. שימושי כאשר מחזורים מזוהים תמיד כראוי ואתה מוצא שההנחיות מסיחות את הדעת.", + "duration_tolerance": "סובלנות להערכות שנותרו בזמן ריצה (0.0-0.5 תואם 0-50%).", + "smoothing_window": "מספר קריאות החשמל האחרונות ששימשו להחלקה של ממוצע נע.", + "profile_duration_tolerance": "סובלנות בשימוש על ידי התאמת פרופיל עבור שונות משך הכולל (0.0-0.5). התנהגות ברירת מחדל: ±25%." + } + }, + "timing_section": { + "name": "תזמון, תחזוקה וניפוי באגים", + "description": "Watchdog, איפוס התקדמות, תחזוקה אוטומטית וחשיפת ישויות ניפוי באגים.", + "data": { + "watchdog_interval": "מרווח כלב שמירה (שניות)", + "no_update_active_timeout": "זמן קצוב ללא עדכון (ים)", + "progress_reset_delay": "עיכוב איפוס התקדמות (שניות)", + "auto_maintenance": "הפעל תחזוקה אוטומטית", + "expose_debug_entities": "חשוף ישויות ניפוי באגים", + "save_debug_traces": "שמור עקבות ניפוי באגים" + }, + "data_description": { + "watchdog_interval": "שניות בין בדיקות כלב שמירה בזמן ריצה. ברירת מחדל: 5 שניות. אזהרה: ודא שזה גבוה יותר ממרווח העדכון של החיישן שלך כדי למנוע עצירות שווא (לדוגמה, אם החיישן מתעדכן כל 60 שניות, השתמש ב-65 שניות+).", + "no_update_active_timeout": "עבור חיישני פרסום בשינוי: רק בכוח לסיים מחזור ACTIVE אם לא מגיעים עדכונים למשך שניות רבות כל כך. השלמת הספק נמוך עדיין משתמשת בהשהיית הכיבוי.", + "progress_reset_delay": "לאחר סיום מחזור (100%), המתן שניות רבות של סרק לפני איפוס ההתקדמות ל-0%. ברירת מחדל: 300s.", + "auto_maintenance": "אפשר תחזוקה אוטומטית (תיקון דוגמאות, מיזוג קטעים).", + "expose_debug_entities": "הצג חיישנים מתקדמים (ביטחון, שלב, עמימות) לניפוי באגים.", + "save_debug_traces": "אחסן נתוני דירוג ומעקב כוח מפורטים בהיסטוריה (מגדיל את השימוש באחסון)." + } + }, + "anti_wrinkle_section": { + "name": "מגן נגד קמטים (מייבשים)", + "description": "התעלם מסיבובי תוף לאחר סיום המחזור כדי למנוע מחזורי רפאים. למייבש / מכונת כביסה-מייבש בלבד.", + "data": { + "anti_wrinkle_enabled": "מגן נגד קמטים (מייבש בלבד)", + "anti_wrinkle_max_power": "כוח מרבי נגד קמטים (W)", + "anti_wrinkle_max_duration": "משך מקסימום נגד קמטים (ים)", + "anti_wrinkle_exit_power": "כוח יציאה נגד קמטים (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "התעלם מסיבובי תוף קצרים בעוצמה נמוכה לאחר סיום מחזור כדי למנוע מחזורי רפאים (מייבש/מייבש כביסה בלבד).", + "anti_wrinkle_max_power": "התפרצויות בעוצמה זו או מתחתיה מטופלות כסיבובים נגד קמטים במקום מחזורים חדשים. חל רק כאשר מגן נגד קמטים מופעל.", + "anti_wrinkle_max_duration": "אורך התפרצות מרבי לטיפול כסיבוב נגד קמטים. חל רק כאשר מגן נגד קמטים מופעל.", + "anti_wrinkle_exit_power": "אם הכוח יורד מתחת לרמה זו, צא מיד נגד קמטים (true off). חל רק כאשר מגן נגד קמטים מופעל." + } + }, + "delay_start_section": { + "name": "זיהוי התחלה מושהית", + "description": "התייחס למצב המתנה רציף בצריכת חשמל נמוכה כאל 'ממתין להתחלה' עד שהמחזור מתחיל בפועל.", + "data": { + "delay_start_detect_enabled": "אפשר זיהוי התחלה מושהית", + "delay_confirm_seconds": "התחלה מושהית: זמן אישור המתנה (שנ')", + "delay_timeout_hours": "התחלה מושהית: זמן המתנה מרבי (שע')" + }, + "data_description": { + "delay_start_detect_enabled": "כאשר מופעל, זינוק ניקוז קצר בהספק נמוך ואחריו כוח המתנה מתמשך מזוהה כהתחלה מושהית. חיישן המצב מראה 'ממתין להתחלה' במהלך ההשהיה. אין מעקב אחר זמן התוכנית או ההתקדמות עד שהמחזור מתחיל בפועל. מחייב את הגדרת start_threshold_w מעל עוצמת ספייק הניקוז.", + "delay_confirm_seconds": "ההספק חייב להישאר בין סף העצירה (W) לסף ההתחלה (W) למשך מספר השניות הזה לפני ש-WashData עוברת למצב 'ממתין להתחלה'. מסנן קפיצות קצרות בניווט בתפריט. ברירת מחדל: 60 שנ'.", + "delay_timeout_hours": "פסק זמן בטיחות: אם המכונה עדיין במצב 'ממתין להפעלה' לאחר שעות רבות כל כך, WashData מתאפס למצב כבוי. ברירת מחדל: 8 שעות." + } + }, + "external_triggers_section": { + "name": "טריגרים חיצוניים, דלת והשהיה", + "description": "חיישן אופציונלי לטריגר סיום חיצוני, חיישן דלת לזיהוי השהיה/נקי, ומתג לניתוק מתח בעת השהיה.", + "data": { + "external_end_trigger_enabled": "אפשר את טריגר סיום מחזור חיצוני", + "external_end_trigger": "חיישן סיום מחזור חיצוני", + "external_end_trigger_inverted": "היפוך היגיון טריגר (טריגר מופעל כבוי)", + "door_sensor_entity": "חיישן דלת", + "pause_cuts_power": "ניתוק חשמל בעת השהייה", + "switch_entity": "מתג ישות (עבור הפסקת חשמל)", + "notify_unload_delay_minutes": "עיכוב בהודעת כביסה ממתינה (דקות)" + }, + "data_description": { + "external_end_trigger_enabled": "אפשר ניטור של חיישן בינארי חיצוני כדי להפעיל השלמת מחזור.", + "external_end_trigger": "בחר ישות חיישן בינארי. כאשר חיישן זה מופעל, המחזור הנוכחי יסתיים במצב 'הושלם'.", + "external_end_trigger_inverted": "כברירת מחדל, ההדק מופעל כאשר החיישן מופעל. סמן את התיבה הזו כדי להפעיל כאשר החיישן נכבה במקום זאת.", + "door_sensor_entity": "חיישן בינארי אופציונלי לדלת המכונה (מופעל = פתוח, כבוי = סגור). כאשר הדלת נפתחת במהלך מחזור פעיל, WashData תאשר את המחזור כמושהה בכוונה. לאחר סיום המחזור, אם הדלת עדיין סגורה, המצב משתנה ל'נקי' עד לפתיחת הדלת. הערה: סגירת הדלת אינה ממשיכה אוטומטית מחזור מושהה - יש להפעיל את החידוש באופן ידני באמצעות לחצן חידוש מחזור או שירות.", + "pause_cuts_power": "בעת השהייה באמצעות לחצן או שירות השהה מחזור, כבה גם את ישות המתג המוגדרת. ייכנס לתוקף רק אם מוגדרת ישות מתג עבור מכשיר זה.", + "switch_entity": "ישות המתג למעבר בעת השהייה או חידוש. נדרש כאשר 'ניתוק חשמל בעת השהייה' מופעל.", + "notify_unload_delay_minutes": "שלח הודעה דרך ערוץ הודעת הסיום לאחר סיום המחזור והדלת לא נפתחה במשך דקות רבות כל כך (דורש חיישן דלת). הגדר ל-0 כדי להשבית. ברירת מחדל: 60 דקות." + } + }, + "pump_section": { + "name": "ניטור משאבה", + "description": "לסוג התקן משאבה בלבד. מפעיל אירוע אם מחזור משאבה נמשך מעבר למשך זה.", + "data": { + "pump_stuck_duration": "סף התראה על משאבה תקועה (ים) (משאבה בלבד)" + }, + "data_description": { + "pump_stuck_duration": "אם מחזור משאבה עדיין פועל לאחר שניות רבות כל כך, אירוע ha_washdata_pump_stuck מופעל פעם אחת. השתמש בזה כדי לזהות מנוע תקוע (הפעולה הטיפוסית של משאבת הבור היא מתחת ל-60 שניות). ברירת מחדל: 1800 שניות (30 דקות). סוג התקן משאבה בלבד." + } + }, + "device_link_section": { + "name": "קישור", + "description": "קישור באופן אופציונלי למכשיר ה-Voldata הזה למכשיר קיים (לדוגמה, לתקע החכם או לתוספת עצמה). לאחר מכן, מכשיר ה- Washdata מוצג כ-\"מדבק באמצעות\" אותו מכשיר. השאירו ריק כדי לשמור על שטיפת מידע כמכשיר עמידה.", + "data": { + "linked_device": "התקן המקושר" + }, + "data_description": { + "linked_device": "בחר את המכשיר כדי לחבר את שטף הנתונים. זה מוסיף הפניה \"מוגדרת באמצעות\" במרשם המכשיר; ו- WashData שומרת לעצמה את כרטיס המכשיר והגופים שלה. יש לנקות את הבחירה כדי להסיר את הקישור." + } + } + } + }, + "diagnostics": { + "title": "אבחון ותחזוקה", + "description": "הפעל פעולות תחזוקה כמו מיזוג מחזורים מפוצלים או העברת נתונים מאוחסנים.\n\n**שימוש באחסון**\n\n- גודל קובץ: {file_size_kb} KB\n- מחזורים: {cycle_count}\n- פרופילים: {profile_count}\n- עקבות ניפוי באגים: {debug_count}", + "menu_options": { + "reprocess_history": "תחזוקה: עיבוד ואופטימיזציה של נתונים", + "clear_debug_data": "נקה נתוני ניפוי באגים (פנה מקום)", + "wipe_history": "מחק את כל הנתונים עבור המכשיר הזה (בלתי הפיך)", + "export_import": "ייצוא/ייבוא ​​JSON עם הגדרות (העתק/הדבק)", + "menu_back": "← חזרה" + } + }, + "clear_debug_data": { + "title": "נקה נתוני ניפוי באגים", + "description": "האם אתה בטוח שברצונך למחוק את כל עקבות ניפוי באגים המאוחסנים? זה יפנה מקום אך יסיר מידע דירוג מפורט ממחזורים קודמים." + }, + "manage_cycles": { + "title": "נהל מחזורים", + "description": "מחזורים אחרונים:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "תיוג אוטומטי של מחזורים ישנים", + "select_cycle_to_label": "מחזור ספציפי של תווית", + "select_cycle_to_delete": "מחק מחזור", + "interactive_editor": "מיזוג/פיצול עורך אינטראקטיבי", + "trim_cycle_select": "נתוני חיתוך מחזור", + "menu_back": "← חזרה" + } + }, + "manage_cycles_empty": { + "title": "נהל מחזורים", + "description": "עדיין לא נרשמו מחזורים.", + "menu_options": { + "auto_label_cycles": "תיוג אוטומטי של מחזורים ישנים", + "menu_back": "← חזרה" + } + }, + "manage_profiles": { + "title": "נהל פרופילים", + "description": "סיכום פרופיל:\n{profile_summary}", + "menu_options": { + "create_profile": "צור פרופיל חדש", + "edit_profile": "ערוך/שנה שם פרופיל", + "delete_profile_select": "מחק פרופיל", + "profile_stats": "סטטיסטיקת פרופיל", + "cleanup_profile": "נקה היסטוריה - גרף ומחק", + "assign_profile_phases_select": "הקצה טווחי שלבים", + "menu_back": "← חזרה" + } + }, + "manage_profiles_empty": { + "title": "נהל פרופילים", + "description": "עדיין לא נוצרו פרופילים.", + "menu_options": { + "create_profile": "צור פרופיל חדש", + "menu_back": "← חזרה" + } + }, + "manage_phase_catalog": { + "title": "נהל את קטלוג השלבים", + "description": "קטלוג שלבים (ברירות מחדל עבור מכשיר זה + שלבים מותאמים אישית):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "צור שלב חדש", + "phase_catalog_edit_select": "ערוך שלב", + "phase_catalog_delete": "מחק את השלב", + "menu_back": "← חזרה" + } + }, + "phase_catalog_create": { + "title": "צור שלב מותאם אישית", + "description": "צור שם ותיאור שלב מותאמים אישית ובחר לאיזה סוג מכשיר הוא חל עליו.", + "data": { + "target_device_type": "סוג מכשיר יעד", + "phase_name": "שם השלב", + "phase_description": "תיאור השלב" + } + }, + "phase_catalog_edit_select": { + "title": "ערוך שלב מותאם אישית", + "description": "בחר שלב מותאם אישית לעריכה.", + "data": { + "phase_name": "שלב מותאם אישית" + } + }, + "phase_catalog_edit": { + "title": "ערוך שלב מותאם אישית", + "description": "שלב העריכה: {phase_name}", + "data": { + "phase_name": "שם השלב", + "phase_description": "תיאור השלב" + } + }, + "phase_catalog_delete": { + "title": "מחק שלב מותאם אישית", + "description": "בחר שלב מותאם אישית למחיקה. כל הטווחים שהוקצו באמצעות שלב זה יוסרו.", + "data": { + "phase_name": "שלב מותאם אישית" + } + }, + "assign_profile_phases": { + "title": "הקצה טווחי שלבים", + "description": "תצוגה מקדימה של שלב לפרופיל: **{profile_name}**\n\n{timeline_svg}\n\n**טווחים נוכחיים:**\n{current_ranges}\n\nהשתמש בפעולות למטה כדי להוסיף, לערוך, למחוק או לשמור טווחים.", + "menu_options": { + "assign_profile_phases_add": "הוסף טווח שלבים", + "assign_profile_phases_edit_select": "ערוך טווח שלבים", + "assign_profile_phases_delete": "מחק טווח שלבים", + "phase_ranges_clear": "נקה את כל הטווחים", + "assign_profile_phases_auto_detect": "זיהוי אוטומטי של שלבים", + "phase_ranges_save": "שמור והחזר", + "menu_back": "← חזרה" + } + }, + "assign_profile_phases_add": { + "title": "הוסף טווח שלבים", + "description": "הוסף טווח שלבים אחד לטיוטה הנוכחית.", + "data": { + "phase_name": "שָׁלָב", + "start_min": "דקת התחלה", + "end_min": "דקת סיום" + } + }, + "assign_profile_phases_edit_select": { + "title": "ערוך טווח שלבים", + "description": "בחר טווח לעריכה.", + "data": { + "range_index": "טווח שלבים" + } + }, + "assign_profile_phases_edit": { + "title": "ערוך טווח שלבים", + "description": "עדכן את טווח השלבים שנבחר.", + "data": { + "phase_name": "שָׁלָב", + "start_min": "דקת התחלה", + "end_min": "דקת סיום" + } + }, + "assign_profile_phases_delete": { + "title": "מחק טווח שלבים", + "description": "בחר טווח להסרה מהטיוטה.", + "data": { + "range_index": "טווח שלבים" + } + }, + "assign_profile_phases_auto_detect": { + "title": "שלבים שזוהו אוטומטית", + "description": "פרופיל: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} שלבים זוהו באופן אוטומטי.** בחר פעולה למטה.", + "data": { + "action": "פְּעוּלָה" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "שם זוהה שלבים", + "description": "פרופיל: **{profile_name}**\n\n**{detected_count} שלב(ים) זוהו:**\n{ranges_summary}\n\nהקצה שם לכל שלב." + }, + "assign_profile_phases_select": { + "title": "הקצה טווחי שלבים", + "description": "בחר פרופיל להגדרת טווחי פאזות.", + "data": { + "profile": "פּרוֹפִיל" + } + }, + "profile_stats": { + "title": "סטטיסטיקת פרופיל", + "description": "{stats_table}" + }, + "create_profile": { + "title": "צור פרופיל חדש", + "description": "צור פרופיל חדש באופן ידני או ממחזור עבר.\n\nדוגמאות לשמות פרופיל: 'מעדנים', 'חובה כבדה', 'כביסה מהירה'", + "data": { + "profile_name": "שם פרופיל", + "reference_cycle": "מחזור התייחסות (אופציונלי)", + "manual_duration": "משך ידני (דקות)" + } + }, + "edit_profile": { + "title": "ערוך פרופיל", + "description": "בחר פרופיל לשינוי שם", + "data": { + "profile": "פּרוֹפִיל" + } + }, + "rename_profile": { + "title": "ערוך את פרטי הפרופיל", + "description": "פרופיל נוכחי: {current_name}", + "data": { + "new_name": "שם פרופיל", + "manual_duration": "משך ידני (דקות)" + } + }, + "delete_profile_select": { + "title": "מחק פרופיל", + "description": "בחר פרופיל למחיקה", + "data": { + "profile": "פּרוֹפִיל" + } + }, + "delete_profile_confirm": { + "title": "אשר את מחיקת הפרופיל", + "description": "⚠️ פעולה זו תמחק לצמיתות את הפרופיל: {profile_name}", + "data": { + "unlabel_cycles": "הסר תווית ממחזורים באמצעות פרופיל זה" + } + }, + "auto_label_cycles": { + "title": "תיוג אוטומטי של מחזורים ישנים", + "description": "נמצאו {total_count} מחזורים בסך הכל. פרופילים: {profiles}", + "data": { + "confidence_threshold": "ביטחון מינימלי (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "בחר מחזור לתווית", + "description": "✓ = הושלם, ⚠ = התחדש, ✗ = הופסק", + "data": { + "cycle_id": "מַחזוֹר" + } + }, + "select_cycle_to_delete": { + "title": "מחק מחזור", + "description": "⚠️ פעולה זו תמחק לצמיתות את המחזור שנבחר", + "data": { + "cycle_id": "מחזור למחיקה" + } + }, + "label_cycle": { + "title": "מחזור התוויות", + "description": "{cycle_info}", + "data": { + "profile_name": "פּרוֹפִיל", + "new_profile_name": "שם פרופיל חדש (אם יוצר)" + } + }, + "post_process": { + "title": "היסטוריית תהליכים (מיזוג/פיצול)", + "description": "נקה היסטוריית מחזוריות על ידי מיזוג ריצות מקוטעות ופיצול מחזורים ממוזגים שגויים בתוך חלון זמן. הזן מספר שעות כדי להסתכל אחורה (השתמש ב-999999 עבור כולם).", + "data": { + "time_range": "חלון מבט לאחור (שעות)", + "gap_seconds": "מיזוג/פיצול פער (שניות)" + }, + "data_description": { + "time_range": "מספר השעות לסריקה לאיתור מחזורים מקוטעים למיזוג. השתמש ב-999999 כדי לעבד את כל הנתונים ההיסטוריים.", + "gap_seconds": "סף להחליט על פיצול מול מיזוג. פערים קצרים מזה מתמזגים. פערים ארוכים מזה מפוצלים. הורד את זה כדי לכפות פיצול במחזור שאוחד בצורה שגויה." + } + }, + "cleanup_profile": { + "title": "ניקוי היסטוריית - בחר פרופיל", + "description": "בחר פרופיל להצגה. פעולה זו תיצור גרף המציג את כל המחזורים הקודמים עבור פרופיל זה כדי לסייע בזיהוי חריגים.", + "data": { + "profile": "פּרוֹפִיל" + } + }, + "cleanup_select": { + "title": "נקה היסטוריה - גרף ומחק", + "description": "![גרף]({graph_url})\n\n**הצגת {profile_name}**\n\nהגרף מציג מחזורים בודדים כקווים צבעוניים. זהה חריגים (קווים רחוקים מהקבוצה) והתאמת צבעם ברשימה למטה כדי למחוק אותם.\n\n**בחר מחזורים למחיקה לצמיתות:**", + "data": { + "cycles_to_delete": "מחזורים למחיקה (בדוק צבעים תואמים)" + } + }, + "interactive_editor": { + "title": "מיזוג/פיצול עורך אינטראקטיבי", + "description": "בחר פעולה ידנית לביצוע בהיסטוריית המחזור שלך.", + "menu_options": { + "editor_split": "פיצול מחזור (מצא פערים)", + "editor_merge": "מיזוג מחזורים (הצטרפות לשברים)", + "editor_delete": "מחק מחזורים", + "menu_back": "← חזרה" + } + }, + "editor_select": { + "title": "בחר מחזורים", + "description": "בחר את המחזור/ים לעיבוד.\n\n{info_text}", + "data": { + "selected_cycles": "מחזורים" + } + }, + "editor_configure": { + "title": "הגדר והחל", + "description": "{preview_md}", + "data": { + "confirm_action": "פְּעוּלָה", + "merged_profile": "הפרופיל המתקבל", + "new_profile_name": "שם פרופיל חדש", + "confirm_commit": "כן, אני מאשר את הפעולה הזו", + "segment_0_profile": "פרופיל פלח 1", + "segment_1_profile": "פרופיל פלח 2", + "segment_2_profile": "פרופיל פלח 3", + "segment_3_profile": "פרופיל פלח 4", + "segment_4_profile": "פרופיל פלח 5", + "segment_5_profile": "פרופיל פלח 6", + "segment_6_profile": "פרופיל פלח 7", + "segment_7_profile": "פרופיל פלח 8", + "segment_8_profile": "פרופיל פלח 9", + "segment_9_profile": "פרופיל פלח 10" + } + }, + "editor_split_params": { + "title": "פיצול תצורה", + "description": "התאם את הרגישות לפיצול מחזור זה. כל מרווח סרק ארוך מזה יגרום לפיצול.", + "data": { + "split_mode": "שיטת פיצול" + } + }, + "editor_split_auto_params": { + "title": "תצורת פיצול אוטומטי", + "description": "התאם את הרגישות לפיצול המחזור הזה. כל פער חוסר פעילות ארוך יותר מזה יגרום לפיצול.", + "data": { + "min_gap_seconds": "סף פער הפיצול (שניות)" + } + }, + "editor_split_manual_params": { + "title": "חותמות זמן לפיצול ידני", + "description": "{preview_md}חלון המחזור: **{cycle_start_wallclock} → {cycle_end_wallclock}** בתאריך {cycle_date}.\n\nהזן חותמת זמן אחת או יותר לפיצול (`HH:MM` או `HH:MM:SS`), אחת בכל שורה. המחזור ייחתך בכל חותמת זמן — N חותמות זמן יפיקו N+1 מקטעים.", + "data": { + "split_timestamps": "פיצול חותמות זמן" + } + }, + "reprocess_history": { + "title": "תחזוקה: עיבוד ואופטימיזציה של נתונים", + "description": "מבצע ניקוי עמוק של נתונים היסטוריים:\n1. קיצוץ קריאות אפס הספק (שקט).\n2. מתקן תזמון/משך מחזור.\n3. מחשב מחדש את כל המודלים הסטטיסטיים (מעטפות).\n\n**הפעל את זה כדי לתקן בעיות נתונים או להחיל אלגוריתמים חדשים.**" + }, + "wipe_history": { + "title": "מחק היסטוריה (בדיקות בלבד)", + "description": "⚠️ פעולה זו תמחק לצמיתות את כל המחזורים והפרופילים המאוחסנים עבור המכשיר הזה. אי אפשר לבטל את זה!" + }, + "export_import": { + "title": "ייצוא/ייבוא ​​JSON", + "description": "העתק/הדבק מחזורים, פרופילים והגדרות עבור מכשיר זה. ייצא למטה או הדבק JSON כדי לייבא.", + "data": { + "mode": "פְּעוּלָה", + "json_payload": "השלם תצורה JSON" + }, + "data_description": { + "mode": "בחר ייצוא כדי להעתיק נתונים והגדרות נוכחיות, או ייבא כדי להדביק נתונים מיוצאים מהתקן WashData אחר.", + "json_payload": "לייצוא: העתק את ה-JSON הזה (כולל מחזורים, פרופילים וכל ההגדרות המכוונות). לייבוא: הדבק JSON שיוצא מ-WashData." + } + }, + "record_cycle": { + "title": "מחזור הקלטות", + "description": "הקלט באופן ידני מחזור כדי ליצור פרופיל נקי ללא הפרעות.\n\nסטטוס: **{status}**\nמשך: {duration} שניות\nדוגמאות: {samples}", + "menu_options": { + "record_refresh": "סטטוס רענון", + "record_stop": "הפסקת הקלטה (שמירה ועיבוד)", + "record_start": "התחל הקלטה חדשה", + "record_process": "עיבוד הקלטה אחרונה (חתוך ושמירה)", + "record_discard": "בטל את ההקלטה האחרונה", + "menu_back": "← חזרה" + } + }, + "record_process": { + "title": "תהליך הקלטת", + "description": "![גרף]({graph_url})\n\n**הגרף מציג הקלטה גולמית.** כחול = שמור, אדום = חיתוך מוצע.\n*הגרף סטטי ואינו מתעדכן עם שינויים בטפסים.*\n\nההקלטה הופסקה. הגזרות מיושרות לקצב הדגימה שזוהה (~{sampling_rate} שניות).\n\nמשך גולמי: {duration} שניות\nדוגמאות: {samples}", + "data": { + "head_trim": "התחלה של חיתוך (שניות)", + "tail_trim": "קצץ סוף (שניות)", + "save_mode": "שמור יעד", + "profile_name": "שם פרופיל" + } + }, + "learning_feedbacks": { + "title": "משוב למידה", + "description": "משוב ממתין: {count}\n\n{pending_table}\n\nבחר בקשת סקירת מחזור לעיבוד.", + "menu_options": { + "learning_feedbacks_pick": "סקור משוב ממתין", + "learning_feedbacks_dismiss_all": "דחה את כל המשובים הממתינים", + "menu_back": "← חזרה" + } + }, + "learning_feedbacks_pick": { + "title": "בחר משוב לסקירה", + "description": "בחר בקשת סקירת מחזור לפתיחה.", + "data": { + "selected_feedback": "בחר מחזור לסקירה" + } + }, + "learning_feedbacks_empty": { + "title": "משוב למידה", + "description": "לא נמצאו בקשות משוב ממתינות.", + "menu_options": { + "menu_back": "← חזרה" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "דחה את כל המשובים הממתינים", + "description": "אתה עומד לדחות **{count}** בקשות משוב ממתינות. מחזורים שנדחו יישארו בהיסטוריה שלך, אך לא יתרמו אות למידה חדש. לא ניתן לבטל זאת.\n\n✅ סמן את התיבה למטה ולחץ על **שלח** כדי לדחות את הכול.\n❌ השאר אותה לא מסומנת ולחץ על **שלח** כדי לבטל ולחזור.", + "data": { + "confirm_dismiss_all": "אישור: לדחות את כל בקשות המשוב הממתינות" + } + }, + "resolve_feedback": { + "title": "פתרון משוב", + "description": "{comparison_img}{candidates_table}\n**פרופיל שזוהה**: {detected_profile} ({confidence_pct}%)\n**משך משוער**: {est_duration_min} דקות\n**משך בפועל**: {act_duration_min} דקות\n\n__בחר פעולה למטה:__", + "data": { + "action": "מה היית רוצה לעשות?", + "corrected_profile": "תוכנית נכונה (אם מתקן)", + "corrected_duration": "משך נכון בשניות (אם מתקן)" + } + }, + "trim_cycle_select": { + "title": "מחזור חיתוך - בחר מחזור", + "description": "בחר מחזור לקצץ. ✓ = הושלם, ⚠ = התחדש, ✗ = הופסק", + "data": { + "cycle_id": "מַחזוֹר" + } + }, + "trim_cycle": { + "title": "מחזור חיתוך", + "description": "חלון נוכחי: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "פְּעוּלָה" + } + }, + "trim_cycle_start": { + "title": "הגדר תחילת חיתוך", + "description": "חלון מחזור: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nהתחלה נוכחית: **{current_wallclock}** (+{current_offset_min} דקות מתחילת המחזור)\n\nבחר שעת התחלה חדשה באמצעות בורר השעון למטה.", + "data": { + "trim_start_time": "שעת התחלה חדשה", + "trim_start_min": "התחלה חדשה (דקות מתחילת המחזור)" + } + }, + "trim_cycle_end": { + "title": "הגדר סיום חיתוך", + "description": "חלון מחזור: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nסיום נוכחי: **{current_wallclock}** (+{current_offset_min} דקות מתחילת המחזור)\n\nבחר שעת סיום חדשה באמצעות בורר השעון למטה.", + "data": { + "trim_end_time": "שעת סיום חדשה", + "trim_end_min": "סוף חדש (דקות מתחילת המחזור)" + } + } + }, + "error": { + "import_failed": "הייבוא ​​נכשל. אנא הדבק JSON ייצוא חוקי של WashData.", + "select_exactly_one": "בחר בדיוק מחזור אחד עבור פעולה זו.", + "select_at_least_one": "בחר לפחות מחזור אחד עבור פעולה זו.", + "select_at_least_two": "בחר לפחות שני מחזורים עבור פעולה זו.", + "profile_exists": "פרופיל בשם זה כבר קיים", + "rename_failed": "שינוי שם הפרופיל נכשל. ייתכן שהפרופיל אינו קיים או ששם חדש כבר נלקח.", + "assignment_failed": "הקצאת הפרופיל למחזור נכשלה", + "duplicate_phase": "שלב בשם זה כבר קיים.", + "phase_not_found": "השלב שנבחר לא נמצא.", + "invalid_phase_name": "שם השלב לא יכול להיות ריק.", + "phase_name_too_long": "שם השלב ארוך מדי.", + "invalid_phase_range": "טווח השלבים אינו חוקי. סוף חייב להיות גדול מההתחלה.", + "invalid_phase_timestamp": "חותמת הזמן אינה חוקית. השתמש בפורמט YYYY-MM-DD HH:MM או HH:MM.", + "overlapping_phase_ranges": "טווחי השלבים חופפים. התאם את הטווחים ונסה שוב.", + "incomplete_phase_row": "כל שורת שלב חייבת לכלול שלב בתוספת ערכי התחלה/סיום מלאים עבור המצב שנבחר.", + "timestamp_mode_cycle_required": "אנא בחר מחזור מקור בעת שימוש במצב חותמת זמן.", + "no_phase_ranges": "אין עדיין טווחי שלבים זמינים עבור הפעולה הזו.", + "feedback_notification_title": "WashData: מחזור אימות ({device})", + "feedback_notification_message": "**מכשיר**: {device}\n**תוכנית**: {program} ({confidence}% ביטחון)\n**זמן**: {time}\n\nWashData זקוק לעזרתכם כדי לאמת את המחזור שזוהה.\n\nאנא עבור אל **הגדרות > מכשירים ושירותים > WashData > קבע תצורה > משוב למידה** כדי לאשר או לתקן תוצאה זו.", + "suggestions_ready_notification_title": "WashData: הגדרות מוצעות מוכנות ({device})", + "suggestions_ready_notification_message": "חיישן **הגדרות מוצעות** מדווח כעת על המלצות **{count}** שניתן לפעול.\n\nכדי לסקור ולהחיל אותם: **הגדרות > מכשירים ושירותים > WashData > הגדר > הגדרות מתקדמות > החל ערכים מוצעים**.\n\nהצעות הן אופציונליות ומוצגות לבדיקה לפני השמירה.", + "trim_range_invalid": "ההתחלה חייבת להיות לפני הסוף. נא להתאים את נקודות החיתוך שלך.", + "trim_failed": "החיתוך נכשל. ייתכן שהמחזור כבר לא קיים או שהחלון ריק.", + "invalid_split_timestamp": "חותמת הזמן אינה חוקית או מחוץ לחלון המחזור. השתמש ב-HH:MM או HH:MM:SS בתוך חלון המחזור.", + "no_split_segments_found": "לא ניתן היה ליצור מקטעים חוקיים מחותמות הזמן האלה. ודא שכל חותמת זמן נמצאת בתוך חלון המחזור ושאורך כל מקטע הוא לפחות דקה אחת.", + "auto_tune_suggestion": "{device_type} {device_title} זוהו מחזורי רפאים. שינוי הספק המינימלי: {current_min}W -> {new_min}W (לא מוחל אוטומטית).", + "auto_tune_title": "כוונון אוטומטי של WashData", + "auto_tune_fallback": "{device_type} {device_title} זוהו מחזורי רפאים. שינוי הספק המינימלי: {current_min}W -> {new_min}W (לא מוחל אוטומטית)." + }, + "abort": { + "no_cycles_found": "לא נמצאו מחזורים", + "no_split_segments_found": "לא נמצאו מקטעים ניתנים לפיצול במחזור שנבחר.", + "cycle_not_found": "לא ניתן היה לטעון את המחזור שנבחר.", + "no_power_data": "אין נתוני מעקב כוח זמינים עבור המחזור שנבחר.", + "no_profiles_found": "לא נמצאו פרופילים. תחילה צור פרופיל.", + "no_profiles_for_matching": "אין פרופילים זמינים להתאמה. תחילה צור פרופילים.", + "no_unlabeled_cycles": "כל המחזורים כבר מסומנים", + "no_suggestions": "אין עדיין ערכים מוצעים זמינים. הפעל כמה מחזורים ונסה שוב.", + "no_predictions": "אין תחזיות", + "no_custom_phases": "לא נמצאו שלבים מותאמים אישית.", + "reprocess_success": "{count} מחזורים עיבדו מחדש בהצלחה.", + "debug_data_cleared": "ניקה בהצלחה נתוני ניפוי באגים מ-{count} מחזורים." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "התחלת מחזור", + "cycle_finish": "סיום מחזור", + "cycle_live": "התקדמות חיה" + } + }, + "common_text": { + "options": { + "unlabeled": "(ללא תווית)", + "create_new_profile": "צור פרופיל חדש", + "remove_label": "הסר תווית", + "no_reference_cycle": "(ללא מחזור התייחסות)", + "all_device_types": "כל סוגי המכשירים", + "current_suffix": "(נוֹכְחִי)", + "editor_select_info": "בחר מחזור אחד לפיצול, או 2+ מחזורים למיזוג.", + "editor_select_info_split": "בחר מחזור אחד לפיצול.", + "editor_select_info_merge": "בחר 2+ מחזורים למיזוג.", + "editor_select_info_delete": "בחר 1+ מחזורים למחיקה.", + "no_cycles_recorded": "עדיין לא נרשמו מחזורים.", + "profile_summary_line": "- **{name}**: {count} מחזורים, {avg}מ' ממוצע", + "no_profiles_created": "עדיין לא נוצרו פרופילים.", + "phase_preview": "תצוגה מקדימה של שלב", + "phase_preview_no_curve": "עקומת פרופיל ממוצעת אינה זמינה עדיין. הפעל/תווית מחזורים נוספים עבור פרופיל זה.", + "average_power_curve": "עקומת כוח ממוצעת", + "unit_min": "דקה", + "profile_option_fmt": "{name} ({count} מחזורים, ~{duration}מ' ממוצע)", + "profile_option_short_fmt": "{name} ({count} מחזורים, ~{duration} מ')", + "deleted_cycles_from_profile": "נמחקו {count} מחזורים מ-{profile}.", + "not_enough_data_graph": "אין מספיק נתונים כדי ליצור גרף.", + "no_profiles_found": "לא נמצאו פרופילים.", + "cycle_info_fmt": "מחזור: {start}, {duration} מ', נוכחי: {label}", + "top_candidates_header": "### מועמדים מובילים", + "tbl_profile": "פּרוֹפִיל", + "tbl_confidence": "אֵמוּן", + "tbl_correlation": "מִתאָם", + "tbl_duration_match": "התאמת משך", + "detected_profile": "זוהה פרופיל", + "estimated_duration": "משך זמן משוער", + "actual_duration": "משך בפועל", + "choose_action": "__בחר פעולה למטה:__", + "tbl_count": "לִסְפּוֹר", + "tbl_avg": "ממוצע", + "tbl_min": "מינימום", + "tbl_max": "מקסימום", + "tbl_energy_avg": "אנרגיה (ממוצע)", + "tbl_energy_total": "אנרגיה (סה\"כ)", + "tbl_consistency": "לְהַכִיל.", + "tbl_last_run": "ריצה אחרונה", + "graph_legend_title": "מקרא גרף", + "graph_legend_body": "הפס הכחול מייצג את טווח צריכת החשמל המינימלי והמקסימלי שנצפה. הקו מציג את עקומת הכוח הממוצעת.", + "recording_preview": "תצוגה מקדימה של הקלטה", + "trim_start": "תחילת חיתוך", + "trim_end": "קצץ סוף", + "split_preview_title": "פיצול תצוגה מקדימה", + "split_preview_found_fmt": "נמצאו {count} פלחים.", + "split_preview_confirm_fmt": "לחץ על אשר כדי לפצל מחזור זה ל-{count} מחזורים נפרדים.", + "merge_preview_title": "מיזוג תצוגה מקדימה", + "merge_preview_joining_fmt": "מצטרף ל-{count} מחזורים. הפערים יתמלאו בקריאות של 0W.", + "editor_delete_preview_title": "תצוגה מקדימה למחיקה", + "editor_delete_preview_intro": "המחזורים שנבחרו יימחקו לצמיתות:", + "editor_delete_preview_confirm": "לחץ על אשר כדי למחוק לצמיתות את רשומות המחזור האלה.", + "no_power_preview": "*אין נתוני חשמל זמינים לתצוגה מקדימה.*", + "profile_comparison": "השוואת פרופילים", + "actual_cycle_label": "מחזור זה (בפועל)", + "storage_usage_header": "שימוש באחסון", + "storage_file_size": "גודל קובץ", + "storage_cycles": "מחזורים", + "storage_profiles": "פרופילים", + "storage_debug_traces": "איתור באגים", + "overview_suffix": "סקירה כללית", + "phase_preview_for_profile": "תצוגה מקדימה של שלב לפרופיל", + "current_ranges_header": "טווחים נוכחיים", + "assign_phases_help": "השתמש בפעולות למטה כדי להוסיף, לערוך, למחוק או לשמור טווחים.", + "profile_summary_header": "סיכום פרופיל", + "recent_cycles_header": "מחזורים אחרונים", + "trim_cycle_preview_title": "תצוגה מקדימה של חיתוך מחזור", + "trim_cycle_preview_no_data": "אין נתוני חשמל זמינים עבור מחזור זה.", + "trim_cycle_preview_kept_suffix": "נשמר", + "table_program": "תָכְנִית", + "table_when": "מתי", + "table_length": "משך", + "table_match": "התאמה", + "table_profile": "פּרוֹפִיל", + "table_cycles": "מחזורים", + "table_avg_length": "משך ממוצע", + "table_last_run": "ריצה אחרונה", + "table_avg_energy": "אנרגיה ממוצעת", + "table_detected_program": "תוכנית שזוהתה", + "table_confidence": "אֵמוּן", + "table_reported": "דווח", + "phase_builtin_suffix": "(מובנה)", + "phase_none_available": "אין שלבים זמינים.", + "settings_deprecation_warning": "⚠️ **סוג מכשיר שהוצא משימוש:** {device_type} מתוכנן להסרה במהדורה עתידית. הצינור התואם של WashData אינו מייצר תוצאות אמינות עבור מחלקת מכשיר זה. השילוב שלך ממשיך לעבוד במהלך תקופת ההוצאה משימוש; כדי להשתיק אזהרה זו, החלף את **סוג ההתקן** למטה לאחד מהסוגים הנתמכים (מכונת כביסה, מייבש, משולבת כביסה-מייבש, מדיח כלים, פרייר אוויר, מכונת לחם או משאבה), או ל-**אחר (מתקדם)** אם המכשיר שלך אינו תואם לאף אחד מהסוגים הנתמכים. **אחר (מתקדם)** מספק ברירות מחדל כלליות בכוונה שאינן מכוונות לאף מכשיר ספציפי, כך שתצטרך להגדיר בעצמך ספים, פסקי זמן ופרמטרים תואמים; כל ההגדרות הקיימות שלך נשמרות בעת המעבר.", + "phase_other_device_types": "סוגי מכשירים אחרים:" + } + }, + "interactive_editor_action": { + "options": { + "split": "פיצול מחזור (מצא פערים)", + "merge": "מיזוג מחזורים (הצטרפות לשברים)", + "delete": "מחק מחזורים" + } + }, + "split_mode": { + "options": { + "auto": "זיהוי אוטומטי של פערי חוסר פעילות", + "manual": "חותמות זמן ידניות" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "תחזוקה: עיבוד ואופטימיזציה של נתונים", + "clear_debug_data": "נקה נתוני ניפוי באגים (פנה מקום)", + "wipe_history": "מחק את כל הנתונים עבור המכשיר הזה (בלתי הפיך)", + "export_import": "ייצוא/ייבוא ​​JSON עם הגדרות (העתק/הדבק)" + } + }, + "export_import_mode": { + "options": { + "export": "ייצוא כל הנתונים", + "import": "ייבוא/מיזוג נתונים" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "תיוג אוטומטי של מחזורים ישנים", + "select_cycle_to_label": "מחזור ספציפי של תווית", + "select_cycle_to_delete": "מחק מחזור", + "interactive_editor": "מיזוג/פיצול עורך אינטראקטיבי", + "trim_cycle": "נתוני חיתוך מחזור" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "צור פרופיל חדש", + "edit_profile": "ערוך/שנה שם פרופיל", + "delete_profile": "מחק פרופיל", + "profile_stats": "סטטיסטיקת פרופיל", + "cleanup_profile": "נקה היסטוריה - גרף ומחק", + "assign_phases": "הקצה טווחי שלבים" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "צור שלב חדש", + "edit_custom_phase": "ערוך שלב", + "delete_custom_phase": "מחק את השלב" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "הוסף טווח שלבים", + "edit_range": "ערוך טווח שלבים", + "delete_range": "מחק טווח שלבים", + "clear_ranges": "נקה את כל הטווחים", + "auto_detect_ranges": "זיהוי אוטומטי של שלבים", + "save_ranges": "שמור והחזר" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "סטטוס רענון", + "stop_recording": "הפסקת הקלטה (שמירה ועיבוד)", + "start_recording": "התחל הקלטה חדשה", + "process_recording": "עיבוד הקלטה אחרונה (חתוך ושמירה)", + "discard_recording": "בטל את ההקלטה האחרונה" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "צור פרופיל חדש", + "existing_profile": "הוסף לפרופיל קיים", + "discard": "בטל את ההקלטה" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "אשר - זיהוי נכון", + "correct": "נכון - בחר תוכנית/משך", + "ignore": "התעלם - מחזור חיובי/רועש כוזב", + "delete": "מחק - הסר מההיסטוריה" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "שם והחל שלבים", + "cancel": "חזור ללא שינויים" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "הגדר נקודת התחלה", + "set_end": "הגדר נקודת סיום", + "reset": "אפס למשך מלא", + "apply": "החל חיתוך", + "cancel": "לְבַטֵל" + } + }, + "device_type": { + "options": { + "washing_machine": "מְכוֹנַת כְּבִיסָה", + "dryer": "מְיַבֵּשׁ", + "washer_dryer": "משולבת מכונת כביסה-מייבש", + "dishwasher": "מַדִיחַ כֵּלִים", + "coffee_machine": "מכונת קפה (הוצא משימוש)", + "ev": "רכב חשמלי (הוצא משימוש)", + "air_fryer": "אייר פרייר", + "heat_pump": "משאבת חום (הוצא משימוש)", + "bread_maker": "מכין לחם", + "pump": "משאבה / משאבת בור", + "oven": "תנור (הוצא משימוש)", + "other": "אחר (מתקדם)" + } + } + }, + "services": { + "label_cycle": { + "name": "מחזור התוויות", + "description": "הקצה פרופיל קיים למחזור עבר, או הסר את התווית.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה לתיוג." + }, + "cycle_id": { + "name": "מזהה מחזור", + "description": "המזהה של המחזור לתיוג." + }, + "profile_name": { + "name": "שם פרופיל", + "description": "שם פרופיל קיים (צור פרופילים בתפריט ניהול פרופילים). השאר ריק כדי להסיר את התווית." + } + } + }, + "create_profile": { + "name": "צור פרופיל", + "description": "צור פרופיל חדש (עצמאי או מבוסס על מחזור התייחסות).", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה." + }, + "profile_name": { + "name": "שם פרופיל", + "description": "שם עבור הפרופיל החדש (למשל 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "מזהה מחזור עזר", + "description": "מזהה מחזור אופציונלי לבסס פרופיל זה." + } + } + }, + "delete_profile": { + "name": "מחק פרופיל", + "description": "מחק פרופיל ובאופן אופציונלי בטל תווית של מחזורים באמצעותו.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה." + }, + "profile_name": { + "name": "שם פרופיל", + "description": "הפרופיל שיש למחוק." + }, + "unlabel_cycles": { + "name": "ביטול תווית מחזורים", + "description": "הסר את תווית הפרופיל ממחזורים באמצעות פרופיל זה." + } + } + }, + "auto_label_cycles": { + "name": "תיוג אוטומטי של מחזורים ישנים", + "description": "סמן רטרואקטיבית מחזורים ללא תווית באמצעות התאמת פרופיל.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה." + }, + "confidence_threshold": { + "name": "סף ביטחון", + "description": "בטחון התאמה מינימלי (0.50-0.95) להחלת תוויות." + } + } + }, + "export_config": { + "name": "ייצוא תצורה", + "description": "ייצא את הפרופילים, המחזורים וההגדרות של המכשיר הזה לקובץ JSON (לכל מכשיר).", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה לייצוא." + }, + "path": { + "name": "נָתִיב", + "description": "נתיב קובץ אבסולוטי אופציונלי לכתיבה (ברירת המחדל היא /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "ייבוא ​​תצורה", + "description": "ייבא פרופילים, מחזורים והגדרות עבור מכשיר זה מקובץ ייצוא JSON (ייתכן שההגדרות יידרסו).", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה לייבא אליו." + }, + "path": { + "name": "נָתִיב", + "description": "נתיב מוחלט לקובץ ה-JSON הייצוא לייבוא." + } + } + }, + "submit_cycle_feedback": { + "name": "שלח משוב מחזור", + "description": "אשר או תקן תוכנית שזוהתה אוטומטית לאחר מחזור שהושלם. ספק `entry_id` (מתקדם) או `device_id` (מומלץ).", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "התקן WashData (מומלץ במקום entry_id)." + }, + "entry_id": { + "name": "מזהה כניסה", + "description": "מזהה ערך התצורה של המכשיר (חלופה ל-device_id)." + }, + "cycle_id": { + "name": "מזהה מחזור", + "description": "מזהה המחזור המוצג בהודעת המשוב/יומנים." + }, + "user_confirmed": { + "name": "אשר את התוכנית שזוהתה", + "description": "הגדר אמת אם התוכנית שזוהתה נכונה." + }, + "corrected_profile": { + "name": "פרופיל מתוקן", + "description": "אם לא אושר, ספק את שם הפרופיל/התוכנית הנכון." + }, + "corrected_duration": { + "name": "משך זמן מתוקן (שניות)", + "description": "משך זמן מתוקן אופציונלי בשניות." + }, + "notes": { + "name": "הערות", + "description": "הערות אופציונליות לגבי מחזור זה." + } + } + }, + "record_start": { + "name": "התחלת מחזור שיא", + "description": "התחל להקליט ידני של מחזור נקי (עוקף את כל ההיגיון התואם).", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה להקליט." + } + } + }, + "record_stop": { + "name": "עצירת מחזור שיא", + "description": "הפסק את ההקלטה הידנית.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר מכונת הכביסה להפסקת ההקלטה." + } + } + }, + "trim_cycle": { + "name": "מחזור חיתוך", + "description": "חתוך את נתוני ההספק של מחזור עבר לחלון זמן מסוים.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר ה-WashData." + }, + "cycle_id": { + "name": "מזהה מחזור", + "description": "מזהה המחזור שיש לקצץ." + }, + "trim_start_s": { + "name": "התחלה של חיתוך (שניות)", + "description": "שמור נתונים מהקיזוז זה בשניות. ברירת מחדל 0." + }, + "trim_end_s": { + "name": "קצץ סוף (שניות)", + "description": "שמור נתונים עד לקיזוז זה תוך שניות. ברירת מחדל = משך מלא." + } + } + }, + "pause_cycle": { + "name": "השהה מחזור", + "description": "השהה את המחזור הפעיל עבור התקן WashData.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר ה-WashData להשהות." + } + } + }, + "resume_cycle": { + "name": "המשך מחזור", + "description": "המשך מחזור מושהה עבור מכשיר WashData.", + "fields": { + "device_id": { + "name": "הֶתקֵן", + "description": "מכשיר WashData לחידוש." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "רִיצָה" + }, + "match_ambiguity": { + "name": "התאמה עמימות" + } + }, + "sensor": { + "washer_state": { + "name": "מְדִינָה", + "state": { + "off": "כבוי", + "idle": "לְהִתְבַּטֵל", + "starting": "מתחיל", + "running": "רִיצָה", + "paused": "מושהה", + "user_paused": "הושהה על ידי המשתמש", + "ending": "סִיוּם", + "finished": "גָמוּר", + "anti_wrinkle": "נגד קמטים", + "delay_wait": "מחכה להתחלה", + "interrupted": "מוּפרָע", + "force_stopped": "כוח נעצר", + "rinse": "לִשְׁטוֹף", + "unknown": "לֹא יְדוּעַ", + "clean": "נקי" + } + }, + "washer_program": { + "name": "תָכְנִית" + }, + "time_remaining": { + "name": "זמן שנותר" + }, + "total_duration": { + "name": "משך זמן כולל" + }, + "cycle_progress": { + "name": "הִתקַדְמוּת" + }, + "current_power": { + "name": "כוח נוכחי" + }, + "elapsed_time": { + "name": "זמן שחלף" + }, + "debug_info": { + "name": "מידע על ניפוי באגים" + }, + "match_confidence": { + "name": "רמת ביטחון בהתאמה" + }, + "top_candidates": { + "name": "מועמדים מובילים", + "state": { + "none": "אַף לֹא אֶחָד" + } + }, + "current_phase": { + "name": "השלב הנוכחי" + }, + "wash_phase": { + "name": "שָׁלָב" + }, + "profile_cycle_count": { + "name": "ספירת פרופיל {profile_name}", + "unit_of_measurement": "מחזורים" + }, + "suggestions": { + "name": "הגדרות מוצעות" + }, + "pump_runs_today": { + "name": "הפעלת המשאבה (24 השעות האחרונות)" + }, + "cycle_count": { + "name": "ספירת מחזור" + } + }, + "select": { + "program_select": { + "name": "תוכנית מחזור", + "state": { + "auto_detect": "זיהוי אוטומטי" + } + } + }, + "button": { + "force_end_cycle": { + "name": "כוח סיום מחזור" + }, + "pause_cycle": { + "name": "השהה מחזור" + }, + "resume_cycle": { + "name": "המשך מחזור" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "נדרש device_id חוקי." + }, + "cycle_id_required": { + "message": "נדרש cycle_id חוקי." + }, + "profile_name_required": { + "message": "נדרש profile_name חוקי." + }, + "device_not_found": { + "message": "המכשיר לא נמצא." + }, + "no_config_entry": { + "message": "לא נמצאה ערך תצורה עבור המכשיר." + }, + "integration_not_loaded": { + "message": "אינטגרציה לא נטענה עבור מכשיר זה." + }, + "cycle_not_found_or_no_power": { + "message": "המחזור לא נמצא או שאין לו נתוני חשמל." + }, + "trim_failed_empty_window": { + "message": "החיתוך נכשל - המחזור לא נמצא, אין נתוני צריכת חשמל או שהחלון שנוצר ריק." + }, + "trim_invalid_range": { + "message": "trim_end_s חייב להיות גדול יותר trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/hi.json b/custom_components/ha_washdata/translations/hi.json new file mode 100644 index 0000000..6ff7238 --- /dev/null +++ b/custom_components/ha_washdata/translations/hi.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "वॉशडेटा सेटअप", + "description": "अपनी वॉशिंग मशीन या अन्य उपकरण कॉन्फ़िगर करें।\n\nपावर सेंसर आवश्यक है.\n\n**इसके बाद, आपसे पूछा जाएगा कि क्या आप अपनी पहली प्रोफ़ाइल बनाना चाहते हैं।**", + "data": { + "name": "डिवाइस का नाम", + "device_type": "डिवाइस का प्रकार", + "power_sensor": "पावर सेंसर", + "min_power": "न्यूनतम विद्युत सीमा (डब्ल्यू)" + }, + "data_description": { + "name": "इस उपकरण के लिए एक अनुकूल नाम (जैसे, 'वॉशिंग मशीन', 'डिशवॉशर')।", + "device_type": "यह किस प्रकार का उपकरण है? विशिष्ट पहचान और लेबलिंग में मदद करता है।", + "power_sensor": "सेंसर इकाई जो आपके स्मार्ट प्लग से वास्तविक समय की बिजली खपत (वाट में) की रिपोर्ट करती है।", + "min_power": "इस सीमा से ऊपर पावर रीडिंग (वाट में) इंगित करती है कि उपकरण चल रहा है। अधिकांश उपकरणों के लिए 2W से प्रारंभ करें।" + } + }, + "first_profile": { + "title": "प्रथम प्रोफ़ाइल बनाएं", + "description": "एक **साइकिल** आपके उपकरण का पूरा चलना है (उदाहरण के लिए, कपड़े धोने का भार)। एक **प्रोफ़ाइल** इन चक्रों को प्रकार के आधार पर समूहित करता है (उदाहरण के लिए, 'कॉटन', 'क्विक वॉश')। अब एक प्रोफ़ाइल बनाने से एकीकरण की अवधि का तुरंत अनुमान लगाने में मदद मिलती है।\n\nयदि यह प्रोफ़ाइल स्वचालित रूप से पता नहीं चलती है तो आप डिवाइस नियंत्रण में मैन्युअल रूप से इसका चयन कर सकते हैं।\n\n**इस चरण को छोड़ने के लिए 'प्रोफ़ाइल नाम' को खाली छोड़ दें।**", + "data": { + "profile_name": "प्रोफ़ाइल नाम (वैकल्पिक)", + "manual_duration": "अनुमानित अवधि (मिनट)" + } + } + }, + "error": { + "cannot_connect": "जोडने में विफल", + "invalid_auth": "अमान्य प्रमाणीकरण", + "unknown": "अप्रत्याशित त्रुटि" + }, + "abort": { + "already_configured": "डिवाइस पहले से ही कॉन्फ़िगर किया गया है" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "सीखने की प्रतिक्रिया की समीक्षा करें", + "settings": "सेटिंग्स", + "notifications": "सूचनाएं", + "manage_cycles": "चक्र प्रबंधित करें", + "manage_profiles": "प्रोफ़ाइल प्रबंधित करें", + "manage_phase_catalog": "चरण कैटलॉग प्रबंधित करें", + "record_cycle": "रिकार्ड चक्र (मैनुअल)", + "diagnostics": "निदान एवं रखरखाव" + } + }, + "apply_suggestions": { + "title": "सुझाए गए मान कॉपी करें", + "description": "यह वर्तमान सुझाए गए मानों को आपकी सहेजी गई सेटिंग्स में कॉपी कर देगा। यह एक बार की कार्रवाई है और ऑटो-ओवरराइट को सक्षम नहीं करती है।\n\nकॉपी करने के लिए सुझाए गए मान:\n{suggested}", + "data": { + "confirm": "प्रतिलिपि कार्रवाई की पुष्टि करें" + } + }, + "apply_suggestions_confirm": { + "title": "सुझाए गए परिवर्तनों की समीक्षा करें", + "description": "वॉशडेटा को **{pending_count}** लागू करने के लिए सुझाए गए मान मिले।\n\nपरिवर्तन:\n{changes}\n\n✅ इन परिवर्तनों को तुरंत लागू करने और सहेजने के लिए नीचे दिए गए बॉक्स को चेक करें और **सबमिट** पर क्लिक करें।\n❌ इसे अनियंत्रित छोड़ दें और रद्द करने और वापस जाने के लिए **सबमिट** पर क्लिक करें।", + "data": { + "confirm_apply_suggestions": "इन परिवर्तनों को लागू करें और सहेजें" + } + }, + "settings": { + "title": "सेटिंग्स", + "description": "{deprecation_warning}ट्यून डिटेक्शन थ्रेशोल्ड, सीखना और उन्नत व्यवहार। अधिकांश सेटअपों के लिए डिफ़ॉल्ट अच्छी तरह से काम करते हैं।\n\nवर्तमान में उपलब्ध सुझाई गई सेटिंग्स: {suggestions_count}.\nयदि यह 0 से अधिक है, तो उन्नत सेटिंग्स खोलें और सिफारिशें देखने के लिए 'सुझाए गए मान लागू करें' चुनें।", + "data": { + "apply_suggestions": "सुझाए गए मान लागू करें", + "device_type": "डिवाइस का प्रकार", + "power_sensor": "पावर सेंसर इकाई", + "min_power": "न्यूनतम बिजली (डब्ल्यू)", + "off_delay": "चक्र समाप्ति विलंब", + "notify_actions": "अधिसूचना क्रियाएँ", + "notify_people": "जब तक ये लोग घर नहीं पहुंच जाते तब तक डिलिवरी में देरी करें", + "notify_only_when_home": "चयनित व्यक्ति के घर आने तक विलंबित सूचनाएं", + "notify_fire_events": "स्वचालन घटनाएँ सक्रिय करें", + "notify_start_services": "चक्र प्रारंभ - अधिसूचना लक्ष्य", + "notify_finish_services": "चक्र समाप्त - अधिसूचना लक्ष्य", + "notify_live_services": "लाइव प्रगति - अधिसूचना लक्ष्य", + "notify_before_end_minutes": "समापन पूर्व अधिसूचना (समाप्ति से कुछ मिनट पहले)", + "notify_title": "अधिसूचना शीर्षक", + "notify_icon": "अधिसूचना चिह्न", + "notify_start_message": "संदेश स्वरूप प्रारंभ करें", + "notify_finish_message": "संदेश प्रारूप समाप्त करें", + "notify_pre_complete_message": "पूर्व-समापन संदेश प्रारूप", + "show_advanced": "उन्नत सेटिंग्स संपादित करें (अगला चरण)" + }, + "data_description": { + "apply_suggestions": "लर्निंग एल्गोरिदम द्वारा सुझाए गए मानों को फ़ॉर्म में कॉपी करने के लिए इस बॉक्स को चेक करें। सहेजने से पहले समीक्षा करें.\n\nसुझाव (सीखने से):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "उपकरण का प्रकार चुनें (वॉशिंग मशीन, ड्रायर, डिशवॉशर, कॉफी मशीन, ईवी)। बेहतर संगठन के लिए यह टैग प्रत्येक चक्र के साथ संग्रहीत किया जाता है।", + "power_sensor": "सेंसर इकाई वास्तविक समय बिजली (वाट में) की रिपोर्ट करती है। आप ऐतिहासिक डेटा खोए बिना इसे कभी भी बदल सकते हैं-यदि आप स्मार्ट प्लग बदलते हैं तो यह उपयोगी है।", + "min_power": "इस सीमा से ऊपर पावर रीडिंग (वाट में) इंगित करती है कि उपकरण चल रहा है। अधिकांश उपकरणों के लिए 2W से प्रारंभ करें।", + "off_delay": "पूरा होने को चिह्नित करने के लिए सेकंडों में सुचारू शक्ति को स्टॉप सीमा से नीचे रहना चाहिए। डिफ़ॉल्ट: 120s.", + "notify_actions": "सूचनाओं (स्क्रिप्ट, नोटिफाई, टीटीएस, आदि) के लिए चलाने के लिए वैकल्पिक होम असिस्टेंट क्रिया अनुक्रम। यदि सेट किया गया है, तो कार्रवाई फ़ॉलबैक सूचना सेवा से पहले चलती है।", + "notify_people": "उपस्थिति गेटिंग के लिए उपयोग की जाने वाली लोग इकाइयाँ। सक्षम होने पर, अधिसूचना वितरण में तब तक देरी होती है जब तक कि कम से कम एक चयनित व्यक्ति घर पर न आ जाए।", + "notify_only_when_home": "यदि सक्षम है, तो कम से कम एक चयनित व्यक्ति के घर आने तक सूचनाएं विलंबित करें।", + "notify_fire_events": "यदि सक्षम किया गया है, तो ऑटोमेशन को चलाने के लिए चक्र प्रारंभ और समाप्ति (ha_washdata_cycle_started और ha_washdata_cycle_ended) के लिए होम असिस्टेंट ईवेंट सक्रिय करें।", + "notify_start_services": "एक या अधिक सेवाओं को चक्र शुरू होने पर सचेत करने के लिए सूचित करना। प्रारंभ सूचनाओं को छोड़ने के लिए खाली छोड़ें।", + "notify_finish_services": "जब कोई चक्र समाप्त होता है या पूरा होने के करीब होता है तो एक या अधिक सेवाओं को सचेत करने के लिए सूचित करते हैं। समाप्ति सूचनाएं छोड़ने के लिए खाली छोड़ें।", + "notify_live_services": "चक्र चलने के दौरान लाइव प्रगति अपडेट प्राप्त करने के लिए एक या अधिक सेवाओं को सूचित करें (केवल मोबाइल साथी ऐप)। लाइव अपडेट छोड़ने के लिए खाली छोड़ें।", + "notify_before_end_minutes": "एक अधिसूचना ट्रिगर करने के लिए चक्र के अनुमानित अंत से कुछ मिनट पहले। अक्षम करने के लिए 0 पर सेट करें. डिफ़ॉल्ट: 0.", + "notify_title": "सूचनाओं के लिए कस्टम शीर्षक. {device} प्लेसहोल्डर का समर्थन करता है.", + "notify_icon": "सूचनाओं के लिए उपयोग किया जाने वाला चिह्न (उदा. mdi:washing-machine)। बिना चिह्न के भेजने के लिए खाली छोड़ें.", + "notify_start_message": "एक चक्र शुरू होने पर संदेश भेजा जाता है. {device} प्लेसहोल्डर का समर्थन करता है.", + "notify_finish_message": "एक चक्र समाप्त होने पर संदेश भेजा जाता है. {device}, {duration}, {program}, {energy_kwh}, {cost} प्लेसहोल्डर्स का समर्थन करता है।", + "notify_pre_complete_message": "चक्र चलने के दौरान आवर्ती लाइव प्रगति अपडेट का पाठ। {device}, {minutes}, {program} प्लेसहोल्डर्स का समर्थन करता है।" + } + }, + "notifications": { + "title": "सूचनाएं", + "description": "अधिसूचना चैनल, टेम्पलेट और वैकल्पिक लाइव मोबाइल प्रगति अपडेट कॉन्फ़िगर करें।", + "data": { + "notify_actions": "अधिसूचना क्रियाएँ", + "notify_people": "जब तक ये लोग घर नहीं पहुंच जाते तब तक डिलिवरी में देरी करें", + "notify_only_when_home": "चयनित व्यक्ति के घर आने तक विलंबित सूचनाएं", + "notify_fire_events": "स्वचालन घटनाएँ सक्रिय करें", + "notify_start_services": "चक्र प्रारंभ - अधिसूचना लक्ष्य", + "notify_finish_services": "चक्र समाप्त - अधिसूचना लक्ष्य", + "notify_live_services": "लाइव प्रगति - अधिसूचना लक्ष्य", + "notify_before_end_minutes": "समापन पूर्व अधिसूचना (समाप्ति से कुछ मिनट पहले)", + "notify_live_interval_seconds": "लाइव अपडेट अंतराल (सेकंड)", + "notify_live_overrun_percent": "लाइव अपडेट ओवररन भत्ता (%)", + "notify_live_chronometer": "क्रोनोमीटर उलटी गिनती घड़ी", + "notify_title": "अधिसूचना शीर्षक", + "notify_icon": "अधिसूचना चिह्न", + "notify_start_message": "संदेश स्वरूप प्रारंभ करें", + "notify_finish_message": "संदेश प्रारूप समाप्त करें", + "notify_pre_complete_message": "पूर्व-समापन संदेश प्रारूप", + "energy_price_entity": "ऊर्जा मूल्य - इकाई (वैकल्पिक)", + "energy_price_static": "ऊर्जा मूल्य - स्थैतिक मूल्य (वैकल्पिक)", + "go_back": "सहेजे बिना वापस जाएँ", + "notify_reminder_message": "अनुस्मारक संदेश प्रारूप", + "notify_timeout_seconds": "स्वतः-बर्खास्तगी के बाद (सेकंड)", + "notify_channel": "अधिसूचना चैनल (स्थिति/लाइव/अनुस्मारक)", + "notify_finish_channel": "समाप्त अधिसूचना चैनल" + }, + "data_description": { + "go_back": "इसे चुनें और ऊपर के किसी भी परिवर्तन को छोड़कर मुख्य मेनू पर लौटने के लिए Submit पर क्लिक करें।", + "notify_actions": "सूचनाओं (स्क्रिप्ट, नोटिफाई, टीटीएस, आदि) के लिए चलाने के लिए वैकल्पिक होम असिस्टेंट क्रिया अनुक्रम। यदि सेट किया गया है, तो कार्रवाई फ़ॉलबैक सूचना सेवा से पहले चलती है।", + "notify_people": "उपस्थिति गेटिंग के लिए उपयोग की जाने वाली लोग इकाइयाँ। सक्षम होने पर, अधिसूचना वितरण में तब तक देरी होती है जब तक कि कम से कम एक चयनित व्यक्ति घर पर न आ जाए।", + "notify_only_when_home": "यदि सक्षम है, तो कम से कम एक चयनित व्यक्ति के घर आने तक सूचनाएं विलंबित करें।", + "notify_fire_events": "यदि सक्षम किया गया है, तो ऑटोमेशन को चलाने के लिए चक्र प्रारंभ और समाप्ति (ha_washdata_cycle_started और ha_washdata_cycle_ended) के लिए होम असिस्टेंट ईवेंट सक्रिय करें।", + "notify_start_services": "चक्र शुरू होने पर अलर्ट करने के लिए एक या अधिक सूचना सेवाएँ (उदाहरण के लिए notify.mobile_app_my_phone)। प्रारंभ सूचनाओं को छोड़ने के लिए खाली छोड़ें।", + "notify_finish_services": "जब कोई चक्र समाप्त होता है या पूरा होने के करीब होता है तो एक या अधिक सेवाओं को सचेत करने के लिए सूचित करते हैं। समाप्ति सूचनाएं छोड़ने के लिए खाली छोड़ें।", + "notify_live_services": "चक्र चलने के दौरान लाइव प्रगति अपडेट प्राप्त करने के लिए एक या अधिक सेवाओं को सूचित करें (केवल मोबाइल साथी ऐप)। लाइव अपडेट छोड़ने के लिए खाली छोड़ें।", + "notify_before_end_minutes": "एक अधिसूचना ट्रिगर करने के लिए चक्र के अनुमानित अंत से कुछ मिनट पहले। अक्षम करने के लिए 0 पर सेट करें. डिफ़ॉल्ट: 0.", + "notify_live_interval_seconds": "चलते समय कितनी बार प्रगति अद्यतन भेजे जाते हैं। कम मान अधिक प्रतिक्रियाशील होते हैं लेकिन अधिक सूचनाओं का उपभोग करते हैं। न्यूनतम 30 सेकंड लागू किया जाता है - 30 सेकंड से नीचे का मान स्वचालित रूप से 30 सेकंड तक पूर्णांकित हो जाता है।", + "notify_live_overrun_percent": "लंबे/अधिक चलने वाले चक्रों के लिए अनुमानित अद्यतन संख्या से अधिक सुरक्षा मार्जिन (उदाहरण के लिए 20% 1.2x अनुमानित अद्यतन की अनुमति देता है)।", + "notify_live_chronometer": "सक्षम होने पर, प्रत्येक लाइव अपडेट में अनुमानित समाप्ति समय के लिए एक क्रोनोमीटर उलटी गिनती शामिल होती है। नोटिफिकेशन टाइमर अपडेट के बीच डिवाइस पर स्वचालित रूप से टिक जाता है (केवल एंड्रॉइड साथी ऐप)।", + "notify_title": "सूचनाओं के लिए कस्टम शीर्षक. {device} प्लेसहोल्डर का समर्थन करता है.", + "notify_icon": "सूचनाओं के लिए उपयोग किया जाने वाला चिह्न (उदा. mdi:washing-machine)। बिना चिह्न के भेजने के लिए खाली छोड़ें.", + "notify_start_message": "एक चक्र शुरू होने पर संदेश भेजा जाता है. {device} प्लेसहोल्डर का समर्थन करता है.", + "notify_finish_message": "एक चक्र समाप्त होने पर संदेश भेजा जाता है. {device}, {duration}, {program}, {energy_kwh}, {cost} प्लेसहोल्डर्स का समर्थन करता है।", + "energy_price_entity": "एक संख्यात्मक इकाई का चयन करें जो वर्तमान बिजली मूल्य (जैसे sensor.electricity_price, input_number.energy_rate) रखती है। सेट होने पर, {cost} प्लेसहोल्डर को सक्षम करता है। यदि दोनों कॉन्फ़िगर किए गए हैं तो स्थैतिक मान पर प्राथमिकता ली जाती है।", + "energy_price_static": "प्रति किलोवाट-घंटा बिजली की कीमत तय। सेट होने पर, {cost} प्लेसहोल्डर को सक्षम करता है। यदि कोई इकाई ऊपर कॉन्फ़िगर की गई है तो उसे अनदेखा कर दिया जाता है। संदेश टेम्पलेट में अपना मुद्रा चिह्न जोड़ें, उदा. {cost} €.", + "notify_reminder_message": "एक बार के अनुस्मारक का पाठ अनुमानित समाप्ति से पहले मिनटों की कॉन्फ़िगर संख्या में भेजा गया। उपरोक्त आवर्ती लाइव अपडेट से अलग। {device}, {minutes}, {program} प्लेसहोल्डर्स का समर्थन करता है।", + "notify_timeout_seconds": "इतने सेकंड के बाद स्वचालित रूप से सूचनाएं खारिज कर दी जाती हैं (साथी ऐप 'टाइमआउट' के रूप में अग्रेषित)। खारिज होने तक उन्हें बनाए रखने के लिए 0 पर सेट करें। डिफ़ॉल्ट: 0.", + "notify_channel": "Android स्थिति, लाइव प्रगति और अनुस्मारक सूचनाओं के लिए सहयोगी ऐप अधिसूचना चैनल। पहली बार चैनल नाम का उपयोग करने पर चैनल की ध्वनि और महत्व को साथी ऐप में कॉन्फ़िगर किया जाता है - वॉशडेटा केवल चैनल का नाम सेट करता है। ऐप डिफॉल्ट के लिए खाली छोड़ दें।", + "notify_finish_channel": "तैयार अलर्ट के लिए अलग Android चैनल (रिमाइंडर और लॉन्ड्री-वेटिंग नाग द्वारा भी उपयोग किया जाता है) ताकि इसकी अपनी ध्वनि हो सके। उपरोक्त चैनल का पुन: उपयोग करने के लिए इसे खाली छोड़ दें।", + "notify_pre_complete_message": "चक्र चलने के दौरान आवर्ती लाइव प्रगति अपडेट का पाठ। {device}, {minutes}, {program} प्लेसहोल्डर्स का समर्थन करता है।" + } + }, + "advanced_settings": { + "title": "एडवांस सेटिंग", + "description": "ट्यून डिटेक्शन थ्रेशोल्ड, सीखना और उन्नत व्यवहार। अधिकांश सेटअपों के लिए डिफ़ॉल्ट अच्छी तरह से काम करते हैं।", + "sections": { + "suggestions_section": { + "name": "सुझाई गई सेटिंग्स", + "description": "{suggestions_count} सीखे गए सुझाव उपलब्ध हैं। सहेजने से पहले प्रस्तावित परिवर्तनों की समीक्षा करने के लिए 'सुझाए गए मान लागू करें' चुनें।", + "data": { + "apply_suggestions": "सुझाए गए मान लागू करें" + }, + "data_description": { + "apply_suggestions": "शिक्षण एल्गोरिदम से सुझाए गए मानों के लिए समीक्षा चरण खोलने के लिए इस बॉक्स को चेक करें। कुछ भी स्वचालित रूप से सहेजा नहीं जाता है.\n\nसुझाव (सीखने से):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "पहचान और पावर थ्रेशोल्ड", + "description": "उपकरण कब चालू या बंद माना जाता है, ऊर्जा गेट, और स्थिति परिवर्तन का समय।", + "data": { + "start_duration_threshold": "आरंभ डिबाउंस अवधि", + "start_energy_threshold": "प्रारंभ ऊर्जा गेट (Wh)", + "completion_min_seconds": "समापन न्यूनतम रनटाइम (सेकंड)", + "start_threshold_w": "प्रारंभ सीमा (डब्ल्यू)", + "stop_threshold_w": "स्टॉप थ्रेशोल्ड (डब्ल्यू)", + "end_energy_threshold": "अंत ऊर्जा गेट (डब्ल्यूएच)", + "running_dead_zone": "रनिंग डेड ज़ोन (सेकंड)", + "end_repeat_count": "दोहराव गणना समाप्त करें", + "min_off_gap": "चक्रों के बीच न्यूनतम अंतराल", + "sampling_interval": "नमूनाकरण अंतराल" + }, + "data_description": { + "start_duration_threshold": "इस अवधि (सेकंड) से कम की संक्षिप्त पावर स्पाइक्स पर ध्यान न दें। वास्तविक चक्र शुरू होने से पहले बूट स्पाइक्स (उदाहरण के लिए, 2s के लिए 60W) को फ़िल्टर करता है। डिफ़ॉल्ट: 5s.", + "start_energy_threshold": "पुष्टि के लिए START चरण के दौरान साइकिल को कम से कम इतनी ऊर्जा (Wh) जमा करनी होगी। डिफ़ॉल्ट: 0.005 क.", + "completion_min_seconds": "इससे छोटे चक्रों को 'बाधित' के रूप में चिह्नित किया जाएगा, भले ही वे स्वाभाविक रूप से समाप्त हो जाएं। डिफ़ॉल्ट: 600s.", + "start_threshold_w": "एक चक्र शुरू करने के लिए शक्ति को इस सीमा से ऊपर उठना होगा। अपनी स्टैंडबाय पावर से अधिक सेट करें। डिफ़ॉल्ट: min_power + 1 W.", + "stop_threshold_w": "एक चक्र को समाप्त करने के लिए शक्ति को इस सीमा से नीचे गिरना चाहिए। शोर उत्पन्न करने वाले झूठे सिरों से बचने के लिए शून्य से ठीक ऊपर सेट करें। डिफ़ॉल्ट: 0.6 * न्यूनतम_शक्ति।", + "end_energy_threshold": "चक्र तभी समाप्त होता है जब अंतिम ऑफ-डिले विंडो में ऊर्जा इस मान (Wh) से कम हो। यदि बिजली शून्य के करीब उतार-चढ़ाव करती है तो झूठे सिरों को रोकता है। डिफ़ॉल्ट: 0.05 क.", + "running_dead_zone": "चक्र शुरू होने के बाद, प्रारंभिक स्पिन-अप चरण के दौरान गलत अंत का पता लगाने से रोकने के लिए इतने सेकंड के लिए पावर डिप्स को अनदेखा करें। अक्षम करने के लिए 0 पर सेट करें. डिफ़ॉल्ट: 0s.", + "end_repeat_count": "चक्र समाप्त होने से पहले अंतिम शर्त (off_delay के लिए सीमा से नीचे की शक्ति) को लगातार कितनी बार पूरा किया जाना चाहिए। उच्च मान विराम के दौरान गलत अंत को रोकते हैं। डिफ़ॉल्ट: 1.", + "min_off_gap": "यदि कोई चक्र पिछले समाप्त होने के इतने सेकंड के भीतर शुरू होता है, तो उन्हें एक ही चक्र में विलीन कर दिया जाएगा।", + "sampling_interval": "सेकंड में न्यूनतम नमूना अंतराल. इस दर से अधिक तेज़ अपडेट को नज़रअंदाज कर दिया जाएगा। डिफ़ॉल्ट: 30s (वॉशिंग मशीन, वॉशर-ड्रायर और डिशवॉशर के लिए 2s)।" + } + }, + "matching_section": { + "name": "प्रोफ़ाइल मिलान और सीखना", + "description": "WashData चल रहे चक्रों को सीखी हुई प्रोफ़ाइलों से कितनी आक्रामकता से मिलाता है और कब प्रतिक्रिया मांगता है।", + "data": { + "profile_match_min_duration_ratio": "प्रोफ़ाइल मिलान न्यूनतम अवधि अनुपात (0.1-1.0)", + "profile_match_interval": "प्रोफ़ाइल मिलान अंतराल (सेकंड)", + "profile_match_threshold": "प्रोफ़ाइल मिलान सीमा", + "profile_unmatch_threshold": "प्रोफ़ाइल बेजोड़ सीमा", + "auto_label_confidence": "ऑटो-लेबल कॉन्फिडेंस (0-1)", + "learning_confidence": "प्रतिक्रिया अनुरोध आत्मविश्वास (0-1)", + "suppress_feedback_notifications": "फीडबैक अधिसूचनाएँ दबाएँ", + "duration_tolerance": "अवधि सहनशीलता (0-0.5)", + "smoothing_window": "स्मूथिंग विंडो (नमूने)", + "profile_duration_tolerance": "प्रोफ़ाइल मिलान अवधि सहनशीलता (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "प्रोफ़ाइल मिलान के लिए न्यूनतम अवधि अनुपात. चलने के चक्र का मिलान करने के लिए कम से कम प्रोफ़ाइल अवधि का यह अंश होना चाहिए। निचला = पहले का मिलान। डिफ़ॉल्ट: 0.50 (50%).", + "profile_match_interval": "एक चक्र के दौरान प्रोफ़ाइल मिलान का प्रयास कितनी बार (सेकेंड में) करना है। कम मान तेजी से पहचान प्रदान करते हैं लेकिन अधिक CPU का उपयोग करते हैं। डिफ़ॉल्ट: 300 सेकंड (5 मिनट)।", + "profile_match_threshold": "किसी प्रोफ़ाइल को मिलान मानने के लिए न्यूनतम DTW समानता स्कोर (0.0-1.0) आवश्यक है। उच्चतर = सख्त मिलान, कम गलत सकारात्मकता। डिफ़ॉल्ट: 0.4.", + "profile_unmatch_threshold": "DTW समानता स्कोर (0.0-1.0) जिसके नीचे पहले से मिलान की गई प्रोफ़ाइल को अस्वीकार कर दिया जाता है। झिलमिलाहट को रोकने के लिए मैच सीमा से कम होना चाहिए। डिफ़ॉल्ट: 0.35.", + "auto_label_confidence": "पूरा होने पर स्वचालित रूप से चक्रों को इस आत्मविश्वास पर या उससे ऊपर लेबल करें (0.0-1.0)।", + "learning_confidence": "किसी ईवेंट के माध्यम से उपयोगकर्ता सत्यापन का अनुरोध करने के लिए न्यूनतम आत्मविश्वास (0.0-1.0)।", + "suppress_feedback_notifications": "सक्षम होने पर, वॉशडेटा अभी भी आंतरिक रूप से ट्रैक करेगा और फीडबैक का अनुरोध करेगा, लेकिन आपसे चक्रों की पुष्टि करने के लिए लगातार सूचनाएं नहीं दिखाएगा। तब उपयोगी जब चक्रों का हमेशा सही पता लगाया जाता है और आपको संकेत ध्यान भटकाने वाले लगते हैं।", + "duration_tolerance": "दौड़ के दौरान समय-शेष अनुमानों के लिए सहनशीलता (0.0-0.5 0-50% से मेल खाती है)।", + "smoothing_window": "मूविंग-एवरेज स्मूथिंग के लिए उपयोग की गई हालिया पावर रीडिंग की संख्या।", + "profile_duration_tolerance": "कुल अवधि विचरण (0.0-0.5) के लिए प्रोफ़ाइल मिलान द्वारा उपयोग की जाने वाली सहनशीलता। डिफ़ॉल्ट व्यवहार: ±25%।" + } + }, + "timing_section": { + "name": "समय, रखरखाव और डिबग", + "description": "वॉचडॉग, प्रगति रीसेट, ऑटो-रखरखाव और डिबग एंटिटी का प्रदर्शन।", + "data": { + "watchdog_interval": "वॉचडॉग अंतराल (सेकंड)", + "no_update_active_timeout": "नो-अपडेट टाइमआउट", + "progress_reset_delay": "प्रगति रीसेट विलंब (सेकंड)", + "auto_maintenance": "ऑटो-रखरखाव सक्षम करें", + "expose_debug_entities": "डिबग इकाइयों को उजागर करें", + "save_debug_traces": "डिबग ट्रेस सहेजें" + }, + "data_description": { + "watchdog_interval": "दौड़ते समय वॉचडॉग की जाँच के बीच के सेकंड। डिफ़ॉल्ट: 5s. चेतावनी: झूठे स्टॉप से ​​बचने के लिए सुनिश्चित करें कि यह आपके सेंसर के अपडेट अंतराल से अधिक है (उदाहरण के लिए, यदि सेंसर हर 60 के दशक में अपडेट होता है, तो 65+ का उपयोग करें)।", + "no_update_active_timeout": "पब्लिश-ऑन-चेंज सेंसर के लिए: यदि इतने सेकंड तक कोई अपडेट नहीं आता है तो केवल सक्रिय चक्र को बलपूर्वक समाप्त करें। कम-शक्ति पूर्णता अभी भी ऑफ डिले का उपयोग करती है।", + "progress_reset_delay": "एक चक्र पूरा होने के बाद (100%), प्रगति को 0% पर रीसेट करने से पहले निष्क्रियता के इतने सेकंड प्रतीक्षा करें। डिफ़ॉल्ट: 300s.", + "auto_maintenance": "ऑटो-रखरखाव सक्षम करें (नमूने की मरम्मत करें, टुकड़ों को मर्ज करें)।", + "expose_debug_entities": "डिबगिंग के लिए उन्नत सेंसर (आत्मविश्वास, चरण, अस्पष्टता) दिखाएं।", + "save_debug_traces": "इतिहास में विस्तृत रैंकिंग और पावर ट्रेस डेटा संग्रहीत करें (भंडारण उपयोग बढ़ जाता है)।" + } + }, + "anti_wrinkle_section": { + "name": "एंटी-रिंकल शील्ड (ड्रायर)", + "description": "घोस्ट चक्रों को रोकने के लिए चक्र समाप्त होने के बाद ड्रम घुमावों को अनदेखा करें। केवल ड्रायर / वॉशर-ड्रायर।", + "data": { + "anti_wrinkle_enabled": "एंटी-रिंकल शील्ड (केवल ड्रायर)", + "anti_wrinkle_max_power": "एंटी-रिंकल मैक्स पावर (डब्ल्यू)", + "anti_wrinkle_max_duration": "एंटी-रिंकल अधिकतम अवधि", + "anti_wrinkle_exit_power": "एंटी-रिंकल एग्ज़िट पावर (डब्ल्यू)" + }, + "data_description": { + "anti_wrinkle_enabled": "भूत चक्र (केवल ड्रायर/वॉशर-ड्रायर) को रोकने के लिए एक चक्र समाप्त होने के बाद कम-शक्ति वाले ड्रम घुमावों पर ध्यान न दें।", + "anti_wrinkle_max_power": "इस शक्ति पर या उससे कम फटने को नए चक्रों के बजाय एंटी-रिंकल रोटेशन के रूप में माना जाता है। केवल तभी लागू होता है जब एंटी-रिंकल शील्ड सक्षम हो।", + "anti_wrinkle_max_duration": "अधिकतम बर्स्ट लंबाई को एंटी-रिंकल रोटेशन के रूप में माना जाता है। केवल तभी लागू होता है जब एंटी-रिंकल शील्ड सक्षम हो।", + "anti_wrinkle_exit_power": "यदि बिजली इस स्तर से नीचे गिरती है, तो तुरंत एंटी-रिंकल से बाहर निकलें (ट्रू ऑफ)। केवल तभी लागू होता है जब एंटी-रिंकल शील्ड सक्षम हो।" + } + }, + "delay_start_section": { + "name": "विलंबित प्रारंभ पहचान", + "description": "लगातार कम-शक्ति स्टैंडबाय को 'वेटिंग टू स्टार्ट' मानें जब तक कि चक्र वास्तव में शुरू न हो जाए।", + "data": { + "delay_start_detect_enabled": "विलंबित प्रारंभ का पता लगाना सक्षम करें", + "delay_confirm_seconds": "विलंबित प्रारंभ: स्टैंडबाय पुष्टि समय (सेकंड)", + "delay_timeout_hours": "विलंबित प्रारंभ: अधिकतम प्रतीक्षा समय (एच)" + }, + "data_description": { + "delay_start_detect_enabled": "सक्षम होने पर, एक संक्षिप्त कम-पावर ड्रेन स्पाइक और उसके बाद निरंतर स्टैंडबाय पावर को विलंबित शुरुआत के रूप में पहचाना जाता है। देरी के दौरान राज्य सेंसर 'वेटिंग टू स्टार्ट' दिखाता है। जब तक चक्र वास्तव में शुरू नहीं होता तब तक किसी भी कार्यक्रम के समय या प्रगति को ट्रैक नहीं किया जाता है। ड्रेन स्पाइक पावर के ऊपर स्टार्ट_थ्रेसहोल्ड_w को सेट करने की आवश्यकता है।", + "delay_confirm_seconds": "WashData के 'वेटिंग टू स्टार्ट' में प्रवेश करने से पहले शक्ति को इतने सेकंड तक स्टॉप थ्रेशोल्ड (W) और स्टार्ट थ्रेशोल्ड (W) के बीच रहना चाहिए। यह मेनू-नेविगेशन की संक्षिप्त पीक को फ़िल्टर करता है। डिफ़ॉल्ट: 60 सेकंड।", + "delay_timeout_hours": "सुरक्षा समयबाह्य: यदि मशीन इतने घंटों के बाद भी 'स्टार्ट होने की प्रतीक्षा' में है, तो वॉशडेटा बंद पर रीसेट हो जाता है। डिफ़ॉल्ट: 8 घंटे." + } + }, + "external_triggers_section": { + "name": "बाहरी ट्रिगर, दरवाज़ा और विराम", + "description": "वैकल्पिक बाहरी अंत-ट्रिगर सेंसर, विराम/स्वच्छ पहचान के लिए दरवाज़ा सेंसर, और विराम पर पावर-कट स्विच।", + "data": { + "external_end_trigger_enabled": "बाहरी साइकिल अंत ट्रिगर सक्षम करें", + "external_end_trigger": "बाहरी चक्र अंत सेंसर", + "external_end_trigger_inverted": "इनवर्ट ट्रिगर लॉजिक (ट्रिगर ऑन ऑफ)", + "door_sensor_entity": "दरवाज़ा सेंसर", + "pause_cuts_power": "रुकते समय बिजली काट दें", + "switch_entity": "स्विच एंटिटी (पावर कट रोकने के लिए)", + "notify_unload_delay_minutes": "लाँड्री प्रतीक्षा अधिसूचना विलंब (मिनट)" + }, + "data_description": { + "external_end_trigger_enabled": "चक्र पूर्णता को ट्रिगर करने के लिए बाहरी बाइनरी सेंसर की निगरानी सक्षम करें।", + "external_end_trigger": "एक बाइनरी सेंसर इकाई का चयन करें. जब यह सेंसर चालू हो जाएगा, तो वर्तमान चक्र 'पूर्ण' स्थिति के साथ समाप्त हो जाएगा।", + "external_end_trigger_inverted": "डिफ़ॉल्ट रूप से, सेंसर चालू होने पर ट्रिगर सक्रिय हो जाता है। इसके बजाय सेंसर बंद होने पर इस बॉक्स को चालू करने के लिए चेक करें।", + "door_sensor_entity": "मशीन के दरवाजे के लिए वैकल्पिक बाइनरी सेंसर (चालू = खुला, बंद = बंद)। जब सक्रिय चक्र के दौरान दरवाज़ा खुलता है, तो वॉशडेटा पुष्टि करेगा कि चक्र जानबूझकर रोका गया है। चक्र समाप्त होने के बाद, यदि दरवाज़ा अभी भी बंद है, तो दरवाज़ा खुलने तक स्थिति 'स्वच्छ' में बदल जाती है। ध्यान दें: दरवाज़ा बंद करने से रुका हुआ चक्र अपने आप फिर से शुरू नहीं होता है - रेज़्यूमे को रेज़्यूमे साइकिल बटन या सेवा के माध्यम से मैन्युअल रूप से चालू किया जाना चाहिए।", + "pause_cuts_power": "पॉज़ साइकल बटन या सेवा के माध्यम से रुकते समय, कॉन्फ़िगर की गई स्विच इकाई को भी बंद कर दें। केवल तभी प्रभावी होता है जब इस डिवाइस के लिए एक स्विच इकाई कॉन्फ़िगर की गई हो।", + "switch_entity": "रुकने या फिर से शुरू करने पर टॉगल करने वाली स्विच इकाई। 'रुकते समय बिजली काटना' सक्षम होने पर यह आवश्यक है।", + "notify_unload_delay_minutes": "चक्र समाप्त होने के बाद फिनिश नोटिफिकेशन चैनल के माध्यम से एक अधिसूचना भेजें और इतने मिनटों तक दरवाजा नहीं खोला गया है (डोर सेंसर की आवश्यकता है)। अक्षम करने के लिए 0 पर सेट करें. डिफ़ॉल्ट: 60 मिनट." + } + }, + "pump_section": { + "name": "पंप मॉनिटर", + "description": "केवल पंप डिवाइस प्रकार। यदि पंप चक्र इस अवधि से अधिक चलता है तो एक इवेंट सक्रिय करता है।", + "data": { + "pump_stuck_duration": "पंप अटक चेतावनी सीमा (सेकंड) (केवल पंप)" + }, + "data_description": { + "pump_stuck_duration": "यदि एक पंप चक्र इतने सेकंड के बाद भी चल रहा है, तो ha_washdata_pump_stuck इवेंट को एक बार सक्रिय किया जाता है। जाम मोटर का पता लगाने के लिए इसका उपयोग करें (सामान्य नाबदान पंप रन 60 सेकंड से कम है)। डिफ़ॉल्ट: 1800 सेकंड (30 मिनट)। केवल पंप डिवाइस प्रकार." + } + }, + "device_link_section": { + "name": "डिवाइस लिंक", + "description": "वैकल्पिक रूप से इस वॉशडेटा डिवाइस को मौजूदा Home Assistant डिवाइस से लिंक करें (उदाहरण के लिए स्मार्ट प्लग या उपकरण स्वयं)। फिर वॉशडेटा डिवाइस को उस डिवाइस के माध्यम से \"कनेक्टेड\" के रूप में दिखाया जाता है। वॉशडेटा को एक स्टैंडअलोन डिवाइस के रूप में रखने के लिए इसे खाली छोड़ दें।", + "data": { + "linked_device": "लिंक्ड डिवाइस" + }, + "data_description": { + "linked_device": "वॉशडेटा को कनेक्ट करने के लिए डिवाइस का चयन करें। यह डिवाइस रजिस्ट्री में 'कनेक्टेड थ्रू' संदर्भ जोड़ता है; वॉशडेटा अपना स्वयं का डिवाइस कार्ड और इकाइयां रखता है। लिंक हटाने के लिए चयन साफ़ करें." + } + } + } + }, + "diagnostics": { + "title": "निदान एवं रखरखाव", + "description": "खंडित चक्रों को मर्ज करने या संग्रहीत डेटा को स्थानांतरित करने जैसी रखरखाव क्रियाएँ चलाएँ।\n\n**भंडारण उपयोग**\n\n- फ़ाइल का आकार: {file_size_kb} KB\n- चक्र: {cycle_count}\n- प्रोफ़ाइल: {profile_count}\n- डीबग निशान: {debug_count}", + "menu_options": { + "reprocess_history": "रखरखाव: डेटा को पुन: संसाधित और अनुकूलित करें", + "clear_debug_data": "डिबग डेटा साफ़ करें (स्थान खाली करें)", + "wipe_history": "इस डिवाइस का सारा डेटा मिटा दें (अपरिवर्तनीय)", + "export_import": "सेटिंग्स के साथ JSON निर्यात/आयात करें (कॉपी/पेस्ट करें)", + "menu_back": "← वापस" + } + }, + "clear_debug_data": { + "title": "डिबग डेटा साफ़ करें", + "description": "क्या आप वाकई सभी संग्रहीत डिबग निशान हटाना चाहते हैं? इससे जगह तो खाली हो जाएगी लेकिन पिछले चक्रों की विस्तृत रैंकिंग जानकारी हट जाएगी।" + }, + "manage_cycles": { + "title": "चक्र प्रबंधित करें", + "description": "हाल के चक्र:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "पुरानी साइकिलों को ऑटो-लेबल करें", + "select_cycle_to_label": "विशिष्ट चक्र लेबल करें", + "select_cycle_to_delete": "चक्र हटाएँ", + "interactive_editor": "मर्ज/स्प्लिट इंटरैक्टिव संपादक", + "trim_cycle_select": "ट्रिम साइकिल डेटा", + "menu_back": "← वापस" + } + }, + "manage_cycles_empty": { + "title": "चक्र प्रबंधित करें", + "description": "अभी तक कोई चक्र रिकॉर्ड नहीं किया गया.", + "menu_options": { + "auto_label_cycles": "पुरानी साइकिलों को ऑटो-लेबल करें", + "menu_back": "← वापस" + } + }, + "manage_profiles": { + "title": "प्रोफ़ाइल प्रबंधित करें", + "description": "प्रोफ़ाइल सारांश:\n{profile_summary}", + "menu_options": { + "create_profile": "नई प्रोफ़ाइल बनाएं", + "edit_profile": "प्रोफ़ाइल संपादित करें/नाम बदलें", + "delete_profile_select": "प्रोफ़ाइल हटाएं", + "profile_stats": "प्रोफ़ाइल सांख्यिकी", + "cleanup_profile": "इतिहास साफ़ करें - ग्राफ़ और हटाएँ", + "assign_profile_phases_select": "चरण रेंज निर्दिष्ट करें", + "menu_back": "← वापस" + } + }, + "manage_profiles_empty": { + "title": "प्रोफ़ाइल प्रबंधित करें", + "description": "अभी तक कोई प्रोफ़ाइल नहीं बनाई गई.", + "menu_options": { + "create_profile": "नई प्रोफ़ाइल बनाएं", + "menu_back": "← वापस" + } + }, + "manage_phase_catalog": { + "title": "चरण कैटलॉग प्रबंधित करें", + "description": "चरण कैटलॉग (इस डिवाइस के लिए डिफ़ॉल्ट + कस्टम चरण):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "नया चरण बनाएं", + "phase_catalog_edit_select": "चरण संपादित करें", + "phase_catalog_delete": "चरण हटाएँ", + "menu_back": "← वापस" + } + }, + "phase_catalog_create": { + "title": "कस्टम चरण बनाएं", + "description": "एक कस्टम चरण नाम और विवरण बनाएं, और चुनें कि यह किस डिवाइस प्रकार पर लागू होता है।", + "data": { + "target_device_type": "लक्ष्य डिवाइस प्रकार", + "phase_name": "चरण का नाम", + "phase_description": "चरण विवरण" + } + }, + "phase_catalog_edit_select": { + "title": "कस्टम चरण संपादित करें", + "description": "संपादित करने के लिए एक कस्टम चरण चुनें.", + "data": { + "phase_name": "कस्टम चरण" + } + }, + "phase_catalog_edit": { + "title": "कस्टम चरण संपादित करें", + "description": "संपादन चरण: {phase_name}", + "data": { + "phase_name": "चरण का नाम", + "phase_description": "चरण विवरण" + } + }, + "phase_catalog_delete": { + "title": "कस्टम चरण हटाएँ", + "description": "हटाने के लिए एक कस्टम चरण चुनें. इस चरण का उपयोग करने वाली कोई भी निर्दिष्ट सीमा हटा दी जाएगी।", + "data": { + "phase_name": "कस्टम चरण" + } + }, + "assign_profile_phases": { + "title": "चरण रेंज निर्दिष्ट करें", + "description": "प्रोफ़ाइल के लिए चरण पूर्वावलोकन: **{profile_name}**\n\n{timeline_svg}\n\n**वर्तमान श्रेणियाँ:**\n{current_ranges}\n\nश्रेणियाँ जोड़ने, संपादित करने, हटाने या सहेजने के लिए नीचे दी गई क्रियाओं का उपयोग करें।", + "menu_options": { + "assign_profile_phases_add": "चरण सीमा जोड़ें", + "assign_profile_phases_edit_select": "चरण सीमा संपादित करें", + "assign_profile_phases_delete": "चरण सीमा हटाएँ", + "phase_ranges_clear": "सभी श्रेणियां साफ़ करें", + "assign_profile_phases_auto_detect": "चरणों का स्वतः पता लगाएं", + "phase_ranges_save": "सहेजें और वापस करें", + "menu_back": "← वापस" + } + }, + "assign_profile_phases_add": { + "title": "चरण सीमा जोड़ें", + "description": "वर्तमान ड्राफ्ट में एक चरण श्रेणी जोड़ें।", + "data": { + "phase_name": "चरण", + "start_min": "प्रारंभ मिनट", + "end_min": "समाप्ति मिनट" + } + }, + "assign_profile_phases_edit_select": { + "title": "चरण सीमा संपादित करें", + "description": "संपादित करने के लिए एक श्रेणी चुनें.", + "data": { + "range_index": "चरण सीमा" + } + }, + "assign_profile_phases_edit": { + "title": "चरण सीमा संपादित करें", + "description": "चयनित चरण सीमा को अद्यतन करें.", + "data": { + "phase_name": "चरण", + "start_min": "प्रारंभ मिनट", + "end_min": "समाप्ति मिनट" + } + }, + "assign_profile_phases_delete": { + "title": "चरण सीमा हटाएँ", + "description": "ड्राफ्ट से हटाने के लिए एक श्रेणी का चयन करें।", + "data": { + "range_index": "चरण सीमा" + } + }, + "assign_profile_phases_auto_detect": { + "title": "स्वत: पता लगाए गए चरण", + "description": "प्रोफ़ाइल: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} चरण स्वचालित रूप से पता चला।** नीचे एक क्रिया चुनें।", + "data": { + "action": "कार्रवाई" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "पता लगाए गए चरणों का नाम", + "description": "प्रोफ़ाइल: **{profile_name}**\n\n**{detected_count} चरण का पता चला:**\n{ranges_summary}\n\nप्रत्येक चरण को एक नाम निर्दिष्ट करें." + }, + "assign_profile_phases_select": { + "title": "चरण रेंज निर्दिष्ट करें", + "description": "चरण श्रेणियों को कॉन्फ़िगर करने के लिए एक प्रोफ़ाइल का चयन करें।", + "data": { + "profile": "प्रोफ़ाइल" + } + }, + "profile_stats": { + "title": "प्रोफ़ाइल सांख्यिकी", + "description": "{stats_table}" + }, + "create_profile": { + "title": "नई प्रोफ़ाइल बनाएं", + "description": "मैन्युअल रूप से या पिछले चक्र से एक नई प्रोफ़ाइल बनाएं।\n\nप्रोफ़ाइल नाम के उदाहरण: 'डेलिकेट्स', 'हैवी ड्यूटी', 'क्विक वॉश'", + "data": { + "profile_name": "प्रोफ़ाइल नाम", + "reference_cycle": "संदर्भ चक्र (वैकल्पिक)", + "manual_duration": "मैनुअल अवधि (मिनट)" + } + }, + "edit_profile": { + "title": "प्रोफ़ाइल संपादित करें", + "description": "नाम बदलने के लिए एक प्रोफ़ाइल चुनें", + "data": { + "profile": "प्रोफ़ाइल" + } + }, + "rename_profile": { + "title": "प्रोफ़ाइल विवरण संपादित करें", + "description": "वर्तमान प्रोफ़ाइल: {current_name}", + "data": { + "new_name": "प्रोफ़ाइल नाम", + "manual_duration": "मैनुअल अवधि (मिनट)" + } + }, + "delete_profile_select": { + "title": "प्रोफ़ाइल हटाएं", + "description": "हटाने के लिए एक प्रोफ़ाइल चुनें", + "data": { + "profile": "प्रोफ़ाइल" + } + }, + "delete_profile_confirm": { + "title": "प्रोफ़ाइल हटाएं की पुष्टि करें", + "description": "⚠️ इससे प्रोफ़ाइल स्थायी रूप से हट जाएगी: {profile_name}", + "data": { + "unlabel_cycles": "इस प्रोफ़ाइल का उपयोग करके साइकिलों से लेबल हटाएँ" + } + }, + "auto_label_cycles": { + "title": "पुरानी साइकिलों को ऑटो-लेबल करें", + "description": "कुल चक्र {total_count} मिले. प्रोफ़ाइल: {profiles}", + "data": { + "confidence_threshold": "न्यूनतम आत्मविश्वास (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "लेबल करने के लिए साइकिल का चयन करें", + "description": "✓ = पूरा, ⚠ = फिर से शुरू, ✗ = बाधित", + "data": { + "cycle_id": "चक्र" + } + }, + "select_cycle_to_delete": { + "title": "चक्र हटाएँ", + "description": "⚠️ यह चयनित चक्र को स्थायी रूप से हटा देगा", + "data": { + "cycle_id": "हटाने के लिए चक्र" + } + }, + "label_cycle": { + "title": "लेबल चक्र", + "description": "{cycle_info}", + "data": { + "profile_name": "प्रोफ़ाइल", + "new_profile_name": "नया प्रोफ़ाइल नाम (यदि बना रहे हैं)" + } + }, + "post_process": { + "title": "प्रक्रिया इतिहास (विलय/विभाजन)", + "description": "खंडित रनों को मर्ज करके और गलत तरीके से मर्ज किए गए चक्रों को एक समय विंडो के भीतर विभाजित करके चक्र इतिहास को साफ़ करें। पीछे देखने के लिए घंटों की संख्या दर्ज करें (सभी के लिए 999999 का उपयोग करें)।", + "data": { + "time_range": "लुकबैक विंडो (घंटे)", + "gap_seconds": "मर्ज/स्प्लिट गैप (सेकंड)" + }, + "data_description": { + "time_range": "खंडित चक्रों को मर्ज करने के लिए स्कैन करने के लिए घंटों की संख्या। सभी ऐतिहासिक डेटा को संसाधित करने के लिए 999999 का उपयोग करें।", + "gap_seconds": "विभाजन बनाम विलय का निर्णय लेने की सीमा। इससे छोटे अंतरालों को मिला दिया जाता है। इससे अधिक लंबे अंतराल विभाजित हैं। गलत तरीके से मर्ज किए गए चक्र पर विभाजन को मजबूर करने के लिए इसे कम करें।" + } + }, + "cleanup_profile": { + "title": "इतिहास साफ़ करें - प्रोफ़ाइल चुनें", + "description": "विज़ुअलाइज़ करने के लिए एक प्रोफ़ाइल चुनें. यह आउटलेर्स की पहचान करने में मदद करने के लिए इस प्रोफ़ाइल के सभी पिछले चक्रों को दर्शाने वाला एक ग्राफ़ उत्पन्न करेगा।", + "data": { + "profile": "प्रोफ़ाइल" + } + }, + "cleanup_select": { + "title": "इतिहास साफ़ करें - ग्राफ़ और हटाएँ", + "description": "![ग्राफ़]({graph_url})\n\n**विज़ुअलाइज़ेशन {profile_name}**\n\nग्राफ़ अलग-अलग चक्रों को रंगीन रेखाओं के रूप में दिखाता है। आउटलेर्स (समूह से दूर की रेखाएं) को पहचानें और उन्हें हटाने के लिए नीचे दी गई सूची में उनके रंग का मिलान करें।\n\n**स्थायी रूप से हटाने के लिए चक्र चुनें:**", + "data": { + "cycles_to_delete": "हटाने के लिए चक्र (मिलान रंग जांचें)" + } + }, + "interactive_editor": { + "title": "मर्ज/स्प्लिट इंटरैक्टिव संपादक", + "description": "अपने चक्र इतिहास पर प्रदर्शन करने के लिए एक मैन्युअल कार्रवाई का चयन करें।", + "menu_options": { + "editor_split": "एक चक्र विभाजित करें (अंतराल खोजें)", + "editor_merge": "चक्र मर्ज करें (टुकड़ों को जोड़ें)", + "editor_delete": "चक्र हटाएँ", + "menu_back": "← वापस" + } + }, + "editor_select": { + "title": "चक्र चुनें", + "description": "संसाधित करने के लिए चक्र का चयन करें.\n\n{info_text}", + "data": { + "selected_cycles": "साइकिल" + } + }, + "editor_configure": { + "title": "कॉन्फ़िगर करें और लागू करें", + "description": "{preview_md}", + "data": { + "confirm_action": "कार्रवाई", + "merged_profile": "परिणामी प्रोफ़ाइल", + "new_profile_name": "नया प्रोफ़ाइल नाम", + "confirm_commit": "हां, मैं इस कार्रवाई की पुष्टि करता हूं", + "segment_0_profile": "खंड 1 प्रोफ़ाइल", + "segment_1_profile": "खंड 2 प्रोफ़ाइल", + "segment_2_profile": "खंड 3 प्रोफ़ाइल", + "segment_3_profile": "खंड 4 प्रोफ़ाइल", + "segment_4_profile": "खंड 5 प्रोफाइल", + "segment_5_profile": "खंड 6 प्रोफ़ाइल", + "segment_6_profile": "खंड 7 प्रोफ़ाइल", + "segment_7_profile": "खंड 8 प्रोफ़ाइल", + "segment_8_profile": "खंड 9 प्रोफ़ाइल", + "segment_9_profile": "खंड 10 प्रोफ़ाइल" + } + }, + "editor_split_params": { + "title": "विभाजन विन्यास", + "description": "इस चक्र को विभाजित करने के लिए संवेदनशीलता को समायोजित करें। इससे अधिक लंबा कोई भी निष्क्रिय अंतराल विभाजन का कारण बनेगा।", + "data": { + "split_mode": "विभाजन विधि" + } + }, + "editor_split_auto_params": { + "title": "ऑटो-स्प्लिट कॉन्फ़िगरेशन", + "description": "इस चक्र को विभाजित करने की संवेदनशीलता समायोजित करें। इससे लंबा कोई भी निष्क्रिय अंतराल विभाजन कर देगा।", + "data": { + "min_gap_seconds": "विभाजन अंतराल सीमा (सेकंड)" + } + }, + "editor_split_manual_params": { + "title": "मैनुअल विभाजन टाइमस्टैम्प", + "description": "{preview_md}चक्र विंडो: **{cycle_start_wallclock} → {cycle_end_wallclock}** को {cycle_date}.\n\nएक या अधिक विभाजन टाइमस्टैम्प (`HH:MM` या `HH:MM:SS`) दर्ज करें, प्रति पंक्ति एक। प्रत्येक टाइमस्टैम्प पर चक्र काट दिया जाएगा — N टाइमस्टैम्प N+1 खंड बनाते हैं।", + "data": { + "split_timestamps": "स्प्लिट टाइमस्टैम्प" + } + }, + "reprocess_history": { + "title": "रखरखाव: डेटा को पुन: संसाधित और अनुकूलित करें", + "description": "ऐतिहासिक डेटा की गहन सफाई करता है:\n1. शून्य-शक्ति रीडिंग (मौन) को ट्रिम करता है।\n2. चक्र का समय/अवधि निश्चित करता है।\n3. सभी सांख्यिकीय मॉडल (लिफाफे) की पुनर्गणना करता है।\n\n**डेटा समस्याओं को ठीक करने या नए एल्गोरिदम लागू करने के लिए इसे चलाएं।**" + }, + "wipe_history": { + "title": "इतिहास मिटाएँ (केवल परीक्षण)", + "description": "⚠️ यह इस डिवाइस के लिए सभी संग्रहीत चक्र और प्रोफ़ाइल को स्थायी रूप से हटा देगा। इसे असंपादित नहीं किया जा सकता है!" + }, + "export_import": { + "title": "JSON निर्यात/आयात करें", + "description": "इस डिवाइस के लिए चक्र, प्रोफ़ाइल और सेटिंग्स कॉपी/पेस्ट करें। नीचे निर्यात करें या आयात करने के लिए JSON पेस्ट करें।", + "data": { + "mode": "कार्रवाई", + "json_payload": "पूर्ण कॉन्फ़िगरेशन JSON" + }, + "data_description": { + "mode": "वर्तमान डेटा और सेटिंग्स की प्रतिलिपि बनाने के लिए निर्यात चुनें, या किसी अन्य वॉशडेटा डिवाइस से निर्यात किए गए डेटा को चिपकाने के लिए आयात चुनें।", + "json_payload": "निर्यात के लिए: इस JSON को कॉपी करें (इसमें चक्र, प्रोफ़ाइल और सभी सुव्यवस्थित सेटिंग्स शामिल हैं)। आयात के लिए: वॉशडेटा से निर्यातित JSON पेस्ट करें।" + } + }, + "record_cycle": { + "title": "रिकार्ड चक्र", + "description": "बिना किसी व्यवधान के एक स्वच्छ प्रोफ़ाइल बनाने के लिए एक चक्र को मैन्युअल रूप से रिकॉर्ड करें।\n\nस्थिति: **{status}**\nअवधि: {duration}s\nनमूने: {samples}", + "menu_options": { + "record_refresh": "ताज़ा स्थिति", + "record_stop": "रिकॉर्डिंग बंद करें (सहेजें और प्रोसेस करें)", + "record_start": "नई रिकॉर्डिंग शुरू", + "record_process": "अंतिम रिकॉर्डिंग की प्रक्रिया (ट्रिम और सेव करें)", + "record_discard": "अंतिम रिकॉर्डिंग त्यागें", + "menu_back": "← वापस" + } + }, + "record_process": { + "title": "प्रक्रिया रिकॉर्डिंग", + "description": "![ग्राफ़]({graph_url})\n\n**ग्राफ़ कच्ची रिकॉर्डिंग दिखाता है।** नीला = रखें, लाल = प्रस्तावित ट्रिम।\n*ग्राफ़ स्थिर है और प्रपत्र परिवर्तन के साथ अद्यतन नहीं होता है।*\n\nरिकॉर्डिंग बंद हो गई. ट्रिम्स को पता लगाए गए नमूनाकरण दर (~{sampling_rate}s) से संरेखित किया गया है।\n\nकच्ची अवधि: {duration}s\nनमूने: {samples}", + "data": { + "head_trim": "ट्रिम प्रारंभ (सेकंड)", + "tail_trim": "ट्रिम अंत (सेकंड)", + "save_mode": "गंतव्य सहेजें", + "profile_name": "प्रोफ़ाइल नाम" + } + }, + "learning_feedbacks": { + "title": "सीखने की प्रतिक्रियाएँ", + "description": "लंबित फीडबैक: {count}\n\n{pending_table}\n\nप्रक्रिया के लिए चक्र समीक्षा अनुरोध चुनें।", + "menu_options": { + "learning_feedbacks_pick": "एक लंबित फीडबैक की समीक्षा करें", + "learning_feedbacks_dismiss_all": "सभी लंबित फीडबैक खारिज करें", + "menu_back": "← वापस" + } + }, + "learning_feedbacks_pick": { + "title": "समीक्षा के लिए फीडबैक चुनें", + "description": "खोलने के लिए एक चक्र समीक्षा अनुरोध चुनें।", + "data": { + "selected_feedback": "समीक्षा के लिए चक्र चुनें" + } + }, + "learning_feedbacks_empty": { + "title": "सीखने की प्रतिक्रियाएँ", + "description": "कोई लंबित फीडबैक अनुरोध नहीं मिला.", + "menu_options": { + "menu_back": "← वापस" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "सभी लंबित फीडबैक खारिज करें", + "description": "आप **{count}** लंबित फीडबैक अनुरोधों को खारिज करने वाले हैं। खारिज किए गए चक्र आपके इतिहास में रहेंगे, लेकिन नया सीखने का संकेत नहीं देंगे। इसे पूर्ववत नहीं किया जा सकता.\n\n✅ सभी को खारिज करने के लिए नीचे दिए गए बॉक्स को चेक करें और **सबमिट** पर क्लिक करें.\n❌ इसे अनचेक छोड़ दें और रद्द करके वापस जाने के लिए **सबमिट** पर क्लिक करें।", + "data": { + "confirm_dismiss_all": "पुष्टि करें: सभी लंबित फीडबैक अनुरोध खारिज करें" + } + }, + "resolve_feedback": { + "title": "फीडबैक का समाधान करें", + "description": "{comparison_img}{candidates_table}\n**पता चला प्रोफ़ाइल**: {detected_profile} ({confidence_pct}%)\n**अनुमानित अवधि**: {est_duration_min} मिनट\n**वास्तविक अवधि**: {act_duration_min} मिनट\n\n__नीचे कोई क्रिया चुनें:__", + "data": { + "action": "आप क्या करना चाहेंगे?", + "corrected_profile": "सही प्रोग्राम (यदि सही हो)", + "corrected_duration": "सही अवधि सेकंड में (यदि सही हो)" + } + }, + "trim_cycle_select": { + "title": "ट्रिम साइकिल - साइकिल का चयन करें", + "description": "ट्रिम करने के लिए एक चक्र चुनें. ✓ = पूरा, ⚠ = फिर से शुरू, ✗ = बाधित", + "data": { + "cycle_id": "चक्र" + } + }, + "trim_cycle": { + "title": "ट्रिम साइकिल", + "description": "वर्तमान विंडो: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "कार्रवाई" + } + }, + "trim_cycle_start": { + "title": "ट्रिम स्टार्ट सेट करें", + "description": "साइकिल विंडो: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nवर्तमान प्रारंभ: **{current_wallclock}** (चक्र प्रारंभ से +{current_offset_min} मिनट)\n\nनीचे दिए गए घड़ी पिकर का उपयोग करके एक नया प्रारंभ समय चुनें।", + "data": { + "trim_start_time": "नई शुरुआत का समय", + "trim_start_min": "नई शुरुआत (साइकिल शुरू होने से कुछ मिनट)" + } + }, + "trim_cycle_end": { + "title": "ट्रिम एंड सेट करें", + "description": "साइकिल विंडो: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nवर्तमान समाप्ति: **{current_wallclock}** (चक्र प्रारंभ से +{current_offset_min} मिनट)\n\nनीचे दिए गए घड़ी पिकर का उपयोग करके एक नया समाप्ति समय चुनें।", + "data": { + "trim_end_time": "नया अंत समय", + "trim_end_min": "नया अंत (चक्र प्रारंभ से मिनट)" + } + } + }, + "error": { + "import_failed": "आयात विफल. कृपया एक वैध वॉशडेटा निर्यात JSON चिपकाएँ।", + "select_exactly_one": "इस कार्रवाई के लिए ठीक एक चक्र चुनें।", + "select_at_least_one": "इस कार्रवाई के लिए कम से कम एक चक्र चुनें।", + "select_at_least_two": "इस कार्रवाई के लिए कम से कम दो चक्र चुनें।", + "profile_exists": "इस नाम की एक प्रोफ़ाइल पहले से मौजूद है", + "rename_failed": "प्रोफ़ाइल का नाम बदलने में विफल. प्रोफ़ाइल मौजूद नहीं हो सकती है या नया नाम पहले ही लिया जा चुका है.", + "assignment_failed": "साइकिल के लिए प्रोफ़ाइल निर्दिष्ट करने में विफल", + "duplicate_phase": "इस नाम का एक चरण पहले से मौजूद है।", + "phase_not_found": "चयनित चरण नहीं मिला.", + "invalid_phase_name": "चरण नाम रिक्त नहीं हो सकता.", + "phase_name_too_long": "चरण का नाम बहुत लंबा है.", + "invalid_phase_range": "चरण सीमा अमान्य है. अंत प्रारंभ से बड़ा होना चाहिए.", + "invalid_phase_timestamp": "टाइमस्टैम्प अमान्य है. YYYY-MM-DD HH:MM या HH:MM प्रारूप का उपयोग करें।", + "overlapping_phase_ranges": "चरण श्रेणियाँ ओवरलैप होती हैं। श्रेणियाँ समायोजित करें और पुनः प्रयास करें।", + "incomplete_phase_row": "प्रत्येक चरण पंक्ति में चयनित मोड के लिए एक चरण प्लस पूर्ण प्रारंभ/अंत मान शामिल होना चाहिए।", + "timestamp_mode_cycle_required": "टाइमस्टैम्प मोड का उपयोग करते समय कृपया एक स्रोत चक्र का चयन करें।", + "no_phase_ranges": "उस क्रिया के लिए अभी तक कोई चरण सीमा उपलब्ध नहीं है।", + "feedback_notification_title": "वॉशडेटा: चक्र सत्यापित करें ({device})", + "feedback_notification_message": "**डिवाइस**: {device}\n**कार्यक्रम**: {program} ({confidence}% विश्वास)\n**समय**: {time}\n\nइस ज्ञात चक्र को सत्यापित करने के लिए वॉशडेटा को आपकी सहायता की आवश्यकता है।\n\nकृपया इस परिणाम की पुष्टि या सुधार करने के लिए **सेटिंग्स > डिवाइस और सेवाएं > वॉशडेटा > कॉन्फिगर > लर्निंग फीडबैक** पर जाएं।", + "suggestions_ready_notification_title": "वॉशडेटा: सुझाई गई सेटिंग्स तैयार ({device})", + "suggestions_ready_notification_message": "**सुझाई गई सेटिंग्स** सेंसर अब **{count}** कार्रवाई योग्य अनुशंसाओं की रिपोर्ट करता है।\n\nउनकी समीक्षा करने और उन्हें लागू करने के लिए: **सेटिंग्स > उपकरण और सेवाएँ > वॉशडेटा > कॉन्फ़िगर करें > उन्नत सेटिंग्स > सुझाए गए मान लागू करें**।\n\nसुझाव वैकल्पिक हैं और आपके सहेजने से पहले समीक्षा के लिए दिखाए जाते हैं।", + "trim_range_invalid": "प्रारंभ अंत से पहले होना चाहिए. कृपया अपने ट्रिम पॉइंट समायोजित करें।", + "trim_failed": "ट्रिम विफल रहा. हो सकता है कि चक्र अब मौजूद न हो या खिड़की खाली हो।", + "invalid_split_timestamp": "टाइमस्टैम्प अमान्य है या चक्र विंडो के बाहर है। चक्र विंडो के भीतर HH:MM या HH:MM:SS का उपयोग करें।", + "no_split_segments_found": "इन टाइमस्टैम्प से कोई वैध सेगमेंट नहीं बन सके। सुनिश्चित करें कि प्रत्येक टाइमस्टैम्प चक्र विंडो के भीतर है और प्रत्येक सेगमेंट कम से कम 1 मिनट लंबा है।", + "auto_tune_suggestion": "{device_type} {device_title} भूत चक्रों का पता चला। सुझाया गया न्यूनतम_शक्ति परिवर्तन: {current_min}डब्ल्यू -> {new_min}डब्ल्यू (स्वचालित रूप से लागू नहीं)।", + "auto_tune_title": "वॉशडेटा ऑटो-ट्यून", + "auto_tune_fallback": "{device_type} {device_title} भूत चक्रों का पता चला। सुझाया गया न्यूनतम_शक्ति परिवर्तन: {current_min}डब्ल्यू -> {new_min}डब्ल्यू (स्वचालित रूप से लागू नहीं)।" + }, + "abort": { + "no_cycles_found": "कोई साइकिल नहीं मिली", + "no_split_segments_found": "चयनित चक्र में विभाजित किए जा सकने वाले कोई सेगमेंट नहीं मिले।", + "cycle_not_found": "चयनित चक्र लोड नहीं किया जा सका।", + "no_power_data": "चयनित चक्र के लिए कोई पावर ट्रेस डेटा उपलब्ध नहीं है।", + "no_profiles_found": "कोई प्रोफ़ाइल नहीं मिली. सबसे पहले एक प्रोफ़ाइल बनाएं.", + "no_profiles_for_matching": "मिलान के लिए कोई प्रोफ़ाइल उपलब्ध नहीं है. पहले प्रोफ़ाइल बनाएं.", + "no_unlabeled_cycles": "सभी चक्र पहले से ही लेबल किए गए हैं", + "no_suggestions": "अभी तक कोई सुझाया गया मान उपलब्ध नहीं है. कुछ चक्र चलाएँ और पुनः प्रयास करें।", + "no_predictions": "कोई भविष्यवाणी नहीं", + "no_custom_phases": "कोई कस्टम चरण नहीं मिला.", + "reprocess_success": "{count} चक्रों को सफलतापूर्वक पुन:संसाधित किया गया।", + "debug_data_cleared": "{count} चक्रों से डिबग डेटा सफलतापूर्वक साफ़ किया गया।" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "चक्र प्रारंभ", + "cycle_finish": "चक्र समाप्त", + "cycle_live": "लाइव प्रगति" + } + }, + "common_text": { + "options": { + "unlabeled": "(बिना लेबल वाला)", + "create_new_profile": "नई प्रोफ़ाइल बनाएं", + "remove_label": "लेबल हटाएँ", + "no_reference_cycle": "(कोई संदर्भ चक्र नहीं)", + "all_device_types": "सभी डिवाइस प्रकार", + "current_suffix": "(मौजूदा)", + "editor_select_info": "विभाजित करने के लिए 1 चक्र चुनें, या विलय करने के लिए 2+ चक्र चुनें।", + "editor_select_info_split": "विभाजित करने के लिए 1 चक्र चुनें।", + "editor_select_info_merge": "मर्ज करने के लिए 2+ चक्र चुनें।", + "editor_select_info_delete": "हटाने के लिए 1+ चक्र चुनें।", + "no_cycles_recorded": "अभी तक कोई चक्र रिकॉर्ड नहीं किया गया.", + "profile_summary_line": "- **{name}**: {count} चक्र, {avg}मी औसत", + "no_profiles_created": "अभी तक कोई प्रोफ़ाइल नहीं बनाई गई.", + "phase_preview": "चरण पूर्वावलोकन", + "phase_preview_no_curve": "औसत प्रोफ़ाइल वक्र अभी तक उपलब्ध नहीं है. इस प्रोफ़ाइल के लिए और अधिक चक्र चलाएँ/लेबल करें।", + "average_power_curve": "औसत शक्ति वक्र", + "unit_min": "मिन", + "profile_option_fmt": "{name} ({count} चक्र, ~{duration}मीटर औसत)", + "profile_option_short_fmt": "{name} ({count} साइकिल, ~{duration}मी.)", + "deleted_cycles_from_profile": "{profile} से {count} चक्र हटा दिए गए।", + "not_enough_data_graph": "ग्राफ़ उत्पन्न करने के लिए पर्याप्त डेटा नहीं है.", + "no_profiles_found": "कोई प्रोफ़ाइल नहीं मिली.", + "cycle_info_fmt": "साइकिल: {start}, {duration}मी, वर्तमान: {label}", + "top_candidates_header": "### शीर्ष उम्मीदवार", + "tbl_profile": "प्रोफ़ाइल", + "tbl_confidence": "आत्मविश्वास", + "tbl_correlation": "सह - संबंध", + "tbl_duration_match": "अवधि मिलान", + "detected_profile": "प्रोफ़ाइल का पता लगाया गया", + "estimated_duration": "अनुमानित अवधि", + "actual_duration": "वास्तविक अवधि", + "choose_action": "__नीचे कोई क्रिया चुनें:__", + "tbl_count": "गिनती करना", + "tbl_avg": "औसत", + "tbl_min": "मिन", + "tbl_max": "अधिकतम", + "tbl_energy_avg": "ऊर्जा (औसत)", + "tbl_energy_total": "ऊर्जा (कुल)", + "tbl_consistency": "निहित होना।", + "tbl_last_run": "आखरी बार", + "graph_legend_title": "ग्राफ लीजेंड", + "graph_legend_body": "नीला बैंड देखी गई न्यूनतम और अधिकतम पावर ड्रॉ रेंज का प्रतिनिधित्व करता है। रेखा औसत शक्ति वक्र दर्शाती है।", + "recording_preview": "रिकॉर्डिंग पूर्वावलोकन", + "trim_start": "ट्रिम प्रारंभ", + "trim_end": "ट्रिम अंत", + "split_preview_title": "विभाजित पूर्वावलोकन", + "split_preview_found_fmt": "{count} खंड मिले.", + "split_preview_confirm_fmt": "इस चक्र को {count} अलग-अलग चक्रों में विभाजित करने के लिए पुष्टि करें पर क्लिक करें।", + "merge_preview_title": "पूर्वावलोकन मर्ज करें", + "merge_preview_joining_fmt": "{count} चक्रों में शामिल होना। अंतराल को 0W रीडिंग से भरा जाएगा।", + "editor_delete_preview_title": "हटाने का पूर्वावलोकन", + "editor_delete_preview_intro": "चयनित चक्र स्थायी रूप से हटा दिए जाएंगे:", + "editor_delete_preview_confirm": "इन चक्र रिकॉर्डों को स्थायी रूप से हटाने के लिए Confirm पर क्लिक करें।", + "no_power_preview": "*पूर्वावलोकन के लिए कोई पावर डेटा उपलब्ध नहीं है।*", + "profile_comparison": "प्रोफ़ाइल तुलना", + "actual_cycle_label": "यह चक्र (वास्तविक)", + "storage_usage_header": "भंडारण उपयोग", + "storage_file_size": "फ़ाइल का साइज़", + "storage_cycles": "साइकिल", + "storage_profiles": "प्रोफाइल", + "storage_debug_traces": "निशान डीबग करें", + "overview_suffix": "सिंहावलोकन", + "phase_preview_for_profile": "प्रोफ़ाइल के लिए चरण पूर्वावलोकन", + "current_ranges_header": "वर्तमान श्रेणियाँ", + "assign_phases_help": "श्रेणियाँ जोड़ने, संपादित करने, हटाने या सहेजने के लिए नीचे दी गई क्रियाओं का उपयोग करें।", + "profile_summary_header": "प्रोफ़ाइल सारांश", + "recent_cycles_header": "हाल के चक्र", + "trim_cycle_preview_title": "साइकिल ट्रिम पूर्वावलोकन", + "trim_cycle_preview_no_data": "इस चक्र के लिए कोई पावर डेटा उपलब्ध नहीं है।", + "trim_cycle_preview_kept_suffix": "रखा", + "table_program": "कार्यक्रम", + "table_when": "कब", + "table_length": "अवधि", + "table_match": "मिलान", + "table_profile": "प्रोफ़ाइल", + "table_cycles": "साइकिल", + "table_avg_length": "औसत अवधि", + "table_last_run": "आखरी बार", + "table_avg_energy": "औसत ऊर्जा", + "table_detected_program": "पहचाना गया कार्यक्रम", + "table_confidence": "आत्मविश्वास", + "table_reported": "रिपोर्ट किया गया", + "phase_builtin_suffix": "(अंतर्निहित)", + "phase_none_available": "कोई चरण उपलब्ध नहीं है.", + "settings_deprecation_warning": "⚠️ **अस्वीकृत डिवाइस प्रकार:** {device_type} को भविष्य के रिलीज़ में हटाने के लिए निर्धारित किया गया है। वॉशडेटा की मिलान पाइपलाइन इस उपकरण वर्ग के लिए विश्वसनीय परिणाम नहीं देती है। आपका एकीकरण बहिष्करण अवधि के दौरान काम करता रहता है; इस चेतावनी को शांत करने के लिए, नीचे दिए गए **डिवाइस प्रकार** को समर्थित प्रकारों (वॉशिंग मशीन, ड्रायर, वॉशर-ड्रायर कॉम्बो, डिशवॉशर, एयर फ्रायर, ब्रेड मेकर, या पंप) में से एक पर स्विच करें, या **अन्य (उन्नत)** पर स्विच करें यदि आपका उपकरण किसी भी समर्थित प्रकार से मेल नहीं खाता है। **अन्य (उन्नत)** जानबूझकर सामान्य डिफ़ॉल्ट भेजता है जो किसी विशिष्ट उपकरण के लिए ट्यून नहीं किया जाता है, इसलिए आपको थ्रेशोल्ड, टाइमआउट और मिलान पैरामीटर स्वयं कॉन्फ़िगर करने की आवश्यकता होगी; जब आप स्विच करते हैं तो आपकी सभी मौजूदा सेटिंग्स संरक्षित रहती हैं।", + "phase_other_device_types": "अन्य उपकरण प्रकार:" + } + }, + "interactive_editor_action": { + "options": { + "split": "एक चक्र विभाजित करें (अंतराल खोजें)", + "merge": "चक्र मर्ज करें (टुकड़ों को जोड़ें)", + "delete": "चक्र हटाएँ" + } + }, + "split_mode": { + "options": { + "auto": "निष्क्रिय अंतराल स्वतः पहचानें", + "manual": "मैनुअल टाइमस्टैम्प" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "रखरखाव: डेटा को पुन: संसाधित और अनुकूलित करें", + "clear_debug_data": "डिबग डेटा साफ़ करें (स्थान खाली करें)", + "wipe_history": "इस डिवाइस का सारा डेटा मिटा दें (अपरिवर्तनीय)", + "export_import": "सेटिंग्स के साथ JSON निर्यात/आयात करें (कॉपी/पेस्ट करें)" + } + }, + "export_import_mode": { + "options": { + "export": "सभी डेटा निर्यात करें", + "import": "डेटा आयात/मर्ज करें" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "पुरानी साइकिलों को ऑटो-लेबल करें", + "select_cycle_to_label": "विशिष्ट चक्र लेबल करें", + "select_cycle_to_delete": "चक्र हटाएँ", + "interactive_editor": "मर्ज/स्प्लिट इंटरैक्टिव संपादक", + "trim_cycle": "ट्रिम साइकिल डेटा" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "नई प्रोफ़ाइल बनाएं", + "edit_profile": "प्रोफ़ाइल संपादित करें/नाम बदलें", + "delete_profile": "प्रोफ़ाइल हटाएं", + "profile_stats": "प्रोफ़ाइल सांख्यिकी", + "cleanup_profile": "इतिहास साफ़ करें - ग्राफ़ और हटाएँ", + "assign_phases": "चरण रेंज निर्दिष्ट करें" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "नया चरण बनाएं", + "edit_custom_phase": "चरण संपादित करें", + "delete_custom_phase": "चरण हटाएँ" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "चरण सीमा जोड़ें", + "edit_range": "चरण सीमा संपादित करें", + "delete_range": "चरण सीमा हटाएँ", + "clear_ranges": "सभी श्रेणियां साफ़ करें", + "auto_detect_ranges": "चरणों का स्वतः पता लगाएं", + "save_ranges": "सहेजें और वापस करें" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "ताज़ा स्थिति", + "stop_recording": "रिकॉर्डिंग बंद करें (सहेजें और प्रोसेस करें)", + "start_recording": "नई रिकॉर्डिंग शुरू", + "process_recording": "अंतिम रिकॉर्डिंग की प्रक्रिया (ट्रिम और सेव करें)", + "discard_recording": "अंतिम रिकॉर्डिंग त्यागें" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "नई प्रोफ़ाइल बनाएं", + "existing_profile": "मौजूदा प्रोफ़ाइल में जोड़ें", + "discard": "रिकॉर्डिंग त्यागें" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "पुष्टि करें - सही जांच", + "correct": "सही - कार्यक्रम/अवधि चुनें", + "ignore": "नज़रअंदाज़ करें - ग़लत सकारात्मक/शोर चक्र", + "delete": "हटाएँ - इतिहास से हटाएँ" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "चरणों को नाम दें और लागू करें", + "cancel": "बिना बदलाव के वापस जाएं" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "प्रारंभिक बिंदू निश्चित करें", + "set_end": "अंतिम बिंदु सेट करें", + "reset": "पूर्ण अवधि पर रीसेट करें", + "apply": "ट्रिम लागू करें", + "cancel": "रद्द करना" + } + }, + "device_type": { + "options": { + "washing_machine": "वाशिंग मशीन", + "dryer": "ड्रायर", + "washer_dryer": "वॉशर-ड्रायर कॉम्बो", + "dishwasher": "डिशवॉशर", + "coffee_machine": "कॉफ़ी मशीन (अस्वीकृत)", + "ev": "इलेक्ट्रिक वाहन (अस्वीकृत)", + "air_fryer": "एयर फ़्रायर", + "heat_pump": "हीट पंप (अस्वीकृत)", + "bread_maker": "रोटी बनाने वाला", + "pump": "पंप/नाबदान पंप", + "oven": "ओवन (बहिष्कृत)", + "other": "अन्य (उन्नत)" + } + } + }, + "services": { + "label_cycle": { + "name": "लेबल चक्र", + "description": "किसी मौजूदा प्रोफ़ाइल को पिछले चक्र के लिए निर्दिष्ट करें, या लेबल हटा दें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "लेबल करने के लिए वॉशिंग मशीन डिवाइस।" + }, + "cycle_id": { + "name": "साइकिल आईडी", + "description": "लेबल करने के लिए चक्र की आईडी." + }, + "profile_name": { + "name": "प्रोफ़ाइल नाम", + "description": "किसी मौजूदा प्रोफ़ाइल का नाम (प्रोफ़ाइल प्रबंधित करें मेनू में प्रोफ़ाइल बनाएं)। लेबल हटाने के लिए खाली छोड़ दें." + } + } + }, + "create_profile": { + "name": "प्रोफ़ाइल बनाएं", + "description": "एक नई प्रोफ़ाइल बनाएं (स्टैंडअलोन या संदर्भ चक्र के आधार पर)।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशिंग मशीन डिवाइस." + }, + "profile_name": { + "name": "प्रोफ़ाइल नाम", + "description": "नई प्रोफ़ाइल के लिए नाम (जैसे 'हैवी ड्यूटी', 'डेलिकेट्स')।" + }, + "reference_cycle_id": { + "name": "संदर्भ साइकिल आईडी", + "description": "इस प्रोफ़ाइल को आधार बनाने के लिए वैकल्पिक चक्र आईडी।" + } + } + }, + "delete_profile": { + "name": "प्रोफ़ाइल हटाएं", + "description": "एक प्रोफ़ाइल हटाएं और वैकल्पिक रूप से इसका उपयोग करके चक्रों को अनलेबल करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशिंग मशीन डिवाइस." + }, + "profile_name": { + "name": "प्रोफ़ाइल नाम", + "description": "हटाने योग्य प्रोफ़ाइल." + }, + "unlabel_cycles": { + "name": "चक्रों को अनलेबल करें", + "description": "इस प्रोफ़ाइल का उपयोग करके साइकिल से प्रोफ़ाइल लेबल हटाएं।" + } + } + }, + "auto_label_cycles": { + "name": "पुरानी साइकिलों को ऑटो-लेबल करें", + "description": "प्रोफ़ाइल मिलान का उपयोग करके बिना लेबल वाले चक्रों को पूर्वव्यापी रूप से लेबल करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशिंग मशीन डिवाइस." + }, + "confidence_threshold": { + "name": "आत्मविश्वास की दहलीज", + "description": "लेबल लगाने के लिए न्यूनतम मिलान आत्मविश्वास (0.50-0.95)।" + } + } + }, + "export_config": { + "name": "कॉन्फ़िग निर्यात करें", + "description": "इस डिवाइस की प्रोफ़ाइल, चक्र और सेटिंग्स को JSON फ़ाइल (प्रति डिवाइस) में निर्यात करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "निर्यात करने के लिए वॉशिंग मशीन उपकरण।" + }, + "path": { + "name": "पथ", + "description": "लिखने के लिए वैकल्पिक पूर्ण फ़ाइल पथ (डिफ़ॉल्ट रूप से /config/ha_washdata_export_{entry}.json)।" + } + } + }, + "import_config": { + "name": "कॉन्फ़िगरेशन आयात करें", + "description": "JSON निर्यात फ़ाइल से इस डिवाइस के लिए प्रोफ़ाइल, चक्र और सेटिंग्स आयात करें (सेटिंग्स को ओवरराइट किया जा सकता है)।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "आयात करने के लिए वॉशिंग मशीन उपकरण।" + }, + "path": { + "name": "पथ", + "description": "आयात करने के लिए निर्यात JSON फ़ाइल का पूर्ण पथ।" + } + } + }, + "submit_cycle_feedback": { + "name": "साइकिल फ़ीडबैक सबमिट करें", + "description": "पूर्ण चक्र के बाद स्वतः-पहचान किए गए प्रोग्राम की पुष्टि या सुधार करें। या तो `entry_id` (उन्नत) या `device_id` (अनुशंसित) प्रदान करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशडेटा डिवाइस (एंट्री_आईडी के बजाय अनुशंसित)।" + }, + "entry_id": { + "name": "प्रवेश आईडी", + "description": "डिवाइस के लिए कॉन्फ़िगरेशन प्रविष्टि आईडी (device_id का विकल्प)।" + }, + "cycle_id": { + "name": "साइकिल आईडी", + "description": "फीडबैक अधिसूचना/लॉग में दिखाई गई साइकिल आईडी।" + }, + "user_confirmed": { + "name": "पता लगाए गए प्रोग्राम की पुष्टि करें", + "description": "यदि पाया गया प्रोग्राम सही है तो सही सेट करें।" + }, + "corrected_profile": { + "name": "सही प्रोफ़ाइल", + "description": "यदि पुष्टि नहीं हुई है, तो सही प्रोफ़ाइल/प्रोग्राम नाम प्रदान करें।" + }, + "corrected_duration": { + "name": "सही अवधि (सेकंड)", + "description": "सेकंड में वैकल्पिक संशोधित अवधि।" + }, + "notes": { + "name": "टिप्पणियाँ", + "description": "इस चक्र के बारे में वैकल्पिक नोट्स." + } + } + }, + "record_start": { + "name": "रिकार्ड चक्र प्रारंभ", + "description": "एक स्वच्छ चक्र को मैन्युअल रूप से रिकॉर्ड करना प्रारंभ करें (सभी मेल खाने वाले तर्क को दरकिनार कर देता है)।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "रिकॉर्ड करने के लिए वॉशिंग मशीन डिवाइस।" + } + } + }, + "record_stop": { + "name": "रिकॉर्ड साइकिल स्टॉप", + "description": "मैन्युअल रिकॉर्डिंग बंद करें.", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "रिकॉर्डिंग बंद करने के लिए वॉशिंग मशीन डिवाइस।" + } + } + }, + "trim_cycle": { + "name": "ट्रिम साइकिल", + "description": "पिछले चक्र के पावर डेटा को एक विशिष्ट समय विंडो में ट्रिम करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशडेटा डिवाइस।" + }, + "cycle_id": { + "name": "साइकिल आईडी", + "description": "ट्रिम करने के लिए साइकिल की आईडी." + }, + "trim_start_s": { + "name": "ट्रिम प्रारंभ (सेकंड)", + "description": "इस ऑफसेट से डेटा को सेकंडों में रखें। डिफ़ॉल्ट 0." + }, + "trim_end_s": { + "name": "ट्रिम अंत (सेकंड)", + "description": "सेकंड में इस ऑफसेट तक डेटा रखें। डिफ़ॉल्ट = पूर्ण अवधि." + } + } + }, + "pause_cycle": { + "name": "चक्र रोकें", + "description": "वॉशडेटा डिवाइस के लिए सक्रिय चक्र को रोकें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशडेटा डिवाइस को रोकने के लिए।" + } + } + }, + "resume_cycle": { + "name": "साइकिल फिर से शुरू करें", + "description": "वॉशडेटा डिवाइस के लिए रुके हुए चक्र को फिर से शुरू करें।", + "fields": { + "device_id": { + "name": "उपकरण", + "description": "वॉशडेटा डिवाइस फिर से शुरू करने के लिए।" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "दौड़ना" + }, + "match_ambiguity": { + "name": "अस्पष्टता का मिलान करें" + } + }, + "sensor": { + "washer_state": { + "name": "राज्य", + "state": { + "off": "बंद", + "idle": "निठल्ला", + "starting": "प्रारंभ", + "running": "दौड़ना", + "paused": "रुका हुआ", + "user_paused": "उपयोगकर्ता द्वारा रोका गया", + "ending": "ख़त्म होना", + "finished": "खत्म", + "anti_wrinkle": "सिकुड़न प्रतिरोधी", + "delay_wait": "प्रारंभ होने की प्रतीक्षा में", + "interrupted": "बाधित", + "force_stopped": "बलपूर्वक रोका गया", + "rinse": "कुल्ला", + "unknown": "अज्ञात", + "clean": "साफ" + } + }, + "washer_program": { + "name": "कार्यक्रम" + }, + "time_remaining": { + "name": "शेष समय" + }, + "total_duration": { + "name": "कुल अवधि" + }, + "cycle_progress": { + "name": "प्रगति" + }, + "current_power": { + "name": "वर्तमान शक्ति" + }, + "elapsed_time": { + "name": "बीता हुआ समय" + }, + "debug_info": { + "name": "डीबग जानकारी" + }, + "match_confidence": { + "name": "आत्मविश्वास का मिलान करें" + }, + "top_candidates": { + "name": "शीर्ष उम्मीदवार", + "state": { + "none": "कोई नहीं" + } + }, + "current_phase": { + "name": "वर्तमान चरण" + }, + "wash_phase": { + "name": "चरण" + }, + "profile_cycle_count": { + "name": "प्रोफ़ाइल {profile_name} गिनती", + "unit_of_measurement": "चक्र" + }, + "suggestions": { + "name": "सुझाई गई सेटिंग्स" + }, + "pump_runs_today": { + "name": "पंप रन (पिछले 24 घंटे)" + }, + "cycle_count": { + "name": "चक्र गणना" + } + }, + "select": { + "program_select": { + "name": "साइकिल कार्यक्रम", + "state": { + "auto_detect": "ऑटो का पता लगाने" + } + } + }, + "button": { + "force_end_cycle": { + "name": "बल समाप्ति चक्र" + }, + "pause_cycle": { + "name": "चक्र रोकें" + }, + "resume_cycle": { + "name": "साइकिल फिर से शुरू करें" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "एक वैध डिवाइस_आईडी आवश्यक है।" + }, + "cycle_id_required": { + "message": "एक वैध cycle_id आवश्यक है." + }, + "profile_name_required": { + "message": "एक वैध प्रोफ़ाइल_नाम आवश्यक है." + }, + "device_not_found": { + "message": "युक्ति नहीं मिला।" + }, + "no_config_entry": { + "message": "डिवाइस के लिए कोई कॉन्फ़िगरेशन प्रविष्टि नहीं मिली." + }, + "integration_not_loaded": { + "message": "इस डिवाइस के लिए एकीकरण लोड नहीं किया गया है." + }, + "cycle_not_found_or_no_power": { + "message": "साइकिल नहीं मिली या उसमें पावर डेटा नहीं है।" + }, + "trim_failed_empty_window": { + "message": "ट्रिम विफल - चक्र नहीं मिला, कोई पावर डेटा नहीं, या परिणामी विंडो खाली है।" + }, + "trim_invalid_range": { + "message": "trim_end_s, trim_start_s से बड़ा होना चाहिए।" + } + } +} diff --git a/custom_components/ha_washdata/translations/hr.json b/custom_components/ha_washdata/translations/hr.json new file mode 100644 index 0000000..e6c4887 --- /dev/null +++ b/custom_components/ha_washdata/translations/hr.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Postavljanje WashData", + "description": "Konfigurirajte svoju perilicu rublja ili neki drugi uređaj.\n\nPotreban je senzor snage.\n\n**Potom ćete biti upitani želite li izraditi svoj prvi profil.**", + "data": { + "name": "Naziv uređaja", + "device_type": "Vrsta uređaja", + "power_sensor": "Senzor snage", + "min_power": "Minimalni prag snage (W)" + }, + "data_description": { + "name": "Prijateljski naziv za ovaj uređaj (npr. \"Perilica rublja\", \"Perilica posuđa\").", + "device_type": "Koja je ovo vrsta uređaja? Pomaže u prilagođavanju otkrivanja i označavanja.", + "power_sensor": "Entitet senzora koji prijavljuje potrošnju energije u stvarnom vremenu (u vatima) iz vašeg pametnog utikača.", + "min_power": "Očitavanje snage iznad ovog praga (u vatima) znači da uređaj radi. Počnite s 2 W za većinu uređaja." + } + }, + "first_profile": { + "title": "Napravi prvi profil", + "description": "**Ciklus** je cijeli rad vašeg uređaja (npr. puno rublja). **Profil** grupira ove cikluse prema vrsti (npr. 'Pamuk', 'Brzo pranje'). Stvaranje profila sada pomaže u trenutnoj procjeni trajanja integracije.\n\nOvaj profil možete ručno odabrati u kontrolama uređaja ako se ne otkrije automatski.\n\n**Ostavite 'Naziv profila' prazno da biste preskočili ovaj korak.**", + "data": { + "profile_name": "Ime profila (neobavezno)", + "manual_duration": "Procijenjeno trajanje (minute)" + } + } + }, + "error": { + "cannot_connect": "Povezivanje nije uspjelo", + "invalid_auth": "Nevažeća provjera autentičnosti", + "unknown": "Neočekivana pogreška" + }, + "abort": { + "already_configured": "Uređaj je već konfiguriran" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Pregledajte povratne informacije o učenju", + "settings": "Postavke", + "notifications": "Obavijesti", + "manage_cycles": "Upravljanje ciklusima", + "manage_profiles": "Upravljanje profilima", + "manage_phase_catalog": "Upravljanje katalogom faza", + "record_cycle": "Ciklus snimanja (ručno)", + "diagnostics": "Dijagnostika i održavanje" + } + }, + "apply_suggestions": { + "title": "Kopiraj predložene vrijednosti", + "description": "Ovo će kopirati trenutno predložene vrijednosti u vaše spremljene postavke. Ovo je jednokratna radnja i ne omogućuje automatsko prepisivanje.\n\nPredložene vrijednosti za kopiranje:\n{suggested}", + "data": { + "confirm": "Potvrdite radnju kopiranja" + } + }, + "apply_suggestions_confirm": { + "title": "Pregledajte predložene promjene", + "description": "WashData je pronašao **{pending_count}** predloženu vrijednost(i) za primjenu.\n\nPromjene:\n{changes}\n\n✅ Označite okvir u nastavku i kliknite **Pošalji** za primjenu i spremanje ovih promjena odmah.\n❌ Ostavite ga neoznačenim i kliknite **Pošalji** za odustajanje i povratak.", + "data": { + "confirm_apply_suggestions": "Primijenite i spremite ove promjene" + } + }, + "settings": { + "title": "Postavke", + "description": "{deprecation_warning}Podešavanje pragova otkrivanja, učenja i naprednog ponašanja. Zadane postavke dobro funkcioniraju za većinu postavki.\n\nTrenutno dostupne predložene postavke: {suggestions_count}.\nAko je taj broj veći od 0, otvorite Napredne postavke i upotrijebite 'Primijeni predložene vrijednosti' za pregled preporuka.", + "data": { + "apply_suggestions": "Primijeni predložene vrijednosti", + "device_type": "Vrsta uređaja", + "power_sensor": "Entitet senzora snage", + "min_power": "Minimalna snaga (W)", + "off_delay": "Odgoda završetka ciklusa (s)", + "notify_actions": "Radnje obavijesti", + "notify_people": "Odgodite dostavu dok ovi ljudi ne dođu kući", + "notify_only_when_home": "Odgodite obavijesti dok odabrana osoba ne bude kod kuće", + "notify_fire_events": "Aktiviranje automatizacijskih događaja", + "notify_start_services": "Početak ciklusa - Ciljevi obavijesti", + "notify_finish_services": "Završetak ciklusa - Ciljevi obavijesti", + "notify_live_services": "Napredak uživo - Ciljevi obavijesti", + "notify_before_end_minutes": "Obavijest prije završetka (minuti prije završetka)", + "notify_title": "Naslov obavijesti", + "notify_icon": "Ikona obavijesti", + "notify_start_message": "Pokrenite format poruke", + "notify_finish_message": "Završi format poruke", + "notify_pre_complete_message": "Format poruke prije završetka", + "show_advanced": "Uredi napredne postavke (sljedeći korak)" + }, + "data_description": { + "apply_suggestions": "Označite ovaj okvir za kopiranje vrijednosti koje predlaže algoritam učenja u obrazac. Pregledajte prije spremanja.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Odaberite vrstu uređaja (perilica rublja, sušilica rublja, perilica posuđa, aparat za kavu, EV). Ova oznaka se pohranjuje sa svakim ciklusom radi bolje organizacije.", + "power_sensor": "Entitet senzora koji prijavljuje snagu u stvarnom vremenu (u vatima). To možete promijeniti bilo kada bez gubitka povijesnih podataka-korisno ako zamijenite pametni utikač.", + "min_power": "Očitavanje snage iznad ovog praga (u vatima) znači da uređaj radi. Počnite s 2 W za većinu uređaja.", + "off_delay": "U sekundama izglađena snaga mora ostati ispod praga zaustavljanja da bi se označilo završetak. Zadano: 120 s.", + "notify_actions": "Izborni niz radnji kućnog pomoćnika za pokretanje za obavijesti (skripte, obavijesti, TTS itd.). Ako je postavljeno, radnje se pokreću prije servisa obavijesti o zamjeni.", + "notify_people": "Entiteti ljudi koji se koriste za provjeru prisutnosti. Kada je omogućeno, isporuka obavijesti se odgađa dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odgodi obavijesti dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktivira događaje kućnog pomoćnika za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_ended) za pokretanje automatizacije.", + "notify_start_services": "Jedan ili više servisa za obavještavanje o početku ciklusa. Ostavite prazno za preskakanje početnih obavijesti.", + "notify_finish_services": "Jedan ili više servisa za obavještavanje kada ciklus završi ili se približi kraju. Ostavite prazno za preskakanje obavijesti o završetku.", + "notify_live_services": "Jedna ili više usluga obavijesti za primanje ažuriranja napretka uživo dok ciklus traje (samo popratna aplikacija za mobilne uređaje). Ostavite prazno za preskakanje ažuriranja uživo.", + "notify_before_end_minutes": "Minute prije procijenjenog kraja ciklusa za pokretanje obavijesti. Postavite na 0 za onemogućavanje. Zadano: 0.", + "notify_title": "Prilagođeni naslov za obavijesti. Podržava rezervirano mjesto {device}.", + "notify_icon": "Ikona koja se koristi za obavijesti (npr. mdi:washing-machine). Ostavite prazno za slanje bez ikone.", + "notify_start_message": "Poruka se šalje kada ciklus započne. Podržava rezervirano mjesto {device}.", + "notify_finish_message": "Poruka se šalje kada ciklus završi. Podržava rezervirana mjesta {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Tekst ponavljajućeg ažuriranja napretka uživo tijekom ciklusa. Podržava rezervirana mjesta {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Obavijesti", + "description": "Konfigurirajte kanale obavijesti, predloške i izborna ažuriranja mobilnog napretka uživo.", + "data": { + "notify_actions": "Radnje obavijesti", + "notify_people": "Odgodite dostavu dok ovi ljudi ne dođu kući", + "notify_only_when_home": "Odgodite obavijesti dok odabrana osoba ne bude kod kuće", + "notify_fire_events": "Aktiviranje automatizacijskih događaja", + "notify_start_services": "Početak ciklusa - Ciljevi obavijesti", + "notify_finish_services": "Završetak ciklusa - Ciljevi obavijesti", + "notify_live_services": "Napredak uživo - Ciljevi obavijesti", + "notify_before_end_minutes": "Obavijest prije završetka (minuti prije završetka)", + "notify_live_interval_seconds": "Interval ažuriranja uživo (sekunde)", + "notify_live_overrun_percent": "Dopušteno prekoračenje ažuriranja uživo (%)", + "notify_live_chronometer": "Odbrojavanje vremena za kronometar", + "notify_title": "Naslov obavijesti", + "notify_icon": "Ikona obavijesti", + "notify_start_message": "Pokrenite format poruke", + "notify_finish_message": "Završi format poruke", + "notify_pre_complete_message": "Format poruke prije završetka", + "energy_price_entity": "Cijena energije - entitet (nije obavezno)", + "energy_price_static": "Cijena energije - statička vrijednost (izborno)", + "go_back": "Vrati se bez spremanja", + "notify_reminder_message": "Format poruke podsjetnika", + "notify_timeout_seconds": "Automatski odbaci nakon (sekundi)", + "notify_channel": "Kanal obavijesti (status/uživo/podsjetnik)", + "notify_finish_channel": "Završen kanal obavijesti" + }, + "data_description": { + "go_back": "Označite ovo i kliknite Pošalji kako biste odbacili sve gore navedene promjene i vratili se na glavni izbornik.", + "notify_actions": "Izborni niz radnji kućnog pomoćnika za pokretanje za obavijesti (skripte, obavijesti, TTS itd.). Ako je postavljeno, radnje se pokreću prije servisa obavijesti o zamjeni.", + "notify_people": "Entiteti ljudi koji se koriste za provjeru prisutnosti. Kada je omogućeno, isporuka obavijesti se odgađa dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odgodi obavijesti dok barem jedna odabrana osoba ne bude kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktivira događaje kućnog pomoćnika za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_ended) za pokretanje automatizacije.", + "notify_start_services": "Jedna ili više usluga obavijesti koje upozoravaju kada ciklus započne (npr. notify.mobile_app_my_phone). Ostavite prazno za preskakanje početnih obavijesti.", + "notify_finish_services": "Jedan ili više servisa za obavještavanje kada ciklus završi ili se približi kraju. Ostavite prazno za preskakanje obavijesti o završetku.", + "notify_live_services": "Jedna ili više usluga obavijesti za primanje ažuriranja napretka uživo dok ciklus traje (samo popratna aplikacija za mobilne uređaje). Ostavite prazno za preskakanje ažuriranja uživo.", + "notify_before_end_minutes": "Minute prije procijenjenog kraja ciklusa za pokretanje obavijesti. Postavite na 0 za onemogućavanje. Zadano: 0.", + "notify_live_interval_seconds": "Koliko se često ažuriranja napretka šalju tijekom izvođenja. Niže vrijednosti su responzivnije, ali troše više obavijesti. Primjenjuje se minimalno 30 sekundi - vrijednosti ispod 30 s automatski se zaokružuju na 30 s.", + "notify_live_overrun_percent": "Sigurnosna granica iznad procijenjenog broja ažuriranja za duge cikluse/cikluse koji se prekoračuju (na primjer, 20% dopušta 1,2x procijenjena ažuriranja).", + "notify_live_chronometer": "Kada je omogućeno, svako ažuriranje uživo uključuje odbrojavanje kronometra do procijenjenog vremena cilja. Mjerač vremena obavijesti automatski otkucava na uređaju između ažuriranja (samo popratna aplikacija za Android).", + "notify_title": "Prilagođeni naslov za obavijesti. Podržava rezervirano mjesto {device}.", + "notify_icon": "Ikona koja se koristi za obavijesti (npr. mdi:washing-machine). Ostavite prazno za slanje bez ikone.", + "notify_start_message": "Poruka se šalje kada ciklus započne. Podržava rezervirano mjesto {device}.", + "notify_finish_message": "Poruka se šalje kada ciklus završi. Podržava rezervirana mjesta {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Odaberite numerički entitet koji sadrži trenutnu cijenu električne energije (npr. sensor.electricity_price, input_number.energy_rate). Kada je postavljeno, omogućuje rezervirano mjesto {cost}. Ima prednost nad statičkom vrijednošću ako su obje konfigurirane.", + "energy_price_static": "Fiksna cijena električne energije po kWh. Kada je postavljeno, omogućuje rezervirano mjesto {cost}. Ignorira se ako je entitet konfiguriran iznad. Dodajte simbol valute u predložak poruke, npr. {cost} €.", + "notify_reminder_message": "Tekst jednokratnog podsjetnika šalje konfigurirani broj minuta prije procijenjenog završetka. Za razliku od gornjih ponavljajućih ažuriranja uživo. Podržava rezervirana mjesta {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Automatski odbaci obavijesti nakon toliko sekundi (prosljeđuje se kao 'vremensko ograničenje' popratne aplikacije). Postavite na 0 da ih zadržite do odbacivanja. Zadano: 0.", + "notify_channel": "Android kanal obavijesti popratne aplikacije za status, napredak uživo i obavijesti podsjetnika. Zvuk i važnost kanala konfiguriraju se u popratnoj aplikaciji prvi put kada se naziv kanala koristi - WashData samo postavlja naziv kanala. Ostavite prazno za zadanu aplikaciju.", + "notify_finish_channel": "Odvojite Android kanal za dovršeno upozorenje (također ga koriste podsjetnik i zanovijetanje za pranje rublja) tako da može imati vlastiti zvuk. Ostavite prazno za ponovno korištenje gornjeg kanala.", + "notify_pre_complete_message": "Tekst ponavljajućeg ažuriranja napretka uživo tijekom ciklusa. Podržava rezervirana mjesta {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Napredne postavke", + "description": "Podesite pragove otkrivanja, učenje i napredno ponašanje. Zadane postavke dobro funkcioniraju za većinu konfiguracija.", + "sections": { + "suggestions_section": { + "name": "Predložene postavke", + "description": "Dostupno je {suggestions_count} naučenih prijedloga. Označite 'Primijeni predložene vrijednosti' kako biste prije spremanja pregledali predložene promjene.", + "data": { + "apply_suggestions": "Primijeni predložene vrijednosti" + }, + "data_description": { + "apply_suggestions": "Označite ovaj okvir da biste otvorili korak pregleda za predložene vrijednosti iz algoritma učenja. Ništa se ne sprema automatski.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Otkrivanje i pragovi snage", + "description": "Određuje kada se uređaj smatra UKLJUČENIM ili ISKLJUČENIM, energetske pragove i vremensko određivanje prijelaza stanja.", + "data": { + "start_duration_threshold": "Trajanje pokretanja odbijanja (s)", + "start_energy_threshold": "Prag početne energije (Wh)", + "completion_min_seconds": "Minimalno vrijeme izvođenja do završetka (sekunde)", + "start_threshold_w": "Početni prag (W)", + "stop_threshold_w": "Zaustavni prag (W)", + "end_energy_threshold": "Vrata krajnje energije (Wh)", + "running_dead_zone": "Mrtva zona pri pokretanju (sekunde)", + "end_repeat_count": "Kraj brojanja ponavljanja", + "min_off_gap": "Min. razmak između ciklusa (s)", + "sampling_interval": "Interval uzorkovanja (s)" + }, + "data_description": { + "start_duration_threshold": "Zanemarite kratke skokove snage kraće od ovog trajanja (sekunde). Filtrira skokove pri pokretanju (npr. 60 W za 2 s) prije nego što započne pravi ciklus. Zadano: 5s.", + "start_energy_threshold": "Ciklus mora akumulirati barem toliko energije (Wh) tijekom START faze da bi bio potvrđen. Zadano: 0,005 Wh.", + "completion_min_seconds": "Ciklusi kraći od ovoga bit će označeni kao \"prekinuti\" čak i ako završe prirodnim putem. Zadano: 600 s.", + "start_threshold_w": "Snaga mora porasti IZNAD ovog praga da bi ciklus POČEO. Postavite više od snage u stanju pripravnosti. Zadano: min_power + 1 W.", + "stop_threshold_w": "Snaga mora pasti ISPOD ovog praga da bi se ciklus ZAVRŠIO. Postavite malo iznad nule kako biste izbjegli buku koja izaziva lažne završetke. Zadano: 0,6 * min_power.", + "end_energy_threshold": "Ciklus završava samo ako je energija u zadnjem prozoru isključenja ispod ove vrijednosti (Wh). Sprječava lažne završetke ako snaga fluktuira blizu nule. Zadano: 0,05 Wh.", + "running_dead_zone": "Nakon što ciklus započne, zanemarite padove snage toliko sekundi kako biste spriječili otkrivanje lažnog kraja tijekom početne faze vrtnje. Postavite na 0 za onemogućavanje. Zadano: 0s.", + "end_repeat_count": "Koliko puta se uvjet završetka (snaga ispod praga za off_delay) mora ispuniti uzastopno prije završetka ciklusa. Više vrijednosti sprječavaju lažne završetke tijekom pauza. Zadano: 1.", + "min_off_gap": "Ako ciklus započne unutar toliko sekundi od završetka prethodnog, bit će SPOJENI u jedan ciklus.", + "sampling_interval": "Minimalni interval uzorkovanja u sekundama. Ažuriranja brža od ove stope bit će zanemarena. Zadano: 30 s (2 s za perilice rublja, perilice-sušilice i perilice posuđa)." + } + }, + "matching_section": { + "name": "Podudaranje profila i učenje", + "description": "Određuje koliko agresivno WashData uspoređuje aktivne cikluse s naučenim profilima i kada zatražiti povratne informacije.", + "data": { + "profile_match_min_duration_ratio": "Omjer minimalnog trajanja podudaranja profila (0,1-1,0)", + "profile_match_interval": "Interval podudaranja profila (sekunde)", + "profile_match_threshold": "Prag podudaranja profila", + "profile_unmatch_threshold": "Prag nepoklapanja profila", + "auto_label_confidence": "Pouzdanost automatske oznake (0-1)", + "learning_confidence": "Povjerljivost zahtjeva za povratne informacije (0-1)", + "suppress_feedback_notifications": "Spriječi obavijesti o povratnim informacijama", + "duration_tolerance": "Tolerancija trajanja (0-0,5)", + "smoothing_window": "Prozor za izglađivanje (uzorci)", + "profile_duration_tolerance": "Tolerancija trajanja podudaranja profila (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Omjer minimalnog trajanja za podudaranje profila. Ciklus rada mora biti barem ovaj dio trajanja profila da bi se podudarao. Niže = ranije podudaranje. Zadano: 0,50 (50%).", + "profile_match_interval": "Koliko često (u sekundama) pokušavati pronaći profil tijekom ciklusa. Niže vrijednosti omogućuju brže otkrivanje, ali koriste više procesora. Zadano: 300 s (5 minuta).", + "profile_match_threshold": "Minimalni DTW rezultat sličnosti (0,0–1,0) potreban da bi se profil smatrao podudaranjem. Više = strože podudaranje, manje lažno pozitivnih rezultata. Zadano: 0.4.", + "profile_unmatch_threshold": "DTW rezultat sličnosti (0,0–1,0) ispod kojeg se prethodno upareni profil odbija. Trebao bi biti niži od praga podudaranja kako bi se spriječilo treperenje. Zadano: 0,35.", + "auto_label_confidence": "Automatski označite cikluse na ili iznad ove pouzdanosti po završetku (0,0–1,0).", + "learning_confidence": "Minimalna pouzdanost za traženje provjere korisnika putem događaja (0,0–1,0).", + "suppress_feedback_notifications": "Kada je omogućeno, WashData će i dalje interno pratiti i zahtijevati povratne informacije, ali neće prikazivati ​​stalne obavijesti u kojima se traži da potvrdite cikluse. Korisno kada se ciklusi uvijek ispravno detektiraju, a upute vam odvlače pažnju.", + "duration_tolerance": "Tolerancija za procjene preostalog vremena tijekom izvođenja (0,0–0,5 odgovara 0–50%).", + "smoothing_window": "Broj nedavnih očitanja snage korištenih za izglađivanje pomičnog prosjeka.", + "profile_duration_tolerance": "Tolerancija koju koristi podudaranje profila za varijancu ukupnog trajanja (0,0–0,5). Zadano ponašanje: ±25%." + } + }, + "timing_section": { + "name": "Vremenski parametri, održavanje i otklanjanje pogrešaka", + "description": "Nadzornik, resetiranje napretka, automatsko održavanje i izlaganje entiteta za otklanjanje pogrešaka.", + "data": { + "watchdog_interval": "Interval čuvara (sekunde)", + "no_update_active_timeout": "Istek vremena bez ažuriranja (s)", + "progress_reset_delay": "Odgoda poništavanja napretka (sekunde)", + "auto_maintenance": "Omogući automatsko održavanje", + "expose_debug_entities": "Izložite entitete za otklanjanje pogrešaka", + "save_debug_traces": "Spremite tragove otklanjanja pogrešaka" + }, + "data_description": { + "watchdog_interval": "Sekunde između nadzornih provjera tijekom rada. Zadano: 5s. UPOZORENJE: Provjerite je li ovo VEĆI od intervala ažuriranja vašeg senzora kako biste izbjegli lažna zaustavljanja (npr. ako se senzor ažurira svakih 60 s, koristite 65 s+).", + "no_update_active_timeout": "Za senzore objavljivanja nakon promjene: prisilno prekini AKTIVNI ciklus samo ako ažuriranja ne stignu toliko sekundi. Završetak niske potrošnje još uvijek koristi odgodu isključivanja.", + "progress_reset_delay": "Nakon završetka ciklusa (100%), pričekajte ovoliko sekundi mirovanja prije ponovnog postavljanja napretka na 0%. Zadano: 300 s.", + "auto_maintenance": "Omogućite automatsko održavanje (popravak uzoraka, spajanje fragmenata).", + "expose_debug_entities": "Prikaži napredne senzore (povjerenje, faza, dvosmislenost) za otklanjanje pogrešaka.", + "save_debug_traces": "Pohrani detaljne podatke o rangiranju i praćenju napajanja u povijesti (povećava upotrebu pohrane)." + } + }, + "anti_wrinkle_section": { + "name": "Štit protiv bora (sušilice)", + "description": "Zanemarite okretaje bubnja nakon završetka ciklusa kako biste spriječili fantomske cikluse. Samo sušilica / perilica-sušilica.", + "data": { + "anti_wrinkle_enabled": "Štit protiv bora (samo sušilica)", + "anti_wrinkle_max_power": "Maksimalna snaga protiv bora (W)", + "anti_wrinkle_max_duration": "Maksimalno trajanje protiv bora (s)", + "anti_wrinkle_exit_power": "Izlazna snaga protiv bora (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Zanemarite kratke rotacije bubnja male snage nakon završetka ciklusa kako biste spriječili dvostruke cikluse (samo sušilica/perilica-sušilica).", + "anti_wrinkle_max_power": "Izbijanja na ili ispod ove snage tretiraju se kao rotacije protiv bora umjesto kao novi ciklusi. Primjenjuje se samo kada je omogućen Anti-Wrinkle Shield.", + "anti_wrinkle_max_duration": "Maksimalna duljina pucanja koja se tretira kao rotacija protiv bora. Primjenjuje se samo kada je omogućen Anti-Wrinkle Shield.", + "anti_wrinkle_exit_power": "Ako snaga padne ispod ove razine, odmah izađite iz anti-wrinkla (true off). Primjenjuje se samo kada je omogućen Anti-Wrinkle Shield." + } + }, + "delay_start_section": { + "name": "Otkrivanje odgođenog početka", + "description": "Trajnu pripravnost s niskom potrošnjom tretirajte kao stanje 'Čekanje na početak' dok ciklus stvarno ne započne.", + "data": { + "delay_start_detect_enabled": "Omogući otkrivanje odgođenog početka", + "delay_confirm_seconds": "Odgođeni početak: vrijeme potvrde pripravnosti (s)", + "delay_timeout_hours": "Odgođeni početak: maksimalno vrijeme čekanja (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kada je omogućeno, kratki skok niske potrošnje energije praćen kontinuiranim napajanjem u stanju pripravnosti otkriva se kao odgođeni početak. Senzor stanja pokazuje 'Čekanje na početak' tijekom odgode. Vrijeme trajanja programa ili napredak ne prati se dok ciklus zapravo ne započne. Zahtijeva da start_threshold_w bude postavljen iznad drain spike snage.", + "delay_confirm_seconds": "Snaga mora ostati između praga zaustavljanja (W) i praga pokretanja (W) ovoliko sekundi prije nego što WashData prijeđe u stanje 'Čekanje na početak'. Time se filtriraju kratki vrhovi pri kretanju kroz izbornike. Zadano: 60 s.", + "delay_timeout_hours": "Sigurnosno vremensko ograničenje: ako je stroj još uvijek u 'Čekanju pokretanja' nakon toliko sati, WashData se vraća na Isključeno. Zadano: 8 h." + } + }, + "external_triggers_section": { + "name": "Vanjski okidači, vrata i pauza", + "description": "Neobavezni vanjski senzor završetka ciklusa, senzor vrata za otkrivanje pauze / pražnjenja i prekidač za isključivanje napajanja tijekom pauze.", + "data": { + "external_end_trigger_enabled": "Omogući vanjski okidač završetka ciklusa", + "external_end_trigger": "Vanjski senzor završetka ciklusa", + "external_end_trigger_inverted": "Invertiraj logiku okidača (okidač isključen)", + "door_sensor_entity": "Senzor vrata", + "pause_cuts_power": "Isključite napajanje prilikom pauziranja", + "switch_entity": "Preklopni entitet (za pauziranje isključenja struje)", + "notify_unload_delay_minutes": "Odgoda obavijesti o čekanju praonice rublja (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Omogućite nadzor vanjskog binarnog senzora za pokretanje dovršetka ciklusa.", + "external_end_trigger": "Odaberite entitet binarnog senzora. Kada se ovaj senzor aktivira, trenutni ciklus će završiti sa statusom 'završeno'.", + "external_end_trigger_inverted": "Prema zadanim postavkama, okidač se aktivira kada se senzor UKLJUČI. Označite ovaj okvir za aktiviranje kada se senzor umjesto toga isključi.", + "door_sensor_entity": "Opcijski binarni senzor za vrata stroja (uključeno = otvoreno, isključeno = zatvoreno). Kada se vrata otvore tijekom aktivnog ciklusa, WashData će potvrditi da je ciklus namjerno pauziran. Nakon završetka ciklusa, ako su vrata i dalje zatvorena, stanje se mijenja u 'Čisto' dok se vrata ne otvore. Napomena: zatvaranje vrata NE automatski nastavlja pauzirani ciklus - nastavak se mora pokrenuti ručno putem gumba Nastavi ciklus ili usluge.", + "pause_cuts_power": "Kada pauzirate putem gumba Pause Cycle ili usluge, također isključite konfigurirani entitet prekidača. Stupa na snagu samo ako je entitet prekidača konfiguriran za ovaj uređaj.", + "switch_entity": "Entitet prekidača koji se mijenja prilikom pauziranja ili nastavka. Potrebno kada je omogućeno \"Isključi napajanje prilikom pauziranja\".", + "notify_unload_delay_minutes": "Pošaljite obavijest putem kanala obavijesti o završetku nakon što ciklus završi i vrata nisu otvorena toliko minuta (potreban je senzor za vrata). Postavite na 0 za onemogućavanje. Zadano: 60 min." + } + }, + "pump_section": { + "name": "Nadzor pumpe", + "description": "Samo za uređaje tipa pumpa. Aktivira događaj ako ciklus pumpe traje dulje od ovog vremena.", + "data": { + "pump_stuck_duration": "Prag (s) upozorenja za blokiranje pumpe (samo pumpa)" + }, + "data_description": { + "pump_stuck_duration": "Ako ciklus pumpanja još uvijek traje nakon toliko sekundi, ha_washdata_pump_stuck događaj se aktivira jednom. Upotrijebite ovo za otkrivanje zaglavljenog motora (uobičajeni rad pumpe je ispod 60 s). Zadano: 1800 s (30 min). Samo tip uređaja pumpe." + } + }, + "device_link_section": { + "name": "Veza uređaja", + "description": "Po želji povežite ovaj WashData uređaj s postojećim Home Assistant uređajem (na primjer pametnim utikačem ili samim uređajem). Uređaj WashData tada se prikazuje kao \"Povezan putem\" tog uređaja. Ostavite prazno kako bi WashData ostao samostalan uređaj.", + "data": { + "linked_device": "Povezani uređaj" + }, + "data_description": { + "linked_device": "Odaberite uređaj s kojim želite povezati WashData. Ovo dodaje referencu 'Povezano putem' u registar uređaja; WashData čuva vlastitu karticu uređaja i entitete. Poništite odabir da biste uklonili vezu." + } + } + } + }, + "diagnostics": { + "title": "Dijagnostika i održavanje", + "description": "Pokrenite radnje održavanja poput spajanja fragmentiranih ciklusa ili migracije pohranjenih podataka.\n\n**Upotreba prostora za pohranu**\n\n- Veličina datoteke: {file_size_kb} KB\n- Ciklusi: {cycle_count}\n- Profili: {profile_count}\n- Tragovi otklanjanja pogrešaka: {debug_count}", + "menu_options": { + "reprocess_history": "Održavanje: Ponovna obrada i optimizacija podataka", + "clear_debug_data": "Obrišite podatke o otklanjanju pogrešaka (oslobodite prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovratno)", + "export_import": "Izvoz/uvoz JSON s postavkama (kopiraj/zalijepi)", + "menu_back": "← Natrag" + } + }, + "clear_debug_data": { + "title": "Očisti podatke o otklanjanju pogrešaka", + "description": "Jeste li sigurni da želite izbrisati sve pohranjene tragove otklanjanja pogrešaka? Ovo će osloboditi prostor, ali ukloniti detaljne informacije o poretku iz prošlih ciklusa." + }, + "manage_cycles": { + "title": "Upravljanje ciklusima", + "description": "Nedavni ciklusi:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatsko označavanje starih ciklusa", + "select_cycle_to_label": "Specifični ciklus oznake", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/razdvajanja", + "trim_cycle_select": "Podaci o ciklusu trimanja", + "menu_back": "← Natrag" + } + }, + "manage_cycles_empty": { + "title": "Upravljanje ciklusima", + "description": "Još nema zabilježenih ciklusa.", + "menu_options": { + "auto_label_cycles": "Automatsko označavanje starih ciklusa", + "menu_back": "← Natrag" + } + }, + "manage_profiles": { + "title": "Upravljanje profilima", + "description": "Sažetak profila:\n{profile_summary}", + "menu_options": { + "create_profile": "Stvori novi profil", + "edit_profile": "Uredi/preimenuj profil", + "delete_profile_select": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Očisti povijest - grafikon i brisanje", + "assign_profile_phases_select": "Dodijelite fazne raspone", + "menu_back": "← Natrag" + } + }, + "manage_profiles_empty": { + "title": "Upravljanje profilima", + "description": "Još nema kreiranih profila.", + "menu_options": { + "create_profile": "Stvori novi profil", + "menu_back": "← Natrag" + } + }, + "manage_phase_catalog": { + "title": "Upravljanje katalogom faza", + "description": "Katalog faza (zadano za ovaj uređaj + prilagođene faze):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Stvori novu fazu", + "phase_catalog_edit_select": "Uredi fazu", + "phase_catalog_delete": "Izbriši fazu", + "menu_back": "← Natrag" + } + }, + "phase_catalog_create": { + "title": "Stvorite prilagođenu fazu", + "description": "Stvorite naziv i opis prilagođene faze i odaberite na koju se vrstu uređaja odnosi.", + "data": { + "target_device_type": "Vrsta ciljanog uređaja", + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_edit_select": { + "title": "Uredi prilagođenu fazu", + "description": "Odaberite prilagođenu fazu za uređivanje.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "phase_catalog_edit": { + "title": "Uredi prilagođenu fazu", + "description": "Faza uređivanja: {phase_name}", + "data": { + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_delete": { + "title": "Izbriši prilagođenu fazu", + "description": "Odaberite prilagođenu fazu za brisanje. Svi rasponi dodijeljeni pomoću ove faze bit će uklonjeni.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "assign_profile_phases": { + "title": "Dodijelite fazne raspone", + "description": "Pregled faze za profil: **{profile_name}**\n\n{timeline_svg}\n\n**Trenutni rasponi:**\n{current_ranges}\n\nUpotrijebite radnje u nastavku za dodavanje, uređivanje, brisanje ili spremanje raspona.", + "menu_options": { + "assign_profile_phases_add": "Dodaj fazni raspon", + "assign_profile_phases_edit_select": "Uredi raspon faza", + "assign_profile_phases_delete": "Izbriši fazni raspon", + "phase_ranges_clear": "Očisti sve raspone", + "assign_profile_phases_auto_detect": "Automatsko otkrivanje faza", + "phase_ranges_save": "Spremi i vrati", + "menu_back": "← Natrag" + } + }, + "assign_profile_phases_add": { + "title": "Dodaj fazni raspon", + "description": "Dodajte jedan raspon faza trenutnom nacrtu.", + "data": { + "phase_name": "Faza", + "start_min": "Startna minuta", + "end_min": "Završna minuta" + } + }, + "assign_profile_phases_edit_select": { + "title": "Uredi raspon faza", + "description": "Odaberite raspon za uređivanje.", + "data": { + "range_index": "Raspon faza" + } + }, + "assign_profile_phases_edit": { + "title": "Uredi raspon faza", + "description": "Ažurirajte odabrani raspon faza.", + "data": { + "phase_name": "Faza", + "start_min": "Startna minuta", + "end_min": "Završna minuta" + } + }, + "assign_profile_phases_delete": { + "title": "Izbriši fazni raspon", + "description": "Odaberite raspon koji želite ukloniti iz nacrta.", + "data": { + "range_index": "Raspon faza" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatski otkrivene faze", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} faza(e) otkriveno automatski.** Odaberite radnju ispod.", + "data": { + "action": "Akcijski" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Imenujte otkrivene faze", + "description": "Profil: **{profile_name}**\n\n**{detected_count} otkrivene faze:**\n{ranges_summary}\n\nDodijelite ime svakoj fazi." + }, + "assign_profile_phases_select": { + "title": "Dodijelite fazne raspone", + "description": "Odaberite profil za konfiguraciju raspona faza.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistika profila", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Stvori novi profil", + "description": "Stvorite novi profil ručno ili iz prošlog ciklusa.\n\nPrimjeri naziva profila: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Ime profila", + "reference_cycle": "Referentni ciklus (izborno)", + "manual_duration": "Ručno trajanje (minute)" + } + }, + "edit_profile": { + "title": "Uredi profil", + "description": "Odaberite profil za promjenu naziva", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Uredi pojedinosti profila", + "description": "Trenutačni profil: {current_name}", + "data": { + "new_name": "Ime profila", + "manual_duration": "Ručno trajanje (minute)" + } + }, + "delete_profile_select": { + "title": "Izbriši profil", + "description": "Odaberite profil za brisanje", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potvrdite brisanje profila", + "description": "⚠️ Ovo će trajno izbrisati profil: {profile_name}", + "data": { + "unlabel_cycles": "Uklonite oznaku iz ciklusa pomoću ovog profila" + } + }, + "auto_label_cycles": { + "title": "Automatsko označavanje starih ciklusa", + "description": "Pronađeno ukupno {total_count} ciklusa. Profili: {profiles}", + "data": { + "confidence_threshold": "Minimalna pouzdanost (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Odaberite Cycle to Label", + "description": "✓ = dovršeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "select_cycle_to_delete": { + "title": "Izbriši ciklus", + "description": "⚠️ Ovo će trajno izbrisati odabrani ciklus", + "data": { + "cycle_id": "Kruži za brisanje" + } + }, + "label_cycle": { + "title": "Ciklus naljepnica", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Ime novog profila (ako se stvara)" + } + }, + "post_process": { + "title": "Povijest procesa (spoj/razdvajanje)", + "description": "Očistite povijest ciklusa spajanjem fragmentiranih ciklusa i dijeljenjem neispravno spojenih ciklusa unutar vremenskog okvira. Unesite broj sati za pregled (upotrijebite 999999 za sve).", + "data": { + "time_range": "Prozor unatrag (sati)", + "gap_seconds": "Spajanje/dijeljenje razmaka (sekunde)" + }, + "data_description": { + "time_range": "Broj sati za skeniranje fragmentiranih ciklusa za spajanje. Koristite 999999 za obradu svih povijesnih podataka.", + "gap_seconds": "Prag za odlučivanje o razdvajanju ili spajanju. Spajaju se praznine KRAĆE od ove. Praznine DULJE od ovoga su podijeljene. Smanjite ovo za prisilno razdvajanje ciklusa koji je pogrešno spojen." + } + }, + "cleanup_profile": { + "title": "Očisti povijest - Odaberite profil", + "description": "Odaberite profil za vizualizaciju. Ovo će generirati grafikon koji prikazuje sve prošle cikluse za ovaj profil kako bi se lakše identificirali odstupanja.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Očisti povijest - grafikon i brisanje", + "description": "![Grafikon]({graph_url})\n\n**Vizualizacija {profile_name}**\n\nGrafikon prikazuje pojedinačne cikluse kao obojene linije. Identificirajte outliere (linije daleko od grupe) i uskladite njihovu boju na donjem popisu kako biste ih izbrisali.\n\n**Odaberite cikluse za TRAJNO brisanje:**", + "data": { + "cycles_to_delete": "Ciklusi za brisanje (provjerite podudarne boje)" + } + }, + "interactive_editor": { + "title": "Interaktivni uređivač spajanja/razdvajanja", + "description": "Odaberite ručnu radnju koju ćete izvršiti na povijesti ciklusa.", + "menu_options": { + "editor_split": "Podijelite ciklus (pronađite praznine)", + "editor_merge": "Ciklusi spajanja (spajanje fragmenata)", + "editor_delete": "Izbriši ciklus(e)", + "menu_back": "← Natrag" + } + }, + "editor_select": { + "title": "Odaberite Ciklusi", + "description": "Odaberite ciklus(e) za obradu.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklusi" + } + }, + "editor_configure": { + "title": "Konfiguriraj i primijeni", + "description": "{preview_md}", + "data": { + "confirm_action": "Akcijski", + "merged_profile": "Rezultirajući profil", + "new_profile_name": "Novi naziv profila", + "confirm_commit": "Da, potvrđujem ovu akciju", + "segment_0_profile": "Profil segmenta 1", + "segment_1_profile": "Profil segmenta 2", + "segment_2_profile": "Profil segmenta 3", + "segment_3_profile": "Profil segmenta 4", + "segment_4_profile": "Profil segmenta 5", + "segment_5_profile": "Profil segmenta 6", + "segment_6_profile": "Profil segmenta 7", + "segment_7_profile": "Profil segmenta 8", + "segment_8_profile": "Profil segmenta 9", + "segment_9_profile": "Profil segmenta 10" + } + }, + "editor_split_params": { + "title": "Split konfiguracija", + "description": "Podesite osjetljivost za razdvajanje ovog ciklusa. Svaki razmak u praznom hodu dulji od ovoga uzrokovat će razdvajanje.", + "data": { + "split_mode": "Metoda dijeljenja" + } + }, + "editor_split_auto_params": { + "title": "Konfiguracija automatskog razdvajanja", + "description": "Prilagodite osjetljivost za razdvajanje ovog ciklusa. Svaki razmak mirovanja duži od ovoga uzrokovat će razdvajanje.", + "data": { + "min_gap_seconds": "Prag razmaka za razdvajanje (sekunde)" + } + }, + "editor_split_manual_params": { + "title": "Ručne vremenske oznake razdvajanja", + "description": "{preview_md}Prozor ciklusa: **{cycle_start_wallclock} → {cycle_end_wallclock}** na {cycle_date}.\n\nUnesite jednu ili više vremenskih oznaka razdvajanja (`HH:MM` ili `HH:MM:SS`), po jednu u svaki red. Ciklus će se prerezati na svakoj vremenskoj oznaci — N vremenskih oznaka daje N+1 segmenata.", + "data": { + "split_timestamps": "Vremenske oznake podjele" + } + }, + "reprocess_history": { + "title": "Održavanje: Ponovna obrada i optimizacija podataka", + "description": "Obavlja dubinsko čišćenje povijesnih podataka:\n1. Smanjuje očitanja nulte snage (tišina).\n2. Popravlja vrijeme/trajanje ciklusa.\n3. Ponovno izračunava sve statističke modele (omotnice).\n\n**Pokrenite ovo da biste riješili probleme s podacima ili primijenili nove algoritme.**" + }, + "wipe_history": { + "title": "Brisanje povijesti (samo testiranje)", + "description": "⚠️ Ovo će trajno izbrisati SVE pohranjene cikluse i profile za ovaj uređaj. Ovo se ne može poništiti!" + }, + "export_import": { + "title": "Izvoz/Uvoz JSON", + "description": "Kopirajte/zalijepite cikluse, profile i postavke za ovaj uređaj. Izvezite ispod ili zalijepite JSON za uvoz.", + "data": { + "mode": "Akcijski", + "json_payload": "Potpuna konfiguracija JSON" + }, + "data_description": { + "mode": "Odaberite Izvoz za kopiranje trenutnih podataka i postavki ili Uvoz za lijepljenje izvezenih podataka s drugog WashData uređaja.", + "json_payload": "Za izvoz: kopirajte ovaj JSON (uključuje cikluse, profile i sve fino podešene postavke). Za uvoz: zalijepite JSON izvezen iz WashData." + } + }, + "record_cycle": { + "title": "Ciklus snimanja", + "description": "Ručno snimite ciklus kako biste stvorili čisti profil bez smetnji.\n\nStatus: **{status}**\nTrajanje: {duration}s\nUzorci: {samples}", + "menu_options": { + "record_refresh": "Osvježi status", + "record_stop": "Zaustavi snimanje (spremi i obradi)", + "record_start": "Započni novo snimanje", + "record_process": "Obradi posljednju snimku (izreži i spremi)", + "record_discard": "Odbaci posljednju snimku", + "menu_back": "← Natrag" + } + }, + "record_process": { + "title": "Snimanje procesa", + "description": "![Grafikon]({graph_url})\n\n**Grafikon prikazuje neobrađenu snimku.** Plavo = Zadrži, Crveno = Predloženo skraćivanje.\n*Graf je statičan i ne ažurira se s promjenama obrasca.*\n\nSnimanje zaustavljeno. Obrezivanja su usklađena s otkrivenom brzinom uzorkovanja (~{sampling_rate} s).\n\nNeobrađeno trajanje: {duration} s\nUzorci: {samples}", + "data": { + "head_trim": "Početak skraćivanja (sekunde)", + "tail_trim": "Kraj skraćivanja (sekunde)", + "save_mode": "Spremi odredište", + "profile_name": "Ime profila" + } + }, + "learning_feedbacks": { + "title": "Povratne informacije o učenju", + "description": "Neriješene povratne informacije: {count}\n\n{pending_table}\n\nOdaberite zahtjev za pregled ciklusa za obradu.", + "menu_options": { + "learning_feedbacks_pick": "Pregledaj neriješenu povratnu informaciju", + "learning_feedbacks_dismiss_all": "Odbaci sve neriješene povratne informacije", + "menu_back": "← Natrag" + } + }, + "learning_feedbacks_pick": { + "title": "Odaberite povratne informacije za pregled", + "description": "Odaberite zahtjev za pregled ciklusa koji želite otvoriti.", + "data": { + "selected_feedback": "Odaberite ciklus za pregled" + } + }, + "learning_feedbacks_empty": { + "title": "Povratne informacije o učenju", + "description": "Nema pronađenih zahtjeva za povratne informacije na čekanju.", + "menu_options": { + "menu_back": "← Natrag" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Odbaci sve povratne informacije na čekanju", + "description": "Upravo ćete odbaciti **{count}** zahtjeva za povratne informacije na čekanju. Odbačeni ciklusi ostaju u vašoj povijesti, ali neće pridonijeti novom signalu učenja. Ovo se ne može poništiti.\n\n✅ Označite okvir ispod i kliknite **Pošalji** da odbacite sve.\n❌ Ostavite ga neoznačenim i kliknite **Pošalji** da otkažete i vratite se.", + "data": { + "confirm_dismiss_all": "Potvrdi: odbaci sve neriješene zahtjeve za povratne informacije" + } + }, + "resolve_feedback": { + "title": "Riješite povratne informacije", + "description": "{comparison_img}{candidates_table}\n**Otkriveni profil**: {detected_profile} ({confidence_pct}%)\n**Procijenjeno trajanje**: {est_duration_min} min\n**Stvarno trajanje**: {act_duration_min} min\n\n__Odaberi radnju ispod:__", + "data": { + "action": "Što biste htjeli raditi?", + "corrected_profile": "Ispravan program (ako se ispravlja)", + "corrected_duration": "Točno trajanje u sekundama (ako se ispravlja)" + } + }, + "trim_cycle_select": { + "title": "Ciklus trimanja - Odaberite Ciklus", + "description": "Odaberite ciklus za obrezivanje. ✓ = dovršeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "trim_cycle": { + "title": "Ciklus podrezivanja", + "description": "Trenutačni prozor: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akcijski" + } + }, + "trim_cycle_start": { + "title": "Postavite početak trimanja", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutačni početak: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nOdaberite novo vrijeme početka pomoću alata za odabir sata u nastavku.", + "data": { + "trim_start_time": "Novo vrijeme početka", + "trim_start_min": "Novi početak (minute od početka ciklusa)" + } + }, + "trim_cycle_end": { + "title": "Postavite završetak skraćivanja", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutačni kraj: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nOdaberite novo vrijeme završetka pomoću alata za odabir sata u nastavku.", + "data": { + "trim_end_time": "Novo vrijeme završetka", + "trim_end_min": "Novi kraj (minute od početka ciklusa)" + } + } + }, + "error": { + "import_failed": "Uvoz nije uspio. Zalijepite valjani JSON za izvoz WashData.", + "select_exactly_one": "Odaberite točno jedan ciklus za ovu radnju.", + "select_at_least_one": "Odaberite barem jedan ciklus za ovu radnju.", + "select_at_least_two": "Odaberite barem dva ciklusa za ovu radnju.", + "profile_exists": "Profil s ovim nazivom već postoji", + "rename_failed": "Preimenovanje profila nije uspjelo. Profil možda ne postoji ili je novo ime već zauzeto.", + "assignment_failed": "Nije uspjelo dodjeljivanje profila ciklusu", + "duplicate_phase": "Faza s ovim imenom već postoji.", + "phase_not_found": "Odabrana faza nije pronađena.", + "invalid_phase_name": "Naziv faze ne može biti prazan.", + "phase_name_too_long": "Naziv faze je predug.", + "invalid_phase_range": "Fazni raspon je nevažeći. Kraj mora biti veći od početka.", + "invalid_phase_timestamp": "Vremenska oznaka nije važeća. Koristite format GGGG-MM-DD HH:MM ili HH:MM.", + "overlapping_phase_ranges": "Rasponi faza se preklapaju. Podesite raspone i pokušajte ponovno.", + "incomplete_phase_row": "Svaki red faze mora sadržavati fazu plus potpune početne/završne vrijednosti za odabrani način.", + "timestamp_mode_cycle_required": "Odaberite izvorni ciklus kada koristite način vremenskog označavanja.", + "no_phase_ranges": "Za tu radnju još nisu dostupni rasponi faza.", + "feedback_notification_title": "WashData: Ciklus provjere ({device})", + "feedback_notification_message": "**Uređaj**: {device}\n**Program**: {program} ({confidence}% pouzdanosti)\n**Vrijeme**: {time}\n\nWashData treba vašu pomoć da potvrdi ovaj otkriveni ciklus.\n\nIdite na **Postavke > Uređaji i usluge > WashData > Konfiguracija > Povratne informacije o učenju** kako biste potvrdili ili ispravili ovaj rezultat.", + "suggestions_ready_notification_title": "WashData: Predložene postavke spremne ({device})", + "suggestions_ready_notification_message": "Senzor **Predložene postavke** sada javlja **{count}** preporuke koje se mogu poduzeti.\n\nDa biste ih pregledali i primijenili: **Postavke > Uređaji i usluge > WashData > Konfiguracija > Napredne postavke > Primijeni predložene vrijednosti**.\n\nPrijedlozi nisu obavezni i prikazuju se na pregled prije spremanja.", + "trim_range_invalid": "Početak mora biti prije kraja. Molimo prilagodite svoje trim točke.", + "trim_failed": "Trimanje nije uspjelo. Ciklus možda više ne postoji ili je prozor prazan.", + "invalid_split_timestamp": "Vremenska oznaka nije valjana ili je izvan prozora ciklusa. Upotrijebite HH:MM ili HH:MM:SS unutar prozora ciklusa.", + "no_split_segments_found": "Iz ovih vremenskih oznaka nije bilo moguće stvoriti valjane segmente. Provjerite je li svaka vremenska oznaka unutar prozora ciklusa i da segmenti traju najmanje 1 minutu.", + "auto_tune_suggestion": "{device_type} {device_title} otkrivenih dvostrukih ciklusa. Predložena promjena min_power: {current_min}W -> {new_min}W (ne primjenjuje se automatski).", + "auto_tune_title": "Automatsko podešavanje WashData", + "auto_tune_fallback": "{device_type} {device_title} otkrivenih dvostrukih ciklusa. Predložena promjena min_power: {current_min}W -> {new_min}W (ne primjenjuje se automatski)." + }, + "abort": { + "no_cycles_found": "Nisu pronađeni ciklusi", + "no_split_segments_found": "U odabranom ciklusu nisu pronađeni segmenti koji se mogu podijeliti.", + "cycle_not_found": "Odabrani ciklus nije moguće učitati.", + "no_power_data": "Nema dostupnih podataka o praćenju snage za odabrani ciklus.", + "no_profiles_found": "Nema pronađenih profila. Prvo napravite profil.", + "no_profiles_for_matching": "Nema dostupnih profila za podudaranje. Prvo napravite profile.", + "no_unlabeled_cycles": "Svi ciklusi su već označeni", + "no_suggestions": "Još nema dostupnih predloženih vrijednosti. Pokrenite nekoliko ciklusa i pokušajte ponovno.", + "no_predictions": "Bez predviđanja", + "no_custom_phases": "Nisu pronađene prilagođene faze.", + "reprocess_success": "Uspješno ponovno obrađeno {count} ciklusa.", + "debug_data_cleared": "Uspješno izbrisani podaci o otklanjanju pogrešaka iz {count} ciklusa." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Početak ciklusa", + "cycle_finish": "Završetak ciklusa", + "cycle_live": "Napredak uživo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Bez natpisa)", + "create_new_profile": "Stvori novi profil", + "remove_label": "Ukloni oznaku", + "no_reference_cycle": "(Nema referentnog ciklusa)", + "all_device_types": "Sve vrste uređaja", + "current_suffix": "(trenutno)", + "editor_select_info": "Odaberite 1 ciklus za razdvajanje ili 2+ ciklusa za spajanje.", + "editor_select_info_split": "Odaberite 1 ciklus za podjelu.", + "editor_select_info_merge": "Odaberite 2+ ciklusa za spajanje.", + "editor_select_info_delete": "Odaberite 1+ ciklusa za brisanje.", + "no_cycles_recorded": "Još nema zabilježenih ciklusa.", + "profile_summary_line": "- **{name}**: {count} ciklusa, {avg}m prosj", + "no_profiles_created": "Još nema kreiranih profila.", + "phase_preview": "Pregled faze", + "phase_preview_no_curve": "Prosječna krivulja profila još nije dostupna. Pokreni/označi više ciklusa za ovaj profil.", + "average_power_curve": "Krivulja prosječne snage", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciklusa, ~{duration}m prosječno)", + "profile_option_short_fmt": "{name} ({count} ciklusa, ~{duration} m)", + "deleted_cycles_from_profile": "Izbrisano {count} ciklusa iz {profile}.", + "not_enough_data_graph": "Nema dovoljno podataka za generiranje grafikona.", + "no_profiles_found": "Nema pronađenih profila.", + "cycle_info_fmt": "Ciklus: {start}, {duration}m, struja: {label}", + "top_candidates_header": "### Najbolji kandidati", + "tbl_profile": "Profil", + "tbl_confidence": "Povjerenje", + "tbl_correlation": "Korelacija", + "tbl_duration_match": "Podudaranje trajanja", + "detected_profile": "Otkriven profil", + "estimated_duration": "Procijenjeno trajanje", + "actual_duration": "Stvarno trajanje", + "choose_action": "__Odaberi radnju ispod:__", + "tbl_count": "Računati", + "tbl_avg": "Prosj", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energija (prosj.)", + "tbl_energy_total": "Energija (ukupno)", + "tbl_consistency": "Sastoji se.", + "tbl_last_run": "Zadnja trka", + "graph_legend_title": "Legenda grafikona", + "graph_legend_body": "Plava traka predstavlja promatrani minimalni i maksimalni raspon potrošnje energije. Linija prikazuje krivulju prosječne snage.", + "recording_preview": "Pregled snimanja", + "trim_start": "Početak podrezivanja", + "trim_end": "Trim End", + "split_preview_title": "Split Preview", + "split_preview_found_fmt": "Pronađeno je {count} segmenata.", + "split_preview_confirm_fmt": "Kliknite Potvrdi da biste ovaj ciklus podijelili na {count} zasebnih ciklusa.", + "merge_preview_title": "Pregled spajanja", + "merge_preview_joining_fmt": "Pridruživanje {count} ciklusa. Praznine će biti popunjene očitanjima od 0W.", + "editor_delete_preview_title": "Pregled brisanja", + "editor_delete_preview_intro": "Odabrani ciklusi bit će trajno izbrisani:", + "editor_delete_preview_confirm": "Kliknite Potvrdi za trajno brisanje ovih zapisa ciklusa.", + "no_power_preview": "*Nema dostupnih podataka o snazi ​​za pregled.*", + "profile_comparison": "Usporedba profila", + "actual_cycle_label": "Ovaj ciklus (stvarno)", + "storage_usage_header": "Korištenje pohrane", + "storage_file_size": "Veličina datoteke", + "storage_cycles": "Ciklusi", + "storage_profiles": "Profili", + "storage_debug_traces": "Tragovi otklanjanja pogrešaka", + "overview_suffix": "Pregled", + "phase_preview_for_profile": "Pregled faze za profil", + "current_ranges_header": "Trenutni rasponi", + "assign_phases_help": "Upotrijebite radnje u nastavku za dodavanje, uređivanje, brisanje ili spremanje raspona.", + "profile_summary_header": "Sažetak profila", + "recent_cycles_header": "Nedavni ciklusi", + "trim_cycle_preview_title": "Pregled ciklusa skraćivanja", + "trim_cycle_preview_no_data": "Nema dostupnih podataka o snazi ​​za ovaj ciklus.", + "trim_cycle_preview_kept_suffix": "zadržao", + "table_program": "Program", + "table_when": "Kada", + "table_length": "Trajanje", + "table_match": "Podudaranje", + "table_profile": "Profil", + "table_cycles": "Ciklusi", + "table_avg_length": "Prosj. trajanje", + "table_last_run": "Zadnja trka", + "table_avg_energy": "Prosj. energija", + "table_detected_program": "Otkriveni program", + "table_confidence": "Povjerenje", + "table_reported": "Prijavljeno", + "phase_builtin_suffix": "(Ugrađeno)", + "phase_none_available": "Nema dostupnih faza.", + "settings_deprecation_warning": "⚠️ **Zastarjela vrsta uređaja:** {device_type} je zakazano za uklanjanje u budućem izdanju. Podudarni cjevovod WashData ne daje pouzdane rezultate za ovu klasu uređaja. Vaša integracija nastavlja raditi tijekom razdoblja obustave; da biste utišali ovo upozorenje, prebacite **Vrstu uređaja** u nastavku na jednu od podržanih vrsta (Perilica rublja, Sušilica, Kombinirana perilica-sušilica, Perilica posuđa, Friteza, Pekač kruha ili Pumpa) ili na **Ostalo (Napredno)** ako vaš uređaj ne odgovara nijednoj od podržanih vrsta. **Ostalo (Napredno)** namjerno isporučuje generičke zadane postavke koje nisu podešene ni za jedan određeni uređaj, tako da ćete morati sami konfigurirati pragove, vremenska ograničenja i odgovarajuće parametre; sve vaše postojeće postavke su sačuvane kada se prebacite.", + "phase_other_device_types": "Ostale vrste uređaja:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Podijelite ciklus (pronađite praznine)", + "merge": "Ciklusi spajanja (spajanje fragmenata)", + "delete": "Izbriši ciklus(e)" + } + }, + "split_mode": { + "options": { + "auto": "Automatski otkrij razmake neaktivnosti", + "manual": "Ručne vremenske oznake" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Održavanje: Ponovna obrada i optimizacija podataka", + "clear_debug_data": "Obrišite podatke o otklanjanju pogrešaka (oslobodite prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovratno)", + "export_import": "Izvoz/uvoz JSON s postavkama (kopiraj/zalijepi)" + } + }, + "export_import_mode": { + "options": { + "export": "Izvezi sve podatke", + "import": "Uvoz/spoj podataka" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatsko označavanje starih ciklusa", + "select_cycle_to_label": "Specifični ciklus oznake", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/razdvajanja", + "trim_cycle": "Podaci o ciklusu trimanja" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Stvori novi profil", + "edit_profile": "Uredi/preimenuj profil", + "delete_profile": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Očisti povijest - grafikon i brisanje", + "assign_phases": "Dodijelite fazne raspone" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Stvori novu fazu", + "edit_custom_phase": "Uredi fazu", + "delete_custom_phase": "Izbriši fazu" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Dodaj fazni raspon", + "edit_range": "Uredi raspon faza", + "delete_range": "Izbriši fazni raspon", + "clear_ranges": "Očisti sve raspone", + "auto_detect_ranges": "Automatsko otkrivanje faza", + "save_ranges": "Spremi i vrati" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Osvježi status", + "stop_recording": "Zaustavi snimanje (spremi i obradi)", + "start_recording": "Započni novo snimanje", + "process_recording": "Obradi posljednju snimku (izreži i spremi)", + "discard_recording": "Odbaci posljednju snimku" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Stvori novi profil", + "existing_profile": "Dodaj u postojeći profil", + "discard": "Odbaci snimanje" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potvrdi - Ispravno otkrivanje", + "correct": "Točno - Odaberite program/trajanje", + "ignore": "Ignoriraj - Lažno pozitivan/bučan ciklus", + "delete": "Izbriši - Ukloni iz povijesti" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Imenujte i primijenite faze", + "cancel": "Vrati se bez promjena" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Postavite početnu točku", + "set_end": "Postavite krajnju točku", + "reset": "Vrati na puno trajanje", + "apply": "Primijeni trimanje", + "cancel": "Otkazati" + } + }, + "device_type": { + "options": { + "washing_machine": "Perilica za rublje", + "dryer": "Sušilica", + "washer_dryer": "Perilica-sušilica Combo", + "dishwasher": "Perilica posuđa", + "coffee_machine": "Aparat za kavu (zastarjelo)", + "ev": "Električno vozilo (zastarjelo)", + "air_fryer": "Zračna friteza", + "heat_pump": "Toplinska pumpa (zastarjelo)", + "bread_maker": "pekač kruha", + "pump": "Pumpa / Sump Pumpa", + "oven": "Pećnica (zastarjelo)", + "other": "Ostalo (napredno)" + } + } + }, + "services": { + "label_cycle": { + "name": "Ciklus naljepnica", + "description": "Dodijelite postojeći profil prošlom ciklusu ili uklonite oznaku.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice za označavanje." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za označavanje." + }, + "profile_name": { + "name": "Ime profila", + "description": "Naziv postojećeg profila (izradite profile u izborniku Upravljanje profilima). Ostavite prazno za uklanjanje oznake." + } + } + }, + "create_profile": { + "name": "Stvori profil", + "description": "Napravite novi profil (samostalni ili temeljen na referentnom ciklusu).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice rublja." + }, + "profile_name": { + "name": "Ime profila", + "description": "Naziv za novi profil (npr. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "ID referentnog ciklusa", + "description": "Izborni ID ciklusa na kojem se temelji ovaj profil." + } + } + }, + "delete_profile": { + "name": "Izbriši profil", + "description": "Izbrišite profil i opcionalno poništite označavanje ciklusa koji ga koriste.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice rublja." + }, + "profile_name": { + "name": "Ime profila", + "description": "Profil za brisanje." + }, + "unlabel_cycles": { + "name": "Poništavanje oznaka ciklusa", + "description": "Uklonite oznaku profila iz ciklusa koji koriste ovaj profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatsko označavanje starih ciklusa", + "description": "Retroaktivno označite neoznačene cikluse pomoću podudaranja profila.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice rublja." + }, + "confidence_threshold": { + "name": "Prag povjerenja", + "description": "Minimalna pouzdanost podudaranja (0,50-0,95) za primjenu oznaka." + } + } + }, + "export_config": { + "name": "Izvoz konfiguracije", + "description": "Izvezite profile, cikluse i postavke ovog uređaja u JSON datoteku (po uređaju).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Perilica rublja uređaj za izvoz." + }, + "path": { + "name": "Put", + "description": "Izborni apsolutni put datoteke za pisanje (zadano je /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Uvezi konfiguraciju", + "description": "Uvezite profile, cikluse i postavke za ovaj uređaj iz JSON izvozne datoteke (postavke se mogu prebrisati).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje rublja u koji se uvozi." + }, + "path": { + "name": "Put", + "description": "Apsolutni put do izvozne JSON datoteke za uvoz." + } + } + }, + "submit_cycle_feedback": { + "name": "Pošaljite povratne informacije o ciklusu", + "description": "Potvrdite ili ispravite automatski otkriveni program nakon završenog ciklusa. Navedite `entry_id` (napredno) ili `device_id` (preporučeno).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData (preporučuje se umjesto entry_id)." + }, + "entry_id": { + "name": "ID unosa", + "description": "ID unosa konfiguracije za uređaj (alternativa device_id)." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa prikazan u obavijesti o povratnim informacijama/zapisnicima." + }, + "user_confirmed": { + "name": "Potvrdite otkriveni program", + "description": "Postavite true ako je detektirani program točan." + }, + "corrected_profile": { + "name": "Ispravljeni profil", + "description": "Ako nije potvrđeno, navedite točan naziv profila/programa." + }, + "corrected_duration": { + "name": "Ispravljeno trajanje (sekunde)", + "description": "Izborno ispravljeno trajanje u sekundama." + }, + "notes": { + "name": "Bilješke", + "description": "Neobavezne bilješke o ovom ciklusu." + } + } + }, + "record_start": { + "name": "Početak ciklusa snimanja", + "description": "Započnite ručno snimanje čistog ciklusa (zaobilazi svu podudarnu logiku).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice za snimanje." + } + } + }, + "record_stop": { + "name": "Zaustavljanje ciklusa snimanja", + "description": "Zaustavite ručno snimanje.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj perilice za zaustavljanje snimanja." + } + } + }, + "trim_cycle": { + "name": "Ciklus podrezivanja", + "description": "Skratite podatke o snazi ​​prošlog ciklusa na određeni vremenski prozor.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za obrezivanje." + }, + "trim_start_s": { + "name": "Početak skraćivanja (sekunde)", + "description": "Sačuvajte podatke iz ovog pomaka u sekundi. Zadano 0." + }, + "trim_end_s": { + "name": "Kraj skraćivanja (sekunde)", + "description": "Zadržite podatke na ovom pomaku u sekundi. Zadano = puno trajanje." + } + } + }, + "pause_cycle": { + "name": "Pauziraj ciklus", + "description": "Pauzirajte aktivni ciklus za uređaj WashData.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za pauziranje." + } + } + }, + "resume_cycle": { + "name": "Nastavi ciklus", + "description": "Nastavite pauzirani ciklus za WashData uređaj.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za nastavak." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Trčanje" + }, + "match_ambiguity": { + "name": "Dvosmislenost podudaranja" + } + }, + "sensor": { + "washer_state": { + "name": "Stanje", + "state": { + "off": "Isključeno", + "idle": "besposlen", + "starting": "Počinje", + "running": "Trčanje", + "paused": "Pauzirano", + "user_paused": "Pauzirano od korisnika", + "ending": "Završetak", + "finished": "Gotovo", + "anti_wrinkle": "Protiv bora", + "delay_wait": "Čekanje na početak", + "interrupted": "Prekinut", + "force_stopped": "Prisilno zaustavljeno", + "rinse": "Ispiranje", + "unknown": "Nepoznato", + "clean": "Čist" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Preostalo vrijeme" + }, + "total_duration": { + "name": "Ukupno trajanje" + }, + "cycle_progress": { + "name": "Napredak" + }, + "current_power": { + "name": "Trenutna snaga" + }, + "elapsed_time": { + "name": "Proteklo vrijeme" + }, + "debug_info": { + "name": "Informacije o otklanjanju pogrešaka" + }, + "match_confidence": { + "name": "Pouzdanost podudaranja" + }, + "top_candidates": { + "name": "Najbolji kandidati", + "state": { + "none": "Nijedan" + } + }, + "current_phase": { + "name": "Trenutna faza" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Broj profila {profile_name}", + "unit_of_measurement": "ciklusi" + }, + "suggestions": { + "name": "Predložene postavke" + }, + "pump_runs_today": { + "name": "Pumpa radi (zadnja 24 h)" + }, + "cycle_count": { + "name": "Broj ciklusa" + } + }, + "select": { + "program_select": { + "name": "Program ciklusa", + "state": { + "auto_detect": "Automatsko otkrivanje" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Prisilni završetak ciklusa" + }, + "pause_cycle": { + "name": "Pauziraj ciklus" + }, + "resume_cycle": { + "name": "Nastavi ciklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Potreban je važeći device_id." + }, + "cycle_id_required": { + "message": "Potreban je važeći cycle_id." + }, + "profile_name_required": { + "message": "Potreban je važeći profile_name." + }, + "device_not_found": { + "message": "Uređaj nije pronađen." + }, + "no_config_entry": { + "message": "Nije pronađen unos konfiguracije za uređaj." + }, + "integration_not_loaded": { + "message": "Integracija nije učitana za ovaj uređaj." + }, + "cycle_not_found_or_no_power": { + "message": "Ciklus nije pronađen ili nema podataka o snazi." + }, + "trim_failed_empty_window": { + "message": "Podrezivanje nije uspjelo - ciklus nije pronađen, nema podataka o snazi ​​ili je rezultirajući prozor prazan." + }, + "trim_invalid_range": { + "message": "trim_end_s mora biti veći od trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/hu.json b/custom_components/ha_washdata/translations/hu.json new file mode 100644 index 0000000..6bb3954 --- /dev/null +++ b/custom_components/ha_washdata/translations/hu.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData beállítása", + "description": "Állítsa be a mosógépet vagy más készüléket.\n\nTeljesítményérzékelő szükséges.\n\n**Ezután a rendszer megkérdezi, hogy szeretné-e létrehozni első profilját.**", + "data": { + "name": "Eszköz neve", + "device_type": "Eszköz típusa", + "power_sensor": "Teljesítmény érzékelő", + "min_power": "Minimális teljesítmény küszöb (W)" + }, + "data_description": { + "name": "Barátságos név ennek az eszköznek (pl. „Mosógép”, „Mosogatógép”).", + "device_type": "Milyen típusú készülék ez? Segít személyre szabni az észlelést és a címkézést.", + "power_sensor": "Az érzékelő entitás, amely valós idejű energiafogyasztást jelent (wattban) az okosdugóról.", + "min_power": "Az e küszöbérték feletti teljesítményértékek (wattban) azt jelzik, hogy a készülék működik. Kezdje 2 W-tal a legtöbb eszköz esetében." + } + }, + "first_profile": { + "title": "Első profil létrehozása", + "description": "A **Ciklus** a készülék teljes működése (pl. egy adag szennyes). A **Profil** ezeket a ciklusokat típus szerint csoportosítja (pl. „Pamut”, „Gyorsmosás”). A profil létrehozása azonnal segíti az integráció becsült időtartamát.\n\nEzt a profilt manuálisan is kiválaszthatja az eszközvezérlők között, ha a rendszer nem észleli automatikusan.\n\n**A lépés kihagyásához hagyja üresen a „Profilnév” mezőt.**", + "data": { + "profile_name": "Profilnév (nem kötelező)", + "manual_duration": "Becsült időtartam (perc)" + } + } + }, + "error": { + "cannot_connect": "Nem sikerült csatlakozni", + "invalid_auth": "Érvénytelen hitelesítés", + "unknown": "Váratlan hiba" + }, + "abort": { + "already_configured": "Az eszköz már konfigurálva van" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Tekintse át a tanulási visszajelzéseket", + "settings": "Beállítások", + "notifications": "Értesítések", + "manage_cycles": "Ciklusok kezelése", + "manage_profiles": "Profilok kezelése", + "manage_phase_catalog": "Fáziskatalógus kezelése", + "record_cycle": "Felvételi ciklus (kézi)", + "diagnostics": "Diagnosztika és karbantartás" + } + }, + "apply_suggestions": { + "title": "Javasolt értékek másolása", + "description": "Ez átmásolja az aktuális javasolt értékeket a mentett beállításokba. Ez egy egyszeri művelet, és nem engedélyezi az automatikus felülírást.\n\nMásolandó értékek:\n{suggested}", + "data": { + "confirm": "Erősítse meg a másolási műveletet" + } + }, + "apply_suggestions_confirm": { + "title": "Tekintse át a javasolt változtatásokat", + "description": "A WashData **{pending_count}** javasolt értéket talált az alkalmazásra.\n\nVáltozások:\n{changes}\n\n✅ Jelölje be az alábbi négyzetet, és kattintson a **Küldés** gombra a módosítások azonnali alkalmazásához és mentéséhez.\n❌ Hagyja bejelölés nélkül, és kattintson a **Küldés** gombra a visszavonáshoz és a visszalépéshez.", + "data": { + "confirm_apply_suggestions": "Alkalmazza és mentse el ezeket a módosításokat" + } + }, + "settings": { + "title": "Beállítások", + "description": "{deprecation_warning}Az észlelési küszöbök, a tanulás és a speciális viselkedés hangolása. Az alapértelmezések jól működnek a legtöbb beállításnál.\n\nJelenleg elérhető javasolt beállítások: {suggestions_count}.\nHa ez a szám 0 felett van, nyissa meg a Speciális beállításokat, és használja a „Javasolt értékek alkalmazása” lehetőséget az ajánlások áttekintéséhez.", + "data": { + "apply_suggestions": "Alkalmazza a javasolt értékeket", + "device_type": "Eszköz típusa", + "power_sensor": "Teljesítményérzékelő entitás", + "min_power": "Minimális teljesítmény (W)", + "off_delay": "Ciklusvégi késleltetés (ek)", + "notify_actions": "Értesítési műveletek", + "notify_people": "Késlelteti a kézbesítést, amíg ezek az emberek otthon nem lesznek", + "notify_only_when_home": "Az értesítések késleltetése, amíg a kiválasztott személy otthon van", + "notify_fire_events": "Automatizálási esemény indítása", + "notify_start_services": "Ciklus indítása - Értesítési célok", + "notify_finish_services": "Ciklus befejezése - Értesítési célok", + "notify_live_services": "Élő folyamat – Értesítési célok", + "notify_before_end_minutes": "Befejezés előtti értesítés (perccel a vége előtt)", + "notify_title": "Értesítés címe", + "notify_icon": "Értesítési ikon", + "notify_start_message": "Indítsa el az Üzenetformátumot", + "notify_finish_message": "Üzenetformátum befejezése", + "notify_pre_complete_message": "Kitöltés előtti üzenetformátum", + "show_advanced": "Speciális beállítások szerkesztése (következő lépés)" + }, + "data_description": { + "apply_suggestions": "Jelölje be ezt a négyzetet a tanulási algoritmus által javasolt értékek űrlapba másolásához. Mentés előtt tekintse át.\n\nJavasolt (tanulásból):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Válassza ki a készülék típusát (mosógép, szárítógép, mosogatógép, kávéfőző, elektromos). Ezt a címkét a rendszer minden ciklusban tárolja a jobb szervezés érdekében.", + "power_sensor": "Az érzékelő entitás, amely valós idejű teljesítményt jelent (wattban). Ezt bármikor módosíthatja anélkül, hogy elveszítené az előzményadatokat – hasznos lehet, ha kicseréli az intelligens csatlakozót.", + "min_power": "Az e küszöbérték feletti teljesítményértékek (wattban) azt jelzik, hogy a készülék működik. Kezdje 2 W-tal a legtöbb eszköz esetében.", + "off_delay": "Másodpercek alatt a simított teljesítménynek a leállítási küszöb alatt kell maradnia, hogy jelezze a befejezést. Alapértelmezett: 120 s.", + "notify_actions": "Opcionális Home Assistant műveletsor az értesítésekhez (szkriptek, értesítés, TTS stb.) Ha be van állítva, a műveletek a tartalék értesítési szolgáltatás előtt futnak le.", + "notify_people": "Jelenlétkapuzáshoz használt személy entitások. Ha engedélyezve van, az értesítések kézbesítése addig késik, amíg legalább egy kiválasztott személy otthon nem lesz.", + "notify_only_when_home": "Ha engedélyezve van, késleltesse az értesítéseket, amíg legalább egy kiválasztott személy otthon lesz.", + "notify_fire_events": "Ha engedélyezve van, aktiválja a Home Assistant eseményeit a ciklus kezdetéhez és végéhez (ha_washdata_cycle_started és ha_washdata_cycle_ended) az automatizálások irányítása érdekében.", + "notify_start_services": "Egy vagy több szolgáltatás értesíti, ha egy ciklus elkezdődik. Hagyja üresen az indítási értesítések kihagyásához.", + "notify_finish_services": "Egy vagy több szolgáltatás értesíti, ha egy ciklus véget ér vagy a végéhez közeledik. Hagyja üresen a befejezéssel kapcsolatos értesítések kihagyásához.", + "notify_live_services": "Egy vagy több értesítést küld a szolgáltatásnak, hogy a ciklus lefutása közben élő folyamatfrissítéseket kapjon (csak mobil kísérőalkalmazás). Hagyja üresen az élő frissítések kihagyásához.", + "notify_before_end_minutes": "Percekkel a ciklus becsült vége előtt az értesítés elindításához. A letiltáshoz állítsa 0-ra. Alapértelmezett: 0.", + "notify_title": "Egyéni cím az értesítésekhez. Támogatja a {device} helyőrzőt.", + "notify_icon": "Az értesítésekhez használandó ikon (pl. mdi:mosógép). Hagyja üresen, ha nem szeretne ikont küldeni.", + "notify_start_message": "Üzenet elküldve, amikor egy ciklus elindul. Támogatja a {device} helyőrzőt.", + "notify_finish_message": "Üzenet elküldve, amikor egy ciklus véget ért. Támogatja a {device}, {duration}, {program}, {energy_kwh}, {cost} helyőrzőket.", + "notify_pre_complete_message": "Az ismétlődő élő haladás szövege a ciklus futása közben frissül. Támogatja a {device}, {minutes}, {program} helyőrzőket." + } + }, + "notifications": { + "title": "Értesítések", + "description": "Konfigurálhatja az értesítési csatornákat, sablonokat és opcionális élő mobil-frissítéseket.", + "data": { + "notify_actions": "Értesítési műveletek", + "notify_people": "Késlelteti a kézbesítést, amíg ezek az emberek otthon nem lesznek", + "notify_only_when_home": "Az értesítések késleltetése, amíg a kiválasztott személy otthon van", + "notify_fire_events": "Automatizálási esemény indítása", + "notify_start_services": "Ciklus indítása - Értesítési célok", + "notify_finish_services": "Ciklus befejezése - Értesítési célok", + "notify_live_services": "Élő folyamat – Értesítési célok", + "notify_before_end_minutes": "Befejezés előtti értesítés (perccel a vége előtt)", + "notify_live_interval_seconds": "Élő frissítési időköz (másodperc)", + "notify_live_overrun_percent": "Élő frissítés túllépési engedmény (%)", + "notify_live_chronometer": "Kronométer Visszaszámláló időzítő", + "notify_title": "Értesítés címe", + "notify_icon": "Értesítési ikon", + "notify_start_message": "Indítsa el az Üzenetformátumot", + "notify_finish_message": "Üzenetformátum befejezése", + "notify_pre_complete_message": "Kitöltés előtti üzenetformátum", + "energy_price_entity": "Energiaár – entitás (opcionális)", + "energy_price_static": "Energiaár – Statikus érték (opcionális)", + "go_back": "Vissza mentés nélkül", + "notify_reminder_message": "Emlékeztető üzenet formátuma", + "notify_timeout_seconds": "Automatikus elvetés után (másodperc)", + "notify_channel": "Értesítési csatorna (állapot/élő/emlékeztető)", + "notify_finish_channel": "Elkészült az értesítési csatorna" + }, + "data_description": { + "go_back": "Jelölje be ezt, majd kattintson a Beküldés gombra, hogy eldobja a fenti módosításokat és visszatérjen a főmenübe.", + "notify_actions": "Opcionális Home Assistant műveletsor az értesítésekhez (szkriptek, értesítés, TTS stb.) Ha be van állítva, a műveletek a tartalék értesítési szolgáltatás előtt futnak le.", + "notify_people": "Jelenlétkapuzáshoz használt személy entitások. Ha engedélyezve van, az értesítések kézbesítése addig késik, amíg legalább egy kiválasztott személy otthon nem lesz.", + "notify_only_when_home": "Ha engedélyezve van, késleltesse az értesítéseket, amíg legalább egy kiválasztott személy otthon lesz.", + "notify_fire_events": "Ha engedélyezve van, aktiválja a Home Assistant eseményeit a ciklus kezdetéhez és végéhez (ha_washdata_cycle_started és ha_washdata_cycle_ended) az automatizálások irányítása érdekében.", + "notify_start_services": "Egy vagy több értesítési szolgáltatás figyelmezteti a ciklus kezdetét (pl. notify.mobile_app_my_phone). Hagyja üresen az indítási értesítések kihagyásához.", + "notify_finish_services": "Egy vagy több szolgáltatás értesíti, ha egy ciklus véget ér vagy a végéhez közeledik. Hagyja üresen a befejezéssel kapcsolatos értesítések kihagyásához.", + "notify_live_services": "Egy vagy több értesítést küld a szolgáltatásnak, hogy a ciklus lefutása közben élő folyamatfrissítéseket kapjon (csak mobilalkalmazás esetén). Hagyja üresen az élő frissítések kihagyásához.", + "notify_before_end_minutes": "Percekkel a ciklus becsült vége előtt az értesítés elindításához. A letiltáshoz állítsa 0-ra. Alapértelmezett: 0.", + "notify_live_interval_seconds": "Milyen gyakran küldenek frissítéseket futás közben. Az alacsonyabb értékek jobban reagálnak, de több értesítést igényelnek. A rendszer legalább 30 másodpercet kényszerít ki – a 30 másodperc alatti értékeket a rendszer automatikusan 30 másodpercre kerekíti.", + "notify_live_overrun_percent": "A hosszú/túlfutási ciklusok becsült frissítésszáma feletti biztonsági ráhagyás (például a 20% 1,2-szeres becsült frissítést tesz lehetővé).", + "notify_live_chronometer": "Ha engedélyezve van, minden élő frissítés tartalmaz egy kronométer visszaszámlálást a becsült befejezési időig. Az értesítési időzítő automatikusan folytatja a visszaszámlálást az eszközön a frissítések között (csak Android kísérőalkalmazások esetén).", + "notify_title": "Egyéni cím az értesítésekhez. Támogatja a {device} helyőrzőt.", + "notify_icon": "Az értesítésekhez használandó ikon (pl. mdi:mosógép). Hagyja üresen, ha nem szeretne ikont küldeni.", + "notify_start_message": "Üzenet elküldve, amikor egy ciklus elindul. Támogatja a {device} helyőrzőt.", + "notify_finish_message": "Üzenet elküldve, amikor egy ciklus véget ért. Támogatja a {device}, {duration}, {program}, {energy_kwh}, {cost} helyőrzőket.", + "energy_price_entity": "Válasszon egy numerikus entitást, amely tartalmazza az aktuális villamosenergia-árat (pl. sensor.electricity_price, input_number.energy_rate). Ha be van állítva, engedélyezi a(z) {cost} helyőrzőt. Elsőbbséget élvez a statikus értékkel szemben, ha mindkettő be van állítva.", + "energy_price_static": "Fix villanyár kWh-nként. Ha be van állítva, engedélyezi a(z) {cost} helyőrzőt. Figyelmen kívül hagyva, ha egy entitás fent van konfigurálva. Adja hozzá a pénznem szimbólumát az üzenetsablonhoz, pl. {cost} €.", + "notify_reminder_message": "Az egyszeri emlékeztető szövege elküldte a beállított számú percet a becsült vége előtt. Különbözik a fenti, ismétlődő élő frissítésektől. Támogatja a {device}, {minutes}, {program} helyőrzőket.", + "notify_timeout_seconds": "Az értesítések automatikus elvetése ennyi másodperc után (a kísérőalkalmazás „időtúllépéseként” továbbítása). Állítsa 0-ra, hogy megőrizze őket az elvetésig. Alapértelmezett: 0.", + "notify_channel": "Android kísérőalkalmazás-értesítési csatorna az állapotról, az élő folyamatról és az emlékeztetőkről. A csatorna hangja és fontossága a kísérőalkalmazásban állítható be a csatornanév első használatakor – a WashData csak a csatorna nevét állítja be. Hagyja üresen az alkalmazás alapértelmezett beállításához.", + "notify_finish_channel": "Külön Android csatorna a kész riasztáshoz (ezt az emlékeztető és a mosásra váró hang is használja), hogy saját hangja legyen. Hagyja üresen a fenti csatorna újrafelhasználásához.", + "notify_pre_complete_message": "Az ismétlődő élő haladás szövege a ciklus futása közben frissül. Támogatja a {device}, {minutes}, {program} helyőrzőket." + } + }, + "advanced_settings": { + "title": "Speciális beállítások", + "description": "Az észlelési küszöbök, a tanulás és a speciális viselkedés hangolása. Az alapértelmezések jól működnek a legtöbb konfigurációban.", + "sections": { + "suggestions_section": { + "name": "Javasolt beállítások", + "description": "Elérhető {suggestions_count} megtanult javaslat. Jelölje be a 'Javasolt értékek alkalmazása' lehetőséget, hogy mentés előtt áttekinthesse a javasolt módosításokat.", + "data": { + "apply_suggestions": "Alkalmazza a javasolt értékeket" + }, + "data_description": { + "apply_suggestions": "Jelölje be ezt a jelölőnégyzetet a tanulási algoritmus által javasolt értékek áttekintési lépésének megnyitásához. Semmi sem kerül automatikusan mentésre.\n\nJavasolt (tanulásból):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Észlelés és teljesítményküszöbök", + "description": "Meghatározza, mikor tekinthető a készülék BEKAPCSOLTNAK vagy KIKAPCSOLTNAK, az energiaküszöböket és az állapotátmenetek időzítését.", + "data": { + "start_duration_threshold": "Visszapattanás kezdeti időtartama (s)", + "start_energy_threshold": "Indítási energiakapu (Wh)", + "completion_min_seconds": "Befejezési minimális futási idő (másodperc)", + "start_threshold_w": "Kezdési küszöb (W)", + "stop_threshold_w": "Leállási küszöb (W)", + "end_energy_threshold": "Befejezési energiakapu (Wh)", + "running_dead_zone": "Holtidő az indulás után (másodperc)", + "end_repeat_count": "Ismétlésszám vége", + "min_off_gap": "Minimális rés a ciklusok között (s)", + "sampling_interval": "Mintavételi intervallum(ok)" + }, + "data_description": { + "start_duration_threshold": "Hagyja figyelmen kívül az ennél az időtartamnál (másodpercekben) rövidebb rövid teljesítményugrásokat. Kiszűri a rendszerindítási tüskéket (pl. 60 W 2 másodpercig) a valódi ciklus megkezdése előtt. Alapértelmezett: 5s.", + "start_energy_threshold": "A ciklusnak legalább ennyi energiát (Wh) kell felhalmoznia a START fázis alatt, hogy megerősítsük. Alapértelmezett: 0,005 Wh.", + "completion_min_seconds": "Az ennél rövidebb ciklusok „megszakítottként” lesznek megjelölve, még akkor is, ha természetesen befejeződnek. Alapértelmezett: 600.", + "start_threshold_w": "A teljesítménynek e küszöb fölé kell emelkednie a ciklus elindításához. Állítsa magasabbra, mint a készenléti teljesítmény. Alapértelmezett: min_power + 1 W.", + "stop_threshold_w": "A teljesítménynek e küszöb alá kell csökkennie a ciklus VÉGÉHEZ. Állítsa valamivel nulla fölé, hogy elkerülje a hamis végeket okozó zajt. Alapértelmezett: 0,6 * min_power.", + "end_energy_threshold": "A ciklus csak akkor ér véget, ha az utolsó kikapcsolási késleltetési ablakban az energia ennél az értéknél alacsonyabb (Wh). Megakadályozza a hamis célokat, ha a teljesítmény nulla közelében ingadozik. Alapértelmezett: 0,05 Wh.", + "running_dead_zone": "A ciklus indítása után hagyja figyelmen kívül a teljesítménycsökkenéseket ennyi másodpercig, hogy elkerülje a téves végérzékelést a kezdeti felpörgetési fázisban. A letiltáshoz állítsa 0-ra. Alapértelmezett: 0s.", + "end_repeat_count": "A befejezési feltételnek (az off_delay küszöbértéke alatti teljesítmény) egymás után teljesülnie kell a ciklus befejezése előtt. A magasabb értékek megakadályozzák a hamis befejezést a szünetek alatt. Alapértelmezett: 1.", + "min_off_gap": "Ha egy ciklus az előző befejezésétől számított ennyi másodpercen belül kezdődik, akkor ezek egyetlen ciklusba egyesülnek.", + "sampling_interval": "Minimális mintavételi intervallum másodpercben. Az ennél gyorsabb frissítéseket figyelmen kívül hagyja. Alapértelmezés: 30 s (2 mp mosógépeknél, mosó-szárítógépeknél és mosogatógépeknél)." + } + }, + "matching_section": { + "name": "Profilillesztés és tanulás", + "description": "Meghatározza, milyen agresszíven illeszti a WashData a futó ciklusokat a megtanult profilokhoz, és mikor kérjen visszajelzést.", + "data": { + "profile_match_min_duration_ratio": "Profilegyezési minimális időtartam aránya (0,1–1,0)", + "profile_match_interval": "Profilegyezési intervallum (másodperc)", + "profile_match_threshold": "Profil egyezési küszöb", + "profile_unmatch_threshold": "Profileltérési küszöb", + "auto_label_confidence": "Automatikus címke megbízhatóság (0-1)", + "learning_confidence": "Visszajelzési kérés bizalom (0-1)", + "suppress_feedback_notifications": "A visszajelzési értesítések letiltása", + "duration_tolerance": "Időtartam tolerancia (0-0,5)", + "smoothing_window": "Simító ablak (minták)", + "profile_duration_tolerance": "Profilegyezési időtartam tolerancia (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimális időtartam aránya a profilillesztéshez. A futási ciklusnak legalább a profil időtartamának ezen hányadának kell lennie, hogy megfeleljen. Alsó = korábbi egyezés. Alapértelmezett: 0,50 (50%).", + "profile_match_interval": "Milyen gyakran (másodpercben) próbálja meg a profilegyeztetést egy ciklus során. Az alacsonyabb értékek gyorsabb észlelést biztosítanak, de több CPU-t használnak. Alapértelmezés: 300 mp (5 perc).", + "profile_match_threshold": "Minimális DTW hasonlósági pontszám (0,0–1,0) ahhoz, hogy egy profilt egyezésnek tekintsünk. Magasabb = szigorúbb egyezés, kevesebb téves pozitív. Alapértelmezett: 0.4.", + "profile_unmatch_threshold": "DTW hasonlósági pontszám (0,0–1,0), amely alatt a korábban egyeztetett profil elutasításra kerül. Alacsonyabbnak kell lennie az egyezési küszöbnél a villogás elkerülése érdekében. Alapértelmezett: 0,35.", + "auto_label_confidence": "A ciklusok automatikus címkézése ezen a konfidenciaszinten vagy a felett a befejezéskor (0,0–1,0).", + "learning_confidence": "Minimális megbízhatóság a felhasználó-ellenőrzés kéréséhez egy eseményen keresztül (0,0–1,0).", + "suppress_feedback_notifications": "Ha engedélyezve van, a WashData továbbra is nyomon követi és belső visszajelzést kér, de nem jelenít meg folyamatos értesítéseket, amelyek a ciklusok megerősítését kérik. Hasznos, ha a rendszer mindig megfelelően érzékeli a ciklusokat, és a figyelmeztetéseket elvonja a figyelmed.", + "duration_tolerance": "Tolerancia a hátralévő időre vonatkozó becslésekhez egy futás során (0,0–0,5 0–50%-nak felel meg).", + "smoothing_window": "A mozgóátlag simításhoz használt legutóbbi teljesítményleolvasások száma.", + "profile_duration_tolerance": "A profilillesztés által használt tűrés a teljes időtartam varianciájához (0,0–0,5). Alapértelmezett viselkedés: ±25%." + } + }, + "timing_section": { + "name": "Időzítés, karbantartás és hibakeresés", + "description": "Watchdog, előrehaladás-visszaállítás, automatikus karbantartás és hibakereső entitások megjelenítése.", + "data": { + "watchdog_interval": "Watchdog intervallum (másodperc)", + "no_update_active_timeout": "Frissítés nélküli időtúllépés (ek)", + "progress_reset_delay": "Folyamat visszaállítási késleltetése (másodperc)", + "auto_maintenance": "Engedélyezze az automatikus karbantartást", + "expose_debug_entities": "A hibakeresési entitások közzététele", + "save_debug_traces": "Mentse a hibakeresési nyomokat" + }, + "data_description": { + "watchdog_interval": "Másodpercek az őrző-ellenőrzések között futás közben. Alapértelmezett: 5s. FIGYELMEZTETÉS: Győződjön meg arról, hogy ez NAGYOBB, mint az érzékelő frissítési időköze, hogy elkerülje a téves leállásokat (pl. ha az érzékelő 60 másodpercenként frissül, használjon 65 másodpercet+).", + "no_update_active_timeout": "Változáskor közzétett érzékelők esetén: csak akkor kényszerítsen le egy AKTÍV ciklust, ha ennyi másodpercig nem érkezik frissítés. Az alacsony fogyasztású befejezés továbbra is a kikapcsolási késleltetést használja.", + "progress_reset_delay": "Egy ciklus befejeződése (100%) után várjon ennyi másodpercnyi üresjáratot, mielőtt visszaállítja a folyamatot 0%-ra. Alapértelmezett: 300.", + "auto_maintenance": "Az automatikus karbantartás engedélyezése (minták javítása, töredékek egyesítése).", + "expose_debug_entities": "Speciális érzékelők (bizalom, fázis, kétértelműség) megjelenítése a hibakereséshez.", + "save_debug_traces": "Részletes rangsorolási és teljesítmény-nyomkövetési adatokat tárolhat az előzményekben (Növeli a tárhelyhasználatot)." + } + }, + "anti_wrinkle_section": { + "name": "Ránctalanító védelem (szárítók)", + "description": "Hagyja figyelmen kívül a dob forgásait a ciklus vége után, hogy elkerülje a fantomciklusokat. Csak szárítógép / mosó-szárítógép.", + "data": { + "anti_wrinkle_enabled": "Ránctalanító védelem (csak szárítógépen)", + "anti_wrinkle_max_power": "Ránctalanító maximális teljesítmény (W)", + "anti_wrinkle_max_duration": "Ránctalanító maximális időtartam (s)", + "anti_wrinkle_exit_power": "Ránctalanító kilépési teljesítmény (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Hagyja figyelmen kívül a rövid, kis teljesítményű dobforgatásokat a ciklus végén, hogy elkerülje a szellemciklusokat (csak szárító/mosó-szárítógép).", + "anti_wrinkle_max_power": "Az ennél a teljesítménynél vagy az alatti kitöréseket a rendszer ránctalanító forgatásként kezeli az új ciklusok helyett. Csak akkor érvényes, ha a ránctalanító pajzs engedélyezve van.", + "anti_wrinkle_max_duration": "Maximális kitörési hossz ránctalanító forgatásként kezelhető. Csak akkor érvényes, ha a ránctalanító pajzs engedélyezve van.", + "anti_wrinkle_exit_power": "Ha a teljesítmény e szint alá csökken, azonnal lépjen ki a ránctalanítóból (true off). Csak akkor érvényes, ha a ránctalanító pajzs engedélyezve van." + } + }, + "delay_start_section": { + "name": "Késleltetett indítás észlelése", + "description": "A tartós, kis fogyasztású készenlétet tekintse 'Várakozás az indításra' állapotnak, amíg a ciklus ténylegesen el nem indul.", + "data": { + "delay_start_detect_enabled": "Késleltetett indítás észlelésének engedélyezése", + "delay_confirm_seconds": "Késleltetett indítás: készenléti megerősítési idő (s)", + "delay_timeout_hours": "Késleltetett indítás: maximális várakozási idő (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Ha engedélyezve van, a rendszer késleltetett indításként egy rövid, alacsony fogyasztású lemerülési csúcsot, majd tartós készenléti tápellátást észlel. Az állapotérzékelő a „Várakozás az indításra” üzenetet mutatja a késleltetés alatt. A program nem követi nyomon a program idejét vagy előrehaladását, amíg a ciklus el nem kezdődik. A start_threshold_w értéket a leeresztőcsúcs-teljesítmény fölé kell beállítani.", + "delay_confirm_seconds": "A teljesítménynek ennyi másodpercig a leállítási küszöb (W) és az indítási küszöb (W) között kell maradnia, mielőtt a WashData 'Várakozás az indításra' állapotba lép. Ez kiszűri a menünavigáció közbeni rövid kiugrásokat. Alapértelmezett: 60 s.", + "delay_timeout_hours": "Biztonsági időtúllépés: ha a gép ennyi óra elteltével is „Várakozás az indításra” üzemmódban van, a WashData visszaáll Ki értékre. Alapértelmezett: 8 óra." + } + }, + "external_triggers_section": { + "name": "Külső triggerek, ajtó és szünet", + "description": "Opcionális külső ciklusvégi érzékelő, ajtóérzékelő a szünet / kiürítés észleléséhez, valamint áramtalanító kapcsoló szünet esetén.", + "data": { + "external_end_trigger_enabled": "Külső ciklusvégi trigger engedélyezése", + "external_end_trigger": "Külső ciklusvég érzékelő", + "external_end_trigger_inverted": "Invert trigger logika (trigger KI)", + "door_sensor_entity": "Ajtó érzékelő", + "pause_cuts_power": "Áramtalanítás szüneteltetéskor", + "switch_entity": "Kapcsoló entitás (az áramszünet szüneteltetéséhez)", + "notify_unload_delay_minutes": "A mosásra váró értesítés késése (perc)" + }, + "data_description": { + "external_end_trigger_enabled": "Külső bináris érzékelő felügyeletének engedélyezése a ciklus befejezésének indításához.", + "external_end_trigger": "Válasszon ki egy bináris érzékelő entitást. Amikor ez az érzékelő aktiválódik, az aktuális ciklus „befejezett” állapottal ér véget.", + "external_end_trigger_inverted": "Alapértelmezés szerint a trigger akkor lép működésbe, amikor az érzékelő BEKAPCSOL. Jelölje be ezt a jelölőnégyzetet, ha ehelyett az érzékelő KIKAPCSOLÁSA esetén tüzel.", + "door_sensor_entity": "Opcionális bináris érzékelő a gépajtóhoz (be = nyitva, ki = zárva). Ha az ajtó kinyílik egy aktív ciklus alatt, a WashData megerősíti, hogy a ciklus szándékosan szünetel. A ciklus végén, ha az ajtó még mindig zárva van, az állapot „Tiszta”-ra vált, amíg az ajtót ki nem nyitják. Megjegyzés: az ajtó bezárása NEM folytatja automatikusan a szünetelt ciklust – a folytatást manuálisan kell elindítani a Ciklus folytatása gombbal vagy a szervizzel.", + "pause_cuts_power": "Ha a Pause Cycle gombbal vagy szolgáltatással szüneteltet, kapcsolja ki a konfigurált kapcsoló entitást is. Csak akkor lép életbe, ha kapcsoló entitás van konfigurálva ehhez az eszközhöz.", + "switch_entity": "A kapcsoló entitás, amely szüneteltetéskor vagy folytatáskor válthat. Szükséges, ha a „Tápellátás leállítása szüneteltetéskor” engedélyezve van.", + "notify_unload_delay_minutes": "Értesítés küldése a befejezésértesítési csatornán, miután a ciklus véget ért, és az ajtót ennyi percig nem nyitották ki (Ajtóérzékelő szükséges). A letiltáshoz állítsa 0-ra. Alapértelmezett: 60 perc." + } + }, + "pump_section": { + "name": "Szivattyúfigyelő", + "description": "Csak szivattyú típusú eszközökhöz. Eseményt vált ki, ha a szivattyúciklus ennél hosszabb ideig fut.", + "data": { + "pump_stuck_duration": "A szivattyú elakadt riasztási küszöbértéke (csak a szivattyú)" + }, + "data_description": { + "pump_stuck_duration": "Ha egy szivattyúciklus ennyi másodperc után is fut, a ha_washdata_pump_stuck esemény egyszer aktiválódik. Használja ezt az elakadt motor észlelésére (tipikus olajteknő szivattyú futása 60 s alatt van). Alapértelmezett: 1800 s (30 perc). Csak a szivattyú készülék típusa." + } + }, + "device_link_section": { + "name": "Eszköz hivatkozás", + "description": "Opcionálisan kapcsolja össze ezt a WashData eszközt egy meglévő Home Assistant eszközzel (például az intelligens csatlakozóval vagy magával a készülékkel). Ezután a WashData eszköz „Csatlakozva” az adott eszközön keresztül jelenik meg. Hagyja üresen, hogy a WashData önálló eszköz maradjon.", + "data": { + "linked_device": "Kapcsolt eszköz" + }, + "data_description": { + "linked_device": "Válassza ki azt az eszközt, amelyhez a WashData-t csatlakoztatni szeretné. Ez hozzáad egy „Csatlakozva” hivatkozást az eszköznyilvántartáshoz; A WashData megőrzi saját eszközkártyáját és entitásait. A hivatkozás eltávolításához törölje a kijelölést." + } + } + } + }, + "diagnostics": { + "title": "Diagnosztika és karbantartás", + "description": "Futtasson karbantartási műveleteket, például egyesítse a töredezett ciklusokat vagy migrálja a tárolt adatokat.\n\n**Tárhelyhasználat**\n\n- Fájlméret: {file_size_kb} KB\n- Ciklusok: {cycle_count}\n- Profilok: {profile_count}\n- Hibakeresési nyomok: {debug_count}", + "menu_options": { + "reprocess_history": "Karbantartás: Adatok újrafeldolgozása és optimalizálása", + "clear_debug_data": "Hibakeresési adatok törlése (tárhely felszabadítása)", + "wipe_history": "Az eszköz ÖSSZES adatának törlése (visszafordíthatatlan)", + "export_import": "JSON exportálása/importálása beállításokkal (másolás/beillesztés)", + "menu_back": "← Vissza" + } + }, + "clear_debug_data": { + "title": "Törölje a hibakeresési adatokat", + "description": "Biztosan törölni szeretné az összes tárolt hibakeresési nyomot? Ez helyet szabadít fel, de eltávolítja a részletes rangsorolási információkat az elmúlt ciklusokból." + }, + "manage_cycles": { + "title": "Ciklusok kezelése", + "description": "Legutóbbi ciklusok:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Régi ciklusok automatikus címkézése", + "select_cycle_to_label": "Címkespecifikus ciklus", + "select_cycle_to_delete": "Ciklus törlése", + "interactive_editor": "Egyesítés/felosztás interaktív szerkesztő", + "trim_cycle_select": "Vágási ciklus adatai", + "menu_back": "← Vissza" + } + }, + "manage_cycles_empty": { + "title": "Ciklusok kezelése", + "description": "Még nem rögzítettek ciklusokat.", + "menu_options": { + "auto_label_cycles": "Régi ciklusok automatikus címkézése", + "menu_back": "← Vissza" + } + }, + "manage_profiles": { + "title": "Profilok kezelése", + "description": "Profil összefoglaló:\n{profile_summary}", + "menu_options": { + "create_profile": "Új profil létrehozása", + "edit_profile": "Profil szerkesztése/átnevezése", + "delete_profile_select": "Profil törlése", + "profile_stats": "Profil statisztika", + "cleanup_profile": "Törölje az előzményeket – Graph & Delete", + "assign_profile_phases_select": "Fázistartományok hozzárendelése", + "menu_back": "← Vissza" + } + }, + "manage_profiles_empty": { + "title": "Profilok kezelése", + "description": "Még nincsenek profilok.", + "menu_options": { + "create_profile": "Új profil létrehozása", + "menu_back": "← Vissza" + } + }, + "manage_phase_catalog": { + "title": "Fáziskatalógus kezelése", + "description": "Fáziskatalógus (az eszköz alapértelmezései + egyéni fázisok):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Új fázis létrehozása", + "phase_catalog_edit_select": "Fázis szerkesztése", + "phase_catalog_delete": "Fázis törlése", + "menu_back": "← Vissza" + } + }, + "phase_catalog_create": { + "title": "Egyéni fázis létrehozása", + "description": "Hozzon létre egy egyéni fázisnevet és -leírást, és válassza ki, hogy melyik eszköztípusra vonatkozik.", + "data": { + "target_device_type": "Céleszköz típusa", + "phase_name": "Fázis neve", + "phase_description": "Fázis leírása" + } + }, + "phase_catalog_edit_select": { + "title": "Egyéni fázis szerkesztése", + "description": "Válassza ki a szerkeszteni kívánt egyéni fázist.", + "data": { + "phase_name": "Egyedi fázis" + } + }, + "phase_catalog_edit": { + "title": "Egyéni fázis szerkesztése", + "description": "Szerkesztési fázis: {phase_name}", + "data": { + "phase_name": "Fázis neve", + "phase_description": "Fázis leírása" + } + }, + "phase_catalog_delete": { + "title": "Egyéni fázis törlése", + "description": "Válassza ki a törölni kívánt egyéni fázist. Az ezt a fázist használó minden hozzárendelt tartomány el lesz távolítva.", + "data": { + "phase_name": "Egyedi fázis" + } + }, + "assign_profile_phases": { + "title": "Fázistartományok hozzárendelése", + "description": "A profil fázis előnézete: **{profile_name}**\n\n{timeline_svg}\n\n**Jelenlegi tartományok:**\n{current_ranges}\n\nTartományok hozzáadásához, szerkesztéséhez, törléséhez vagy mentéséhez használja az alábbi műveleteket.", + "menu_options": { + "assign_profile_phases_add": "Fázistartomány hozzáadása", + "assign_profile_phases_edit_select": "Fázistartomány szerkesztése", + "assign_profile_phases_delete": "Fázistartomány törlése", + "phase_ranges_clear": "Minden tartomány törlése", + "assign_profile_phases_auto_detect": "Fázisok automatikus felismerése", + "phase_ranges_save": "Mentés és visszaküldés", + "menu_back": "← Vissza" + } + }, + "assign_profile_phases_add": { + "title": "Fázistartomány hozzáadása", + "description": "Adjon hozzá egy fázistartományt az aktuális piszkozathoz.", + "data": { + "phase_name": "Fázis", + "start_min": "Kezdő perc", + "end_min": "Vég perc" + } + }, + "assign_profile_phases_edit_select": { + "title": "Fázistartomány szerkesztése", + "description": "Válassza ki a szerkeszteni kívánt tartományt.", + "data": { + "range_index": "Fázis tartomány" + } + }, + "assign_profile_phases_edit": { + "title": "Fázistartomány szerkesztése", + "description": "Frissítse a kiválasztott fázistartományt.", + "data": { + "phase_name": "Fázis", + "start_min": "Kezdő perc", + "end_min": "Vég perc" + } + }, + "assign_profile_phases_delete": { + "title": "Fázistartomány törlése", + "description": "Válassza ki a vázlatból eltávolítani kívánt tartományt.", + "data": { + "range_index": "Fázis tartomány" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatikusan észlelt fázisok", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fázis automatikusan észlelve.** Válasszon egy műveletet lent.", + "data": { + "action": "Akció" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Az észlelt fázisok neve", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fázis észlelve:**\n{ranges_summary}\n\nAdjon nevet minden fázisnak." + }, + "assign_profile_phases_select": { + "title": "Fázistartományok hozzárendelése", + "description": "Válasszon egy profilt a fázistartományok konfigurálásához.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profil statisztika", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Új profil létrehozása", + "description": "Hozzon létre egy új profilt manuálisan vagy egy korábbi ciklusból.\n\nPéldák a profilnévre: „Kímélő”, „Nagy teherbírás”, „Gyorsmosás”", + "data": { + "profile_name": "Profil neve", + "reference_cycle": "Referenciaciklus (opcionális)", + "manual_duration": "Manuális időtartam (perc)" + } + }, + "edit_profile": { + "title": "Profil szerkesztése", + "description": "Válassza ki az átnevezni kívánt profilt", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Profiladatok szerkesztése", + "description": "Jelenlegi profil: {current_name}", + "data": { + "new_name": "Profil neve", + "manual_duration": "Manuális időtartam (perc)" + } + }, + "delete_profile_select": { + "title": "Profil törlése", + "description": "Válassza ki a törölni kívánt profilt", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Erősítse meg a Profil törlését", + "description": "⚠️ Ezzel véglegesen törli a következő profilt: {profile_name}", + "data": { + "unlabel_cycles": "Távolítsa el a címkét a ciklusokról ezzel a profillal" + } + }, + "auto_label_cycles": { + "title": "Régi ciklusok automatikus címkézése", + "description": "Összesen {total_count} ciklust találtunk. Profilok: {profiles}", + "data": { + "confidence_threshold": "Minimális bizalom (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Válassza a Ciklus a címkézéshez lehetőséget", + "description": "✓ = befejezve, ⚠ = folytatva, ✗ = megszakítva", + "data": { + "cycle_id": "Ciklus" + } + }, + "select_cycle_to_delete": { + "title": "Ciklus törlése", + "description": "⚠️ Ezzel véglegesen törli a kiválasztott ciklust", + "data": { + "cycle_id": "Ciklus a törléshez" + } + }, + "label_cycle": { + "title": "Címkeciklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Új profilnév (ha létrehozza)" + } + }, + "post_process": { + "title": "Folyamatelőzmények (egyesítés/felosztás)", + "description": "Tisztítsa meg a cikluselőzményeket a töredezett futtatások egyesítésével és a helytelenül egyesített ciklusok időablakon belüli felosztásával. Adja meg a visszatekintéshez szükséges órák számát (mindenhez használjon 999999-et).", + "data": { + "time_range": "Visszatekintési ablak (óra)", + "gap_seconds": "Egyesítés/felosztás (másodperc)" + }, + "data_description": { + "time_range": "Az órák száma a töredezett ciklusok egyesítéséhez szükséges kereséshez. Használja a 999999-et az összes előzményadat feldolgozásához.", + "gap_seconds": "A felosztás és az összevonás eldöntésének küszöbe. Az ennél RÖVIDEBB hézagok összevonásra kerülnek. Ennél HOSSZABB rések hasadnak. Csökkentse ezt, hogy felosztást kényszerítsen ki egy hibásan összevont ciklusra." + } + }, + "cleanup_profile": { + "title": "Törölje az előzményeket – válassza a Profilt", + "description": "Válassza ki a megjelenítendő profilt. Ez létrehoz egy diagramot, amely bemutatja a profil összes elmúlt ciklusát, hogy segítsen azonosítani a kiugró értékeket.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Törölje az előzményeket – Graph & Delete", + "description": "![Grafikon]({graph_url})\n\n**Vizualizálás {profile_name}**\n\nA grafikonon az egyes ciklusok színes vonalakként jelennek meg. Az alábbi listában azonosítsa a kiugró értékeket (a csoporttól távol eső vonalakat), és állítsa be színüket a törléshez.\n\n**Válassza ki a VÉGLEGES törléshez szükséges ciklusokat:**", + "data": { + "cycles_to_delete": "Ciklus a törléshez (ellenőrizze a megfelelő színeket)" + } + }, + "interactive_editor": { + "title": "Egyesítés/felosztás interaktív szerkesztő", + "description": "Válasszon ki egy kézi műveletet a cikluselőzményeken végrehajtani.", + "menu_options": { + "editor_split": "Ciklus felosztása (Hézagok keresése)", + "editor_merge": "Ciklusok egyesítése (töredékek összekapcsolása)", + "editor_delete": "Ciklus(ok) törlése", + "menu_back": "← Vissza" + } + }, + "editor_select": { + "title": "Válassza a Ciklusok lehetőséget", + "description": "Válassza ki a feldolgozni kívánt ciklus(oka)t.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklusok" + } + }, + "editor_configure": { + "title": "Konfigurálás és alkalmazás", + "description": "{preview_md}", + "data": { + "confirm_action": "Akció", + "merged_profile": "Az eredményül kapott profil", + "new_profile_name": "Új profilnév", + "confirm_commit": "Igen, megerősítem ezt a műveletet", + "segment_0_profile": "1. szegmens profilja", + "segment_1_profile": "2. szegmens profilja", + "segment_2_profile": "3. szegmens profilja", + "segment_3_profile": "4. szegmens profilja", + "segment_4_profile": "5. szegmens profilja", + "segment_5_profile": "6. szegmens profilja", + "segment_6_profile": "7. szegmens profilja", + "segment_7_profile": "8. szegmens profilja", + "segment_8_profile": "9. szegmens profilja", + "segment_9_profile": "10. szegmens profilja" + } + }, + "editor_split_params": { + "title": "Osztott konfiguráció", + "description": "Állítsa be a ciklus felosztásának érzékenységét. Bármilyen ennél hosszabb üresjárati hézag törést okoz.", + "data": { + "split_mode": "Felosztási módszer" + } + }, + "editor_split_auto_params": { + "title": "Automatikus felosztás beállítása", + "description": "Állítsa be a ciklus felosztásának érzékenységét. Az ennél hosszabb tétlen rés felosztást okoz.", + "data": { + "min_gap_seconds": "Felosztási rés küszöbértéke (másodperc)" + } + }, + "editor_split_manual_params": { + "title": "Kézi felosztási időbélyegek", + "description": "{preview_md}Ciklusablak: **{cycle_start_wallclock} → {cycle_end_wallclock}** ekkor: {cycle_date}.\n\nAdjon meg egy vagy több felosztási időbélyeget (`HH:MM` vagy `HH:MM:SS`), soronként egyet. A ciklus minden időbélyegnél elvágásra kerül — N időbélyeg N+1 szakaszt eredményez.", + "data": { + "split_timestamps": "Felosztási időbélyegek" + } + }, + "reprocess_history": { + "title": "Karbantartás: Adatok újrafeldolgozása és optimalizálása", + "description": "Az előzményadatok mélyreható tisztítását végzi:\n1. Levágja a nulla teljesítményű értékeket (csend).\n2. Rögzíti a ciklusidőt/időtartamot.\n3. Újraszámítja az összes statisztikai modellt (borítékot).\n\n**Futtassa ezt az adatproblémák kijavításához vagy új algoritmusok alkalmazásához.**" + }, + "wipe_history": { + "title": "Törlési előzmények (csak tesztelés)", + "description": "⚠️ Ezzel véglegesen törli az eszköz ÖSSZES tárolt ciklusát és profilját. Ezt nem lehet visszavonni!" + }, + "export_import": { + "title": "JSON exportálása/importálása", + "description": "Ciklusok, profilok és beállítások másolása/beillesztése ehhez az eszközhöz. Exportáljon alább, vagy illessze be a JSON-fájlt az importáláshoz.", + "data": { + "mode": "Akció", + "json_payload": "Teljes konfigurációs JSON" + }, + "data_description": { + "mode": "Válassza az Exportálás lehetőséget az aktuális adatok és beállítások másolásához, vagy az Importálás lehetőséget egy másik WashData eszközről exportált adatok beillesztéséhez.", + "json_payload": "Exportáláshoz: másolja ezt a JSON-t (a ciklusokat, profilokat és az összes finomhangolt beállítást tartalmazza). Importáláshoz: illessze be a WashData-ból exportált JSON-t." + } + }, + "record_cycle": { + "title": "Felvételi ciklus", + "description": "Rögzítsen manuálisan egy ciklust a tiszta profil létrehozásához interferencia nélkül.\n\nÁllapot: **{status}**\nIdőtartam: {duration} mp\nMinták: {samples}", + "menu_options": { + "record_refresh": "Állapot frissítése", + "record_stop": "Felvétel leállítása (Mentés és feldolgozás)", + "record_start": "Új felvétel indítása", + "record_process": "Utolsó felvétel feldolgozása (kivágás és mentés)", + "record_discard": "Utolsó felvétel elvetése", + "menu_back": "← Vissza" + } + }, + "record_process": { + "title": "Folyamat rögzítése", + "description": "![Grafikon]({graph_url})\n\n**A grafikon a nyers felvételt mutatja.** Kék = Megtartás, Piros = Javasolt vágás.\n*A grafikon statikus, és nem frissül az űrlap változásaival.*\n\nA felvétel leállt. A kivágások az észlelt mintavételezési frekvenciához igazodnak (~{sampling_rate}s).\n\nNyers időtartam: {duration} s\nMinták: {samples}", + "data": { + "head_trim": "Vágás kezdete (másodperc)", + "tail_trim": "Vágás vége (másodperc)", + "save_mode": "Cél mentése", + "profile_name": "Profil neve" + } + }, + "learning_feedbacks": { + "title": "Tanulási visszajelzések", + "description": "Függőben lévő visszajelzések: {count}\n\n{pending_table}\n\nVálassza ki a feldolgozandó ciklus-ellenőrzési kérelmet.", + "menu_options": { + "learning_feedbacks_pick": "Függőben lévő visszajelzés áttekintése", + "learning_feedbacks_dismiss_all": "Az összes függőben lévő visszajelzés elvetése", + "menu_back": "← Vissza" + } + }, + "learning_feedbacks_pick": { + "title": "Válasszon áttekintendő visszajelzést", + "description": "Válasszon egy megnyitandó ciklus-felülvizsgálati kérést.", + "data": { + "selected_feedback": "Válassza ki az áttekintendő ciklust" + } + }, + "learning_feedbacks_empty": { + "title": "Tanulási visszajelzések", + "description": "Nem található függőben lévő visszajelzési kérés.", + "menu_options": { + "menu_back": "← Vissza" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Az összes függőben lévő visszajelzés elutasítása", + "description": "Ön **{count}** függőben lévő visszajelzési kérelem elutasítására készül. Az elutasított ciklusok az előzményeiben maradnak, de nem járulnak hozzá új tanulási jelhez. Ez nem vonható vissza.\n\n✅ Jelölje be az alábbi négyzetet, és kattintson a **Küldés** gombra az összes elutasításához.\n❌ Hagyja bejelölés nélkül, és kattintson a **Küldés** gombra a visszavonáshoz és a visszalépéshez.", + "data": { + "confirm_dismiss_all": "Megerősítés: az összes függőben lévő visszajelzési kérés elvetése" + } + }, + "resolve_feedback": { + "title": "Visszajelzés megoldása", + "description": "{comparison_img}{candidates_table}\n**Észlelt profil**: {detected_profile} ({confidence_pct}%)\n**Becsült időtartam**: {est_duration_min} perc\n**Tényleges időtartam**: {act_duration_min} perc\n\n__Válasszon egy műveletet alább:__", + "data": { + "action": "mit szeretnél csinálni?", + "corrected_profile": "Helyes program (ha javít)", + "corrected_duration": "Helyes időtartam másodpercekben (javítás esetén)" + } + }, + "trim_cycle_select": { + "title": "Vágási ciklus - Válasszon ciklust", + "description": "Válassza ki a vágni kívánt ciklust. ✓ = befejezve, ⚠ = folytatva, ✗ = megszakítva", + "data": { + "cycle_id": "Ciklus" + } + }, + "trim_cycle": { + "title": "Vágási ciklus", + "description": "Jelenlegi ablak: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akció" + } + }, + "trim_cycle_start": { + "title": "Állítsa be a Vágás kezdetét", + "description": "Ciklusablak: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nJelenlegi kezdés: **{current_wallclock}** (+{current_offset_min} perc a ciklus kezdetétől)\n\nVálasszon új kezdési időpontot az alábbi óraválasztó segítségével.", + "data": { + "trim_start_time": "Új kezdési időpont", + "trim_start_min": "Új kezdet (percek a ciklus kezdetétől)" + } + }, + "trim_cycle_end": { + "title": "Állítsa be a Vágás végét", + "description": "Ciklusablak: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nJelenlegi vége: **{current_wallclock}** (+{current_offset_min} perc a ciklus kezdetétől)\n\nVálasszon új befejezési időt az alábbi óraválasztó segítségével.", + "data": { + "trim_end_time": "Új befejezési idő", + "trim_end_min": "Új vége (percek a ciklus kezdetétől)" + } + } + }, + "error": { + "import_failed": "Az importálás sikertelen. Illesszen be egy érvényes WashData exportálási JSON-t.", + "select_exactly_one": "Ehhez a művelethez pontosan egy ciklust válasszon.", + "select_at_least_one": "Ehhez a művelethez válasszon legalább egy ciklust.", + "select_at_least_two": "Ehhez a művelethez válasszon legalább két ciklust.", + "profile_exists": "Már létezik ilyen nevű profil", + "rename_failed": "Nem sikerült átnevezni a profilt. Előfordulhat, hogy a profil nem létezik, vagy az új név már foglalt.", + "assignment_failed": "Nem sikerült profilt rendelni a ciklushoz", + "duplicate_phase": "Már létezik ilyen nevű fázis.", + "phase_not_found": "A kiválasztott fázis nem található.", + "invalid_phase_name": "A fázis neve nem lehet üres.", + "phase_name_too_long": "A fázis neve túl hosszú.", + "invalid_phase_range": "A fázistartomány érvénytelen. A végnek nagyobbnak kell lennie, mint a kezdetnek.", + "invalid_phase_timestamp": "Az időbélyeg érvénytelen. Használja ÉÉÉÉ-HH-NN ÓÓ:PP vagy ÓÓ:PP formátumot.", + "overlapping_phase_ranges": "A fázistartományok átfedik egymást. Állítsa be a tartományokat, és próbálja újra.", + "incomplete_phase_row": "Minden fázissornak tartalmaznia kell egy fázist, valamint egy teljes kezdő/végi értéket a kiválasztott módhoz.", + "timestamp_mode_cycle_required": "Kérjük, válasszon forrásciklust az időbélyeg mód használatakor.", + "no_phase_ranges": "Ehhez a művelethez még nem állnak rendelkezésre fázistartományok.", + "feedback_notification_title": "WashData: Ciklus ellenőrzése ({device})", + "feedback_notification_message": "**Eszköz**: {device}\n**Program**: {program} ({confidence}%-os megbízhatóság)\n**Idő**: {time}\n\nA WashData segítségére van szüksége az észlelt ciklus ellenőrzéséhez.\n\nAz eredmény megerősítéséhez vagy javításához lépjen a **Beállítások > Eszközök és szolgáltatások > WashData > Konfigurálás > Tanulási visszajelzések** menüpontra.", + "suggestions_ready_notification_title": "WashData: A javasolt beállítások készen állnak ({device})", + "suggestions_ready_notification_message": "A **Javasolt beállítások** érzékelő mostantól **{count}** használható javaslatokat közöl.\n\nÁttekintésük és alkalmazásuk: **Beállítások > Eszközök és szolgáltatások > WashData > Konfigurálás > Speciális beállítások > Javasolt értékek alkalmazása**.\n\nA javaslatok nem kötelezőek, és mentés előtt áttekinthetők.", + "trim_range_invalid": "A kezdésnek a vége előtt kell lennie. Kérjük, módosítsa a vágási pontokat.", + "trim_failed": "A vágás nem sikerült. Lehetséges, hogy a ciklus már nem létezik, vagy az ablak üres.", + "invalid_split_timestamp": "Az időbélyeg érvénytelen vagy kívül esik a ciklus időablakán. A ciklus időablakán belül használjon HH:MM vagy HH:MM:SS formátumot.", + "no_split_segments_found": "Ezekből az időbélyegekből nem lehetett érvényes szegmenseket létrehozni. Győződjön meg arról, hogy minden időbélyeg a ciklus időablakán belül van, és a szegmensek legalább 1 percesek.", + "auto_tune_suggestion": "{device_type} {device_title} szellemciklusokat észlelt. Javasolt minimális teljesítményváltozás: {current_min}W -> {new_min}W (nem alkalmazzák automatikusan).", + "auto_tune_title": "WashData automatikus hangolás", + "auto_tune_fallback": "{device_type} {device_title} szellemciklusokat észlelt. Javasolt minimális teljesítményváltozás: {current_min}W -> {new_min}W (nem alkalmazzák automatikusan)." + }, + "abort": { + "no_cycles_found": "Nem található ciklus", + "no_split_segments_found": "A kiválasztott ciklusban nem találhatók felosztható szegmensek.", + "cycle_not_found": "A kiválasztott ciklust nem lehetett betölteni.", + "no_power_data": "A kiválasztott ciklushoz nem állnak rendelkezésre teljesítménynyomkövetési adatok.", + "no_profiles_found": "Nem találhatók profilok. Először hozzon létre egy profilt.", + "no_profiles_for_matching": "Nincsenek megfelelő profilok. Először hozzon létre profilokat.", + "no_unlabeled_cycles": "Minden ciklus már fel van címkézve", + "no_suggestions": "Még nem állnak rendelkezésre javasolt értékek. Futtasson néhány ciklust, és próbálja újra.", + "no_predictions": "Nincsenek jóslatok", + "no_custom_phases": "Nem található egyéni fázis.", + "reprocess_success": "{count} ciklus sikeresen újrafeldolgozva.", + "debug_data_cleared": "A hibakeresési adatok sikeresen törölve {count} ciklusból." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Ciklusindítás", + "cycle_finish": "Ciklus befejezése", + "cycle_live": "Élő Haladás" + } + }, + "common_text": { + "options": { + "unlabeled": "(Cimkézetlen)", + "create_new_profile": "Új profil létrehozása", + "remove_label": "Címke eltávolítása", + "no_reference_cycle": "(Nincs referencia ciklus)", + "all_device_types": "Minden eszköztípus", + "current_suffix": "(jelenlegi)", + "editor_select_info": "Válasszon 1 ciklust a felosztáshoz, vagy 2+ ciklust az egyesítéshez.", + "editor_select_info_split": "Válasszon 1 ciklust felosztáshoz.", + "editor_select_info_merge": "Válasszon 2+ ciklust egyesítéshez.", + "editor_select_info_delete": "Válasszon 1+ ciklust törléshez.", + "no_cycles_recorded": "Még nem rögzítettek ciklusokat.", + "profile_summary_line": "- **{name}**: {count} ciklus, {avg} m átlag", + "no_profiles_created": "Még nincsenek profilok.", + "phase_preview": "Fázis előnézete", + "phase_preview_no_curve": "Átlagos profilgörbe még nem elérhető. További ciklusok futtatása/címkézése ehhez a profilhoz.", + "average_power_curve": "Átlagos teljesítmény görbe", + "unit_min": "perc", + "profile_option_fmt": "{name} ({count} ciklus, ~{duration} perc átlag)", + "profile_option_short_fmt": "{name} ({count} ciklus, ~{duration} perc)", + "deleted_cycles_from_profile": "{count} ciklus törölve innen: {profile}.", + "not_enough_data_graph": "Nincs elég adat a grafikon létrehozásához.", + "no_profiles_found": "Nem találhatók profilok.", + "cycle_info_fmt": "Ciklus: {start}, {duration} perc, jelenlegi: {label}", + "top_candidates_header": "### Legjobb jelöltek", + "tbl_profile": "Profil", + "tbl_confidence": "Bizalom", + "tbl_correlation": "Korreláció", + "tbl_duration_match": "Időtartam mérkőzés", + "detected_profile": "Észlelt profil", + "estimated_duration": "Becsült időtartam", + "actual_duration": "Tényleges időtartam", + "choose_action": "__Válasszon egy műveletet alább:__", + "tbl_count": "Db", + "tbl_avg": "Átl", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (átl.)", + "tbl_energy_total": "Energia (összesen)", + "tbl_consistency": "áll.", + "tbl_last_run": "Utolsó futás", + "graph_legend_title": "Graph Legend", + "graph_legend_body": "A kék sáv a megfigyelt minimális és maximális teljesítményfelvételi tartományt jelzi. A vonal az átlagos teljesítménygörbét mutatja.", + "recording_preview": "Felvétel előnézete", + "trim_start": "Vágási kezdőpont", + "trim_end": "Vágás vége", + "split_preview_title": "Megosztott előnézet", + "split_preview_found_fmt": "{count} szegmens található.", + "split_preview_confirm_fmt": "Kattintson a Megerősítés gombra, ha ezt a ciklust {count} különálló ciklusra szeretné felosztani.", + "merge_preview_title": "Egyesítés előnézete", + "merge_preview_joining_fmt": "Csatlakozás {count} ciklushoz. A hiányosságokat 0 W-os leolvasások töltik ki.", + "editor_delete_preview_title": "Törlési előnézet", + "editor_delete_preview_intro": "A kiválasztott ciklusok véglegesen törlődnek:", + "editor_delete_preview_confirm": "Kattintson a Megerősítés gombra ezen ciklusrekordok végleges törléséhez.", + "no_power_preview": "*Az előnézethez nem állnak rendelkezésre teljesítményadatok.*", + "profile_comparison": "Profilok összehasonlítása", + "actual_cycle_label": "Ez a ciklus (tényleges)", + "storage_usage_header": "Tárolási használat", + "storage_file_size": "Fájl mérete", + "storage_cycles": "Ciklusok", + "storage_profiles": "Profilok", + "storage_debug_traces": "Hibakeresés adatok", + "overview_suffix": "Áttekintés", + "phase_preview_for_profile": "A profil fázis előnézete", + "current_ranges_header": "Aktuális tartományok", + "assign_phases_help": "Tartományok hozzáadásához, szerkesztéséhez, törléséhez vagy mentéséhez használja az alábbi műveleteket.", + "profile_summary_header": "Profil összefoglaló", + "recent_cycles_header": "Legutóbbi ciklusok", + "trim_cycle_preview_title": "Vágási ciklus előnézete", + "trim_cycle_preview_no_data": "Ehhez a ciklushoz nem állnak rendelkezésre teljesítményadatok.", + "trim_cycle_preview_kept_suffix": "tartotta", + "table_program": "Program", + "table_when": "Mikor", + "table_length": "Hossz", + "table_match": "Egyezés", + "table_profile": "Profil", + "table_cycles": "Ciklusok", + "table_avg_length": "Átl. hossz", + "table_last_run": "Utolsó futás", + "table_avg_energy": "Átl. energia", + "table_detected_program": "Észlelt program", + "table_confidence": "Bizalom", + "table_reported": "Jelentve", + "phase_builtin_suffix": "(Beépített)", + "phase_none_available": "Nincs elérhető fázis.", + "settings_deprecation_warning": "⚠️ **Elavult eszköztípus:** A(z) {device_type} eltávolítása egy jövőbeli kiadásban várható. A WashData egyező folyamata nem ad megbízható eredményeket ehhez a készülékosztályhoz. Az integráció az elavulási időszakon keresztül is működik; a figyelmeztetés elnémításához állítsa az alábbi **Eszköztípus** lehetőséget a támogatott típusok egyikére (mosógép, szárítógép, mosó-szárítógép kombináció, mosogatógép, légsütő, kenyérsütő vagy szivattyú), vagy az **Egyéb (speciális)** lehetőségre, ha a készülék nem egyezik a támogatott típusok egyikével sem. Az **Other (Advanced)** szándékosan olyan általános alapértelmezett beállításokat szállít, amelyek nincsenek beállítva egyetlen készülékhez sem, ezért Önnek kell konfigurálnia a küszöbértékeket, az időtúllépéseket és a megfelelő paramétereket; váltáskor az összes meglévő beállítás megmarad.", + "phase_other_device_types": "Egyéb készüléktípusok:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Ciklus felosztása (Hézagok keresése)", + "merge": "Ciklusok egyesítése (töredékek összekapcsolása)", + "delete": "Ciklus(ok) törlése" + } + }, + "split_mode": { + "options": { + "auto": "Tétlen szünetek automatikus felismerése", + "manual": "Kézi időbélyegek" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Karbantartás: Adatok újrafeldolgozása és optimalizálása", + "clear_debug_data": "Hibakeresési adatok törlése (tárhely felszabadítása)", + "wipe_history": "Az eszköz ÖSSZES adatának törlése (visszafordíthatatlan)", + "export_import": "JSON exportálása/importálása beállításokkal (másolás/beillesztés)" + } + }, + "export_import_mode": { + "options": { + "export": "Minden adat exportálása", + "import": "Adatok importálása/egyesítése" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Régi ciklusok automatikus címkézése", + "select_cycle_to_label": "Címkespecifikus ciklus", + "select_cycle_to_delete": "Ciklus törlése", + "interactive_editor": "Egyesítés/felosztás interaktív szerkesztő", + "trim_cycle": "Vágási ciklus adatai" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Új profil létrehozása", + "edit_profile": "Profil szerkesztése/átnevezése", + "delete_profile": "Profil törlése", + "profile_stats": "Profil statisztika", + "cleanup_profile": "Törölje az előzményeket – Graph & Delete", + "assign_phases": "Fázistartományok hozzárendelése" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Új fázis létrehozása", + "edit_custom_phase": "Fázis szerkesztése", + "delete_custom_phase": "Fázis törlése" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Fázistartomány hozzáadása", + "edit_range": "Fázistartomány szerkesztése", + "delete_range": "Fázistartomány törlése", + "clear_ranges": "Minden tartomány törlése", + "auto_detect_ranges": "Fázisok automatikus felismerése", + "save_ranges": "Mentés és visszaküldés" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Állapot frissítése", + "stop_recording": "Felvétel leállítása (Mentés és feldolgozás)", + "start_recording": "Új felvétel indítása", + "process_recording": "Utolsó felvétel feldolgozása (kivágás és mentés)", + "discard_recording": "Utolsó felvétel elvetése" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Új profil létrehozása", + "existing_profile": "Hozzáadás a meglévő profilhoz", + "discard": "Felvétel elvetése" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Megerősítés – Helyes észlelés", + "correct": "Helyes – Válassza a Program/Időtartam lehetőséget", + "ignore": "Figyelmen kívül hagyás – téves pozitív/zajos ciklus", + "delete": "Törlés – Eltávolítás az előzményekből" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nevezze meg és alkalmazza a fázisokat", + "cancel": "Menjen vissza változtatások nélkül" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Állítsa be a kezdőpontot", + "set_end": "Végpont beállítása", + "reset": "Állítsa vissza a teljes időtartamra", + "apply": "Vágás alkalmazása", + "cancel": "Mégsem" + } + }, + "device_type": { + "options": { + "washing_machine": "Mosógép", + "dryer": "Szárítógép", + "washer_dryer": "Mosó-szárító kombó", + "dishwasher": "Mosogatógép", + "coffee_machine": "Kávéfőző (elavult)", + "ev": "Elektromos jármű (elavult)", + "air_fryer": "Air Fryer", + "heat_pump": "Hőszivattyú (elavult)", + "bread_maker": "Kenyérsütő", + "pump": "Szivattyú / olajteknő szivattyú", + "oven": "Sütő (elavult)", + "other": "Egyéb (haladó)" + } + } + }, + "services": { + "label_cycle": { + "name": "Címkeciklus", + "description": "Rendeljen hozzá egy meglévő profilt egy elmúlt ciklushoz, vagy távolítsa el a címkét.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép készüléket címkézni." + }, + "cycle_id": { + "name": "Ciklusazonosító", + "description": "A címkézendő ciklus azonosítója." + }, + "profile_name": { + "name": "Profil neve", + "description": "Egy meglévő profil neve (profilok létrehozása a Profilok kezelése menüben). Hagyja üresen a címke eltávolításához." + } + } + }, + "create_profile": { + "name": "Profil létrehozása", + "description": "Hozzon létre egy új profilt (önálló vagy referenciacikluson alapuló).", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép készülék." + }, + "profile_name": { + "name": "Profil neve", + "description": "Az új profil neve (pl. „Heavy Duty”, „Delicates”)." + }, + "reference_cycle_id": { + "name": "Referencia ciklusazonosító", + "description": "Opcionális ciklusazonosító a profil alapjául." + } + } + }, + "delete_profile": { + "name": "Profil törlése", + "description": "Töröljön egy profilt, és opcionálisan törölje a ciklusok címkéit a használatával.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép készülék." + }, + "profile_name": { + "name": "Profil neve", + "description": "A törölni kívánt profil." + }, + "unlabel_cycles": { + "name": "Ciklusok címkézése", + "description": "Távolítsa el a profilcímkét a ciklusokból ezzel a profillal." + } + } + }, + "auto_label_cycles": { + "name": "Régi ciklusok automatikus címkézése", + "description": "A címkézetlen ciklusok visszamenőleges címkézése profilillesztéssel.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép készülék." + }, + "confidence_threshold": { + "name": "Magabiztossági küszöb", + "description": "Minimális egyezési megbízhatóság (0,50–0,95) a címkék alkalmazásához." + } + } + }, + "export_config": { + "name": "Konfiguráció exportálása", + "description": "Exportálja az eszköz profiljait, ciklusait és beállításait JSON-fájlba (eszközönként).", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép eszközt exportálni." + }, + "path": { + "name": "Útvonal", + "description": "Opcionális abszolút fájl elérési útja az íráshoz (alapértelmezett a /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Konfiguráció importálása", + "description": "Importálja az eszköz profiljait, ciklusait és beállításait egy JSON exportfájlból (a beállítások felülírhatók).", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "Az importálandó mosógép eszköz." + }, + "path": { + "name": "Útvonal", + "description": "Az exportálandó JSON-fájl abszolút elérési útja." + } + } + }, + "submit_cycle_feedback": { + "name": "Ciklus visszajelzés küldése", + "description": "Erősítse meg vagy javítsa ki az automatikusan észlelt programot a ciklus befejezése után. Adja meg az „entry_id” (speciális) vagy az „device_id” (ajánlott) értéket.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A WashData eszköz (ajánlott az entry_id helyett)." + }, + "entry_id": { + "name": "Belépési azonosító", + "description": "Az eszköz konfigurációs bejegyzés azonosítója (a device_id alternatívája)." + }, + "cycle_id": { + "name": "Ciklusazonosító", + "description": "A visszajelzési értesítésben/naplókban látható ciklusazonosító." + }, + "user_confirmed": { + "name": "Erősítse meg az észlelt programot", + "description": "Állítsa igazat, ha az észlelt program helyes." + }, + "corrected_profile": { + "name": "Javított profil", + "description": "Ha nem erősíti meg, adja meg a megfelelő profil-/programnevet." + }, + "corrected_duration": { + "name": "Javított időtartam (másodperc)", + "description": "Opcionális korrigált időtartam másodpercben." + }, + "notes": { + "name": "Megjegyzések", + "description": "Választható megjegyzések erről a ciklusról." + } + } + }, + "record_start": { + "name": "Felvételi ciklus indítása", + "description": "Kezdje el manuálisan rögzíteni a tiszta ciklust (megkerül minden egyező logikát).", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép rögzítésére szolgáló eszköz." + } + } + }, + "record_stop": { + "name": "Felvételi ciklus leállítása", + "description": "Állítsa le a kézi felvételt.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A mosógép eszköz a felvétel leállításához." + } + } + }, + "trim_cycle": { + "name": "Vágási ciklus", + "description": "Vágja le az elmúlt ciklus teljesítményadatait egy adott időablakra.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A WashData eszköz." + }, + "cycle_id": { + "name": "Ciklusazonosító", + "description": "A vágni kívánt ciklus azonosítója." + }, + "trim_start_s": { + "name": "Vágás kezdete (másodperc)", + "description": "Az ebből az eltolásból származó adatokat másodpercben tárolja. Alapértelmezett 0." + }, + "trim_end_s": { + "name": "Vágás vége (másodperc)", + "description": "Tartsa az adatokat másodpercek alatt ezen eltolásig. Alapértelmezett = teljes időtartam." + } + } + }, + "pause_cycle": { + "name": "Ciklus szüneteltetése", + "description": "A WashData eszköz aktív ciklusának szüneteltetése.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A WashData eszköz szüneteltetése." + } + } + }, + "resume_cycle": { + "name": "Ciklus folytatása", + "description": "Szünetelt ciklus folytatása egy WashData eszköznél.", + "fields": { + "device_id": { + "name": "Eszköz", + "description": "A WashData eszköz a folytatáshoz." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Futás" + }, + "match_ambiguity": { + "name": "Találat kétértelműsége" + } + }, + "sensor": { + "washer_state": { + "name": "Állapot", + "state": { + "off": "Kikapcsolva", + "idle": "Tétlen", + "starting": "Indulás", + "running": "Futás", + "paused": "Szüneteltetve", + "user_paused": "Felhasználó által szüneteltetve", + "ending": "Befejező", + "finished": "Befejezett", + "anti_wrinkle": "Ránctalanító", + "delay_wait": "Várakozás a kezdésre", + "interrupted": "Megszakított", + "force_stopped": "Kényszer leállított", + "rinse": "Öblítés", + "unknown": "Ismeretlen", + "clean": "Tiszta" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Hátralévő idő" + }, + "total_duration": { + "name": "Teljes időtartam" + }, + "cycle_progress": { + "name": "Előrehaladás" + }, + "current_power": { + "name": "Jelenlegi teljesítmény" + }, + "elapsed_time": { + "name": "Eltelt idő" + }, + "debug_info": { + "name": "Hibakeresési információ" + }, + "match_confidence": { + "name": "Egyezési megbízhatóság" + }, + "top_candidates": { + "name": "Legjobb jelöltek", + "state": { + "none": "Egyik sem" + } + }, + "current_phase": { + "name": "Jelenlegi fázis" + }, + "wash_phase": { + "name": "Fázis" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Szám", + "unit_of_measurement": "ciklusok" + }, + "suggestions": { + "name": "Javasolt beállítások" + }, + "pump_runs_today": { + "name": "A szivattyú üzemel (az elmúlt 24 órában)" + }, + "cycle_count": { + "name": "Ciklusszám" + } + }, + "select": { + "program_select": { + "name": "Ciklus program", + "state": { + "auto_detect": "Automatikus észlelés" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Ciklus kényszerítése" + }, + "pause_cycle": { + "name": "Ciklus szüneteltetése" + }, + "resume_cycle": { + "name": "Ciklus folytatása" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Érvényes device_id megadása kötelező." + }, + "cycle_id_required": { + "message": "Érvényes ciklusazonosító szükséges." + }, + "profile_name_required": { + "message": "Érvényes profile_name megadása kötelező." + }, + "device_not_found": { + "message": "Az eszköz nem található." + }, + "no_config_entry": { + "message": "Nem található az eszköz konfigurációs bejegyzése." + }, + "integration_not_loaded": { + "message": "Az integráció nincs betöltve ehhez az eszközhöz." + }, + "cycle_not_found_or_no_power": { + "message": "A ciklus nem található, vagy nincsenek tápellátási adatai." + }, + "trim_failed_empty_window": { + "message": "A vágás sikertelen – a ciklus nem található, nincsenek tápellátási adatok, vagy a kapott ablak üres." + }, + "trim_invalid_range": { + "message": "A trim_end_s nagyobbnak kell lennie, mint a trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/hy.json b/custom_components/ha_washdata/translations/hy.json new file mode 100644 index 0000000..8d38239 --- /dev/null +++ b/custom_components/ha_washdata/translations/hy.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Լվացքի տվյալների կարգավորում", + "description": "Կարգավորեք ձեր լվացքի մեքենան կամ այլ սարքը:\n\nՊահանջվում է հոսանքի սենսոր:\n\n**Հաջորդը ձեզ կհարցնեն, արդյոք ցանկանում եք ստեղծել ձեր առաջին պրոֆիլը:**", + "data": { + "name": "Սարքի անվանումը", + "device_type": "Սարքի տեսակը", + "power_sensor": "Էլեկտրաէներգիայի ցուցիչ", + "min_power": "Հզորության նվազագույն շեմ (Վտ)" + }, + "data_description": { + "name": "Այս սարքի բարեկամական անվանումը (օրինակ՝ «Լվացքի մեքենա», «Սպասք լվացող մեքենա»):", + "device_type": "Ի՞նչ տեսակի սարքավորում է սա: Օգնում է հարմարեցնել հայտնաբերումը և պիտակավորումը:", + "power_sensor": "Սենսորային միավորը, որը հաղորդում է իրական ժամանակի էներգիայի սպառումը (վտերով) ձեր խելացի վարդակից:", + "min_power": "Այս շեմից բարձր հզորության ցուցանիշները (վտ) ցույց են տալիս, որ սարքը աշխատում է: Սարքերի մեծ մասի համար սկսեք 2W-ից:" + } + }, + "first_profile": { + "title": "Ստեղծեք առաջին պրոֆիլը", + "description": "**Ցիկլ**-ը ձեր սարքի ամբողջական գործարկումն է (օրինակ՝ լվացքի բեռնվածություն): **Պրոֆիլը** խմբավորում է այս ցիկլերը ըստ տեսակի (օրինակ՝ «Բամբակ», «Արագ լվացում»): Այժմ պրոֆիլի ստեղծումն օգնում է անմիջապես ինտեգրման գնահատման տևողությանը:\n\nԴուք կարող եք ձեռքով ընտրել այս պրոֆիլը սարքի կառավարում, եթե այն ինքնաբերաբար չի հայտնաբերվում:\n\n**Այս քայլը բաց թողնելու համար թողեք «Պրոֆիլի անունը» դատարկ։**", + "data": { + "profile_name": "Պրոֆիլի անունը (ըստ ցանկության)", + "manual_duration": "Մոտավոր տեւողությունը (րոպե)" + } + } + }, + "error": { + "cannot_connect": "Չհաջողվեց միանալ", + "invalid_auth": "Անվավեր նույնականացում", + "unknown": "Անսպասելի սխալ" + }, + "abort": { + "already_configured": "Սարքն արդեն կազմաձևված է" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Վերանայեք ուսուցման հետադարձ կապը", + "settings": "Կարգավորումներ", + "notifications": "Ծանուցումներ", + "manage_cycles": "Կառավարեք ցիկլերը", + "manage_profiles": "Կառավարեք պրոֆիլները", + "manage_phase_catalog": "Կառավարեք փուլային կատալոգը", + "record_cycle": "Ձայնագրման ցիկլ (Ձեռնարկ)", + "diagnostics": "Ախտորոշում և սպասարկում" + } + }, + "apply_suggestions": { + "title": "Պատճենել առաջարկվող արժեքները", + "description": "Սա ընթացիկ առաջարկվող արժեքները պատճենելու է ձեր պահպանված կարգավորումներում: Սա մեկանգամյա գործողություն է և թույլ չի տալիս ավտոմատ վերագրանցումները:\n\nԱռաջարկվող արժեքները պատճենելու համար.\n{suggested}", + "data": { + "confirm": "Հաստատեք պատճենման գործողությունը" + } + }, + "apply_suggestions_confirm": { + "title": "Վերանայեք առաջարկվող փոփոխությունները", + "description": "WashData-ն գտել է **{pending_count}** առաջարկված արժեք(ներ)՝ կիրառելու համար:\n\nՓոփոխություններ:\n{changes}\n\n✅ Ստուգեք ստորև նշված վանդակը և սեղմեք **Ներկայացնել**՝ այս փոփոխությունները անմիջապես կիրառելու և պահելու համար:\n❌ Թողեք այն չստուգված և սեղմեք **Ներկայացնել**՝ չեղարկելու և հետ գնալու համար:", + "data": { + "confirm_apply_suggestions": "Կիրառեք և պահպանեք այս փոփոխությունները" + } + }, + "settings": { + "title": "Կարգավորումներ", + "description": "{deprecation_warning}Կարգավորել հայտնաբերման շեմերը, սովորելը և առաջադեմ վարքագիծը: Կանխադրվածները լավ են աշխատում կարգավորումների մեծ մասի համար:\n\nՀասանելի առաջարկված կարգավորումներ. {suggestions_count}:\nԵթե ​​այս թիվը 0-ից մեծ է, բացեք Ընդլայնված կարգավորումները և օգտագործեք «Կիրառել առաջարկվող արժեքները» տարբերակը՝ առաջարկությունները վերանայելու համար:", + "data": { + "apply_suggestions": "Կիրառել առաջարկվող արժեքները", + "device_type": "Սարքի տեսակը", + "power_sensor": "Էլեկտրաէներգիայի ցուցիչի միավոր", + "min_power": "Նվազագույն հզորություն (Վտ)", + "off_delay": "Ցիկլի ավարտի ուշացում (ներ)", + "notify_actions": "Ծանուցման գործողություններ", + "notify_people": "Հետաձգեք առաքումը, քանի դեռ այս մարդիկ տանը չեն", + "notify_only_when_home": "Հետաձգել ծանուցումները, քանի դեռ ընտրված անձը տուն չէ", + "notify_fire_events": "Ավտոմատացման իրադարձությունների գործարկում", + "notify_start_services": "Ցիկլի սկիզբ - Ծանուցման թիրախներ", + "notify_finish_services": "Ցիկլի ավարտ - Ծանուցման թիրախներ", + "notify_live_services": "Ուղիղ առաջընթաց - Ծանուցման թիրախներ", + "notify_before_end_minutes": "Նախնական ավարտի ծանուցում (ավարտից րոպեներ առաջ)", + "notify_title": "Ծանուցման վերնագիր", + "notify_icon": "Ծանուցման պատկերակ", + "notify_start_message": "Սկսեք հաղորդագրության ձևաչափը", + "notify_finish_message": "Ավարտել հաղորդագրության ձևաչափը", + "notify_pre_complete_message": "Հաղորդագրության նախնական ավարտի ձևաչափ", + "show_advanced": "Խմբագրել Ընդլայնված կարգավորումները (Հաջորդ քայլը)" + }, + "data_description": { + "apply_suggestions": "Նշեք այս վանդակը՝ ուսուցման ալգորիթմի առաջարկած արժեքները ձևաթղթում պատճենելու համար: Վերանայեք նախքան պահպանումը:\n\nԱռաջարկվում է (սովորելուց).\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Ընտրեք սարքի տեսակը (Լվացքի մեքենա, չորանոց, աման լվացող մեքենա, սուրճի մեքենա, EV): Այս պիտակը պահվում է յուրաքանչյուր ցիկլի հետ ավելի լավ կազմակերպման համար:", + "power_sensor": "Սենսորային միավորը, որը հաղորդում է իրական ժամանակի հզորությունը (վտներով): Դուք ցանկացած ժամանակ կարող եք փոխել սա՝ առանց պատմական տվյալների կորստի, ինչը օգտակար է, եթե փոխարինեք խելացի վարդակից:", + "min_power": "Այս շեմից բարձր հզորության ցուցանիշները (վտ) ցույց են տալիս, որ սարքը աշխատում է: Սարքերի մեծ մասի համար սկսեք 2W-ից:", + "off_delay": "Վայրկյաններով հարթեցված հզորությունը պետք է մնա կանգառի շեմից ցածր՝ ավարտը նշելու համար: Կանխադրված՝ 120 վրկ.", + "notify_actions": "Տնային օգնականի կամընտիր գործողությունների հաջորդականությունը՝ ծանուցումների համար (սկրիպտներ, ծանուցումներ, TTS և այլն): Եթե ​​կարգավորված է, գործողությունները կկատարվեն նախքան հետադարձ ծանուցման ծառայությունը:", + "notify_people": "Մարդկանց միավորներ, որոնք օգտագործվում են ներկայության դարպասների համար: Երբ միացված է, ծանուցումների առաքումը հետաձգվում է այնքան ժամանակ, քանի դեռ ընտրված առնվազն մեկը տանը չէ:", + "notify_only_when_home": "Եթե ​​միացված է, հետաձգեք ծանուցումները այնքան ժամանակ, մինչև ընտրված առնվազն մեկը տանը լինի:", + "notify_fire_events": "Եթե ​​միացված է, գործարկեք Home Assistant-ի իրադարձությունները ցիկլի մեկնարկի և ավարտի համար (ha_washdata_cycle_started և ha_washdata_cycle_ended)՝ ավտոմատներ վարելու համար:", + "notify_start_services": "Մեկ կամ մի քանի ծառայություններ ծանուցելու համար, երբ ցիկլը սկսվում է: Դատարկ թողեք՝ մեկնարկի ծանուցումները բաց թողնելու համար:", + "notify_finish_services": "Մեկ կամ մի քանի ծառայություններ ծանուցելու համար, երբ ցիկլը ավարտվում է կամ մոտենում է ավարտին: Դատարկ թողեք՝ ավարտի ծանուցումները բաց թողնելու համար:", + "notify_live_services": "Մեկ կամ մի քանի ծառայություններ ծանուցում են՝ ցիկլը գործելու ընթացքում ուղիղ առաջընթացի թարմացումներ ստանալու համար (միայն շարժական ուղեկից հավելված): Թողեք դատարկ՝ ուղիղ թարմացումները բաց թողնելու համար:", + "notify_before_end_minutes": "Ծանուցում գործարկելու համար ցիկլի մոտավոր ավարտից րոպեներ առաջ: Անջատելու համար դրեք 0: Կանխադրված՝ 0:", + "notify_title": "Ծանուցումների հատուկ վերնագիր: Աջակցում է {device} տեղապահին:", + "notify_icon": "Ծանուցումների համար օգտագործելու պատկերակ (օրինակ՝ mdi:washing-machine): Դատարկ թողեք՝ պատկերակ չուղարկելու համար:", + "notify_start_message": "Հաղորդագրություն ուղարկվում է, երբ ցիկլը սկսվում է: Աջակցում է {device} տեղապահին:", + "notify_finish_message": "Հաղորդագրությունն ուղարկվում է, երբ ցիկլն ավարտվում է: Աջակցում է {device}, {duration}, {program}, {energy_kwh}, {cost} տեղապահեր:", + "notify_pre_complete_message": "Պարբերական կենդանի առաջընթացի տեքստը թարմացվում է ցիկլը գործելու ընթացքում: Աջակցում է {device}, {minutes}, {program} տեղապահեր:" + } + }, + "notifications": { + "title": "Ծանուցումներ", + "description": "Կարգավորեք ծանուցման ալիքները, ձևանմուշները և կամընտիր շարժական շարժական առաջընթացի թարմացումները:", + "data": { + "notify_actions": "Ծանուցման գործողություններ", + "notify_people": "Հետաձգեք առաքումը, քանի դեռ այս մարդիկ տանը չեն", + "notify_only_when_home": "Հետաձգել ծանուցումները, քանի դեռ ընտրված անձը տուն չէ", + "notify_fire_events": "Ավտոմատացման իրադարձությունների գործարկում", + "notify_start_services": "Ցիկլի սկիզբ - Ծանուցման թիրախներ", + "notify_finish_services": "Ցիկլի ավարտ - Ծանուցման թիրախներ", + "notify_live_services": "Ուղիղ առաջընթաց - Ծանուցման թիրախներ", + "notify_before_end_minutes": "Նախնական ավարտի ծանուցում (ավարտից րոպեներ առաջ)", + "notify_live_interval_seconds": "Ուղիղ թարմացումների ընդմիջում (վայրկյաններ)", + "notify_live_overrun_percent": "Ուղիղ թարմացումների գերազանցման նպաստ (%)", + "notify_live_chronometer": "Քրոնոմետրի հետհաշվարկի ժամանակաչափ", + "notify_title": "Ծանուցման վերնագիր", + "notify_icon": "Ծանուցման պատկերակ", + "notify_start_message": "Սկսեք հաղորդագրության ձևաչափը", + "notify_finish_message": "Ավարտել հաղորդագրության ձևաչափը", + "notify_pre_complete_message": "Հաղորդագրության նախնական ավարտի ձևաչափ", + "energy_price_entity": "Էներգիայի գին - սուբյեկտ (ըստ ցանկության)", + "energy_price_static": "Էներգիայի գին - Ստատիկ արժեք (ըստ ցանկության)", + "go_back": "Վերադառնալ առանց պահպանելու", + "notify_reminder_message": "Հիշեցման հաղորդագրության ձևաչափ", + "notify_timeout_seconds": "Ավտոմատ անջատում հետո (վայրկյաններ)", + "notify_channel": "Ծանուցման ալիք (կարգավիճակ/ուղիղ/հիշեցում)", + "notify_finish_channel": "Ավարտված ծանուցման ալիքը" + }, + "data_description": { + "go_back": "Նշեք սա և սեղմեք «Ներկայացնել», որպեսզի վերևի բոլոր փոփոխությունները չեղարկվեն և վերադառնաք գլխավոր մենյու։", + "notify_actions": "Տնային օգնականի կամընտիր գործողությունների հաջորդականությունը՝ ծանուցումների համար (սկրիպտներ, ծանուցումներ, TTS և այլն): Եթե ​​կարգավորված է, գործողությունները կկատարվեն նախքան հետադարձ ծանուցման ծառայությունը:", + "notify_people": "Մարդկանց միավորներ, որոնք օգտագործվում են ներկայության դարպասների համար: Երբ միացված է, ծանուցումների առաքումը հետաձգվում է այնքան ժամանակ, քանի դեռ ընտրված առնվազն մեկը տանը չէ:", + "notify_only_when_home": "Եթե ​​միացված է, հետաձգեք ծանուցումները այնքան ժամանակ, մինչև ընտրված առնվազն մեկը տանը լինի:", + "notify_fire_events": "Եթե ​​միացված է, գործարկեք Home Assistant-ի իրադարձությունները ցիկլի մեկնարկի և ավարտի համար (ha_washdata_cycle_started և ha_washdata_cycle_ended)՝ ավտոմատներ վարելու համար:", + "notify_start_services": "Մեկ կամ մի քանի ծանուցման ծառայություններ զգուշացնելու համար, երբ ցիկլը սկսվում է (օրինակ՝ notify.mobile_app_my_phone): Դատարկ թողեք՝ մեկնարկի ծանուցումները բաց թողնելու համար:", + "notify_finish_services": "Մեկ կամ մի քանի ծառայություններ ծանուցելու համար, երբ ցիկլը ավարտվում է կամ մոտենում է ավարտին: Դատարկ թողեք՝ ավարտի ծանուցումները բաց թողնելու համար:", + "notify_live_services": "Մեկ կամ մի քանի ծառայություններ ծանուցում են՝ ցիկլը գործելու ընթացքում ուղիղ առաջընթացի թարմացումներ ստանալու համար (միայն շարժական ուղեկից հավելված): Թողեք դատարկ՝ ուղիղ թարմացումները բաց թողնելու համար:", + "notify_before_end_minutes": "Ծանուցում գործարկելու համար ցիկլի մոտավոր ավարտից րոպեներ առաջ: Անջատելու համար դրեք 0: Կանխադրված՝ 0:", + "notify_live_interval_seconds": "Որքան հաճախ են առաջընթացի թարմացումներն ուղարկվում գործարկման ընթացքում: Ավելի ցածր արժեքներն ավելի արձագանքող են, բայց սպառում են ավելի շատ ծանուցումներ: Ստիպված է նվազագույնը 30 վայրկյան. 30 վրկ-ից ցածր արժեքները ավտոմատ կերպով կլորացվում են մինչև 30 վրկ:", + "notify_live_overrun_percent": "Անվտանգության մարժան բարձր է երկարաժամկետ/գերազանցող ցիկլերի համար թարմացումների գնահատված քանակից (օրինակ՝ 20%-ը թույլ է տալիս 1,2 անգամ գնահատված թարմացումներ):", + "notify_live_chronometer": "Երբ միացված է, յուրաքանչյուր ուղիղ թարմացում ներառում է ժամանակաչափի հետհաշվարկ մինչև ավարտի գնահատված ժամանակը: Ծանուցման ժամաչափը սարքի վրա ավտոմատ կերպով իջնում ​​է թարմացումների միջև (միայն Android-ի ուղեկից հավելված):", + "notify_title": "Ծանուցումների հատուկ վերնագիր: Աջակցում է {device} տեղապահին:", + "notify_icon": "Ծանուցումների համար օգտագործելու պատկերակ (օրինակ՝ mdi:washing-machine): Դատարկ թողեք՝ պատկերակ չուղարկելու համար:", + "notify_start_message": "Հաղորդագրություն ուղարկվում է, երբ ցիկլը սկսվում է: Աջակցում է {device} տեղապահին:", + "notify_finish_message": "Հաղորդագրությունն ուղարկվում է, երբ ցիկլն ավարտվում է: Աջակցում է {device}, {duration}, {program}, {energy_kwh}, {cost} տեղապահեր:", + "energy_price_entity": "Ընտրեք թվային միավոր, որը պահպանում է էլեկտրաէներգիայի ընթացիկ գինը (օրինակ՝ sensor.electricity_price, input_number.energy_rate): Երբ սահմանված է, միացնում է {cost} տեղապահը: Գերակայություն է ստանում ստատիկ արժեքից, եթե երկուսն էլ կազմաձևված են:", + "energy_price_static": "Էլեկտրաէներգիայի ֆիքսված գինը կՎտ/ժ-ի համար: Երբ սահմանված է, միացնում է {cost} տեղապահը: Անտեսվում է, եթե կառույցը կազմաձևված է վերևում: Ավելացրեք ձեր արժույթի խորհրդանիշը հաղորդագրության ձևանմուշում, օրինակ. {cost} €.", + "notify_reminder_message": "Մեկանգամյա հիշեցման տեքստն ուղարկվել է հաշվարկված ավարտից առաջ կազմաձևված րոպեների քանակը: Տարբերակ վերևում կրկնվող ուղիղ թարմացումներից: Աջակցում է {device}, {minutes}, {program} տեղապահեր:", + "notify_timeout_seconds": "Այսքան վայրկյանից հետո ավտոմատ կերպով մերժել ծանուցումները (փոխանցվում է որպես ուղեկից հավելվածի «ժամկետը»): Սահմանեք 0՝ դրանք պահելու համար մինչև չհրաժարվեն: Կանխադրված՝ 0:", + "notify_channel": "Android ուղեկից հավելվածի ծանուցումների ալիք՝ կարգավիճակի, ուղիղ առաջընթացի և հիշեցումների ծանուցումների համար: Ալիքի ձայնը և նշանակությունը կազմաձևվում են ուղեկից հավելվածում, երբ առաջին անգամ օգտագործվում է ալիքի անունը. WashData-ն սահմանում է միայն ալիքի անունը: Դատարկ թողեք հավելվածի լռելյայն համար:", + "notify_finish_channel": "Առանձնացրեք Android ալիքը պատրաստի ծանուցման համար (օգտագործվում է նաև հիշեցման և լվացքի սպասող ալիքի կողմից), որպեսզի այն կարողանա ունենալ իր սեփական ձայնը: Դատարկ թողեք վերևի ալիքը նորից օգտագործելու համար:", + "notify_pre_complete_message": "Պարբերական կենդանի առաջընթացի տեքստը թարմացվում է ցիկլը գործելու ընթացքում: Աջակցում է {device}, {minutes}, {program} տեղապահեր:" + } + }, + "advanced_settings": { + "title": "Ընդլայնված կարգավորումներ", + "description": "Կարգավորեք հայտնաբերման շեմերը, ուսուցումը և ընդլայնված վարքագիծը։ Լռելյայնները լավ են աշխատում կարգավորումների մեծ մասի համար։", + "sections": { + "suggestions_section": { + "name": "Առաջարկվող կարգավորումներ", + "description": "Հասանելի է {suggestions_count} ուսուցված առաջարկ։ Պահպանելուց առաջ առաջարկվող փոփոխությունները վերանայելու համար նշեք «Կիրառել առաջարկվող արժեքները»։", + "data": { + "apply_suggestions": "Կիրառել առաջարկվող արժեքները" + }, + "data_description": { + "apply_suggestions": "Նշեք այս վանդակը՝ ուսումնական ալգորիթմից առաջարկվող արժեքների վերանայման քայլ բացելու համար: Ոչինչ ինքնաբերաբար չի պահպանվում:\n\nԱռաջարկվում է (սովորելուց).\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Հայտնաբերում և հզորության շեմեր", + "description": "Երբ է սարքը համարվում միացված կամ անջատված, էներգիայի շեմերը և վիճակների անցումների ժամանակավորումը։", + "data": { + "start_duration_threshold": "Սկսել Debounce-ի տևողությունը (ներ)", + "start_energy_threshold": "Մեկնարկի էներգիայի դարպաս (Wh)", + "completion_min_seconds": "Ավարտման նվազագույն ժամկետը (վայրկյան)", + "start_threshold_w": "Մեկնարկի շեմը (Վտ)", + "stop_threshold_w": "Կանգառի շեմ (Վտ)", + "end_energy_threshold": "Ավարտի էներգիայի դարպաս (Wh)", + "running_dead_zone": "Մեռյալ գոտի մեկնարկից հետո (վայրկյան)", + "end_repeat_count": "Ավարտել Կրկնությունների հաշվարկը", + "min_off_gap": "Մինի բացը ցիկլերի (ների) միջև", + "sampling_interval": "Նմուշառման միջակայք (ներ)" + }, + "data_description": { + "start_duration_threshold": "Անտեսեք այս տևողությունից (վայրկյան) ավելի կարճ հոսանքի արագությունը: Զտում է բեռնախցիկի բարձրությունները (օրինակ՝ 60 Վտ 2 վրկ-ի համար) մինչև իրական ցիկլի մեկնարկը: Կանխադրված՝ 5 վրկ.", + "start_energy_threshold": "Հաստատվելու համար ցիկլը պետք է առնվազն այսքան էներգիա (Վտժ) կուտակի START փուլում: Կանխադրված՝ 0,005 Վտ.", + "completion_min_seconds": "Սրանից կարճ ցիկլերը կնշվեն որպես «ընդհատված», նույնիսկ եթե դրանք բնականաբար ավարտվեն: Կանխադրված՝ 600 վ.", + "start_threshold_w": "Ցիկլը սկսելու համար հզորությունը պետք է բարձրանա այս շեմից: Սահմանեք ավելի բարձր, քան ձեր սպասման հզորությունը: Կանխադրված՝ min_power + 1 W:", + "stop_threshold_w": "Հզորությունը պետք է ընկնի այս շեմից՝ ցիկլը ավարտելու համար: Սահմանեք զրոյից մի փոքր վեր՝ կեղծ ծայրեր առաջացնող աղմուկից խուսափելու համար: Կանխադրված՝ 0,6 * min_power:", + "end_energy_threshold": "Ցիկլը ավարտվում է միայն այն դեպքում, եթե վերջին Off-Delay պատուհանում էներգիան ցածր է այս արժեքից (Wh): Կանխում է կեղծ ծայրերը, եթե հզորությունը տատանվում է զրոյի մոտ: Կանխադրված՝ 0,05 Վտ.", + "running_dead_zone": "Ցիկլը սկսելուց հետո այսքան վայրկյան անտեսեք հոսանքի անկումները՝ սկզբնական պտտման փուլում կանխելու սխալ վերջնակետերի հայտնաբերումը: Անջատելու համար դրեք 0: Կանխադրված՝ 0 վրկ.", + "end_repeat_count": "Վերջնական պայմանը (off_delay-ի շեմից ցածր հզորությունը) պետք է անընդմեջ բավարարվի նախքան ցիկլի ավարտը: Ավելի բարձր արժեքները կանխում են կեղծ ծայրերը դադարների ժամանակ: Կանխադրված՝ 1.", + "min_off_gap": "Եթե ​​ցիկլը սկսվում է նախորդի ավարտից այս վայրկյանների ընթացքում, ապա դրանք կմիաձուլվեն մեկ ցիկլի մեջ:", + "sampling_interval": "Նմուշառման նվազագույն միջակայքը վայրկյաններով: Այս սակագնից ավելի արագ թարմացումները անտեսվելու են: Կանխադրված՝ 30 վրկ (2 վրկ լվացքի մեքենաների, լվացքի չորանոցների և աման լվացող մեքենաների համար):" + } + }, + "matching_section": { + "name": "Պրոֆիլների համապատասխանեցում և ուսուցում", + "description": "Որքան ագրեսիվ է WashData-ն ընթացիկ ցիկլերը համեմատում սովորած պրոֆիլների հետ և երբ է պետք հարցնել հետադարձ կապ։", + "data": { + "profile_match_min_duration_ratio": "Պրոֆիլի համապատասխանության նվազագույն տևողության հարաբերակցությունը (0.1-1.0)", + "profile_match_interval": "Պրոֆիլի համընկնման ընդմիջում (վայրկյաններ)", + "profile_match_threshold": "Պրոֆիլների համապատասխանության շեմը", + "profile_unmatch_threshold": "Պրոֆիլների չհամապատասխանեցման շեմը", + "auto_label_confidence": "Ավտոմատ պիտակի վստահություն (0-1)", + "learning_confidence": "Հետադարձ կապի հարցում վստահություն (0-1)", + "suppress_feedback_notifications": "Չեղարկել հետադարձ կապի ծանուցումները", + "duration_tolerance": "Տևողության հանդուրժողականություն (0-0,5)", + "smoothing_window": "Հարթեցնող պատուհան (նմուշներ)", + "profile_duration_tolerance": "Պրոֆիլի համընկնման տևողության հանդուրժողականություն (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Պրոֆիլի համապատասխանության նվազագույն տևողության հարաբերակցությունը: Վազքի ցիկլը պետք է լինի պրոֆիլի տևողության առնվազն այս բաժինը, որպեսզի համապատասխանի: Ստորին = ավելի վաղ համընկնում: Կանխադրված՝ 0,50 (50%):", + "profile_match_interval": "Որքան հաճախ (վայրկյանների ընթացքում) փորձեք պրոֆիլի համապատասխանությունը ցիկլի ընթացքում: Ցածր արժեքները ապահովում են ավելի արագ հայտնաբերում, բայց ավելի շատ CPU օգտագործում: Կանխադրված՝ 300 վրկ (5 րոպե):", + "profile_match_threshold": "DTW-ի նմանության նվազագույն միավորը (0,0–1,0) պահանջվում է պրոֆիլը համընկնող համարելու համար: Ավելի բարձր = ավելի խիստ համապատասխանություն, ավելի քիչ կեղծ դրական: Կանխադրված՝ 0.4:", + "profile_unmatch_threshold": "DTW նմանության միավոր (0.0–1.0), որից ցածր նախկինում համապատասխանող պրոֆիլը մերժվում է: Թարթումը կանխելու համար պետք է լինի համընկնող շեմից ցածր: Կանխադրված՝ 0,35:", + "auto_label_confidence": "Ավարտելիս ավտոմատ կերպով պիտակավորեք ցիկլերը այս վստահությունից կամ ավելի բարձր (0.0–1.0):", + "learning_confidence": "Միջոցառման միջոցով օգտվողի հաստատում պահանջելու նվազագույն վստահություն (0.0–1.0):", + "suppress_feedback_notifications": "Երբ միացված է, WashData-ն նախկինի պես կհետևի և ներքին հետադարձ կապ կպահանջի, բայց չի ցուցադրի մշտական ​​ծանուցումներ, որոնք խնդրում են հաստատել ցիկլերը: Օգտակար է, երբ ցիկլերը միշտ ճիշտ են հայտնաբերվում, և հուշումները շեղում են:", + "duration_tolerance": "Հանդուրժողականություն վազքի ընթացքում մնացած ժամանակի գնահատումների նկատմամբ (0,0–0,5 համապատասխանում է 0–50%)։", + "smoothing_window": "Շարժվող միջին հարթեցման համար օգտագործված էներգիայի վերջին ընթերցումների քանակը:", + "profile_duration_tolerance": "Հանդուրժողականություն, որն օգտագործվում է պրոֆիլների համընկնումով ընդհանուր տևողության շեղումների համար (0,0–0,5): Կանխադրված վարքագիծը՝ ±25%:" + } + }, + "timing_section": { + "name": "Ժամանակավորում, սպասարկում և վրիպազերծում", + "description": "Watchdog, առաջընթացի վերականգնում, ավտոմատ սպասարկում և վրիպազերծման միավորների ցուցադրում։", + "data": { + "watchdog_interval": "Watchdog ընդմիջում (վայրկյան)", + "no_update_active_timeout": "Առանց թարմացման ժամանակի ավարտ (ներ)", + "progress_reset_delay": "Առաջընթացի վերակայման հետաձգում (վայրկյան)", + "auto_maintenance": "Միացնել ավտոմատ սպասարկումը", + "expose_debug_entities": "Բացահայտեք վրիպազերծման սուբյեկտները", + "save_debug_traces": "Պահպանեք վրիպազերծման հետքերը" + }, + "data_description": { + "watchdog_interval": "Վազքի ընթացքում պահակային ստուգումների միջև ընկած վայրկյաններ: Կանխադրված՝ 5 վրկ. ԶԳՈՒՇԱՑՈՒՄ. Համոզվեք, որ սա ավելի բարձր է, քան ձեր սենսորի թարմացման միջակայքը՝ կեղծ կանգառներից խուսափելու համար (օրինակ՝ եթե սենսորը թարմացվում է 60 վայրկյանը մեկ, օգտագործեք 65 վրկ+):", + "no_update_active_timeout": "Հրապարակման փոփոխման սենսորների համար. միայն ստիպողաբար ավարտեք ԱԿՏԻՎ ցիկլը, եթե այսքան վայրկյանի ընթացքում թարմացումներ չկան: Ցածր էներգիայի ավարտը դեռ օգտագործում է Off Delay-ը:", + "progress_reset_delay": "Ցիկլը ավարտվելուց հետո (100%), սպասեք այսքան վայրկյան անգործության, նախքան առաջընթացը 0% վերականգնելը: Կանխադրված՝ 300 վրկ.", + "auto_maintenance": "Միացնել ավտոմատ սպասարկումը (նմուշների վերանորոգում, բեկորների միաձուլում):", + "expose_debug_entities": "Ցույց տալ առաջադեմ սենսորներ (Վստահություն, փուլ, երկիմաստություն) վրիպազերծման համար:", + "save_debug_traces": "Պահպանեք մանրամասն դասակարգման և հզորության հետքի տվյալները պատմության մեջ (մեծացնում է պահեստի օգտագործումը):" + } + }, + "anti_wrinkle_section": { + "name": "Հակակնճիռային պաշտպանություն (չորանոցներ)", + "description": "Անտեսեք ցիկլից հետո թմբուկի պտույտները՝ ուրվական ցիկլերից խուսափելու համար։ Միայն չորանոց / լվացք-չորանոց։", + "data": { + "anti_wrinkle_enabled": "Հակակնճիռների պաշտպանություն (միայն չորանոց)", + "anti_wrinkle_max_power": "Հակակնճիռների առավելագույն հզորությունը (Վտ)", + "anti_wrinkle_max_duration": "Հակակնճիռների առավելագույն տևողությունը (ներ)", + "anti_wrinkle_exit_power": "Հակակնճիռների ելքի հզորություն (Վտ)" + }, + "data_description": { + "anti_wrinkle_enabled": "Անտեսեք ցածր էներգիայի թմբուկի կարճ պտույտները ցիկլի ավարտից հետո՝ կանխելու ուրվականների ցիկլերը (միայն չորանոց/լվացքի-չորանոց):", + "anti_wrinkle_max_power": "Այս հզորությունից կամ դրանից ցածր պոռթկումները դիտարկվում են որպես հակակնճիռային պտույտներ՝ նոր ցիկլերի փոխարեն: Կիրառվում է միայն այն դեպքում, երբ հակակնճիռների պաշտպանությունը միացված է:", + "anti_wrinkle_max_duration": "Պայթեցման առավելագույն երկարություն՝ որպես հակակնճիռային ռոտացիա բուժելու համար: Կիրառվում է միայն այն դեպքում, երբ հակակնճիռների պաշտպանությունը միացված է:", + "anti_wrinkle_exit_power": "Եթե ​​հզորությունը իջնի այս մակարդակից ցածր, անմիջապես դուրս եկեք հակակնճիռներից (ճշմարիտ անջատված): Կիրառվում է միայն այն դեպքում, երբ հակակնճիռների պաշտպանությունը միացված է:" + } + }, + "delay_start_section": { + "name": "Հետաձգված մեկնարկի հայտնաբերում", + "description": "Կայուն ցածր հզորության սպասման ռեժիմը համարեք «Սպասում է մեկնարկին», մինչև ցիկլը իրականում սկսվի։", + "data": { + "delay_start_detect_enabled": "Միացնել հետաձգված մեկնարկի հայտնաբերումը", + "delay_confirm_seconds": "Հետաձգված մեկնարկ. սպասման հաստատման ժամանակ (վրկ)", + "delay_timeout_hours": "Հետաձգված մեկնարկ. առավելագույն սպասման ժամանակ (ժ)" + }, + "data_description": { + "delay_start_detect_enabled": "Երբ միացված է, ցածր էներգիայի արտահոսքի կարճ ցատկը, որին հաջորդում է սպասման կայուն հոսանքը, հայտնաբերվում է որպես ուշ մեկնարկ: Ուշացման ընթացքում վիճակի սենսորը ցույց է տալիս «Սպասում է մեկնարկին»: Ծրագրի ժամանակին կամ առաջընթացին չի հետևում մինչև ցիկլը իրականում սկսվի: Պահանջում է, որ start_threshold_w-ը դրվի արտահոսքի բարձրության հզորությունից բարձր:", + "delay_confirm_seconds": "Էլեկտրական հզորությունը պետք է այսքան վայրկյան մնա Կանգառի շեմի (Վտ) և Մեկնարկի շեմի (Վտ) միջև, մինչև WashData-ն անցնի «Սպասում է մեկնարկին» վիճակին։ Զտում է մենյուում տեղաշարժվելու կարճ պիկերը։ Լռելյայն՝ 60 վրկ։", + "delay_timeout_hours": "Անվտանգության ժամանակի սպառում. եթե այսքան ժամ անց սարքը դեռ «Սպասում է գործարկմանը», WashData-ն վերականգնվում է Անջատվածի: Կանխադրված՝ 8 ժ." + } + }, + "external_triggers_section": { + "name": "Արտաքին թրիգերներ, դուռ և դադար", + "description": "Ընտրովի արտաքին ավարտի թրիգեր-սենսոր, դռան սենսոր՝ դադարի/մաքուրի հայտնաբերման համար, և դադարի ժամանակ հոսանքը կտրող անջատիչ։", + "data": { + "external_end_trigger_enabled": "Միացնել արտաքին ցիկլի ավարտի գործարկիչը", + "external_end_trigger": "Արտաքին ցիկլի ավարտի սենսոր", + "external_end_trigger_inverted": "Շրջել ձգանման տրամաբանությունը (գործարկիչը անջատված է)", + "door_sensor_entity": "Դռների սենսոր", + "pause_cuts_power": "Անջատեք սնուցումը դադարի ժամանակ", + "switch_entity": "Անջատիչ սարք (սնուցման անջատման դադարեցման համար)", + "notify_unload_delay_minutes": "Լվացքի սպասման ծանուցման ուշացում (րոպե)" + }, + "data_description": { + "external_end_trigger_enabled": "Միացնել արտաքին երկուական սենսորի մոնիտորինգը՝ ցիկլի ավարտը գործարկելու համար:", + "external_end_trigger": "Ընտրեք երկուական սենսորային միավոր: Երբ այս սենսորը գործարկվի, ընթացիկ ցիկլը կավարտվի «ավարտված» կարգավիճակով:", + "external_end_trigger_inverted": "Լռելյայնորեն, ձգանն աշխատում է, երբ սենսորը միանում է: Նշեք այս վանդակը, որպեսզի գործարկվի, երբ դրա փոխարեն սենսորն անջատվի:", + "door_sensor_entity": "Լրացուցիչ երկուական սենսոր մեքենայի դռան համար (միացված = բաց, անջատված = փակ): Երբ դուռը բացվում է ակտիվ ցիկլի ընթացքում, WashData-ն կհաստատի ցիկլը որպես միտումնավոր դադար: Ցիկլը ավարտվելուց հետո, եթե դուռը դեռ փակ է, վիճակը փոխվում է «Մաքուր» մինչև դուռը բացվի: Նշում․ դուռը փակելով չի վերսկսվում դադարեցված ցիկլը. ռեզյումեն պետք է գործարկվի ձեռքով Resume Cycle կոճակի կամ ծառայության միջոցով:", + "pause_cuts_power": "Pause Cycle կոճակի կամ ծառայության միջոցով դադարեցնելիս անջատեք նաև կազմաձևված անջատիչ սարքը: Գործում է միայն այն դեպքում, եթե անջատիչի կազմաձևված է այս սարքի համար:", + "switch_entity": "Անջատիչի միավորը պետք է փոխարկվի դադարի կամ վերսկսման ժամանակ: Պահանջվում է, երբ միացված է «Կտրել սնուցումը դադարեցման ժամանակ»:", + "notify_unload_delay_minutes": "Ուղարկեք ծանուցում ավարտի ծանուցման ալիքով այն բանից հետո, երբ ցիկլը ավարտվի, և դուռը այսքան րոպե չի բացվել (պահանջվում է Դռան ցուցիչ): Անջատելու համար դրեք 0: Կանխադրված՝ 60 րոպե:" + } + }, + "pump_section": { + "name": "Պոմպի հսկիչ", + "description": "Միայն պոմպ տեսակի սարքի համար։ Եթե պոմպի ցիկլը գերազանցի այս տևողությունը, կուղարկվի իրադարձություն։", + "data": { + "pump_stuck_duration": "Pump Stuck Alert շեմ (ներ) (միայն պոմպ)" + }, + "data_description": { + "pump_stuck_duration": "Եթե ​​այսքան վայրկյանից հետո պոմպի ցիկլը դեռ աշխատում է, ha_washdata_pump_stuck իրադարձությունը գործարկվում է մեկ անգամ: Օգտագործեք սա խցանված շարժիչը հայտնաբերելու համար (սովորական ջրամբարի պոմպի աշխատանքը 60 վրկ-ից ցածր է): Կանխադրված՝ 1800 վ (30 րոպե): Միայն պոմպի սարքի տեսակը:" + } + }, + "device_link_section": { + "name": "Սարքի հղում", + "description": "Ընտրովի միացրեք այս WashData սարքը գոյություն ունեցող Home Assistant սարքի հետ (օրինակ՝ խելացի վարդակից կամ սարքին): Այնուհետև WashData սարքը ցուցադրվում է որպես «Միացված է այդ սարքի միջոցով»: Թողեք դատարկ՝ WashData-ն որպես ինքնուրույն սարք պահելու համար:", + "data": { + "linked_device": "Կապակցված սարք" + }, + "data_description": { + "linked_device": "Ընտրեք սարքը, որին միացնում եք WashData-ն: Սա սարքի ռեեստրում ավելացնում է «Միացված է» հղումը. WashData-ն պահում է իր սարքի քարտն ու իրերը: Մաքրել ընտրությունը՝ հղումը հեռացնելու համար:" + } + } + } + }, + "diagnostics": { + "title": "Ախտորոշում և սպասարկում", + "description": "Կատարեք սպասարկման գործողություններ, ինչպիսիք են մասնատված ցիկլերի միաձուլումը կամ պահված տվյալների տեղափոխումը:\n\n**Պահպանման օգտագործում**\n\n- Ֆայլի չափը՝ {file_size_kb} ԿԲ\n- Ցիկլեր՝ {cycle_count}\n- Պրոֆիլներ՝ {profile_count}\n- Վրիպազերծման հետքեր՝ {debug_count}", + "menu_options": { + "reprocess_history": "Սպասարկում. Վերամշակել և օպտիմիզացնել տվյալները", + "clear_debug_data": "Մաքրել վրիպազերծման տվյալները (տեղ ազատել)", + "wipe_history": "Ջնջել այս սարքի ԲՈԼՈՐ տվյալները (անշրջելի)", + "export_import": "Արտահանել/ներմուծել JSON կարգավորումներով (պատճենել/տեղադրել)", + "menu_back": "← Հետ" + } + }, + "clear_debug_data": { + "title": "Մաքրել վրիպազերծման տվյալները", + "description": "Իսկապե՞ս ուզում եք ջնջել բոլոր պահված վրիպազերծման հետքերը: Սա տարածք կազատի, բայց կհեռացնի դասակարգման մանրամասն տեղեկությունները անցյալ փուլերից:" + }, + "manage_cycles": { + "title": "Կառավարեք ցիկլերը", + "description": "Վերջին ցիկլերը.\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Հին ցիկլերի ավտոմատ պիտակավորում", + "select_cycle_to_label": "Պիտակի հատուկ ցիկլ", + "select_cycle_to_delete": "Ջնջել ցիկլը", + "interactive_editor": "Միաձուլել / բաժանել ինտերակտիվ խմբագիր", + "trim_cycle_select": "Կտրման ցիկլի տվյալները", + "menu_back": "← Հետ" + } + }, + "manage_cycles_empty": { + "title": "Կառավարեք ցիկլերը", + "description": "Դեռևս ցիկլեր չեն գրանցվել:", + "menu_options": { + "auto_label_cycles": "Հին ցիկլերի ավտոմատ պիտակավորում", + "menu_back": "← Հետ" + } + }, + "manage_profiles": { + "title": "Կառավարեք պրոֆիլները", + "description": "Անձնագրի ամփոփում:\n{profile_summary}", + "menu_options": { + "create_profile": "Ստեղծեք նոր պրոֆիլ", + "edit_profile": "Խմբագրել/վերանվանել պրոֆիլը", + "delete_profile_select": "Ջնջել պրոֆիլը", + "profile_stats": "Պրոֆիլների վիճակագրություն", + "cleanup_profile": "Մաքրել պատմությունը - գրաֆիկ և ջնջել", + "assign_profile_phases_select": "Նշանակել փուլերի միջակայքերը", + "menu_back": "← Հետ" + } + }, + "manage_profiles_empty": { + "title": "Կառավարեք պրոֆիլները", + "description": "Դեռևս ստեղծված պրոֆիլներ չկան:", + "menu_options": { + "create_profile": "Ստեղծեք նոր պրոֆիլ", + "menu_back": "← Հետ" + } + }, + "manage_phase_catalog": { + "title": "Կառավարեք փուլային կատալոգը", + "description": "Փուլային կատալոգ (կանխադրված այս սարքի համար + հատուկ փուլեր).\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Ստեղծեք նոր փուլ", + "phase_catalog_edit_select": "Խմբագրել փուլ", + "phase_catalog_delete": "Ջնջել փուլը", + "menu_back": "← Հետ" + } + }, + "phase_catalog_create": { + "title": "Ստեղծեք հատուկ փուլ", + "description": "Ստեղծեք հատուկ փուլի անուն և նկարագրություն և ընտրեք, թե որ սարքի տեսակն է այն կիրառելի:", + "data": { + "target_device_type": "Թիրախային սարքի տեսակը", + "phase_name": "Փուլի անվանումը", + "phase_description": "Փուլի նկարագրություն" + } + }, + "phase_catalog_edit_select": { + "title": "Խմբագրել անհատական ​​փուլը", + "description": "Ընտրեք հատուկ փուլ՝ խմբագրելու համար:", + "data": { + "phase_name": "Պատվերով փուլ" + } + }, + "phase_catalog_edit": { + "title": "Խմբագրել անհատական ​​փուլը", + "description": "Խմբագրման փուլ՝ {phase_name}", + "data": { + "phase_name": "Փուլի անվանումը", + "phase_description": "Փուլի նկարագրություն" + } + }, + "phase_catalog_delete": { + "title": "Ջնջել հատուկ փուլը", + "description": "Ընտրեք հատուկ փուլ ջնջելու համար: Այս փուլն օգտագործող նշանակված ընդգրկույթները կհեռացվեն:", + "data": { + "phase_name": "Պատվերով փուլ" + } + }, + "assign_profile_phases": { + "title": "Նշանակել փուլերի միջակայքերը", + "description": "Փուլային նախադիտում պրոֆիլի համար՝ **{profile_name}**\n\n{timeline_svg}\n\n** Ընթացիկ միջակայքերը: **\n{current_ranges}\n\nՕգտագործեք ստորև նշված գործողությունները՝ միջակայքերը ավելացնելու, խմբագրելու, ջնջելու կամ պահպանելու համար:", + "menu_options": { + "assign_profile_phases_add": "Ավելացնել փուլային տիրույթ", + "assign_profile_phases_edit_select": "Խմբագրել փուլերի տիրույթը", + "assign_profile_phases_delete": "Ջնջել փուլային տիրույթը", + "phase_ranges_clear": "Մաքրել բոլոր տիրույթները", + "assign_profile_phases_auto_detect": "Ավտոմատ հայտնաբերման փուլեր", + "phase_ranges_save": "Պահպանել և վերադարձնել", + "menu_back": "← Հետ" + } + }, + "assign_profile_phases_add": { + "title": "Ավելացնել փուլային տիրույթ", + "description": "Ավելացրեք մեկ փուլային տիրույթ ընթացիկ նախագծին:", + "data": { + "phase_name": "Փուլ", + "start_min": "Մեկնարկային րոպե", + "end_min": "Ավարտի րոպե" + } + }, + "assign_profile_phases_edit_select": { + "title": "Խմբագրել փուլերի տիրույթը", + "description": "Ընտրեք տիրույթ՝ խմբագրելու համար:", + "data": { + "range_index": "Փուլային միջակայք" + } + }, + "assign_profile_phases_edit": { + "title": "Խմբագրել փուլերի տիրույթը", + "description": "Թարմացրեք ընտրված փուլային տիրույթը:", + "data": { + "phase_name": "Փուլ", + "start_min": "Մեկնարկային րոպե", + "end_min": "Ավարտի րոպե" + } + }, + "assign_profile_phases_delete": { + "title": "Ջնջել փուլային տիրույթը", + "description": "Ընտրեք ընդգրկույթ՝ սևագրից հեռացնելու համար:", + "data": { + "range_index": "Փուլային միջակայք" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Ավտոմատ հայտնաբերված փուլեր", + "description": "Պրոֆիլ՝ **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} փուլ(եր) ինքնաբերաբար հայտնաբերվեց:** Ընտրեք գործողությունը ստորև:", + "data": { + "action": "Գործողություն" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Հայտնաբերված փուլերի անվանումը", + "description": "Պրոֆիլ՝ **{profile_name}**\n\n** Հայտնաբերվել է {detected_count} փուլ(եր)՝ **\n{ranges_summary}\n\nՅուրաքանչյուր փուլին անուն տվեք:" + }, + "assign_profile_phases_select": { + "title": "Նշանակել փուլերի միջակայքերը", + "description": "Ընտրեք պրոֆիլ՝ փուլային միջակայքերը կարգավորելու համար:", + "data": { + "profile": "Անձնագիր" + } + }, + "profile_stats": { + "title": "Պրոֆիլների վիճակագրություն", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Ստեղծեք նոր պրոֆիլ", + "description": "Ստեղծեք նոր պրոֆիլ ձեռքով կամ անցյալ շրջանից:\n\nՊրոֆիլների անունների օրինակներ՝ «Նուրբ», «Ծանր պարտականություն», «Արագ լվացում»", + "data": { + "profile_name": "Պրոֆիլի անվանումը", + "reference_cycle": "Հղման ցիկլ (ըստ ցանկության)", + "manual_duration": "Ձեռնարկի տևողությունը (րոպե)" + } + }, + "edit_profile": { + "title": "Խմբագրել պրոֆիլը", + "description": "Ընտրեք պրոֆիլ՝ վերանվանելու համար", + "data": { + "profile": "Անձնագիր" + } + }, + "rename_profile": { + "title": "Խմբագրել պրոֆիլի մանրամասները", + "description": "Ընթացիկ պրոֆիլը՝ {current_name}", + "data": { + "new_name": "Պրոֆիլի անվանումը", + "manual_duration": "Ձեռնարկի տևողությունը (րոպե)" + } + }, + "delete_profile_select": { + "title": "Ջնջել պրոֆիլը", + "description": "Ընտրեք պրոֆիլ՝ ջնջելու համար", + "data": { + "profile": "Անձնագիր" + } + }, + "delete_profile_confirm": { + "title": "Հաստատեք Ջնջել պրոֆիլը", + "description": "⚠️ Սա ընդմիշտ կջնջի պրոֆիլը՝ {profile_name}", + "data": { + "unlabel_cycles": "Հեռացրեք պիտակը այս պրոֆիլի օգտագործմամբ ցիկլերից" + } + }, + "auto_label_cycles": { + "title": "Հին ցիկլերի ավտոմատ պիտակավորում", + "description": "Գտնվել է ընդհանուր {total_count} ցիկլ: Պրոֆիլներ՝ {profiles}", + "data": { + "confidence_threshold": "Նվազագույն վստահություն (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Ընտրեք ցիկլը պիտակավորելու համար", + "description": "✓ = ավարտված, ⚠ = վերսկսված, ✗ = ընդհատված", + "data": { + "cycle_id": "Ցիկլ" + } + }, + "select_cycle_to_delete": { + "title": "Ջնջել ցիկլը", + "description": "⚠️ Սա ընդմիշտ կջնջի ընտրված ցիկլը", + "data": { + "cycle_id": "Ջնջման ցիկլ" + } + }, + "label_cycle": { + "title": "Պիտակի ցիկլ", + "description": "{cycle_info}", + "data": { + "profile_name": "Անձնագիր", + "new_profile_name": "Նոր պրոֆիլի անուն (եթե ստեղծվում է)" + } + }, + "post_process": { + "title": "Գործընթացների պատմություն (միաձուլում/բաժանում)", + "description": "Մաքրել ցիկլի պատմությունը՝ միաձուլելով մասնատված գործարկումները և ժամանակային պատուհանում բաժանելով սխալ միավորված ցիկլերը: Մուտքագրեք ժամերի քանակը հետ նայելու համար (օգտագործեք 999999 բոլորի համար):", + "data": { + "time_range": "Հետադարձ պատուհան (ժամեր)", + "gap_seconds": "Միաձուլել/բաժանել բացը (վայրկյաններ)" + }, + "data_description": { + "time_range": "Ժամերի քանակը սկանավորելու համար մասնատված ցիկլերի միաձուլման համար: Օգտագործեք 999999 բոլոր պատմական տվյալները մշակելու համար:", + "gap_seconds": "Պառակտում ընդդեմ միաձուլման որոշման շեմ: Սրանից ԱՎԵԼԻ ԿԱՐՃ բացերը միավորված են: Սրանից ավելի երկար բացերը բաժանված են: Իջեցրե՛ք սա՝ սխալ միաձուլված ցիկլի բաժանումը հարկադրելու համար:" + } + }, + "cleanup_profile": { + "title": "Մաքրել պատմությունը - Ընտրեք պրոֆիլը", + "description": "Ընտրեք պրոֆիլ՝ պատկերացնելու համար: Սա կստեղծի գրաֆիկ, որը ցույց է տալիս այս պրոֆիլի բոլոր անցյալ ցիկլերը՝ օգնելու բացահայտելու արտանետումները:", + "data": { + "profile": "Անձնագիր" + } + }, + "cleanup_select": { + "title": "Մաքրել պատմությունը - գրաֆիկ և ջնջել", + "description": "![Գծանկար]({graph_url})\n\n**Վիզուալացվում է {profile_name}**\n\nԳրաֆիկը ցույց է տալիս առանձին ցիկլերը գունավոր գծերի տեսքով: Ստորև բերված ցանկում նշեք ծայրամասերը (խմբից հեռու տողեր) և դրանց գույնի համապատասխանությունը՝ դրանք ջնջելու համար:\n\n**Ընտրեք ցիկլերը ՄԻՇՏ Ջնջելու համար.**", + "data": { + "cycles_to_delete": "Ջնջման ցիկլեր (ստուգեք համապատասխան գույները)" + } + }, + "interactive_editor": { + "title": "Միաձուլել / բաժանել ինտերակտիվ խմբագիր", + "description": "Ընտրեք ձեռքով գործողություն՝ ձեր ցիկլի պատմության վրա կատարելու համար:", + "menu_options": { + "editor_split": "Բաժանել ցիկլը (գտնել բացերը)", + "editor_merge": "Միաձուլման ցիկլեր (միացնել հատվածները)", + "editor_delete": "Ջնջել ցիկլ(ները)", + "menu_back": "← Հետ" + } + }, + "editor_select": { + "title": "Ընտրեք ցիկլեր", + "description": "Ընտրեք մշակման ենթակա ցիկլ(ներ):\n\n{info_text}", + "data": { + "selected_cycles": "Ցիկլեր" + } + }, + "editor_configure": { + "title": "Կարգավորել և կիրառել", + "description": "{preview_md}", + "data": { + "confirm_action": "Գործողություն", + "merged_profile": "Ստացված պրոֆիլը", + "new_profile_name": "Նոր պրոֆիլի անուն", + "confirm_commit": "Այո, ես հաստատում եմ այս գործողությունը", + "segment_0_profile": "Հատված 1 պրոֆիլ", + "segment_1_profile": "Սեգմենտի 2 պրոֆիլ", + "segment_2_profile": "Սեգմենտի 3 պրոֆիլ", + "segment_3_profile": "4-րդ հատվածի պրոֆիլը", + "segment_4_profile": "5-րդ հատվածի պրոֆիլը", + "segment_5_profile": "6-րդ հատվածի պրոֆիլը", + "segment_6_profile": "7-րդ հատվածի պրոֆիլը", + "segment_7_profile": "8-րդ հատվածի պրոֆիլը", + "segment_8_profile": "9-րդ հատվածի պրոֆիլը", + "segment_9_profile": "Սեգմենտի 10 պրոֆիլ" + } + }, + "editor_split_params": { + "title": "Պառակտման կոնֆիգուրացիա", + "description": "Կարգավորեք զգայունությունը այս ցիկլը բաժանելու համար: Սրանից երկար ցանկացած պարապ բացը կառաջացնի պառակտում:", + "data": { + "split_mode": "Բաժանման մեթոդ" + } + }, + "editor_split_auto_params": { + "title": "Ավտոմատ բաժանման կարգավորում", + "description": "Կարգավորեք այս ցիկլը բաժանելու զգայունությունը։ Դրանից ավելի երկար անգործության բացը կպատճառի բաժանում։", + "data": { + "min_gap_seconds": "Բաժանման դադարի շեմը (վայրկյան)" + } + }, + "editor_split_manual_params": { + "title": "Ձեռքով բաժանման ժամադրոշմներ", + "description": "{preview_md}Ցիկլի պատուհան՝ **{cycle_start_wallclock} → {cycle_end_wallclock}** {cycle_date}-ին։\n\nՄուտքագրեք մեկ կամ ավելի բաժանման ժամադրոշմ (`HH:MM` կամ `HH:MM:SS`), յուրաքանչյուր տողում մեկական։ Ցիկլը կկտրվի յուրաքանչյուր ժամադրոշմի պահին — N ժամադրոշմը տալիս է N+1 հատված։", + "data": { + "split_timestamps": "Բաժանման ժամային նշումներ" + } + }, + "reprocess_history": { + "title": "Սպասարկում. Վերամշակել և օպտիմիզացնել տվյալները", + "description": "Կատարում է պատմական տվյալների խորը մաքրում.\n1. Կտրում է զրոյական հզորության ցուցանիշները (լռություն):\n2. Ամրագրում է ցիկլի ժամանակը/տեւողությունը:\n3. Վերահաշվարկում է բոլոր վիճակագրական մոդելները (ծրարները):\n\n** Գործարկեք սա՝ տվյալների հետ կապված խնդիրները շտկելու կամ նոր ալգորիթմներ կիրառելու համար։**" + }, + "wipe_history": { + "title": "Ջնջել պատմությունը (միայն թեստավորում)", + "description": "⚠️ Սա ընդմիշտ կջնջի այս սարքի ԲՈԼՈՐ պահված ցիկլերն ու պրոֆիլները: Սա հնարավոր չէ հետարկել:" + }, + "export_import": { + "title": "Արտահանել/Ներմուծել JSON", + "description": "Պատճենեք/տեղադրեք այս սարքի ցիկլերը, պրոֆիլները և կարգավորումները: Արտահանեք ներքևում կամ տեղադրեք JSON՝ ներմուծելու համար:", + "data": { + "mode": "Գործողություն", + "json_payload": "Ամբողջական կազմաձևում JSON" + }, + "data_description": { + "mode": "Ընտրեք Արտահանել՝ ընթացիկ տվյալներն ու կարգավորումները պատճենելու համար, կամ Ներմուծել՝ արտահանված տվյալները մեկ այլ WashData սարքից տեղադրելու համար:", + "json_payload": "Արտահանման համար. պատճենեք այս JSON-ը (ներառում է ցիկլեր, պրոֆիլներ և բոլոր ճշգրտված կարգավորումները): Ներմուծման համար տեղադրեք JSON արտահանված WashData-ից:" + } + }, + "record_cycle": { + "title": "Գրանցման ցիկլ", + "description": "Ձեռքով ձայնագրեք ցիկլը՝ առանց միջամտության մաքուր պրոֆիլ ստեղծելու համար:\n\nԿարգավիճակը՝ **{status}**\nՏևողությունը՝ {duration}վ\nՆմուշներ՝ {samples}", + "menu_options": { + "record_refresh": "Թարմացնել կարգավիճակը", + "record_stop": "Դադարեցնել ձայնագրումը (Պահպանել և մշակել)", + "record_start": "Սկսեք նոր ձայնագրությունը", + "record_process": "Ընթացքի վերջին ձայնագրությունը (կտրում և պահպանում)", + "record_discard": "Վերացնել վերջին ձայնագրությունը", + "menu_back": "← Հետ" + } + }, + "record_process": { + "title": "Գործընթացի ձայնագրում", + "description": "![Գծանկար]({graph_url})\n\n**Գծանկարը ցույց է տալիս չմշակված ձայնագրությունը:** Կապույտ = Պահել, Կարմիր = Առաջարկվող կտրվածք:\n*Գծապատկերը ստատիկ է և չի թարմացվում ձևի փոփոխություններով։*\n\nՁայնագրումը դադարեցվեց: Կտրվածքները հավասարեցվում են հայտնաբերված նմուշառման արագությանը (~{sampling_rate}s):\n\nՀումքի տևողությունը՝ {duration} վ\nՆմուշներ՝ {samples}", + "data": { + "head_trim": "Կտրել սկիզբ (վայրկյան)", + "tail_trim": "Կտրել վերջը (վայրկյան)", + "save_mode": "Պահպանել նպատակակետը", + "profile_name": "Պրոֆիլի անվանումը" + } + }, + "learning_feedbacks": { + "title": "Ուսուցման հետադարձ կապ", + "description": "Սպասման մեջ գտնվող հետադարձ կապեր՝ {count}\n\n{pending_table}\n\nԸնտրեք ցիկլի վերանայման հարցում՝ մշակելու համար։", + "menu_options": { + "learning_feedbacks_pick": "Վերանայել սպասման մեջ գտնվող մեկ հետադարձ կապ", + "learning_feedbacks_dismiss_all": "Մերժել բոլոր սպասման մեջ գտնվող հետադարձ կապերը", + "menu_back": "← Հետ" + } + }, + "learning_feedbacks_pick": { + "title": "Ընտրեք հետադարձ կապ վերանայելու համար", + "description": "Ընտրեք բացելու ցիկլի վերանայման հարցում։", + "data": { + "selected_feedback": "Ընտրեք վերանայվող ցիկլը" + } + }, + "learning_feedbacks_empty": { + "title": "Ուսուցման հետադարձ կապ", + "description": "Հետադարձ կապի առկախ հարցումներ չեն գտնվել:", + "menu_options": { + "menu_back": "← Հետ" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Մերժել բոլոր սպասվող հետադարձ կապերը", + "description": "Դուք պատրաստվում եք մերժել **{count}** սպասվող հետադարձ կապի հարցում։ Մերժված ցիկլերը կմնան ձեր պատմության մեջ, բայց նոր ուսուցման ազդանշան չեն ավելացնի։ Սա հնարավոր չէ հետարկել։\n\n✅ Նշեք ստորև նշված վանդակը և սեղմեք **Ներկայացնել**՝ բոլորը մերժելու համար։\n❌ Թողեք այն չնշված և սեղմեք **Ներկայացնել**՝ չեղարկելու և հետ գնալու համար։", + "data": { + "confirm_dismiss_all": "Հաստատել. մերժել բոլոր սպասման մեջ գտնվող հետադարձ կապի հարցումները" + } + }, + "resolve_feedback": { + "title": "Լուծել հետադարձ կապը", + "description": "{comparison_img}{candidates_table}\n**Հայտնաբերված պրոֆիլ**՝ {detected_profile} ({confidence_pct}%)\n**Մոտավոր տեւողությունը**՝ {est_duration_min} ր\n**Փաստացի տևողությունը**՝ {act_duration_min} ր\n\n__Ընտրեք ստորև նշված գործողությունը՝__", + "data": { + "action": "Ի՞նչ կցանկանայիք անել։", + "corrected_profile": "Ճիշտ ծրագիր (եթե ուղղվում է)", + "corrected_duration": "Ճիշտ տեւողությունը վայրկյաններով (եթե ուղղվում է)" + } + }, + "trim_cycle_select": { + "title": "Կտրման ցիկլ - Ընտրեք ցիկլ", + "description": "Ընտրեք ցիկլը կտրելու համար: ✓ = ավարտված, ⚠ = վերսկսված, ✗ = ընդհատված", + "data": { + "cycle_id": "Ցիկլ" + } + }, + "trim_cycle": { + "title": "Կտրման ցիկլ", + "description": "Ընթացիկ պատուհան՝ {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Գործողություն" + } + }, + "trim_cycle_start": { + "title": "Սահմանել կտրման մեկնարկ", + "description": "Շրջանակային պատուհան՝ {cycle_start_wallclock} → {cycle_end_wallclock}\n\nԸնթացիկ սկիզբ՝ **{current_wallclock}** (+{current_offset_min} րոպե ցիկլի մեկնարկից)\n\nԸնտրեք նոր մեկնարկի ժամը՝ օգտագործելով ստորև նշված ժամացույցի ընտրիչը:", + "data": { + "trim_start_time": "Նոր մեկնարկի ժամանակ", + "trim_start_min": "Նոր սկիզբ (րոպեներ ցիկլի մեկնարկից)" + } + }, + "trim_cycle_end": { + "title": "Սահմանել կտրման ավարտ", + "description": "Շրջանակային պատուհան՝ {cycle_start_wallclock} → {cycle_end_wallclock}\n\nԸնթացիկ ավարտ՝ **{current_wallclock}** (+{current_offset_min} րոպե ցիկլի սկզբից)\n\nԸնտրեք ավարտի նոր ժամ՝ օգտագործելով ստորև նշված ժամացույցի ընտրիչը:", + "data": { + "trim_end_time": "Նոր ավարտի ժամանակ", + "trim_end_min": "Նոր ավարտ (րոպեներ ցիկլի սկզբից)" + } + } + }, + "error": { + "import_failed": "Չհաջողվեց ներմուծել: Խնդրում ենք տեղադրել վավեր WashData արտահանման JSON:", + "select_exactly_one": "Այս գործողության համար ընտրեք ճիշտ մեկ ցիկլ։", + "select_at_least_one": "Այս գործողության համար ընտրեք առնվազն մեկ ցիկլ։", + "select_at_least_two": "Այս գործողության համար ընտրեք առնվազն երկու ցիկլ։", + "profile_exists": "Այս անունով պրոֆիլն արդեն գոյություն ունի", + "rename_failed": "Չհաջողվեց վերանվանել պրոֆիլը: Հնարավոր է, որ պրոֆիլը գոյություն չունի, կամ նոր անունն արդեն վերցված է:", + "assignment_failed": "Չհաջողվեց ցիկլային պրոֆիլը նշանակել", + "duplicate_phase": "Այս անունով փուլ արդեն գոյություն ունի:", + "phase_not_found": "Ընտրված փուլը չի ​​գտնվել:", + "invalid_phase_name": "Ֆազի անունը չի կարող դատարկ լինել:", + "phase_name_too_long": "Ֆազի անունը չափազանց երկար է:", + "invalid_phase_range": "Փուլային տիրույթն անվավեր է: Վերջը պետք է ավելի մեծ լինի, քան սկիզբը:", + "invalid_phase_timestamp": "Ժամացույցն անվավեր է: Օգտագործեք YYYY-MM-DD HH:MM կամ HH:MM ձևաչափը:", + "overlapping_phase_ranges": "Փուլերի միջակայքերը համընկնում են: Կարգավորեք միջակայքերը և նորից փորձեք:", + "incomplete_phase_row": "Յուրաքանչյուր փուլային տող պետք է ներառի փուլ, գումարած ընտրված ռեժիմի մեկնարկի/ավարտի ամբողջական արժեքները:", + "timestamp_mode_cycle_required": "Խնդրում ենք ընտրել աղբյուրի ցիկլը ժամանակի դրոշման ռեժիմն օգտագործելիս:", + "no_phase_ranges": "Այդ գործողության համար փուլային տիրույթներ դեռ հասանելի չեն:", + "feedback_notification_title": "WashData. Ստուգեք ցիկլը ({device})", + "feedback_notification_message": "**Սարք**՝ {device}\n**Ծրագիր**՝ {program} ({confidence}% վստահություն)\n**Ժամանակը**՝ {time}\n\nWashData-ն ձեր օգնության կարիքն ունի այս հայտնաբերված ցիկլը ստուգելու համար:\n\nԽնդրում ենք գնալ **Կարգավորումներ > Սարքեր և ծառայություններ > WashData > Կարգավորել > Ուսուցման հետադարձ կապ**՝ այս արդյունքը հաստատելու կամ ուղղելու համար:", + "suggestions_ready_notification_title": "WashData. Առաջարկվող կարգավորումները պատրաստ են ({device})", + "suggestions_ready_notification_message": "**Առաջարկվող կարգավորումներ** սենսորն այժմ հաղորդում է **{count}** գործող առաջարկներ:\n\nԴրանք վերանայելու և կիրառելու համար՝ **Կարգավորումներ > Սարքեր և ծառայություններ > WashData > Կարգավորել > Ընդլայնված կարգավորումներ > Կիրառել առաջարկվող արժեքները**:\n\nԱռաջարկությունները կամընտիր են և ցուցադրվում են վերանայման համար՝ նախքան պահելը:", + "trim_range_invalid": "Սկիզբը պետք է լինի մինչև վերջ: Խնդրում ենք հարմարեցնել ձեր կտրման կետերը:", + "trim_failed": "Չհաջողվեց կտրել: Ցիկլը կարող է այլևս գոյություն չունենա կամ պատուհանը դատարկ է:", + "invalid_split_timestamp": "Ժամային նշումը անվավեր է կամ ցիկլի պատուհանից դուրս է։ Օգտագործեք HH:MM կամ HH:MM:SS՝ ցիկլի պատուհանի սահմաններում։", + "no_split_segments_found": "Այս ժամային նշումներից վավեր հատվածներ ստանալ չհաջողվեց։ Համոզվեք, որ յուրաքանչյուր ժամային նշում ցիկլի պատուհանի ներսում է, և հատվածները առնվազն 1 րոպե են։", + "auto_tune_suggestion": "{device_type} {device_title}-ը հայտնաբերել է ուրվականների ցիկլեր: Առաջարկվող նվազագույն հզորության փոփոխություն՝ {current_min}W -> {new_min}W (ինքնաբերաբար չի կիրառվում):", + "auto_tune_title": "WashData ավտոմատ կարգավորում", + "auto_tune_fallback": "{device_type} {device_title}-ը հայտնաբերել է ուրվականների ցիկլեր: Առաջարկվող նվազագույն հզորության փոփոխություն՝ {current_min}W -> {new_min}W (ինքնաբերաբար չի կիրառվում):" + }, + "abort": { + "no_cycles_found": "Ցիկլեր չեն գտնվել", + "no_split_segments_found": "Ընտրված ցիկլում բաժանելի հատվածներ չեն գտնվել։", + "cycle_not_found": "Ընտրված ցիկլը հնարավոր չեղավ բեռնել։", + "no_power_data": "Ընտրված ցիկլի համար հոսանքի հետքի տվյալներ չկան:", + "no_profiles_found": "Պրոֆիլներ չեն գտնվել: Նախ ստեղծեք պրոֆիլ:", + "no_profiles_for_matching": "Համապատասխանող պրոֆիլներ չկան: Նախ ստեղծեք պրոֆիլներ:", + "no_unlabeled_cycles": "Բոլոր ցիկլերն արդեն պիտակավորված են", + "no_suggestions": "Առաջարկվող արժեքներ դեռ չկան: Գործարկեք մի քանի ցիկլ և նորից փորձեք:", + "no_predictions": "Կանխատեսումներ չկան", + "no_custom_phases": "Հատուկ փուլեր չեն գտնվել:", + "reprocess_success": "{count} ցիկլերը հաջողությամբ վերամշակվեցին:", + "debug_data_cleared": "{count} ցիկլերից վրիպազերծման տվյալները հաջողությամբ մաքրվեցին:" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Ցիկլի սկիզբ", + "cycle_finish": "Ցիկլերի ավարտ", + "cycle_live": "Live Progress" + } + }, + "common_text": { + "options": { + "unlabeled": "(Չպիտակավորված)", + "create_new_profile": "Ստեղծեք նոր պրոֆիլ", + "remove_label": "Հեռացնել պիտակը", + "no_reference_cycle": "(Ոչ մի հղման ցիկլ)", + "all_device_types": "Սարքի բոլոր տեսակները", + "current_suffix": "(ընթացիկ)", + "editor_select_info": "Ընտրեք 1 ցիկլ՝ բաժանելու համար, կամ 2+ ցիկլ՝ միաձուլվելու համար:", + "editor_select_info_split": "Բաժանելու համար ընտրեք 1 ցիկլ։", + "editor_select_info_merge": "Միավորելու համար ընտրեք 2+ ցիկլ։", + "editor_select_info_delete": "Ջնջելու համար ընտրեք 1+ ցիկլ։", + "no_cycles_recorded": "Դեռևս ցիկլեր չեն գրանցվել:", + "profile_summary_line": "- **{name}**. {count} ցիկլեր, միջինը {avg}մ", + "no_profiles_created": "Դեռևս ստեղծված պրոֆիլներ չկան:", + "phase_preview": "Փուլի նախադիտում", + "phase_preview_no_curve": "Պրոֆիլի միջին կորը դեռ հասանելի չէ: Գործարկել/պիտակավորել ավելի շատ ցիկլեր այս պրոֆիլի համար:", + "average_power_curve": "Միջին հզորության կորը", + "unit_min": "ր", + "profile_option_fmt": "{name} ({count} ցիկլ, միջինը ~{duration}մ)", + "profile_option_short_fmt": "{name} ({count} ցիկլ, ~{duration}մ)", + "deleted_cycles_from_profile": "Ջնջվել է {count} ցիկլ {profile}-ից:", + "not_enough_data_graph": "Գրաֆիկ ստեղծելու համար բավարար տվյալներ չկան:", + "no_profiles_found": "Պրոֆիլներ չեն գտնվել:", + "cycle_info_fmt": "Ցիկլ՝ {start}, {duration}մ, ընթացիկ՝ {label}", + "top_candidates_header": "### Լավագույն թեկնածուներ", + "tbl_profile": "Անձնագիր", + "tbl_confidence": "Վստահություն", + "tbl_correlation": "Հարաբերակցություն", + "tbl_duration_match": "Տևողությունը համընկնում", + "detected_profile": "Հայտնաբերված պրոֆիլ", + "estimated_duration": "Մոտավոր տեւողությունը", + "actual_duration": "Փաստացի տևողությունը", + "choose_action": "__Ընտրեք ստորև նշված գործողությունը՝__", + "tbl_count": "հաշվել", + "tbl_avg": "Միջին", + "tbl_min": "Min", + "tbl_max": "Մաքս", + "tbl_energy_avg": "Էներգիա (միջին)", + "tbl_energy_total": "Էներգիա (ընդհանուր)", + "tbl_consistency": "Կազմել.", + "tbl_last_run": "Վերջին վազքը", + "graph_legend_title": "Գրաֆիկի լեգենդ", + "graph_legend_body": "Կապույտ գոտին ներկայացնում է նվազագույն և առավելագույն էներգիայի ընդունման տիրույթը: Գիծը ցույց է տալիս միջին հզորության կորը:", + "recording_preview": "Ձայնագրման նախադիտում", + "trim_start": "Կտրման մեկնարկ", + "trim_end": "Կտրել վերջը", + "split_preview_title": "Բաժանման նախադիտում", + "split_preview_found_fmt": "Գտնվել է {count} հատված:", + "split_preview_confirm_fmt": "Սեղմեք Հաստատել՝ այս ցիկլը {count} առանձին ցիկլերի բաժանելու համար:", + "merge_preview_title": "Միաձուլման նախադիտում", + "merge_preview_joining_fmt": "{count} ցիկլերի միացում: Բացերը կլրացվեն 0W ցուցումներով:", + "editor_delete_preview_title": "Ջնջման նախադիտում", + "editor_delete_preview_intro": "Ընտրված ցիկլերը մշտապես կջնջվեն՝", + "editor_delete_preview_confirm": "Սեղմեք «Հաստատել»՝ այս ցիկլի գրառումները մշտապես ջնջելու համար։", + "no_power_preview": "*Նախադիտման համար հոսանքի տվյալներ չկան։*", + "profile_comparison": "Պրոֆիլների համեմատություն", + "actual_cycle_label": "Այս ցիկլը (իրական)", + "storage_usage_header": "Պահպանման օգտագործումը", + "storage_file_size": "Ֆայլի չափ", + "storage_cycles": "Ցիկլեր", + "storage_profiles": "Պրոֆիլներ", + "storage_debug_traces": "Վրիպազերծման հետքեր", + "overview_suffix": "Ընդհանուր ակնարկ", + "phase_preview_for_profile": "Փուլային նախադիտում պրոֆիլի համար", + "current_ranges_header": "Ընթացիկ միջակայքերը", + "assign_phases_help": "Օգտագործեք ստորև նշված գործողությունները՝ միջակայքերը ավելացնելու, խմբագրելու, ջնջելու կամ պահպանելու համար:", + "profile_summary_header": "Անձնագրի ամփոփում", + "recent_cycles_header": "Վերջին ցիկլերը", + "trim_cycle_preview_title": "Ցիկլի կտրվածքի նախադիտում", + "trim_cycle_preview_no_data": "Էլեկտրաէներգիայի վերաբերյալ տվյալներ չկան այս ցիկլի համար:", + "trim_cycle_preview_kept_suffix": "պահված", + "table_program": "Ծրագիր", + "table_when": "Երբ", + "table_length": "Տևողություն", + "table_match": "Համընկնում", + "table_profile": "Անձնագիր", + "table_cycles": "Ցիկլեր", + "table_avg_length": "Միջ. տևողություն", + "table_last_run": "Վերջին վազքը", + "table_avg_energy": "Միջ. էներգիա", + "table_detected_program": "Հայտնաբերված ծրագիր", + "table_confidence": "Վստահություն", + "table_reported": "Հաղորդված", + "phase_builtin_suffix": "(Ներկառուցված)", + "phase_none_available": "Հասանելի փուլեր չկան:", + "settings_deprecation_warning": "⚠️ **Սարքի հնացած տեսակ.** {device_type}-ը նախատեսվում է հեռացնել ապագա թողարկումում: WashData-ի համապատասխան խողովակաշարը հուսալի արդյունքներ չի տալիս սարքերի այս դասի համար: Ձեր ինտեգրումը շարունակում է աշխատել հնացած ժամանակահատվածի ընթացքում. Այս նախազգուշացումը լռեցնելու համար, ներքևում անցեք **Սարքի տեսակը** աջակցվող տեսակներից մեկի (Լվացքի մեքենա, չորանոց, լվացքի մեքենա-չորացնող սարք, աման լվացող մեքենա, օդափոխիչ, հաց պատրաստող կամ պոմպ) կամ **Այլ (Ընդլայնված)**, եթե ձեր սարքը չի համապատասխանում աջակցվող տեսակներից որևէ մեկին: **Այլ (Ընդլայնված)** առաքում է միտումնավոր ընդհանուր լռելյայններ, որոնք հարմարեցված չեն որևէ հատուկ սարքի համար, այնպես որ դուք պետք է ինքներդ կարգավորեք շեմերը, ժամանակի ընդհատումները և համապատասխանող պարամետրերը. Ձեր բոլոր առկա կարգավորումները պահպանվում են, երբ փոխարկեք:", + "phase_other_device_types": "Սարքի այլ տեսակներ." + } + }, + "interactive_editor_action": { + "options": { + "split": "Բաժանել ցիկլը (գտնել բացերը)", + "merge": "Միաձուլման ցիկլեր (միացնել հատվածները)", + "delete": "Ջնջել ցիկլ(ները)" + } + }, + "split_mode": { + "options": { + "auto": "Ավտոմատ հայտնաբերել դադարի բացերը", + "manual": "Ձեռքով ժամային նշում(ներ)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Սպասարկում. Վերամշակել և օպտիմիզացնել տվյալները", + "clear_debug_data": "Մաքրել վրիպազերծման տվյալները (տեղ ազատել)", + "wipe_history": "Ջնջել այս սարքի ԲՈԼՈՐ տվյալները (անշրջելի)", + "export_import": "Արտահանել/ներմուծել JSON կարգավորումներով (պատճենել/տեղադրել)" + } + }, + "export_import_mode": { + "options": { + "export": "Արտահանել բոլոր տվյալները", + "import": "Տվյալների ներմուծում/միաձուլում" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Հին ցիկլերի ավտոմատ պիտակավորում", + "select_cycle_to_label": "Պիտակի հատուկ ցիկլ", + "select_cycle_to_delete": "Ջնջել ցիկլը", + "interactive_editor": "Միաձուլել / բաժանել ինտերակտիվ խմբագիր", + "trim_cycle": "Կտրման ցիկլի տվյալները" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Ստեղծեք նոր պրոֆիլ", + "edit_profile": "Խմբագրել/վերանվանել պրոֆիլը", + "delete_profile": "Ջնջել պրոֆիլը", + "profile_stats": "Պրոֆիլների վիճակագրություն", + "cleanup_profile": "Մաքրել պատմությունը - գրաֆիկ և ջնջել", + "assign_phases": "Նշանակել փուլերի միջակայքերը" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Ստեղծեք նոր փուլ", + "edit_custom_phase": "Խմբագրել փուլ", + "delete_custom_phase": "Ջնջել փուլը" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Ավելացնել փուլային տիրույթ", + "edit_range": "Խմբագրել փուլերի տիրույթը", + "delete_range": "Ջնջել փուլային տիրույթը", + "clear_ranges": "Մաքրել բոլոր տիրույթները", + "auto_detect_ranges": "Ավտոմատ հայտնաբերման փուլեր", + "save_ranges": "Պահպանել և վերադարձնել" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Թարմացնել կարգավիճակը", + "stop_recording": "Դադարեցնել ձայնագրումը (Պահպանել և մշակել)", + "start_recording": "Սկսեք նոր ձայնագրությունը", + "process_recording": "Ընթացքի վերջին ձայնագրությունը (կտրում և պահպանում)", + "discard_recording": "Վերացնել վերջին ձայնագրությունը" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Ստեղծեք նոր պրոֆիլ", + "existing_profile": "Ավելացնել գոյություն ունեցող պրոֆիլին", + "discard": "Հեռացնել ձայնագրությունը" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Հաստատել - ճիշտ հայտնաբերում", + "correct": "Ճիշտ - Ընտրեք Ծրագիր/Տևողություն", + "ignore": "Անտեսել - Կեղծ դրական/աղմկոտ ցիկլ", + "delete": "Ջնջել - Հեռացնել պատմությունից" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Անվանեք և կիրառեք փուլերը", + "cancel": "Վերադարձեք առանց փոփոխությունների" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Սահմանեք մեկնարկային կետը", + "set_end": "Սահմանեք վերջնակետը", + "reset": "Վերականգնել ամբողջական տևողության", + "apply": "Կիրառել Trim-ը", + "cancel": "Չեղարկել" + } + }, + "device_type": { + "options": { + "washing_machine": "Լվացքի մեքենա", + "dryer": "Չորանոց", + "washer_dryer": "Լվացքի-չորանոց կոմբինատ", + "dishwasher": "Սպասք լվացող մեքենա", + "coffee_machine": "Սուրճի մեքենա (հնացած)", + "ev": "Էլեկտրական մեքենա (հնացած)", + "air_fryer": "Օդային Fryer", + "heat_pump": "Ջերմային պոմպ (հնացած)", + "bread_maker": "Հաց պատրաստող", + "pump": "Pump / Sump Pump", + "oven": "Վառարան (հնացած)", + "other": "Այլ (Ընդլայնված)" + } + } + }, + "services": { + "label_cycle": { + "name": "Պիտակի ցիկլ", + "description": "Վերագրեք գոյություն ունեցող պրոֆիլը անցյալ շրջանին կամ հեռացրեք պիտակը:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը պիտակավորելու համար:" + }, + "cycle_id": { + "name": "Ցիկլի ID", + "description": "Պիտակի համար նախատեսված ցիկլի ID-ն:" + }, + "profile_name": { + "name": "Պրոֆիլի անվանումը", + "description": "Գոյություն ունեցող պրոֆիլի անվանումը (ստեղծեք պրոֆիլներ Կառավարեք պրոֆիլների ընտրացանկից): Պիտակը հեռացնելու համար թողեք դատարկ:" + } + } + }, + "create_profile": { + "name": "Ստեղծել պրոֆիլ", + "description": "Ստեղծեք նոր պրոֆիլ (ինքնուրույն կամ հղման ցիկլի հիման վրա):", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը." + }, + "profile_name": { + "name": "Պրոֆիլի անվանումը", + "description": "Անունը նոր պրոֆիլի համար (օրինակ՝ «Ծանր պարտականություն», «Նուրբ»):" + }, + "reference_cycle_id": { + "name": "Հղման ցիկլի ID", + "description": "Ընտրովի ցիկլի ID՝ այս պրոֆիլը հիմնելու համար:" + } + } + }, + "delete_profile": { + "name": "Ջնջել պրոֆիլը", + "description": "Ջնջել պրոֆիլը և կամայականորեն հեռացնել ցիկլերը դրա միջոցով:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը." + }, + "profile_name": { + "name": "Պրոֆիլի անվանումը", + "description": "Ջնջման պրոֆիլը:" + }, + "unlabel_cycles": { + "name": "Unlabel Cycles", + "description": "Հեռացրեք պրոֆիլի պիտակը այս պրոֆիլի օգտագործմամբ ցիկլերից:" + } + } + }, + "auto_label_cycles": { + "name": "Հին ցիկլերի ավտոմատ պիտակավորում", + "description": "Հետադարձ կերպով պիտակավորեք չպիտակավորված ցիկլերը՝ օգտագործելով պրոֆիլների համապատասխանությունը:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը." + }, + "confidence_threshold": { + "name": "Վստահության շեմ", + "description": "Համապատասխանության նվազագույն վստահություն (0,50-0,95) պիտակներ կիրառելու համար:" + } + } + }, + "export_config": { + "name": "Արտահանել Config", + "description": "Արտահանեք այս սարքի պրոֆիլները, ցիկլերը և կարգավորումները JSON ֆայլ (յուրաքանչյուր սարքի համար):", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը արտահանման համար." + }, + "path": { + "name": "Ճանապարհ", + "description": "Գրելու համար ընտրովի բացարձակ ֆայլի ուղի (կանխադրված է /config/ha_washdata_export_{entry}.json):" + } + } + }, + "import_config": { + "name": "Ներմուծման կոնֆիգուրացիա", + "description": "Ներմուծեք այս սարքի պրոֆիլները, ցիկլերը և կարգավորումները JSON արտահանման ֆայլից (կարգավորումները կարող են վերագրանցվել):", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը ներմուծման համար." + }, + "path": { + "name": "Ճանապարհ", + "description": "Արտահանման JSON ֆայլի բացարձակ ճանապարհ՝ ներմուծման համար:" + } + } + }, + "submit_cycle_feedback": { + "name": "Ներկայացրեք ցիկլի հետադարձ կապ", + "description": "Ավարտված ցիկլից հետո հաստատեք կամ ուղղեք ավտոմատ հայտնաբերված ծրագիրը: Տրամադրեք «entry_id» (ընդլայնված) կամ «device_id» (խորհուրդ է տրվում):", + "fields": { + "device_id": { + "name": "Սարք", + "description": "WashData սարքը (խորհուրդ է տրվում entry_id-ի փոխարեն):" + }, + "entry_id": { + "name": "Մուտքի ID", + "description": "Սարքի կոնֆիգուրացիայի մուտքագրման ID-ն (device_id-ի այլընտրանք):" + }, + "cycle_id": { + "name": "Ցիկլի ID", + "description": "Հետադարձ կապի ծանուցման / մատյաններում ցուցադրված ցիկլի ID-ն:" + }, + "user_confirmed": { + "name": "Հաստատեք հայտնաբերված ծրագիրը", + "description": "Սահմանեք true, եթե հայտնաբերված ծրագիրը ճիշտ է:" + }, + "corrected_profile": { + "name": "Ուղղված պրոֆիլ", + "description": "Եթե ​​հաստատված չէ, տրամադրեք ճիշտ պրոֆիլի/ծրագրի անունը:" + }, + "corrected_duration": { + "name": "Ուղղված տևողություն (վայրկյաններ)", + "description": "Ընտրովի շտկված տևողությունը վայրկյաններով:" + }, + "notes": { + "name": "Նշումներ", + "description": "Ընտրովի նշումներ այս ցիկլի մասին:" + } + } + }, + "record_start": { + "name": "Գրանցման ցիկլի սկիզբ", + "description": "Սկսեք ձեռքով ձայնագրել մաքուր ցիկլը (շրջանցում է բոլոր համապատասխան տրամաբանությունը):", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը ձայնագրելու համար." + } + } + }, + "record_stop": { + "name": "Record Cycle Stop", + "description": "Դադարեցրեք ձեռքով ձայնագրումը:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "Լվացքի մեքենայի սարքը՝ ձայնագրումը դադարեցնելու համար։" + } + } + }, + "trim_cycle": { + "name": "Կտրման ցիկլ", + "description": "Կտրեք անցյալ ցիկլի էներգիայի տվյալները որոշակի ժամանակային պատուհանի վրա:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "WashData սարքը:" + }, + "cycle_id": { + "name": "Ցիկլի ID", + "description": "Կտրելու ցիկլի ID-ն:" + }, + "trim_start_s": { + "name": "Կտրել սկիզբ (վայրկյան)", + "description": "Պահպանեք տվյալներն այս օֆսեթից վայրկյանների ընթացքում: Կանխադրված 0:" + }, + "trim_end_s": { + "name": "Կտրել վերջը (վայրկյան)", + "description": "Պահպանեք տվյալները մինչև այս օֆսեթը վայրկյանների ընթացքում: Կանխադրված = լրիվ տևողություն:" + } + } + }, + "pause_cycle": { + "name": "Դադար ցիկլ", + "description": "Դադարեցնել WashData սարքի ակտիվ ցիկլը:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "WashData սարքը դադարեցնելու համար:" + } + } + }, + "resume_cycle": { + "name": "Վերսկսման ցիկլ", + "description": "Վերսկսեք դադարեցված ցիկլը WashData սարքի համար:", + "fields": { + "device_id": { + "name": "Սարք", + "description": "WashData սարքը վերսկսվում է:" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Վազում" + }, + "match_ambiguity": { + "name": "Համապատասխանության երկիմաստություն" + } + }, + "sensor": { + "washer_state": { + "name": "Պետություն", + "state": { + "off": "Անջատված", + "idle": "Պարապ", + "starting": "Սկսած", + "running": "Վազում", + "paused": "Դադարեցված է", + "user_paused": "Կասեցվել է օգտատիրոջ կողմից", + "ending": "Ավարտ", + "finished": "Ավարտված", + "anti_wrinkle": "Հակակնճիռների դեմ", + "delay_wait": "Սպասում է մեկնարկին", + "interrupted": "Ընդհատվել է", + "force_stopped": "Ուժը դադարեցրեց", + "rinse": "Ողողել", + "unknown": "Անհայտ", + "clean": "Մաքուր" + } + }, + "washer_program": { + "name": "Ծրագիր" + }, + "time_remaining": { + "name": "Մնացած ժամանակը" + }, + "total_duration": { + "name": "Ընդհանուր տևողությունը" + }, + "cycle_progress": { + "name": "Առաջընթաց" + }, + "current_power": { + "name": "Ընթացիկ հզորություն" + }, + "elapsed_time": { + "name": "Անցած ժամանակ" + }, + "debug_info": { + "name": "Վրիպազերծման տեղեկատվությունը" + }, + "match_confidence": { + "name": "Համապատասխանության վստահություն" + }, + "top_candidates": { + "name": "Լավագույն թեկնածուներ", + "state": { + "none": "Ոչ մեկը" + } + }, + "current_phase": { + "name": "Ընթացիկ փուլ" + }, + "wash_phase": { + "name": "Փուլ" + }, + "profile_cycle_count": { + "name": "Անձնագիր {profile_name} Քանակ", + "unit_of_measurement": "ցիկլեր" + }, + "suggestions": { + "name": "Առաջարկվող կարգավորումներ" + }, + "pump_runs_today": { + "name": "Պոմպի աշխատանքը (վերջին 24 ժամը)" + }, + "cycle_count": { + "name": "Ցիկլերի հաշվարկ" + } + }, + "select": { + "program_select": { + "name": "Ցիկլային ծրագիր", + "state": { + "auto_detect": "Ավտոմատ հայտնաբերում" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Ուժի ավարտի ցիկլը" + }, + "pause_cycle": { + "name": "Դադար ցիկլ" + }, + "resume_cycle": { + "name": "Վերսկսման ցիկլ" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Պահանջվում է վավեր device_id:" + }, + "cycle_id_required": { + "message": "Պահանջվում է վավեր cycle_id:" + }, + "profile_name_required": { + "message": "Պահանջվում է վավեր profile_name:" + }, + "device_not_found": { + "message": "Սարքը չի գտնվել:" + }, + "no_config_entry": { + "message": "Սարքի կոնֆիգուրացիայի մուտքագրում չի գտնվել:" + }, + "integration_not_loaded": { + "message": "Ինտեգրումը բեռնված չէ այս սարքի համար:" + }, + "cycle_not_found_or_no_power": { + "message": "Ցիկլը չի ​​գտնվել կամ չունի էներգիայի տվյալներ:" + }, + "trim_failed_empty_window": { + "message": "Չհաջողվեց կրճատել. ցիկլը չի ​​գտնվել, հոսանքի տվյալներ չկան, կամ արդյունքում պատուհանը դատարկ է:" + }, + "trim_invalid_range": { + "message": "trim_end_s-ը պետք է մեծ լինի trim_start_s-ից:" + } + } +} diff --git a/custom_components/ha_washdata/translations/id.json b/custom_components/ha_washdata/translations/id.json new file mode 100644 index 0000000..c09d36d --- /dev/null +++ b/custom_components/ha_washdata/translations/id.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Pengaturan WashData", + "description": "Konfigurasikan mesin cuci atau peralatan lainnya.\n\nSensor daya diperlukan.\n\n**Selanjutnya, Anda akan ditanya apakah Anda ingin membuat profil pertama Anda.**", + "data": { + "name": "Nama Perangkat", + "device_type": "Jenis Perangkat", + "power_sensor": "Sensor Daya", + "min_power": "Ambang Batas Daya Minimum (W)" + }, + "data_description": { + "name": "Nama yang sesuai untuk perangkat ini (misalnya, 'Mesin Cuci', 'Mesin Cuci Piring').", + "device_type": "Jenis alat apakah ini? Membantu menyesuaikan deteksi dan pelabelan.", + "power_sensor": "Entitas sensor yang melaporkan konsumsi daya real-time (dalam watt) dari smart plug Anda.", + "min_power": "Pembacaan daya di atas ambang batas ini (dalam watt) menunjukkan alat sedang bekerja. Mulailah dengan 2W untuk sebagian besar perangkat." + } + }, + "first_profile": { + "title": "Buat Profil Pertama", + "description": "**Siklus** adalah pengoperasian peralatan Anda secara menyeluruh (misalnya, cucian). **Profil** mengelompokkan siklus ini berdasarkan jenisnya (misalnya, 'Kapas', 'Cuci Cepat'). Membuat profil sekarang membantu integrasi memperkirakan durasi dengan segera.\n\nAnda dapat memilih profil ini secara manual di kontrol perangkat jika tidak terdeteksi secara otomatis.\n\n**Biarkan 'Nama Profil' kosong untuk melewati langkah ini.**", + "data": { + "profile_name": "Nama Profil (Opsional)", + "manual_duration": "Perkiraan Durasi (menit)" + } + } + }, + "error": { + "cannot_connect": "Gagal terhubung", + "invalid_auth": "Otentikasi tidak valid", + "unknown": "Kesalahan yang tidak terduga" + }, + "abort": { + "already_configured": "Perangkat sudah dikonfigurasi" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Tinjau Umpan Balik Pembelajaran", + "settings": "Pengaturan", + "notifications": "Pemberitahuan", + "manage_cycles": "Kelola Siklus", + "manage_profiles": "Kelola Profil", + "manage_phase_catalog": "Kelola Katalog Fase", + "record_cycle": "Siklus Rekam (Manual)", + "diagnostics": "Diagnostik & Pemeliharaan" + } + }, + "apply_suggestions": { + "title": "Salin Nilai yang Disarankan", + "description": "Ini akan menyalin nilai yang disarankan saat ini ke dalam Pengaturan yang Anda simpan. Ini adalah tindakan satu kali dan tidak mengaktifkan penimpaan otomatis.\n\nNilai yang disarankan untuk disalin:\n{suggested}", + "data": { + "confirm": "Konfirmasikan tindakan penyalinan" + } + }, + "apply_suggestions_confirm": { + "title": "Tinjau Perubahan yang Disarankan", + "description": "WashData menemukan **{pending_count}** nilai yang disarankan untuk diterapkan.\n\nPerubahan:\n{changes}\n\n✅ Centang kotak di bawah dan klik **Kirim** untuk segera menerapkan dan menyimpan perubahan ini.\n❌ Biarkan tidak dicentang dan klik **Kirim** untuk membatalkan dan kembali.", + "data": { + "confirm_apply_suggestions": "Terapkan dan simpan perubahan ini" + } + }, + "settings": { + "title": "Pengaturan", + "description": "{deprecation_warning}Sesuaikan ambang deteksi, pembelajaran, dan perilaku tingkat lanjut. Default berfungsi dengan baik untuk sebagian besar pengaturan.\n\nPengaturan yang disarankan saat ini tersedia: {suggestions_count}.\nJika angka ini di atas 0, buka Pengaturan Lanjutan dan gunakan 'Terapkan Nilai yang Disarankan' untuk meninjau rekomendasi.", + "data": { + "apply_suggestions": "Terapkan Nilai yang Disarankan", + "device_type": "Jenis Perangkat", + "power_sensor": "Entitas Sensor Daya", + "min_power": "Daya Minimal (W)", + "off_delay": "Penundaan Akhir Siklus", + "notify_actions": "Tindakan Pemberitahuan", + "notify_people": "Tunda Pengiriman Sampai Orang-Orang Ini Pulang", + "notify_only_when_home": "Tunda Pemberitahuan Hingga Orang Terpilih Ada di Rumah", + "notify_fire_events": "Aktifkan Acara Otomasi", + "notify_start_services": "Siklus Mulai - Target Pemberitahuan", + "notify_finish_services": "Siklus Selesai - Target Pemberitahuan", + "notify_live_services": "Kemajuan Langsung - Target Pemberitahuan", + "notify_before_end_minutes": "Pemberitahuan Pra-penyelesaian (menit sebelum akhir)", + "notify_title": "Judul Pemberitahuan", + "notify_icon": "Ikon Pemberitahuan", + "notify_start_message": "Mulai Format Pesan", + "notify_finish_message": "Selesaikan Format Pesan", + "notify_pre_complete_message": "Format Pesan Pra-Penyelesaian", + "show_advanced": "Edit Pengaturan Lanjutan (Langkah Berikutnya)" + }, + "data_description": { + "apply_suggestions": "Centang kotak ini untuk menyalin nilai yang disarankan oleh algoritma pembelajaran ke dalam formulir. Tinjau sebelum menyimpan.\n\nDisarankan (dari pembelajaran):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Pilih jenis peralatan (Mesin Cuci, Pengering, Mesin Pencuci Piring, Mesin Kopi, EV). Tag ini disimpan pada setiap siklus untuk pengorganisasian yang lebih baik.", + "power_sensor": "Entitas sensor melaporkan daya secara real-time (dalam watt). Anda dapat mengubahnya kapan saja tanpa kehilangan data historis-berguna jika Anda mengganti smart plug.", + "min_power": "Pembacaan daya di atas ambang batas ini (dalam watt) menunjukkan alat sedang bekerja. Mulailah dengan 2W untuk sebagian besar perangkat.", + "off_delay": "Beberapa detik tenaga yang dihaluskan harus tetap berada di bawah ambang batas berhenti untuk menandai selesainya. Standar: 120 detik.", + "notify_actions": "Urutan tindakan Asisten Rumah opsional untuk dijalankan untuk notifikasi (skrip, beri tahu, TTS, dll.). Jika disetel, tindakan akan dijalankan sebelum layanan pemberitahuan fallback.", + "notify_people": "Entitas orang yang digunakan untuk gerbang kehadiran. Jika diaktifkan, pengiriman notifikasi akan tertunda hingga setidaknya satu orang yang dipilih ada di rumah.", + "notify_only_when_home": "Jika diaktifkan, tunda pemberitahuan hingga setidaknya satu orang yang dipilih ada di rumah.", + "notify_fire_events": "Jika diaktifkan, aktifkan peristiwa Home Assistant untuk memulai dan mengakhiri siklus (ha_washdata_cycle_started dan ha_washdata_cycle_ended) untuk mendorong otomatisasi.", + "notify_start_services": "Satu atau lebih layanan pemberitahuan untuk memperingatkan ketika siklus dimulai. Biarkan kosong untuk melewati pemberitahuan awal.", + "notify_finish_services": "Satu atau lebih layanan pemberitahuan untuk memperingatkan ketika suatu siklus selesai atau hampir selesai. Biarkan kosong untuk melewati pemberitahuan selesai.", + "notify_live_services": "Satu atau lebih layanan pemberitahuan untuk menerima pembaruan kemajuan langsung saat siklus berjalan (hanya aplikasi pendamping seluler). Biarkan kosong untuk melewati pembaruan langsung.", + "notify_before_end_minutes": "Beberapa menit sebelum perkiraan akhir siklus untuk memicu notifikasi. Setel ke 0 untuk menonaktifkan. Bawaan: 0.", + "notify_title": "Judul khusus untuk notifikasi. Mendukung {device} pengganti.", + "notify_icon": "Ikon yang digunakan untuk notifikasi (misalnya mdi:mesin cuci). Biarkan kosong untuk mengirim tanpa ikon.", + "notify_start_message": "Pesan dikirim ketika siklus dimulai. Mendukung {device} pengganti.", + "notify_finish_message": "Pesan dikirim ketika siklus selesai. Mendukung placeholder {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Teks pembaruan kemajuan langsung yang berulang saat siklus berjalan. Mendukung placeholder {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Pemberitahuan", + "description": "Konfigurasikan saluran notifikasi, templat, dan pembaruan kemajuan seluler opsional secara langsung.", + "data": { + "notify_actions": "Tindakan Pemberitahuan", + "notify_people": "Tunda Pengiriman Sampai Orang-Orang Ini Pulang", + "notify_only_when_home": "Tunda Pemberitahuan Hingga Orang Terpilih Ada di Rumah", + "notify_fire_events": "Aktifkan Acara Otomasi", + "notify_start_services": "Siklus Mulai - Target Pemberitahuan", + "notify_finish_services": "Siklus Selesai - Target Pemberitahuan", + "notify_live_services": "Kemajuan Langsung - Target Pemberitahuan", + "notify_before_end_minutes": "Pemberitahuan Pra-penyelesaian (menit sebelum akhir)", + "notify_live_interval_seconds": "Interval Pembaruan Langsung (detik)", + "notify_live_overrun_percent": "Tunjangan Kelebihan Pembaruan Langsung (%)", + "notify_live_chronometer": "Penghitung Waktu Mundur Kronometer", + "notify_title": "Judul Pemberitahuan", + "notify_icon": "Ikon Pemberitahuan", + "notify_start_message": "Mulai Format Pesan", + "notify_finish_message": "Selesaikan Format Pesan", + "notify_pre_complete_message": "Format Pesan Pra-Penyelesaian", + "energy_price_entity": "Harga Energi - Entitas (opsional)", + "energy_price_static": "Harga Energi - Nilai statis (opsional)", + "go_back": "Kembali tanpa menyimpan", + "notify_reminder_message": "Format Pesan Pengingat", + "notify_timeout_seconds": "Tutup otomatis Setelah (detik)", + "notify_channel": "Saluran Pemberitahuan (status/langsung/pengingat)", + "notify_finish_channel": "Saluran Notifikasi Selesai" + }, + "data_description": { + "go_back": "Centang ini lalu klik Kirim untuk membuang perubahan apa pun di atas dan kembali ke menu utama.", + "notify_actions": "Urutan tindakan Asisten Rumah opsional untuk dijalankan untuk notifikasi (skrip, beri tahu, TTS, dll.). Jika disetel, tindakan akan dijalankan sebelum layanan pemberitahuan fallback.", + "notify_people": "Entitas orang yang digunakan untuk gerbang kehadiran. Jika diaktifkan, pengiriman notifikasi akan tertunda hingga setidaknya satu orang yang dipilih ada di rumah.", + "notify_only_when_home": "Jika diaktifkan, tunda pemberitahuan hingga setidaknya satu orang yang dipilih ada di rumah.", + "notify_fire_events": "Jika diaktifkan, aktifkan peristiwa Home Assistant untuk memulai dan mengakhiri siklus (ha_washdata_cycle_started dan ha_washdata_cycle_ended) untuk mendorong otomatisasi.", + "notify_start_services": "Satu atau lebih layanan notifikasi untuk memberi peringatan ketika siklus dimulai (misalnya notify.mobile_app_my_phone). Biarkan kosong untuk melewati pemberitahuan awal.", + "notify_finish_services": "Satu atau lebih layanan pemberitahuan untuk memperingatkan ketika suatu siklus selesai atau hampir selesai. Biarkan kosong untuk melewati pemberitahuan selesai.", + "notify_live_services": "Satu atau lebih layanan pemberitahuan untuk menerima pembaruan kemajuan langsung saat siklus berjalan (hanya aplikasi pendamping seluler). Biarkan kosong untuk melewati pembaruan langsung.", + "notify_before_end_minutes": "Beberapa menit sebelum perkiraan akhir siklus untuk memicu notifikasi. Setel ke 0 untuk menonaktifkan. Bawaan: 0.", + "notify_live_interval_seconds": "Seberapa sering pembaruan kemajuan dikirim saat berjalan. Nilai yang lebih rendah lebih responsif tetapi menggunakan lebih banyak notifikasi. Minimum 30 detik diberlakukan - nilai di bawah 30 detik secara otomatis dibulatkan menjadi 30 detik.", + "notify_live_overrun_percent": "Margin keamanan di atas perkiraan jumlah pembaruan untuk siklus yang panjang/berlebihan (misalnya 20% memungkinkan 1,2x perkiraan pembaruan).", + "notify_live_chronometer": "Jika diaktifkan, setiap pembaruan langsung menyertakan hitungan mundur kronometer hingga perkiraan waktu selesai. Pengatur waktu notifikasi berdetak secara otomatis di perangkat di antara pembaruan (khusus aplikasi pendamping Android).", + "notify_title": "Judul khusus untuk notifikasi. Mendukung {device} pengganti.", + "notify_icon": "Ikon yang digunakan untuk notifikasi (misalnya mdi:mesin cuci). Biarkan kosong untuk mengirim tanpa ikon.", + "notify_start_message": "Pesan dikirim ketika siklus dimulai. Mendukung {device} pengganti.", + "notify_finish_message": "Pesan dikirim ketika siklus selesai. Mendukung placeholder {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Pilih entitas numerik yang menyimpan harga listrik saat ini (misalnya sensor.electricity_price, input_number.energy_rate). Jika disetel, aktifkan placeholder {cost}. Diutamakan daripada nilai statis jika keduanya dikonfigurasi.", + "energy_price_static": "Harga listrik tetap per kWh. Jika disetel, aktifkan placeholder {cost}. Diabaikan jika entitas dikonfigurasi di atas. Tambahkan simbol mata uang Anda di templat pesan, mis. {cost}€.", + "notify_reminder_message": "Teks pengingat satu kali mengirimkan jumlah menit yang dikonfigurasi sebelum perkiraan akhir. Berbeda dengan live update berulang di atas. Mendukung placeholder {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Secara otomatis menutup notifikasi setelah beberapa detik ini (diteruskan sebagai 'batas waktu' aplikasi pendamping). Setel ke 0 untuk menyimpannya hingga ditutup. Bawaan: 0.", + "notify_channel": "Android saluran notifikasi aplikasi pendamping untuk status, kemajuan langsung, dan notifikasi pengingat. Suara dan pentingnya saluran dikonfigurasikan di aplikasi pendamping saat pertama kali nama saluran digunakan - WashData hanya menetapkan nama saluran. Biarkan kosong untuk default aplikasi.", + "notify_finish_channel": "Pisahkan saluran Android untuk peringatan yang sudah selesai (juga digunakan oleh pengingat dan cerewet menunggu cucian) sehingga dapat mempunyai bunyi sendiri. Biarkan kosong untuk menggunakan kembali saluran di atas.", + "notify_pre_complete_message": "Teks pembaruan kemajuan langsung yang berulang saat siklus berjalan. Mendukung placeholder {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Pengaturan Lanjutan", + "description": "Sesuaikan ambang deteksi, pembelajaran, dan perilaku tingkat lanjut. Default berfungsi dengan baik untuk sebagian besar pengaturan.", + "sections": { + "suggestions_section": { + "name": "Pengaturan yang Disarankan", + "description": "Tersedia {suggestions_count} saran hasil pembelajaran. Centang 'Terapkan Nilai yang Disarankan' untuk meninjau perubahan yang diusulkan sebelum menyimpan.", + "data": { + "apply_suggestions": "Terapkan Nilai yang Disarankan" + }, + "data_description": { + "apply_suggestions": "Centang kotak ini untuk membuka langkah peninjauan nilai yang disarankan dari algoritma pembelajaran. Tidak ada yang disimpan secara otomatis.\n\nDisarankan (dari pembelajaran):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Deteksi & Ambang Daya", + "description": "Kapan perangkat dianggap MENYALA atau MATI, gerbang energi, dan waktu untuk transisi status.", + "data": { + "start_duration_threshold": "Mulai Durasi Debounce", + "start_energy_threshold": "Mulai Gerbang Energi (Wh)", + "completion_min_seconds": "Waktu Proses Minimum Penyelesaian (detik)", + "start_threshold_w": "Ambang Batas Mulai (W)", + "stop_threshold_w": "Hentikan Ambang Batas (W)", + "end_energy_threshold": "Gerbang Energi Akhir (Wh)", + "running_dead_zone": "Zona Mati Awal Siklus (detik)", + "end_repeat_count": "Akhir Pengulangan Hitungan", + "min_off_gap": "Kesenjangan Min Antar Siklus", + "sampling_interval": "Interval Pengambilan Sampel" + }, + "data_description": { + "start_duration_threshold": "Abaikan lonjakan daya singkat yang lebih pendek dari durasi ini (detik). Menyaring lonjakan booting (misalnya, 60W selama 2 detik) sebelum siklus sebenarnya dimulai. Bawaan: 5 detik.", + "start_energy_threshold": "Siklus harus mengumpulkan setidaknya energi (Wh) sebanyak ini selama fase START untuk dapat dikonfirmasi. Default: 0,005Wh.", + "completion_min_seconds": "Siklus yang lebih pendek dari ini akan ditandai sebagai 'terganggu' meskipun siklus tersebut berakhir secara alami. Bawaan: 600an.", + "start_threshold_w": "Daya harus naik DI ATAS ambang batas ini untuk MEMULAI sebuah siklus. Tetapkan lebih tinggi dari daya siaga Anda. Bawaan: min_power + 1 W.", + "stop_threshold_w": "Daya harus berada DI BAWAH ambang batas ini untuk MENGAKHIRI sebuah siklus. Tetapkan tepat di atas nol untuk menghindari kebisingan yang memicu tujuan yang salah. Bawaan: 0,6 * min_power.", + "end_energy_threshold": "Siklus berakhir hanya jika energi pada jendela Off-Delay terakhir berada di bawah nilai ini (Wh). Mencegah kesalahan jika daya berfluktuasi mendekati nol. Default: 0,05Wh.", + "running_dead_zone": "Setelah siklus dimulai, abaikan penurunan daya selama beberapa detik untuk mencegah deteksi ujung yang salah selama fase pemutaran awal. Setel ke 0 untuk menonaktifkan. Bawaan: 0 detik.", + "end_repeat_count": "Berapa kali kondisi akhir (daya di bawah ambang batas untuk off_delay) harus dipenuhi secara berurutan sebelum mengakhiri siklus. Nilai yang lebih tinggi mencegah tujuan yang salah selama jeda. Bawaan: 1.", + "min_off_gap": "Jika sebuah siklus dimulai dalam beberapa detik dari akhir siklus sebelumnya, siklus tersebut akan DIGABUNG menjadi satu siklus.", + "sampling_interval": "Interval pengambilan sampel minimum dalam hitungan detik. Pembaruan yang lebih cepat dari kecepatan ini akan diabaikan. Default: 30 detik (2 detik untuk mesin cuci, mesin cuci-pengering, dan mesin pencuci piring)." + } + }, + "matching_section": { + "name": "Pencocokan Profil & Pembelajaran", + "description": "Seberapa agresif WashData mencocokkan siklus yang sedang berjalan dengan profil yang dipelajari, dan kapan meminta umpan balik.", + "data": { + "profile_match_min_duration_ratio": "Rasio Durasi Min Pertandingan Profil (0,1-1,0)", + "profile_match_interval": "Interval Kecocokan Profil (detik)", + "profile_match_threshold": "Ambang Batas Kecocokan Profil", + "profile_unmatch_threshold": "Ambang Batas Ketidakcocokan Profil", + "auto_label_confidence": "Keyakinan Label Otomatis (0-1)", + "learning_confidence": "Keyakinan Permintaan Umpan Balik (0-1)", + "suppress_feedback_notifications": "Menekan Pemberitahuan Umpan Balik", + "duration_tolerance": "Toleransi Durasi (0-0,5)", + "smoothing_window": "Jendela Penghalusan (sampel)", + "profile_duration_tolerance": "Toleransi Durasi Pertandingan Profil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Rasio durasi minimum untuk pencocokan profil. Siklus berjalan setidaknya harus sesuai dengan durasi profil ini. Lebih rendah = pencocokan sebelumnya. Bawaan: 0,50 (50%).", + "profile_match_interval": "Seberapa sering (dalam detik) mencoba pencocokan profil selama satu siklus. Nilai yang lebih rendah memberikan deteksi lebih cepat tetapi menggunakan lebih banyak CPU. Default: 300 detik (5 menit).", + "profile_match_threshold": "Skor kesamaan DTW minimum (0,0–1,0) diperlukan untuk mempertimbangkan suatu profil sebagai kecocokan. Lebih tinggi = pencocokan lebih ketat, lebih sedikit positif palsu. Bawaan: 0,4.", + "profile_unmatch_threshold": "Skor kesamaan DTW (0,0–1,0) di bawahnya berarti profil yang cocok sebelumnya akan ditolak. Harus lebih rendah dari ambang kecocokan untuk mencegah kedipan. Bawaan: 0,35.", + "auto_label_confidence": "Secara otomatis memberi label pada siklus pada atau di atas keyakinan ini setelah selesai (0,0–1,0).", + "learning_confidence": "Keyakinan minimum untuk meminta verifikasi pengguna melalui suatu peristiwa (0,0–1,0).", + "suppress_feedback_notifications": "Saat diaktifkan, WashData akan tetap melacak dan meminta umpan balik secara internal tetapi tidak akan menampilkan pemberitahuan terus-menerus yang meminta Anda mengonfirmasi siklus. Berguna ketika siklus selalu terdeteksi dengan benar dan Anda merasa petunjuknya mengganggu.", + "duration_tolerance": "Toleransi untuk perkiraan sisa waktu selama lari (0,0–0,5 sama dengan 0–50%).", + "smoothing_window": "Jumlah pembacaan daya terkini yang digunakan untuk pemulusan rata-rata bergerak.", + "profile_duration_tolerance": "Toleransi yang digunakan oleh pencocokan profil untuk varian durasi total (0,0–0,5). Perilaku default: ±25%." + } + }, + "timing_section": { + "name": "Waktu, Pemeliharaan & Debug", + "description": "Watchdog, setel ulang progres, pemeliharaan otomatis, dan penayangan entitas debug.", + "data": { + "watchdog_interval": "Interval Pengawas (detik)", + "no_update_active_timeout": "Batas Waktu Tanpa Pembaruan", + "progress_reset_delay": "Penundaan Penyetelan Ulang Kemajuan (detik)", + "auto_maintenance": "Aktifkan Pemeliharaan Otomatis", + "expose_debug_entities": "Ekspos Entitas Debug", + "save_debug_traces": "Simpan Jejak Debug" + }, + "data_description": { + "watchdog_interval": "Detik antara pemeriksaan pengawas saat berlari. Bawaan: 5 detik. PERINGATAN: Pastikan ini LEBIH TINGGI dari interval pembaruan sensor Anda untuk menghindari penghentian yang salah (misalnya, jika sensor diperbarui setiap 60 detik, gunakan 65 detik+).", + "no_update_active_timeout": "Untuk sensor publikasikan saat perubahan: hanya akhiri paksa siklus AKTIF jika tidak ada pembaruan yang tiba selama beberapa detik ini. Penyelesaian berdaya rendah masih menggunakan Off Delay.", + "progress_reset_delay": "Setelah satu siklus selesai (100%), tunggu beberapa detik dalam keadaan diam sebelum mengatur ulang kemajuan ke 0%. Bawaan: 300 detik.", + "auto_maintenance": "Aktifkan pemeliharaan otomatis (perbaiki sampel, gabungkan fragmen).", + "expose_debug_entities": "Tampilkan sensor tingkat lanjut (Keyakinan, Fase, Ambiguitas) untuk debugging.", + "save_debug_traces": "Simpan peringkat terperinci dan data jejak daya dalam riwayat (Meningkatkan penggunaan penyimpanan)." + } + }, + "anti_wrinkle_section": { + "name": "Pelindung Anti-Kerut (Pengering)", + "description": "Abaikan putaran drum setelah siklus selesai untuk mencegah siklus hantu. Khusus pengering / mesin cuci-pengering.", + "data": { + "anti_wrinkle_enabled": "Pelindung Anti-Kerut (Khusus Pengering)", + "anti_wrinkle_max_power": "Daya Maks Anti-Kerut (W)", + "anti_wrinkle_max_duration": "Durasi Maks Anti-Kerut", + "anti_wrinkle_exit_power": "Daya Keluar Anti-Kerut (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Abaikan putaran pendek drum berdaya rendah setelah siklus berakhir untuk mencegah siklus hantu (pengering/mesin cuci-pengering saja).", + "anti_wrinkle_max_power": "Semburan pada atau di bawah kekuatan ini dianggap sebagai rotasi anti-kerut, bukan siklus baru. Hanya berlaku bila Anti-Wrinkle Shield diaktifkan.", + "anti_wrinkle_max_duration": "Panjang ledakan maksimum yang dianggap sebagai rotasi anti-kerut. Hanya berlaku bila Anti-Wrinkle Shield diaktifkan.", + "anti_wrinkle_exit_power": "Jika daya turun di bawah level ini, segera keluar dari anti-kerut (true off). Hanya berlaku bila Anti-Wrinkle Shield diaktifkan." + } + }, + "delay_start_section": { + "name": "Deteksi Mulai Tertunda", + "description": "Perlakukan mode siaga daya rendah yang berkelanjutan sebagai 'Menunggu untuk Memulai' sampai siklus benar-benar dimulai.", + "data": { + "delay_start_detect_enabled": "Aktifkan Deteksi Mulai Tertunda", + "delay_confirm_seconds": "Mulai Tertunda: Waktu Konfirmasi Siaga (dtk)", + "delay_timeout_hours": "Mulai Tertunda: Waktu Tunggu Maks (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Saat diaktifkan, lonjakan pengurasan daya rendah yang diikuti dengan daya siaga berkelanjutan terdeteksi sebagai permulaan yang tertunda. Sensor status menunjukkan 'Menunggu Mulai' selama penundaan. Tidak ada waktu atau kemajuan program yang dilacak hingga siklus benar-benar dimulai. Membutuhkan start_threshold_w untuk disetel di atas daya spike saluran pembuangan.", + "delay_confirm_seconds": "Daya harus tetap berada di antara Ambang Berhenti (W) dan Ambang Mulai (W) selama beberapa detik ini sebelum WashData masuk ke status 'Menunggu untuk Memulai'. Ini menyaring lonjakan singkat saat menavigasi menu. Default: 60 dtk.", + "delay_timeout_hours": "Batas waktu keselamatan: jika mesin masih berada dalam status 'Menunggu Mulai' setelah beberapa jam, WashData akan diatur ulang ke Mati. Default: 8 jam." + } + }, + "external_triggers_section": { + "name": "Pemicu Eksternal, Pintu & Jeda", + "description": "Sensor pemicu akhir eksternal opsional, sensor pintu untuk deteksi jeda/bersih, dan sakelar pemutus daya saat jeda.", + "data": { + "external_end_trigger_enabled": "Aktifkan Pemicu Akhir Siklus Eksternal", + "external_end_trigger": "Sensor Akhir Siklus Eksternal", + "external_end_trigger_inverted": "Pembalikan Logika Pemicu (Pemicu dalam keadaan MATI)", + "door_sensor_entity": "Sensor Pintu", + "pause_cuts_power": "Matikan Daya Saat Menjeda", + "switch_entity": "Ganti Entitas (untuk Jeda Pemadaman Listrik)", + "notify_unload_delay_minutes": "Penundaan Notifikasi Tunggu Laundry (mnt)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktifkan pemantauan sensor biner eksternal untuk memicu penyelesaian siklus.", + "external_end_trigger": "Pilih entitas sensor biner. Saat sensor ini terpicu, siklus saat ini akan berakhir dengan status 'selesai'.", + "external_end_trigger_inverted": "Secara default, pemicu menyala saat sensor AKTIF. Centang kotak ini untuk menyala ketika sensor dimatikan.", + "door_sensor_entity": "Sensor biner opsional untuk pintu mesin (hidup = terbuka, mati = tertutup). Ketika pintu terbuka selama siklus aktif, WashData akan mengonfirmasi bahwa siklus tersebut sengaja dijeda. Setelah siklus berakhir, jika pintu masih tertutup, statusnya berubah menjadi 'Bersih' hingga pintu terbuka. Catatan: menutup pintu TIDAK secara otomatis melanjutkan siklus yang dijeda - melanjutkan harus dipicu secara manual melalui tombol atau layanan Lanjutkan Siklus.", + "pause_cuts_power": "Saat menjeda melalui tombol atau layanan Pause Cycle, matikan juga entitas sakelar yang dikonfigurasi. Hanya berlaku jika entitas saklar dikonfigurasi untuk perangkat ini.", + "switch_entity": "Entitas peralihan untuk beralih saat menjeda atau melanjutkan. Diperlukan ketika 'Memutus Daya Saat Menjeda' diaktifkan.", + "notify_unload_delay_minutes": "Kirim notifikasi melalui saluran notifikasi selesai setelah siklus berakhir dan pintu belum dibuka selama beberapa menit (membutuhkan Sensor Pintu). Setel ke 0 untuk menonaktifkan. Default: 60 menit." + } + }, + "pump_section": { + "name": "Monitor Pompa", + "description": "Hanya untuk jenis perangkat pompa. Akan memicu sebuah event jika siklus pompa melebihi durasi ini.", + "data": { + "pump_stuck_duration": "Ambang Batas Peringatan Pompa Terjebak (detik) (Hanya Pompa)" + }, + "data_description": { + "pump_stuck_duration": "Jika siklus pompa masih berjalan setelah beberapa detik ini, kejadian ha_washdata_pump_stuck dipicu satu kali. Gunakan ini untuk mendeteksi motor yang macet (umumnya pompa air bekerja di bawah 60 detik). Default: 1800 detik (30 menit). Hanya jenis perangkat pompa." + } + }, + "device_link_section": { + "name": "Tautan Perangkat", + "description": "Secara opsional, tautkan perangkat WashData ini ke perangkat Home Assistant yang ada (misalnya smart plug atau peralatan itu sendiri). Perangkat WashData kemudian ditampilkan sebagai \"Terhubung melalui\" perangkat itu. Biarkan kosong untuk menjaga WashData sebagai perangkat mandiri.", + "data": { + "linked_device": "Perangkat Tertaut" + }, + "data_description": { + "linked_device": "Pilih perangkat untuk menghubungkan WashData. Ini menambahkan referensi 'Terhubung melalui' di registri perangkat; WashData menyimpan kartu perangkat dan entitasnya sendiri. Hapus pilihan untuk menghapus tautan." + } + } + } + }, + "diagnostics": { + "title": "Diagnostik & Pemeliharaan", + "description": "Jalankan tindakan pemeliharaan seperti menggabungkan siklus terfragmentasi atau memigrasikan data yang disimpan.\n\n**Penggunaan Penyimpanan**\n\n- Ukuran File: {file_size_kb} KB\n- Siklus: {cycle_count}\n- Profil: {profile_count}\n- Jejak Debug: {debug_count}", + "menu_options": { + "reprocess_history": "Pemeliharaan: Memproses Ulang & Mengoptimalkan Data", + "clear_debug_data": "Hapus Data Debug (Kosongkan ruang)", + "wipe_history": "Hapus SEMUA data untuk perangkat ini (tidak dapat diubah)", + "export_import": "Ekspor/Impor JSON dengan pengaturan (salin/tempel)", + "menu_back": "← Kembali" + } + }, + "clear_debug_data": { + "title": "Hapus Data Debug", + "description": "Apakah Anda yakin ingin menghapus semua jejak debug yang tersimpan? Ini akan mengosongkan ruang tetapi menghapus informasi peringkat terperinci dari siklus sebelumnya." + }, + "manage_cycles": { + "title": "Kelola Siklus", + "description": "Siklus terkini:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Beri Label Otomatis pada Siklus Lama", + "select_cycle_to_label": "Labeli Siklus Tertentu", + "select_cycle_to_delete": "Hapus Siklus", + "interactive_editor": "Gabungkan/Pisahkan Editor Interaktif", + "trim_cycle_select": "Potong Data Siklus", + "menu_back": "← Kembali" + } + }, + "manage_cycles_empty": { + "title": "Kelola Siklus", + "description": "Belum ada siklus yang tercatat.", + "menu_options": { + "auto_label_cycles": "Beri Label Otomatis pada Siklus Lama", + "menu_back": "← Kembali" + } + }, + "manage_profiles": { + "title": "Kelola Profil", + "description": "Ringkasan Profil:\n{profile_summary}", + "menu_options": { + "create_profile": "Buat Profil Baru", + "edit_profile": "Edit/Ganti Nama Profil", + "delete_profile_select": "Hapus Profil", + "profile_stats": "Statistik Profil", + "cleanup_profile": "Bersihkan Riwayat - Grafik & Hapus", + "assign_profile_phases_select": "Tetapkan Rentang Fase", + "menu_back": "← Kembali" + } + }, + "manage_profiles_empty": { + "title": "Kelola Profil", + "description": "Belum ada profil yang dibuat.", + "menu_options": { + "create_profile": "Buat Profil Baru", + "menu_back": "← Kembali" + } + }, + "manage_phase_catalog": { + "title": "Kelola Katalog Fase", + "description": "Katalog fase (default untuk perangkat ini + fase khusus):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Buat Fase Baru", + "phase_catalog_edit_select": "Fase Sunting", + "phase_catalog_delete": "Hapus Fase", + "menu_back": "← Kembali" + } + }, + "phase_catalog_create": { + "title": "Buat Fase Kustom", + "description": "Buat nama dan deskripsi fase khusus, lalu pilih jenis perangkat yang berlaku.", + "data": { + "target_device_type": "Jenis Perangkat Target", + "phase_name": "Nama Fase", + "phase_description": "Deskripsi Fase" + } + }, + "phase_catalog_edit_select": { + "title": "Edit Fase Kustom", + "description": "Pilih fase khusus untuk diedit.", + "data": { + "phase_name": "Fase Kustom" + } + }, + "phase_catalog_edit": { + "title": "Edit Fase Kustom", + "description": "Tahap pengeditan: {phase_name}", + "data": { + "phase_name": "Nama Fase", + "phase_description": "Deskripsi Fase" + } + }, + "phase_catalog_delete": { + "title": "Hapus Fase Kustom", + "description": "Pilih fase khusus yang akan dihapus. Rentang apa pun yang ditetapkan menggunakan fase ini akan dihapus.", + "data": { + "phase_name": "Fase Kustom" + } + }, + "assign_profile_phases": { + "title": "Tetapkan Rentang Fase", + "description": "Pratinjau fase untuk profil: **{profile_name}**\n\n{timeline_svg}\n\n**Rentang saat ini:**\n{current_ranges}\n\nGunakan tindakan di bawah ini untuk menambah, mengedit, menghapus, atau menyimpan rentang.", + "menu_options": { + "assign_profile_phases_add": "Tambahkan Rentang Fase", + "assign_profile_phases_edit_select": "Edit Rentang Fase", + "assign_profile_phases_delete": "Hapus Rentang Fase", + "phase_ranges_clear": "Hapus Semua Rentang", + "assign_profile_phases_auto_detect": "Fase Deteksi Otomatis", + "phase_ranges_save": "Simpan dan Kembalikan", + "menu_back": "← Kembali" + } + }, + "assign_profile_phases_add": { + "title": "Tambahkan Rentang Fase", + "description": "Tambahkan satu rentang fase ke draf saat ini.", + "data": { + "phase_name": "Fase", + "start_min": "Mulai Menit", + "end_min": "Menit Akhir" + } + }, + "assign_profile_phases_edit_select": { + "title": "Edit Rentang Fase", + "description": "Pilih rentang yang akan diedit.", + "data": { + "range_index": "Rentang Fase" + } + }, + "assign_profile_phases_edit": { + "title": "Edit Rentang Fase", + "description": "Perbarui rentang fase yang dipilih.", + "data": { + "phase_name": "Fase", + "start_min": "Mulai Menit", + "end_min": "Menit Akhir" + } + }, + "assign_profile_phases_delete": { + "title": "Hapus Rentang Fase", + "description": "Pilih rentang yang akan dihapus dari draf.", + "data": { + "range_index": "Rentang Fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fase yang terdeteksi secara otomatis", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase terdeteksi secara otomatis.** Pilih tindakan di bawah.", + "data": { + "action": "Tindakan" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nama Fase Terdeteksi", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fase terdeteksi:**\n{ranges_summary}\n\nTetapkan nama untuk setiap fase." + }, + "assign_profile_phases_select": { + "title": "Tetapkan Rentang Fase", + "description": "Pilih profil untuk mengonfigurasi rentang fase.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistik Profil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Buat Profil Baru", + "description": "Buat profil baru secara manual atau dari siklus sebelumnya.\n\nContoh nama profil: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Nama Profil", + "reference_cycle": "Siklus Referensi (opsional)", + "manual_duration": "Durasi Manual (menit)" + } + }, + "edit_profile": { + "title": "Sunting Profil", + "description": "Pilih profil yang akan diganti namanya", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Edit Detail Profil", + "description": "Profil saat ini: {current_name}", + "data": { + "new_name": "Nama Profil", + "manual_duration": "Durasi Manual (menit)" + } + }, + "delete_profile_select": { + "title": "Hapus Profil", + "description": "Pilih profil yang akan dihapus", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Konfirmasi Hapus Profil", + "description": "⚠️ Tindakan ini akan menghapus profil secara permanen: {profile_name}", + "data": { + "unlabel_cycles": "Hapus label dari siklus menggunakan profil ini" + } + }, + "auto_label_cycles": { + "title": "Beri Label Otomatis pada Siklus Lama", + "description": "Ditemukan {total_count} total siklus. Profil: {profiles}", + "data": { + "confidence_threshold": "Keyakinan Minimum (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Pilih Siklus ke Label", + "description": "✓ = selesai, ⚠ = dilanjutkan, ✗ = terputus", + "data": { + "cycle_id": "Siklus" + } + }, + "select_cycle_to_delete": { + "title": "Hapus Siklus", + "description": "⚠️ Ini akan menghapus siklus yang dipilih secara permanen", + "data": { + "cycle_id": "Siklus untuk Menghapus" + } + }, + "label_cycle": { + "title": "Siklus Label", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nama Profil Baru (jika dibuat)" + } + }, + "post_process": { + "title": "Riwayat Proses (Gabung/Pisahkan)", + "description": "Bersihkan riwayat siklus dengan menggabungkan proses yang terfragmentasi dan memisahkan siklus yang digabungkan secara tidak benar dalam jangka waktu tertentu. Masukkan jumlah jam untuk melihat ke belakang (gunakan 999999 untuk semua).", + "data": { + "time_range": "Periode Lihat Balik (jam)", + "gap_seconds": "Gabungkan/Pisahkan Jarak (detik)" + }, + "data_description": { + "time_range": "Jumlah jam untuk memindai siklus terfragmentasi untuk digabungkan. Gunakan 999999 untuk memproses semua data historis.", + "gap_seconds": "Ambang batas untuk memutuskan pemisahan vs penggabungan. Kesenjangan yang LEBIH PENDEK dari ini digabungkan. Kesenjangan yang LEBIH PANJANG dari ini akan terpecah. Turunkan ini untuk memaksa pemisahan pada siklus yang digabungkan secara tidak benar." + } + }, + "cleanup_profile": { + "title": "Bersihkan Riwayat - Pilih Profil", + "description": "Pilih profil untuk divisualisasikan. Ini akan menghasilkan grafik yang menunjukkan semua siklus sebelumnya untuk profil ini guna membantu mengidentifikasi outlier.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Bersihkan Riwayat - Grafik & Hapus", + "description": "![Grafik]({graph_url})\n\n**Memvisualisasikan {profile_name}**\n\nGrafik menunjukkan siklus individual sebagai garis berwarna. Identifikasi outlier (garis yang jauh dari grup) dan cocokkan warnanya pada daftar di bawah untuk menghapusnya.\n\n**Pilih siklus yang akan dihapus secara PERMANEN:**", + "data": { + "cycles_to_delete": "Siklus untuk Menghapus (periksa warna yang cocok)" + } + }, + "interactive_editor": { + "title": "Gabungkan/Pisahkan Editor Interaktif", + "description": "Pilih tindakan manual untuk dilakukan pada riwayat siklus Anda.", + "menu_options": { + "editor_split": "Pisahkan Siklus (Temukan celah)", + "editor_merge": "Gabungkan Siklus (Gabungkan fragmen)", + "editor_delete": "Hapus Siklus", + "menu_back": "← Kembali" + } + }, + "editor_select": { + "title": "Pilih Siklus", + "description": "Pilih siklus yang akan diproses.\n\n{info_text}", + "data": { + "selected_cycles": "Siklus" + } + }, + "editor_configure": { + "title": "Konfigurasikan & Terapkan", + "description": "{preview_md}", + "data": { + "confirm_action": "Tindakan", + "merged_profile": "Profil yang Dihasilkan", + "new_profile_name": "Nama Profil Baru", + "confirm_commit": "Ya, saya mengkonfirmasi tindakan ini", + "segment_0_profile": "Profil Segmen 1", + "segment_1_profile": "Profil Segmen 2", + "segment_2_profile": "Profil Segmen 3", + "segment_3_profile": "Profil Segmen 4", + "segment_4_profile": "Profil Segmen 5", + "segment_5_profile": "Profil Segmen 6", + "segment_6_profile": "Profil Segmen 7", + "segment_7_profile": "Profil Segmen 8", + "segment_8_profile": "Profil Segmen 9", + "segment_9_profile": "Profil Segmen 10" + } + }, + "editor_split_params": { + "title": "Konfigurasi Terpisah", + "description": "Sesuaikan sensitivitas untuk membagi siklus ini. Kesenjangan menganggur yang lebih lama dari ini akan menyebabkan perpecahan.", + "data": { + "split_mode": "Metode pemisahan" + } + }, + "editor_split_auto_params": { + "title": "Konfigurasi Pemisahan Otomatis", + "description": "Sesuaikan sensitivitas untuk memisahkan siklus ini. Jeda idle yang lebih lama dari ini akan menyebabkan pemisahan.", + "data": { + "min_gap_seconds": "Ambang Jeda Pemisahan (detik)" + } + }, + "editor_split_manual_params": { + "title": "Stempel waktu pemisahan manual", + "description": "{preview_md}Jendela siklus: **{cycle_start_wallclock} → {cycle_end_wallclock}** pada {cycle_date}.\n\nMasukkan satu atau lebih stempel waktu pemisahan (`HH:MM` atau `HH:MM:SS`), satu per baris. Siklus akan dipotong pada setiap stempel waktu — N stempel waktu menghasilkan N+1 segmen.", + "data": { + "split_timestamps": "Stempel Waktu Pemisahan" + } + }, + "reprocess_history": { + "title": "Pemeliharaan: Memproses Ulang & Mengoptimalkan Data", + "description": "Melakukan pembersihan mendalam terhadap data historis:\n1. Memangkas pembacaan daya nol (diam).\n2. Memperbaiki waktu/durasi siklus.\n3. Menghitung ulang semua model statistik (amplop).\n\n**Jalankan ini untuk memperbaiki masalah data atau menerapkan algoritme baru.**" + }, + "wipe_history": { + "title": "Hapus Riwayat (Hanya Pengujian)", + "description": "⚠️ Ini akan menghapus secara permanen SEMUA siklus dan profil yang tersimpan untuk perangkat ini. Ini tidak dapat dibatalkan!" + }, + "export_import": { + "title": "Ekspor/Impor JSON", + "description": "Salin/tempel siklus, profil, DAN pengaturan untuk perangkat ini. Ekspor di bawah atau tempel JSON untuk mengimpor.", + "data": { + "mode": "Tindakan", + "json_payload": "Konfigurasi JSON Lengkap" + }, + "data_description": { + "mode": "Pilih Ekspor untuk menyalin data dan pengaturan saat ini, atau Impor untuk menempelkan data yang diekspor dari perangkat WashData lain.", + "json_payload": "Untuk Ekspor: salin JSON ini (termasuk siklus, profil, dan semua pengaturan yang telah disesuaikan). Untuk Impor: tempel JSON yang diekspor dari WashData." + } + }, + "record_cycle": { + "title": "Siklus Rekam", + "description": "Rekam siklus secara manual untuk membuat profil bersih tanpa gangguan.\n\nStatus: **{status}**\nDurasi: {duration}dtk\nSampel: {samples}", + "menu_options": { + "record_refresh": "Segarkan Status", + "record_stop": "Hentikan Perekaman (Simpan & Proses)", + "record_start": "Mulai Perekaman Baru", + "record_process": "Proses Perekaman Terakhir (Potong & Simpan)", + "record_discard": "Buang Rekaman Terakhir", + "menu_back": "← Kembali" + } + }, + "record_process": { + "title": "Perekaman Proses", + "description": "![Grafik]({graph_url})\n\n**Grafik menunjukkan rekaman mentah.** Biru = Simpan, Merah = Usulan Pangkas.\n*Grafik bersifat statis dan tidak diperbarui seiring perubahan formulir.*\n\nPerekaman dihentikan. Pemangkasan diselaraskan dengan laju pengambilan sampel yang terdeteksi (~{sampling_rate}s).\n\nDurasi Mentah: {duration}dtk\nContoh: {samples}", + "data": { + "head_trim": "Pangkas Mulai (detik)", + "tail_trim": "Potong Akhir (detik)", + "save_mode": "Simpan Tujuan", + "profile_name": "Nama Profil" + } + }, + "learning_feedbacks": { + "title": "Umpan Balik Pembelajaran", + "description": "Masukan tertunda: {count}\n\n{pending_table}\n\nPilih permintaan peninjauan siklus untuk diproses.", + "menu_options": { + "learning_feedbacks_pick": "Tinjau satu umpan balik yang tertunda", + "learning_feedbacks_dismiss_all": "Tutup semua umpan balik yang tertunda", + "menu_back": "← Kembali" + } + }, + "learning_feedbacks_pick": { + "title": "Pilih umpan balik untuk ditinjau", + "description": "Pilih permintaan peninjauan siklus untuk dibuka.", + "data": { + "selected_feedback": "Pilih Siklus untuk Ditinjau" + } + }, + "learning_feedbacks_empty": { + "title": "Umpan Balik Pembelajaran", + "description": "Tidak ditemukan permintaan masukan yang tertunda.", + "menu_options": { + "menu_back": "← Kembali" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Tolak Semua Umpan Balik yang Tertunda", + "description": "Anda akan menolak **{count}** permintaan umpan balik yang tertunda. Siklus yang ditolak tetap ada di riwayat Anda tetapi tidak akan memberikan sinyal pembelajaran baru. Ini tidak dapat dibatalkan.\n\n✅ Centang kotak di bawah dan klik **Kirim** untuk menolak semuanya.\n❌ Biarkan tidak dicentang dan klik **Kirim** untuk membatalkan dan kembali.", + "data": { + "confirm_dismiss_all": "Konfirmasi: tutup semua permintaan umpan balik yang tertunda" + } + }, + "resolve_feedback": { + "title": "Selesaikan Umpan Balik", + "description": "{comparison_img}{candidates_table}\n**Profil Terdeteksi**: {detected_profile} ({confidence_pct}%)\n**Perkiraan Durasi**: {est_duration_min} mnt\n**Durasi Sebenarnya**: {act_duration_min} mnt\n\n__Pilih tindakan di bawah ini:__", + "data": { + "action": "Apa yang ingin kamu lakukan?", + "corrected_profile": "Program yang Benar (jika mengoreksi)", + "corrected_duration": "Durasi Benar dalam hitungan detik (jika mengoreksi)" + } + }, + "trim_cycle_select": { + "title": "Potong Siklus - Pilih Siklus", + "description": "Pilih siklus yang akan dipangkas. ✓ = selesai, ⚠ = dilanjutkan, ✗ = terputus", + "data": { + "cycle_id": "Siklus" + } + }, + "trim_cycle": { + "title": "Siklus Potong", + "description": "Jendela saat ini: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Tindakan" + } + }, + "trim_cycle_start": { + "title": "Atur Mulai Pangkas", + "description": "Jendela siklus: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAwal saat ini: **{current_wallclock}** (+{current_offset_min} mnt dari awal siklus)\n\nPilih waktu mulai yang baru menggunakan pemilih jam di bawah.", + "data": { + "trim_start_time": "Waktu mulai yang baru", + "trim_start_min": "Awal baru (menit dari awal siklus)" + } + }, + "trim_cycle_end": { + "title": "Atur Akhir Pangkas", + "description": "Jendela siklus: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAkhir saat ini: **{current_wallclock}** (+{current_offset_min} mnt dari awal siklus)\n\nPilih waktu berakhir yang baru menggunakan pemilih jam di bawah.", + "data": { + "trim_end_time": "Waktu akhir yang baru", + "trim_end_min": "Akhir baru (menit dari awal siklus)" + } + } + }, + "error": { + "import_failed": "Impor gagal. Harap tempelkan JSON ekspor WashData yang valid.", + "select_exactly_one": "Pilih tepat satu siklus untuk tindakan ini.", + "select_at_least_one": "Pilih setidaknya satu siklus untuk tindakan ini.", + "select_at_least_two": "Pilih setidaknya dua siklus untuk tindakan ini.", + "profile_exists": "Profil dengan nama ini sudah ada", + "rename_failed": "Gagal mengganti nama profil. Profil mungkin belum ada atau nama baru sudah dipakai.", + "assignment_failed": "Gagal menetapkan profil ke siklus", + "duplicate_phase": "Fase dengan nama ini sudah ada.", + "phase_not_found": "Fase yang dipilih tidak ditemukan.", + "invalid_phase_name": "Nama fase tidak boleh kosong.", + "phase_name_too_long": "Nama fase terlalu panjang.", + "invalid_phase_range": "Rentang fase tidak valid. Akhir harus lebih besar dari awal.", + "invalid_phase_timestamp": "Stempel waktu tidak valid. Gunakan format YYYY-MM-DD HH:MM atau HH:MM.", + "overlapping_phase_ranges": "Rentang fase tumpang tindih. Sesuaikan rentangnya dan coba lagi.", + "incomplete_phase_row": "Setiap baris fase harus menyertakan fase ditambah nilai awal/akhir yang lengkap untuk mode yang dipilih.", + "timestamp_mode_cycle_required": "Silakan pilih siklus sumber saat menggunakan mode stempel waktu.", + "no_phase_ranges": "Belum ada rentang fase yang tersedia untuk tindakan tersebut.", + "feedback_notification_title": "WashData: Verifikasi Siklus ({device})", + "feedback_notification_message": "**Perangkat**: {device}\n**Program**: {program} (keyakinan {confidence}%)\n**Waktu**: {time}\n\nWashData memerlukan bantuan Anda untuk memverifikasi siklus yang terdeteksi ini.\n\nSilakan buka **Pengaturan > Perangkat & Layanan > WashData > Konfigurasi > Masukan Pembelajaran** untuk mengonfirmasi atau memperbaiki hasil ini.", + "suggestions_ready_notification_title": "WashData: Setelan yang Disarankan Siap ({device})", + "suggestions_ready_notification_message": "Sensor **Setelan yang Disarankan** kini melaporkan **{count}** rekomendasi yang dapat ditindaklanjuti.\n\nUntuk meninjau dan menerapkannya: **Pengaturan > Perangkat & Layanan > WashData > Konfigurasi > Pengaturan Lanjutan > Terapkan Nilai yang Disarankan**.\n\nSaran bersifat opsional dan ditampilkan untuk ditinjau sebelum Anda menyimpannya.", + "trim_range_invalid": "Awal harus sebelum akhir. Silakan sesuaikan titik trim Anda.", + "trim_failed": "Pemangkasan gagal. Siklus tersebut mungkin sudah tidak ada lagi atau jendelanya kosong.", + "invalid_split_timestamp": "Stempel waktu tidak valid atau berada di luar jendela siklus. Gunakan HH:MM atau HH:MM:SS di dalam jendela siklus.", + "no_split_segments_found": "Tidak ada segmen valid yang dapat dibuat dari stempel waktu ini. Pastikan setiap stempel waktu berada di dalam jendela siklus dan setiap segmen berdurasi setidaknya 1 menit.", + "auto_tune_suggestion": "{device_type} {device_title} mendeteksi siklus hantu. Perubahan min_power yang disarankan: {current_min}W -> {new_min}W (tidak diterapkan secara otomatis).", + "auto_tune_title": "Penyetelan Otomatis WashData", + "auto_tune_fallback": "{device_type} {device_title} mendeteksi siklus hantu. Perubahan min_power yang disarankan: {current_min}W -> {new_min}W (tidak diterapkan secara otomatis)." + }, + "abort": { + "no_cycles_found": "Tidak ada siklus yang ditemukan", + "no_split_segments_found": "Tidak ada segmen yang dapat dipisah ditemukan pada siklus yang dipilih.", + "cycle_not_found": "Siklus yang dipilih tidak dapat dimuat.", + "no_power_data": "Tidak ada data jejak daya yang tersedia untuk siklus yang dipilih.", + "no_profiles_found": "Tidak ada profil yang ditemukan. Buat profil terlebih dahulu.", + "no_profiles_for_matching": "Tidak ada profil yang tersedia untuk dicocokkan. Buat profil terlebih dahulu.", + "no_unlabeled_cycles": "Semua siklus sudah diberi label", + "no_suggestions": "Belum ada nilai yang disarankan. Jalankan beberapa siklus dan coba lagi.", + "no_predictions": "Tidak ada prediksi", + "no_custom_phases": "Tidak ada fase khusus yang ditemukan.", + "reprocess_success": "Berhasil memproses ulang {count} siklus.", + "debug_data_cleared": "Berhasil menghapus data debug dari {count} siklus." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Siklus Mulai", + "cycle_finish": "Siklus Selesai", + "cycle_live": "Kemajuan Langsung" + } + }, + "common_text": { + "options": { + "unlabeled": "(Tidak berlabel)", + "create_new_profile": "Buat Profil Baru", + "remove_label": "Hapus Label", + "no_reference_cycle": "(Tidak ada siklus referensi)", + "all_device_types": "Semua Jenis Perangkat", + "current_suffix": "(saat ini)", + "editor_select_info": "Pilih 1 siklus untuk dipisahkan, atau 2+ siklus untuk digabungkan.", + "editor_select_info_split": "Pilih 1 siklus untuk dipisah.", + "editor_select_info_merge": "Pilih 2+ siklus untuk digabung.", + "editor_select_info_delete": "Pilih 1+ siklus untuk dihapus.", + "no_cycles_recorded": "Belum ada siklus yang tercatat.", + "profile_summary_line": "- **{name}**: {count} siklus, rata-rata {avg}m", + "no_profiles_created": "Belum ada profil yang dibuat.", + "phase_preview": "Pratinjau Fase", + "phase_preview_no_curve": "Kurva profil rata-rata belum tersedia. Jalankan/beri label lebih banyak siklus untuk profil ini.", + "average_power_curve": "Kurva Daya Rata-rata", + "unit_min": "menit", + "profile_option_fmt": "{name} ({count} siklus, ~{duration}m rata-rata)", + "profile_option_short_fmt": "{name} ({count} siklus, ~{duration}m)", + "deleted_cycles_from_profile": "Menghapus {count} siklus dari {profile}.", + "not_enough_data_graph": "Data tidak cukup untuk menghasilkan grafik.", + "no_profiles_found": "Tidak ada profil yang ditemukan.", + "cycle_info_fmt": "Siklus: {start}, {duration}m, Arus: {label}", + "top_candidates_header": "### Kandidat Teratas", + "tbl_profile": "Profil", + "tbl_confidence": "Kepercayaan diri", + "tbl_correlation": "Korelasi", + "tbl_duration_match": "Kecocokan Durasi", + "detected_profile": "Profil Terdeteksi", + "estimated_duration": "Perkiraan Durasi", + "actual_duration": "Durasi Sebenarnya", + "choose_action": "__Pilih tindakan di bawah ini:__", + "tbl_count": "Menghitung", + "tbl_avg": "Rata-rata", + "tbl_min": "Minimal", + "tbl_max": "Maks", + "tbl_energy_avg": "Energi (Rata-rata)", + "tbl_energy_total": "Energi (Total)", + "tbl_consistency": "Terdiri atas.", + "tbl_last_run": "Jalankan Terakhir", + "graph_legend_title": "Legenda Grafik", + "graph_legend_body": "Pita biru mewakili rentang penarikan daya minimum dan maksimum yang diamati. Garis tersebut menunjukkan kurva daya rata-rata.", + "recording_preview": "Pratinjau Rekaman", + "trim_start": "Pangkas Mulai", + "trim_end": "Potong Akhir", + "split_preview_title": "Pratinjau Terpisah", + "split_preview_found_fmt": "Ditemukan {count} segmen.", + "split_preview_confirm_fmt": "Klik Konfirmasi untuk membagi siklus ini menjadi {count} siklus terpisah.", + "merge_preview_title": "Gabungkan Pratinjau", + "merge_preview_joining_fmt": "Bergabung dengan {count} siklus. Kesenjangan akan diisi dengan pembacaan 0W.", + "editor_delete_preview_title": "Pratinjau Penghapusan", + "editor_delete_preview_intro": "Siklus yang dipilih akan dihapus secara permanen:", + "editor_delete_preview_confirm": "Klik Konfirmasi untuk menghapus catatan siklus ini secara permanen.", + "no_power_preview": "*Tidak ada data daya yang tersedia untuk pratinjau.*", + "profile_comparison": "Perbandingan Profil", + "actual_cycle_label": "Siklus ini (aktual)", + "storage_usage_header": "Penggunaan Penyimpanan", + "storage_file_size": "Ukuran Berkas", + "storage_cycles": "Siklus", + "storage_profiles": "Profil", + "storage_debug_traces": "Jejak Debug", + "overview_suffix": "Ringkasan", + "phase_preview_for_profile": "Pratinjau fase untuk profil", + "current_ranges_header": "Rentang saat ini", + "assign_phases_help": "Gunakan tindakan di bawah ini untuk menambah, mengedit, menghapus, atau menyimpan rentang.", + "profile_summary_header": "Ringkasan Profil", + "recent_cycles_header": "Siklus terkini", + "trim_cycle_preview_title": "Pratinjau Pemangkasan Siklus", + "trim_cycle_preview_no_data": "Tidak ada data daya yang tersedia untuk siklus ini.", + "trim_cycle_preview_kept_suffix": "disimpan", + "table_program": "Program", + "table_when": "Kapan", + "table_length": "Durasi", + "table_match": "Kecocokan", + "table_profile": "Profil", + "table_cycles": "Siklus", + "table_avg_length": "Durasi Rata-rata", + "table_last_run": "Jalankan Terakhir", + "table_avg_energy": "Energi Rata-rata", + "table_detected_program": "Program Terdeteksi", + "table_confidence": "Kepercayaan diri", + "table_reported": "Dilaporkan", + "phase_builtin_suffix": "(Bawaan)", + "phase_none_available": "Tidak ada fase yang tersedia.", + "settings_deprecation_warning": "⚠️ **Jenis perangkat yang tidak digunakan lagi:** {device_type} dijadwalkan untuk dihapus pada rilis mendatang. Alur pencocokan WashData tidak memberikan hasil yang dapat diandalkan untuk kelas peralatan ini. Integrasi Anda terus berjalan selama periode penghentian; untuk menonaktifkan peringatan ini, alihkan **Jenis Perangkat** di bawah ke salah satu jenis yang didukung (Mesin Cuci, Pengering, Kombinasi Mesin Cuci-Pengering, Mesin Pencuci Piring, Penggoreng Udara, Pembuat Roti, atau Pompa), atau ke **Lainnya (Lanjutan)** jika peralatan Anda tidak cocok dengan jenis apa pun yang didukung. **Lainnya (Lanjutan)** sengaja mengirimkan default generik yang tidak disetel untuk peralatan tertentu, sehingga Anda perlu mengonfigurasi sendiri ambang batas, batas waktu, dan parameter yang cocok; semua pengaturan yang ada dipertahankan saat Anda beralih.", + "phase_other_device_types": "Jenis perangkat lainnya:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Pisahkan Siklus (Temukan celah)", + "merge": "Gabungkan Siklus (Gabungkan fragmen)", + "delete": "Hapus Siklus" + } + }, + "split_mode": { + "options": { + "auto": "Deteksi otomatis jeda idle", + "manual": "Stempel waktu manual" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Pemeliharaan: Memproses Ulang & Mengoptimalkan Data", + "clear_debug_data": "Hapus Data Debug (Kosongkan ruang)", + "wipe_history": "Hapus SEMUA data untuk perangkat ini (tidak dapat diubah)", + "export_import": "Ekspor/Impor JSON dengan pengaturan (salin/tempel)" + } + }, + "export_import_mode": { + "options": { + "export": "Ekspor Semua Data", + "import": "Impor/Gabungkan Data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Beri Label Otomatis pada Siklus Lama", + "select_cycle_to_label": "Labeli Siklus Tertentu", + "select_cycle_to_delete": "Hapus Siklus", + "interactive_editor": "Gabungkan/Pisahkan Editor Interaktif", + "trim_cycle": "Potong Data Siklus" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Buat Profil Baru", + "edit_profile": "Edit/Ganti Nama Profil", + "delete_profile": "Hapus Profil", + "profile_stats": "Statistik Profil", + "cleanup_profile": "Bersihkan Riwayat - Grafik & Hapus", + "assign_phases": "Tetapkan Rentang Fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Buat Fase Baru", + "edit_custom_phase": "Fase Sunting", + "delete_custom_phase": "Hapus Fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Tambahkan Rentang Fase", + "edit_range": "Edit Rentang Fase", + "delete_range": "Hapus Rentang Fase", + "clear_ranges": "Hapus Semua Rentang", + "auto_detect_ranges": "Fase Deteksi Otomatis", + "save_ranges": "Simpan dan Kembalikan" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Segarkan Status", + "stop_recording": "Hentikan Perekaman (Simpan & Proses)", + "start_recording": "Mulai Perekaman Baru", + "process_recording": "Proses Perekaman Terakhir (Potong & Simpan)", + "discard_recording": "Buang Rekaman Terakhir" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Buat Profil Baru", + "existing_profile": "Tambahkan ke Profil yang Ada", + "discard": "Buang Rekaman" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Konfirmasi - Deteksi Benar", + "correct": "Benar - Pilih Program/Durasi", + "ignore": "Abaikan - Siklus Positif Palsu/Bising", + "delete": "Hapus - Hapus dari Riwayat" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Beri nama & terapkan fase", + "cancel": "Kembali tanpa perubahan" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Tetapkan Titik Awal", + "set_end": "Tetapkan Titik Akhir", + "reset": "Reset ke Durasi Penuh", + "apply": "Terapkan Pangkas", + "cancel": "Membatalkan" + } + }, + "device_type": { + "options": { + "washing_machine": "Mesin cuci", + "dryer": "Pengering", + "washer_dryer": "Kombo Mesin Cuci-Pengering", + "dishwasher": "Mesin pencuci piring", + "coffee_machine": "Mesin Kopi (tidak digunakan lagi)", + "ev": "Kendaraan Listrik (tidak digunakan lagi)", + "air_fryer": "Penggorengan Udara", + "heat_pump": "Pompa Panas (tidak digunakan lagi)", + "bread_maker": "Pembuat Roti", + "pump": "Pompa / Pompa Bah", + "oven": "Oven (tidak digunakan lagi)", + "other": "Lainnya (Lanjutan)" + } + } + }, + "services": { + "label_cycle": { + "name": "Siklus Label", + "description": "Tetapkan profil yang ada ke siklus sebelumnya, atau hapus labelnya.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci untuk diberi label." + }, + "cycle_id": { + "name": "ID Siklus", + "description": "ID siklus yang akan diberi label." + }, + "profile_name": { + "name": "Nama Profil", + "description": "Nama profil yang ada (buat profil di menu Kelola Profil). Biarkan kosong untuk menghapus label." + } + } + }, + "create_profile": { + "name": "Buat Profil", + "description": "Buat profil baru (mandiri atau berdasarkan siklus referensi).", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci." + }, + "profile_name": { + "name": "Nama Profil", + "description": "Nama untuk profil baru (misalnya 'Tugas Berat', 'Halus')." + }, + "reference_cycle_id": { + "name": "ID Siklus Referensi", + "description": "ID siklus opsional sebagai dasar profil ini." + } + } + }, + "delete_profile": { + "name": "Hapus Profil", + "description": "Hapus profil dan secara opsional batalkan label siklus yang menggunakannya.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci." + }, + "profile_name": { + "name": "Nama Profil", + "description": "Profil yang akan dihapus." + }, + "unlabel_cycles": { + "name": "Batalkan Label Siklus", + "description": "Hapus label profil dari siklus menggunakan profil ini." + } + } + }, + "auto_label_cycles": { + "name": "Beri Label Otomatis pada Siklus Lama", + "description": "Memberi label secara retroaktif pada siklus yang tidak berlabel menggunakan pencocokan profil.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci." + }, + "confidence_threshold": { + "name": "Ambang Keyakinan", + "description": "Keyakinan kecocokan minimum (0,50-0,95) untuk menerapkan label." + } + } + }, + "export_config": { + "name": "Ekspor Konfigurasi", + "description": "Ekspor profil, siklus, dan pengaturan perangkat ini ke file JSON (per perangkat).", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci untuk diekspor." + }, + "path": { + "name": "Jalur", + "description": "Jalur file absolut opsional untuk ditulis (defaultnya adalah /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Impor Konfigurasi", + "description": "Impor profil, siklus, dan pengaturan untuk perangkat ini dari file ekspor JSON (pengaturan mungkin ditimpa).", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci yang akan diimpor." + }, + "path": { + "name": "Jalur", + "description": "Jalur absolut ke file JSON ekspor yang akan diimpor." + } + } + }, + "submit_cycle_feedback": { + "name": "Kirimkan Umpan Balik Siklus", + "description": "Konfirmasikan atau perbaiki program yang terdeteksi otomatis setelah siklus selesai. Berikan `entry_id` (lanjutan) atau `device_id` (disarankan).", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat WashData (disarankan daripada entry_id)." + }, + "entry_id": { + "name": "ID entri", + "description": "ID entri konfigurasi untuk perangkat (alternatif dari device_id)." + }, + "cycle_id": { + "name": "ID Siklus", + "description": "Id siklus ditampilkan dalam pemberitahuan/log umpan balik." + }, + "user_confirmed": { + "name": "Konfirmasikan Program yang Terdeteksi", + "description": "Setel true jika program yang terdeteksi benar." + }, + "corrected_profile": { + "name": "Profil yang Dikoreksi", + "description": "Jika tidak dikonfirmasi, berikan profil/nama program yang benar." + }, + "corrected_duration": { + "name": "Durasi yang Dikoreksi (detik)", + "description": "Durasi koreksi opsional dalam hitungan detik." + }, + "notes": { + "name": "Catatan", + "description": "Catatan opsional tentang siklus ini." + } + } + }, + "record_start": { + "name": "Rekam Siklus Mulai", + "description": "Mulai merekam siklus bersih secara manual (melewati semua logika yang cocok).", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci untuk merekam." + } + } + }, + "record_stop": { + "name": "Rekam Siklus Berhenti", + "description": "Hentikan perekaman manual.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat mesin cuci berhenti merekam." + } + } + }, + "trim_cycle": { + "name": "Siklus Potong", + "description": "Pangkas data daya dari siklus sebelumnya ke jangka waktu tertentu.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat WashData." + }, + "cycle_id": { + "name": "ID Siklus", + "description": "ID siklus yang akan dipangkas." + }, + "trim_start_s": { + "name": "Pangkas Mulai (detik)", + "description": "Simpan data dari offset ini dalam hitungan detik. Bawaan 0." + }, + "trim_end_s": { + "name": "Potong Akhir (detik)", + "description": "Simpan data hingga offset ini dalam hitungan detik. Default = durasi penuh." + } + } + }, + "pause_cycle": { + "name": "Jeda Siklus", + "description": "Jeda siklus aktif untuk perangkat WashData.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat WashData akan dijeda." + } + } + }, + "resume_cycle": { + "name": "Lanjutkan Siklus", + "description": "Melanjutkan siklus yang dijeda untuk perangkat WashData.", + "fields": { + "device_id": { + "name": "Perangkat", + "description": "Perangkat WashData akan dilanjutkan." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Berlari" + }, + "match_ambiguity": { + "name": "Ambiguitas Pertandingan" + } + }, + "sensor": { + "washer_state": { + "name": "Negara", + "state": { + "off": "Mati", + "idle": "Menganggur", + "starting": "Mulai", + "running": "Berlari", + "paused": "Dijeda", + "user_paused": "Dijeda oleh pengguna", + "ending": "Akhir", + "finished": "Selesai", + "anti_wrinkle": "Anti-Kerut", + "delay_wait": "Menunggu untuk Memulai", + "interrupted": "Terganggu", + "force_stopped": "Paksa Berhenti", + "rinse": "Membilas", + "unknown": "Tidak dikenal", + "clean": "Bersih" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Sisa Waktu" + }, + "total_duration": { + "name": "Durasi Total" + }, + "cycle_progress": { + "name": "Kemajuan" + }, + "current_power": { + "name": "Kekuatan Saat Ini" + }, + "elapsed_time": { + "name": "Waktu yang Berlalu" + }, + "debug_info": { + "name": "Info Debug" + }, + "match_confidence": { + "name": "Keyakinan Kecocokan" + }, + "top_candidates": { + "name": "Kandidat Teratas", + "state": { + "none": "Tidak ada" + } + }, + "current_phase": { + "name": "Fase Saat Ini" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Hitung", + "unit_of_measurement": "siklus" + }, + "suggestions": { + "name": "Pengaturan yang Disarankan" + }, + "pump_runs_today": { + "name": "Pompa Berjalan (24 jam terakhir)" + }, + "cycle_count": { + "name": "Hitungan Siklus" + } + }, + "select": { + "program_select": { + "name": "Program Siklus", + "state": { + "auto_detect": "Deteksi Otomatis" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Siklus Akhir Paksa" + }, + "pause_cycle": { + "name": "Jeda Siklus" + }, + "resume_cycle": { + "name": "Lanjutkan Siklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Device_id yang valid diperlukan." + }, + "cycle_id_required": { + "message": "Cycle_id yang valid diperlukan." + }, + "profile_name_required": { + "message": "Diperlukan nama_profil yang valid." + }, + "device_not_found": { + "message": "Perangkat tidak ditemukan." + }, + "no_config_entry": { + "message": "Tidak ada entri konfigurasi yang ditemukan untuk perangkat." + }, + "integration_not_loaded": { + "message": "Integrasi tidak dimuat untuk perangkat ini." + }, + "cycle_not_found_or_no_power": { + "message": "Siklus tidak ditemukan atau tidak memiliki data daya." + }, + "trim_failed_empty_window": { + "message": "Pemangkasan gagal - siklus tidak ditemukan, tidak ada data daya, atau jendela yang dihasilkan kosong." + }, + "trim_invalid_range": { + "message": "trim_end_s harus lebih besar dari trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/is.json b/custom_components/ha_washdata/translations/is.json new file mode 100644 index 0000000..361946c --- /dev/null +++ b/custom_components/ha_washdata/translations/is.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData uppsetning", + "description": "Stilltu þvottavélina þína eða annað tæki.\n\nKraftur skynjari er nauðsynlegur.\n\n**Þá verður þú spurður hvort þú viljir búa til fyrsta prófílinn þinn.**", + "data": { + "name": "Nafn tækis", + "device_type": "Tegund tækis", + "power_sensor": "Aflskynjari", + "min_power": "Lágmarksaflsþröskuldur (W)" + }, + "data_description": { + "name": "Vingjarnlegt nafn fyrir þetta tæki (t.d. „Þvottavél“, „Uppþvottavél“).", + "device_type": "Hvers konar tæki er þetta? Hjálpar til við að sérsníða greiningu og merkingu.", + "power_sensor": "Skynjareiningin sem tilkynnir um orkunotkun í rauntíma (í vöttum) frá snjalltenginu þínu.", + "min_power": "Aflmælingar yfir þessum viðmiðunarmörkum (í vöttum) gefa til kynna að heimilistækið sé í gangi. Byrjaðu með 2W fyrir flest tæki." + } + }, + "first_profile": { + "title": "Búðu til fyrsta prófíl", + "description": "**Hringrás** er heildarkeyrsla á heimilistækinu þínu (t.d. fullt af þvotti). **Prófíll** flokkar þessar lotur eftir tegund (t.d. „Bómull”, „Fljótþvottur”). Að búa til prófíl hjálpar nú við að meta tímalengd samþættingar strax.\n\nÞú getur valið þennan prófíl handvirkt í stjórntækjum tækisins ef það greinist ekki sjálfkrafa.\n\n**Láttu 'Nafn prófíls' autt til að sleppa þessu skrefi.**", + "data": { + "profile_name": "Nafn prófíls (valfrjálst)", + "manual_duration": "Áætluð lengd (mínútur)" + } + } + }, + "error": { + "cannot_connect": "Tókst ekki að tengjast", + "invalid_auth": "Ógild auðkenning", + "unknown": "Óvænt villa" + }, + "abort": { + "already_configured": "Tæki er þegar stillt" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Skoðaðu endurgjöf um nám", + "settings": "Stillingar", + "notifications": "Tilkynningar", + "manage_cycles": "Stjórna hringrásum", + "manage_profiles": "Stjórna prófílum", + "manage_phase_catalog": "Stjórna áfangaskrá", + "record_cycle": "Upptökulota (handvirkt)", + "diagnostics": "Greining og viðhald" + } + }, + "apply_suggestions": { + "title": "Afritaðu tillögur að gildum", + "description": "Þetta mun afrita núverandi tillögur að gildum í vistaðar stillingar þínar. Þetta er einu sinni aðgerð og gerir ekki kleift að skrifa yfir sjálfvirkt.\n\nRáð til að afrita gildi:\n{suggested}", + "data": { + "confirm": "Staðfestu afritunaraðgerð" + } + }, + "apply_suggestions_confirm": { + "title": "Skoðaðu breytingartillögur", + "description": "WashData fundust **{pending_count}** tillögð gildi til að nota.\n\nBreytingar:\n{changes}\n\n✅ Hakaðu í reitinn hér að neðan og smelltu á **Senda** til að sækja um og vista þessar breytingar strax.\n❌ Skildu ekki hakað við og smelltu á **Senda** til að hætta við og fara til baka.", + "data": { + "confirm_apply_suggestions": "Notaðu og vistaðu þessar breytingar" + } + }, + "settings": { + "title": "Stillingar", + "description": "{deprecation_warning}Stilla greiningarþröskuld, nám og háþróaða hegðun. Sjálfgefnar stillingar virka vel fyrir flestar uppsetningar.\n\nTillögðar stillingar í boði: {suggestions_count}.\nEf þetta er hærra en 0, opnaðu Ítarlegar stillingar og notaðu 'Notaðu tillögð gildi' til að fara yfir ráðleggingar.", + "data": { + "apply_suggestions": "Notaðu tillögð gildi", + "device_type": "Tegund tækis", + "power_sensor": "Aflskynjareining", + "min_power": "Lágmarksafl (W)", + "off_delay": "Lokaseinkun (s)", + "notify_actions": "Tilkynningaaðgerðir", + "notify_people": "Fresta afhendingu þar til þetta fólk er komið heim", + "notify_only_when_home": "Seinkað tilkynningum þar til valinn einstaklingur er kominn heim", + "notify_fire_events": "Kveikja á sjálfvirkniviðburðum", + "notify_start_services": "Cycle Start - Tilkynningarmarkmið", + "notify_finish_services": "Ljúka hringrás - Tilkynningarmarkmið", + "notify_live_services": "Framfarir í beinni - Tilkynningarmarkmið", + "notify_before_end_minutes": "Tilkynning um fyrir lok (mínútum fyrir lok)", + "notify_title": "Titill tilkynningar", + "notify_icon": "Tilkynningatákn", + "notify_start_message": "Byrjaðu skilaboðasnið", + "notify_finish_message": "Ljúktu skilaboðasniði", + "notify_pre_complete_message": "Skilaboðasnið fyrir útfyllingu", + "show_advanced": "Breyta ítarlegum stillingum (næsta skref)" + }, + "data_description": { + "apply_suggestions": "Merktu við þennan reit til að afrita gildi sem lærdómsreikniritið leggur til í eyðublaðið. Skoðaðu áður en þú vistar.\n\nTillögur (frá námi):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Veldu gerð tækis (þvottavél, þurrkari, uppþvottavél, kaffivél, rafbíll). Þetta merki er geymt með hverri lotu fyrir betra skipulag.", + "power_sensor": "Skynjaraeiningin gefur frá sér rauntímaafli (í vöttum). Þú getur breytt þessu hvenær sem er án þess að tapa sögulegum gögnum - gagnlegt ef þú skiptir um snjalltappa.", + "min_power": "Aflmælingar yfir þessum viðmiðunarmörkum (í vöttum) gefa til kynna að heimilistækið sé í gangi. Byrjaðu með 2W fyrir flest tæki.", + "off_delay": "Sekúndur verður slétta krafturinn að vera undir stöðvunarmörkum til að marka lok. Sjálfgefið: 120s.", + "notify_actions": "Valfrjáls Home Assistant aðgerðaröð til að keyra fyrir tilkynningar (forskriftir, tilkynna, TTS osfrv.). Ef stillt er, keyra aðgerðir áður en varatilkynningaþjónustu er veitt.", + "notify_people": "Fólkseiningar notaðar fyrir viðveruhlið. Þegar kveikt er á því seinkar sendingu tilkynninga þar til að minnsta kosti einn valinn einstaklingur er heima.", + "notify_only_when_home": "Ef það er virkt skaltu fresta tilkynningum þar til að minnsta kosti einn valinn einstaklingur er heima.", + "notify_fire_events": "Ef það er virkt, kveiktu á Home Assistant-viðburðum fyrir upphaf og lok lotu (ha_washdata_cycle_started og ha_washdata_cycle_ended) til að knýja fram sjálfvirkni.", + "notify_start_services": "Ein eða fleiri tilkynna þjónustur til að láta vita þegar lota hefst. Skildu autt til að sleppa byrjunartilkynningum.", + "notify_finish_services": "Ein eða fleiri tilkynna þjónustur til að láta vita þegar lotu lýkur eða nálgast það að ljúka. Skildu autt til að sleppa tilkynningum um að ljúka.", + "notify_live_services": "Ein eða fleiri tilkynningaþjónustur til að fá framfarauppfærslur í beinni á meðan lotan keyrir (aðeins farsímaforrit). Skildu eftir autt til að sleppa uppfærslum í beinni.", + "notify_before_end_minutes": "Mínútum fyrir áætlaðan lok lotunnar til að kalla fram tilkynningu. Stilltu á 0 til að slökkva á. Sjálfgefið: 0.", + "notify_title": "Sérsniðinn titill fyrir tilkynningar. Styður {device} staðgengil.", + "notify_icon": "Tákn til að nota fyrir tilkynningar (t.d. mdi: þvottavél). Skildu eftir tómt til að senda án tákns.", + "notify_start_message": "Skilaboð send þegar hringrás byrjar. Styður {device} staðgengil.", + "notify_finish_message": "Skilaboð send þegar lotu lýkur. Styður {device}, {duration}, {program}, {energy_kwh}, {cost} staðgengla.", + "notify_pre_complete_message": "Texti endurtekinna framfarauppfærslur í beinni á meðan lotan keyrir. Styður {device}, {minutes}, {program} staðgengla." + } + }, + "notifications": { + "title": "Tilkynningar", + "description": "Stilltu tilkynningarásir, sniðmát og valfrjálsar uppfærslur fyrir farsímaframvindu í beinni.", + "data": { + "notify_actions": "Tilkynningaaðgerðir", + "notify_people": "Fresta afhendingu þar til þetta fólk er komið heim", + "notify_only_when_home": "Seinkað tilkynningum þar til valinn einstaklingur er kominn heim", + "notify_fire_events": "Kveikja á sjálfvirkniviðburðum", + "notify_start_services": "Cycle Start - Tilkynningarmarkmið", + "notify_finish_services": "Ljúka hringrás - Tilkynningarmarkmið", + "notify_live_services": "Framfarir í beinni - Tilkynningarmarkmið", + "notify_before_end_minutes": "Tilkynning um fyrir lok (mínútum fyrir lok)", + "notify_live_interval_seconds": "Lifandi uppfærslubil (sekúndur)", + "notify_live_overrun_percent": "Heimildir umframkeyrslu í beinni (%)", + "notify_live_chronometer": "Chronometer Niðurteljari", + "notify_title": "Titill tilkynningar", + "notify_icon": "Tilkynningatákn", + "notify_start_message": "Byrjaðu skilaboðasnið", + "notify_finish_message": "Ljúktu skilaboðasniði", + "notify_pre_complete_message": "Skilaboðasnið fyrir útfyllingu", + "energy_price_entity": "Orkuverð – eining (valfrjálst)", + "energy_price_static": "Orkuverð - Statískt gildi (valfrjálst)", + "go_back": "Fara til baka án vistunar", + "notify_reminder_message": "Snið áminningarskilaboða", + "notify_timeout_seconds": "Loka sjálfkrafa eftir (sekúndur)", + "notify_channel": "Tilkynningarrás (staða/í beinni/áminning)", + "notify_finish_channel": "Tilkynningarás lokið" + }, + "data_description": { + "go_back": "Hakaðu við þetta og smelltu á Senda til að henda öllum breytingum hér að ofan og fara aftur í aðalvalmyndina.", + "notify_actions": "Valfrjáls Home Assistant aðgerðaröð til að keyra fyrir tilkynningar (forskriftir, tilkynna, TTS osfrv.). Ef stillt er, keyra aðgerðir áður en varatilkynningaþjónustu er veitt.", + "notify_people": "Fólkseiningar notaðar fyrir viðveruhlið. Þegar kveikt er á því seinkar sendingu tilkynninga þar til að minnsta kosti einn valinn einstaklingur er heima.", + "notify_only_when_home": "Ef það er virkt skaltu fresta tilkynningum þar til að minnsta kosti einn valinn einstaklingur er heima.", + "notify_fire_events": "Ef það er virkt, kveiktu á Home Assistant-viðburðum fyrir upphaf og lok lotu (ha_washdata_cycle_started og ha_washdata_cycle_ended) til að knýja fram sjálfvirkni.", + "notify_start_services": "Ein eða fleiri tilkynningaþjónustur til að láta vita þegar lota hefst (t.d. notify.mobile_app_my_phone). Skildu autt til að sleppa byrjunartilkynningum.", + "notify_finish_services": "Ein eða fleiri tilkynna þjónustur til að láta vita þegar lotu lýkur eða nálgast það að ljúka. Skildu autt til að sleppa tilkynningum um að ljúka.", + "notify_live_services": "Ein eða fleiri tilkynningaþjónustur til að fá framfarauppfærslur í beinni á meðan lotan keyrir (aðeins farsímaforrit). Skildu eftir autt til að sleppa uppfærslum í beinni.", + "notify_before_end_minutes": "Mínútum fyrir áætlaðan lok lotunnar til að kalla fram tilkynningu. Stilltu á 0 til að slökkva á. Sjálfgefið: 0.", + "notify_live_interval_seconds": "Hversu oft framvinduuppfærslur eru sendar meðan þær eru í gangi. Lægri gildi svara betur en neyta fleiri tilkynninga. Að lágmarki 30 sekúndum er framfylgt - gildi undir 30 sekúndum eru sjálfkrafa námunduð upp í 30 sek.", + "notify_live_overrun_percent": "Öryggismörk yfir áætlaðri uppfærslutölu fyrir langa/framkeyrandi lotur (til dæmis leyfa 20% 1,2x áætlaðar uppfærslur).", + "notify_live_chronometer": "Þegar virkjað er, inniheldur hver lifandi uppfærsla niðurtalning á tímamæli að áætluðum lokatíma. Tilkynningatíminn tikkar sjálfkrafa niður á tækinu á milli uppfærslna (aðeins Android fylgiforrit).", + "notify_title": "Sérsniðinn titill fyrir tilkynningar. Styður {device} staðgengil.", + "notify_icon": "Tákn til að nota fyrir tilkynningar (t.d. mdi: þvottavél). Skildu eftir tómt til að senda án tákns.", + "notify_start_message": "Skilaboð send þegar hringrás byrjar. Styður {device} staðgengil.", + "notify_finish_message": "Skilaboð send þegar lotu lýkur. Styður {device}, {duration}, {program}, {energy_kwh}, {cost} staðgengla.", + "energy_price_entity": "Veldu tölulega einingu sem heldur núverandi raforkuverði (t.d. sensor.electricity_price, input_number.energy_rate). Þegar stillt er á {cost} staðgengillinn. Tekur forgang fram yfir fasta gildið ef bæði eru stillt.", + "energy_price_static": "Fast raforkuverð á kWst. Þegar stillt er á {cost} staðgengillinn. Hunsuð ef eining er stillt hér að ofan. Bættu gjaldmiðlinum þínum við í skilaboðasniðmátinu, t.d. {cost} €.", + "notify_reminder_message": "Texti í einu sinni áminningu sendi stilltan fjölda mínútna fyrir áætlaðan lokadag. Aðgreindur frá endurteknum lifandi uppfærslum hér að ofan. Styður {device}, {minutes}, {program} staðgengla.", + "notify_timeout_seconds": "Hunsa tilkynningar sjálfkrafa eftir þessar margar sekúndur (framsent sem „timeout“ fylgiforritinu). Stilltu á 0 til að halda þeim þar til þeim er vísað frá. Sjálfgefið: 0.", + "notify_channel": "Android tilkynningarás fyrir fylgiforrit fyrir stöðu, framfarir í beinni og áminningartilkynningar. Hljóð og mikilvægi rásar er stillt í fylgiforritinu í fyrsta skipti sem rásarheitið er notað - WashData stillir aðeins rásarnafnið. Skildu eftir tómt fyrir sjálfgefið forrit.", + "notify_finish_channel": "Aðskildu Android rás fyrir viðvörunina sem er lokið (einnig notuð af áminningunni og nöldrinu sem bíður þvotta) svo hún geti fengið sitt eigið hljóð. Skildu eftir tómt til að endurnýta rásina hér að ofan.", + "notify_pre_complete_message": "Texti endurtekinna framfarauppfærslur í beinni á meðan lotan keyrir. Styður {device}, {minutes}, {program} staðgengla." + } + }, + "advanced_settings": { + "title": "Ítarlegar stillingar", + "description": "Stilltu greiningarþröskulda, nám og háþróaða hegðun. Sjálfgefnar stillingar virka vel fyrir flestar uppsetningar.", + "sections": { + "suggestions_section": { + "name": "Tillögðar stillingar", + "description": "{suggestions_count} lærðar tillögur tiltækar. Hakaðu við Nota tillögð gildi til að fara yfir fyrirhugaðar breytingar áður en vistað er.", + "data": { + "apply_suggestions": "Notaðu tillögð gildi" + }, + "data_description": { + "apply_suggestions": "Merktu við þennan reit til að opna yfirferðarskref fyrir tillögur að gildum úr námsalgríminu. Ekkert er vistað sjálfkrafa.\n\nTillögur (frá námi):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Greining og aflmörk", + "description": "Hvenær tækið telst KVEIKT eða SLÖKKT, orkugáttir og tímasetning stöðubreytinga.", + "data": { + "start_duration_threshold": "Hefja frávarpslengd (s)", + "start_energy_threshold": "Orkuhlið við ræsingu (Wh)", + "completion_min_seconds": "Lágmarks keyrslutími (sekúndur)", + "start_threshold_w": "Byrjunarþröskuldur (W)", + "stop_threshold_w": "Stöðvunarþröskuldur (W)", + "end_energy_threshold": "Orkuhlið við lok (Wh)", + "running_dead_zone": "Dautt svæði við ræsingu (sekúndur)", + "end_repeat_count": "Endurtaka talningu", + "min_off_gap": "Lágmarksbil á milli lota (s)", + "sampling_interval": "Sýnatökubil (s)" + }, + "data_description": { + "start_duration_threshold": "Hunsa stutta krafta sem eru styttri en þessa lengd (sekúndur). Síar út stígvélatoppa (t.d. 60W í 2s) áður en raunveruleg hringrás hefst. Sjálfgefið: 5s.", + "start_energy_threshold": "Hringrásin verður að safna að minnsta kosti þessari miklu orku (Wh) á START áfanganum til að vera staðfest. Sjálfgefið: 0,005 Wh.", + "completion_min_seconds": "Hringrásir sem eru styttri en þetta verða merktar sem „rofnar“ jafnvel þótt þær ljúki náttúrulega. Sjálfgefið: 600s.", + "start_threshold_w": "Afl verður að hækka ÚR þessum þröskuldi til að BYRJA lotu. Stilltu hærra en biðstyrkur þinn. Sjálfgefið: min_power + 1 W.", + "stop_threshold_w": "Afl verður að falla UNDIR þessum þröskuldi til að LOKA lotu. Stilltu rétt fyrir ofan núll til að koma í veg fyrir hávaða sem kallar fram falska enda. Sjálfgefið: 0,6 * mín_power.", + "end_energy_threshold": "Hringrás lýkur aðeins ef orka í síðasta Off-Delay glugga er undir þessu gildi (Wh). Kemur í veg fyrir falska enda ef afl sveiflast nálægt núlli. Sjálfgefið: 0,05 Wh.", + "running_dead_zone": "Eftir að lotan byrjar skaltu hunsa afllækkanir í þessar margar sekúndur til að koma í veg fyrir falska endagreiningu á upphafsstigi. Stilltu á 0 til að slökkva á. Sjálfgefið: 0s.", + "end_repeat_count": "Fjöldi skipta þarf að uppfylla lokaskilyrði (afl undir þröskuldi fyrir off_delay) í röð áður en lotunni lýkur. Hærri gildi koma í veg fyrir falskar endir í hléum. Sjálfgefið: 1.", + "min_off_gap": "Ef lota byrjar innan margra sekúndna frá því að fyrri lýkur, verða þær sameinaðar í eina lotu.", + "sampling_interval": "Lágmarks sýnatökutímabil í sekúndum. Uppfærslur hraðar en þetta hlutfall verða hunsaðar. Sjálfgefið: 30s (2s fyrir þvottavélar, þvottavél-þurrkara og uppþvottavélar)." + } + }, + "matching_section": { + "name": "Prófílsamsvörun og nám", + "description": "Hversu ákveðið WashData samsvarar keyrandi hringrásum við lærð prófíl og hvenær á að biðja um ábendingar.", + "data": { + "profile_match_min_duration_ratio": "Lágmarkslengdarhlutfall prófílsamsvörunar (0,1-1,0)", + "profile_match_interval": "Samsvörunarbil prófíls (sekúndur)", + "profile_match_threshold": "Samsvörunarþröskuldur prófíls", + "profile_unmatch_threshold": "Prófíll Ósamræmd þröskuldur", + "auto_label_confidence": "Sjálfvirk merkisöryggi (0-1)", + "learning_confidence": "Álitsbeiðni um traust (0-1)", + "suppress_feedback_notifications": "Bældu viðbrögðstilkynningar", + "duration_tolerance": "Tímaþol (0-0,5)", + "smoothing_window": "Sléttunargluggi (sýnishorn)", + "profile_duration_tolerance": "Tímalengd prófílsamsvörunar (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Lágmarkslengdarhlutfall fyrir samsvörun sniðs. Hlaupalota verður að vera að minnsta kosti þetta brot af lengd prófílsins til að passa. Lægri = fyrri samsvörun. Sjálfgefið: 0,50 (50%).", + "profile_match_interval": "Hversu oft (í sekúndum) á að reyna samsvörun sniðs meðan á lotu stendur. Lægri gildi veita hraðari uppgötvun en nota meiri CPU. Sjálfgefið: 300s (5 mínútur).", + "profile_match_threshold": "Lágmarks DTW líkindastig (0,0–1,0) sem þarf til að líta á prófíl sem samsvörun. Hærra = strangari samsvörun, færri falskar jákvæðar. Sjálfgefið: 0,4.", + "profile_unmatch_threshold": "DTW líkindiskor (0,0–1,0) þar sem áður samsvarandi prófíl er hafnað. Ætti að vera lægra en samsvarandi þröskuldur til að koma í veg fyrir flökt. Sjálfgefið: 0,35.", + "auto_label_confidence": "Merktu hringrásir sjálfkrafa við eða yfir þessu trausti þegar þeim er lokið (0,0–1,0).", + "learning_confidence": "Lágmarksöryggi til að biðja um staðfestingu notanda í gegnum viðburð (0.0–1.0).", + "suppress_feedback_notifications": "Þegar kveikt er á því mun WashData samt rekja og biðja um endurgjöf innbyrðis en mun ekki sýna viðvarandi tilkynningar sem biðja þig um að staðfesta lotur. Gagnlegt þegar hringrásir finnast alltaf rétt og þér finnst leiðbeiningarnar trufla þig.", + "duration_tolerance": "Umburðarlyndi fyrir áætlanir sem eftir eru á meðan á keyrslu stendur (0,0–0,5 samsvarar 0–50%).", + "smoothing_window": "Fjöldi nýlegra aflestraraflestra sem notaðar eru til að jafna hreyfanlegt meðaltal.", + "profile_duration_tolerance": "Umburðarlyndi notað af prófílsamsvörun fyrir heildarlengdarfrávik (0,0–0,5). Sjálfgefin hegðun: ±25%." + } + }, + "timing_section": { + "name": "Tímasetningar, viðhald og aflúsun", + "description": "Varðhundur, framvinduendurstilling, sjálfvirkt viðhald og birting aflúsunar-eininga.", + "data": { + "watchdog_interval": "Varðhundabil (sekúndur)", + "no_update_active_timeout": "Tímamörk án uppfærslu", + "progress_reset_delay": "Töf á framvindu endurstillingar (sekúndur)", + "auto_maintenance": "Virkja sjálfvirkt viðhald", + "expose_debug_entities": "Afhjúpa villuleitareiningar", + "save_debug_traces": "Vista villuleitarspor" + }, + "data_description": { + "watchdog_interval": "Sekúndur á milli eftirlits með varðhundi meðan á hlaupi stendur. Sjálfgefið: 5s. VIÐVÖRUN: Gakktu úr skugga um að þetta sé HÆRRA en uppfærslubil skynjarans til að forðast rangar stopp (t.d. ef skynjari uppfærist á 60s fresti, notaðu 65s+).", + "no_update_active_timeout": "Fyrir birtingar-við-breytingar skynjara: þvingaðu aðeins til loka virkra hringrásar ef engar uppfærslur berast í þessar margar sekúndur. Lítið afl frágangur notar enn slökkt seinkun.", + "progress_reset_delay": "Eftir að lotu lýkur (100%) skaltu bíða í þessar margar sekúndur af aðgerðaleysi áður en þú endurstillir framvinduna í 0%. Sjálfgefið: 300s.", + "auto_maintenance": "Virkja sjálfvirkt viðhald (gera við sýni, sameina brot).", + "expose_debug_entities": "Sýna háþróaða skynjara (sjálfstraust, fasa, tvíræðni) til villuleitar.", + "save_debug_traces": "Geymdu nákvæma röðun og aflsporsgögn í sögu (eykur geymslunotkun)." + } + }, + "anti_wrinkle_section": { + "name": "Krumpuvarnarskjöldur (Þurrkarar)", + "description": "Hunsa tromlusnúninga eftir lotulok til að koma í veg fyrir draugalotur. Aðeins þurrkari / þvottaþurrkari.", + "data": { + "anti_wrinkle_enabled": "Hrukkuvörn (aðeins þurrkari)", + "anti_wrinkle_max_power": "Hámarksafl gegn hrukkum (W)", + "anti_wrinkle_max_duration": "Hámarkslengd gegn hrukkum (s)", + "anti_wrinkle_exit_power": "Útgangsstyrkur gegn hrukkum (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Hunsa stutta snúninga á litlum trommu eftir að lotu lýkur til að koma í veg fyrir draugalotur (aðeins þurrkari/þvottavél-þurrkari).", + "anti_wrinkle_max_power": "Sprungur við eða undir þessum krafti eru meðhöndlaðir sem snúningar gegn hrukkum í stað nýrra hringrása. Gildir aðeins þegar Anti-Wrinkle Shield er virkt.", + "anti_wrinkle_max_duration": "Hámarks sprengilengd til að meðhöndla sem snúning gegn hrukkum. Gildir aðeins þegar Anti-Wrinkle Shield er virkt.", + "anti_wrinkle_exit_power": "Ef kraftur fer niður fyrir þetta stig, farðu strax úr hrukkuvörn (true off). Gildir aðeins þegar Anti-Wrinkle Shield er virkt." + } + }, + "delay_start_section": { + "name": "Greining seinkaðrar ræsingar", + "description": "Meðhöndlaðu viðvarandi lágorku biðstöðu sem 'Bíður eftir ræsingu' þar til hringrásin byrjar í raun.", + "data": { + "delay_start_detect_enabled": "Virkja seinkun upphafsskynjunar", + "delay_confirm_seconds": "Seinkuð ræsing: staðfestingartími biðstöðu (s)", + "delay_timeout_hours": "Seinkun á byrjun: Hámarksbiðtími (klst.)" + }, + "data_description": { + "delay_start_detect_enabled": "Þegar kveikt er á því, greinist stuttur tæmingarstuðull með litlum krafti á eftir viðvarandi biðafli sem seinkun á ræsingu. Ástandsskynjarinn sýnir 'Bíður eftir ræsingu' meðan á seinkuninni stendur. Enginn áætlunartími eða framvindu er fylgst með fyrr en lotan byrjar í raun. Krefst að start_threshold_w sé stillt fyrir ofan tæmingarafl.", + "delay_confirm_seconds": "Afl þarf að vera á milli stöðvunarþröskulds (W) og ræsiskulds (W) í þetta margar sekúndur áður en WashData fer í stöðuna 'Bíður eftir ræsingu'. Síar út stutta toppa vegna valmyndaflakks. Sjálfgefið: 60 s.", + "delay_timeout_hours": "Öryggistími: ef vélin er enn í „Bíð eftir að ræsa“ eftir þessar margar klukkustundir, endurstillir WashData á Off. Sjálfgefið: 8 klst." + } + }, + "external_triggers_section": { + "name": "Ytri kveikjur, hurð og hlé", + "description": "Valfrjáls ytri lokakveikjuskynjari, hurðarskynjari fyrir hlé/hreint-greiningu og rofi sem slekkur á afli í hléi.", + "data": { + "external_end_trigger_enabled": "Virkja ytri hringrásarlok", + "external_end_trigger": "Ytri hringrásarskynjari", + "external_end_trigger_inverted": "Snúa kveikjulögfræði við (kveikja á OFF)", + "door_sensor_entity": "Hurðarskynjari", + "pause_cuts_power": "Slökktu á rafmagni þegar gert er hlé", + "switch_entity": "Skiptaeining (fyrir hlé á raforku)", + "notify_unload_delay_minutes": "Seinkun á tilkynningu um þvottahús (mín.)" + }, + "data_description": { + "external_end_trigger_enabled": "Virkjaðu eftirlit með utanaðkomandi tvíundarskynjara til að kveikja á því að hringrás sé lokið.", + "external_end_trigger": "Veldu tvíundarskynjaraeiningu. Þegar þessi skynjari ræsir mun núverandi lotu enda með stöðunni „lokið“.", + "external_end_trigger_inverted": "Sjálfgefið ræsir kveikjan þegar kveikt er á skynjaranum. Merktu við þennan reit til að kvikna þegar skynjarinn slekkur á sér í staðinn.", + "door_sensor_entity": "Valfrjáls tvöfaldur skynjari fyrir hurð vélarinnar (kveikt = opið, slökkt = lokað). Þegar hurðin opnast meðan á virkri lotu stendur mun WashData staðfesta að lotan sé vísvitandi í bið. Eftir að lotunni lýkur, ef hurðin er enn lokuð, breytist ástandið í „Hreint“ þar til hurðin er opnuð. Athugið: lokun hurðarinnar fer EKKI í gang sjálfkrafa aftur í hléi - endurupptöku verður að vera ræst handvirkt með hnappinum Resume Cycle eða þjónustu.", + "pause_cuts_power": "Þegar gert er hlé með Pause Cycle hnappinum eða þjónustunni, slökktu einnig á stillta rofaeiningunni. Tekur aðeins gildi ef skiptaeining er stillt fyrir þetta tæki.", + "switch_entity": "Skiptaeiningin til að skipta á þegar gert er hlé á eða haldið áfram. Nauðsynlegt þegar 'Slökkva á rafmagni við hlé' er virkt.", + "notify_unload_delay_minutes": "Sendu tilkynningu í gegnum tilkynningarásina eftir að lotunni lýkur og hurðin hefur ekki verið opnuð í þessar margar mínútur (þarfnast hurðarskynjara). Stilltu á 0 til að slökkva á. Sjálfgefið: 60 mín." + } + }, + "pump_section": { + "name": "Dælueftirlit", + "description": "Aðeins fyrir dælutegund tækis. Kveikir á atburði ef dæluhringrás keyrir lengur en þetta tímabil.", + "data": { + "pump_stuck_duration": "Viðvörunarþröskuldur við dælu föst (s) (aðeins dæla)" + }, + "data_description": { + "pump_stuck_duration": "Ef dælulota er enn í gangi eftir þessar margar sekúndur, er ha_washdata_pump_stuck atvik ræst einu sinni. Notaðu þetta til að greina fastan mótor (venjulegur dælugangur er undir 60 sekúndum). Sjálfgefið: 1800 s (30 mín). Aðeins gerð dælubúnaðar." + } + }, + "device_link_section": { + "name": "Tækjatenging", + "description": "Hægt er að tengja þetta WashData tæki við núverandi Home Assistant tæki (til dæmis snjallstunguna eða heimilistækið sjálft). WashData tækið er síðan sýnt sem „Tengt í gegnum“ það tæki. Skildu eftir tómt til að halda WashData sem sjálfstætt tæki.", + "data": { + "linked_device": "Tengt tæki" + }, + "data_description": { + "linked_device": "Veldu tækið til að tengja WashData við. Þetta bætir við 'Connected via' tilvísun í tækjaskránni; WashData heldur sínu eigin tækjakorti og einingum. Hreinsaðu valið til að fjarlægja tengilinn." + } + } + } + }, + "diagnostics": { + "title": "Greining og viðhald", + "description": "Keyra viðhaldsaðgerðir eins og að sameina sundurliðaðar lotur eða flytja vistuð gögn.\n\n**Geymslunotkun**\n\n- Skráarstærð: {file_size_kb} KB\n- Hringrásir: {cycle_count}\n- Prófílar: {profile_count}\n- Villuleitarspor: {debug_count}", + "menu_options": { + "reprocess_history": "Viðhald: Endurvinnsla og fínstilla gögn", + "clear_debug_data": "Hreinsa villuleitargögn (lausa um pláss)", + "wipe_history": "Þurrkaðu ÖLL gögn fyrir þetta tæki (óafturkræft)", + "export_import": "Flytja út / flytja inn JSON með stillingum (afrita/líma)", + "menu_back": "← Til baka" + } + }, + "clear_debug_data": { + "title": "Hreinsaðu villuleitargögn", + "description": "Ertu viss um að þú viljir eyða öllum geymdum villuleitarsporum? Þetta mun losa um pláss en fjarlægja nákvæmar upplýsingar um röðun úr fyrri lotum." + }, + "manage_cycles": { + "title": "Stjórna hringrásum", + "description": "Nýlegar lotur:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Sjálfvirk merking á gömlum hringrásum", + "select_cycle_to_label": "Merki ákveðin hringrás", + "select_cycle_to_delete": "Eyða hringrás", + "interactive_editor": "Sameina/skipta gagnvirkum ritstjóra", + "trim_cycle_select": "Gögn klippingar hringrásar", + "menu_back": "← Til baka" + } + }, + "manage_cycles_empty": { + "title": "Stjórna hringrásum", + "description": "Engar lotur skráðar ennþá.", + "menu_options": { + "auto_label_cycles": "Sjálfvirk merking á gömlum hringrásum", + "menu_back": "← Til baka" + } + }, + "manage_profiles": { + "title": "Stjórna prófílum", + "description": "Yfirlit yfir prófíl:\n{profile_summary}", + "menu_options": { + "create_profile": "Búðu til nýjan prófíl", + "edit_profile": "Breyta/endurnefna prófíl", + "delete_profile_select": "Eyða prófíl", + "profile_stats": "Tölfræði prófíls", + "cleanup_profile": "Hreinsaðu sögu - Grafið og eytt", + "assign_profile_phases_select": "Úthluta áfangasviðum", + "menu_back": "← Til baka" + } + }, + "manage_profiles_empty": { + "title": "Stjórna prófílum", + "description": "Engir prófílar búnir til ennþá.", + "menu_options": { + "create_profile": "Búðu til nýjan prófíl", + "menu_back": "← Til baka" + } + }, + "manage_phase_catalog": { + "title": "Stjórna áfangaskrá", + "description": "Fasaskrá (sjálfgefið fyrir þetta tæki + sérsniðnir áfangar):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Búa til nýjan áfanga", + "phase_catalog_edit_select": "Breyta áfanga", + "phase_catalog_delete": "Eyða áfanga", + "menu_back": "← Til baka" + } + }, + "phase_catalog_create": { + "title": "Búðu til sérsniðna áfanga", + "description": "Búðu til sérsniðið áfangaheiti og lýsingu og veldu hvaða tækisgerð það á við.", + "data": { + "target_device_type": "Tegund marktækis", + "phase_name": "Fasa nafn", + "phase_description": "Áfangalýsing" + } + }, + "phase_catalog_edit_select": { + "title": "Breyta sérsniðnum áfanga", + "description": "Veldu sérsniðna áfanga til að breyta.", + "data": { + "phase_name": "Sérsniðinn áfangi" + } + }, + "phase_catalog_edit": { + "title": "Breyta sérsniðnum áfanga", + "description": "Breytingaráfangi: {phase_name}", + "data": { + "phase_name": "Fasa nafn", + "phase_description": "Áfangalýsing" + } + }, + "phase_catalog_delete": { + "title": "Eyða sérsniðnum áfanga", + "description": "Veldu sérsniðna áfanga til að eyða. Öll úthlutað svið sem nota þennan áfanga verða fjarlægð.", + "data": { + "phase_name": "Sérsniðinn áfangi" + } + }, + "assign_profile_phases": { + "title": "Úthluta áfangasviðum", + "description": "Forskoðun á áfanga fyrir prófíl: **{profile_name}**\n\n{timeline_svg}\n\n**Núverandi svið:**\n{current_ranges}\n\nNotaðu aðgerðir hér að neðan til að bæta við, breyta, eyða eða vista svið.", + "menu_options": { + "assign_profile_phases_add": "Bæta við áfangasviði", + "assign_profile_phases_edit_select": "Breyta áfangasviði", + "assign_profile_phases_delete": "Eyða áfangasviði", + "phase_ranges_clear": "Hreinsaðu öll svið", + "assign_profile_phases_auto_detect": "Greina áfanga sjálfkrafa", + "phase_ranges_save": "Vista og skila", + "menu_back": "← Til baka" + } + }, + "assign_profile_phases_add": { + "title": "Bæta við áfangasviði", + "description": "Bættu einu áfangasviði við núverandi drög.", + "data": { + "phase_name": "Áfangi", + "start_min": "Byrjunarmínúta", + "end_min": "Lokamínúta" + } + }, + "assign_profile_phases_edit_select": { + "title": "Breyta áfangasviði", + "description": "Veldu svið til að breyta.", + "data": { + "range_index": "Fasasvið" + } + }, + "assign_profile_phases_edit": { + "title": "Breyta áfangasviði", + "description": "Uppfærðu valið áfangasvið.", + "data": { + "phase_name": "Áfangi", + "start_min": "Byrjunarmínúta", + "end_min": "Lokamínúta" + } + }, + "assign_profile_phases_delete": { + "title": "Eyða áfangasviði", + "description": "Veldu svið til að fjarlægja úr drögunum.", + "data": { + "range_index": "Fasasvið" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Sjálfvirk greindir áfangar", + "description": "Prófíll: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} áfanga(r) greindir sjálfkrafa.** Veldu aðgerð hér að neðan.", + "data": { + "action": "Aðgerð" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nafn greint áfangar", + "description": "Prófíll: **{profile_name}**\n\n**{detected_count} áfanga(r) fundust:**\n{ranges_summary}\n\nGefðu hverjum áfanga nafn." + }, + "assign_profile_phases_select": { + "title": "Úthluta áfangasviðum", + "description": "Veldu snið til að stilla áfangasvið.", + "data": { + "profile": "Prófíll" + } + }, + "profile_stats": { + "title": "Tölfræði prófíls", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Búðu til nýjan prófíl", + "description": "Búðu til nýtt snið handvirkt eða úr fyrri lotu.\n\nDæmi um prófílnafn: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Nafn prófíls", + "reference_cycle": "Viðmiðunarlota (valfrjálst)", + "manual_duration": "Handvirk lengd (mínútur)" + } + }, + "edit_profile": { + "title": "Breyta prófíl", + "description": "Veldu prófíl til að endurnefna", + "data": { + "profile": "Prófíll" + } + }, + "rename_profile": { + "title": "Breyta upplýsingar um prófíl", + "description": "Núverandi prófíll: {current_name}", + "data": { + "new_name": "Nafn prófíls", + "manual_duration": "Handvirk lengd (mínútur)" + } + }, + "delete_profile_select": { + "title": "Eyða prófíl", + "description": "Veldu prófíl til að eyða", + "data": { + "profile": "Prófíll" + } + }, + "delete_profile_confirm": { + "title": "Staðfestu Eyða prófíl", + "description": "⚠️ Þetta mun eyða prófílnum varanlega: {profile_name}", + "data": { + "unlabel_cycles": "Fjarlægðu merki úr lotum með þessu sniði" + } + }, + "auto_label_cycles": { + "title": "Sjálfvirk merking á gömlum hringrásum", + "description": "Fann {total_count} lotur samtals. Prófílar: {profiles}", + "data": { + "confidence_threshold": "Lágmarksöryggi (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Veldu Cycle to label", + "description": "✓ = lokið, ⚠ = haldið áfram, ✗ = truflað", + "data": { + "cycle_id": "Hringrás" + } + }, + "select_cycle_to_delete": { + "title": "Eyða hringrás", + "description": "⚠️ Þetta mun eyða valinni lotu varanlega", + "data": { + "cycle_id": "Hringdu til að eyða" + } + }, + "label_cycle": { + "title": "Label Cycle", + "description": "{cycle_info}", + "data": { + "profile_name": "Prófíll", + "new_profile_name": "Nýtt prófílnafn (ef búið er til)" + } + }, + "post_process": { + "title": "Ferlissaga (sameina/skipt)", + "description": "Hreinsaðu upp lotuferil með því að sameina sundurliðaðar keyrslur og skipta ranglega sameinuðum lotum innan tímaglugga. Sláðu inn fjölda klukkustunda til að líta til baka (notaðu 999999 fyrir alla).", + "data": { + "time_range": "Yfirlitsgluggi (klst.)", + "gap_seconds": "Sameina/skipta bili (sekúndur)" + }, + "data_description": { + "time_range": "Fjöldi klukkustunda til að leita að sundurliðuðum lotum til að sameinast. Notaðu 999999 til að vinna úr öllum sögulegum gögnum.", + "gap_seconds": "Þröskuldur til að ákveða skiptingu vs sameiningu. Skýmri eyður en þetta sameinast. LENGRI eyður en þetta skiptast. Lækkaðu þetta til að þvinga fram skiptingu á hringrás sem var sameinuð rangt." + } + }, + "cleanup_profile": { + "title": "Hreinsunarferill - Veldu prófíl", + "description": "Veldu prófíl til að sjá. Þetta mun búa til línurit sem sýnir allar fyrri lotur fyrir þetta snið til að hjálpa til við að bera kennsl á útlægar.", + "data": { + "profile": "Prófíll" + } + }, + "cleanup_select": { + "title": "Hreinsaðu sögu - Grafið og eytt", + "description": "![Línurit]({graph_url})\n\n**Sjánarmynd {profile_name}**\n\nGrafið sýnir einstaka lotur sem litaðar línur. Finndu útlínur (línur langt frá hópnum) og passaðu lit þeirra á listanum hér að neðan til að eyða þeim.\n\n**Veldu lotur til að eyða varanlega:**", + "data": { + "cycles_to_delete": "Hringrás til að eyða (athugaðu samsvarandi liti)" + } + }, + "interactive_editor": { + "title": "Sameina/skipta gagnvirkum ritstjóra", + "description": "Veldu handvirka aðgerð til að framkvæma á hringrásarsögunni þinni.", + "menu_options": { + "editor_split": "Skiptu hringrás (finndu eyður)", + "editor_merge": "Sameina hringrás (sameina brot)", + "editor_delete": "Eyða hringrás(um)", + "menu_back": "← Til baka" + } + }, + "editor_select": { + "title": "Veldu Cycles", + "description": "Veldu hringrásina sem á að vinna úr.\n\n{info_text}", + "data": { + "selected_cycles": "Hringrásir" + } + }, + "editor_configure": { + "title": "Stilla og nota", + "description": "{preview_md}", + "data": { + "confirm_action": "Aðgerð", + "merged_profile": "Úrkomandi prófíll", + "new_profile_name": "Nýtt prófílnafn", + "confirm_commit": "Já, ég staðfesti þessa aðgerð", + "segment_0_profile": "Hluti 1 prófíl", + "segment_1_profile": "Hluti 2 prófíl", + "segment_2_profile": "Hluti 3 prófíl", + "segment_3_profile": "Hluti 4 prófíl", + "segment_4_profile": "Hluti 5 prófíl", + "segment_5_profile": "Hluti 6 prófíl", + "segment_6_profile": "Hluti 7 prófíl", + "segment_7_profile": "Hluti 8 prófíl", + "segment_8_profile": "Hluti 9 prófíl", + "segment_9_profile": "Hluti 10 prófíl" + } + }, + "editor_split_params": { + "title": "Skipting stillingar", + "description": "Stilltu næmni fyrir að skipta þessari lotu. Öll aðgerðalaus bil sem er lengri en þetta mun valda sundrun.", + "data": { + "split_mode": "Skiptingaraðferð" + } + }, + "editor_split_auto_params": { + "title": "Stilling sjálfskiptingar", + "description": "Aðlagaðu næmni fyrir skiptingu þessarar hringrásar. Hvert aðgerðalaust bil sem er lengra en þetta veldur skiptingu.", + "data": { + "min_gap_seconds": "Þröskuldur skiptingarbils (sekúndur)" + } + }, + "editor_split_manual_params": { + "title": "Handvirk skiptingartímamerki", + "description": "{preview_md}Hringrásargluggi: **{cycle_start_wallclock} → {cycle_end_wallclock}** þann {cycle_date}.\n\nSláðu inn eitt eða fleiri skiptingartímamerki (`HH:MM` eða `HH:MM:SS`), eitt í hverri línu. Hringrásin verður skorin við hvert tímamerki — N tímamerki gefa N+1 hluta.", + "data": { + "split_timestamps": "Skiptingartímastimplar" + } + }, + "reprocess_history": { + "title": "Viðhald: Endurvinnsla og fínstilla gögn", + "description": "Framkvæmir djúphreinsun á sögulegum gögnum:\n1. Klippir núll-afl lestur (þögn).\n2. Lagar tímasetningu/tíma lotu.\n3. Endurreiknar öll tölfræðilíkön (umslög).\n\n** Keyrðu þetta til að laga gagnavandamál eða beita nýjum reikniritum.**" + }, + "wipe_history": { + "title": "Þurrka sögu (aðeins prófun)", + "description": "⚠️ Þetta mun eyða varanlega ÖLLUM vistuðum lotum og prófílum fyrir þetta tæki. Þetta er ekki hægt að afturkalla!" + }, + "export_import": { + "title": "Flytja út / flytja inn JSON", + "description": "Afritaðu/límdu lotur, snið OG stillingar fyrir þetta tæki. Flyttu út fyrir neðan eða límdu JSON til að flytja inn.", + "data": { + "mode": "Aðgerð", + "json_payload": "Ljúktu stillingum JSON" + }, + "data_description": { + "mode": "Veldu Flytja út til að afrita núverandi gögn og stillingar, eða Flytja inn til að líma útflutt gögn úr öðru WashData tæki.", + "json_payload": "Fyrir útflutning: afritaðu þetta JSON (inniheldur lotur, snið og allar fínstilltar stillingar). Fyrir innflutning: límdu JSON flutt út frá WashData." + } + }, + "record_cycle": { + "title": "Record Cycle", + "description": "Taktu upp lotu handvirkt til að búa til hreint snið án truflana.\n\nStaða: **{status}**\nLengd: {duration}s\nSýnishorn: {samples}", + "menu_options": { + "record_refresh": "Endurnýja stöðu", + "record_stop": "Stöðva upptöku (vista og vinna)", + "record_start": "Byrjaðu nýja upptöku", + "record_process": "Vinnsla síðustu upptöku (klippa og vista)", + "record_discard": "Fleygðu síðustu upptöku", + "menu_back": "← Til baka" + } + }, + "record_process": { + "title": "Ferli Upptaka", + "description": "![Línurit]({graph_url})\n\n**Línurit sýnir hráa upptöku.** Blár = Halda, Rauður = Fyrirhuguð klipping.\n*Línurit er kyrrstætt og uppfærist ekki með formbreytingum.*\n\nUpptaka hætt. Snyrtingar eru stilltar við greint sýnatökutíðni (~{sampling_rate}s).\n\nHrátímalengd: {duration}s\nSýnishorn: {samples}", + "data": { + "head_trim": "Trim Start (sekúndur)", + "tail_trim": "Klippa enda (sekúndur)", + "save_mode": "Vista áfangastað", + "profile_name": "Nafn prófíls" + } + }, + "learning_feedbacks": { + "title": "Viðbrögð við námi", + "description": "Ábendingar í bið: {count}\n\n{pending_table}\n\nVeldu beiðni um endurskoðun hringrásar til að vinna úr.", + "menu_options": { + "learning_feedbacks_pick": "Skoða ábendingu í bið", + "learning_feedbacks_dismiss_all": "Hafna öllum ábendingum í bið", + "menu_back": "← Til baka" + } + }, + "learning_feedbacks_pick": { + "title": "Veldu endurgjöf til yfirferðar", + "description": "Veldu beiðni um yfirferð á hringrás til að opna.", + "data": { + "selected_feedback": "Veldu hringrás til yfirferðar" + } + }, + "learning_feedbacks_empty": { + "title": "Viðbrögð við námi", + "description": "Engar ábendingarbeiðnir fundust.", + "menu_options": { + "menu_back": "← Til baka" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Hafna allri endurgjöf sem bíður", + "description": "Þú ert að fara að hafna **{count}** endurgjafarbeiðnum sem bíða. Hafnaðar hringrásir verða áfram í ferlinum þínum en munu ekki leggja til nýtt námsmerki. Þetta er ekki hægt að afturkalla.\n\n✅ Hakaðu í reitinn hér að neðan og smelltu á **Senda** til að hafna öllu.\n❌ Skildu hann óhakaðan og smelltu á **Senda** til að hætta við og fara til baka.", + "data": { + "confirm_dismiss_all": "Staðfesta: hafna öllum ábendingarbeiðnum í bið" + } + }, + "resolve_feedback": { + "title": "Leysa viðbrögð", + "description": "{comparison_img}{candidates_table}\n**Genndur prófíll**: {detected_profile} ({confidence_pct}%)\n**Áætluð lengd**: {est_duration_min} mín\n**Raunveruleg lengd**: {act_duration_min} mín\n\n__Veldu aðgerð hér að neðan:__", + "data": { + "action": "Hvað myndir þú vilja gera?", + "corrected_profile": "Rétt forrit (ef leiðrétt er)", + "corrected_duration": "Rétt lengd í sekúndum (ef leiðrétt er)" + } + }, + "trim_cycle_select": { + "title": "Klipptu hringrás - Veldu hringrás", + "description": "Veldu hringrás til að klippa. ✓ = lokið, ⚠ = haldið áfram, ✗ = truflað", + "data": { + "cycle_id": "Hringrás" + } + }, + "trim_cycle": { + "title": "Klipptu hringrás", + "description": "Núverandi gluggi: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aðgerð" + } + }, + "trim_cycle_start": { + "title": "Stilltu klippiupphaf", + "description": "Hringrás gluggi: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNúverandi byrjun: **{current_wallclock}** (+{current_offset_min} mín frá upphafi lotu)\n\nVeldu nýjan upphafstíma með því að nota klukkuvalið hér að neðan.", + "data": { + "trim_start_time": "Nýr upphafstími", + "trim_start_min": "Ný byrjun (mínútur frá byrjun hringrásar)" + } + }, + "trim_cycle_end": { + "title": "Stilltu klippienda", + "description": "Hringrás gluggi: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNúverandi lok: **{current_wallclock}** (+{current_offset_min} mín frá upphafi lotu)\n\nVeldu nýjan lokatíma með því að nota klukkuvalið hér að neðan.", + "data": { + "trim_end_time": "Nýr lokatími", + "trim_end_min": "Nýr endir (mínútur frá upphafi lotu)" + } + } + }, + "error": { + "import_failed": "Innflutningur mistókst. Vinsamlegast límdu gilt WashData útflutnings JSON.", + "select_exactly_one": "Veldu nákvæmlega eina hringrás fyrir þessa aðgerð.", + "select_at_least_one": "Veldu að minnsta kosti eina hringrás fyrir þessa aðgerð.", + "select_at_least_two": "Veldu að minnsta kosti tvær hringrásir fyrir þessa aðgerð.", + "profile_exists": "Prófíll með þessu nafni er þegar til", + "rename_failed": "Mistókst að endurnefna prófíl. Hugsanlega er prófíllinn ekki til eða nýtt nafn er þegar tekið.", + "assignment_failed": "Mistókst að úthluta prófíl til að hringja", + "duplicate_phase": "Áfangi með þessu nafni er þegar til.", + "phase_not_found": "Valinn áfangi fannst ekki.", + "invalid_phase_name": "Fasaheiti má ekki vera autt.", + "phase_name_too_long": "Fasaheiti er of langt.", + "invalid_phase_range": "Fasasvið er ógilt. Endir verður að vera meiri en byrjun.", + "invalid_phase_timestamp": "Tímastimpill er ógildur. Notaðu ÁÁÁÁ-MM-DD HH:MM eða HH:MM snið.", + "overlapping_phase_ranges": "Áfangasvið skarast. Stilltu svið og reyndu aftur.", + "incomplete_phase_row": "Hver áfangalína verður að innihalda áfanga auk heill upphafs/lokagilda fyrir valda stillingu.", + "timestamp_mode_cycle_required": "Vinsamlegast veldu upprunalotu þegar þú notar tímastimplaham.", + "no_phase_ranges": "Engin áfangasvið eru enn tiltæk fyrir þá aðgerð.", + "feedback_notification_title": "WashData: Staðfestu hringrás ({device})", + "feedback_notification_message": "**Tæki**: {device}\n**Prógramm**: {program} ({confidence}% sjálfstraust)\n**Tími**: {time}\n\nWashData þarf á aðstoð þinni að halda til að staðfesta þessa greindu lotu.\n\nVinsamlegast farðu í **Stillingar > Tæki og þjónusta > WashData > Stilla > Námsendurgjöf** til að staðfesta eða leiðrétta þessa niðurstöðu.", + "suggestions_ready_notification_title": "WashData: Ráðlagðar stillingar tilbúnar ({device})", + "suggestions_ready_notification_message": "Skynjarinn **Tillögur að stillingum** tilkynnir nú **{count}** ráðleggingar sem hægt er að nota.\n\nTil að fara yfir og nota þær: **Stillingar > Tæki og þjónusta > WashData > Stilla > Ítarlegar stillingar > Nota ráðlögð gildi**.\n\nTillögur eru valfrjálsar og sýndar til skoðunar áður en þú vistar.", + "trim_range_invalid": "Byrjun verður að vera fyrir lok. Vinsamlegast stilltu klippingarpunktana þína.", + "trim_failed": "Snyrting mistókst. Hringrásin gæti ekki lengur verið til eða glugginn er tómur.", + "invalid_split_timestamp": "Tímastimpill er ógildur eða utan glugga hringrásarinnar. Notaðu HH:MM eða HH:MM:SS innan glugga hringrásarinnar.", + "no_split_segments_found": "Ekki tókst að mynda gild hlutaskeið úr þessum tímastimplum. Gakktu úr skugga um að hver tímastimpill sé innan glugga hringrásarinnar og að hlutaskeið séu að minnsta kosti 1 mínútu löng.", + "auto_tune_suggestion": "{device_type} {device_title} fundu draugalotur. Ráðlagður min_power breyting: {current_min}W -> {new_min}W (ekki beitt sjálfkrafa).", + "auto_tune_title": "WashData sjálfvirk stilling", + "auto_tune_fallback": "{device_type} {device_title} fundu draugalotur. Ráðlagður min_power breyting: {current_min}W -> {new_min}W (ekki beitt sjálfkrafa)." + }, + "abort": { + "no_cycles_found": "Engar lotur fundust", + "no_split_segments_found": "Engin skiptanleg hlutaskeið fundust í völdu hringrásinni.", + "cycle_not_found": "Ekki tókst að hlaða valda hringrás.", + "no_power_data": "Engin gögn um orkuspor eru tiltæk fyrir valda lotu.", + "no_profiles_found": "Engir prófílar fundust. Búðu til prófíl fyrst.", + "no_profiles_for_matching": "Engir prófílar í boði fyrir samsvörun. Búðu til prófíla fyrst.", + "no_unlabeled_cycles": "Allar lotur eru þegar merktar", + "no_suggestions": "Engin ráðlögð gildi eru enn tiltæk. Keyrðu nokkrar lotur og reyndu aftur.", + "no_predictions": "Engar spár", + "no_custom_phases": "Engir sérsniðnir áfangar fundust.", + "reprocess_success": "Tókst að endurvinna {count} lotur.", + "debug_data_cleared": "Tókst að hreinsa villuleitargögn úr {count} lotum." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cycle Start", + "cycle_finish": "Ljúka hringrás", + "cycle_live": "Framfarir í beinni" + } + }, + "common_text": { + "options": { + "unlabeled": "(Ómerkt)", + "create_new_profile": "Búðu til nýjan prófíl", + "remove_label": "Fjarlægðu merkimiðann", + "no_reference_cycle": "(Engin viðmiðunarlota)", + "all_device_types": "Allar gerðir tækja", + "current_suffix": "(núverandi)", + "editor_select_info": "Veldu 1 lotu til að skipta eða 2+ lotur til að sameinast.", + "editor_select_info_split": "Veldu 1 hringrás til að skipta.", + "editor_select_info_merge": "Veldu 2+ hringrásir til að sameina.", + "editor_select_info_delete": "Veldu 1+ hringrás(ir) til að eyða.", + "no_cycles_recorded": "Engar lotur skráðar ennþá.", + "profile_summary_line": "- **{name}**: {count} lotur, {avg}m meðaltal", + "no_profiles_created": "Engir prófílar búnir til ennþá.", + "phase_preview": "Forskoðun á áfanga", + "phase_preview_no_curve": "Meðalprófílferill er ekki tiltækur ennþá. Keyra/merkja fleiri lotur fyrir þetta snið.", + "average_power_curve": "Meðalaflsferill", + "unit_min": "mín", + "profile_option_fmt": "{name} ({count} lotur, ~{duration}m meðaltal)", + "profile_option_short_fmt": "{name} ({count} lotur, ~{duration}m)", + "deleted_cycles_from_profile": "Eyddi {count} lotum frá {profile}.", + "not_enough_data_graph": "Ekki næg gögn til að búa til línurit.", + "no_profiles_found": "Engir prófílar fundust.", + "cycle_info_fmt": "Hringrás: {start}, {duration}m, núverandi: {label}", + "top_candidates_header": "### Helstu frambjóðendur", + "tbl_profile": "Prófíll", + "tbl_confidence": "Sjálfstraust", + "tbl_correlation": "Fylgni", + "tbl_duration_match": "Lengd Match", + "detected_profile": "Fann prófíl", + "estimated_duration": "Áætluð tímalengd", + "actual_duration": "Raunveruleg lengd", + "choose_action": "__Veldu aðgerð hér að neðan:__", + "tbl_count": "Telja", + "tbl_avg": "Meðaltal", + "tbl_min": "Min", + "tbl_max": "Hámark", + "tbl_energy_avg": "Orka (meðal)", + "tbl_energy_total": "Orka (samtals)", + "tbl_consistency": "Samanstanda.", + "tbl_last_run": "Síðasta hlaup", + "graph_legend_title": "Grafík Legend", + "graph_legend_body": "Bláa bandið táknar lágmarks- og hámarksaflspennusvið sem sést. Línan sýnir meðalaflsferilinn.", + "recording_preview": "Forskoðun upptöku", + "trim_start": "Klippa upphaf", + "trim_end": "Klipptu enda", + "split_preview_title": "Skipt forskoðun", + "split_preview_found_fmt": "Fann {count} hluti.", + "split_preview_confirm_fmt": "Smelltu á Staðfesta til að skipta þessari lotu í {count} aðskildar lotur.", + "merge_preview_title": "Sameina forskoðun", + "merge_preview_joining_fmt": "Tengist {count} lotum. Eyður verða fylltar með 0W lestri.", + "editor_delete_preview_title": "Forskoðun eyðingar", + "editor_delete_preview_intro": "Valdar hringrásir verða eyddar varanlega:", + "editor_delete_preview_confirm": "Smelltu á Staðfesta til að eyða þessum hringrásarskrám varanlega.", + "no_power_preview": "*Engin orkugögn tiltæk til forskoðunar.*", + "profile_comparison": "Samanburður á prófíl", + "actual_cycle_label": "Þessi hringrás (raunveruleg)", + "storage_usage_header": "Geymslunotkun", + "storage_file_size": "Skráarstærð", + "storage_cycles": "Hringrásir", + "storage_profiles": "Snið", + "storage_debug_traces": "Villuleita spor", + "overview_suffix": "Yfirlit", + "phase_preview_for_profile": "Fasa forskoðun fyrir prófíl", + "current_ranges_header": "Núverandi svið", + "assign_phases_help": "Notaðu aðgerðir hér að neðan til að bæta við, breyta, eyða eða vista svið.", + "profile_summary_header": "Samantekt prófíls", + "recent_cycles_header": "Nýlegar lotur", + "trim_cycle_preview_title": "Forskoðun klippingar hringrásar", + "trim_cycle_preview_no_data": "Engin orkugögn tiltæk fyrir þessa lotu.", + "trim_cycle_preview_kept_suffix": "haldið", + "table_program": "Dagskrá", + "table_when": "Hvenær", + "table_length": "Lengd", + "table_match": "Samsvörun", + "table_profile": "Prófíll", + "table_cycles": "Hringrásir", + "table_avg_length": "Meðallengd", + "table_last_run": "Síðasta hlaup", + "table_avg_energy": "Meðalorka", + "table_detected_program": "Greint forrit", + "table_confidence": "Sjálfstraust", + "table_reported": "Tilkynnt", + "phase_builtin_suffix": "(Innbyggt)", + "phase_none_available": "Engir áfangar í boði.", + "settings_deprecation_warning": "⚠️ **Undanleg tækisgerð:** Áætlað er að {device_type} verði fjarlægður í framtíðarútgáfu. Samsvarandi leiðsla WashData gefur ekki áreiðanlegar niðurstöður fyrir þennan tækjaflokk. Samþætting þín heldur áfram að virka í gegnum afskriftartímabilið; til að þagga niður í þessari viðvörun skaltu skipta **Tegun tækis** hér að neðan yfir í eina af studdu gerðunum (þvottavél, þurrkara, þvottavél-þurrkara, uppþvottavél, loftsteikingarvél, brauðvél eða dælu), eða í **Annað (íþróað)** ef heimilistækið þitt passar ekki við neinar af þeim studdu gerðum. **Annað (Advanced)** sendir viljandi almennar sjálfgefnar stillingar sem eru ekki stilltar fyrir neitt sérstakt tæki, svo þú þarft að stilla þröskulda, tímamörk og samsvarandi færibreytur sjálfur; allar núverandi stillingar þínar varðveitast þegar þú skiptir.", + "phase_other_device_types": "Aðrar gerðir tækja:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Skiptu hringrás (finndu eyður)", + "merge": "Sameina hringrás (sameina brot)", + "delete": "Eyða hringrás(um)" + } + }, + "split_mode": { + "options": { + "auto": "Finna biðbil sjálfkrafa", + "manual": "Handvirk(ur) tímastimpill(ar)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Viðhald: Endurvinnsla og fínstilla gögn", + "clear_debug_data": "Hreinsa villuleitargögn (lausa um pláss)", + "wipe_history": "Þurrkaðu ÖLL gögn fyrir þetta tæki (óafturkræft)", + "export_import": "Flytja út / flytja inn JSON með stillingum (afrita/líma)" + } + }, + "export_import_mode": { + "options": { + "export": "Flytja út öll gögn", + "import": "Flytja inn / sameina gögn" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Sjálfvirk merking á gömlum hringrásum", + "select_cycle_to_label": "Merki ákveðin hringrás", + "select_cycle_to_delete": "Eyða hringrás", + "interactive_editor": "Sameina/skipta gagnvirkum ritstjóra", + "trim_cycle": "Gögn klippingar hringrásar" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Búðu til nýjan prófíl", + "edit_profile": "Breyta/endurnefna prófíl", + "delete_profile": "Eyða prófíl", + "profile_stats": "Tölfræði prófíls", + "cleanup_profile": "Hreinsaðu sögu - Grafið og eytt", + "assign_phases": "Úthluta áfangasviðum" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Búa til nýjan áfanga", + "edit_custom_phase": "Breyta áfanga", + "delete_custom_phase": "Eyða áfanga" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Bæta við áfangasviði", + "edit_range": "Breyta áfangasviði", + "delete_range": "Eyða áfangasviði", + "clear_ranges": "Hreinsaðu öll svið", + "auto_detect_ranges": "Greina áfanga sjálfkrafa", + "save_ranges": "Vista og skila" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Endurnýja stöðu", + "stop_recording": "Stöðva upptöku (vista og vinna)", + "start_recording": "Byrjaðu nýja upptöku", + "process_recording": "Vinnsla síðustu upptöku (klippa og vista)", + "discard_recording": "Fleygðu síðustu upptöku" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Búðu til nýjan prófíl", + "existing_profile": "Bæta við núverandi prófíl", + "discard": "Fargaðu upptöku" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Staðfesta - Rétt uppgötvun", + "correct": "Rétt - Veldu Dagskrá/Tímalengd", + "ignore": "Hunsa - False Positive/Noisy Cycle", + "delete": "Eyða - Fjarlægja úr sögu" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nefndu og notaðu áfanga", + "cancel": "Farðu til baka án breytinga" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Stilltu upphafspunkt", + "set_end": "Stilltu endapunkt", + "reset": "Endurstilla á fulla lengd", + "apply": "Beita klippingu", + "cancel": "Hætta við" + } + }, + "device_type": { + "options": { + "washing_machine": "Þvottavél", + "dryer": "Þurrkari", + "washer_dryer": "Þvottavél-þurrkari samsettur", + "dishwasher": "Uppþvottavél", + "coffee_machine": "Kaffivél (úrelt)", + "ev": "Rafmagns ökutæki (úrelt)", + "air_fryer": "Air Fryer", + "heat_pump": "Varmadæla (úrelt)", + "bread_maker": "Brauðgerðarmaður", + "pump": "Dæla / Sump Pump", + "oven": "Ofn (úreltur)", + "other": "Annað (háþróað)" + } + } + }, + "services": { + "label_cycle": { + "name": "Label Cycle", + "description": "Úthlutaðu núverandi prófíl við fyrri lotu eða fjarlægðu merkið.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavél tæki til að merkja." + }, + "cycle_id": { + "name": "Auðkenni hringrásar", + "description": "Auðkenni lotunnar sem á að merkja." + }, + "profile_name": { + "name": "Nafn prófíls", + "description": "Heiti sniðs sem fyrir er (búið til snið í valmyndinni Stjórna sniðum). Skildu eftir autt til að fjarlægja merkimiðann." + } + } + }, + "create_profile": { + "name": "Búðu til prófíl", + "description": "Búðu til nýtt snið (sjálfstætt eða byggt á viðmiðunarferli).", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavélabúnaðurinn." + }, + "profile_name": { + "name": "Nafn prófíls", + "description": "Heiti fyrir nýja prófílinn (t.d. 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Auðkenni tilvísunarhrings", + "description": "Valfrjálst lotuauðkenni til að byggja þetta prófíl á." + } + } + }, + "delete_profile": { + "name": "Eyða prófíl", + "description": "Eyddu sniði og afmerktu hringrás með því að nota það.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavélabúnaðurinn." + }, + "profile_name": { + "name": "Nafn prófíls", + "description": "Prófíllinn til að eyða." + }, + "unlabel_cycles": { + "name": "Afmerkja hringrásir", + "description": "Fjarlægðu prófílmerki úr lotum með því að nota þetta prófíl." + } + } + }, + "auto_label_cycles": { + "name": "Sjálfvirk merking á gömlum hringrásum", + "description": "Merktu afturvirkt ómerktar lotur með því að nota prófílsamsvörun.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavélabúnaðurinn." + }, + "confidence_threshold": { + "name": "Sjálfstraustsþröskuldur", + "description": "Lágmarksöryggi (0,50-0,95) til að setja á merki." + } + } + }, + "export_config": { + "name": "Flytja út stillingar", + "description": "Flyttu út prófíla, lotur og stillingar þessa tækis í JSON skrá (á hvert tæki).", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavél tæki til að flytja út." + }, + "path": { + "name": "Slóð", + "description": "Valfrjáls alger skráarslóð til að skrifa (sjálfgefið er /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Flytja inn stillingar", + "description": "Flytja inn snið, lotur og stillingar fyrir þetta tæki úr JSON útflutningsskrá (stillingar gætu verið skrifaðar yfir).", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavélin til að flytja inn í." + }, + "path": { + "name": "Slóð", + "description": "Alger slóð að útflutnings JSON skránni sem á að flytja inn." + } + } + }, + "submit_cycle_feedback": { + "name": "Sendu Cycle Feedback", + "description": "Staðfestu eða leiðréttu sjálfvirkt forrit sem greint hefur verið frá eftir að lotunni er lokið. Gefðu upp annað hvort 'entry_id' (háþróað) eða 'device_id' (ráðlagt).", + "fields": { + "device_id": { + "name": "Tæki", + "description": "WashData tækið (mælt með í staðinn fyrir entry_id)." + }, + "entry_id": { + "name": "Inngangsauðkenni", + "description": "Auðkenni stillingarfærslu fyrir tækið (valkostur við device_id)." + }, + "cycle_id": { + "name": "Auðkenni hringrásar", + "description": "Auðkenni hringrásarinnar sem sýnt er í endurgjöfstilkynningunni / logs." + }, + "user_confirmed": { + "name": "Staðfestu uppgötvað forrit", + "description": "Stilltu satt ef forritið sem fannst er rétt." + }, + "corrected_profile": { + "name": "Leiðrétt prófíl", + "description": "Ef það er ekki staðfest skaltu gefa upp rétt snið/nafn forrits." + }, + "corrected_duration": { + "name": "Leiðrétt lengd (sekúndur)", + "description": "Valfrjáls leiðrétt lengd í sekúndum." + }, + "notes": { + "name": "Skýringar", + "description": "Valfrjálsar athugasemdir um þessa lotu." + } + } + }, + "record_start": { + "name": "Upptaka hringrás Start", + "description": "Byrjaðu handvirkt að taka upp hreina lotu (framhjá öllum samsvarandi rökfræði).", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavél tæki til að taka upp." + } + } + }, + "record_stop": { + "name": "Record Cycle Stop", + "description": "Stöðva handvirka upptöku.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "Þvottavél tæki til að hætta upptöku." + } + } + }, + "trim_cycle": { + "name": "Klipptu hringrás", + "description": "Klipptu aflgögn fyrri lotu í ákveðinn tímaglugga.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "WashData tækið." + }, + "cycle_id": { + "name": "Auðkenni hringrásar", + "description": "Auðkenni hringrásarinnar sem á að klippa." + }, + "trim_start_s": { + "name": "Klippiupphaf (sekúndur)", + "description": "Geymdu gögn frá þessari færslu á nokkrum sekúndum. Sjálfgefið 0." + }, + "trim_end_s": { + "name": "Klipptu enda (sekúndur)", + "description": "Haltu gögnum upp að þessari frávik á nokkrum sekúndum. Sjálfgefið = full lengd." + } + } + }, + "pause_cycle": { + "name": "Gera hlé á hringrás", + "description": "Gerðu hlé á virku hringrásinni fyrir WashData tæki.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "WashData tækið til að gera hlé." + } + } + }, + "resume_cycle": { + "name": "Halda áfram hringrás", + "description": "Haltu áfram að gera hlé á lotu fyrir WashData tæki.", + "fields": { + "device_id": { + "name": "Tæki", + "description": "WashData tækið að halda áfram." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Hlaupandi" + }, + "match_ambiguity": { + "name": "Samsvörun tvíræðni" + } + }, + "sensor": { + "washer_state": { + "name": "Ríki", + "state": { + "off": "Slökkt", + "idle": "Aðgerðarlaus", + "starting": "Byrjar", + "running": "Hlaupandi", + "paused": "Gert hlé", + "user_paused": "Í bið af notanda", + "ending": "Endir", + "finished": "Lokið", + "anti_wrinkle": "Anti-hrukku", + "delay_wait": "Bíður eftir að byrja", + "interrupted": "Truflað", + "force_stopped": "Þvingun stöðvuð", + "rinse": "Skolaðu", + "unknown": "Óþekkt", + "clean": "Hreint" + } + }, + "washer_program": { + "name": "Dagskrá" + }, + "time_remaining": { + "name": "Tími sem eftir er" + }, + "total_duration": { + "name": "Heildarlengd" + }, + "cycle_progress": { + "name": "Framfarir" + }, + "current_power": { + "name": "Núverandi afl" + }, + "elapsed_time": { + "name": "Tími liðinn" + }, + "debug_info": { + "name": "Villuleit upplýsingar" + }, + "match_confidence": { + "name": "Samsvörunaröryggi" + }, + "top_candidates": { + "name": "Efstu frambjóðendur", + "state": { + "none": "Engin" + } + }, + "current_phase": { + "name": "Núverandi áfangi" + }, + "wash_phase": { + "name": "Áfangi" + }, + "profile_cycle_count": { + "name": "Prófíl {profile_name} Talning", + "unit_of_measurement": "hringrásir" + }, + "suggestions": { + "name": "Tillögur að stillingum" + }, + "pump_runs_today": { + "name": "Dæla gengur (síðasti 24 klst.)" + }, + "cycle_count": { + "name": "Talning hringrásar" + } + }, + "select": { + "program_select": { + "name": "Hringrásarforrit", + "state": { + "auto_detect": "Sjálfvirk uppgötvun" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Þvingaðu lokahring" + }, + "pause_cycle": { + "name": "Gera hlé á hringrás" + }, + "resume_cycle": { + "name": "Halda áfram hringrás" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Gilt device_id er krafist." + }, + "cycle_id_required": { + "message": "Gilt cycle_id er krafist." + }, + "profile_name_required": { + "message": "Gilt prófílnafn er krafist." + }, + "device_not_found": { + "message": "Tæki fannst ekki." + }, + "no_config_entry": { + "message": "Engin stillingarfærsla fannst fyrir tækið." + }, + "integration_not_loaded": { + "message": "Samþætting ekki hlaðin fyrir þetta tæki." + }, + "cycle_not_found_or_no_power": { + "message": "Hringrás fannst ekki eða hefur engin orkugögn." + }, + "trim_failed_empty_window": { + "message": "Trim mistókst - hringrás fannst ekki, engin orkugögn eða gluggi sem myndast er tómur." + }, + "trim_invalid_range": { + "message": "trim_end_s verða að vera stærri en trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/it.json b/custom_components/ha_washdata/translations/it.json new file mode 100644 index 0000000..1bef250 --- /dev/null +++ b/custom_components/ha_washdata/translations/it.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configurazione WashData", + "description": "Configura la tua lavatrice o altro elettrodomestico.\n\nÈ richiesto il sensore di potenza.\n\n**Successivamente ti verrà chiesto se desideri creare il tuo primo profilo.**", + "data": { + "name": "Nome dispositivo", + "device_type": "Tipo di dispositivo", + "power_sensor": "Sensore di potenza", + "min_power": "Soglia di potenza minima (W)" + }, + "data_description": { + "name": "Un nome descrittivo per questo dispositivo (ad esempio \"Lavatrice\", \"Lavastoviglie\").", + "device_type": "Che tipo di elettrodomestico è questo? Aiuta a personalizzare il rilevamento e l'etichettatura.", + "power_sensor": "L'entità sensore che segnala il consumo energetico in tempo reale (in watt) dalla tua presa intelligente.", + "min_power": "Le letture di potenza superiori a questa soglia (in watt) indicano che l'elettrodomestico è in funzione. Inizia con 2 W per la maggior parte dei dispositivi." + } + }, + "first_profile": { + "title": "Crea il primo profilo", + "description": "Un **Ciclo** è un'esecuzione completa dell'elettrodomestico (ad esempio, un carico di bucato). Un **Profilo** raggruppa questi cicli per tipo (ad esempio, \"Cotone\", \"Lavaggio rapido\"). La creazione di un profilo ora consente all'integrazione di stimare subito la durata.\n\nPuoi selezionare manualmente questo profilo nei controlli del dispositivo se non viene rilevato automaticamente.\n\n**Lascia vuoto il campo \"Nome profilo\" per saltare questo passaggio.**", + "data": { + "profile_name": "Nome profilo (facoltativo)", + "manual_duration": "Durata stimata (minuti)" + } + } + }, + "error": { + "cannot_connect": "Connessione non riuscita", + "invalid_auth": "Autenticazione non valida", + "unknown": "Errore imprevisto" + }, + "abort": { + "already_configured": "Il dispositivo è già configurato" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Rivedi i feedback di apprendimento", + "settings": "Impostazioni", + "notifications": "Notifiche", + "manage_cycles": "Gestisci i cicli", + "manage_profiles": "Gestisci profili", + "manage_phase_catalog": "Gestisci il catalogo delle fasi", + "record_cycle": "Registra ciclo (manuale)", + "diagnostics": "Diagnostica e manutenzione" + } + }, + "apply_suggestions": { + "title": "Copia i valori suggeriti", + "description": "Questa operazione copierà i valori suggeriti correnti nelle impostazioni salvate. Si tratta di un'azione una tantum e non abilita la sovrascrittura automatica.\n\nValori suggeriti da copiare:\n{suggested}", + "data": { + "confirm": "Conferma l'azione di copia" + } + }, + "apply_suggestions_confirm": { + "title": "Rivedi le modifiche suggerite", + "description": "WashData ha trovato **{pending_count}** valore/i suggerito/i da applicare.\n\nModifiche:\n{changes}\n\n✅ Seleziona la casella in basso e fai clic su **Invia** per applicare e salvare immediatamente queste modifiche.\n❌ Lascialo deselezionato e fai clic su **Invia** per annullare e tornare indietro.", + "data": { + "confirm_apply_suggestions": "Applica e salva queste modifiche" + } + }, + "settings": { + "title": "Impostazioni", + "description": "{deprecation_warning}Ottimizza le soglie di rilevamento, l'apprendimento e il comportamento avanzato. Le impostazioni predefinite funzionano bene per la maggior parte delle configurazioni.\n\nImpostazioni suggerite attualmente disponibili: {suggestions_count}.\nSe il valore è maggiore di 0, apri le Impostazioni avanzate e usa Applica valori suggeriti per esaminare i consigli.", + "data": { + "apply_suggestions": "Applica valori suggeriti", + "device_type": "Tipo di dispositivo", + "power_sensor": "Entità sensore di potenza", + "min_power": "Potenza minima (W)", + "off_delay": "Ritardo fine ciclo (s)", + "notify_actions": "Azioni di notifica", + "notify_people": "Ritarda la consegna finché queste persone sono a casa", + "notify_only_when_home": "Ritarda le notifiche finché la persona selezionata è a casa", + "notify_fire_events": "Attiva eventi di automazione", + "notify_start_services": "Inizio ciclo: obiettivi di notifica", + "notify_finish_services": "Fine ciclo: obiettivi di notifica", + "notify_live_services": "Progresso in tempo reale: obiettivi di notifica", + "notify_before_end_minutes": "Notifica pre-completamento (minuti prima della fine)", + "notify_title": "Titolo della notifica", + "notify_icon": "Icona di notifica", + "notify_start_message": "Formato messaggio di avvio", + "notify_finish_message": "Formato messaggio di fine", + "notify_pre_complete_message": "Formato messaggio pre-completamento", + "show_advanced": "Modifica impostazioni avanzate (passaggio successivo)" + }, + "data_description": { + "apply_suggestions": "Seleziona questa casella per copiare i valori suggeriti dall'algoritmo di apprendimento nel modulo. Rivedi prima di salvare.\n\nSuggeriti (dall'apprendimento):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Seleziona il tipo di elettrodomestico (Lavatrice, Asciugatrice, Lavastoviglie, Macchina da caffè, Auto elettrica). Questo tag viene memorizzato con ogni ciclo per una migliore organizzazione.", + "power_sensor": "L'entità sensore che segnala la potenza in tempo reale (in watt). Puoi modificarla in qualsiasi momento senza perdere i dati storici - utile se sostituisci una presa intelligente.", + "min_power": "Le letture di potenza superiori a questa soglia (in watt) indicano che l'elettrodomestico è in funzione. Inizia con 2 W per la maggior parte dei dispositivi.", + "off_delay": "Secondi in cui la potenza livellata deve rimanere al di sotto della soglia di arresto per segnare il completamento. Predefinito: 120 s.", + "notify_actions": "Sequenza di azioni Home Assistant facoltativa da eseguire per le notifiche (script, notifica, TTS, ecc.). Se impostata, le azioni vengono eseguite prima del servizio di notifica di fallback.", + "notify_people": "Entità persone usate per il controllo della presenza. Se abilitato, la consegna delle notifiche viene ritardata finché almeno una persona selezionata è a casa.", + "notify_only_when_home": "Se abilitato, ritarda le notifiche finché almeno una persona selezionata è a casa.", + "notify_fire_events": "Se abilitato, attiva gli eventi di Home Assistant per l'inizio e la fine del ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) per pilotare le automazioni.", + "notify_start_services": "Uno o più servizi di notifica per avvisare quando inizia un ciclo. Lascia vuoto per saltare le notifiche di avvio.", + "notify_finish_services": "Uno o più servizi di notifica per avvisare quando un ciclo termina o si avvicina al completamento. Lascia vuoto per saltare le notifiche di fine.", + "notify_live_services": "Uno o più servizi di notifica per ricevere aggiornamenti in tempo reale sui progressi durante l'esecuzione del ciclo (solo app complementare mobile). Lascia vuoto per saltare gli aggiornamenti in tempo reale.", + "notify_before_end_minutes": "Minuti prima della fine stimata del ciclo per attivare una notifica. Imposta su 0 per disabilitare. Predefinito: 0.", + "notify_title": "Titolo personalizzato per le notifiche. Supporta il segnaposto {device}.", + "notify_icon": "Icona da usare per le notifiche (es. mdi:washing-machine). Lascia vuoto per inviare senza icona.", + "notify_start_message": "Messaggio inviato all'avvio di un ciclo. Supporta il segnaposto {device}.", + "notify_finish_message": "Messaggio inviato al termine di un ciclo. Supporta i segnaposto {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Testo degli aggiornamenti ricorrenti sull'avanzamento in tempo reale durante l'esecuzione del ciclo. Supporta i segnaposto {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notifiche", + "description": "Configura canali di notifica, modelli e aggiornamenti facoltativi sull'avanzamento in tempo reale.", + "data": { + "notify_actions": "Azioni di notifica", + "notify_people": "Ritarda la consegna finché queste persone sono a casa", + "notify_only_when_home": "Ritarda le notifiche finché la persona selezionata è a casa", + "notify_fire_events": "Attiva eventi di automazione", + "notify_start_services": "Inizio ciclo: obiettivi di notifica", + "notify_finish_services": "Fine ciclo: obiettivi di notifica", + "notify_live_services": "Progresso in tempo reale: obiettivi di notifica", + "notify_before_end_minutes": "Notifica pre-completamento (minuti prima della fine)", + "notify_live_interval_seconds": "Intervallo aggiornamenti in tempo reale (secondi)", + "notify_live_overrun_percent": "Margine di sforamento aggiornamenti in tempo reale (%)", + "notify_live_chronometer": "Cronometro Conto Alla Rovescia", + "notify_title": "Titolo della notifica", + "notify_icon": "Icona di notifica", + "notify_start_message": "Formato messaggio di avvio", + "notify_finish_message": "Formato messaggio di fine", + "notify_pre_complete_message": "Formato messaggio pre-completamento", + "energy_price_entity": "Prezzo dell'energia - Entità (facoltativo)", + "energy_price_static": "Prezzo dell'energia: valore statico (facoltativo)", + "go_back": "Torna indietro senza salvare", + "notify_reminder_message": "Formato del messaggio di promemoria", + "notify_timeout_seconds": "Ignora automaticamente dopo (secondi)", + "notify_channel": "Canale di notifica (stato/live/promemoria)", + "notify_finish_channel": "Canale di notifica terminato" + }, + "data_description": { + "go_back": "Seleziona questa opzione e fai clic su Invia per scartare le modifiche sopra e tornare al menu principale.", + "notify_actions": "Sequenza di azioni Home Assistant facoltativa da eseguire per le notifiche (script, notifica, TTS, ecc.). Se impostata, le azioni vengono eseguite prima del servizio di notifica di fallback.", + "notify_people": "Entità persone usate per il controllo della presenza. Se abilitato, la consegna delle notifiche viene ritardata finché almeno una persona selezionata è a casa.", + "notify_only_when_home": "Se abilitato, ritarda le notifiche finché almeno una persona selezionata è a casa.", + "notify_fire_events": "Se abilitato, attiva gli eventi di Home Assistant per l'inizio e la fine del ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) per pilotare le automazioni.", + "notify_start_services": "Uno o più servizi di notifica per avvisare quando un ciclo inizia (ad esempio notify.mobile_app_my_phone). Lascia vuoto per saltare le notifiche di avvio.", + "notify_finish_services": "Uno o più servizi di notifica per avvisare quando un ciclo termina o si avvicina al completamento. Lascia vuoto per saltare le notifiche di fine.", + "notify_live_services": "Uno o più servizi di notifica per ricevere aggiornamenti in tempo reale sui progressi durante l'esecuzione del ciclo (solo app complementare mobile). Lascia vuoto per saltare gli aggiornamenti in tempo reale.", + "notify_before_end_minutes": "Minuti prima della fine stimata del ciclo per attivare una notifica. Imposta su 0 per disabilitare. Predefinito: 0.", + "notify_live_interval_seconds": "Con quale frequenza vengono inviati gli aggiornamenti sull'avanzamento durante l'esecuzione. Valori più bassi sono più reattivi ma consumano più notifiche. È applicato un minimo di 30 secondi - i valori inferiori a 30 s vengono arrotondati automaticamente a 30 s.", + "notify_live_overrun_percent": "Margine di sicurezza oltre il conteggio stimato degli aggiornamenti per cicli lunghi o in ritardo (ad esempio il 20% consente 1,2x aggiornamenti stimati).", + "notify_live_chronometer": "Se abilitato, ogni aggiornamento live include un conto alla rovescia del cronometro fino all'ora di fine stimata. Il timer delle notifiche scorre automaticamente sul dispositivo tra gli aggiornamenti (solo app complementare Android).", + "notify_title": "Titolo personalizzato per le notifiche. Supporta il segnaposto {device}.", + "notify_icon": "Icona da usare per le notifiche (es. mdi:washing-machine). Lascia vuoto per inviare senza icona.", + "notify_start_message": "Messaggio inviato all'avvio di un ciclo. Supporta il segnaposto {device}.", + "notify_finish_message": "Messaggio inviato al termine di un ciclo. Supporta i segnaposto {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Seleziona un'entità numerica che contiene il prezzo corrente dell'elettricità (ad esempio sensor.electricity_price, input_number.energy_rate). Se impostato, abilita il segnaposto {cost}. Ha la precedenza sul valore statico se entrambi sono configurati.", + "energy_price_static": "Prezzo fisso dell'elettricità per kWh. Se impostato, abilita il segnaposto {cost}. Ignorato se un'entità è configurata sopra. Aggiungi il simbolo della tua valuta nel modello di messaggio, ad es. {cost} €.", + "notify_reminder_message": "Il testo del promemoria una tantum inviato il numero configurato di minuti prima della fine prevista. Distinto dagli aggiornamenti live ricorrenti di cui sopra. Supporta i segnaposto {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Ignora automaticamente le notifiche dopo questo numero di secondi (inoltrate come \"timeout\" dell'app complementare). Impostarli su 0 per mantenerli fino allo scioglimento. Impostazione predefinita: 0.", + "notify_channel": "Android canale di notifica dell'app complementare per notifiche di stato, progressi in tempo reale e promemoria. Il suono e l'importanza di un canale vengono configurati nell'app complementare la prima volta che viene utilizzato il nome del canale: WashData imposta solo il nome del canale. Lascia vuoto per l'impostazione predefinita dell'app.", + "notify_finish_channel": "Canale Android separato per l'avviso di fine lavoro (utilizzato anche dal promemoria e dal ronzino di lavanderia in attesa) in modo che possa avere il proprio suono. Lascia vuoto per riutilizzare il canale sopra.", + "notify_pre_complete_message": "Testo degli aggiornamenti ricorrenti sull'avanzamento in tempo reale durante l'esecuzione del ciclo. Supporta i segnaposto {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Impostazioni avanzate", + "description": "Ottimizza le soglie di rilevamento, l'apprendimento e il comportamento avanzato. Le impostazioni predefinite funzionano bene per la maggior parte delle configurazioni.", + "sections": { + "suggestions_section": { + "name": "Impostazioni suggerite", + "description": "{suggestions_count} suggerimenti appresi disponibili. Seleziona Applica valori suggeriti per rivedere le modifiche proposte prima di salvare.", + "data": { + "apply_suggestions": "Applica valori suggeriti" + }, + "data_description": { + "apply_suggestions": "Seleziona questa casella per aprire una fase di revisione dei valori suggeriti dall'algoritmo di apprendimento. Nulla viene salvato automaticamente.\n\nSuggeriti (dall'apprendimento):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Rilevamento e soglie di potenza", + "description": "Quando l'elettrodomestico è considerato ACCESO o SPENTO, soglie di energia e temporizzazione delle transizioni di stato.", + "data": { + "start_duration_threshold": "Durata antirimbalzo avvio (s)", + "start_energy_threshold": "Gate energetico di avvio (Wh)", + "completion_min_seconds": "Durata minima per completamento (s)", + "start_threshold_w": "Soglia di avvio (W)", + "stop_threshold_w": "Soglia di arresto (W)", + "end_energy_threshold": "Gate energetico di fine (Wh)", + "running_dead_zone": "Zona morta di avvio (s)", + "end_repeat_count": "Conteggio ripetizioni fine ciclo", + "min_off_gap": "Intervallo minimo tra cicli (s)", + "sampling_interval": "Intervallo di campionamento (s)" + }, + "data_description": { + "start_duration_threshold": "Ignora i brevi picchi di potenza più corti di questa durata (secondi). Filtra i picchi di avvio (es. 60 W per 2 s) prima dell'inizio del ciclo effettivo. Predefinito: 5 s.", + "start_energy_threshold": "Il ciclo deve accumulare almeno questa quantità di energia (Wh) durante la fase di AVVIO per essere confermato. Predefinito: 0,005 Wh.", + "completion_min_seconds": "I cicli più brevi di questo valore verranno contrassegnati come \"interrotti\" anche se terminano naturalmente. Predefinito: 600 s.", + "start_threshold_w": "La potenza deve salire AL DI SOPRA di questa soglia per AVVIARE un ciclo. Imposta un valore superiore alla potenza in standby. Predefinito: min_power + 1 W.", + "stop_threshold_w": "La potenza deve scendere AL DI SOTTO di questa soglia per TERMINARE un ciclo. Imposta appena sopra lo zero per evitare che il rumore generi false terminazioni. Predefinito: 0,6 × min_power.", + "end_energy_threshold": "Il ciclo termina solo se l'energia nell'ultima finestra di ritardo di spegnimento è inferiore a questo valore (Wh). Previene false terminazioni se la potenza oscilla vicino allo zero. Predefinito: 0,05 Wh.", + "running_dead_zone": "Dopo l'avvio del ciclo, ignora i cali di potenza per questo numero di secondi per evitare il rilevamento di false terminazioni durante la fase di avviamento iniziale. Imposta su 0 per disabilitare. Predefinito: 0 s.", + "end_repeat_count": "Numero di volte consecutive in cui la condizione di fine (potenza sotto la soglia per off_delay) deve essere soddisfatta prima di terminare il ciclo. Valori più alti prevengono false terminazioni durante le pause. Predefinito: 1.", + "min_off_gap": "Se un ciclo inizia entro questo numero di secondi dalla fine del precedente, i due cicli verranno UNITI in un unico ciclo.", + "sampling_interval": "Intervallo minimo di campionamento in secondi. Gli aggiornamenti più veloci di questa velocità verranno ignorati. Default: 30s (2s per lavatrici, lavasciuga e lavastoviglie)." + } + }, + "matching_section": { + "name": "Corrispondenza profili e apprendimento", + "description": "Con quanta aggressività WashData abbina i cicli in corso ai profili appresi e quando chiedere feedback.", + "data": { + "profile_match_min_duration_ratio": "Rapporto durata minima corrispondenza profilo (0,1-1,0)", + "profile_match_interval": "Intervallo corrispondenza profilo (s)", + "profile_match_threshold": "Soglia di corrispondenza profilo", + "profile_unmatch_threshold": "Soglia di non corrispondenza profilo", + "auto_label_confidence": "Confidenza etichettatura automatica (0-1)", + "learning_confidence": "Confidenza richiesta feedback (0-1)", + "suppress_feedback_notifications": "Elimina le notifiche di feedback", + "duration_tolerance": "Tolleranza durata (0-0,5)", + "smoothing_window": "Finestra di livellamento (campioni)", + "profile_duration_tolerance": "Tolleranza durata corrispondenza profilo (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Rapporto di durata minima per la corrispondenza del profilo. Il ciclo in esecuzione deve aver raggiunto almeno questa frazione della durata del profilo per corrispondere. Più basso = corrispondenza anticipata. Predefinito: 0,50 (50%).", + "profile_match_interval": "Con quale frequenza (in secondi) tentare la corrispondenza del profilo durante un ciclo. Valori più bassi forniscono un rilevamento più rapido ma aumentano l'utilizzo della CPU. Predefinito: 300 s (5 minuti).", + "profile_match_threshold": "Punteggio minimo di similarità DTW (0,0–1,0) richiesto per considerare un profilo come corrispondente. Più alto = corrispondenza più rigida, meno falsi positivi. Predefinito: 0,4.", + "profile_unmatch_threshold": "Punteggio di similarità DTW (0,0–1,0) al di sotto del quale un profilo precedentemente abbinato viene rifiutato. Dovrebbe essere inferiore alla soglia di corrispondenza per evitare sfarfallio. Predefinito: 0,35.", + "auto_label_confidence": "Etichetta automaticamente i cicli con confidenza pari o superiore a questo valore al completamento (0,0–1,0).", + "learning_confidence": "Confidenza minima per richiedere la verifica dell'utente tramite un evento (0,0–1,0).", + "suppress_feedback_notifications": "Se abilitato, WashData continuerà a monitorare e richiedere feedback internamente, ma non mostrerà notifiche persistenti che chiedono di confermare i cicli. Utile quando i cicli vengono sempre rilevati correttamente e le istruzioni ti distraggono.", + "duration_tolerance": "Tolleranza per le stime del tempo rimanente durante un'esecuzione (0,0–0,5 corrisponde a 0–50%).", + "smoothing_window": "Numero di letture di potenza recenti usate per il livellamento a media mobile.", + "profile_duration_tolerance": "Tolleranza usata dalla corrispondenza del profilo per la varianza della durata totale (0,0–0,5). Comportamento predefinito: ±25%." + } + }, + "timing_section": { + "name": "Tempi, manutenzione e debug", + "description": "Watchdog, reimpostazione del progresso, manutenzione automatica ed esposizione delle entità di debug.", + "data": { + "watchdog_interval": "Intervallo watchdog (s)", + "no_update_active_timeout": "Timeout senza aggiornamenti (s)", + "progress_reset_delay": "Ritardo ripristino avanzamento (s)", + "auto_maintenance": "Abilita manutenzione automatica", + "expose_debug_entities": "Esponi entità di debug", + "save_debug_traces": "Salva tracce di debug" + }, + "data_description": { + "watchdog_interval": "Secondi tra i controlli watchdog durante l'esecuzione. Predefinito: 5 s. ATTENZIONE: assicurarsi che questo valore sia MAGGIORE dell'intervallo di aggiornamento del sensore per evitare false terminazioni (es. se il sensore si aggiorna ogni 60 s, usare 65 s o più).", + "no_update_active_timeout": "Per i sensori che pubblicano solo al cambiamento: forza la fine di un ciclo ATTIVO solo se non arrivano aggiornamenti per questo numero di secondi. Il completamento a bassa potenza utilizza ancora il ritardo di spegnimento.", + "progress_reset_delay": "Dopo che un ciclo si completa (100%), attendi questo numero di secondi di inattività prima di reimpostare l'avanzamento allo 0%. Predefinito: 300 s.", + "auto_maintenance": "Abilita la manutenzione automatica (ripara campioni, unisce frammenti).", + "expose_debug_entities": "Mostra sensori avanzati (Confidenza, Fase, Ambiguità) per il debug.", + "save_debug_traces": "Memorizza dati dettagliati di classificazione e traccia di potenza nella cronologia (aumenta l'utilizzo dello spazio di archiviazione)." + } + }, + "anti_wrinkle_section": { + "name": "Scudo antipiega (asciugatrici)", + "description": "Ignora le rotazioni del cestello dopo il ciclo per evitare cicli fantasma. Solo asciugatrice / lavasciuga.", + "data": { + "anti_wrinkle_enabled": "Protezione antirughe (solo asciugatrice/lavasciuga)", + "anti_wrinkle_max_power": "Potenza massima antirughe (W) (solo asciugatrice/lavasciuga)", + "anti_wrinkle_max_duration": "Durata massima antirughe (s) (solo asciugatrice/lavasciuga)", + "anti_wrinkle_exit_power": "Potenza di uscita antirughe (W) (solo asciugatrice/lavasciuga)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignora le brevi rotazioni del cestello a bassa potenza al termine di un ciclo per evitare cicli fantasma (solo asciugatrice/lavasciuga).", + "anti_wrinkle_max_power": "Le raffiche a potenza pari o inferiore a questo valore vengono trattate come rotazioni antirughe anziché come nuovi cicli. Si applica solo quando la Protezione antirughe è abilitata.", + "anti_wrinkle_max_duration": "Durata massima delle raffiche da trattare come rotazione antirughe. Si applica solo quando la Protezione antirughe è abilitata.", + "anti_wrinkle_exit_power": "Se la potenza scende al di sotto di questo livello, esci immediatamente dalla modalità antirughe (spegnimento vero). Si applica solo quando la Protezione antirughe è abilitata." + } + }, + "delay_start_section": { + "name": "Rilevamento avvio ritardato", + "description": "Tratta la modalità standby prolungata a bassa potenza come 'In attesa di avvio' finché il ciclo non inizia davvero.", + "data": { + "delay_start_detect_enabled": "Abilita il rilevamento dell'avvio ritardato", + "delay_confirm_seconds": "Avvio ritardato: tempo di conferma standby (s)", + "delay_timeout_hours": "Avvio ritardato: tempo di attesa massimo (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Quando abilitato, un breve picco di consumo a bassa potenza seguito da un consumo prolungato in standby viene rilevato come un avvio ritardato. Il sensore di stato mostra \"In attesa di avvio\" durante il ritardo. La durata o l'avanzamento del programma non vengono monitorati finché il ciclo non inizia effettivamente. Richiede che start_threshold_w sia impostato al di sopra della potenza del picco di drenaggio.", + "delay_confirm_seconds": "La potenza deve restare tra Soglia di arresto (W) e Soglia di avvio (W) per questo numero di secondi prima che WashData entri in 'In attesa di avvio'. Filtra i brevi picchi dovuti alla navigazione nei menu. Predefinito: 60 s.", + "delay_timeout_hours": "Timeout di sicurezza: se la macchina è ancora in \"In attesa di avvio\" dopo queste ore, WashData si reimposta su Off. Predefinito: 8 ore." + } + }, + "external_triggers_section": { + "name": "Trigger esterni, porta e pausa", + "description": "Sensore esterno opzionale di fine ciclo, sensore porta per rilevare pausa/pulito e interruttore di taglio alimentazione durante la pausa.", + "data": { + "external_end_trigger_enabled": "Abilita trigger esterno di fine ciclo", + "external_end_trigger": "Sensore esterno di fine ciclo", + "external_end_trigger_inverted": "Inverti la logica del trigger (attiva su SPENTO)", + "door_sensor_entity": "Sensore porta", + "pause_cuts_power": "Interrompere l'alimentazione durante la pausa", + "switch_entity": "Cambia entità (per pausa interruzione alimentazione)", + "notify_unload_delay_minutes": "Ritardo notifica attesa lavanderia (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Abilita il monitoraggio di un sensore binario esterno per attivare il completamento del ciclo.", + "external_end_trigger": "Seleziona un'entità sensore binario. Quando questo sensore si attiva, il ciclo corrente terminerà con stato \"completato\".", + "external_end_trigger_inverted": "Per impostazione predefinita, il trigger si attiva quando il sensore si accende. Seleziona questa casella per attivarlo quando il sensore si spegne.", + "door_sensor_entity": "Sensore binario opzionale per la porta della macchina (on = aperto, off = chiuso). Quando la porta si apre durante un ciclo attivo, WashData confermerà che il ciclo è stato intenzionalmente messo in pausa. Al termine del ciclo, se la porta è ancora chiusa, lo stato cambia in \"Pulizia\" finché la porta non viene aperta. Nota: la chiusura della porta NON riprende automaticamente un ciclo in pausa: la ripresa deve essere attivata manualmente tramite il pulsante o il servizio Riprendi ciclo.", + "pause_cuts_power": "Quando si mette in pausa tramite il pulsante o il servizio Pausa ciclo, disattivare anche l'entità interruttore configurata. Ha effetto solo se è configurata un'entità interruttore per questo dispositivo.", + "switch_entity": "L'entità interruttore da attivare durante la pausa o la ripresa. Richiesto quando è abilitata l'opzione \"Taglia alimentazione durante la pausa\".", + "notify_unload_delay_minutes": "Invia una notifica tramite il canale di notifica di fine dopo che il ciclo è terminato e la porta non è stata aperta per questo numero di minuti (richiede il sensore porta). Impostare su 0 per disabilitare. Predefinito: 60 minuti." + } + }, + "pump_section": { + "name": "Monitor pompa", + "description": "Solo per il tipo di dispositivo pompa. Genera un evento se un ciclo della pompa supera questa durata.", + "data": { + "pump_stuck_duration": "Soglia/e di avviso pompa bloccata (solo pompa)" + }, + "data_description": { + "pump_stuck_duration": "Se un ciclo della pompa è ancora in esecuzione dopo questo numero di secondi, viene generato un evento ha_washdata_pump_stuck una volta. Utilizzarlo per rilevare un motore bloccato (il funzionamento tipico della pompa di raccolta è inferiore a 60 s). Impostazione predefinita: 1800 s (30 min). Solo tipo di dispositivo pompa." + } + }, + "device_link_section": { + "name": "Collegamento del dispositivo", + "description": "Facoltativamente, collega questo dispositivo WashData a un dispositivo Home Assistant esistente (ad esempio la presa intelligente o l'elettrodomestico stesso). Il dispositivo WashData viene quindi visualizzato come \"Connesso tramite\" quel dispositivo. Lasciare vuoto per mantenere WashData come dispositivo autonomo.", + "data": { + "linked_device": "Dispositivo collegato" + }, + "data_description": { + "linked_device": "Seleziona il dispositivo a cui connettere WashData. Ciò aggiunge un riferimento \"Connesso tramite\" nel registro del dispositivo; WashData mantiene la propria scheda dispositivo ed entità. Deselezionare la selezione per rimuovere il collegamento." + } + } + } + }, + "diagnostics": { + "title": "Diagnostica e manutenzione", + "description": "Esegui azioni di manutenzione come l'unione di cicli frammentati o la migrazione dei dati archiviati.\n\n**Utilizzo dello spazio di archiviazione**\n\n- Dimensione file: {file_size_kb} KB\n- Cicli: {cycle_count}\n- Profili: {profile_count}\n- Tracce di debug: {debug_count}", + "menu_options": { + "reprocess_history": "Manutenzione: rielabora e ottimizza i dati", + "clear_debug_data": "Cancella dati di debug (libera spazio)", + "wipe_history": "Cancella TUTTI i dati di questo dispositivo (irreversibile)", + "export_import": "Esporta/Importa JSON con impostazioni (copia/incolla)", + "menu_back": "← Indietro" + } + }, + "clear_debug_data": { + "title": "Cancella dati di debug", + "description": "Sei sicuro di voler eliminare tutte le tracce di debug memorizzate? Questa operazione libererà spazio ma rimuoverà le informazioni dettagliate sulla classificazione dai cicli precedenti." + }, + "manage_cycles": { + "title": "Gestisci i cicli", + "description": "Cicli recenti:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etichettatura automatica dei cicli precedenti", + "select_cycle_to_label": "Etichetta ciclo specifico", + "select_cycle_to_delete": "Elimina ciclo", + "interactive_editor": "Editor interattivo Unisci/Dividi", + "trim_cycle_select": "Taglia dati del ciclo", + "menu_back": "← Indietro" + } + }, + "manage_cycles_empty": { + "title": "Gestisci i cicli", + "description": "Nessun ciclo ancora registrato.", + "menu_options": { + "auto_label_cycles": "Etichettatura automatica dei cicli precedenti", + "menu_back": "← Indietro" + } + }, + "manage_profiles": { + "title": "Gestisci profili", + "description": "Riepilogo profili:\n{profile_summary}", + "menu_options": { + "create_profile": "Crea nuovo profilo", + "edit_profile": "Modifica/rinomina profilo", + "delete_profile_select": "Elimina profilo", + "profile_stats": "Statistiche del profilo", + "cleanup_profile": "Pulizia cronologia - Grafico ed eliminazione", + "assign_profile_phases_select": "Assegna intervalli di fase", + "menu_back": "← Indietro" + } + }, + "manage_profiles_empty": { + "title": "Gestisci profili", + "description": "Nessun profilo ancora creato.", + "menu_options": { + "create_profile": "Crea nuovo profilo", + "menu_back": "← Indietro" + } + }, + "manage_phase_catalog": { + "title": "Gestisci il catalogo delle fasi", + "description": "Catalogo fasi (predefiniti per questo dispositivo + fasi personalizzate):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Crea nuova fase", + "phase_catalog_edit_select": "Modifica fase", + "phase_catalog_delete": "Elimina fase", + "menu_back": "← Indietro" + } + }, + "phase_catalog_create": { + "title": "Crea fase personalizzata", + "description": "Crea un nome e una descrizione per la fase personalizzata e scegli il tipo di dispositivo a cui si applica.", + "data": { + "target_device_type": "Tipo di dispositivo di destinazione", + "phase_name": "Nome fase", + "phase_description": "Descrizione fase" + } + }, + "phase_catalog_edit_select": { + "title": "Modifica fase personalizzata", + "description": "Seleziona una fase personalizzata da modificare.", + "data": { + "phase_name": "Fase personalizzata" + } + }, + "phase_catalog_edit": { + "title": "Modifica fase personalizzata", + "description": "Modifica della fase: {phase_name}", + "data": { + "phase_name": "Nome fase", + "phase_description": "Descrizione fase" + } + }, + "phase_catalog_delete": { + "title": "Elimina fase personalizzata", + "description": "Seleziona una fase personalizzata da eliminare. Eventuali intervalli assegnati che utilizzano questa fase verranno rimossi.", + "data": { + "phase_name": "Fase personalizzata" + } + }, + "assign_profile_phases": { + "title": "Assegna intervalli di fase", + "description": "Anteprima fase per il profilo: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalli correnti:**\n{current_ranges}\n\nUsa le azioni seguenti per aggiungere, modificare, eliminare o salvare gli intervalli.", + "menu_options": { + "assign_profile_phases_add": "Aggiungi intervallo di fase", + "assign_profile_phases_edit_select": "Modifica intervallo di fase", + "assign_profile_phases_delete": "Elimina intervallo di fase", + "phase_ranges_clear": "Cancella tutti gli intervalli", + "assign_profile_phases_auto_detect": "Rileva fasi automaticamente", + "phase_ranges_save": "Salva e torna", + "menu_back": "← Indietro" + } + }, + "assign_profile_phases_add": { + "title": "Aggiungi intervallo di fase", + "description": "Aggiungi un intervallo di fase alla bozza corrente.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto di inizio", + "end_min": "Minuto di fine" + } + }, + "assign_profile_phases_edit_select": { + "title": "Modifica intervallo di fase", + "description": "Seleziona un intervallo da modificare.", + "data": { + "range_index": "Intervallo di fase" + } + }, + "assign_profile_phases_edit": { + "title": "Modifica intervallo di fase", + "description": "Aggiorna l'intervallo di fase selezionato.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto di inizio", + "end_min": "Minuto di fine" + } + }, + "assign_profile_phases_delete": { + "title": "Elimina intervallo di fase", + "description": "Seleziona un intervallo da rimuovere dalla bozza.", + "data": { + "range_index": "Intervallo di fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fasi rilevate automaticamente", + "description": "Profilo: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase/i rilevata/e automaticamente.** Scegli un'azione di seguito.", + "data": { + "action": "Azione" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Assegna nome alle fasi rilevate", + "description": "Profilo: **{profile_name}**\n\n**{detected_count} fase/i rilevata/e:**\n{ranges_summary}\n\nAssegna un nome a ciascuna fase." + }, + "assign_profile_phases_select": { + "title": "Assegna intervalli di fase", + "description": "Seleziona un profilo per configurare gli intervalli di fase.", + "data": { + "profile": "Profilo" + } + }, + "profile_stats": { + "title": "Statistiche del profilo", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Crea nuovo profilo", + "description": "Crea un nuovo profilo manualmente o da un ciclo passato.\n\nEsempi di nomi profilo: \"Delicati\", \"Cotone pesante\", \"Lavaggio rapido\"", + "data": { + "profile_name": "Nome profilo", + "reference_cycle": "Ciclo di riferimento (facoltativo)", + "manual_duration": "Durata manuale (minuti)" + } + }, + "edit_profile": { + "title": "Modifica profilo", + "description": "Seleziona un profilo da rinominare", + "data": { + "profile": "Profilo" + } + }, + "rename_profile": { + "title": "Modifica dettagli profilo", + "description": "Profilo corrente: {current_name}", + "data": { + "new_name": "Nome profilo", + "manual_duration": "Durata manuale (minuti)" + } + }, + "delete_profile_select": { + "title": "Elimina profilo", + "description": "Seleziona un profilo da eliminare", + "data": { + "profile": "Profilo" + } + }, + "delete_profile_confirm": { + "title": "Conferma eliminazione profilo", + "description": "⚠️ Questa operazione eliminerà definitivamente il profilo: {profile_name}", + "data": { + "unlabel_cycles": "Rimuovi l'etichetta dai cicli che usano questo profilo" + } + }, + "auto_label_cycles": { + "title": "Etichettatura automatica dei cicli precedenti", + "description": "Trovati {total_count} cicli totali. Profili: {profiles}", + "data": { + "confidence_threshold": "Confidenza minima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleziona ciclo da etichettare", + "description": "✓ = completato, ⚠ = ripreso, ✗ = interrotto", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Elimina ciclo", + "description": "⚠️ Questa operazione eliminerà definitivamente il ciclo selezionato", + "data": { + "cycle_id": "Ciclo da eliminare" + } + }, + "label_cycle": { + "title": "Etichetta ciclo", + "description": "{cycle_info}", + "data": { + "profile_name": "Profilo", + "new_profile_name": "Nuovo nome profilo (se in fase di creazione)" + } + }, + "post_process": { + "title": "Elabora cronologia (Unisci/Dividi)", + "description": "Pulisci la cronologia dei cicli unendo corse frammentate e suddividendo i cicli uniti in modo errato all'interno di una finestra temporale. Inserisci il numero di ore da guardare indietro (usa 999999 per tutte).", + "data": { + "time_range": "Finestra di ricerca (ore)", + "gap_seconds": "Gap unisci/dividi (secondi)" + }, + "data_description": { + "time_range": "Numero di ore per la scansione dei cicli frammentati da unire. Utilizzare 999999 per elaborare tutti i dati storici.", + "gap_seconds": "Soglia per decidere se dividere o unire. I gap PIÙ BREVI di questo valore vengono uniti. I gap PIÙ LUNGHI vengono divisi. Riduci questo valore per forzare la divisione di un ciclo unito in modo errato." + } + }, + "cleanup_profile": { + "title": "Pulizia cronologia - Seleziona profilo", + "description": "Seleziona un profilo da visualizzare. Verrà generato un grafico con tutti i cicli passati per questo profilo per aiutare a identificare i valori anomali.", + "data": { + "profile": "Profilo" + } + }, + "cleanup_select": { + "title": "Pulizia cronologia - Grafico ed eliminazione", + "description": "![Grafico]({graph_url})\n\n**Visualizzazione di {profile_name}**\n\nIl grafico mostra i singoli cicli come linee colorate. Identifica i valori anomali (linee lontane dal gruppo) e abbina il loro colore nell'elenco sottostante per eliminarli.\n\n**Seleziona i cicli da eliminare DEFINITIVAMENTE:**", + "data": { + "cycles_to_delete": "Cicli da eliminare (abbina i colori corrispondenti)" + } + }, + "interactive_editor": { + "title": "Editor interattivo Unisci/Dividi", + "description": "Seleziona un'azione manuale da eseguire sulla cronologia dei cicli.", + "menu_options": { + "editor_split": "Dividi un ciclo (trova gap)", + "editor_merge": "Unisci cicli (unisci frammenti)", + "editor_delete": "Elimina ciclo/i", + "menu_back": "← Indietro" + } + }, + "editor_select": { + "title": "Seleziona cicli", + "description": "Seleziona il/i ciclo/i da elaborare.\n\n{info_text}", + "data": { + "selected_cycles": "Cicli" + } + }, + "editor_configure": { + "title": "Configura e applica", + "description": "{preview_md}", + "data": { + "confirm_action": "Azione", + "merged_profile": "Profilo risultante", + "new_profile_name": "Nuovo nome profilo", + "confirm_commit": "Sì, confermo questa azione", + "segment_0_profile": "Profilo del segmento 1", + "segment_1_profile": "Profilo del segmento 2", + "segment_2_profile": "Profilo del segmento 3", + "segment_3_profile": "Profilo del segmento 4", + "segment_4_profile": "Profilo del segmento 5", + "segment_5_profile": "Profilo del segmento 6", + "segment_6_profile": "Profilo del segmento 7", + "segment_7_profile": "Profilo del segmento 8", + "segment_8_profile": "Profilo del segmento 9", + "segment_9_profile": "Profilo del segmento 10" + } + }, + "editor_split_params": { + "title": "Configurazione divisione", + "description": "Regola la sensibilità per la divisione di questo ciclo. Qualsiasi pausa più lunga di questo valore causerà una divisione.", + "data": { + "split_mode": "Metodo di divisione" + } + }, + "editor_split_auto_params": { + "title": "Configurazione divisione automatica", + "description": "Regola la sensibilità per dividere questo ciclo. Qualsiasi intervallo di inattività più lungo di questo causerà una divisione.", + "data": { + "min_gap_seconds": "Soglia intervallo di divisione (secondi)" + } + }, + "editor_split_manual_params": { + "title": "Timestamp di divisione manuale", + "description": "{preview_md}Finestra del ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** del {cycle_date}.\n\nInserisci uno o più timestamp di divisione (`HH:MM` o `HH:MM:SS`), uno per riga. Il ciclo verrà tagliato a ogni timestamp — N timestamp producono N+1 segmenti.", + "data": { + "split_timestamps": "Timestamp di divisione" + } + }, + "reprocess_history": { + "title": "Manutenzione: rielabora e ottimizza i dati", + "description": "Esegue una pulizia approfondita dei dati storici:\n1. Rimuove le letture a potenza zero (silenzio).\n2. Corregge i tempi e la durata dei cicli.\n3. Ricalcola tutti i modelli statistici (inviluppi).\n\n**Esegui questa operazione per risolvere problemi con i dati o applicare nuovi algoritmi.**" + }, + "wipe_history": { + "title": "Cancella cronologia (solo per test)", + "description": "⚠️ Questa operazione eliminerà definitivamente TUTTI i cicli e i profili memorizzati per questo dispositivo. Questa operazione non può essere annullata!" + }, + "export_import": { + "title": "Esporta/Importa JSON", + "description": "Copia/incolla cicli, profili E impostazioni per questo dispositivo. Esporta qui di seguito o incolla un JSON per importare.", + "data": { + "mode": "Azione", + "json_payload": "Configurazione JSON completa" + }, + "data_description": { + "mode": "Scegli Esporta per copiare i dati e le impostazioni correnti, oppure Importa per incollare i dati esportati da un altro dispositivo WashData.", + "json_payload": "Per l'esportazione: copia questo JSON (include cicli, profili e tutte le impostazioni ottimizzate). Per l'importazione: incolla il JSON esportato da WashData." + } + }, + "record_cycle": { + "title": "Registra ciclo", + "description": "Registra manualmente un ciclo per creare un profilo pulito senza interferenze.\n\nStato: **{status}**\nDurata: {duration}s\nCampioni: {samples}", + "menu_options": { + "record_refresh": "Aggiorna stato", + "record_stop": "Interrompi registrazione (salva ed elabora)", + "record_start": "Avvia nuova registrazione", + "record_process": "Elabora ultima registrazione (taglia e salva)", + "record_discard": "Scarta ultima registrazione", + "menu_back": "← Indietro" + } + }, + "record_process": { + "title": "Elabora registrazione", + "description": "![Grafico]({graph_url})\n\n**Il grafico mostra la registrazione grezza.** Blu = Mantieni, Rosso = Taglio proposto.\n*Il grafico è statico e non si aggiorna con le modifiche del modulo.*\n\nRegistrazione interrotta. I tagli sono allineati alla frequenza di campionamento rilevata (~{sampling_rate}s).\n\nDurata grezza: {duration}s\nCampioni: {samples}", + "data": { + "head_trim": "Taglio inizio (secondi)", + "tail_trim": "Taglio fine (secondi)", + "save_mode": "Destinazione di salvataggio", + "profile_name": "Nome profilo" + } + }, + "learning_feedbacks": { + "title": "Feedback di apprendimento", + "description": "Feedback in sospeso: {count}\n\n{pending_table}\n\nSeleziona una richiesta di revisione del ciclo da elaborare.", + "menu_options": { + "learning_feedbacks_pick": "Rivedi un feedback in sospeso", + "learning_feedbacks_dismiss_all": "Ignora tutti i feedback in sospeso", + "menu_back": "← Indietro" + } + }, + "learning_feedbacks_pick": { + "title": "Seleziona feedback da rivedere", + "description": "Seleziona una richiesta di revisione del ciclo da aprire.", + "data": { + "selected_feedback": "Seleziona ciclo da rivedere" + } + }, + "learning_feedbacks_empty": { + "title": "Feedback di apprendimento", + "description": "Nessuna richiesta di feedback in sospeso trovata.", + "menu_options": { + "menu_back": "← Indietro" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Ignora tutti i feedback in sospeso", + "description": "Stai per ignorare **{count}** richiesta/e di feedback in sospeso. I cicli ignorati rimangono nella cronologia ma non contribuiranno con un nuovo segnale di apprendimento. Questa azione non può essere annullata.\n\n✅ Seleziona la casella in basso e fai clic su **Invia** per ignorarli tutti.\n❌ Lasciala deselezionata e fai clic su **Invia** per annullare e tornare indietro.", + "data": { + "confirm_dismiss_all": "Conferma: ignora tutte le richieste di feedback in sospeso" + } + }, + "resolve_feedback": { + "title": "Risolvi feedback", + "description": "{comparison_img}{candidates_table}\n**Profilo rilevato**: {detected_profile} ({confidence_pct}%)\n**Durata stimata**: {est_duration_min} min\n**Durata effettiva**: {act_duration_min} min\n\n__Scegli un'azione di seguito:__", + "data": { + "action": "Cosa vorresti fare?", + "corrected_profile": "Programma corretto (se si corregge)", + "corrected_duration": "Durata corretta in secondi (se si corregge)" + } + }, + "trim_cycle_select": { + "title": "Taglia ciclo - Seleziona ciclo", + "description": "Seleziona un ciclo da tagliare. ✓ = completato, ⚠ = ripreso, ✗ = interrotto", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Taglia ciclo", + "description": "Finestra corrente: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Azione" + } + }, + "trim_cycle_start": { + "title": "Imposta inizio taglio", + "description": "Finestra del ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInizio attuale: **{current_wallclock}** (+{current_offset_min} min dall'inizio del ciclo)\n\nScegli una nuova ora di inizio utilizzando il selettore dell'orologio di seguito.", + "data": { + "trim_start_time": "Nuova ora di inizio", + "trim_start_min": "Nuovo inizio (minuti dall'inizio del ciclo)" + } + }, + "trim_cycle_end": { + "title": "Imposta fine taglio", + "description": "Finestra del ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFine attuale: **{current_wallclock}** (+{current_offset_min} min dall'inizio del ciclo)\n\nScegli una nuova ora di fine utilizzando il selettore dell'orologio di seguito.", + "data": { + "trim_end_time": "Nuova ora di fine", + "trim_end_min": "Nuova fine (minuti dall'inizio del ciclo)" + } + } + }, + "error": { + "import_failed": "Importazione non riuscita. Incolla un JSON di esportazione WashData valido.", + "select_exactly_one": "Seleziona esattamente un ciclo per questa azione.", + "select_at_least_one": "Seleziona almeno un ciclo per questa azione.", + "select_at_least_two": "Seleziona almeno due cicli per questa azione.", + "profile_exists": "Esiste già un profilo con questo nome", + "rename_failed": "Impossibile rinominare il profilo. Il profilo potrebbe non esistere oppure il nuovo nome è già in uso.", + "assignment_failed": "Impossibile assegnare il profilo al ciclo", + "duplicate_phase": "Esiste già una fase con questo nome.", + "phase_not_found": "La fase selezionata non è stata trovata.", + "invalid_phase_name": "Il nome della fase non può essere vuoto.", + "phase_name_too_long": "Il nome della fase è troppo lungo.", + "invalid_phase_range": "L'intervallo di fase non è valido. La fine deve essere maggiore dell'inizio.", + "invalid_phase_timestamp": "Il timestamp non è valido. Usa il formato AAAA-MM-GG HH:MM o HH:MM.", + "overlapping_phase_ranges": "Gli intervalli di fase si sovrappongono. Regola gli intervalli e riprova.", + "incomplete_phase_row": "Ogni riga di fase deve includere una fase e valori di inizio/fine completi per la modalità selezionata.", + "timestamp_mode_cycle_required": "Seleziona un ciclo sorgente quando si usa la modalità timestamp.", + "no_phase_ranges": "Non sono ancora disponibili intervalli di fase per quell'azione.", + "feedback_notification_title": "WashData: Verifica ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programma**: {program} ({confidence}% di confidenza)\n**Ora**: {time}\n\nWashData ha bisogno del tuo aiuto per verificare questo ciclo rilevato.\n\nVai su **Impostazioni > Dispositivi e servizi > WashData > Configura > Feedback di apprendimento** per confermare o correggere questo risultato.", + "suggestions_ready_notification_title": "WashData: impostazioni suggerite disponibili ({device})", + "suggestions_ready_notification_message": "Il sensore **Impostazioni suggerite** segnala ora **{count}** raccomandazioni utilizzabili.\n\nPer rivederle e applicarle: **Impostazioni > Dispositivi e servizi > WashData > Configura > Impostazioni avanzate > Applica valori suggeriti**.\n\nI suggerimenti sono facoltativi e vengono mostrati per la revisione prima del salvataggio.", + "trim_range_invalid": "L'inizio deve essere prima della fine. Regola i punti di taglio.", + "trim_failed": "Taglio non riuscito. Il ciclo potrebbe non esistere più oppure la finestra è vuota.", + "invalid_split_timestamp": "Il timestamp non è valido o è fuori dalla finestra del ciclo. Usa HH:MM o HH:MM:SS all'interno della finestra del ciclo.", + "no_split_segments_found": "Da questi timestamp non è stato possibile produrre segmenti validi. Assicurati che ogni timestamp sia all'interno della finestra del ciclo e che i segmenti durino almeno 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} ha rilevato cicli fantasma. Modifica min_power suggerita: {current_min}W -> {new_min}W (non applicata automaticamente).", + "auto_tune_title": "Sintonizzazione automatica WashData", + "auto_tune_fallback": "{device_type} {device_title} ha rilevato cicli fantasma. Modifica min_power suggerita: {current_min}W -> {new_min}W (non applicata automaticamente)." + }, + "abort": { + "no_cycles_found": "Nessun ciclo trovato", + "no_split_segments_found": "Nel ciclo selezionato non sono stati trovati segmenti divisibili.", + "cycle_not_found": "Il ciclo selezionato non può essere caricato.", + "no_power_data": "Nessun dato di traccia di potenza disponibile per il ciclo selezionato.", + "no_profiles_found": "Nessun profilo trovato. Crea prima un profilo.", + "no_profiles_for_matching": "Nessun profilo disponibile per la corrispondenza. Crea prima i profili.", + "no_unlabeled_cycles": "Tutti i cicli sono già etichettati", + "no_suggestions": "Nessun valore suggerito ancora disponibile. Esegui alcuni cicli e riprova.", + "no_predictions": "Nessuna previsione", + "no_custom_phases": "Nessuna fase personalizzata trovata.", + "reprocess_success": "{count} cicli rielaborati correttamente.", + "debug_data_cleared": "Dati di debug cancellati correttamente da {count} cicli." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Avvio ciclo", + "cycle_finish": "Fine ciclo", + "cycle_live": "Avanzamento in tempo reale" + } + }, + "common_text": { + "options": { + "unlabeled": "(Senza etichetta)", + "create_new_profile": "Crea nuovo profilo", + "remove_label": "Rimuovi etichetta", + "no_reference_cycle": "(Nessun ciclo di riferimento)", + "all_device_types": "Tutti i tipi di dispositivo", + "current_suffix": "(corrente)", + "editor_select_info": "Seleziona 1 ciclo da dividere o 2+ cicli da unire.", + "editor_select_info_split": "Seleziona 1 ciclo da dividere.", + "editor_select_info_merge": "Seleziona 2 o più cicli da unire.", + "editor_select_info_delete": "Seleziona 1 o più cicli da eliminare.", + "no_cycles_recorded": "Nessun ciclo ancora registrato.", + "profile_summary_line": "- **{name}**: {count} cicli, {avg} min in media", + "no_profiles_created": "Nessun profilo ancora creato.", + "phase_preview": "Anteprima fase", + "phase_preview_no_curve": "La curva del profilo medio non è ancora disponibile. Esegui/etichetta più cicli per questo profilo.", + "average_power_curve": "Curva di potenza media", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cicli, ~{duration} min in media)", + "profile_option_short_fmt": "{name} ({count} cicli, ~{duration} min)", + "deleted_cycles_from_profile": "Eliminati {count} cicli da {profile}.", + "not_enough_data_graph": "Dati insufficienti per generare il grafico.", + "no_profiles_found": "Nessun profilo trovato.", + "cycle_info_fmt": "Ciclo: {start}, {duration} min, Corrente: {label}", + "top_candidates_header": "### Principali candidati", + "tbl_profile": "Profilo", + "tbl_confidence": "Confidenza", + "tbl_correlation": "Correlazione", + "tbl_duration_match": "Corrisp. durata", + "detected_profile": "Profilo rilevato", + "estimated_duration": "Durata stimata", + "actual_duration": "Durata effettiva", + "choose_action": "__Scegli un'azione di seguito:__", + "tbl_count": "Conteggio", + "tbl_avg": "Media", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (media)", + "tbl_energy_total": "Energia (totale)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Ultima esecuzione", + "graph_legend_title": "Legenda del grafico", + "graph_legend_body": "La banda blu rappresenta l'intervallo minimo e massimo di assorbimento di potenza osservato. La linea mostra la curva di potenza media.", + "recording_preview": "Anteprima registrazione", + "trim_start": "Inizio taglio", + "trim_end": "Fine taglio", + "split_preview_title": "Anteprima divisione", + "split_preview_found_fmt": "Trovati {count} segmenti.", + "split_preview_confirm_fmt": "Clicca Conferma per dividere questo ciclo in {count} cicli separati.", + "merge_preview_title": "Anteprima unione", + "merge_preview_joining_fmt": "Unione di {count} cicli. I gap verranno riempiti con letture di 0 W.", + "editor_delete_preview_title": "Anteprima eliminazione", + "editor_delete_preview_intro": "I cicli selezionati verranno eliminati definitivamente:", + "editor_delete_preview_confirm": "Fai clic su Conferma per eliminare definitivamente questi record dei cicli.", + "no_power_preview": "*Nessun dato di potenza disponibile per l'anteprima.*", + "profile_comparison": "Confronto profili", + "actual_cycle_label": "Questo ciclo (effettivo)", + "storage_usage_header": "Utilizzo dello spazio di archiviazione", + "storage_file_size": "Dimensione file", + "storage_cycles": "Cicli", + "storage_profiles": "Profili", + "storage_debug_traces": "Tracce di debug", + "overview_suffix": "Panoramica", + "phase_preview_for_profile": "Anteprima fase per il profilo", + "current_ranges_header": "Intervalli correnti", + "assign_phases_help": "Usa le azioni seguenti per aggiungere, modificare, eliminare o salvare gli intervalli.", + "profile_summary_header": "Riepilogo profili", + "recent_cycles_header": "Cicli recenti", + "trim_cycle_preview_title": "Anteprima taglio ciclo", + "trim_cycle_preview_no_data": "Nessun dato di potenza disponibile per questo ciclo.", + "trim_cycle_preview_kept_suffix": "mantenuto", + "table_program": "Programma", + "table_when": "Quando", + "table_length": "Durata", + "table_match": "Corrispondenza", + "table_profile": "Profilo", + "table_cycles": "Cicli", + "table_avg_length": "Durata media", + "table_last_run": "Ultima esecuzione", + "table_avg_energy": "Energia media", + "table_detected_program": "Programma rilevato", + "table_confidence": "Confidenza", + "table_reported": "Segnalato", + "phase_builtin_suffix": "(Incorporato)", + "phase_none_available": "Nessuna fase disponibile.", + "settings_deprecation_warning": "⚠️ **Tipo di dispositivo obsoleto:** è prevista la rimozione di {device_type} in una versione futura. La pipeline di corrispondenza di WashData non produce risultati affidabili per questa classe di apparecchi. La tua integrazione continua a funzionare durante il periodo di deprecazione; per disattivare questo avviso, imposta il **Tipo di dispositivo** di seguito su uno dei tipi supportati (lavatrice, asciugatrice, lavatrice-asciugatrice combinata, lavastoviglie, friggitrice ad aria, macchina per il pane o pompa) o su **Altro (avanzato)** se il tuo elettrodomestico non corrisponde a nessuno dei tipi supportati. **Altro (Avanzato)** fornisce impostazioni predefinite intenzionalmente generiche che non sono ottimizzate per alcun dispositivo specifico, quindi dovrai configurare tu stesso le soglie, i timeout e i parametri di corrispondenza; tutte le impostazioni esistenti vengono conservate quando cambi.", + "phase_other_device_types": "Altri tipi di dispositivi:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividi un ciclo (trova gap)", + "merge": "Unisci cicli (unisci frammenti)", + "delete": "Elimina ciclo/i" + } + }, + "split_mode": { + "options": { + "auto": "Rileva automaticamente gli intervalli inattivi", + "manual": "Timestamp manuali" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Manutenzione: rielabora e ottimizza i dati", + "clear_debug_data": "Cancella dati di debug (libera spazio)", + "wipe_history": "Cancella TUTTI i dati di questo dispositivo (irreversibile)", + "export_import": "Esporta/Importa JSON con impostazioni (copia/incolla)" + } + }, + "export_import_mode": { + "options": { + "export": "Esporta tutti i dati", + "import": "Importa/Unisci dati" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etichettatura automatica dei cicli precedenti", + "select_cycle_to_label": "Etichetta ciclo specifico", + "select_cycle_to_delete": "Elimina ciclo", + "interactive_editor": "Editor interattivo Unisci/Dividi", + "trim_cycle": "Taglia dati del ciclo" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Crea nuovo profilo", + "edit_profile": "Modifica/rinomina profilo", + "delete_profile": "Elimina profilo", + "profile_stats": "Statistiche del profilo", + "cleanup_profile": "Pulizia cronologia - Grafico ed eliminazione", + "assign_phases": "Assegna intervalli di fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Crea nuova fase", + "edit_custom_phase": "Modifica fase", + "delete_custom_phase": "Elimina fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Aggiungi intervallo di fase", + "edit_range": "Modifica intervallo di fase", + "delete_range": "Elimina intervallo di fase", + "clear_ranges": "Cancella tutti gli intervalli", + "auto_detect_ranges": "Rileva fasi automaticamente", + "save_ranges": "Salva e torna" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Aggiorna stato", + "stop_recording": "Interrompi registrazione (salva ed elabora)", + "start_recording": "Avvia nuova registrazione", + "process_recording": "Elabora ultima registrazione (taglia e salva)", + "discard_recording": "Scarta ultima registrazione" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Crea nuovo profilo", + "existing_profile": "Aggiungi al profilo esistente", + "discard": "Scarta registrazione" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Conferma - rilevamento corretto", + "correct": "Correggi - scegli programma/durata", + "ignore": "Ignora - falso positivo/ciclo rumoroso", + "delete": "Elimina - rimuovi dalla cronologia" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Assegna nome e applica le fasi", + "cancel": "Torna indietro senza modifiche" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Imposta punto di inizio", + "set_end": "Imposta punto di fine", + "reset": "Ripristina durata completa", + "apply": "Applica taglio", + "cancel": "Annulla" + } + }, + "device_type": { + "options": { + "washing_machine": "Lavatrice", + "dryer": "Asciugatrice", + "washer_dryer": "Combinazione lavatrice-asciugatrice", + "dishwasher": "Lavastoviglie", + "coffee_machine": "Macchina da caffè (obsoleta)", + "ev": "Veicolo elettrico (obsoleto)", + "air_fryer": "Friggitrice ad aria", + "heat_pump": "Pompa di calore (obsoleto)", + "bread_maker": "Macchina per il pane", + "pump": "Pompa / Pompa di raccolta", + "oven": "Forno (deprecato)", + "other": "Altro (Avanzato)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etichetta ciclo", + "description": "Assegna un profilo esistente a un ciclo passato oppure rimuovi l'etichetta.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData da etichettare." + }, + "cycle_id": { + "name": "ID ciclo", + "description": "L'ID del ciclo da etichettare." + }, + "profile_name": { + "name": "Nome profilo", + "description": "Il nome di un profilo esistente (crea profili nel menu Gestisci profili). Lascia vuoto per rimuovere l'etichetta." + } + } + }, + "create_profile": { + "name": "Crea profilo", + "description": "Crea un nuovo profilo (autonomo o basato su un ciclo di riferimento).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData." + }, + "profile_name": { + "name": "Nome profilo", + "description": "Nome per il nuovo profilo (es. \"Cotone pesante\", \"Delicati\")." + }, + "reference_cycle_id": { + "name": "ID ciclo di riferimento", + "description": "ID ciclo facoltativo su cui basare questo profilo." + } + } + }, + "delete_profile": { + "name": "Elimina profilo", + "description": "Elimina un profilo e, facoltativamente, rimuovi l'etichetta dai cicli che lo utilizzano.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData." + }, + "profile_name": { + "name": "Nome profilo", + "description": "Il profilo da eliminare." + }, + "unlabel_cycles": { + "name": "Rimuovi etichetta dai cicli", + "description": "Rimuovi l'etichetta del profilo dai cicli che utilizzano questo profilo." + } + } + }, + "auto_label_cycles": { + "name": "Etichettatura automatica dei cicli precedenti", + "description": "Etichetta retroattivamente i cicli senza etichetta usando la corrispondenza dei profili.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData." + }, + "confidence_threshold": { + "name": "Soglia di confidenza", + "description": "Confidenza di corrispondenza minima (0,50-0,95) per applicare le etichette." + } + } + }, + "export_config": { + "name": "Esporta configurazione", + "description": "Esporta profili, cicli e impostazioni di questo dispositivo in un file JSON (per dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData da esportare." + }, + "path": { + "name": "Percorso", + "description": "Percorso file assoluto facoltativo in cui scrivere (il valore predefinito è /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importa configurazione", + "description": "Importa profili, cicli e impostazioni per questo dispositivo da un file JSON di esportazione (le impostazioni potrebbero essere sovrascritte).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData in cui importare." + }, + "path": { + "name": "Percorso", + "description": "Percorso assoluto al file JSON di esportazione da importare." + } + } + }, + "submit_cycle_feedback": { + "name": "Invia feedback sul ciclo", + "description": "Conferma o correggi un programma rilevato automaticamente dopo un ciclo completato. Fornisci `entry_id` (avanzato) o `device_id` (consigliato).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData (consigliato al posto di entry_id)." + }, + "entry_id": { + "name": "ID voce", + "description": "L'ID della voce di configurazione per il dispositivo (alternativa a device_id)." + }, + "cycle_id": { + "name": "ID ciclo", + "description": "L'ID ciclo mostrato nella notifica/nei registri di feedback." + }, + "user_confirmed": { + "name": "Conferma programma rilevato", + "description": "Imposta su true se il programma rilevato è corretto." + }, + "corrected_profile": { + "name": "Profilo corretto", + "description": "Se non confermato, fornisci il nome corretto del profilo/programma." + }, + "corrected_duration": { + "name": "Durata corretta (secondi)", + "description": "Durata corretta facoltativa in secondi." + }, + "notes": { + "name": "Note", + "description": "Note facoltative su questo ciclo." + } + } + }, + "record_start": { + "name": "Avvia registrazione ciclo", + "description": "Avvia manualmente la registrazione di un ciclo pulito (ignora tutta la logica di corrispondenza).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData su cui registrare." + } + } + }, + "record_stop": { + "name": "Interrompi registrazione ciclo", + "description": "Interrompe la registrazione manuale.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData su cui interrompere la registrazione." + } + } + }, + "trim_cycle": { + "name": "Taglia ciclo", + "description": "Ritaglia i dati di potenza di un ciclo passato a una finestra temporale specifica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData." + }, + "cycle_id": { + "name": "ID ciclo", + "description": "L'ID del ciclo da tagliare." + }, + "trim_start_s": { + "name": "Inizio taglio (secondi)", + "description": "Conserva i dati a partire da questo offset in secondi. Predefinito: 0." + }, + "trim_end_s": { + "name": "Fine taglio (secondi)", + "description": "Conserva i dati fino a questo offset in secondi. Predefinito = durata intera." + } + } + }, + "pause_cycle": { + "name": "Pausa ciclo", + "description": "Mettere in pausa il ciclo attivo per un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData da mettere in pausa." + } + } + }, + "resume_cycle": { + "name": "Riprendi ciclo", + "description": "Riprendere un ciclo in pausa per un dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "Il dispositivo WashData da riprendere." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "In esecuzione" + }, + "match_ambiguity": { + "name": "Ambiguità corrispondenza" + } + }, + "sensor": { + "washer_state": { + "name": "Stato", + "state": { + "off": "Spento", + "idle": "In attesa", + "starting": "In avvio", + "running": "In esecuzione", + "paused": "In pausa", + "user_paused": "In pausa dall'utente", + "ending": "In chiusura", + "finished": "Completato", + "anti_wrinkle": "Antirughe", + "delay_wait": "In attesa di iniziare", + "interrupted": "Interrotto", + "force_stopped": "Fermato forzatamente", + "rinse": "Risciacquo", + "unknown": "Sconosciuto", + "clean": "Pulito" + } + }, + "washer_program": { + "name": "Programma" + }, + "time_remaining": { + "name": "Tempo rimanente" + }, + "total_duration": { + "name": "Durata totale" + }, + "cycle_progress": { + "name": "Avanzamento" + }, + "current_power": { + "name": "Potenza attuale" + }, + "elapsed_time": { + "name": "Tempo trascorso" + }, + "debug_info": { + "name": "Informazioni di debug" + }, + "match_confidence": { + "name": "Confidenza corrispondenza" + }, + "top_candidates": { + "name": "Principali candidati", + "state": { + "none": "Nessuno" + } + }, + "current_phase": { + "name": "Fase corrente" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Conteggio profilo {profile_name}", + "unit_of_measurement": "cicli" + }, + "suggestions": { + "name": "Impostazioni suggerite disponibili" + }, + "pump_runs_today": { + "name": "Avvii pompa (ultime 24 ore)" + }, + "cycle_count": { + "name": "Conteggio cicli" + } + }, + "select": { + "program_select": { + "name": "Programma ciclo", + "state": { + "auto_detect": "Rilevamento automatico" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Termina ciclo forzatamente" + }, + "pause_cycle": { + "name": "Pausa ciclo" + }, + "resume_cycle": { + "name": "Riprendi ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "È richiesto un device_id valido." + }, + "cycle_id_required": { + "message": "È richiesto un cycle_id valido." + }, + "profile_name_required": { + "message": "È richiesto un nome_profilo valido." + }, + "device_not_found": { + "message": "Dispositivo non trovato." + }, + "no_config_entry": { + "message": "Nessuna voce di configurazione trovata per il dispositivo." + }, + "integration_not_loaded": { + "message": "Integrazione non caricata per questo dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclo non trovato o privo di dati di potenza." + }, + "trim_failed_empty_window": { + "message": "Taglio non riuscito - ciclo non trovato, nessun dato di potenza, oppure la finestra risultante è vuota." + }, + "trim_invalid_range": { + "message": "trim_end_s deve essere maggiore di trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ja.json b/custom_components/ha_washdata/translations/ja.json new file mode 100644 index 0000000..ea2d004 --- /dev/null +++ b/custom_components/ha_washdata/translations/ja.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData セットアップ", + "description": "洗濯機またはその他の家電製品を設定します。\n\n電力センサーが必要です。\n\n**次に、最初のプロファイルを作成するかどうか確認されます。**", + "data": { + "name": "デバイス名", + "device_type": "デバイスの種類", + "power_sensor": "電力センサー", + "min_power": "最小電力しきい値 (W)" + }, + "data_description": { + "name": "このデバイスのわかりやすい名前(例:「洗濯機」、「食器洗い機」)。", + "device_type": "家電製品の種類を選択します。検出とラベル付けの精度向上に役立ちます。", + "power_sensor": "スマートプラグからリアルタイムの消費電力(ワット)を報告するセンサーエンティティ。", + "min_power": "このしきい値(ワット)を超える電力値は、家電製品が動作中であることを示します。ほとんどのデバイスでは 2W から始めてください。" + } + }, + "first_profile": { + "title": "最初のプロファイルを作成", + "description": "**サイクル**とは、家電製品の1回の完全な動作です(例:洗濯1回分)。**プロファイル**は、これらのサイクルをタイプ別にグループ化します(例:「綿」、「おいそぎ」)。今プロファイルを作成すると、インテグレーションが直ちに所要時間を推定できるようになります。\n\n自動で検出されない場合は、デバイスコントロールで手動でこのプロファイルを選択できます。\n\n**「プロファイル名」を空白のままにするとこの手順をスキップできます。**", + "data": { + "profile_name": "プロファイル名(任意)", + "manual_duration": "推定所要時間(分)" + } + } + }, + "error": { + "cannot_connect": "接続に失敗しました", + "invalid_auth": "認証に失敗しました", + "unknown": "予期しないエラーが発生しました" + }, + "abort": { + "already_configured": "このデバイスはすでに設定されています" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "学習フィードバックを確認", + "settings": "設定", + "notifications": "通知", + "manage_cycles": "サイクルの管理", + "manage_profiles": "プロファイルの管理", + "manage_phase_catalog": "フェーズカタログの管理", + "record_cycle": "サイクルの記録(手動)", + "diagnostics": "診断とメンテナンス" + } + }, + "apply_suggestions": { + "title": "推奨値をコピー", + "description": "現在の推奨値を保存済み設定にコピーします。これは1回限りの操作で、自動上書きは有効になりません。\n\nコピーする推奨値:\n{suggested}", + "data": { + "confirm": "コピー操作を確認" + } + }, + "apply_suggestions_confirm": { + "title": "推奨された変更を確認", + "description": "WashData は適用する推奨値 **{pending_count}** を見つけました。\n\n変更点:\n{changes}\n\n✅ 下のボックスをチェックし、**送信** をクリックして、これらの変更をすぐに適用して保存します。\n❌ チェックを外したままにし、**送信**をクリックしてキャンセルして戻ります。", + "data": { + "confirm_apply_suggestions": "これらの変更を適用して保存します" + } + }, + "settings": { + "title": "設定", + "description": "{deprecation_warning}検出しきい値、学習、高度な動作を調整します。デフォルト値はほとんどの設定で適切に機能します。\n\n現在利用可能な推奨設定数:{suggestions_count}。\n0より大きい場合は、詳細設定を開いて「推奨値を適用」で提案内容を確認してください。", + "data": { + "apply_suggestions": "推奨値を適用", + "device_type": "デバイスの種類", + "power_sensor": "電力センサーエンティティ", + "min_power": "最小電力 (W)", + "off_delay": "サイクル終了遅延 (秒)", + "notify_actions": "通知アクション", + "notify_people": "指定した人が帰宅するまで配信を遅延", + "notify_only_when_home": "選択した人が帰宅するまで通知を遅延", + "notify_fire_events": "オートメーションイベントを発火", + "notify_start_services": "サイクル開始 - 通知ターゲット", + "notify_finish_services": "サイクル終了 - 通知ターゲット", + "notify_live_services": "ライブ進行状況 - 通知ターゲット", + "notify_before_end_minutes": "完了前通知(終了前の分数)", + "notify_title": "通知タイトル", + "notify_icon": "通知アイコン", + "notify_start_message": "開始メッセージの形式", + "notify_finish_message": "終了メッセージの形式", + "notify_pre_complete_message": "完了前メッセージの形式", + "show_advanced": "詳細設定を編集(次のステップ)" + }, + "data_description": { + "apply_suggestions": "このボックスにチェックを入れると、学習アルゴリズムの推奨値がフォームにコピーされます。保存前に確認してください。\n\n推奨値(学習より):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "家電製品の種類を選択します(洗濯機、乾燥機、食器洗い機、コーヒーメーカー、EV)。このタグはサイクルとともに保存され、整理に役立ちます。", + "power_sensor": "リアルタイムの電力(ワット)を報告するセンサーエンティティ。スマートプラグを交換しても履歴データを失わずにいつでも変更できます。", + "min_power": "このしきい値(ワット)を超える電力値は、家電製品が動作中であることを示します。ほとんどのデバイスでは 2W から始めてください。", + "off_delay": "サイクル完了とみなすには、平滑化された電力がこの秒数の間、停止しきい値を下回り続ける必要があります。デフォルト:120秒。", + "notify_actions": "通知用のオプションの Home Assistant アクションシーケンス(スクリプト、通知、TTSなど)。設定した場合、フォールバック通知サービスより先に実行されます。", + "notify_people": "在宅確認に使用するユーザーエンティティ。有効にすると、選択した人が少なくとも1人帰宅するまで通知の配信が遅延されます。", + "notify_only_when_home": "有効にすると、選択した人が少なくとも1人帰宅するまで通知が遅延されます。", + "notify_fire_events": "有効にすると、サイクル開始・終了時に Home Assistant イベント(ha_washdata_cycle_started、ha_washdata_cycle_ended)が発火し、オートメーションを駆動できます。", + "notify_start_services": "サイクルの開始時に警告する 1 つ以上の通知サービス。開始通知をスキップするには、空のままにします。", + "notify_finish_services": "サイクルが終了するか完了に近づくと警告を発する 1 つ以上の通知サービス。終了通知をスキップするには、空のままにします。", + "notify_live_services": "サイクルの実行中にライブ進行状況の更新を受信する 1 つ以上の通知サービス (モバイル コンパニオン アプリのみ)。ライブ更新をスキップするには、空のままにします。", + "notify_before_end_minutes": "サイクルの推定終了時刻の何分前に通知を発火するか。0に設定すると無効になります。デフォルト:0。", + "notify_title": "通知のカスタムタイトル。{device} プレースホルダーに対応。", + "notify_icon": "通知に使用するアイコン(例:mdi:washing-machine)。空白にするとアイコンなしで送信。", + "notify_start_message": "サイクル開始時に送信されるメッセージ。{device} プレースホルダーに対応。", + "notify_finish_message": "サイクル終了時に送信されるメッセージ。{device}、{duration}、{program}, {energy_kwh}, {cost} プレースホルダーに対応。", + "notify_pre_complete_message": "サイクルの実行中に定期的に更新されるライブ進行状況のテキスト。 {device}、{minutes}、{program} プレースホルダーをサポートします。" + } + }, + "notifications": { + "title": "通知", + "description": "通知チャンネル、テンプレート、リアルタイム進捗更新のオプションを設定します。", + "data": { + "notify_actions": "通知アクション", + "notify_people": "指定した人が帰宅するまで配信を遅延", + "notify_only_when_home": "選択した人が帰宅するまで通知を遅延", + "notify_fire_events": "オートメーションイベントを発火", + "notify_start_services": "サイクル開始 - 通知ターゲット", + "notify_finish_services": "サイクル終了 - 通知ターゲット", + "notify_live_services": "ライブ進行状況 - 通知ターゲット", + "notify_before_end_minutes": "完了前通知(終了前の分数)", + "notify_live_interval_seconds": "ライブ更新間隔(秒)", + "notify_live_overrun_percent": "ライブ更新超過許容量 (%)", + "notify_live_chronometer": "クロノメーター カウントダウン タイマー", + "notify_title": "通知タイトル", + "notify_icon": "通知アイコン", + "notify_start_message": "開始メッセージの形式", + "notify_finish_message": "終了メッセージの形式", + "notify_pre_complete_message": "完了前メッセージの形式", + "energy_price_entity": "エネルギー価格 - エンティティ (オプション)", + "energy_price_static": "エネルギー価格 - 静的値 (オプション)", + "go_back": "保存せずに戻る", + "notify_reminder_message": "リマインダーメッセージのフォーマット", + "notify_timeout_seconds": "自動終了後 (秒)", + "notify_channel": "通知チャンネル (ステータス/ライブ/リマインダー)", + "notify_finish_channel": "完了した通知チャネル" + }, + "data_description": { + "go_back": "これにチェックを入れて送信をクリックすると、上の変更を破棄してメインメニューに戻ります。", + "notify_actions": "通知用のオプションの Home Assistant アクションシーケンス(スクリプト、通知、TTSなど)。設定した場合、フォールバック通知サービスより先に実行されます。", + "notify_people": "在宅確認に使用するユーザーエンティティ。有効にすると、選択した人が少なくとも1人帰宅するまで通知の配信が遅延されます。", + "notify_only_when_home": "有効にすると、選択した人が少なくとも1人帰宅するまで通知が遅延されます。", + "notify_fire_events": "有効にすると、サイクル開始・終了時に Home Assistant イベント(ha_washdata_cycle_started、ha_washdata_cycle_ended)が発火し、オートメーションを駆動できます。", + "notify_start_services": "サイクルの開始時に警告する 1 つ以上の通知サービス (例: notify.mobile_app_my_phone)。開始通知をスキップするには、空のままにします。", + "notify_finish_services": "サイクルが終了するか完了に近づくと警告を発する 1 つ以上の通知サービス。終了通知をスキップするには、空のままにします。", + "notify_live_services": "サイクルの実行中にライブ進行状況の更新を受信する 1 つ以上の通知サービス (モバイル コンパニオン アプリのみ)。ライブ更新をスキップするには、空のままにします。", + "notify_before_end_minutes": "サイクルの推定終了時刻の何分前に通知を発火するか。0に設定すると無効になります。デフォルト:0。", + "notify_live_interval_seconds": "動作中に進捗更新を送信する頻度。値が小さいほどレスポンシブですが、通知数が増えます。最低30秒が適用されます - 30秒未満の値は自動的に30秒に切り上げられます。", + "notify_live_overrun_percent": "長時間・超過したサイクルに対する推定更新数の余裕マージン(例:20%で推定の1.2倍の更新が許可されます)。", + "notify_live_chronometer": "有効にすると、各ライブ アップデートに推定終了時間までのクロノメーター カウントダウンが含まれます。アップデートの間に、デバイス上で通知タイマーが自動的に作動します (Android コンパニオン アプリのみ)。", + "notify_title": "通知のカスタムタイトル。{device} プレースホルダーに対応。", + "notify_icon": "通知に使用するアイコン(例:mdi:washing-machine)。空白にするとアイコンなしで送信。", + "notify_start_message": "サイクル開始時に送信されるメッセージ。{device} プレースホルダーに対応。", + "notify_finish_message": "サイクル終了時に送信されるメッセージ。{device}、{duration}、{program}, {energy_kwh}, {cost} プレースホルダーに対応。", + "energy_price_entity": "現在の電力価格を保持する数値エンティティを選択します (例: sensor.electricity_price、input_number.energy_rate)。設定すると、{cost} プレースホルダーが有効になります。両方が設定されている場合は、静的な値よりも優先されます。", + "energy_price_static": "kWhあたりの固定電力料金。設定すると、{cost} プレースホルダーが有効になります。エンティティが上記で構成されている場合は無視されます。メッセージ テンプレートに通貨記号を追加します。 {cost} ユーロ。", + "notify_reminder_message": "1 回限りのリマインダーのテキストは、終了予定時刻の設定された分数前に送信されます。上記の定期的なライブ アップデートとは異なります。 {device}、{minutes}、{program} プレースホルダーをサポートします。", + "notify_timeout_seconds": "この秒数が経過すると通知を自動的に消去します (コンパニオン アプリの「タイムアウト」として転送されます)。 0 に設定すると、破棄されるまで保持されます。デフォルト: 0。", + "notify_channel": "Android コンパニオン アプリのステータス、進行状況、リマインダー通知用の通知チャネル。チャンネルのサウンドと重要性は、チャンネル名が初めて使用されるときにコンパニオン アプリで設定されます。WashData はチャンネル名のみを設定します。アプリのデフォルトの場合は空のままにします。", + "notify_finish_channel": "終了アラート用に別の Android チャンネルを作成し (リマインダーや洗濯待ちの小言でも使用)、独自のサウンドを持たせることができます。上のチャネルを再利用するには、空のままにしておきます。", + "notify_pre_complete_message": "サイクルの実行中に定期的に更新されるライブ進行状況のテキスト。 {device}、{minutes}、{program} プレースホルダーをサポートします。" + } + }, + "advanced_settings": { + "title": "詳細設定", + "description": "検出しきい値、学習、高度な動作を調整します。デフォルト値はほとんどの設定で適切に機能します。", + "sections": { + "suggestions_section": { + "name": "推奨設定", + "description": "学習済みの提案が {suggestions_count} 件あります。保存する前に提案された変更を確認するには、「推奨値を適用」にチェックを入れてください。", + "data": { + "apply_suggestions": "推奨値を適用" + }, + "data_description": { + "apply_suggestions": "このボックスにチェックを入れると、学習アルゴリズムの推奨値の確認ステップが開きます。何も自動的に保存されません。\n\n推奨値(学習より):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "検出と電力しきい値", + "description": "デバイスを ON または OFF とみなす条件、エネルギーゲート、および状態遷移のタイミングを設定します。", + "data": { + "start_duration_threshold": "開始デバウンス時間 (秒)", + "start_energy_threshold": "開始エネルギーゲート (Wh)", + "completion_min_seconds": "完了判定 最小実行時間 (秒)", + "start_threshold_w": "開始しきい値 (W)", + "stop_threshold_w": "停止しきい値 (W)", + "end_energy_threshold": "終了エネルギーゲート (Wh)", + "running_dead_zone": "動作デッドゾーン (秒)", + "end_repeat_count": "終了繰り返し回数", + "min_off_gap": "サイクル間の最小間隔 (秒)", + "sampling_interval": "サンプリング間隔 (秒)" + }, + "data_description": { + "start_duration_threshold": "この時間(秒)より短い電力スパイクを無視します。実際のサイクル開始前の起動スパイク(例:2秒間60W)をフィルタリングします。デフォルト:5秒。", + "start_energy_threshold": "確定されるには、サイクルが開始フェーズ中にこのエネルギー量(Wh)以上を蓄積する必要があります。デフォルト:0.005 Wh。", + "completion_min_seconds": "この時間より短いサイクルは、自然に終了しても「中断」としてマークされます。デフォルト:600秒。", + "start_threshold_w": "このしきい値を「超える」ことでサイクルが開始されます。スタンバイ電力より高く設定してください。デフォルト:min_power + 1 W。", + "stop_threshold_w": "このしきい値を「下回る」ことでサイクルが終了します。ノイズによる誤検知を防ぐため、ゼロ直上に設定してください。デフォルト:0.6 × min_power。", + "end_energy_threshold": "最後のオフ遅延ウィンドウのエネルギーがこの値(Wh)を下回る場合のみサイクルが終了します。電力がゼロ付近で変動する場合の誤終了を防ぎます。デフォルト:0.05 Wh。", + "running_dead_zone": "サイクル開始後、初期スピンアップ中の誤終了検知を防ぐため、この秒数の間は電力降下を無視します。0に設定すると無効になります。デフォルト:0秒。", + "end_repeat_count": "サイクルを終了する前に終了条件(off_delay の間しきい値を下回る電力)が連続して満たされる必要がある回数。高い値は一時停止中の誤終了を防ぎます。デフォルト:1。", + "min_off_gap": "前のサイクル終了からこの秒数以内に新しいサイクルが開始した場合、2つのサイクルは1つに統合されます。", + "sampling_interval": "最小サンプリング間隔 (秒単位)。この速度よりも速い更新は無視されます。デフォルト: 30 秒 (洗濯機、洗濯乾燥機、食器洗い機の場合は 2 秒)。" + } + }, + "matching_section": { + "name": "プロファイル照合と学習", + "description": "WashData が実行中のサイクルを学習済みプロファイルとどの程度積極的に照合するか、またいつフィードバックを求めるかを設定します。", + "data": { + "profile_match_min_duration_ratio": "プロファイル照合 最小所要時間比率 (0.1-1.0)", + "profile_match_interval": "プロファイル照合間隔 (秒)", + "profile_match_threshold": "プロファイル照合しきい値", + "profile_unmatch_threshold": "プロファイル不一致しきい値", + "auto_label_confidence": "自動ラベル付け信頼度 (0-1)", + "learning_confidence": "フィードバック要求信頼度 (0-1)", + "suppress_feedback_notifications": "フィードバック通知を抑制する", + "duration_tolerance": "所要時間許容誤差 (0-0.5)", + "smoothing_window": "平滑化ウィンドウ(サンプル数)", + "profile_duration_tolerance": "プロファイル照合 所要時間許容誤差 (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "プロファイル照合の最小所要時間比率。実行中のサイクルがプロファイル所要時間のこの割合以上に達している必要があります。低いほど早期照合になります。デフォルト:0.50(50%)。", + "profile_match_interval": "サイクル中にプロファイル照合を試みる頻度(秒)。値が小さいほど検出は速くなりますが CPU 使用量が増えます。デフォルト:300秒(5分)。", + "profile_match_threshold": "プロファイルが一致とみなされるために必要な最小 DTW 類似スコア(0.0〜1.0)。高いほど厳密な照合、誤検知が少なくなります。デフォルト:0.4。", + "profile_unmatch_threshold": "以前に一致したプロファイルが棄却される DTW 類似スコア(0.0〜1.0)。フリッカーを防ぐため、照合しきい値より低く設定してください。デフォルト:0.35。", + "auto_label_confidence": "完了時にこの信頼度以上のサイクルを自動的にラベル付けします(0.0〜1.0)。", + "learning_confidence": "イベントによるユーザー検証を要求する最小信頼度(0.0〜1.0)。", + "suppress_feedback_notifications": "有効にすると、WashData は内部的にフィードバックを追跡して要求しますが、サイクルの確認を求める永続的な通知は表示されません。サイクルが常に正しく検出され、プロンプトが煩わしい場合に便利です。", + "duration_tolerance": "動作中の残り時間の推定許容誤差(0.0〜0.5 は 0〜50% に対応)。", + "smoothing_window": "移動平均平滑化に使用する直近の電力測定値の数。", + "profile_duration_tolerance": "プロファイル照合が使用する総所要時間の分散許容誤差(0.0〜0.5)。デフォルト動作:±25%。" + } + }, + "timing_section": { + "name": "タイミング、メンテナンス、デバッグ", + "description": "ウォッチドッグ、進捗リセット、自動メンテナンス、デバッグエンティティの表示を設定します。", + "data": { + "watchdog_interval": "ウォッチドッグ間隔 (秒)", + "no_update_active_timeout": "無更新タイムアウト (秒)", + "progress_reset_delay": "進捗リセット遅延 (秒)", + "auto_maintenance": "自動メンテナンスを有効化", + "expose_debug_entities": "デバッグエンティティを公開", + "save_debug_traces": "デバッグトレースを保存" + }, + "data_description": { + "watchdog_interval": "動作中のウォッチドッグチェック間隔(秒)。デフォルト:5秒。警告:誤停止を避けるため、センサーの更新間隔より高く設定してください(例:センサーが60秒ごとに更新する場合は65秒以上)。", + "no_update_active_timeout": "変化時のみ公開するセンサー向け:この秒数の間更新が届かない場合のみ、アクティブなサイクルを強制終了します。低電力での完了には引き続き終了遅延が使用されます。", + "progress_reset_delay": "サイクルが完了(100%)した後、この秒数のアイドル状態が続いてから進捗を0%にリセットします。デフォルト:300秒。", + "auto_maintenance": "自動メンテナンスを有効にします(サンプルの修復、フラグメントの統合)。", + "expose_debug_entities": "デバッグ用の詳細センサー(信頼度、フェーズ、曖昧さ)を表示します。", + "save_debug_traces": "詳細なランキングと電力トレースデータを履歴に保存します(ストレージ使用量が増加します)。" + } + }, + "anti_wrinkle_section": { + "name": "しわ防止シールド(乾燥機)", + "description": "ゴーストサイクルを防ぐため、サイクル終了後のドラム回転を無視します。乾燥機 / 洗濯乾燥機のみ。", + "data": { + "anti_wrinkle_enabled": "しわ防止シールド(乾燥機/洗濯乾燥機のみ)", + "anti_wrinkle_max_power": "しわ防止最大電力 (W)(乾燥機/洗濯乾燥機のみ)", + "anti_wrinkle_max_duration": "しわ防止最大時間 (秒)(乾燥機/洗濯乾燥機のみ)", + "anti_wrinkle_exit_power": "しわ防止終了電力 (W)(乾燥機/洗濯乾燥機のみ)" + }, + "data_description": { + "anti_wrinkle_enabled": "サイクル終了後の短い低電力ドラム回転を無視して、ゴーストサイクルを防止します(乾燥機/洗濯乾燥機のみ)。", + "anti_wrinkle_max_power": "この電力以下のバーストはしわ防止回転として処理され、新しいサイクルとはみなされません。しわ防止シールドが有効な場合のみ適用されます。", + "anti_wrinkle_max_duration": "しわ防止回転として処理されるバーストの最大時間。しわ防止シールドが有効な場合のみ適用されます。", + "anti_wrinkle_exit_power": "電力がこのレベルを下回ると、直ちにしわ防止モードを終了します(真のオフ)。しわ防止シールドが有効な場合のみ適用されます。" + } + }, + "delay_start_section": { + "name": "遅延開始検出", + "description": "サイクルが実際に始まるまで、継続的な低電力スタンバイを「開始を待っています」として扱います。", + "data": { + "delay_start_detect_enabled": "遅延開始検出を有効にする", + "delay_confirm_seconds": "遅延開始: スタンバイ確認時間(秒)", + "delay_timeout_hours": "遅延開始: 最大待機時間 (h)" + }, + "data_description": { + "delay_start_detect_enabled": "有効にすると、持続的なスタンバイ電力が続く短い低電力ドレイン スパイクが遅延開始として検出されます。遅延中、状態センサーは「開始待機中」と表示します。サイクルが実際に開始されるまで、プログラム時間や進行状況は追跡されません。 start_threshold_w をドレイン スパイク電力よりも高く設定する必要があります。", + "delay_confirm_seconds": "電力が停止しきい値 (W) と開始しきい値 (W) の間にこの秒数留まると、WashData は「開始を待っています」状態に入ります。メニュー操作中の短い電力スパイクを除外します。デフォルト: 60秒。", + "delay_timeout_hours": "安全タイムアウト: この時間が経過してもマシンがまだ「起動待機中」の場合、WashData はオフにリセットされます。デフォルト: 8 時間。" + } + }, + "external_triggers_section": { + "name": "外部トリガー、ドア、一時停止", + "description": "オプションの外部終了トリガーセンサー、一時停止/クリーン検出用のドアセンサー、および一時停止時に電源を切るスイッチを設定します。", + "data": { + "external_end_trigger_enabled": "外部サイクル終了トリガーを有効化", + "external_end_trigger": "外部サイクル終了センサー", + "external_end_trigger_inverted": "トリガーロジックを反転(OFF でトリガー)", + "door_sensor_entity": "ドアセンサー", + "pause_cuts_power": "一時停止時に電源を切る", + "switch_entity": "スイッチエンティティ (一時停止電源切断用)", + "notify_unload_delay_minutes": "ランドリー待機通知遅延 (分)" + }, + "data_description": { + "external_end_trigger_enabled": "外部のバイナリセンサーを監視してサイクル完了をトリガーする機能を有効にします。", + "external_end_trigger": "バイナリセンサーエンティティを選択します。このセンサーがトリガーされると、現在のサイクルが「完了」状態で終了します。", + "external_end_trigger_inverted": "デフォルトでは、センサーがONになるとトリガーが発動します。このボックスにチェックを入れると、センサーがOFFになったときにトリガーされます。", + "door_sensor_entity": "機械ドア用のオプションのバイナリ センサー (オン = 開いている、オフ = 閉じている)。アクティブなサイクル中にドアが開くと、WashData はサイクルが意図的に一時停止されたことを確認します。サイクル終了後、ドアがまだ閉じている場合、ドアが開くまで状態は「Clean」に変わります。注: ドアを閉めても、一時停止したサイクルは自動的に再開されません。再開は、「サイクル再開」ボタンまたはサービスを介して手動でトリガーする必要があります。", + "pause_cuts_power": "[Pause Cycle] ボタンまたはサービスを使用して一時停止する場合は、設定されたスイッチ エンティティもオフにします。スイッチ エンティティがこのデバイスに設定されている場合にのみ有効です。", + "switch_entity": "一時停止または再開するときに切り替えるスイッチ エンティティ。 「一時停止時に電源を切る」が有効な場合に必要です。", + "notify_unload_delay_minutes": "サイクルが終了し、一定時間ドアが開かなかった後、終了通知チャネル経由で通知を送信します (ドア センサーが必要)。無効にするには 0 に設定します。デフォルト: 60 分。" + } + }, + "pump_section": { + "name": "ポンプモニター", + "description": "ポンプタイプのデバイス専用です。ポンプサイクルがこの時間を超えて動作するとイベントを発火します。", + "data": { + "pump_stuck_duration": "ポンプスタック警告しきい値 (秒) (ポンプのみ)" + }, + "data_description": { + "pump_stuck_duration": "この秒数が経過してもポンプ サイクルがまだ実行されている場合は、ha_washdata_pump_stuck イベントが 1 回発生します。これを使用してモーターの詰まりを検出します (通常、排水ポンプの動作は 60 秒未満です)。デフォルト: 1800 秒 (30 分)。ポンプ装置タイプのみ。" + } + }, + "device_link_section": { + "name": "デバイスリンク", + "description": "必要に応じて、この WashData デバイスを既存の Home Assistant デバイス (スマート プラグやアプライアンス自体など) にリンクします。その後、WashData デバイスはそのデバイスを「経由して接続」として表示されます。 WashData をスタンドアロン デバイスとして維持するには、空のままにします。", + "data": { + "linked_device": "リンクされたデバイス" + }, + "data_description": { + "linked_device": "WashData を接続するデバイスを選択します。これにより、デバイス レジストリに「接続経由」参照が追加されます。 WashData は独自のデバイス カードとエンティティを保持します。リンクを削除するには、選択を解除します。" + } + } + } + }, + "diagnostics": { + "title": "診断とメンテナンス", + "description": "断片化したサイクルの統合や保存データの移行などのメンテナンス操作を実行します。\n\n**ストレージ使用状況**\n\n- ファイルサイズ:{file_size_kb} KB\n- サイクル数:{cycle_count}\n- プロファイル数:{profile_count}\n- デバッグトレース:{debug_count}", + "menu_options": { + "reprocess_history": "メンテナンス:データの再処理と最適化", + "clear_debug_data": "デバッグデータを消去(スペースを解放)", + "wipe_history": "このデバイスのすべてのデータを消去(元に戻せません)", + "export_import": "設定付き JSON をエクスポート/インポート(コピー/貼り付け)", + "menu_back": "← 戻る" + } + }, + "clear_debug_data": { + "title": "デバッグデータを消去", + "description": "保存済みのすべてのデバッグトレースを削除しますか?ストレージが解放されますが、過去のサイクルの詳細なランキング情報が削除されます。" + }, + "manage_cycles": { + "title": "サイクルの管理", + "description": "最近のサイクル:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "過去のサイクルを自動ラベル付け", + "select_cycle_to_label": "特定のサイクルにラベルを付ける", + "select_cycle_to_delete": "サイクルを削除", + "interactive_editor": "統合/分割 インタラクティブエディター", + "trim_cycle_select": "サイクルデータをトリミング", + "menu_back": "← 戻る" + } + }, + "manage_cycles_empty": { + "title": "サイクルの管理", + "description": "まだサイクルが記録されていません。", + "menu_options": { + "auto_label_cycles": "過去のサイクルを自動ラベル付け", + "menu_back": "← 戻る" + } + }, + "manage_profiles": { + "title": "プロファイルの管理", + "description": "プロファイルの概要:\n{profile_summary}", + "menu_options": { + "create_profile": "新規プロファイルを作成", + "edit_profile": "プロファイルを編集/名前変更", + "delete_profile_select": "プロファイルを削除", + "profile_stats": "プロファイル統計", + "cleanup_profile": "履歴の整理 - グラフと削除", + "assign_profile_phases_select": "フェーズ範囲を割り当て", + "menu_back": "← 戻る" + } + }, + "manage_profiles_empty": { + "title": "プロファイルの管理", + "description": "まだプロファイルが作成されていません。", + "menu_options": { + "create_profile": "新規プロファイルを作成", + "menu_back": "← 戻る" + } + }, + "manage_phase_catalog": { + "title": "フェーズカタログの管理", + "description": "フェーズカタログ(このデバイスのデフォルト + カスタムフェーズ):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "新しいフェーズを作成", + "phase_catalog_edit_select": "フェーズを編集", + "phase_catalog_delete": "フェーズを削除", + "menu_back": "← 戻る" + } + }, + "phase_catalog_create": { + "title": "カスタムフェーズを作成", + "description": "カスタムフェーズの名前と説明を作成し、適用するデバイスの種類を選択します。", + "data": { + "target_device_type": "対象デバイスの種類", + "phase_name": "フェーズ名", + "phase_description": "フェーズの説明" + } + }, + "phase_catalog_edit_select": { + "title": "カスタムフェーズを編集", + "description": "編集するカスタムフェーズを選択します。", + "data": { + "phase_name": "カスタムフェーズ" + } + }, + "phase_catalog_edit": { + "title": "カスタムフェーズを編集", + "description": "フェーズを編集中:{phase_name}", + "data": { + "phase_name": "フェーズ名", + "phase_description": "フェーズの説明" + } + }, + "phase_catalog_delete": { + "title": "カスタムフェーズを削除", + "description": "削除するカスタムフェーズを選択します。このフェーズを使用して割り当てられた範囲はすべて削除されます。", + "data": { + "phase_name": "カスタムフェーズ" + } + }, + "assign_profile_phases": { + "title": "フェーズ範囲を割り当て", + "description": "プロファイルのフェーズプレビュー:**{profile_name}**\n\n{timeline_svg}\n\n**現在の範囲:**\n{current_ranges}\n\n以下の操作を使用して範囲の追加・編集・削除・保存を行います。", + "menu_options": { + "assign_profile_phases_add": "フェーズ範囲を追加", + "assign_profile_phases_edit_select": "フェーズ範囲を編集", + "assign_profile_phases_delete": "フェーズ範囲を削除", + "phase_ranges_clear": "すべての範囲を消去", + "assign_profile_phases_auto_detect": "フェーズを自動検出", + "phase_ranges_save": "保存して戻る", + "menu_back": "← 戻る" + } + }, + "assign_profile_phases_add": { + "title": "フェーズ範囲を追加", + "description": "現在の下書きにフェーズ範囲を1つ追加します。", + "data": { + "phase_name": "フェーズ", + "start_min": "開始分", + "end_min": "終了分" + } + }, + "assign_profile_phases_edit_select": { + "title": "フェーズ範囲を編集", + "description": "編集する範囲を選択します。", + "data": { + "range_index": "フェーズ範囲" + } + }, + "assign_profile_phases_edit": { + "title": "フェーズ範囲を編集", + "description": "選択したフェーズ範囲を更新します。", + "data": { + "phase_name": "フェーズ", + "start_min": "開始分", + "end_min": "終了分" + } + }, + "assign_profile_phases_delete": { + "title": "フェーズ範囲を削除", + "description": "下書きから削除する範囲を選択します。", + "data": { + "range_index": "フェーズ範囲" + } + }, + "assign_profile_phases_auto_detect": { + "title": "自動検出されたフェーズ", + "description": "プロファイル:**{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} 件のフェーズが自動的に検出されました。** 以下の操作を選択してください。", + "data": { + "action": "操作" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "検出されたフェーズに名前を付ける", + "description": "プロファイル:**{profile_name}**\n\n**{detected_count} 件のフェーズが検出されました:**\n{ranges_summary}\n\n各フェーズに名前を割り当てます。" + }, + "assign_profile_phases_select": { + "title": "フェーズ範囲を割り当て", + "description": "フェーズ範囲を設定するプロファイルを選択します。", + "data": { + "profile": "プロファイル" + } + }, + "profile_stats": { + "title": "プロファイル統計", + "description": "{stats_table}" + }, + "create_profile": { + "title": "新規プロファイルを作成", + "description": "手動または過去のサイクルから新しいプロファイルを作成します。\n\nプロファイル名の例:「デリケート」、「強力洗浄」、「おいそぎ」", + "data": { + "profile_name": "プロファイル名", + "reference_cycle": "参照サイクル(任意)", + "manual_duration": "手動設定時間(分)" + } + }, + "edit_profile": { + "title": "プロファイルを編集", + "description": "名前を変更するプロファイルを選択", + "data": { + "profile": "プロファイル" + } + }, + "rename_profile": { + "title": "プロファイルの詳細を編集", + "description": "現在のプロファイル:{current_name}", + "data": { + "new_name": "プロファイル名", + "manual_duration": "手動設定時間(分)" + } + }, + "delete_profile_select": { + "title": "プロファイルを削除", + "description": "削除するプロファイルを選択", + "data": { + "profile": "プロファイル" + } + }, + "delete_profile_confirm": { + "title": "プロファイルの削除を確認", + "description": "⚠️ プロファイルを完全に削除します:{profile_name}", + "data": { + "unlabel_cycles": "このプロファイルを使用しているサイクルからラベルを削除" + } + }, + "auto_label_cycles": { + "title": "過去のサイクルを自動ラベル付け", + "description": "合計 {total_count} サイクルが見つかりました。プロフィール: {profiles}", + "data": { + "confidence_threshold": "最小信頼度 (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "ラベル付けするサイクルを選択", + "description": "✓ = 完了、⚠ = 再開、✗ = 中断", + "data": { + "cycle_id": "サイクル" + } + }, + "select_cycle_to_delete": { + "title": "サイクルを削除", + "description": "⚠️ 選択したサイクルを完全に削除します", + "data": { + "cycle_id": "削除するサイクル" + } + }, + "label_cycle": { + "title": "サイクルにラベルを付ける", + "description": "{cycle_info}", + "data": { + "profile_name": "プロファイル", + "new_profile_name": "新しいプロファイル名(新規作成の場合)" + } + }, + "post_process": { + "title": "履歴を処理(統合/分割)", + "description": "時間枠内で断片化された実行をマージし、誤ってマージされたサイクルを分割することにより、サイクル履歴をクリーンアップします。過去を振り返る時間数を入力します (すべてに 999999 を使用します)。", + "data": { + "time_range": "振り返りウィンドウ(時間)", + "gap_seconds": "統合/分割のギャップ(秒)" + }, + "data_description": { + "time_range": "マージする断片化されたサイクルをスキャンする時間数。すべての履歴データを処理するには、999999 を使用します。", + "gap_seconds": "分割と統合を決定するしきい値。これより「短い」ギャップは統合されます。「長い」ギャップは分割されます。誤って統合されたサイクルを強制分割するには値を下げてください。" + } + }, + "cleanup_profile": { + "title": "履歴の整理 - プロファイルを選択", + "description": "視覚化するプロファイルを選択します。このプロファイルのすべての過去サイクルを示すグラフが生成され、外れ値の特定に役立ちます。", + "data": { + "profile": "プロファイル" + } + }, + "cleanup_select": { + "title": "履歴の整理 - グラフと削除", + "description": "![グラフ]({graph_url})\n\n**{profile_name} を表示中**\n\nグラフは個々のサイクルを色付きの線で表示します。外れ値(グループから離れた線)を特定し、以下のリストで対応する色を見つけて削除します。\n\n**完全に削除するサイクルを選択:**", + "data": { + "cycles_to_delete": "削除するサイクル(色を照合してください)" + } + }, + "interactive_editor": { + "title": "統合/分割 インタラクティブエディター", + "description": "サイクル履歴に対して実行する手動操作を選択します。", + "menu_options": { + "editor_split": "サイクルを分割(ギャップを検索)", + "editor_merge": "サイクルを統合(断片を結合)", + "editor_delete": "サイクルを削除", + "menu_back": "← 戻る" + } + }, + "editor_select": { + "title": "サイクルを選択", + "description": "処理するサイクルを選択します。\n\n{info_text}", + "data": { + "selected_cycles": "サイクル" + } + }, + "editor_configure": { + "title": "設定して適用", + "description": "{preview_md}", + "data": { + "confirm_action": "操作", + "merged_profile": "結果のプロファイル", + "new_profile_name": "新しいプロファイル名", + "confirm_commit": "はい、この操作を確認します", + "segment_0_profile": "セグメント1のプロファイル", + "segment_1_profile": "セグメント2のプロファイル", + "segment_2_profile": "セグメント3のプロファイル", + "segment_3_profile": "セグメント4のプロファイル", + "segment_4_profile": "セグメント5のプロファイル", + "segment_5_profile": "セグメント6のプロファイル", + "segment_6_profile": "セグメント7のプロファイル", + "segment_7_profile": "セグメント8のプロファイル", + "segment_8_profile": "セグメント9のプロファイル", + "segment_9_profile": "セグメント10のプロファイル" + } + }, + "editor_split_params": { + "title": "分割の設定", + "description": "このサイクルの分割感度を調整します。これより長いアイドルギャップがあれば分割されます。", + "data": { + "split_mode": "分割方法" + } + }, + "editor_split_auto_params": { + "title": "自動分割の設定", + "description": "このサイクルを分割する感度を調整します。これより長いアイドルギャップは分割の原因になります。", + "data": { + "min_gap_seconds": "分割ギャップしきい値(秒)" + } + }, + "editor_split_manual_params": { + "title": "手動分割タイムスタンプ", + "description": "{preview_md}サイクルの範囲: **{cycle_start_wallclock} → {cycle_end_wallclock}**、日付は {cycle_date}。\n\n1つ以上の分割タイムスタンプ(`HH:MM` または `HH:MM:SS`)を1行に1つずつ入力してください。サイクルは各タイムスタンプで分割されます — N 個のタイムスタンプから N+1 個のセグメントが生成されます。", + "data": { + "split_timestamps": "分割タイムスタンプ" + } + }, + "reprocess_history": { + "title": "メンテナンス:データの再処理と最適化", + "description": "履歴データの深いクリーニングを実行します:\n1. ゼロ電力の読み取り値(無音)をトリミング。\n2. サイクルのタイミング/所要時間を修正。\n3. すべての統計モデル(エンベロープ)を再計算。\n\n**データの問題を修正したり、新しいアルゴリズムを適用するには、これを実行してください。**" + }, + "wipe_history": { + "title": "履歴を消去(テスト専用)", + "description": "⚠️ このデバイスのすべての保存済みサイクルとプロファイルを完全に削除します。この操作は元に戻せません!" + }, + "export_import": { + "title": "JSON のエクスポート/インポート", + "description": "このデバイスのサイクル、プロファイル、設定をコピー/貼り付けします。以下でエクスポートするか、JSONを貼り付けてインポートします。", + "data": { + "mode": "操作", + "json_payload": "完全な設定 JSON" + }, + "data_description": { + "mode": "エクスポートを選択して現在のデータと設定をコピーするか、インポートを選択して別の WashData デバイスからエクスポートされたデータを貼り付けます。", + "json_payload": "エクスポートの場合:この JSON をコピーします(サイクル、プロファイル、すべての調整済み設定を含む)。インポートの場合:WashData からエクスポートした JSON を貼り付けます。" + } + }, + "record_cycle": { + "title": "サイクルを記録", + "description": "干渉なしでクリーンなプロファイルを作成するため、サイクルを手動で記録します。\n\n状態:**{status}**\n経過時間:{duration}秒\nサンプル数:{samples}", + "menu_options": { + "record_refresh": "状態を更新", + "record_stop": "記録を停止(保存して処理)", + "record_start": "新しい記録を開始", + "record_process": "最後の記録を処理(トリミングして保存)", + "record_discard": "最後の記録を破棄", + "menu_back": "← 戻る" + } + }, + "record_process": { + "title": "記録を処理", + "description": "![グラフ]({graph_url})\n\n**グラフは生の記録を表示します。** 青 = 保持、赤 = トリミング候補。\n*グラフは静的で、フォームの変更では更新されません。*\n\n記録が停止しました。トリミングは検出されたサンプリングレート(〜{sampling_rate}秒)に合わせられます。\n\n生の所要時間:{duration}秒\nサンプル数:{samples}", + "data": { + "head_trim": "開始トリミング(秒)", + "tail_trim": "終了トリミング(秒)", + "save_mode": "保存先", + "profile_name": "プロファイル名" + } + }, + "learning_feedbacks": { + "title": "学習フィードバック", + "description": "保留中のフィードバック: {count}\n\n{pending_table}\n\n処理するサイクルのレビューリクエストを選択します。", + "menu_options": { + "learning_feedbacks_pick": "保留中のフィードバックを確認", + "learning_feedbacks_dismiss_all": "保留中のフィードバックをすべて閉じる", + "menu_back": "← 戻る" + } + }, + "learning_feedbacks_pick": { + "title": "確認するフィードバックを選択", + "description": "開くサイクルレビュー依頼を選択してください。", + "data": { + "selected_feedback": "確認するサイクルを選択" + } + }, + "learning_feedbacks_empty": { + "title": "学習フィードバック", + "description": "保留中のフィードバックリクエストはありません。", + "menu_options": { + "menu_back": "← 戻る" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "保留中のフィードバックをすべて破棄", + "description": "**{count}** 件の保留中のフィードバック依頼を破棄しようとしています。破棄されたサイクルは履歴に残りますが、新しい学習シグナルには寄与しません。これは元に戻せません。\n\n✅ 下のボックスをチェックし、**送信** をクリックしてすべて破棄します。\n❌ チェックを外したままにし、**送信** をクリックしてキャンセルして戻ります。", + "data": { + "confirm_dismiss_all": "確認: 保留中のフィードバックリクエストをすべて閉じます" + } + }, + "resolve_feedback": { + "title": "フィードバックを解決", + "description": "{comparison_img}{candidates_table}\n**検出されたプロファイル**:{detected_profile}({confidence_pct}%)\n**推定所要時間**:{est_duration_min} 分\n**実際の所要時間**:{act_duration_min} 分\n\n__以下の操作を選択してください:__", + "data": { + "action": "どうしますか?", + "corrected_profile": "修正後のプログラム(修正する場合)", + "corrected_duration": "修正後の所要時間(秒)(修正する場合)" + } + }, + "trim_cycle_select": { + "title": "サイクルのトリミング - サイクルを選択", + "description": "トリミングするサイクルを選択します。✓ = 完了、⚠ = 再開、✗ = 中断", + "data": { + "cycle_id": "サイクル" + } + }, + "trim_cycle": { + "title": "サイクルをトリミング", + "description": "現在のウィンドウ:{trim_summary}\n\n{trim_svg}", + "data": { + "action": "操作" + } + }, + "trim_cycle_start": { + "title": "トリミング開始点を設定", + "description": "サイクルウィンドウ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\n現在の開始: **{current_wallclock}** (サイクル開始から +{current_offset_min} 分)\n\n以下のクロックピッカーを使用して、新しい開始時間を選択します。", + "data": { + "trim_start_time": "新しい開始時間", + "trim_start_min": "新しい開始 (サイクル開始からの分)" + } + }, + "trim_cycle_end": { + "title": "トリミング終了点を設定", + "description": "サイクルウィンドウ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\n現在の終了: **{current_wallclock}** (サイクル開始から +{current_offset_min} 分)\n\n以下のクロックピッカーを使用して、新しい終了時刻を選択します。", + "data": { + "trim_end_time": "新しい終了時間", + "trim_end_min": "新しい終了 (サイクル開始からの分)" + } + } + }, + "error": { + "import_failed": "インポートに失敗しました。有効な WashData エクスポート JSON を貼り付けてください。", + "select_exactly_one": "この操作にはちょうど1つのサイクルを選択してください。", + "select_at_least_one": "この操作には少なくとも1つのサイクルを選択してください。", + "select_at_least_two": "この操作には少なくとも2つのサイクルを選択してください。", + "profile_exists": "この名前のプロファイルはすでに存在します", + "rename_failed": "プロファイルの名前変更に失敗しました。プロファイルが存在しないか、新しい名前がすでに使用されています。", + "assignment_failed": "サイクルへのプロファイルの割り当てに失敗しました", + "duplicate_phase": "この名前のフェーズはすでに存在します。", + "phase_not_found": "選択されたフェーズが見つかりませんでした。", + "invalid_phase_name": "フェーズ名を空にすることはできません。", + "phase_name_too_long": "フェーズ名が長すぎます。", + "invalid_phase_range": "フェーズ範囲が無効です。終了は開始より後である必要があります。", + "invalid_phase_timestamp": "タイムスタンプが無効です。YYYY-MM-DD HH:MM または HH:MM 形式を使用してください。", + "overlapping_phase_ranges": "フェーズ範囲が重複しています。範囲を調整して再試行してください。", + "incomplete_phase_row": "各フェーズ行には、選択したモードのフェーズと完全な開始/終了値が必要です。", + "timestamp_mode_cycle_required": "タイムスタンプモードを使用する場合は、ソースサイクルを選択してください。", + "no_phase_ranges": "そのアクションのフェーズ範囲はまだありません。", + "feedback_notification_title": "WashData:サイクルの確認({device})", + "feedback_notification_message": "**デバイス**:{device}\n**プログラム**:{program}(信頼度 {confidence}%)\n**時刻**:{time}\n\nWashData は検出されたこのサイクルの確認にあなたの助けを必要としています。\n\n**設定 > デバイスとサービス > WashData > 設定 > 学習フィードバック** で結果を確認または修正してください。", + "suggestions_ready_notification_title": "WashData:推奨設定が準備できました({device})", + "suggestions_ready_notification_message": "**推奨設定**センサーが **{count}** 件の実行可能な提案を報告しています。\n\n確認して適用するには:**設定 > デバイスとサービス > WashData > 設定 > 詳細設定 > 推奨値を適用**。\n\n提案は任意であり、保存前に確認のために表示されます。", + "trim_range_invalid": "開始は終了より前である必要があります。トリミングポイントを調整してください。", + "trim_failed": "トリミングに失敗しました。サイクルが存在しないか、ウィンドウが空です。", + "invalid_split_timestamp": "タイムスタンプが無効か、サイクルウィンドウの範囲外です。サイクルウィンドウ内の HH:MM または HH:MM:SS を使用してください。", + "no_split_segments_found": "これらのタイムスタンプから有効なセグメントを作成できませんでした。各タイムスタンプがサイクルウィンドウ内にあり、各セグメントが少なくとも1分以上あることを確認してください。", + "auto_tune_suggestion": "{device_type} {device_title} がゴースト サイクルを検出しました。推奨される min_power の変更: {current_min}W -> {new_min}W (自動的には適用されません)。", + "auto_tune_title": "WashData 自動調整", + "auto_tune_fallback": "{device_type} {device_title} がゴースト サイクルを検出しました。推奨される min_power の変更: {current_min}W -> {new_min}W (自動的には適用されません)。" + }, + "abort": { + "no_cycles_found": "サイクルが見つかりません", + "no_split_segments_found": "選択したサイクルに分割可能なセグメントが見つかりませんでした。", + "cycle_not_found": "選択したサイクルを読み込めませんでした。", + "no_power_data": "選択したサイクルの電力トレースデータがありません。", + "no_profiles_found": "プロファイルが見つかりません。先にプロファイルを作成してください。", + "no_profiles_for_matching": "照合に使用できるプロファイルがありません。先にプロファイルを作成してください。", + "no_unlabeled_cycles": "すべてのサイクルにすでにラベルが付いています", + "no_suggestions": "まだ推奨値がありません。いくつかのサイクルを実行してから再試行してください。", + "no_predictions": "予測なし", + "no_custom_phases": "カスタムフェーズが見つかりません。", + "reprocess_success": "{count} 件のサイクルを正常に再処理しました。", + "debug_data_cleared": "{count} 件のサイクルからデバッグデータを正常に消去しました。" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "サイクル開始", + "cycle_finish": "サイクル終了", + "cycle_live": "ライブ進捗" + } + }, + "common_text": { + "options": { + "unlabeled": "(ラベルなし)", + "create_new_profile": "新規プロファイルを作成", + "remove_label": "ラベルを削除", + "no_reference_cycle": "(参照サイクルなし)", + "all_device_types": "すべてのデバイスの種類", + "current_suffix": "(現在)", + "editor_select_info": "分割する場合は1つ、統合する場合は2つ以上のサイクルを選択します。", + "editor_select_info_split": "分割するサイクルを1件選択してください。", + "editor_select_info_merge": "統合するサイクルを2件以上選択してください。", + "editor_select_info_delete": "削除するサイクルを1件以上選択してください。", + "no_cycles_recorded": "まだサイクルが記録されていません。", + "profile_summary_line": "- **{name}**:{count} サイクル、平均 {avg} 分", + "no_profiles_created": "まだプロファイルが作成されていません。", + "phase_preview": "フェーズプレビュー", + "phase_preview_no_curve": "平均プロファイル曲線はまだ利用できません。このプロファイルのサイクルをさらに実行/ラベル付けしてください。", + "average_power_curve": "平均電力曲線", + "unit_min": "分", + "profile_option_fmt": "{name}({count} サイクル、平均 〜{duration} 分)", + "profile_option_short_fmt": "{name}({count} サイクル、〜{duration} 分)", + "deleted_cycles_from_profile": "{profile} から {count} 件のサイクルを削除しました。", + "not_enough_data_graph": "グラフを生成するデータが不足しています。", + "no_profiles_found": "プロファイルが見つかりません。", + "cycle_info_fmt": "サイクル:{start}、{duration} 分、現在:{label}", + "top_candidates_header": "### 上位候補", + "tbl_profile": "プロファイル", + "tbl_confidence": "信頼度", + "tbl_correlation": "相関", + "tbl_duration_match": "時間の一致", + "detected_profile": "検出されたプロファイル", + "estimated_duration": "推定所要時間", + "actual_duration": "実際の所要時間", + "choose_action": "__以下の操作を選択してください:__", + "tbl_count": "件数", + "tbl_avg": "平均", + "tbl_min": "最小", + "tbl_max": "最大", + "tbl_energy_avg": "エネルギー(平均)", + "tbl_energy_total": "エネルギー(合計)", + "tbl_consistency": "一貫性", + "tbl_last_run": "最終実行", + "graph_legend_title": "グラフの凡例", + "graph_legend_body": "青いバンドは観測された最小・最大消費電力の範囲を表します。線は平均電力曲線を示します。", + "recording_preview": "記録のプレビュー", + "trim_start": "トリミング開始", + "trim_end": "トリミング終了", + "split_preview_title": "分割プレビュー", + "split_preview_found_fmt": "{count} 件のセグメントが見つかりました。", + "split_preview_confirm_fmt": "確認をクリックするとこのサイクルを {count} つの別々のサイクルに分割します。", + "merge_preview_title": "統合プレビュー", + "merge_preview_joining_fmt": "{count} 件のサイクルを統合します。ギャップは 0W の読み取り値で埋められます。", + "editor_delete_preview_title": "削除プレビュー", + "editor_delete_preview_intro": "選択したサイクルは完全に削除されます:", + "editor_delete_preview_confirm": "これらのサイクル記録を完全に削除するには「確認」をクリックしてください。", + "no_power_preview": "*プレビュー用の電力データがありません。*", + "profile_comparison": "プロファイル比較", + "actual_cycle_label": "このサイクル(実際)", + "storage_usage_header": "ストレージ使用状況", + "storage_file_size": "ファイルサイズ", + "storage_cycles": "サイクル", + "storage_profiles": "プロファイル", + "storage_debug_traces": "デバッグトレース", + "overview_suffix": "概要", + "phase_preview_for_profile": "プロファイルのフェーズプレビュー", + "current_ranges_header": "現在の範囲", + "assign_phases_help": "以下の操作を使用して範囲の追加・編集・削除・保存を行います。", + "profile_summary_header": "プロファイルの概要", + "recent_cycles_header": "最近のサイクル", + "trim_cycle_preview_title": "サイクルトリミングのプレビュー", + "trim_cycle_preview_no_data": "このサイクルの電力データがありません。", + "trim_cycle_preview_kept_suffix": "保持", + "table_program": "プログラム", + "table_when": "日時", + "table_length": "所要時間", + "table_match": "一致", + "table_profile": "プロファイル", + "table_cycles": "サイクル", + "table_avg_length": "平均所要時間", + "table_last_run": "最終実行", + "table_avg_energy": "平均エネルギー", + "table_detected_program": "検出されたプログラム", + "table_confidence": "信頼度", + "table_reported": "報告", + "phase_builtin_suffix": "(内蔵)", + "phase_none_available": "利用可能なフェーズがありません。", + "settings_deprecation_warning": "⚠️ **非推奨のデバイス タイプ:** {device_type} は将来のリリースで削除される予定です。 WashData のマッチング パイプラインは、このアプライアンス クラスに対して信頼できる結果を生成しません。統合は非推奨期間中も機能し続けます。この警告を消すには、以下の **デバイス タイプ** をサポートされているタイプ (洗濯機、乾燥機、洗濯乾燥機コンボ、食器洗い機、エアフライヤー、パンメーカー、またはポンプ) のいずれかに切り替えるか、アプライアンスがサポートされているタイプのいずれにも一致しない場合は **その他 (詳細)** に切り替えます。 **その他 (詳細)** には、特定のアプライアンス用に調整されていない意図的に汎用的なデフォルトが同梱されているため、しきい値、タイムアウト、および一致するパラメーターを自分で構成する必要があります。切り替えても、既存の設定はすべて保持されます。", + "phase_other_device_types": "他のデバイスタイプ:" + } + }, + "interactive_editor_action": { + "options": { + "split": "サイクルを分割(ギャップを検索)", + "merge": "サイクルを統合(断片を結合)", + "delete": "サイクルを削除" + } + }, + "split_mode": { + "options": { + "auto": "アイドルギャップを自動検出", + "manual": "手動タイムスタンプ" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "メンテナンス:データの再処理と最適化", + "clear_debug_data": "デバッグデータを消去(スペースを解放)", + "wipe_history": "このデバイスのすべてのデータを消去(元に戻せません)", + "export_import": "設定付き JSON をエクスポート/インポート(コピー/貼り付け)" + } + }, + "export_import_mode": { + "options": { + "export": "すべてのデータをエクスポート", + "import": "データをインポート/マージ" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "過去のサイクルを自動ラベル付け", + "select_cycle_to_label": "特定のサイクルにラベルを付ける", + "select_cycle_to_delete": "サイクルを削除", + "interactive_editor": "統合/分割 インタラクティブエディター", + "trim_cycle": "サイクルデータをトリミング" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "新規プロファイルを作成", + "edit_profile": "プロファイルを編集/名前変更", + "delete_profile": "プロファイルを削除", + "profile_stats": "プロファイル統計", + "cleanup_profile": "履歴の整理 - グラフと削除", + "assign_phases": "フェーズ範囲を割り当て" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "新しいフェーズを作成", + "edit_custom_phase": "フェーズを編集", + "delete_custom_phase": "フェーズを削除" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "フェーズ範囲を追加", + "edit_range": "フェーズ範囲を編集", + "delete_range": "フェーズ範囲を削除", + "clear_ranges": "すべての範囲を消去", + "auto_detect_ranges": "フェーズを自動検出", + "save_ranges": "保存して戻る" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "状態を更新", + "stop_recording": "記録を停止(保存して処理)", + "start_recording": "新しい記録を開始", + "process_recording": "最後の記録を処理(トリミングして保存)", + "discard_recording": "最後の記録を破棄" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "新規プロファイルを作成", + "existing_profile": "既存のプロファイルに追加", + "discard": "記録を破棄" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "確認 - 検出は正しい", + "correct": "修正 - プログラム/時間を選択", + "ignore": "無視 - 誤検知/ノイジーなサイクル", + "delete": "削除 - 履歴から削除" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "フェーズに名前を付けて適用", + "cancel": "変更なしで戻る" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "開始点を設定", + "set_end": "終了点を設定", + "reset": "全期間にリセット", + "apply": "トリミングを適用", + "cancel": "キャンセル" + } + }, + "device_type": { + "options": { + "washing_machine": "洗濯機", + "dryer": "ドライヤー", + "washer_dryer": "洗濯乾燥機コンボ", + "dishwasher": "食器洗い機", + "coffee_machine": "コーヒーマシン (非推奨)", + "ev": "電気自動車 (非推奨)", + "air_fryer": "エアーフライヤー", + "heat_pump": "ヒートポンプ (非推奨)", + "bread_maker": "パンメーカー", + "pump": "ポンプ/サンプポンプ", + "oven": "オーブン (非推奨)", + "other": "その他(上級)" + } + } + }, + "services": { + "label_cycle": { + "name": "サイクルにラベルを付ける", + "description": "既存のプロファイルを過去のサイクルに割り当てる、またはラベルを削除します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "ラベルを付ける WashData デバイス。" + }, + "cycle_id": { + "name": "サイクル ID", + "description": "ラベルを付けるサイクルの ID。" + }, + "profile_name": { + "name": "プロファイル名", + "description": "既存のプロファイル名(プロファイル管理メニューでプロファイルを作成)。ラベルを削除するには空白にします。" + } + } + }, + "create_profile": { + "name": "プロファイルを作成", + "description": "新しいプロファイルを作成します(スタンドアロンまたは参照サイクルに基づく)。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイス。" + }, + "profile_name": { + "name": "プロファイル名", + "description": "新しいプロファイルの名前(例:「強力洗浄」、「デリケート」)。" + }, + "reference_cycle_id": { + "name": "参照サイクル ID", + "description": "このプロファイルの基にするオプションのサイクル ID。" + } + } + }, + "delete_profile": { + "name": "プロファイルを削除", + "description": "プロファイルを削除し、オプションでそのプロファイルを使用しているサイクルのラベルを削除します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイス。" + }, + "profile_name": { + "name": "プロファイル名", + "description": "削除するプロファイル。" + }, + "unlabel_cycles": { + "name": "サイクルのラベルを削除", + "description": "このプロファイルを使用しているサイクルからプロファイルラベルを削除します。" + } + } + }, + "auto_label_cycles": { + "name": "過去のサイクルを自動ラベル付け", + "description": "プロファイル照合を使用してラベルなしのサイクルに遡及的にラベルを付けます。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイス。" + }, + "confidence_threshold": { + "name": "信頼度しきい値", + "description": "ラベルを適用する最小照合信頼度(0.50-0.95)。" + } + } + }, + "export_config": { + "name": "設定をエクスポート", + "description": "このデバイスのプロファイル、サイクル、設定を JSON ファイルにエクスポートします(デバイスごと)。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "エクスポートする WashData デバイス。" + }, + "path": { + "name": "パス", + "description": "書き込むオプションの絶対ファイルパス(デフォルト:/config/ha_washdata_export_{entry}.json)。" + } + } + }, + "import_config": { + "name": "設定をインポート", + "description": "JSON エクスポートファイルからこのデバイスのプロファイル、サイクル、設定をインポートします(設定が上書きされる場合があります)。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "インポート先の WashData デバイス。" + }, + "path": { + "name": "パス", + "description": "インポートするエクスポート JSON ファイルへの絶対パス。" + } + } + }, + "submit_cycle_feedback": { + "name": "サイクルフィードバックを送信", + "description": "完了したサイクルの自動検出されたプログラムを確認または修正します。`entry_id`(上級者向け)または `device_id`(推奨)のいずれかを指定します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイス(entry_id の代わりに推奨)。" + }, + "entry_id": { + "name": "エントリ ID", + "description": "デバイスの設定エントリ ID(device_id の代替)。" + }, + "cycle_id": { + "name": "サイクル ID", + "description": "フィードバック通知/ログに表示されるサイクル ID。" + }, + "user_confirmed": { + "name": "検出されたプログラムを確認", + "description": "検出されたプログラムが正しい場合は true に設定します。" + }, + "corrected_profile": { + "name": "修正後のプロファイル", + "description": "確認しない場合は、正しいプロファイル/プログラム名を指定します。" + }, + "corrected_duration": { + "name": "修正後の所要時間(秒)", + "description": "オプションの修正後の所要時間(秒)。" + }, + "notes": { + "name": "メモ", + "description": "このサイクルに関するオプションのメモ。" + } + } + }, + "record_start": { + "name": "サイクル記録を開始", + "description": "クリーンなサイクルの手動記録を開始します(すべての照合ロジックをバイパス)。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "記録する WashData デバイス。" + } + } + }, + "record_stop": { + "name": "サイクル記録を停止", + "description": "手動記録を停止します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "記録を停止する WashData デバイス。" + } + } + }, + "trim_cycle": { + "name": "サイクルをトリミング", + "description": "過去のサイクルの電力データを特定の時間ウィンドウにトリミングします。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイス。" + }, + "cycle_id": { + "name": "サイクル ID", + "description": "トリミングするサイクルの ID。" + }, + "trim_start_s": { + "name": "トリミング開始(秒)", + "description": "このオフセット(秒)からデータを保持します。デフォルト:0。" + }, + "trim_end_s": { + "name": "トリミング終了(秒)", + "description": "このオフセット(秒)までデータを保持します。デフォルト = 全所要時間。" + } + } + }, + "pause_cycle": { + "name": "サイクルを一時停止する", + "description": "WashData デバイスのアクティブなサイクルを一時停止します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "WashData デバイスを一時停止します。" + } + } + }, + "resume_cycle": { + "name": "サイクルを再開する", + "description": "WashData デバイスの一時停止したサイクルを再開します。", + "fields": { + "device_id": { + "name": "デバイス", + "description": "再開する WashData デバイス。" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "動作中" + }, + "match_ambiguity": { + "name": "照合の曖昧さ" + } + }, + "sensor": { + "washer_state": { + "name": "状態", + "state": { + "off": "オフ", + "idle": "待機中", + "starting": "起動中", + "running": "動作中", + "paused": "一時停止中", + "user_paused": "ユーザーにより一時停止中", + "ending": "終了中", + "finished": "完了", + "anti_wrinkle": "しわ防止", + "delay_wait": "開始を待っています", + "interrupted": "中断", + "force_stopped": "強制停止", + "rinse": "すすぎ", + "unknown": "不明", + "clean": "クリーン" + } + }, + "washer_program": { + "name": "プログラム" + }, + "time_remaining": { + "name": "残り時間" + }, + "total_duration": { + "name": "総所要時間" + }, + "cycle_progress": { + "name": "進捗" + }, + "current_power": { + "name": "現在の電力" + }, + "elapsed_time": { + "name": "経過時間" + }, + "debug_info": { + "name": "デバッグ情報" + }, + "match_confidence": { + "name": "照合信頼度" + }, + "top_candidates": { + "name": "上位候補", + "state": { + "none": "なし" + } + }, + "current_phase": { + "name": "現在のフェーズ" + }, + "wash_phase": { + "name": "フェーズ" + }, + "profile_cycle_count": { + "name": "プロファイル {profile_name} の件数", + "unit_of_measurement": "サイクル" + }, + "suggestions": { + "name": "利用可能な推奨設定" + }, + "pump_runs_today": { + "name": "ポンプの稼働 (過去 24 時間)" + }, + "cycle_count": { + "name": "サイクルカウント" + } + }, + "select": { + "program_select": { + "name": "サイクルプログラム", + "state": { + "auto_detect": "自動検出" + } + } + }, + "button": { + "force_end_cycle": { + "name": "サイクルを強制終了" + }, + "pause_cycle": { + "name": "サイクルを一時停止する" + }, + "resume_cycle": { + "name": "サイクルを再開する" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "有効な device_id が必要です。" + }, + "cycle_id_required": { + "message": "有効なcycle_idが必要です。" + }, + "profile_name_required": { + "message": "有効な profile_name が必要です。" + }, + "device_not_found": { + "message": "デバイスが見つかりません。" + }, + "no_config_entry": { + "message": "デバイスの設定エントリが見つかりません。" + }, + "integration_not_loaded": { + "message": "このデバイスのインテグレーションが読み込まれていません。" + }, + "cycle_not_found_or_no_power": { + "message": "サイクルが見つからないか、電力データがありません。" + }, + "trim_failed_empty_window": { + "message": "トリミングに失敗しました - サイクルが見つからないか、電力データがないか、結果ウィンドウが空です。" + }, + "trim_invalid_range": { + "message": "trim_end_s は trim_start_s より大きい必要があります。" + } + } +} diff --git a/custom_components/ha_washdata/translations/ka.json b/custom_components/ha_washdata/translations/ka.json new file mode 100644 index 0000000..b5792e2 --- /dev/null +++ b/custom_components/ha_washdata/translations/ka.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-ის კონფიგურაცია", + "description": "დააკონფიგურირეთ თქვენი სარეცხი მანქანა ან სხვა მოწყობილობა.\n\nსაჭიროა დენის სენსორი.\n\n**შემდეგ მოგეკითხებათ, გსურთ თუ არა თქვენი პირველი პროფილის შექმნა.**", + "data": { + "name": "მოწყობილობის სახელი", + "device_type": "მოწყობილობის ტიპი", + "power_sensor": "დენის სენსორი", + "min_power": "მინიმალური სიმძლავრის ზღვარი (W)" + }, + "data_description": { + "name": "ამ მოწყობილობის სახელი (მაგ., „სარეცხი მანქანა”, „ჭურჭლის სარეცხი მანქანა”).", + "device_type": "რა ტიპის მოწყობილობაა ეს? ეხმარება გამოვლენისა და მარკირების მორგებას.", + "power_sensor": "სენსორის ერთეული, რომელიც ვატებში ასახავს რეალურ დროში ენერგიის მოხმარებას თქვენი ჭკვიანი შტეფსელიდან.", + "min_power": "ამ ზღვარზე (ვატებში) მაღალი მაჩვენებლები მიუთითებს, რომ მოწყობილობა მუშაობს. უმეტეს მოწყობილობებისთვის დაიწყეთ 2W-ით." + } + }, + "first_profile": { + "title": "პირველი პროფილის შექმნა", + "description": "**ციკლი** არის თქვენი მოწყობილობის სრული გაშვება (მაგ., ერთი სარეცხი დატვირთვა). **პროფილი** აჯგუფებს ამ ციკლებს ტიპის მიხედვით (მაგ., „ბამბა”, „სწრაფი რეცხვა”). პროფილის ახლავე შექმნა ეხმარება ინტეგრაციას ხანგრძლივობის დაუყოვნებლივ შეფასებაში.\n\nმოწყობილობის მართვის პანელში შეგიძლიათ ხელით აირჩიოთ ეს პროფილი, თუ ის ავტომატურად ვერ გამოვლინდა.\n\n**დატოვეთ „პროფილის სახელი” ცარიელი ამ ნაბიჯის გამოსატოვებლად.**", + "data": { + "profile_name": "პროფილის სახელი (არჩევითი)", + "manual_duration": "სავარაუდო ხანგრძლივობა (წუთი)" + } + } + }, + "error": { + "cannot_connect": "დაკავშირება ვერ მოხერხდა", + "invalid_auth": "ავთენტიფიკაცია არასწორია", + "unknown": "მოულოდნელი შეცდომა" + }, + "abort": { + "already_configured": "მოწყობილობა უკვე კონფიგურირებულია" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "სასწავლო გამოხმაურებების მიმოხილვა", + "settings": "პარამეტრები", + "notifications": "შეტყობინებები", + "manage_cycles": "ციკლების მართვა", + "manage_profiles": "პროფილების მართვა", + "manage_phase_catalog": "ფაზების კატალოგის მართვა", + "record_cycle": "ციკლის ჩაწერა (სახელმძღვანელო)", + "diagnostics": "დიაგნოსტიკა და მოვლა" + } + }, + "apply_suggestions": { + "title": "შემოთავაზებული მნიშვნელობების კოპირება", + "description": "ეს დააკოპირებს მიმდინარე შემოთავაზებულ მნიშვნელობებს თქვენს შენახულ პარამეტრებში. ეს ერთჯერადი მოქმედებაა და ავტომატური გადაწერა არ ჩაირთვება.\n\nკოპირებისთვის შემოთავაზებული მნიშვნელობები:\n{suggested}", + "data": { + "confirm": "კოპირების მოქმედების დადასტურება" + } + }, + "apply_suggestions_confirm": { + "title": "შემოთავაზებული ცვლილებების მიმოხილვა", + "description": "WashData-მ მოიძებნა **{pending_count}** შემოთავაზებული მნიშვნელობები გამოსაყენებლად.\n\nცვლილებები:\n{changes}\n\n✅ მონიშნეთ ქვემოთ მოცემული ველი და დააწკაპუნეთ **გაგზავნა**, რათა მიმართოთ და შეინახოთ ეს ცვლილებები დაუყოვნებლივ.\n❌ დატოვეთ მონიშნული და დააწკაპუნეთ **გაგზავნა** გასაუქმებლად და უკან დასაბრუნებლად.", + "data": { + "confirm_apply_suggestions": "გამოიყენეთ და შეინახეთ ეს ცვლილებები" + } + }, + "settings": { + "title": "პარამეტრები", + "description": "{deprecation_warning}დაარეგულირეთ გამოვლენის ზღვრები, სწავლა და გაფართოებული ქცევა. ნაგულისხმევი პარამეტრები კარგად მუშაობს უმეტეს კონფიგურაციებში.\n\nამჟამად ხელმისაწვდომი შემოთავაზებული პარამეტრები: {suggestions_count}.\nთუ ეს 0-ზე მეტია, გახსენით გაფართოებული პარამეტრები და გამოიყენეთ შემოთავაზებული მნიშვნელობების გამოყენება.", + "data": { + "apply_suggestions": "შემოთავაზებული მნიშვნელობების გამოყენება", + "device_type": "მოწყობილობის ტიპი", + "power_sensor": "დენის სენსორის ერთეული", + "min_power": "მინიმალური სიმძლავრე (W)", + "off_delay": "ციკლის დასრულების დაყოვნება (წმ)", + "notify_actions": "შეტყობინებების მოქმედებები", + "notify_people": "მიწოდების გადადება, სანამ ეს ადამიანები სახლში არ იქნებიან", + "notify_only_when_home": "შეტყობინებების გადადება, სანამ არჩეული ადამიანი სახლში მოვა", + "notify_fire_events": "ავტომატიზაციის ღონისძიებების გამოშვება", + "notify_start_services": "ციკლის დაწყება - შეტყობინებების მიზნები", + "notify_finish_services": "ციკლის დასრულება - შეტყობინებების მიზნები", + "notify_live_services": "ცოცხალი პროგრესი - შეტყობინებების მიზნები", + "notify_before_end_minutes": "დასრულებამდე შეტყობინება (წუთი დასრულებამდე)", + "notify_title": "შეტყობინების სათაური", + "notify_icon": "შეტყობინების ხატულა", + "notify_start_message": "დაწყების შეტყობინების ფორმატი", + "notify_finish_message": "დასრულების შეტყობინების ფორმატი", + "notify_pre_complete_message": "დასრულებამდე შეტყობინების ფორმატი", + "show_advanced": "გაფართოებული პარამეტრების რედაქტირება (შემდეგი ნაბიჯი)" + }, + "data_description": { + "apply_suggestions": "მონიშნეთ ეს ველი, რათა სასწავლო ალგორითმის შემოთავაზებული მნიშვნელობები ფორმაში გადაიტანოთ. შენახვამდე გადახედეთ.\n\nშემოთავაზებული (სწავლიდან):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "მოწყობილობის ტიპის არჩევა (სარეცხი მანქანა, საშრობი, ჭურჭლის სარეცხი მანქანა, ყავის მანქანა, EV). ეს ტეგი ინახება თითოეულ ციკლთან ერთად უკეთესი ორგანიზებისთვის.", + "power_sensor": "სენსორის ერთეული, რომელიც ვატებში ასახავს რეალურ დროში სიმძლავრეს. ისტორიული მონაცემების დაკარგვის გარეშე შეგიძლიათ ნებისმიერ დროს შეცვალოთ - სასარგებლოა ჭკვიანი შტეფსელის შეცვლისას.", + "min_power": "ამ ზღვარზე (ვატებში) მაღალი მაჩვენებლები მიუთითებს, რომ მოწყობილობა მუშაობს. უმეტეს მოწყობილობებისთვის დაიწყეთ 2W-ით.", + "off_delay": "გლუვი სიმძლავრე ამდენი წამი უნდა დარჩეს გაჩერების ზღვარს ქვემოთ, რომ ციკლი დასრულებულად ჩაითვალოს. ნაგულისხმევი: 120 წმ.", + "notify_actions": "Home Assistant-ის სურვილისამებრ მოქმედებების თანმიმდევრობა შეტყობინებებისთვის (სკრიპტები, notify, TTS და ა.შ.). თუ დაყენებულია, მოქმედებები სარეზერვო სერვისამდე სრულდება.", + "notify_people": "ყოფნის შემოწმებისთვის გამოყენებული ადამიანების ერთეულები. ჩართვისას შეტყობინება მაშინ იგზავნება, როცა მინიმუმ ერთი არჩეული ადამიანი სახლში იქნება.", + "notify_only_when_home": "ჩართვისას შეტყობინებები მაშინ ეგზავნება, როცა მინიმუმ ერთი არჩეული ადამიანი სახლშია.", + "notify_fire_events": "ჩართვისას ციკლის დაწყებისა და დასრულებისთვის Home Assistant-ის ღონისძიებები (ha_washdata_cycle_started და ha_washdata_cycle_ended) გაეშვება ავტომატიზაციის გასაშვებად.", + "notify_start_services": "ერთი ან მეტი სერვისი აფრთხილებს ციკლის დაწყების შესახებ. დატოვეთ ცარიელი, რათა გამოტოვოთ დაწყების შეტყობინებები.", + "notify_finish_services": "ერთმა ან მეტმა აცნობოს სერვისებს, რათა გააფრთხილონ ციკლის დასრულების ან დასრულების შესახებ. დატოვეთ ცარიელი, რათა გამოტოვოთ დასრულების შეტყობინებები.", + "notify_live_services": "ერთი ან მეტი სერვისი ატყობინებს პროგრესის განახლებებს, სანამ ციკლი მუშაობს (მხოლოდ მობილური კომპანიონი აპი). დატოვეთ ცარიელი, რომ გამოტოვოთ პირდაპირი განახლებები.", + "notify_before_end_minutes": "ციკლის სავარაუდო დასრულებამდე რამდენი წუთით ადრე გაეშვას შეტყობინება. გამორთვისთვის დააყენეთ 0. ნაგულისხმევი: 0.", + "notify_title": "შეტყობინებებისთვის მორგებული სათაური. მხარს უჭერს {device} ადგილმფლობელს.", + "notify_icon": "შეტყობინებებისთვის გამოსაყენებელი ხატულა (მაგ. mdi:washing-machine). ხატულის გარეშე გასაგზავნად დატოვეთ ცარიელი.", + "notify_start_message": "ციკლის დაწყებისას გაგზავნილი შეტყობინება. მხარს უჭერს {device} ადგილმფლობელს.", + "notify_finish_message": "ციკლის დასრულებისას გაგზავნილი შეტყობინება. მხარს უჭერს {device}, {duration}, {program}, {energy_kwh}, {cost} ადგილმფლობელებს.", + "notify_pre_complete_message": "განმეორებადი ცოცხალი პროგრესის ტექსტი განახლდება ციკლის გაშვებისას. მხარს უჭერს {device}, {minutes}, {program} ჩანაცვლების ველებს." + } + }, + "notifications": { + "title": "შეტყობინებები", + "description": "შეტყობინებების არხების, შაბლონებისა და სურვილისამებრ პირდაპირი მობილური პროგრესის განახლებების კონფიგურაცია.", + "data": { + "notify_actions": "შეტყობინებების მოქმედებები", + "notify_people": "მიწოდების გადადება, სანამ ეს ადამიანები სახლში არ იქნებიან", + "notify_only_when_home": "შეტყობინებების გადადება, სანამ არჩეული ადამიანი სახლში მოვა", + "notify_fire_events": "ავტომატიზაციის ღონისძიებების გამოშვება", + "notify_start_services": "ციკლის დაწყება - შეტყობინებების მიზნები", + "notify_finish_services": "ციკლის დასრულება - შეტყობინებების მიზნები", + "notify_live_services": "ცოცხალი პროგრესი - შეტყობინებების მიზნები", + "notify_before_end_minutes": "დასრულებამდე შეტყობინება (წუთი დასრულებამდე)", + "notify_live_interval_seconds": "პირდაპირი განახლების ინტერვალი (წამები)", + "notify_live_overrun_percent": "პირდაპირი განახლების გადაჭარბების ზღვარი (%)", + "notify_live_chronometer": "ქრონომეტრის უკუმთვლელი ტაიმერი", + "notify_title": "შეტყობინების სათაური", + "notify_icon": "შეტყობინების ხატულა", + "notify_start_message": "დაწყების შეტყობინების ფორმატი", + "notify_finish_message": "დასრულების შეტყობინების ფორმატი", + "notify_pre_complete_message": "დასრულებამდე შეტყობინების ფორმატი", + "energy_price_entity": "ენერგიის ფასი - პირობა (არასავალდებულო)", + "energy_price_static": "ენერგიის ფასი - სტატიკური მნიშვნელობა (სურვილისამებრ)", + "go_back": "დაბრუნება შენახვის გარეშე", + "notify_reminder_message": "შეხსენების შეტყობინების ფორმატი", + "notify_timeout_seconds": "ავტომატური გაუქმება შემდეგ (წამი)", + "notify_channel": "შეტყობინებების არხი (სტატუსები/პირდაპირი/შეხსენება)", + "notify_finish_channel": "დასრულებული შეტყობინებების არხი" + }, + "data_description": { + "go_back": "მონიშნეთ ეს და დააწკაპუნეთ „გაგზავნა“, რათა ზემოთ არსებული ცვლილებები უარყოთ და მთავარ მენიუში დაბრუნდეთ.", + "notify_actions": "Home Assistant-ის სურვილისამებრ მოქმედებების თანმიმდევრობა შეტყობინებებისთვის (სკრიპტები, notify, TTS და ა.შ.). თუ დაყენებულია, მოქმედებები სარეზერვო სერვისამდე სრულდება.", + "notify_people": "ყოფნის შემოწმებისთვის გამოყენებული ადამიანების ერთეულები. ჩართვისას შეტყობინება მაშინ იგზავნება, როცა მინიმუმ ერთი არჩეული ადამიანი სახლში იქნება.", + "notify_only_when_home": "ჩართვისას შეტყობინებები მაშინ ეგზავნება, როცა მინიმუმ ერთი არჩეული ადამიანი სახლშია.", + "notify_fire_events": "ჩართვისას ციკლის დაწყებისა და დასრულებისთვის Home Assistant-ის ღონისძიებები (ha_washdata_cycle_started და ha_washdata_cycle_ended) გაეშვება ავტომატიზაციის გასაშვებად.", + "notify_start_services": "ერთი ან მეტი სერვისი აცნობებს ციკლის დაწყების შესახებ (მაგ. notify.mobile_app_my_phone). დატოვეთ ცარიელი, რათა გამოტოვოთ დაწყების შეტყობინებები.", + "notify_finish_services": "ერთმა ან მეტმა აცნობოს სერვისებს, რათა გააფრთხილონ ციკლის დასრულების ან დასრულების შესახებ. დატოვეთ ცარიელი, რათა გამოტოვოთ დასრულების შეტყობინებები.", + "notify_live_services": "ერთი ან მეტი სერვისი ატყობინებს პროგრესის განახლებებს, სანამ ციკლი მუშაობს (მხოლოდ მობილური კომპანიონი აპი). დატოვეთ ცარიელი, რომ გამოტოვოთ პირდაპირი განახლებები.", + "notify_before_end_minutes": "ციკლის სავარაუდო დასრულებამდე რამდენი წუთით ადრე გაეშვას შეტყობინება. გამორთვისთვის დააყენეთ 0. ნაგულისხმევი: 0.", + "notify_live_interval_seconds": "რამდენად ხშირად იგზავნება პროგრესის განახლებები მუშაობისას. დაბალი მნიშვნელობები უფრო სწრაფად პასუხობს, მაგრამ უფრო მეტ შეტყობინებას მოიხმარს. მინიმუმი 30 წამია - 30 წამზე ნაკლები მნიშვნელობები ავტომატურად 30 წამამდე მრგვალდება.", + "notify_live_overrun_percent": "უსაფრთხოების ზღვარი სავარაუდო განახლების რაოდენობაზე გრძელი/გადაჭარბებული ციკლებისთვის (მაგ. 20% იძლევა 1.2-ჯერ სავარაუდო განახლების საშუალებას).", + "notify_live_chronometer": "როდესაც ჩართულია, თითოეული პირდაპირი განახლება მოიცავს ქრონომეტრის ათვლას სავარაუდო დასრულების დრომდე. შეტყობინებების ტაიმერი ავტომატურად იკლებს მოწყობილობაზე განახლებებს შორის (მხოლოდ Android-ის კომპანიონი აპი).", + "notify_title": "შეტყობინებებისთვის მორგებული სათაური. მხარს უჭერს {device} ადგილმფლობელს.", + "notify_icon": "შეტყობინებებისთვის გამოსაყენებელი ხატულა (მაგ. mdi:washing-machine). ხატულის გარეშე გასაგზავნად დატოვეთ ცარიელი.", + "notify_start_message": "ციკლის დაწყებისას გაგზავნილი შეტყობინება. მხარს უჭერს {device} ადგილმფლობელს.", + "notify_finish_message": "ციკლის დასრულებისას გაგზავნილი შეტყობინება. მხარს უჭერს {device}, {duration}, {program}, {energy_kwh}, {cost} ადგილმფლობელებს.", + "energy_price_entity": "აირჩიეთ რიცხვითი ერთეული, რომელიც ფლობს ელექტროენერგიის მიმდინარე ფასს (მაგ. sensor.electricity_price, input_number.energy_rate). როდესაც დაყენებულია, ჩართავს {cost} ჩანაცვლების ადგილს. უპირატესობა ენიჭება სტატიკური მნიშვნელობას, თუ ორივე კონფიგურირებულია.", + "energy_price_static": "ელექტროენერგიის ფიქსირებული ფასი კვტ/სთ-ზე. როდესაც დაყენებულია, ჩართავს {cost} ჩანაცვლების ადგილს. იგნორირებულია, თუ ერთეული კონფიგურირებულია ზემოთ. დაამატეთ თქვენი ვალუტის სიმბოლო შეტყობინების შაბლონში, მაგ. {cost} €.", + "notify_reminder_message": "ერთჯერადი შეხსენების ტექსტმა გაგზავნა კონფიგურირებული წუთების რაოდენობა სავარაუდო დასრულებამდე. განსხვავდება ზემოთ მოყვანილი განმეორებადი პირდაპირი განახლებისგან. მხარს უჭერს {device}, {minutes}, {program} ჩანაცვლების ველებს.", + "notify_timeout_seconds": "შეტყობინებების ავტომატური გაუქმება ამ მრავალი წამის შემდეგ (გადაგზავნა, როგორც კომპანიონი აპი „ტაიმი“). დააყენეთ 0-ზე, რათა შეინახოთ ისინი გაუქმებამდე. ნაგულისხმევი: 0.", + "notify_channel": "Android თანმხლები აპის შეტყობინებების არხი სტატუსის, პირდაპირი პროგრესისა და შეხსენების შეტყობინებებისთვის. არხის ხმა და მნიშვნელობა კონფიგურირებულია კომპანიონ აპში არხის სახელის პირველად გამოყენებისას - WashData ადგენს მხოლოდ არხის სახელს. დატოვეთ ცარიელი აპის ნაგულისხმევად.", + "notify_finish_channel": "გამოყავით Android არხი დასრულებული გაფრთხილებისთვის (ასევე გამოიყენება შეხსენებისა და სამრეცხაოების მოლოდინში), რათა მას ჰქონდეს საკუთარი ხმა. დატოვეთ ცარიელი ზემოთ არხის ხელახლა გამოსაყენებლად.", + "notify_pre_complete_message": "განმეორებადი ცოცხალი პროგრესის ტექსტი განახლდება ციკლის გაშვებისას. მხარს უჭერს {device}, {minutes}, {program} ჩანაცვლების ველებს." + } + }, + "advanced_settings": { + "title": "გაფართოებული პარამეტრები", + "description": "მოარგეთ აღმოჩენის ზღვრები, სწავლა და გაფართოებული ქცევა. ნაგულისხმევი პარამეტრები უმეტეს კონფიგურაციებში კარგად მუშაობს.", + "sections": { + "suggestions_section": { + "name": "რეკომენდებული პარამეტრები", + "description": "ხელმისაწვდომია სწავლის შედეგად მიღებული {suggestions_count} რეკომენდაცია. შენახვამდე შემოთავაზებული ცვლილებების გადასახედად მონიშნეთ „შემოთავაზებული მნიშვნელობების გამოყენება“.", + "data": { + "apply_suggestions": "შემოთავაზებული მნიშვნელობების გამოყენება" + }, + "data_description": { + "apply_suggestions": "მონიშნეთ ეს ველი სასწავლო ალგორითმის შემოთავაზებული მნიშვნელობების გადახედვის ეტაპის გასახსნელად. არაფერი ავტომატურად არ ინახება.\n\nშემოთავაზებული (სწავლიდან):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "დეტექცია და სიმძლავრის ზღვრები", + "description": "როდის ითვლება მოწყობილობა ჩართულად ან გამორთულად, ენერგიის კარიბჭეები და მდგომარეობების გადართვის დრო.", + "data": { + "start_duration_threshold": "დაწყების debounce ხანგრძლივობა (წმ)", + "start_energy_threshold": "დაწყების ენერგიის კარიბჭე (Wh)", + "completion_min_seconds": "დასრულების მინიმალური ხანგრძლივობა (წამები)", + "start_threshold_w": "დაწყების ზღვარი (W)", + "stop_threshold_w": "გაჩერების ზღვარი (W)", + "end_energy_threshold": "დასასრულის ენერგიის კარიბჭე (Wh)", + "running_dead_zone": "მუშაობის მკვდარი ზონა (წამი)", + "end_repeat_count": "დასასრულის განმეორების რაოდენობა", + "min_off_gap": "ციკლებს შორის მინიმალური ინტერვალი (წმ)", + "sampling_interval": "შერჩევის ინტერვალი (წმ)" + }, + "data_description": { + "start_duration_threshold": "ამ ხანგრძლივობაზე (წამებში) მოკლე ენერგიის მწვერვალების იგნორირება. ჩართვის მწვერვალებს (მაგ. 60W 2 წამით) ფილტრავს რეალური ციკლის დაწყებამდე. ნაგულისხმევი: 5 წმ.", + "start_energy_threshold": "ციკლი დასადასტურებლად START ფაზაში მინიმუმ ამდენი ენერგია (Wh) უნდა დაგროვდეს. ნაგულისხმევი: 0.005 Wh.", + "completion_min_seconds": "ამაზე მოკლე ციკლები „შეწყვეტილად” მოინიშნება, თუნდაც ბუნებრივად დასრულდეს. ნაგულისხმევი: 600 წმ.", + "start_threshold_w": "ციკლის დასაწყებად სიმძლავრე ამ ზღვარს უნდა გადააჭარბოს. მოლოდინის სიმძლავრეზე მაღლა დააყენეთ. ნაგულისხმევი: min_power + 1 W.", + "stop_threshold_w": "ციკლის დასასრულებლად სიმძლავრე ამ ზღვარს ქვემოთ უნდა ჩავარდეს. ნულზე ოდნავ მაღლა დააყენეთ, რომ ხმაური ყალბ დასასრულებს არ გამოიწვევდეს. ნაგულისხმევი: 0.6 * min_power.", + "end_energy_threshold": "ციკლი მხოლოდ მაშინ სრულდება, თუ ბოლო Off-Delay ფანჯარაში ენერგია ამ მნიშვნელობაზე (Wh) ნაკლებია. სიმძლავრის ნულთან ახლოს მერყეობისას ყალბ დასასრულებს ხელს უშლის. ნაგულისხმევი: 0.05 Wh.", + "running_dead_zone": "ციკლის დაწყების შემდეგ, საწყისი ამოქმედების ფაზაში ყალბი დასასრულების გამოვლენის თავიდან ასაცილებლად ამდენი წამის განმავლობაში სიმძლავრის ვარდნების იგნორირება. გამორთვისთვის დააყენეთ 0. ნაგულისხმევი: 0 წმ.", + "end_repeat_count": "ციკლის დასრულებამდე off_delay-ისთვის ზღვარს ქვემოთ სიმძლავრის პირობა ზედიზედ რამდენჯერ უნდა დაკმაყოფილდეს. მაღალი მნიშვნელობები პაუზების დროს ყალბ დასასრულებს ხელს უშლის. ნაგულისხმევი: 1.", + "min_off_gap": "თუ ციკლი წინა დასრულებიდან ამდენ წამში იწყება, ისინი ერთ ციკლად გაერთიანდება.", + "sampling_interval": "შერჩევის მინიმალური ინტერვალი წამებში. ამ მაჩვენებელზე უფრო სწრაფი განახლებები იგნორირებული იქნება. ნაგულისხმევი: 30 წმ (2 წამი სარეცხი მანქანებისთვის, სარეცხი-საშრობი მანქანებისთვის და ჭურჭლის სარეცხი მანქანებისთვის)." + } + }, + "matching_section": { + "name": "პროფილების შედარება და სწავლა", + "description": "რამდენად აგრესიულად ადარებს WashData მიმდინარე ციკლებს ნასწავლ პროფილებს და როდის უნდა მოითხოვოს გამოხმაურება.", + "data": { + "profile_match_min_duration_ratio": "პროფილის შედარების მინ. ხანგრძლივობის კოეფიციენტი (0.1-1.0)", + "profile_match_interval": "პროფილის შედარების ინტერვალი (წამები)", + "profile_match_threshold": "პროფილის შედარების ზღვარი", + "profile_unmatch_threshold": "პროფილის შეუთავსებლობის ზღვარი", + "auto_label_confidence": "ავტო-ეტიკეტის სანდოობა (0-1)", + "learning_confidence": "გამოხმაურების მოთხოვნის სანდოობა (0-1)", + "suppress_feedback_notifications": "ჩაახშო უკუკავშირის შეტყობინებები", + "duration_tolerance": "ხანგრძლივობის ტოლერანტობა (0-0.5)", + "smoothing_window": "გლუვი ფანჯარა (ნიმუშები)", + "profile_duration_tolerance": "პროფილის შედარების ხანგრძლივობის ტოლერანტობა (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "პროფილის შედარებისთვის მინიმალური ხანგრძლივობის კოეფიციენტი. გაშვებული ციკლი პროფილის ხანგრძლივობის მინიმუმ ამ ნაწილს უნდა შეადგენდეს. დაბალი = უფრო ადრე შედარება. ნაგულისხმევი: 0.50 (50%).", + "profile_match_interval": "ციკლის განმავლობაში პროფილის შედარების სიხშირე (წამებში). დაბალი მნიშვნელობები უფრო სწრაფ გამოვლენას უზრუნველყოფს, მაგრამ მეტ CPU-ს იყენებს. ნაგულისხმევი: 300 წმ (5 წუთი).", + "profile_match_threshold": "პროფილის შედარებად მიჩნევისთვის საჭირო მინიმალური DTW მსგავსების ქულა (0.0–1.0). მაღალი = მკაცრი შედარება, ნაკლები ყალბი დადებითი. ნაგულისხმევი: 0.4.", + "profile_unmatch_threshold": "DTW მსგავსების ქულა (0.0–1.0), რომლის ქვემოთ ადრე შედარებული პროფილი უარყოფილია. ციმციმის თავიდან ასაცილებლად შედარების ზღვარზე დაბლა უნდა იყოს. ნაგულისხმევი: 0.35.", + "auto_label_confidence": "ამ ან მეტი სანდოობით ციკლების ავტომატური ეტიკეტირება დასრულებისას (0.0–1.0).", + "learning_confidence": "ღონისძიებით მომხმარებლის დადასტურების მოთხოვნისთვის მინიმალური სანდოობა (0.0–1.0).", + "suppress_feedback_notifications": "როდესაც ჩართულია, WashData კვლავ თვალყურს ადევნებს და ითხოვს გამოხმაურებას შინაგანად, მაგრამ არ აჩვენებს მუდმივ შეტყობინებებს, რომლებიც მოგთხოვთ ციკლების დადასტურებას. სასარგებლოა, როდესაც ციკლები ყოველთვის სწორად არის გამოვლენილი და მოთხოვნილებები ყურადღების გაფანტვას თვლით.", + "duration_tolerance": "სირბილის დროს დარჩენილი დროის შეფასების ტოლერანტობა (0.0-0.5 შეესაბამება 0-50%).", + "smoothing_window": "მოძრავი საშუალო გლუვობისთვის გამოყენებული ბოლო სიმძლავრის მაჩვენებლების რაოდენობა.", + "profile_duration_tolerance": "პროფილის შედარებით სრული ხანგრძლივობის განაწილებისთვის გამოყენებული ტოლერანტობა (0.0–0.5). ნაგულისხმევი ქცევა: ±25%." + } + }, + "timing_section": { + "name": "დრო, მოვლა და გამართვა", + "description": "Watchdog, პროგრესის განულება, ავტომატური მოვლა და გამართვის ერთეულების გამოჩენა.", + "data": { + "watchdog_interval": "watchdog-ის ინტერვალი (წამები)", + "no_update_active_timeout": "განახლების გარეშე ვადა (წმ)", + "progress_reset_delay": "პროგრესის გადატვირთვის დაყოვნება (წამი)", + "auto_maintenance": "ავტო-მოვლის ჩართვა", + "expose_debug_entities": "გამართვის ერთეულების გამოვლენა", + "save_debug_traces": "გამართვის კვალების შენახვა" + }, + "data_description": { + "watchdog_interval": "მუშაობისას watchdog-ის შემოწმებებს შორის წამები. ნაგულისხმევი: 5 წმ. გაფრთხილება: ეს სენსორის განახლების ინტერვალზე მაღალი უნდა იყოს ყალბი გაჩერებების თავიდან ასაცილებლად (მაგ., თუ სენსორი ყოველ 60 წამში განახლდება, გამოიყენეთ 65+ წმ).", + "no_update_active_timeout": "გამოქვეყნებაზე-ცვლილების სენსორებისთვის: ACTIVE ციკლი ძალით მხოლოდ მაშინ დასრულდება, თუ ამდენი წამის განმავლობაში განახლებები არ მოვიდა. დაბალი ენერგიის დასრულება კვლავ off_delay-ს იყენებს.", + "progress_reset_delay": "ციკლის დასრულების შემდეგ (100%) ამდენი წამის უმოქმედობის შემდეგ პროგრესი 0%-მდე დაბრუნდება. ნაგულისხმევი: 300 წმ.", + "auto_maintenance": "ავტო-მოვლის ჩართვა (ნიმუშების შეკეთება, ფრაგმენტების შერწყმა).", + "expose_debug_entities": "გამართვისთვის გაფართოებული სენსორების ჩვენება (სანდოობა, ფაზა, ორაზროვნება).", + "save_debug_traces": "ისტორიაში დეტალური რანჟირებისა და სიმძლავრის კვალის მონაცემების შენახვა (მეხსიერების გამოყენებას ზრდის)." + } + }, + "anti_wrinkle_section": { + "name": "ნაოჭების საწინააღმდეგო ფარი (საშრობები)", + "description": "დააიგნორეთ ციკლის შემდგომი ბარაბნის ბრუნვები, რათა თავიდან აიცილოთ ყალბი ციკლები. მხოლოდ საშრობი / სარეცხი-საშრობი.", + "data": { + "anti_wrinkle_enabled": "ნაოჭების საწინააღმდეგო ფარი (მხოლოდ საშრობი/სარეცხი-საშრობი)", + "anti_wrinkle_max_power": "ნაოჭების საწინააღმდეგო მაქს. სიმძლავრე (W) (მხოლოდ საშრობი/სარეცხი-საშრობი)", + "anti_wrinkle_max_duration": "ნაოჭების საწინააღმდეგო მაქს. ხანგრძლივობა (წმ) (მხოლოდ საშრობი/სარეცხი-საშრობი)", + "anti_wrinkle_exit_power": "ნაოჭების საწინააღმდეგო გასვლის სიმძლავრე (W) (მხოლოდ საშრობი/სარეცხი-საშრობი)" + }, + "data_description": { + "anti_wrinkle_enabled": "ციკლის დასრულების შემდეგ ყალბი ციკლების თავიდან ასაცილებლად დაბარაბნის ხანმოკლე ბრუნვების იგნორირება (მხოლოდ საშრობი/სარეცხი-საშრობი).", + "anti_wrinkle_max_power": "ამ სიმძლავრეზე ან ქვემოთ ავტომატები ახალი ციკლების ნაცვლად ნაოჭების საწინააღმდეგო ბრუნვებად ითვლება. მხოლოდ ნაოჭების საწინააღმდეგო ფარის ჩართვისას მოქმედებს.", + "anti_wrinkle_max_duration": "ნაოჭების საწინааღმდეგო ბრუნვად მიჩნეული ავტომატის მაქსიმალური სიგრძე. მხოლოდ ნაოჭების საწინააღმდეგო ფარის ჩართვისას მოქმედებს.", + "anti_wrinkle_exit_power": "თუ სიმძლავრე ამ დონეს ქვემოთ ჩამოვარდება, ნაოჭების საწინააღმდეგო რეჟიმიდან დაუყოვნებლივ გამოვა (ნამდვილი გამორთვა). მხოლოდ ნაოჭების საწინააღმდეგო ფარის ჩართვისას მოქმედებს." + } + }, + "delay_start_section": { + "name": "დაგვიანებული დაწყების აღმოჩენა", + "description": "მდგრადი დაბალი სიმძლავრის მოლოდინის რეჟიმი მიიჩნიეთ მდგომარეობად „ელოდება დაწყებას“, სანამ ციკლი რეალურად არ დაიწყება.", + "data": { + "delay_start_detect_enabled": "ჩართეთ დაგვიანებული დაწყების ამოცნობა", + "delay_confirm_seconds": "დაგვიანებული დაწყება: მოლოდინის დადასტურების დრო (წმ)", + "delay_timeout_hours": "დაგვიანებული დაწყება: მაქსიმალური მოლოდინის დრო (სთ)" + }, + "data_description": { + "delay_start_detect_enabled": "როდესაც ჩართულია, მცირე სიმძლავრის გადინების მწვერვალი, რომელსაც მოჰყვება მდგრადი ლოდინის ენერგია, აღმოჩენილია, როგორც დაგვიანებული დაწყება. მდგომარეობის სენსორი აჩვენებს „დაწყების მოლოდინში“ შეფერხებისას. პროგრამის დრო ან პროგრესი არ არის თვალყური ადევნეთ ციკლის რეალურად დაწყებამდე. მოითხოვს start_threshold_w დაყენებას გადინების მწვერვალზე ზემოთ.", + "delay_confirm_seconds": "სიმძლავრე ამდენი წამით უნდა დარჩეს გაჩერების ზღვარს (W) და დაწყების ზღვარს (W) შორის, სანამ WashData შევა მდგომარეობაში „ელოდება დაწყებას“. ფილტრავს მენიუში ნავიგაციისას წარმოქმნილ მოკლე პიკებს. ნაგულისხმევი: 60 წმ.", + "delay_timeout_hours": "უსაფრთხოების ვადის ამოწურვა: თუ მანქანა ამდენი საათის შემდეგ კვლავ „დაწყების მოლოდინშია“, WashData გადადის გამორთვაზე. ნაგულისხმევი: 8 სთ." + } + }, + "external_triggers_section": { + "name": "გარე ტრიგერები, კარი და პაუზა", + "description": "არჩევითი გარე დასრულების ტრიგერის სენსორი, კარის სენსორი პაუზის/გასუფთავების ამოსაცნობად და პაუზისას კვების გამთიშავი გადამრთველი.", + "data": { + "external_end_trigger_enabled": "გარე ციკლის დასრულების ტრიგერის ჩართვა", + "external_end_trigger": "გარე ციკლის დასრულების სენსორი", + "external_end_trigger_inverted": "ტრიგერის ლოგიკის შებრუნება (ტრიგერი გამორთვაზე)", + "door_sensor_entity": "კარის სენსორი", + "pause_cuts_power": "გამორთეთ კვების ბლოკი პაუზის დროს", + "switch_entity": "ერთეულის გადართვა (ენერგიის გათიშვის პაუზისთვის)", + "notify_unload_delay_minutes": "სამრეცხაო ელოდება შეტყობინების დაგვიანება (წთ)" + }, + "data_description": { + "external_end_trigger_enabled": "ციკლის დასრულების გასააქტიურებლად გარე ორობითი სენსორის მონიტორინგის ჩართვა.", + "external_end_trigger": "ორობითი სენსორის ერთეულის არჩევა. ამ სენსორის გააქტიურებისას მიმდინარე ციკლი „დასრულებული” სტატუსით დასრულდება.", + "external_end_trigger_inverted": "ნაგულისხმევად ტრიგერი სენსორის ჩართვისას ამოქმედდება. მონიშნეთ ეს ველი სენსორის გამორთვისას გასააქტიურებლად.", + "door_sensor_entity": "არჩევითი ორობითი სენსორი მანქანის კარისთვის (ჩართული = ღია, გამორთული = დახურული). როდესაც კარი იხსნება აქტიური ციკლის დროს, WashData დაადასტურებს, რომ ციკლი განზრახ შეჩერებულია. ციკლის დასრულების შემდეგ, თუ კარი მაინც დაკეტილია, მდგომარეობა იცვლება „სუფთა“ კარის გახსნამდე. შენიშვნა: კარის დახურვა ავტომატურად არ განაახლებს შეჩერებულ ციკლს - რეზიუმე უნდა ამოქმედდეს ხელით განახლების ციკლი ღილაკის ან სერვისის მეშვეობით.", + "pause_cuts_power": "პაუზის ციკლის ღილაკის ან სერვისის საშუალებით შეჩერებისას, ასევე გამორთეთ კონფიგურირებული გადამრთველი ერთეული. ძალაში შედის მხოლოდ იმ შემთხვევაში, თუ გადამრთველი ერთეული კონფიგურირებულია ამ მოწყობილობისთვის.", + "switch_entity": "გადამრთველი ერთეულის გადართვა შეჩერების ან განახლებისას. საჭიროა, როცა ჩართულია \"გამორთეთ კვების ბლოკი პაუზის დროს\".", + "notify_unload_delay_minutes": "გაგზავნეთ შეტყობინება დასრულების შეტყობინებების არხით ციკლის დასრულების შემდეგ და კარი არ გაიღო ამდენი წუთის განმავლობაში (საჭიროებს კარის სენსორს). გამორთვისთვის დააყენეთ 0. ნაგულისხმევი: 60 წთ." + } + }, + "pump_section": { + "name": "ტუმბოს მონიტორი", + "description": "მხოლოდ ტუმბოს ტიპის მოწყობილობებისთვის. თუ ტუმბოს ციკლი ამ ხანგრძლივობას გადააჭარბებს, გაიგზავნება მოვლენა.", + "data": { + "pump_stuck_duration": "ტუმბოს გაჭედვის სიგნალიზაციის ბარიერი (წამებში) (მხოლოდ ტუმბო)" + }, + "data_description": { + "pump_stuck_duration": "თუ ამდენი წამის შემდეგ ტუმბოს ციკლი კვლავ მუშაობს, ha_washdata_pump_stuck ღონისძიება ერთხელ გააქტიურდება. გამოიყენეთ ეს გაჭედილი ძრავის გამოსავლენად (ჩვეულებრივი წყალსადენის ტუმბოს მუშაობა 60 წამზე ნაკლებია). ნაგულისხმევი: 1800 წმ (30 წთ). მხოლოდ ტუმბოს მოწყობილობის ტიპი." + } + }, + "device_link_section": { + "name": "მოწყობილობის ბმული", + "description": "სურვილისამებრ, დააკავშირეთ ეს WashData მოწყობილობა არსებულ Home Assistant მოწყობილობასთან (მაგალითად, სმარტ შტეფსელთან ან თავად მოწყობილობასთან). WashData მოწყობილობა შემდეგ გამოჩნდება, როგორც \"დაკავშირებულია ამ მოწყობილობის მეშვეობით\". დატოვეთ ცარიელი, რათა WashData დამოუკიდებელ მოწყობილობად შეინახოთ.", + "data": { + "linked_device": "დაკავშირებული მოწყობილობა" + }, + "data_description": { + "linked_device": "აირჩიეთ მოწყობილობა WashData-სთან დასაკავშირებლად. ეს ამატებს მითითებას „დაკავშირებულია მეშვეობით“ მოწყობილობის რეესტრში; WashData ინახავს საკუთარი მოწყობილობის ბარათს და ერთეულებს. გაასუფთავეთ არჩევანი ბმულის წასაშლელად." + } + } + } + }, + "diagnostics": { + "title": "დიაგნოსტიკა და მოვლა", + "description": "შეასრულეთ სამოვლეო მოქმედებები, როგორიცაა ფრაგმენტული ციკლების გაერთიანება ან შენახული მონაცემების მიგრაცია.\n\n**შენახვის გამოყენება**\n\n- ფაილის ზომა: {file_size_kb} KB\n- ციკლები: {cycle_count}\n- პროფილები: {profile_count}\n- გამართვის კვალები: {debug_count}", + "menu_options": { + "reprocess_history": "მოვლა: მონაცემების ხელახალი დამუშავება და ოპტიმიზაცია", + "clear_debug_data": "გამართვის მონაცემების გასუფთავება (სივრცის გათავისუფლება)", + "wipe_history": "ამ მოწყობილობის ყველა მონაცემის წაშლა (შეუქცევადი)", + "export_import": "JSON-ის ექსპორტი/იმპორტი პარამეტრებით (კოპირება/ჩასმა)", + "menu_back": "← უკან" + } + }, + "clear_debug_data": { + "title": "გამართვის მონაცემების გასუფთავება", + "description": "დარწმუნებული ხართ, რომ გსურთ ყველა შენახული გამართვის კვალის წაშლა? ეს სივრცეს გაათავისუფლებს, მაგრამ წარსული ციკლების დეტალური რანჟირების ინფორმაციას წაშლის." + }, + "manage_cycles": { + "title": "ციკლების მართვა", + "description": "ბოლო ციკლები:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "ძველი ციკლების ავტო-ეტიკეტირება", + "select_cycle_to_label": "კონკრეტული ციკლის ეტიკეტირება", + "select_cycle_to_delete": "ციკლის წაშლა", + "interactive_editor": "შერწყმა/გაყოფის ინტერაქტიული რედაქტორი", + "trim_cycle_select": "ციკლის მონაცემების ამოჭრა", + "menu_back": "← უკან" + } + }, + "manage_cycles_empty": { + "title": "ციკლების მართვა", + "description": "ჯერ არ არის ჩაწერილი ციკლები.", + "menu_options": { + "auto_label_cycles": "ძველი ციკლების ავტო-ეტიკეტირება", + "menu_back": "← უკან" + } + }, + "manage_profiles": { + "title": "პროფილების მართვა", + "description": "პროფილის შეჯამება:\n{profile_summary}", + "menu_options": { + "create_profile": "ახალი პროფილის შექმნა", + "edit_profile": "პროფილის რედაქტირება/გადარქმევა", + "delete_profile_select": "პროფილის წაშლა", + "profile_stats": "პროფილის სტატისტიკა", + "cleanup_profile": "ისტორიის გასუფთავება - გრაფიკი და წაშლა", + "assign_profile_phases_select": "ფაზის დიაპაზონების მინიჭება", + "menu_back": "← უკან" + } + }, + "manage_profiles_empty": { + "title": "პროფილების მართვა", + "description": "ჯერ არ არის შექმნილი პროფილები.", + "menu_options": { + "create_profile": "ახალი პროფილის შექმნა", + "menu_back": "← უკან" + } + }, + "manage_phase_catalog": { + "title": "ფაზების კატალოგის მართვა", + "description": "ფაზების კატალოგი (ამ მოწყობილობის ნაგულისხმევი + მორგებული ფაზები):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "ახალი ფაზის შექმნა", + "phase_catalog_edit_select": "ფაზის რედაქტირება", + "phase_catalog_delete": "ფაზის წაშლა", + "menu_back": "← უკან" + } + }, + "phase_catalog_create": { + "title": "მორგებული ფაზის შექმნა", + "description": "შექმენით მორგებული ფაზის სახელი და აღწერა და აირჩიეთ, რომელ მოწყობილობის ტიპს ეხება.", + "data": { + "target_device_type": "სამიზნე მოწყობილობის ტიპი", + "phase_name": "ფაზის სახელი", + "phase_description": "ფაზის აღწერა" + } + }, + "phase_catalog_edit_select": { + "title": "მორგებული ფაზის რედაქტირება", + "description": "აირჩიეთ მორგებული ფაზა სარედაქტირებლად.", + "data": { + "phase_name": "მორგებული ფაზა" + } + }, + "phase_catalog_edit": { + "title": "მორგებული ფაზის რედაქტირება", + "description": "ფაზის რედაქტირება: {phase_name}", + "data": { + "phase_name": "ფაზის სახელი", + "phase_description": "ფაზის აღწერა" + } + }, + "phase_catalog_delete": { + "title": "მორგებული ფაზის წაშლა", + "description": "აირჩიეთ წასაშლელი მორგებული ფაზა. ამ ფაზის მქონე ნებისმიერი მინიჭებული დიაპაზონი წაიშლება.", + "data": { + "phase_name": "მორგებული ფაზა" + } + }, + "assign_profile_phases": { + "title": "ფაზის დიაპაზონების მინიჭება", + "description": "ფაზის გადახედვა პროფილისთვის: **{profile_name}**\n\n{timeline_svg}\n\n**მიმდინარე დიაპაზონები:**\n{current_ranges}\n\nდიაპაზონების დასამატებლად, სარედაქტირებლად, წასაშლელად ან შესანახად გამოიყენეთ ქვემოთ მოცემული მოქმედებები.", + "menu_options": { + "assign_profile_phases_add": "ფაზის დიაპაზონის დამატება", + "assign_profile_phases_edit_select": "ფაზის დიაპაზონის რედაქტირება", + "assign_profile_phases_delete": "ფაზის დიაპაზონის წაშლა", + "phase_ranges_clear": "ყველა დიაპაზონის გასუფთავება", + "assign_profile_phases_auto_detect": "ფაზების ავტო-გამოვლენა", + "phase_ranges_save": "შენახვა და დაბრუნება", + "menu_back": "← უკან" + } + }, + "assign_profile_phases_add": { + "title": "ფაზის დიაპაზონის დამატება", + "description": "მიმდინარე პროექტს დაამატეთ ერთი ფაზის დიაპაზონი.", + "data": { + "phase_name": "ფაზა", + "start_min": "საწყისი წუთი", + "end_min": "საბოლოო წუთი" + } + }, + "assign_profile_phases_edit_select": { + "title": "ფაზის დიაპაზონის რედაქტირება", + "description": "აირჩიეთ სარედაქტირებელი დიაპაზონი.", + "data": { + "range_index": "ფაზის დიაპაზონი" + } + }, + "assign_profile_phases_edit": { + "title": "ფაზის დიაპაზონის რედაქტირება", + "description": "განაახლეთ არჩეული ფაზის დიაპაზონი.", + "data": { + "phase_name": "ფაზა", + "start_min": "საწყისი წუთი", + "end_min": "საბოლოო წუთი" + } + }, + "assign_profile_phases_delete": { + "title": "ფაზის დიაპაზონის წაშლა", + "description": "აირჩიეთ პროექტიდან ამოსაღები დიაპაზონი.", + "data": { + "range_index": "ფაზის დიაპაზონი" + } + }, + "assign_profile_phases_auto_detect": { + "title": "ავტომატურად გამოვლენილი ფაზები", + "description": "პროფილი: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} ფაზა(ები) ავტომატურად გამოვლინდა.** ქვემოთ აირჩიეთ მოქმედება.", + "data": { + "action": "მოქმედება" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "გამოვლენილი ფაზების დასახელება", + "description": "პროფილი: **{profile_name}**\n\n**გამოვლინდა {detected_count} ფაზა(ები):**\n{ranges_summary}\n\nთითოეულ ფაზას მიანიჭეთ სახელი." + }, + "assign_profile_phases_select": { + "title": "ფაზის დიაპაზონების მინიჭება", + "description": "ფაზის დიაპაზონების კონფიგურაციისთვის აირჩიეთ პროფილი.", + "data": { + "profile": "პროფილი" + } + }, + "profile_stats": { + "title": "პროფილის სტატისტიკა", + "description": "{stats_table}" + }, + "create_profile": { + "title": "ახალი პროფილის შექმნა", + "description": "ხელით ან წარსული ციკლიდან შექმენით ახალი პროფილი.\n\nპროფილის სახელების მაგალითები: „დელიკატები”, „ინტენსიური”, „სწრაფი რეცხვა”", + "data": { + "profile_name": "პროფილის სახელი", + "reference_cycle": "საცნობარო ციკლი (არჩევითი)", + "manual_duration": "სახელმძღვანელო ხანგრძლივობა (წუთი)" + } + }, + "edit_profile": { + "title": "პროფილის რედაქტირება", + "description": "გადასარქმევი პროფილის არჩევა", + "data": { + "profile": "პროფილი" + } + }, + "rename_profile": { + "title": "პროფილის დეტალების რედაქტირება", + "description": "მიმდინარე პროფილი: {current_name}", + "data": { + "new_name": "პროფილის სახელი", + "manual_duration": "სახელმძღვანელო ხანგრძლივობა (წუთი)" + } + }, + "delete_profile_select": { + "title": "პროფილის წაშლა", + "description": "წასაშლელი პროფილის არჩევა", + "data": { + "profile": "პროფილი" + } + }, + "delete_profile_confirm": { + "title": "პროფილის წაშლის დადასტურება", + "description": "⚠️ ეს სამუდამოდ წაშლის პროფილს: {profile_name}", + "data": { + "unlabel_cycles": "ამ პროფილის მქონე ციკლებიდან ეტიკეტის ამოღება" + } + }, + "auto_label_cycles": { + "title": "ძველი ციკლების ავტო-ეტიკეტირება", + "description": "ნაპოვნია სულ {total_count} ციკლი. პროფილები: {profiles}", + "data": { + "confidence_threshold": "მინიმალური სანდოობა (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "სამარკავი ციკლის არჩევა", + "description": "✓ = დასრულდა, ⚠ = განახლდა, ✗ = შეწყდა", + "data": { + "cycle_id": "ციკლი" + } + }, + "select_cycle_to_delete": { + "title": "ციკლის წაშლა", + "description": "⚠️ ეს სამუდამოდ წაშლის არჩეულ ციკლს", + "data": { + "cycle_id": "წასაშლელი ციკლი" + } + }, + "label_cycle": { + "title": "ციკლის ეტიკეტი", + "description": "{cycle_info}", + "data": { + "profile_name": "პროფილი", + "new_profile_name": "ახალი პროფილის სახელი (შექმნისას)" + } + }, + "post_process": { + "title": "ისტორიის დამუშავება (შერწყმა/გაყოფა)", + "description": "გაასუფთავეთ ციკლის ისტორია ფრაგმენტული გაშვებების შერწყმით და არასწორად შერწყმული ციკლების გაყოფით დროის ფანჯარაში. შეიყვანეთ საათების რაოდენობა უკან დასახედად (გამოიყენეთ 999999 ყველასთვის).", + "data": { + "time_range": "საძიებო ფანჯარა (საათები)", + "gap_seconds": "შერწყმა/გაყოფის ინტერვალი (წამები)" + }, + "data_description": { + "time_range": "საათების რაოდენობა სკანირებისთვის ფრაგმენტული ციკლების შერწყმის მიზნით. გამოიყენეთ 999999 ყველა ისტორიული მონაცემების დასამუშავებლად.", + "gap_seconds": "გაყოფა/შერწყმის გადაწყვეტის ზღვარი. ამაზე მოკლე ინტერვალები ერთიანდება. ამაზე გრძელი ინტერვალები იყოფა. შეამცირეთ ეს, რომ ციკლის გაყოფა მოახდინოთ, რომელიც არასწორად შეერწყა." + } + }, + "cleanup_profile": { + "title": "ისტორიის გასუფთავება - პროფილის არჩევა", + "description": "ვიზუალიზაციისთვის პროფილის არჩევა. ამ პროფილის ყველა წარსული ციკლის ნახვის გრაფიკი შეიქმნება, რათა გამოარჩეულ მნიშვნელობები გამოიყოთ.", + "data": { + "profile": "პროფილი" + } + }, + "cleanup_select": { + "title": "ისტორიის გასუფთავება - გრაფიკი და წაშლა", + "description": "![გრაფიკი]({graph_url})\n\n**ვიზუალიზაცია: {profile_name}**\n\nგრაფიკი ინდივიდუალურ ციკლებს ფერადი ხაზებით აჩვენებს. გამოარჩიეთ გამოყოფილი მნიშვნელობები (ჯგუფიდან შორს მდგომი ხაზები) და ქვემოთ სიაში მათი ფერის შესაბამისობა მოძებნეთ, რომ წაშალოთ.\n\n**აირჩიეთ სამუდამოდ წასაშლელი ციკლები:**", + "data": { + "cycles_to_delete": "წასაშლელი ციკლები (მოადექით შესაბამის ფერებს)" + } + }, + "interactive_editor": { + "title": "შერწყმა/გაყოფის ინტერაქტიული რედაქტორი", + "description": "ციკლის ისტორიაში შესასრულებლად ხელით მოქმედების არჩევა.", + "menu_options": { + "editor_split": "ციკლის გაყოფა (ინტერვალების პოვნა)", + "editor_merge": "ციკლების შერწყმა (ფრაგმენტების შეერთება)", + "editor_delete": "ციკლ(ებ)ის წაშლა", + "menu_back": "← უკან" + } + }, + "editor_select": { + "title": "ციკლების არჩევა", + "description": "დასამუშავებელი ციკლ(ებ)ის არჩევა.\n\n{info_text}", + "data": { + "selected_cycles": "ციკლები" + } + }, + "editor_configure": { + "title": "კონფიგურაცია და გამოყენება", + "description": "{preview_md}", + "data": { + "confirm_action": "მოქმედება", + "merged_profile": "შედეგად მიღებული პროფილი", + "new_profile_name": "ახალი პროფილის სახელი", + "confirm_commit": "დიახ, ვადასტურებ ამ მოქმედებას", + "segment_0_profile": "სეგმენტი 1-ის პროფილი", + "segment_1_profile": "სეგმენტი 2-ის პროფილი", + "segment_2_profile": "სეგმენტი 3-ის პროფილი", + "segment_3_profile": "სეგმენტი 4-ის პროფილი", + "segment_4_profile": "სეგმენტი 5-ის პროფილი", + "segment_5_profile": "სეგმენტი 6-ის პროფილი", + "segment_6_profile": "სეგმენტი 7-ის პროფილი", + "segment_7_profile": "სეგმენტი 8-ის პროფილი", + "segment_8_profile": "სეგმენტი 9-ის პროფილი", + "segment_9_profile": "სეგმენტი 10-ის პროფილი" + } + }, + "editor_split_params": { + "title": "გაყოფის კონფიგურაცია", + "description": "ამ ციკლის გაყოფის მგრძნობელობის დარეგულირება. ამაზე გრძელი ნებისმიერი უმოქმედო ინტერვალი გამოიწვევს გაყოფას.", + "data": { + "split_mode": "გაყოფის მეთოდი" + } + }, + "editor_split_auto_params": { + "title": "ავტომატური გაყოფის კონფიგურაცია", + "description": "შეასწორეთ ამ ციკლის გაყოფის მგრძნობელობა. ამაზე გრძელი უმოქმედობის ინტერვალი გაყოფას გამოიწვევს.", + "data": { + "min_gap_seconds": "გაყოფის შუალედის ზღვარი (წამები)" + } + }, + "editor_split_manual_params": { + "title": "ხელით გაყოფის დროის ნიშნულები", + "description": "{preview_md}ციკლის ფანჯარა: **{cycle_start_wallclock} → {cycle_end_wallclock}** თარიღზე {cycle_date}.\n\nშეიყვანეთ ერთი ან მეტი გაყოფის დროის ნიშნული (`HH:MM` ან `HH:MM:SS`), თითო თითო ხაზზე. ციკლი თითოეულ დროის ნიშნულზე გაიჭრება — N დროის ნიშნული ქმნის N+1 სეგმენტს.", + "data": { + "split_timestamps": "გაყოფის დროის ნიშნულები" + } + }, + "reprocess_history": { + "title": "მოვლა: მონაცემების ხელახალი დამუშავება და ოპტიმიზაცია", + "description": "ისტორიული მონაცემების ღრმა გაწმენდა:\n1. ნულოვანი სიმძლავრის მაჩვენებლების (სიჩუმის) ამოჭრა.\n2. ციკლის დროის/ხანგრძლივობის გასწორება.\n3. ყველა სტატისტიკური მოდელის (კონვერტების) გადათვლა.\n\n**გაუშვით ეს მონაცემების პრობლემების მოსაგვარებლად ან ახალი ალგორითმების გამოსაყენებლად.**" + }, + "wipe_history": { + "title": "ისტორიის წაშლა (მხოლოდ ტესტირებისთვის)", + "description": "⚠️ ეს სამუდამოდ წაშლის ამ მოწყობილობის ყველა შენახულ ციკლს და პროფილს. ამის გაუქმება შეუძლებელია!" + }, + "export_import": { + "title": "JSON-ის ექსპორტი/იმპორტი", + "description": "ამ მოწყობილობის ციკლების, პროფილებისა და პარამეტრების კოპირება/ჩასმა. ქვემოთ ექსპორტი ან JSON-ის ჩასმა იმპორტისთვის.", + "data": { + "mode": "მოქმედება", + "json_payload": "სრული კონფიგურაცია JSON-ში" + }, + "data_description": { + "mode": "ექსპორტის არჩევა მიმდინარე მონაცემებისა და პარამეტრების კოპირებისთვის, ან იმპორტის არჩევა სხვა WashData მოწყობილობიდან ექსპორტირებული მონაცემების ჩასასვლელად.", + "json_payload": "ექსპორტისთვის: დააკოპირეთ ეს JSON (ციკლებს, პროფილებსა და ყველა გაწვდილ პარამეტრს მოიცავს). იმპორტისთვის: ჩასვით WashData-დან ექსპორტირებული JSON." + } + }, + "record_cycle": { + "title": "ციკლის ჩაწერა", + "description": "ხელით ჩაწერეთ ციკლი, რათა შეფერხების გარეშე სუფთა პროფილი შექმნათ.\n\nსტატუსი: **{status}**\nხანგრძლივობა: {duration} წმ\nნიმუშები: {samples}", + "menu_options": { + "record_refresh": "სტატუსის განახლება", + "record_stop": "ჩაწერის შეჩერება (შენახვა და დამუშავება)", + "record_start": "ახალი ჩაწერის დაწყება", + "record_process": "ბოლო ჩანაწერის დამუშავება (ამოჭრა და შენახვა)", + "record_discard": "ბოლო ჩანაწერის გაუქმება", + "menu_back": "← უკან" + } + }, + "record_process": { + "title": "ჩანაწერის დამუშავება", + "description": "![გრაფიკი]({graph_url})\n\n**გრაფიკი დაუმუშავებელ ჩანაწერს აჩვენებს.** ლურჯი = შენახვა, წითელი = შემოთავაზებული ამოჭრა.\n*გრაფიკი სტატიკურია და ფორმის ცვლილებებით არ განახლდება.*\n\nჩაწერა შეჩერდა. ამოჭრები გამოვლენილი შერჩევის სიხშირეზე (~{sampling_rate}წმ) გასწორებულია.\n\nდაუმუშავებელი ხანგრძლივობა: {duration} წმ\nნიმუშები: {samples}", + "data": { + "head_trim": "ამოჭრის დაწყება (წამი)", + "tail_trim": "ამოჭრის დასასრული (წამი)", + "save_mode": "შენახვის დანიშნულება", + "profile_name": "პროფილის სახელი" + } + }, + "learning_feedbacks": { + "title": "სასწავლო გამოხმაურებები", + "description": "მომლოდინე გამოხმაურებები: {count}\n\n{pending_table}\n\nაირჩიეთ ციკლის გადახედვის მოთხოვნა დასამუშავებლად.", + "menu_options": { + "learning_feedbacks_pick": "გადახედეთ ერთ მომლოდინე გამოხმაურებას", + "learning_feedbacks_dismiss_all": "გააუქმეთ ყველა მომლოდინე გამოხმაურება", + "menu_back": "← უკან" + } + }, + "learning_feedbacks_pick": { + "title": "აირჩიეთ გამოსახმაურებელი საკითხი გადასახედად", + "description": "გასახსნელად აირჩიეთ ციკლის მიმოხილვის მოთხოვნა.", + "data": { + "selected_feedback": "აირჩიეთ ციკლი გადასახედად" + } + }, + "learning_feedbacks_empty": { + "title": "სასწავლო გამოხმაურებები", + "description": "მომლოდინე გამოხმაურების მოთხოვნა ვერ მოიძებნა.", + "menu_options": { + "menu_back": "← უკან" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "უარყავით ყველა მომლოდინე გამოხმაურება", + "description": "თქვენ აპირებთ **{count}** მომლოდინე გამოხმაურების მოთხოვნის უარყოფას. უარყოფილი ციკლები თქვენს ისტორიაში დარჩება, მაგრამ ახალ სასწავლო სიგნალს აღარ დაამატებს. ამის გაუქმება შეუძლებელია.\n\n✅ მონიშნეთ ქვემოთ მოცემული ველი და დააწკაპუნეთ **გაგზავნა** ყველაფრის უარსაყოფად.\n❌ დატოვეთ მოუნიშნავი და დააწკაპუნეთ **გაგზავნა** გასაუქმებლად და უკან დასაბრუნებლად.", + "data": { + "confirm_dismiss_all": "დადასტურება: გააუქმეთ ყველა მომლოდინე გამოხმაურების მოთხოვნა" + } + }, + "resolve_feedback": { + "title": "გამოხმაურების გადაჭრა", + "description": "{comparison_img}{candidates_table}\n**გამოვლენილი პროფილი**: {detected_profile} ({confidence_pct}%)\n**სავარაუდო ხანგრძლივობა**: {est_duration_min} წთ\n**ფაქტობრივი ხანგრძლივობა**: {act_duration_min} წთ\n\n__ქვემოთ აირჩიეთ მოქმედება:__", + "data": { + "action": "რის გაკეთება გსურთ?", + "corrected_profile": "სწორი პროგრამა (შესწორებისას)", + "corrected_duration": "სწორი ხანგრძლივობა წამებში (შესწორებისას)" + } + }, + "trim_cycle_select": { + "title": "ციკლის ამოჭრა - ციკლის არჩევა", + "description": "აირჩიეთ ამოსაჭრელი ციკლი. ✓ = დასრულდა, ⚠ = განახლდა, ✗ = შეწყდა", + "data": { + "cycle_id": "ციკლი" + } + }, + "trim_cycle": { + "title": "ციკლის ამოჭრა", + "description": "მიმდინარე ფანჯარა: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "მოქმედება" + } + }, + "trim_cycle_start": { + "title": "ამოჭრის დასაწყისის დაყენება", + "description": "ციკლის ფანჯარა: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nმიმდინარე დაწყება: **{current_wallclock}** (+{current_offset_min} წთ ციკლის დაწყებიდან)\n\nაირჩიეთ ახალი დაწყების დრო ქვემოთ მოცემული საათის ამომრჩევის გამოყენებით.", + "data": { + "trim_start_time": "ახალი დაწყების დრო", + "trim_start_min": "ახალი დაწყება (წუთები ციკლის დაწყებიდან)" + } + }, + "trim_cycle_end": { + "title": "ამოჭრის დასასრულის დაყენება", + "description": "ციკლის ფანჯარა: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nამჟამინდელი დასასრული: **{current_wallclock}** (+{current_offset_min} წთ ციკლის დაწყებიდან)\n\nაირჩიეთ დასრულების ახალი დრო ქვემოთ მოცემული საათის ამომრჩევის გამოყენებით.", + "data": { + "trim_end_time": "ახალი დასრულების დრო", + "trim_end_min": "ახალი დასასრული (წუთები ციკლის დაწყებიდან)" + } + } + }, + "error": { + "import_failed": "იმპორტი ვერ მოხერხდა. გთხოვთ ჩასვათ WashData-ის სწორი JSON ექსპორტი.", + "select_exactly_one": "ამ მოქმედებისთვის აირჩიეთ ზუსტად ერთი ციკლი.", + "select_at_least_one": "ამ მოქმედებისთვის აირჩიეთ მინიმუმ ერთი ციკლი.", + "select_at_least_two": "ამ მოქმედებისთვის აირჩიეთ მინიმუმ ორი ციკლი.", + "profile_exists": "ამ სახელის პროფილი უკვე არსებობს", + "rename_failed": "პროფილის გადარქმევა ვერ მოხერხდა. პროფილი შეიძლება არ არსებობდეს ან ახალი სახელი უკვე გამოყენებულია.", + "assignment_failed": "ციკლისთვის პროფილის მინიჭება ვერ მოხერხდა", + "duplicate_phase": "ამ სახელის ფაზა უკვე არსებობს.", + "phase_not_found": "არჩეული ფაზა ვერ მოიძებნა.", + "invalid_phase_name": "ფაზის სახელი ცარიელი არ შეიძლება იყოს.", + "phase_name_too_long": "ფაზის სახელი ძალიან გრძელია.", + "invalid_phase_range": "ფაზის დიაპაზონი არასწორია. დასასრული დასაწყისზე მეტი უნდა იყოს.", + "invalid_phase_timestamp": "დროის ანაბეჭდი არასწორია. გამოიყენეთ YYYY-MM-DD HH:MM ან HH:MM ფორმატი.", + "overlapping_phase_ranges": "ფაზის დიაპაზონები გადახლართულია. დაარეგულირეთ დიაპაზონები და სცადეთ ხელახლა.", + "incomplete_phase_row": "თითოეული ფაზის სტრიქონი ფაზასა და არჩეული რეჟიმისთვის სრულ დაწყება/დასასრულის მნიშვნელობებს უნდა შეიცავდეს.", + "timestamp_mode_cycle_required": "გთხოვთ, დროის ანაბეჭდის რეჟიმის გამოყენებისას აირჩიოთ წყაროს ციკლი.", + "no_phase_ranges": "ამ მოქმედებისთვის ჯერ ფაზის დიაპაზონი არ არის ხელმისაწვდომი.", + "feedback_notification_title": "WashData: ციკლის შემოწმება ({device})", + "feedback_notification_message": "**მოწყობილობა**: {device}\n**პროგრამა**: {program} ({confidence}% სანდოობა)\n**დრო**: {time}\n\nWashData-ს სჭირდება თქვენი დახმარება ამ გამოვლენილი ციკლის დასადასტურებლად.\n\nამ შედეგის დასადასტურებლად ან გამოსასწორებლად გადადით **პარამეტრები > მოწყობილობები და სერვისები > WashData > კონფიგურაცია > სასწავლო გამოხმაურებები**.", + "suggestions_ready_notification_title": "WashData: შემოთავაზებული პარამეტრები მზადაა ({device})", + "suggestions_ready_notification_message": "**შემოთავაზებული პარამეტრების** სენსორი ახლა **{count}** ქმედითუნარიან რეკომენდაციას ასახავს.\n\nმათი გადასახედად და გამოსაყენებლად: **პარამეტრები > მოწყობილობები და სერვისები > WashData > კონფიგურაცია > გაფართოებული პარამეტრები > შემოთავაზებული მნიშვნელობების გამოყენება**.\n\nშემოთავაზებები არჩევითია და შენახვამდე განსახილველად გამოჩნდება.", + "trim_range_invalid": "დასაწყისი დასასრულამდე უნდა იყოს. გთხოვთ გაასწოროთ ამოჭრის წერტილები.", + "trim_failed": "ამოჭრა ვერ მოხერხდა. ციკლი შეიძლება აღარ არსებობდეს ან ფანჯარა ცარიელია.", + "invalid_split_timestamp": "დროის ნიშნული არასწორია ან ციკლის ფანჯრის ფარგლებს გარეთაა. გამოიყენეთ HH:MM ან HH:MM:SS ციკლის ფანჯრის ფარგლებში.", + "no_split_segments_found": "ამ დროის ნიშნულებიდან ვალიდური სეგმენტები ვერ შეიქმნა. დარწმუნდით, რომ თითოეული დროის ნიშნული ციკლის ფანჯრის შიგნითაა და სეგმენტების ხანგრძლივობა მინიმუმ 1 წუთია.", + "auto_tune_suggestion": "{device_type} {device_title} აღმოაჩინა მოჩვენებების ციკლები. შემოთავაზებული მინიმალური სიმძლავრის ცვლილება: {current_min}W -> {new_min}W (არ გამოიყენება ავტომატურად).", + "auto_tune_title": "WashData ავტომატური რეგულირება", + "auto_tune_fallback": "{device_type} {device_title} აღმოაჩინა მოჩვენებების ციკლები. შემოთავაზებული მინიმალური სიმძლავრის ცვლილება: {current_min}W -> {new_min}W (არ გამოიყენება ავტომატურად)." + }, + "abort": { + "no_cycles_found": "ციკლები ვერ მოიძებნა", + "no_split_segments_found": "არჩეულ ციკლში გასაყოფი სეგმენტები ვერ მოიძებნა.", + "cycle_not_found": "არჩეული ციკლის ჩატვირთვა ვერ მოხერხდა.", + "no_power_data": "არჩეული ციკლისთვის სიმძლავრის კვალის მონაცემები ხელმისაწვდომი არ არის.", + "no_profiles_found": "პროფილები ვერ მოიძებნა. ჯერ შექმენით პროფილი.", + "no_profiles_for_matching": "შედარებისთვის პროფილები ხელმისაწვდომი არ არის. ჯერ შექმენით პროფილები.", + "no_unlabeled_cycles": "ყველა ციკლი უკვე ეტიკეტირებულია", + "no_suggestions": "შემოთავაზებული მნიშვნელობები ჯერ ხელმისაწვდომი არ არის. გაუშვით რამდენიმე ციკლი და სცადეთ ხელახლა.", + "no_predictions": "პროგნოზი არ არის", + "no_custom_phases": "მორგებული ფაზები ვერ მოიძებნა.", + "reprocess_success": "{count} ციკლი წარმატებით დამუშავდა.", + "debug_data_cleared": "გამართვის მონაცემები {count} ციკლიდან წარმატებით წაიშალა." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "ციკლის დაწყება", + "cycle_finish": "ციკლის დასრულება", + "cycle_live": "პირდაპირი პროგრესი" + } + }, + "common_text": { + "options": { + "unlabeled": "(ეტიკეტის გარეშე)", + "create_new_profile": "ახალი პროფილის შექმნა", + "remove_label": "ეტიკეტის ამოღება", + "no_reference_cycle": "(საცნობარო ციკლი არ არის)", + "all_device_types": "ყველა ტიპის მოწყობილობა", + "current_suffix": "(მიმდინარე)", + "editor_select_info": "გასაყოფად 1 ციკლი ან გასაერთიანებლად 2+ ციკლი აირჩიეთ.", + "editor_select_info_split": "გასაყოფად აირჩიეთ 1 ციკლი.", + "editor_select_info_merge": "გასაერთიანებლად აირჩიეთ 2+ ციკლი.", + "editor_select_info_delete": "წასაშლელად აირჩიეთ 1+ ციკლი.", + "no_cycles_recorded": "ჯერ არ არის ჩაწერილი ციკლები.", + "profile_summary_line": "- **{name}**: {count} ციკლი, საშ. {avg}წთ", + "no_profiles_created": "ჯერ არ არის შექმნილი პროფილები.", + "phase_preview": "ფაზის გადახედვა", + "phase_preview_no_curve": "პროფილის საშუალო მრუდი ჯერ ხელმისაწვდომი არ არის. ამ პროფილისთვის გაუშვით/მონიშნეთ მეტი ციკლი.", + "average_power_curve": "საშუალო სიმძლავრის მრუდი", + "unit_min": "წთ", + "profile_option_fmt": "{name} ({count} ციკლი, ~{duration}წთ საშ.)", + "profile_option_short_fmt": "{name} ({count} ციკლი, ~{duration}წთ)", + "deleted_cycles_from_profile": "{profile}-დან {count} ციკლი წაიშალა.", + "not_enough_data_graph": "გრაფიკის შესაქმნელად მონაცემები არ კმარა.", + "no_profiles_found": "პროფილები ვერ მოიძებნა.", + "cycle_info_fmt": "ციკლი: {start}, {duration}წთ, მიმდინარე: {label}", + "top_candidates_header": "### საუკეთესო კანდიდატები", + "tbl_profile": "პროფილი", + "tbl_confidence": "სანდოობა", + "tbl_correlation": "კორელაცია", + "tbl_duration_match": "ხანგრძლივობის შედარება", + "detected_profile": "გამოვლენილი პროფილი", + "estimated_duration": "სავარაუდო ხანგრძლივობა", + "actual_duration": "ფაქტობრივი ხანგრძლივობა", + "choose_action": "__ქვემოთ აირჩიეთ მოქმედება:__", + "tbl_count": "რაოდენობა", + "tbl_avg": "საშ.", + "tbl_min": "მინ.", + "tbl_max": "მაქს.", + "tbl_energy_avg": "ენერგია (საშ.)", + "tbl_energy_total": "ენერგია (სულ)", + "tbl_consistency": "თანმიმდ.", + "tbl_last_run": "ბოლო გაშვება", + "graph_legend_title": "გრაფიკის ლეგენდა", + "graph_legend_body": "ლურჯი ზოლი გამოვლენილი მინიმალური და მაქსიმალური სიმძლავრის დიაპაზონს წარმოადგენს. ხაზი საშუალო სიმძლავრის მრუდს აჩვენებს.", + "recording_preview": "ჩაწერის გადახედვა", + "trim_start": "ამოჭრის დასაწყისი", + "trim_end": "ამოჭრის დასასრული", + "split_preview_title": "გაყოფის გადახედვა", + "split_preview_found_fmt": "ნაპოვნია {count} სეგმენტი.", + "split_preview_confirm_fmt": "დადასტურებაზე დაჭერით ეს ციკლი {count} ცალკეულ ციკლად გაიყოფა.", + "merge_preview_title": "შერწყმის გადახედვა", + "merge_preview_joining_fmt": "{count} ციკლი ერთიანდება. ინტერვალები 0W მაჩვენებლებით შეივსება.", + "editor_delete_preview_title": "წაშლის გადახედვა", + "editor_delete_preview_intro": "არჩეული ციკლები სამუდამოდ წაიშლება:", + "editor_delete_preview_confirm": "ამ ციკლის ჩანაწერების სამუდამოდ წასაშლელად დააწკაპუნეთ „დადასტურება“.", + "no_power_preview": "*გადახედვისთვის სიმძლავრის მონაცემები ხელმისაწვდომი არ არის.*", + "profile_comparison": "პროფილის შედარება", + "actual_cycle_label": "ეს ციკლი (ფაქტობრივი)", + "storage_usage_header": "შენახვის გამოყენება", + "storage_file_size": "ფაილის ზომა", + "storage_cycles": "ციკლები", + "storage_profiles": "პროფილები", + "storage_debug_traces": "გამართვის კვალები", + "overview_suffix": "მიმოხილვა", + "phase_preview_for_profile": "ფაზის გადახედვა პროფილისთვის", + "current_ranges_header": "მიმდინარე დიაპაზონები", + "assign_phases_help": "დიაპაზონების დასამატებლად, სარედაქტირებლად, წასაშლელად ან შესანახად გამოიყენეთ ქვემოთ მოცემული მოქმედებები.", + "profile_summary_header": "პროფილის შეჯამება", + "recent_cycles_header": "ბოლო ციკლები", + "trim_cycle_preview_title": "ციკლის ამოჭრის გადახედვა", + "trim_cycle_preview_no_data": "ამ ციკლისთვის სიმძლავრის მონაცემები ხელმისაწვდომი არ არის.", + "trim_cycle_preview_kept_suffix": "ინახება", + "table_program": "პროგრამა", + "table_when": "როდის", + "table_length": "ხანგრძლივობა", + "table_match": "დამთხვევა", + "table_profile": "პროფილი", + "table_cycles": "ციკლები", + "table_avg_length": "საშ. ხანგრძლივობა", + "table_last_run": "ბოლო გაშვება", + "table_avg_energy": "საშ. ენერგია", + "table_detected_program": "გამოვლენილი პროგრამა", + "table_confidence": "სანდოობა", + "table_reported": "მითითებული", + "phase_builtin_suffix": "(ჩაშენებული)", + "phase_none_available": "ფაზები არ არის ხელმისაწვდომი.", + "settings_deprecation_warning": "⚠️ **მოძველებული მოწყობილობის ტიპი:** {device_type} დაგეგმილია წაშლა მომავალ გამოშვებაში. WashData-ს შესაბამისი მილსადენი არ იძლევა სანდო შედეგებს მოწყობილობის ამ კლასისთვის. თქვენი ინტეგრაცია აგრძელებს მუშაობას გაუქმების პერიოდში; ამ გაფრთხილების გასაჩუმებლად, გადართეთ **მოწყობილობის ტიპი** ქვემოთ ერთ-ერთ მხარდაჭერილ ტიპზე (სარეცხი მანქანა, საშრობი, სარეცხი მანქანა-საშრობი კომბინირებული, ჭურჭლის სარეცხი მანქანა, ჰაერის შემწვარი, პურის მწარმოებელი ან ტუმბო) ან **სხვა (მოწინავე)**, თუ თქვენი მოწყობილობა არ ემთხვევა მხარდაჭერილ ტიპებს. **სხვა (მოწინავე)** აგზავნის განზრახ ზოგად ნაგულისხმევ ნაგულისხმევს, რომელიც არ არის მორგებული რომელიმე კონკრეტული მოწყობილობისთვის, ასე რომ თქვენ მოგიწევთ ზღურბლების, დროის ამოწურვისა და შესატყვისი პარამეტრების კონფიგურაცია; ყველა არსებული პარამეტრი შენარჩუნებულია გადართვისას.", + "phase_other_device_types": "მოწყობილობის სხვა ტიპები:" + } + }, + "interactive_editor_action": { + "options": { + "split": "ციკლის გაყოფა (ინტერვალების პოვნა)", + "merge": "ციკლების შერწყმა (ფრაგმენტების შეერთება)", + "delete": "ციკლ(ებ)ის წაშლა" + } + }, + "split_mode": { + "options": { + "auto": "უქმე ინტერვალების ავტომატური აღმოჩენა", + "manual": "ხელით მითითებული დროის ნიშნულ(ებ)ი" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "მოვლა: მონაცემების ხელახალი დამუშავება და ოპტიმიზაცია", + "clear_debug_data": "გამართვის მონაცემების გასუფთავება (სივრცის გათავისუფლება)", + "wipe_history": "ამ მოწყობილობის ყველა მონაცემის წაშლა (შეუქცევადი)", + "export_import": "JSON-ის ექსპორტი/იმპორტი პარამეტრებით (კოპირება/ჩასმა)" + } + }, + "export_import_mode": { + "options": { + "export": "ყველა მონაცემის ექსპორტი", + "import": "მონაცემების იმპორტი/შერწყმა" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "ძველი ციკლების ავტო-ეტიკეტირება", + "select_cycle_to_label": "კონკრეტული ციკლის ეტიკეტირება", + "select_cycle_to_delete": "ციკლის წაშლა", + "interactive_editor": "შერწყმა/გაყოფის ინტერაქტიული რედაქტორი", + "trim_cycle": "ციკლის მონაცემების ამოჭრა" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "ახალი პროფილის შექმნა", + "edit_profile": "პროფილის რედაქტირება/გადარქმევა", + "delete_profile": "პროფილის წაშლა", + "profile_stats": "პროფილის სტატისტიკა", + "cleanup_profile": "ისტორიის გასუფთავება - გრაფიკი და წაშლა", + "assign_phases": "ფაზის დიაპაზონების მინიჭება" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "ახალი ფაზის შექმნა", + "edit_custom_phase": "ფაზის რედაქტირება", + "delete_custom_phase": "ფაზის წაშლა" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "ფაზის დიაპაზონის დამატება", + "edit_range": "ფაზის დიაპაზონის რედაქტირება", + "delete_range": "ფაზის დიაპაზონის წაშლა", + "clear_ranges": "ყველა დიაპაზონის გასუფთავება", + "auto_detect_ranges": "ფაზების ავტო-გამოვლენა", + "save_ranges": "შენახვა და დაბრუნება" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "სტატუსის განახლება", + "stop_recording": "ჩაწერის შეჩერება (შენახვა და დამუშავება)", + "start_recording": "ახალი ჩაწერის დაწყება", + "process_recording": "ბოლო ჩანაწერის დამუშავება (ამოჭრა და შენახვა)", + "discard_recording": "ბოლო ჩანაწერის გაუქმება" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "ახალი პროფილის შექმნა", + "existing_profile": "არსებულ პროფილში დამატება", + "discard": "ჩაწერის გაუქმება" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "დადასტურება - გამოვლენა სწორია", + "correct": "გასწორება - პროგრამის/ხანგრძლივობის არჩევა", + "ignore": "იგნორირება - ყალბი დადებითი/ხმაურიანი ციკლი", + "delete": "წაშლა - ისტორიიდან ამოღება" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "ფაზების დასახელება და გამოყენება", + "cancel": "ცვლილებების გარეშე დაბრუნება" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "საწყისი წერტილის დაყენება", + "set_end": "საბოლოო წერტილის დაყენება", + "reset": "სრულ ხანგრძლივობაზე გადაყენება", + "apply": "ამოჭრის გამოყენება", + "cancel": "გაუქმება" + } + }, + "device_type": { + "options": { + "washing_machine": "სარეცხი მანქანა", + "dryer": "საშრობი", + "washer_dryer": "სარეცხი-საშრობი კომბინირებული", + "dishwasher": "ჭურჭლის სარეცხი მანქანა", + "coffee_machine": "ყავის აპარატი (მოძველებული)", + "ev": "ელექტრო მანქანა (მოძველებული)", + "air_fryer": "ჰაერის ფრიერი", + "heat_pump": "სითბოს ტუმბო (მოძველებული)", + "bread_maker": "პურის მწარმოებელი", + "pump": "ტუმბო / Sump Pump", + "oven": "ღუმელი (მოძველებული)", + "other": "სხვა (გაფართოებული)" + } + } + }, + "services": { + "label_cycle": { + "name": "ციკლის ეტიკეტი", + "description": "წარსულ ციკლს მიანიჭეთ არსებული პროფილი ან ეტიკეტი ამოიღეთ.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "ეტიკეტირებისთვის WashData მოწყობილობა." + }, + "cycle_id": { + "name": "ციკლის ID", + "description": "ეტიკეტირებისთვის ციკლის ID." + }, + "profile_name": { + "name": "პროფილის სახელი", + "description": "არსებული პროფილის სახელი (პროფილები შეიქმნება პროფილების მართვის მენიუში). ეტიკეტის მოსაშორებლად დატოვეთ ცარიელი." + } + } + }, + "create_profile": { + "name": "პროფილის შექმნა", + "description": "შექმენით ახალი პროფილი (დამოუკიდებლად ან საცნობარო ციკლის საფუძველზე).", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა." + }, + "profile_name": { + "name": "პროფილის სახელი", + "description": "ახალი პროფილის სახელი (მაგ. „Heavy Duty”, „Delicates”)." + }, + "reference_cycle_id": { + "name": "საცნობარო ციკლის ID", + "description": "ამ პროფილის საფუძვლად გამოსაყენებელი ციკლის ID (არჩევითი)." + } + } + }, + "delete_profile": { + "name": "პროფილის წაშლა", + "description": "წაშალეთ პროფილი და სურვილისამებრ ამოიღეთ ეტიკეტი ამ პროფილის მქონე ციკლებიდან.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა." + }, + "profile_name": { + "name": "პროფილის სახელი", + "description": "წასაშლელი პროფილი." + }, + "unlabel_cycles": { + "name": "ციკლების ეტიკეტის მოხსნა", + "description": "ამ პროფილის მქონე ციკლებიდან პროფილის ეტიკეტის ამოღება." + } + } + }, + "auto_label_cycles": { + "name": "ძველი ციკლების ავტო-ეტიკეტირება", + "description": "პროფილის შედარების გამოყენებით ეტიკეტის გარეშე ციკლების რეტროაქტიული ეტიკეტირება.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა." + }, + "confidence_threshold": { + "name": "სანდოობის ზღვარი", + "description": "ეტიკეტების გამოსაყენებლად მინიმალური შედარების სანდოობა (0.50-0.95)." + } + } + }, + "export_config": { + "name": "კონფიგურაციის ექსპორტი", + "description": "ამ მოწყობილობის პროფილების, ციკლებისა და პარამეტრების JSON ფაილში ექსპორტი (მოწყობილობაზე).", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "ექსპორტირებადი WashData მოწყობილობა." + }, + "path": { + "name": "გზა", + "description": "ჩასაწერის სურვილისამებრ აბსოლუტური ფაილის გზა (ნაგულისხმევი /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "კონფიგურაციის იმპორტი", + "description": "ამ მოწყობილობის პროფილების, ციკლებისა და პარამეტრების JSON ექსპორტის ფაილიდან იმპორტი (პარამეტრები შეიძლება გადაიწეროს).", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა, სადაც მოხდება იმპორტი." + }, + "path": { + "name": "გზა", + "description": "იმპორტირებადი ექსპორტის JSON ფაილის აბსოლუტური გზა." + } + } + }, + "submit_cycle_feedback": { + "name": "ციკლის გამოხმაურების გაგზავნა", + "description": "დაადასტურეთ ან შეასწორეთ დასრულებული ციკლის შემდეგ ავტომატურად გამოვლენილი პროგრამა. მიუთითეთ `entry_id` (გაფართოებული) ან `device_id` (რეკომენდებული).", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა (entry_id-ის ნაცვლად რეკომენდებულია)." + }, + "entry_id": { + "name": "შესვლის ID", + "description": "მოწყობილობის კონფიგურაციის ჩანაწერის ID (device_id-ის ალტერნატივა)." + }, + "cycle_id": { + "name": "ციკლის ID", + "description": "გამოხმაურების შეტყობინებაში / ჟურნალებში ნაჩვენები ციკლის ID." + }, + "user_confirmed": { + "name": "გამოვლენილი პროგრამის დადასტურება", + "description": "true-ის დაყენება, თუ გამოვლენილი პროგრამა სწორია." + }, + "corrected_profile": { + "name": "გასწორებული პროფილი", + "description": "თუ არ არის დადასტურებული, მიუთითეთ სწორი პროფილის/პროგრამის სახელი." + }, + "corrected_duration": { + "name": "გასწორებული ხანგრძლივობა (წამი)", + "description": "წამებში გასწორებული ხანგრძლივობა (არჩევითი)." + }, + "notes": { + "name": "შენიშვნები", + "description": "ამ ციკლის შესახებ არჩევითი შენიშვნები." + } + } + }, + "record_start": { + "name": "ციკლის ჩაწერის დაწყება", + "description": "სუფთა ციკლის ხელით ჩაწერის დაწყება (ყველა შედარების ლოგიკას გვერდს უვლის).", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "ჩასაწერი WashData მოწყობილობა." + } + } + }, + "record_stop": { + "name": "ციკლის ჩაწერის შეჩერება", + "description": "ხელით ჩაწერის შეჩერება.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა, რომელზეც ჩაწერა შეჩერდება." + } + } + }, + "trim_cycle": { + "name": "ციკლის ამოჭრა", + "description": "წარსული ციკლის სიმძლავრის მონაცემების კონკრეტულ დროის ფანჯარაზე ამოჭრა.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა." + }, + "cycle_id": { + "name": "ციკლის ID", + "description": "ამოსაჭრელი ციკლის ID." + }, + "trim_start_s": { + "name": "ამოჭრის დასაწყისი (წამი)", + "description": "ამ ოფსეტიდან წამებში მონაცემების შენახვა. ნაგულისხმევი 0." + }, + "trim_end_s": { + "name": "ამოჭრის დასასრული (წამი)", + "description": "ამ ოფსეტამდე წამებში მონაცემების შენახვა. ნაგულისხმევი = სრული ხანგრძლივობა." + } + } + }, + "pause_cycle": { + "name": "პაუზის ციკლი", + "description": "შეაჩერეთ აქტიური ციკლი WashData მოწყობილობისთვის.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა შეჩერდება." + } + } + }, + "resume_cycle": { + "name": "განახლების ციკლი", + "description": "განაახლეთ შეჩერებული ციკლი WashData მოწყობილობისთვის.", + "fields": { + "device_id": { + "name": "მოწყობილობა", + "description": "WashData მოწყობილობა განახლდება." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "მუშაობს" + }, + "match_ambiguity": { + "name": "შედარების ორაზროვნება" + } + }, + "sensor": { + "washer_state": { + "name": "მდგომარეობა", + "state": { + "off": "გამორთულია", + "idle": "ლოდინი", + "starting": "იწყება", + "running": "მუშაობს", + "paused": "შეჩერებულია", + "user_paused": "მომხმარებლის მიერ დაპაუზებული", + "ending": "სრულდება", + "finished": "დასრულდა", + "anti_wrinkle": "ნაოჭების საწინააღმდეგო", + "delay_wait": "ელოდება დაწყებას", + "interrupted": "შეწყდა", + "force_stopped": "ძალით შეჩერდა", + "rinse": "ვლება", + "unknown": "უცნობი", + "clean": "სუფთა" + } + }, + "washer_program": { + "name": "პროგრამა" + }, + "time_remaining": { + "name": "დარჩენილი დრო" + }, + "total_duration": { + "name": "მთლიანი ხანგრძლივობა" + }, + "cycle_progress": { + "name": "პროგრესი" + }, + "current_power": { + "name": "მიმდინარე სიმძლავრე" + }, + "elapsed_time": { + "name": "გასული დრო" + }, + "debug_info": { + "name": "გამართვის ინფორმაცია" + }, + "match_confidence": { + "name": "შედარების სანდოობა" + }, + "top_candidates": { + "name": "საუკეთესო კანდიდატები", + "state": { + "none": "არცერთი" + } + }, + "current_phase": { + "name": "მიმდინარე ფაზა" + }, + "wash_phase": { + "name": "ფაზა" + }, + "profile_cycle_count": { + "name": "პროფილი {profile_name} - რაოდენობა", + "unit_of_measurement": "ციკლები" + }, + "suggestions": { + "name": "ხელმისაწვდომი შემოთავაზებული პარამეტრები" + }, + "pump_runs_today": { + "name": "ტუმბო მუშაობს (ბოლო 24 საათი)" + }, + "cycle_count": { + "name": "ციკლის რაოდენობა" + } + }, + "select": { + "program_select": { + "name": "ციკლის პროგრამა", + "state": { + "auto_detect": "ავტომატური გამოვლენა" + } + } + }, + "button": { + "force_end_cycle": { + "name": "ციკლის ძალით დასრულება" + }, + "pause_cycle": { + "name": "პაუზის ციკლი" + }, + "resume_cycle": { + "name": "განახლების ციკლი" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "საჭიროა სწორი device_id." + }, + "cycle_id_required": { + "message": "საჭიროა სწორი cycle_id." + }, + "profile_name_required": { + "message": "საჭიროა სწორი profile_name." + }, + "device_not_found": { + "message": "მოწყობილობა ვერ მოიძებნა." + }, + "no_config_entry": { + "message": "მოწყობილობის კონფიგურაციის ჩანაწერი ვერ მოიძებნა." + }, + "integration_not_loaded": { + "message": "ინტეგრაცია ამ მოწყობილობისთვის არ ჩაიტვირთა." + }, + "cycle_not_found_or_no_power": { + "message": "ციკლი ვერ მოიძებნა ან სიმძლავრის მონაცემები არ გააჩნია." + }, + "trim_failed_empty_window": { + "message": "ამოჭრა ვერ მოხერხდა - ციკლი ვერ მოიძებნა, სიმძლავრის მონაცემები არ არის ან შედეგად მიღებული ფანჯარა ცარიელია." + }, + "trim_invalid_range": { + "message": "trim_end_s trim_start_s-ზე მეტი უნდა იყოს." + } + } +} diff --git a/custom_components/ha_washdata/translations/ko.json b/custom_components/ha_washdata/translations/ko.json new file mode 100644 index 0000000..e064300 --- /dev/null +++ b/custom_components/ha_washdata/translations/ko.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData 설정", + "description": "세탁기 또는 기타 가전제품을 구성하세요.\n\n전력 센서가 필요합니다.\n\n**다음으로 첫 번째 프로필을 만들지 여부를 묻는 메시지가 표시됩니다.**", + "data": { + "name": "장치 이름", + "device_type": "장치 유형", + "power_sensor": "전력 센서", + "min_power": "최소 전력 임계값 (W)" + }, + "data_description": { + "name": "이 장치의 이름입니다(예: '세탁기', '식기세척기').", + "device_type": "이 가전제품의 종류는 무엇입니까? 감지 및 라벨링 맞춤에 도움이 됩니다.", + "power_sensor": "스마트 플러그에서 실시간 전력 소비(와트)를 보고하는 센서 엔터티입니다.", + "min_power": "이 임계값(와트)보다 높은 전력 수치는 가전제품이 작동 중임을 나타냅니다. 대부분의 장치에서는 2W로 시작하세요." + } + }, + "first_profile": { + "title": "첫 번째 프로필 만들기", + "description": "**사이클**은 가전제품의 완전한 실행을 의미합니다(예: 세탁 한 회). **프로필**은 이러한 사이클을 유형별로 그룹화합니다(예: '면', '빠른 세탁'). 지금 프로필을 생성하면 통합이 즉시 소요 시간을 예측할 수 있습니다.\n\n프로필이 자동으로 감지되지 않는 경우 장치 컨트롤에서 수동으로 선택할 수 있습니다.\n\n**이 단계를 건너뛰려면 '프로필 이름'을 비워두세요.**", + "data": { + "profile_name": "프로필 이름 (선택사항)", + "manual_duration": "예상 소요 시간 (분)" + } + } + }, + "error": { + "cannot_connect": "연결하지 못했습니다", + "invalid_auth": "인증 실패", + "unknown": "예기치 않은 오류" + }, + "abort": { + "already_configured": "장치가 이미 구성되어 있습니다" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "학습 피드백 검토", + "settings": "설정", + "notifications": "알림", + "manage_cycles": "사이클 관리", + "manage_profiles": "프로필 관리", + "manage_phase_catalog": "단계 카탈로그 관리", + "record_cycle": "사이클 기록 (수동)", + "diagnostics": "진단 및 유지보수" + } + }, + "apply_suggestions": { + "title": "제안된 값 복사", + "description": "현재 제안된 값을 저장된 설정에 복사합니다. 이는 일회성 작업이며 자동 덮어쓰기를 활성화하지 않습니다.\n\n복사할 제안 값:\n{suggested}", + "data": { + "confirm": "복사 작업 확인" + } + }, + "apply_suggestions_confirm": { + "title": "제안된 변경 사항 검토", + "description": "WashData에서 **{pending_count}** 적용할 제안 값을 찾았습니다.\n\n변경사항:\n{changes}\n\n✅ 변경 사항을 즉시 적용하고 저장하려면 아래 확인란을 선택하고 **제출**을 클릭하세요.\n❌ 체크하지 않은 상태로 두고 **제출**을 클릭하여 취소하고 돌아갑니다.", + "data": { + "confirm_apply_suggestions": "변경사항 적용 및 저장" + } + }, + "settings": { + "title": "설정", + "description": "{deprecation_warning}감지 임계값, 학습 및 고급 동작을 조정합니다. 기본값은 대부분의 설정에 적합합니다.\n\n현재 이용 가능한 제안 설정: {suggestions_count}개.\n0보다 크면 고급 설정을 열고 제안 값 적용을 사용하여 권장사항을 검토하세요.", + "data": { + "apply_suggestions": "제안된 값 적용", + "device_type": "장치 유형", + "power_sensor": "전력 센서 엔터티", + "min_power": "최소 전력 (W)", + "off_delay": "사이클 종료 지연 (초)", + "notify_actions": "알림 작업", + "notify_people": "이 사람들이 귀가할 때까지 알림 지연", + "notify_only_when_home": "선택한 사람이 귀가할 때까지 알림 지연", + "notify_fire_events": "자동화 이벤트 실행", + "notify_start_services": "주기 시작 - 알림 대상", + "notify_finish_services": "주기 완료 - 알림 대상", + "notify_live_services": "실시간 진행 - 알림 대상", + "notify_before_end_minutes": "완료 전 알림 (종료 몇 분 전)", + "notify_title": "알림 제목", + "notify_icon": "알림 아이콘", + "notify_start_message": "시작 메시지 형식", + "notify_finish_message": "완료 메시지 형식", + "notify_pre_complete_message": "완료 전 메시지 형식", + "show_advanced": "고급 설정 편집 (다음 단계)" + }, + "data_description": { + "apply_suggestions": "학습 알고리즘에서 제안한 값을 양식에 복사하려면 이 상자를 선택하세요. 저장하기 전에 검토하세요.\n\n제안 (학습에서):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "가전제품 유형을 선택하세요(세탁기, 건조기, 식기세척기, 커피머신, EV). 이 태그는 더 나은 구성을 위해 각 사이클과 함께 저장됩니다.", + "power_sensor": "실시간 전력(와트)을 보고하는 센서 엔터티입니다. 기록 데이터 손실 없이 언제든지 변경할 수 있습니다. 스마트 플러그를 교체할 때 유용합니다.", + "min_power": "이 임계값(와트)보다 높은 전력 수치는 가전제품이 작동 중임을 나타냅니다. 대부분의 장치에서는 2W로 시작하세요.", + "off_delay": "완료를 표시하려면 평활화된 전력이 정지 임계값 아래에서 이 시간(초) 동안 유지되어야 합니다. 기본값: 120초.", + "notify_actions": "알림용으로 실행할 선택적 Home Assistant 작업 시퀀스입니다(스크립트, notify, TTS 등). 설정된 경우 대체 알림 서비스 전에 작업이 실행됩니다.", + "notify_people": "재실 확인에 사용되는 사람 엔터티입니다. 활성화하면 선택한 사람 중 최소 한 명이 귀가할 때까지 알림 전달이 지연됩니다.", + "notify_only_when_home": "활성화하면 선택한 사람 중 최소 한 명이 집에 있을 때까지 알림을 지연합니다.", + "notify_fire_events": "활성화하면 사이클 시작 및 종료 시 Home Assistant 이벤트(ha_washdata_cycle_started, ha_washdata_cycle_ended)를 발생시켜 자동화를 구동합니다.", + "notify_start_services": "주기가 시작되면 경고할 하나 이상의 알림 서비스입니다. 시작 알림을 건너뛰려면 비워 두세요.", + "notify_finish_services": "주기가 완료되거나 완료에 가까워지면 경고할 하나 이상의 알림 서비스입니다. 완료 알림을 건너뛰려면 비워 두세요.", + "notify_live_services": "주기가 실행되는 동안 실시간 진행 상황 업데이트를 수신하는 하나 이상의 알림 서비스입니다(모바일 동반 앱만 해당). 실시간 업데이트를 건너뛰려면 비워 두세요.", + "notify_before_end_minutes": "사이클의 예상 종료 몇 분 전에 알림을 발생시킵니다. 비활성화하려면 0으로 설정하세요. 기본값: 0.", + "notify_title": "알림의 맞춤 제목입니다. {device} 자리 표시자를 지원합니다.", + "notify_icon": "알림에 사용할 아이콘입니다(예: mdi:washing-machine). 아이콘 없이 보내려면 비워두세요.", + "notify_start_message": "사이클이 시작될 때 전송되는 메시지입니다. {device} 자리 표시자를 지원합니다.", + "notify_finish_message": "사이클이 완료될 때 전송되는 메시지입니다. {device}, {duration}, {program}, {energy_kwh}, {cost} 자리 표시자를 지원합니다.", + "notify_pre_complete_message": "주기가 실행되는 동안 반복되는 실시간 진행 상황의 텍스트가 업데이트됩니다. {device}, {minutes}, {program} 자리표시자를 지원합니다." + } + }, + "notifications": { + "title": "알림", + "description": "알림 채널, 템플릿 및 선택적 실시간 모바일 진행 상황 업데이트를 구성합니다.", + "data": { + "notify_actions": "알림 작업", + "notify_people": "이 사람들이 귀가할 때까지 알림 지연", + "notify_only_when_home": "선택한 사람이 귀가할 때까지 알림 지연", + "notify_fire_events": "자동화 이벤트 실행", + "notify_start_services": "주기 시작 - 알림 대상", + "notify_finish_services": "주기 완료 - 알림 대상", + "notify_live_services": "실시간 진행 - 알림 대상", + "notify_before_end_minutes": "완료 전 알림 (종료 몇 분 전)", + "notify_live_interval_seconds": "실시간 업데이트 간격 (초)", + "notify_live_overrun_percent": "실시간 업데이트 초과 허용값 (%)", + "notify_live_chronometer": "크로노미터 카운트다운 타이머", + "notify_title": "알림 제목", + "notify_icon": "알림 아이콘", + "notify_start_message": "시작 메시지 형식", + "notify_finish_message": "완료 메시지 형식", + "notify_pre_complete_message": "완료 전 메시지 형식", + "energy_price_entity": "에너지 가격 - 엔터티(선택 사항)", + "energy_price_static": "에너지 가격 - 정적 가치(선택 사항)", + "go_back": "저장하지 않고 돌아가기", + "notify_reminder_message": "알림 메시지 형식", + "notify_timeout_seconds": "다음 시간 이후 자동 종료(초)", + "notify_channel": "알림 채널(상태/실시간/알림)", + "notify_finish_channel": "완료된 알림 채널" + }, + "data_description": { + "go_back": "위의 변경 사항을 모두 버리고 메인 메뉴로 돌아가려면 이 항목을 체크한 뒤 제출을 클릭하세요.", + "notify_actions": "알림용으로 실행할 선택적 Home Assistant 작업 시퀀스입니다(스크립트, notify, TTS 등). 설정된 경우 대체 알림 서비스 전에 작업이 실행됩니다.", + "notify_people": "재실 확인에 사용되는 사람 엔터티입니다. 활성화하면 선택한 사람 중 최소 한 명이 귀가할 때까지 알림 전달이 지연됩니다.", + "notify_only_when_home": "활성화하면 선택한 사람 중 최소 한 명이 집에 있을 때까지 알림을 지연합니다.", + "notify_fire_events": "활성화하면 사이클 시작 및 종료 시 Home Assistant 이벤트(ha_washdata_cycle_started, ha_washdata_cycle_ended)를 발생시켜 자동화를 구동합니다.", + "notify_start_services": "주기가 시작될 때 경고할 하나 이상의 알림 서비스(예: notify.mobile_app_my_phone). 시작 알림을 건너뛰려면 비워 두세요.", + "notify_finish_services": "주기가 완료되거나 완료에 가까워지면 경고할 하나 이상의 알림 서비스입니다. 완료 알림을 건너뛰려면 비워 두세요.", + "notify_live_services": "주기가 실행되는 동안 실시간 진행 상황 업데이트를 수신하는 하나 이상의 알림 서비스입니다(모바일 동반 앱만 해당). 실시간 업데이트를 건너뛰려면 비워 두세요.", + "notify_before_end_minutes": "사이클의 예상 종료 몇 분 전에 알림을 발생시킵니다. 비활성화하려면 0으로 설정하세요. 기본값: 0.", + "notify_live_interval_seconds": "실행 중 진행 상황 업데이트가 전송되는 빈도입니다. 값이 낮을수록 반응이 빠르지만 알림을 더 많이 사용합니다. 최소 30초가 적용됩니다. 30초 미만의 값은 자동으로 30초로 반올림됩니다.", + "notify_live_overrun_percent": "장기/초과 실행 사이클에 대한 예상 업데이트 횟수 이상의 안전 마진입니다(예: 20%는 예상 업데이트의 1.2배를 허용).", + "notify_live_chronometer": "활성화되면 각 실시간 업데이트에 예상 완료 시간까지의 크로노미터 카운트다운이 포함됩니다. 업데이트 사이에 기기에서 알림 타이머가 자동으로 꺼집니다(Android 도우미 앱만 해당).", + "notify_title": "알림의 맞춤 제목입니다. {device} 자리 표시자를 지원합니다.", + "notify_icon": "알림에 사용할 아이콘입니다(예: mdi:washing-machine). 아이콘 없이 보내려면 비워두세요.", + "notify_start_message": "사이클이 시작될 때 전송되는 메시지입니다. {device} 자리 표시자를 지원합니다.", + "notify_finish_message": "사이클이 완료될 때 전송되는 메시지입니다. {device}, {duration}, {program}, {energy_kwh}, {cost} 자리 표시자를 지원합니다.", + "energy_price_entity": "현재 전기 가격을 보유하는 숫자 엔터티를 선택합니다(예: sensor.electricity_price, input_number.energy_rate). 설정되면 {cost} 자리 표시자를 활성화합니다. 둘 다 구성된 경우 정적 값보다 우선합니다.", + "energy_price_static": "kWh당 전기요금이 고정되어 있습니다. 설정되면 {cost} 자리 표시자를 활성화합니다. 위에서 엔터티가 구성된 경우 무시됩니다. 메시지 템플릿에 통화 기호를 추가하세요. {cost} €.", + "notify_reminder_message": "일회성 알림 텍스트는 예상 종료 시간 이전에 구성된 시간(분)을 보냈습니다. 위의 반복되는 실시간 업데이트와 다릅니다. {device}, {minutes}, {program} 자리표시자를 지원합니다.", + "notify_timeout_seconds": "이 몇 초 후에 알림을 자동으로 해제합니다(컴패니언 앱 '시간 초과'로 전달됨). 해제될 때까지 유지하려면 0으로 설정합니다. 기본값: 0.", + "notify_channel": "상태, 실시간 진행 상황 및 알림 알림을 위한 Android 컴패니언 앱 알림 채널입니다. 채널의 사운드와 중요성은 채널 이름이 처음 사용될 때 동반 앱에서 구성됩니다. WashData는 채널 이름만 설정합니다. 앱 기본값은 비워 두세요.", + "notify_finish_channel": "완료 알림을 위한 별도의 Android 채널(알림 및 세탁 대기 잔소리에도 사용됨)이 자체 사운드를 가질 수 있도록 합니다. 위 채널을 재사용하려면 비워두세요.", + "notify_pre_complete_message": "주기가 실행되는 동안 반복되는 실시간 진행 상황의 텍스트가 업데이트됩니다. {device}, {minutes}, {program} 자리표시자를 지원합니다." + } + }, + "advanced_settings": { + "title": "고급 설정", + "description": "감지 임계값, 학습 및 고급 동작을 조정합니다. 기본값은 대부분의 설정에서 잘 작동합니다.", + "sections": { + "suggestions_section": { + "name": "권장 설정", + "description": "학습된 제안 {suggestions_count}개를 사용할 수 있습니다. 저장하기 전에 제안된 변경 내용을 검토하려면 '제안된 값 적용'을 체크하세요.", + "data": { + "apply_suggestions": "제안된 값 적용" + }, + "data_description": { + "apply_suggestions": "학습 알고리즘에서 제안된 값의 검토 단계를 열려면 이 상자를 선택하세요. 아무것도 자동으로 저장되지 않습니다.\n\n제안 (학습에서):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "감지 및 전력 임계값", + "description": "장치가 언제 켜짐 또는 꺼짐으로 간주되는지, 에너지 게이트와 상태 전환 타이밍을 설정합니다.", + "data": { + "start_duration_threshold": "시작 디바운스 기간 (초)", + "start_energy_threshold": "시작 에너지 게이트 (Wh)", + "completion_min_seconds": "완료 최소 실행 시간 (초)", + "start_threshold_w": "시작 임계값 (W)", + "stop_threshold_w": "정지 임계값 (W)", + "end_energy_threshold": "종료 에너지 게이트 (Wh)", + "running_dead_zone": "실행 중 데드존 (초)", + "end_repeat_count": "종료 반복 횟수", + "min_off_gap": "사이클 간 최소 간격 (초)", + "sampling_interval": "샘플링 간격 (초)" + }, + "data_description": { + "start_duration_threshold": "이 기간(초)보다 짧은 전력 스파이크를 무시합니다. 실제 사이클 시작 전 부팅 스파이크(예: 2초간 60W)를 필터링합니다. 기본값: 5초.", + "start_energy_threshold": "사이클이 확인되려면 START 단계 동안 최소 이 에너지(Wh)를 축적해야 합니다. 기본값: 0.005Wh.", + "completion_min_seconds": "이보다 짧은 사이클은 자연스럽게 끝나더라도 '중단됨'으로 표시됩니다. 기본값: 600초.", + "start_threshold_w": "사이클을 시작하려면 전력이 이 임계값보다 높아야 합니다. 대기 전력보다 높게 설정하세요. 기본값: min_power + 1W.", + "stop_threshold_w": "사이클을 종료하려면 전력이 이 임계값 아래로 떨어져야 합니다. 노이즈로 인한 오작동을 방지하려면 0보다 약간 높게 설정하세요. 기본값: 0.6 * min_power.", + "end_energy_threshold": "마지막 종료 지연 창의 에너지가 이 값(Wh) 미만일 때만 사이클이 종료됩니다. 전력이 0 근처에서 변동할 때 오탐 종료를 방지합니다. 기본값: 0.05Wh.", + "running_dead_zone": "사이클 시작 후 초기 스핀업 단계에서 오탐 종료를 방지하기 위해 이 시간(초) 동안 전력 강하를 무시합니다. 비활성화하려면 0으로 설정하세요. 기본값: 0초.", + "end_repeat_count": "사이클을 종료하기 전에 종료 조건(off_delay 동안 임계값 아래의 전력)이 연속으로 충족되어야 하는 횟수입니다. 높을수록 일시 정지 중 오탐 종료를 방지합니다. 기본값: 1.", + "min_off_gap": "이전 사이클 종료 후 이 시간(초) 이내에 새 사이클이 시작되면 하나로 병합됩니다.", + "sampling_interval": "최소 샘플링 간격(초)입니다. 이 속도보다 빠른 업데이트는 무시됩니다. 기본값: 30초(세탁기, 세탁기-건조기, 식기세척기의 경우 2초)." + } + }, + "matching_section": { + "name": "프로필 일치 및 학습", + "description": "WashData가 실행 중인 사이클을 학습된 프로필과 얼마나 적극적으로 일치시키는지, 그리고 언제 피드백을 요청할지 설정합니다.", + "data": { + "profile_match_min_duration_ratio": "프로필 일치 최소 기간 비율 (0.1-1.0)", + "profile_match_interval": "프로필 일치 간격 (초)", + "profile_match_threshold": "프로필 일치 임계값", + "profile_unmatch_threshold": "프로필 불일치 임계값", + "auto_label_confidence": "자동 라벨 신뢰도 (0-1)", + "learning_confidence": "피드백 요청 신뢰도 (0-1)", + "suppress_feedback_notifications": "피드백 알림 억제", + "duration_tolerance": "기간 허용 오차 (0-0.5)", + "smoothing_window": "평활화 창 (샘플)", + "profile_duration_tolerance": "프로필 일치 기간 허용 오차 (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "프로필 일치를 위한 최소 기간 비율입니다. 실행 중인 사이클은 프로필 기간의 최소 이 비율이어야 합니다. 낮을수록 더 빨리 일치합니다. 기본값: 0.50(50%).", + "profile_match_interval": "사이클 중 프로필 일치를 시도하는 빈도(초)입니다. 값이 낮을수록 감지가 빠르지만 CPU를 더 많이 사용합니다. 기본값: 300초(5분).", + "profile_match_threshold": "프로필을 일치로 간주하는 데 필요한 최소 DTW 유사도 점수(0.0~1.0)입니다. 높을수록 엄격한 일치, 오탐 감소. 기본값: 0.4.", + "profile_unmatch_threshold": "이전에 일치한 프로필을 거부하는 DTW 유사도 점수(0.0~1.0)입니다. 깜빡임 방지를 위해 일치 임계값보다 낮아야 합니다. 기본값: 0.35.", + "auto_label_confidence": "완료 시 이 신뢰도(0.0~1.0) 이상인 사이클에 자동으로 라벨을 지정합니다.", + "learning_confidence": "이벤트를 통해 사용자 확인을 요청하는 최소 신뢰도(0.0~1.0)입니다.", + "suppress_feedback_notifications": "활성화되면 WashData는 계속해서 내부적으로 피드백을 추적하고 요청하지만 주기 확인을 요청하는 지속적인 알림은 표시하지 않습니다. 주기가 항상 올바르게 감지되고 프롬프트가 산만하다고 느낄 때 유용합니다.", + "duration_tolerance": "실행 중 남은 시간 예측의 허용 오차입니다(0.0~0.5는 0~50%에 해당).", + "smoothing_window": "이동 평균 평활화에 사용되는 최근 전력 수치의 수입니다.", + "profile_duration_tolerance": "전체 기간 차이에 대한 프로필 일치 허용 오차(0.0~0.5)입니다. 기본 동작: ±25%." + } + }, + "timing_section": { + "name": "타이밍, 유지보수 및 디버그", + "description": "워치독, 진행 상황 초기화, 자동 유지보수, 디버그 엔터티 노출을 설정합니다.", + "data": { + "watchdog_interval": "감시 간격 (초)", + "no_update_active_timeout": "업데이트 없음 제한 시간 (초)", + "progress_reset_delay": "진행 상황 초기화 지연 (초)", + "auto_maintenance": "자동 유지보수 활성화", + "expose_debug_entities": "디버그 엔터티 노출", + "save_debug_traces": "디버그 추적 저장" + }, + "data_description": { + "watchdog_interval": "실행 중 감시 확인 사이의 시간(초)입니다. 기본값: 5초. 경고: 오탐 정지를 방지하려면 이 값이 센서의 업데이트 간격보다 높아야 합니다(예: 센서가 60초마다 업데이트하면 65초 이상 사용).", + "no_update_active_timeout": "변경 시 게시 센서의 경우: 이 시간(초) 동안 업데이트가 없으면 ACTIVE 사이클을 강제 종료합니다. 저전력 완료 시에는 여전히 종료 지연을 사용합니다.", + "progress_reset_delay": "사이클이 완료된 후(100%) 진행 상황을 0%로 초기화하기 전에 이 시간(초) 동안 대기합니다. 기본값: 300초.", + "auto_maintenance": "자동 유지보수를 활성화합니다(샘플 복구, 조각 병합).", + "expose_debug_entities": "디버깅을 위한 고급 센서(신뢰도, 단계, 모호도)를 표시합니다.", + "save_debug_traces": "상세한 순위 및 전력 추적 데이터를 기록에 저장합니다(저장 용량이 증가합니다)." + } + }, + "anti_wrinkle_section": { + "name": "주름 방지 보호 (건조기)", + "description": "고스트 사이클을 방지하기 위해 사이클 종료 후의 드럼 회전을 무시합니다. 건조기 / 세탁건조기 전용입니다.", + "data": { + "anti_wrinkle_enabled": "주름 방지 보호 (건조기/세탁건조기 전용)", + "anti_wrinkle_max_power": "주름 방지 최대 전력 (W) (건조기/세탁건조기 전용)", + "anti_wrinkle_max_duration": "주름 방지 최대 지속 시간 (초) (건조기/세탁건조기 전용)", + "anti_wrinkle_exit_power": "주름 방지 종료 전력 (W) (건조기/세탁건조기 전용)" + }, + "data_description": { + "anti_wrinkle_enabled": "고스트 사이클을 방지하기 위해 사이클 종료 후 짧은 저전력 드럼 회전을 무시합니다(건조기/세탁건조기 전용).", + "anti_wrinkle_max_power": "이 전력 이하의 버스트는 새 사이클 대신 주름 방지 회전으로 처리됩니다. 주름 방지 보호가 활성화된 경우에만 적용됩니다.", + "anti_wrinkle_max_duration": "주름 방지 회전으로 처리할 최대 버스트 길이입니다. 주름 방지 보호가 활성화된 경우에만 적용됩니다.", + "anti_wrinkle_exit_power": "전력이 이 수준 아래로 떨어지면 주름 방지를 즉시 종료합니다(완전 꺼짐). 주름 방지 보호가 활성화된 경우에만 적용됩니다." + } + }, + "delay_start_section": { + "name": "지연 시작 감지", + "description": "사이클이 실제로 시작될 때까지 지속적인 저전력 대기 상태를 '시작을 기다리는 중'으로 처리합니다.", + "data": { + "delay_start_detect_enabled": "지연된 시작 감지 활성화", + "delay_confirm_seconds": "지연 시작: 대기 확인 시간 (초)", + "delay_timeout_hours": "지연된 시작: 최대 대기 시간(h)" + }, + "data_description": { + "delay_start_detect_enabled": "활성화되면 짧은 저전력 소모 스파이크와 지속적인 대기 전력이 지연된 시작으로 감지됩니다. 지연되는 동안 상태 센서에 '시작 대기 중'이 표시됩니다. 사이클이 실제로 시작될 때까지는 프로그램 시간이나 진행 상황이 추적되지 않습니다. start_threshold_w를 드레인 스파이크 전력보다 높게 설정해야 합니다.", + "delay_confirm_seconds": "전력이 정지 임계값 (W)과 시작 임계값 (W) 사이에서 이 시간(초) 동안 유지되어야 WashData가 '시작을 기다리는 중' 상태로 들어갑니다. 메뉴 탐색 중 발생하는 짧은 피크를 걸러냅니다. 기본값: 60초.", + "delay_timeout_hours": "안전 시간 초과: 이 시간이 지난 후에도 기기가 여전히 '시작 대기 중' 상태이면 WashData가 꺼짐으로 재설정됩니다. 기본값: 8시간" + } + }, + "external_triggers_section": { + "name": "외부 트리거, 도어 및 일시 정지", + "description": "선택적 외부 종료 트리거 센서, 일시 정지/클린 감지를 위한 도어 센서, 그리고 일시 정지 시 전원을 차단하는 스위치를 설정합니다.", + "data": { + "external_end_trigger_enabled": "외부 사이클 종료 트리거 활성화", + "external_end_trigger": "외부 사이클 종료 센서", + "external_end_trigger_inverted": "트리거 로직 반전 (OFF 시 트리거)", + "door_sensor_entity": "도어 센서", + "pause_cuts_power": "일시 정지 시 전원 차단", + "switch_entity": "스위치 엔터티(정전 일시 정지용)", + "notify_unload_delay_minutes": "세탁 대기 알림 지연(분)" + }, + "data_description": { + "external_end_trigger_enabled": "사이클 완료를 트리거하기 위한 외부 이진 센서 모니터링을 활성화합니다.", + "external_end_trigger": "이진 센서 엔터티를 선택하세요. 이 센서가 트리거되면 현재 사이클이 '완료' 상태로 종료됩니다.", + "external_end_trigger_inverted": "기본적으로 센서가 켜질 때 트리거됩니다. 대신 센서가 꺼질 때 트리거하려면 이 상자를 선택하세요.", + "door_sensor_entity": "기계 도어용 옵션 바이너리 센서(켜짐 = 열림, 꺼짐 = 닫힘). 활성 사이클 중에 문이 열리면 WashData는 사이클이 의도적으로 일시 중지되었음을 확인합니다. 사이클 종료 후에도 도어가 여전히 닫혀 있으면 도어가 열릴 때까지 'Clean' 상태로 변경됩니다. 참고: 도어를 닫아도 일시 중지된 주기가 자동으로 재개되지 않습니다. 재개는 주기 재개 버튼 또는 서비스를 통해 수동으로 실행되어야 합니다.", + "pause_cuts_power": "Pause Cycle 버튼이나 서비스를 통해 일시 중지하는 경우 구성된 스위치 엔터티도 끄십시오. 이 장치에 스위치 엔터티가 구성된 경우에만 적용됩니다.", + "switch_entity": "일시중지하거나 재개할 때 전환할 스위치 엔터티입니다. '일시 중지 시 전원 차단'이 활성화된 경우 필요합니다.", + "notify_unload_delay_minutes": "사이클이 종료되고 몇 분 동안 문이 열리지 않으면 완료 알림 채널을 통해 알림을 보냅니다(도어 센서 필요). 비활성화하려면 0으로 설정합니다. 기본값: 60분" + } + }, + "pump_section": { + "name": "펌프 모니터", + "description": "펌프 장치 유형 전용입니다. 펌프 사이클이 이 지속 시간을 초과하면 이벤트를 발생시킵니다.", + "data": { + "pump_stuck_duration": "펌프 정체 경고 임계값(펌프만 해당)" + }, + "data_description": { + "pump_stuck_duration": "이 시간이 지난 후에도 펌프 사이클이 계속 실행 중인 경우 ha_washdata_pump_stuck 이벤트가 한 번 실행됩니다. 이를 사용하여 걸린 모터를 감지합니다(일반적인 배수 펌프 작동 시간은 60초 미만입니다). 기본값: 1800초(30분) 펌프 장치 유형만 해당." + } + }, + "device_link_section": { + "name": "장치 링크", + "description": "선택적으로 이 WashData 장치를 기존 Home Assistant 장치(예: 스마트 플러그 또는 기기 자체)에 연결합니다. 그러면 WashData 장치가 해당 장치를 통해 \"연결됨\"으로 표시됩니다. WashData를 독립형 장치로 유지하려면 비워 두세요.", + "data": { + "linked_device": "연결된 장치" + }, + "data_description": { + "linked_device": "WashData를 연결할 장치를 선택하세요. 이렇게 하면 장치 레지스트리에 'Connected via' 참조가 추가됩니다. WashData는 자체 장치 카드와 엔터티를 유지합니다. 링크를 제거하려면 선택을 취소하세요." + } + } + } + }, + "diagnostics": { + "title": "진단 및 유지보수", + "description": "조각난 사이클 병합 또는 저장된 데이터 마이그레이션과 같은 유지보수 작업을 실행합니다.\n\n**저장 공간 사용량**\n\n- 파일 크기: {file_size_kb} KB\n- 사이클: {cycle_count}\n- 프로필: {profile_count}\n- 디버그 추적: {debug_count}", + "menu_options": { + "reprocess_history": "유지보수: 데이터 재처리 및 최적화", + "clear_debug_data": "디버그 데이터 지우기 (공간 확보)", + "wipe_history": "이 장치의 모든 데이터 삭제 (되돌릴 수 없음)", + "export_import": "설정 포함 JSON 내보내기/가져오기 (복사/붙여넣기)", + "menu_back": "← 뒤로" + } + }, + "clear_debug_data": { + "title": "디버그 데이터 지우기", + "description": "저장된 모든 디버그 추적을 삭제하시겠습니까? 공간이 확보되지만 이전 사이클의 자세한 순위 정보가 제거됩니다." + }, + "manage_cycles": { + "title": "사이클 관리", + "description": "최근 사이클:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "기존 사이클 자동 라벨 지정", + "select_cycle_to_label": "특정 사이클 라벨 지정", + "select_cycle_to_delete": "사이클 삭제", + "interactive_editor": "병합/분할 대화형 편집기", + "trim_cycle_select": "사이클 데이터 트림", + "menu_back": "← 뒤로" + } + }, + "manage_cycles_empty": { + "title": "사이클 관리", + "description": "아직 기록된 사이클이 없습니다.", + "menu_options": { + "auto_label_cycles": "기존 사이클 자동 라벨 지정", + "menu_back": "← 뒤로" + } + }, + "manage_profiles": { + "title": "프로필 관리", + "description": "프로필 요약:\n{profile_summary}", + "menu_options": { + "create_profile": "새 프로필 만들기", + "edit_profile": "프로필 편집/이름 바꾸기", + "delete_profile_select": "프로필 삭제", + "profile_stats": "프로필 통계", + "cleanup_profile": "기록 정리 - 그래프 및 삭제", + "assign_profile_phases_select": "단계 범위 할당", + "menu_back": "← 뒤로" + } + }, + "manage_profiles_empty": { + "title": "프로필 관리", + "description": "아직 생성된 프로필이 없습니다.", + "menu_options": { + "create_profile": "새 프로필 만들기", + "menu_back": "← 뒤로" + } + }, + "manage_phase_catalog": { + "title": "단계 카탈로그 관리", + "description": "단계 카탈로그 (이 장치의 기본값 + 사용자 정의 단계):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "새 단계 만들기", + "phase_catalog_edit_select": "단계 편집", + "phase_catalog_delete": "단계 삭제", + "menu_back": "← 뒤로" + } + }, + "phase_catalog_create": { + "title": "사용자 정의 단계 만들기", + "description": "사용자 정의 단계 이름과 설명을 만들고 적용할 장치 유형을 선택합니다.", + "data": { + "target_device_type": "대상 장치 유형", + "phase_name": "단계 이름", + "phase_description": "단계 설명" + } + }, + "phase_catalog_edit_select": { + "title": "사용자 정의 단계 편집", + "description": "편집할 사용자 정의 단계를 선택하세요.", + "data": { + "phase_name": "사용자 정의 단계" + } + }, + "phase_catalog_edit": { + "title": "사용자 정의 단계 편집", + "description": "단계 편집: {phase_name}", + "data": { + "phase_name": "단계 이름", + "phase_description": "단계 설명" + } + }, + "phase_catalog_delete": { + "title": "사용자 정의 단계 삭제", + "description": "삭제할 사용자 정의 단계를 선택하세요. 이 단계를 사용하는 모든 할당된 범위가 제거됩니다.", + "data": { + "phase_name": "사용자 정의 단계" + } + }, + "assign_profile_phases": { + "title": "단계 범위 할당", + "description": "프로필 단계 미리보기: **{profile_name}**\n\n{timeline_svg}\n\n**현재 범위:**\n{current_ranges}\n\n범위를 추가, 편집, 삭제 또는 저장하려면 아래 작업을 사용하세요.", + "menu_options": { + "assign_profile_phases_add": "단계 범위 추가", + "assign_profile_phases_edit_select": "단계 범위 편집", + "assign_profile_phases_delete": "단계 범위 삭제", + "phase_ranges_clear": "모든 범위 지우기", + "assign_profile_phases_auto_detect": "단계 자동 감지", + "phase_ranges_save": "저장하고 돌아가기", + "menu_back": "← 뒤로" + } + }, + "assign_profile_phases_add": { + "title": "단계 범위 추가", + "description": "현재 초안에 하나의 단계 범위를 추가합니다.", + "data": { + "phase_name": "단계", + "start_min": "시작 분", + "end_min": "종료 분" + } + }, + "assign_profile_phases_edit_select": { + "title": "단계 범위 편집", + "description": "편집할 범위를 선택하세요.", + "data": { + "range_index": "단계 범위" + } + }, + "assign_profile_phases_edit": { + "title": "단계 범위 편집", + "description": "선택한 단계 범위를 업데이트합니다.", + "data": { + "phase_name": "단계", + "start_min": "시작 분", + "end_min": "종료 분" + } + }, + "assign_profile_phases_delete": { + "title": "단계 범위 삭제", + "description": "초안에서 제거할 범위를 선택하세요.", + "data": { + "range_index": "단계 범위" + } + }, + "assign_profile_phases_auto_detect": { + "title": "자동 감지된 단계", + "description": "프로필: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count}개 단계가 자동으로 감지되었습니다.** 아래에서 작업을 선택하세요.", + "data": { + "action": "작업" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "감지된 단계 이름 지정", + "description": "프로필: **{profile_name}**\n\n**{detected_count}개 단계가 감지되었습니다:**\n{ranges_summary}\n\n각 단계에 이름을 지정하세요." + }, + "assign_profile_phases_select": { + "title": "단계 범위 할당", + "description": "단계 범위를 구성할 프로필을 선택하세요.", + "data": { + "profile": "프로필" + } + }, + "profile_stats": { + "title": "프로필 통계", + "description": "{stats_table}" + }, + "create_profile": { + "title": "새 프로필 만들기", + "description": "수동으로 또는 이전 사이클에서 새 프로필을 만듭니다.\n\n프로필 이름 예: '섬세한 의류', '고강도', '빠른 세탁'", + "data": { + "profile_name": "프로필 이름", + "reference_cycle": "참조 사이클 (선택사항)", + "manual_duration": "수동 기간 (분)" + } + }, + "edit_profile": { + "title": "프로필 편집", + "description": "이름을 바꿀 프로필을 선택하세요", + "data": { + "profile": "프로필" + } + }, + "rename_profile": { + "title": "프로필 세부 정보 편집", + "description": "현재 프로필: {current_name}", + "data": { + "new_name": "프로필 이름", + "manual_duration": "수동 기간 (분)" + } + }, + "delete_profile_select": { + "title": "프로필 삭제", + "description": "삭제할 프로필을 선택하세요", + "data": { + "profile": "프로필" + } + }, + "delete_profile_confirm": { + "title": "프로필 삭제 확인", + "description": "⚠️ 다음 프로필이 영구적으로 삭제됩니다: {profile_name}", + "data": { + "unlabel_cycles": "이 프로필을 사용하는 사이클에서 라벨 제거" + } + }, + "auto_label_cycles": { + "title": "기존 사이클 자동 라벨 지정", + "description": "총 {total_count}주기를 찾았습니다. 프로필: {profiles}", + "data": { + "confidence_threshold": "최소 신뢰도 (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "라벨 지정할 사이클 선택", + "description": "✓ = 완료됨, ⚠ = 재개됨, ✗ = 중단됨", + "data": { + "cycle_id": "사이클" + } + }, + "select_cycle_to_delete": { + "title": "사이클 삭제", + "description": "⚠️ 선택한 사이클이 영구적으로 삭제됩니다", + "data": { + "cycle_id": "삭제할 사이클" + } + }, + "label_cycle": { + "title": "사이클 라벨 지정", + "description": "{cycle_info}", + "data": { + "profile_name": "프로필", + "new_profile_name": "새 프로필 이름 (생성하는 경우)" + } + }, + "post_process": { + "title": "기록 처리 (병합/분할)", + "description": "조각난 실행을 병합하고 기간 내에서 잘못 병합된 주기를 분할하여 주기 기록을 정리합니다. 되돌아볼 시간을 입력하세요(모두 999999 사용).", + "data": { + "time_range": "검색 기간 (시간)", + "gap_seconds": "병합/분할 간격 (초)" + }, + "data_description": { + "time_range": "분할된 주기를 병합하기 위해 검색하는 데 걸리는 시간입니다. 모든 과거 데이터를 처리하려면 999999를 사용하세요.", + "gap_seconds": "분할과 병합을 결정하는 임계값입니다. 이보다 짧은 간격은 병합됩니다. 이보다 긴 간격은 분할됩니다. 잘못 병합된 사이클을 강제로 분할하려면 이 값을 낮추세요." + } + }, + "cleanup_profile": { + "title": "기록 정리 - 프로필 선택", + "description": "시각화할 프로필을 선택하세요. 이상값을 식별하는 데 도움이 되도록 이 프로필의 모든 이전 사이클을 보여주는 그래프가 생성됩니다.", + "data": { + "profile": "프로필" + } + }, + "cleanup_select": { + "title": "기록 정리 - 그래프 및 삭제", + "description": "![그래프]({graph_url})\n\n**{profile_name} 시각화 중**\n\n그래프는 개별 사이클을 색상 선으로 표시합니다. 이상값(그룹에서 멀리 떨어진 선)을 식별하고 아래 목록에서 해당 색상을 찾아 삭제하세요.\n\n**영구적으로 삭제할 사이클을 선택하세요:**", + "data": { + "cycles_to_delete": "삭제할 사이클 (일치하는 색상 확인)" + } + }, + "interactive_editor": { + "title": "병합/분할 대화형 편집기", + "description": "사이클 기록에서 수행할 수동 작업을 선택합니다.", + "menu_options": { + "editor_split": "사이클 분할 (간격 찾기)", + "editor_merge": "사이클 병합 (조각 결합)", + "editor_delete": "사이클 삭제", + "menu_back": "← 뒤로" + } + }, + "editor_select": { + "title": "사이클 선택", + "description": "처리할 사이클을 선택하세요.\n\n{info_text}", + "data": { + "selected_cycles": "사이클" + } + }, + "editor_configure": { + "title": "구성 및 적용", + "description": "{preview_md}", + "data": { + "confirm_action": "작업", + "merged_profile": "결과 프로필", + "new_profile_name": "새 프로필 이름", + "confirm_commit": "예, 이 작업을 확인합니다", + "segment_0_profile": "세그먼트 1 프로필", + "segment_1_profile": "세그먼트 2 프로필", + "segment_2_profile": "세그먼트 3 프로필", + "segment_3_profile": "세그먼트 4 프로필", + "segment_4_profile": "세그먼트 5 프로필", + "segment_5_profile": "세그먼트 6 프로필", + "segment_6_profile": "세그먼트 7 프로필", + "segment_7_profile": "세그먼트 8 프로필", + "segment_8_profile": "세그먼트 9 프로필", + "segment_9_profile": "세그먼트 10 프로필" + } + }, + "editor_split_params": { + "title": "분할 구성", + "description": "이 사이클을 분할하는 민감도를 조정합니다. 이보다 긴 유휴 간격이 있으면 분할이 발생합니다.", + "data": { + "split_mode": "분할 방식" + } + }, + "editor_split_auto_params": { + "title": "자동 분할 구성", + "description": "이 사이클을 분할하는 민감도를 조정합니다. 이보다 긴 유휴 간격은 분할을 발생시킵니다.", + "data": { + "min_gap_seconds": "분할 간격 임계값(초)" + } + }, + "editor_split_manual_params": { + "title": "수동 분할 타임스탬프", + "description": "{preview_md}사이클 창: **{cycle_start_wallclock} → {cycle_end_wallclock}** 날짜 {cycle_date}.\n\n하나 이상의 분할 타임스탬프(`HH:MM` 또는 `HH:MM:SS`)를 한 줄에 하나씩 입력하세요. 각 타임스탬프에서 사이클이 잘립니다 — 타임스탬프 N개는 N+1개 구간을 만듭니다.", + "data": { + "split_timestamps": "분할 타임스탬프" + } + }, + "reprocess_history": { + "title": "유지보수: 데이터 재처리 및 최적화", + "description": "기록 데이터를 심층 정리합니다:\n1. 제로 전력 수치(무음)를 자릅니다.\n2. 사이클 타이밍/기간을 수정합니다.\n3. 모든 통계 모델(엔벨로프)을 다시 계산합니다.\n\n**데이터 문제를 해결하거나 새로운 알고리즘을 적용하려면 이 작업을 실행하세요.**" + }, + "wipe_history": { + "title": "기록 삭제 (테스트 전용)", + "description": "⚠️ 이 장치에 저장된 모든 사이클과 프로필이 영구적으로 삭제됩니다. 이 작업은 취소할 수 없습니다!" + }, + "export_import": { + "title": "JSON 내보내기/가져오기", + "description": "이 장치의 사이클, 프로필 및 설정을 복사/붙여넣기하세요. 아래에서 내보내거나 JSON을 붙여넣어 가져오세요.", + "data": { + "mode": "작업", + "json_payload": "전체 구성 JSON" + }, + "data_description": { + "mode": "현재 데이터 및 설정을 복사하려면 내보내기를, 다른 WashData 장치에서 내보낸 데이터를 붙여넣으려면 가져오기를 선택하세요.", + "json_payload": "내보내기: 이 JSON을 복사하세요(사이클, 프로필 및 모든 세부 조정 설정 포함). 가져오기: WashData에서 내보낸 JSON을 붙여넣으세요." + } + }, + "record_cycle": { + "title": "사이클 기록", + "description": "간섭 없이 깔끔한 프로필을 생성하기 위해 사이클을 수동으로 기록합니다.\n\n상태: **{status}**\n기간: {duration}초\n샘플: {samples}", + "menu_options": { + "record_refresh": "상태 새로 고침", + "record_stop": "기록 중지 (저장 및 처리)", + "record_start": "새 기록 시작", + "record_process": "마지막 기록 처리 (트림 및 저장)", + "record_discard": "마지막 기록 취소", + "menu_back": "← 뒤로" + } + }, + "record_process": { + "title": "기록 처리", + "description": "![그래프]({graph_url})\n\n**그래프는 원시 기록을 보여줍니다.** 파란색 = 유지, 빨간색 = 제안된 트림.\n*그래프는 정적이며 양식 변경 시 업데이트되지 않습니다.*\n\n기록이 중지되었습니다. 트림은 감지된 샘플링 속도(~{sampling_rate}초)에 맞춰 정렬됩니다.\n\n원시 기간: {duration}초\n샘플: {samples}", + "data": { + "head_trim": "트림 시작 (초)", + "tail_trim": "트림 종료 (초)", + "save_mode": "저장 대상", + "profile_name": "프로필 이름" + } + }, + "learning_feedbacks": { + "title": "학습 피드백", + "description": "대기 중인 피드백: {count}\n\n{pending_table}\n\n처리할 사이클 검토 요청을 선택하세요.", + "menu_options": { + "learning_feedbacks_pick": "대기 중인 피드백 하나 검토", + "learning_feedbacks_dismiss_all": "대기 중인 피드백 모두 닫기", + "menu_back": "← 뒤로" + } + }, + "learning_feedbacks_pick": { + "title": "검토할 피드백 선택", + "description": "열어볼 사이클 검토 요청을 선택하세요.", + "data": { + "selected_feedback": "검토할 사이클 선택" + } + }, + "learning_feedbacks_empty": { + "title": "학습 피드백", + "description": "대기 중인 피드백 요청이 없습니다.", + "menu_options": { + "menu_back": "← 뒤로" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "보류 중인 모든 피드백 해제", + "description": "보류 중인 피드백 요청 **{count}**건을 해제하려고 합니다. 해제된 사이클은 기록에 남지만 새로운 학습 신호에는 기여하지 않습니다. 이 작업은 되돌릴 수 없습니다.\n\n✅ 모두 해제하려면 아래 확인란을 선택하고 **제출**을 클릭하세요.\n❌ 체크하지 않은 상태로 두고 **제출**을 클릭하여 취소하고 돌아갑니다.", + "data": { + "confirm_dismiss_all": "확인: 대기 중인 피드백 요청을 모두 닫습니다" + } + }, + "resolve_feedback": { + "title": "피드백 해결", + "description": "{comparison_img}{candidates_table}\n**감지된 프로필**: {detected_profile} ({confidence_pct}%)\n**예상 기간**: {est_duration_min}분\n**실제 기간**: {act_duration_min}분\n\n__아래에서 작업을 선택하세요:__", + "data": { + "action": "어떻게 하시겠습니까?", + "corrected_profile": "올바른 프로그램 (수정하는 경우)", + "corrected_duration": "올바른 기간 (초) (수정하는 경우)" + } + }, + "trim_cycle_select": { + "title": "사이클 트림 - 사이클 선택", + "description": "트림할 사이클을 선택하세요. ✓ = 완료됨, ⚠ = 재개됨, ✗ = 중단됨", + "data": { + "cycle_id": "사이클" + } + }, + "trim_cycle": { + "title": "사이클 트림", + "description": "현재 창: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "작업" + } + }, + "trim_cycle_start": { + "title": "트림 시작 설정", + "description": "주기 창: {cycle_start_wallclock} → {cycle_end_wallclock}\n\n현재 시작: **{current_wallclock}** (주기 시작으로부터 +{current_offset_min}분)\n\n아래의 시계 선택기를 사용하여 새로운 시작 시간을 선택하세요.", + "data": { + "trim_start_time": "새로운 시작 시간", + "trim_start_min": "새로운 시작(사이클 시작 후 몇 분)" + } + }, + "trim_cycle_end": { + "title": "트림 종료 설정", + "description": "주기 창: {cycle_start_wallclock} → {cycle_end_wallclock}\n\n현재 종료: **{current_wallclock}** (주기 시작으로부터 +{current_offset_min}분)\n\n아래의 시계 선택기를 사용하여 새로운 종료 시간을 선택하세요.", + "data": { + "trim_end_time": "새로운 종료 시간", + "trim_end_min": "새로운 종료(사이클 시작 후 몇 분)" + } + } + }, + "error": { + "import_failed": "가져오기에 실패했습니다. 유효한 WashData 내보내기 JSON을 붙여넣으세요.", + "select_exactly_one": "이 작업에는 정확히 한 개의 사이클을 선택하세요.", + "select_at_least_one": "이 작업에는 최소 한 개의 사이클을 선택하세요.", + "select_at_least_two": "이 작업에는 최소 두 개의 사이클을 선택하세요.", + "profile_exists": "이 이름의 프로필이 이미 존재합니다", + "rename_failed": "프로필 이름을 바꾸지 못했습니다. 프로필이 존재하지 않거나 새 이름이 이미 사용 중입니다.", + "assignment_failed": "사이클에 프로필을 할당하지 못했습니다", + "duplicate_phase": "이 이름의 단계가 이미 존재합니다.", + "phase_not_found": "선택한 단계를 찾을 수 없습니다.", + "invalid_phase_name": "단계 이름은 비워둘 수 없습니다.", + "phase_name_too_long": "단계 이름이 너무 깁니다.", + "invalid_phase_range": "단계 범위가 올바르지 않습니다. 종료는 시작보다 커야 합니다.", + "invalid_phase_timestamp": "타임스탬프가 올바르지 않습니다. YYYY-MM-DD HH:MM 또는 HH:MM 형식을 사용하세요.", + "overlapping_phase_ranges": "단계 범위가 겹칩니다. 범위를 조정하고 다시 시도하세요.", + "incomplete_phase_row": "각 단계 행에는 선택한 모드에 대한 단계와 완전한 시작/종료 값이 포함되어야 합니다.", + "timestamp_mode_cycle_required": "타임스탬프 모드를 사용할 때 소스 사이클을 선택하세요.", + "no_phase_ranges": "이 작업에 사용할 수 있는 단계 범위가 아직 없습니다.", + "feedback_notification_title": "WashData: 사이클 확인 ({device})", + "feedback_notification_message": "**장치**: {device}\n**프로그램**: {program} ({confidence}% 신뢰도)\n**시간**: {time}\n\nWashData가 감지된 사이클을 확인하려면 귀하의 도움이 필요합니다.\n\n이 결과를 확인하거나 수정하려면 **설정 > 장치 및 서비스 > WashData > 구성 > 학습 피드백**으로 이동하세요.", + "suggestions_ready_notification_title": "WashData: 제안 설정 준비됨 ({device})", + "suggestions_ready_notification_message": "**제안 설정** 센서가 이제 **{count}**개의 실행 가능한 권장사항을 보고합니다.\n\n검토 및 적용하려면: **설정 > 장치 및 서비스 > WashData > 구성 > 고급 설정 > 제안 값 적용**.\n\n제안사항은 선택사항이며 저장하기 전에 검토용으로 표시됩니다.", + "trim_range_invalid": "시작은 종료 이전이어야 합니다. 트림 포인트를 조정해 주세요.", + "trim_failed": "트림에 실패했습니다. 사이클이 더 이상 존재하지 않거나 창이 비어 있습니다.", + "invalid_split_timestamp": "타임스탬프가 올바르지 않거나 사이클 범위를 벗어났습니다. 사이클 범위 안의 HH:MM 또는 HH:MM:SS 형식을 사용하세요.", + "no_split_segments_found": "이 타임스탬프로는 유효한 구간을 만들 수 없습니다. 각 타임스탬프가 사이클 범위 안에 있고, 각 구간의 길이가 최소 1분인지 확인하세요.", + "auto_tune_suggestion": "{device_type} {device_title}이(가) 고스트 사이클을 감지했습니다. 권장되는 min_power 변경: {current_min}W -> {new_min}W(자동으로 적용되지 않음).", + "auto_tune_title": "WashData 자동 조정", + "auto_tune_fallback": "{device_type} {device_title}이(가) 고스트 사이클을 감지했습니다. 권장되는 min_power 변경: {current_min}W -> {new_min}W(자동으로 적용되지 않음)." + }, + "abort": { + "no_cycles_found": "사이클을 찾을 수 없습니다", + "no_split_segments_found": "선택한 사이클에서 분할 가능한 구간을 찾지 못했습니다.", + "cycle_not_found": "선택한 사이클을 불러올 수 없습니다.", + "no_power_data": "선택한 사이클에 사용할 수 있는 전력 추적 데이터가 없습니다.", + "no_profiles_found": "프로필을 찾을 수 없습니다. 먼저 프로필을 만드세요.", + "no_profiles_for_matching": "일치시킬 프로필이 없습니다. 먼저 프로필을 만드세요.", + "no_unlabeled_cycles": "모든 사이클에 이미 라벨이 지정되어 있습니다", + "no_suggestions": "아직 제안된 값이 없습니다. 몇 가지 사이클을 실행한 후 다시 시도하세요.", + "no_predictions": "예측 없음", + "no_custom_phases": "사용자 정의 단계를 찾을 수 없습니다.", + "reprocess_success": "{count}개의 사이클을 성공적으로 재처리했습니다.", + "debug_data_cleared": "{count}개의 사이클에서 디버그 데이터를 성공적으로 지웠습니다." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "사이클 시작", + "cycle_finish": "사이클 완료", + "cycle_live": "실시간 진행" + } + }, + "common_text": { + "options": { + "unlabeled": "(라벨 없음)", + "create_new_profile": "새 프로필 만들기", + "remove_label": "라벨 제거", + "no_reference_cycle": "(참조 사이클 없음)", + "all_device_types": "모든 장치 유형", + "current_suffix": "(현재)", + "editor_select_info": "분할할 사이클 1개 또는 병합할 사이클 2개 이상을 선택하세요.", + "editor_select_info_split": "분할할 사이클을 1개 선택하세요.", + "editor_select_info_merge": "병합할 사이클을 2개 이상 선택하세요.", + "editor_select_info_delete": "삭제할 사이클을 1개 이상 선택하세요.", + "no_cycles_recorded": "아직 기록된 사이클이 없습니다.", + "profile_summary_line": "- **{name}**: {count}회 사이클, 평균 {avg}분", + "no_profiles_created": "아직 생성된 프로필이 없습니다.", + "phase_preview": "단계 미리보기", + "phase_preview_no_curve": "평균 프로필 곡선을 아직 사용할 수 없습니다. 이 프로필에 대한 사이클을 더 실행/라벨 지정하세요.", + "average_power_curve": "평균 전력 곡선", + "unit_min": "분", + "profile_option_fmt": "{name} ({count}회 사이클, ~{duration}분 평균)", + "profile_option_short_fmt": "{name} ({count}회 사이클, ~{duration}분)", + "deleted_cycles_from_profile": "{profile}에서 {count}개의 사이클을 삭제했습니다.", + "not_enough_data_graph": "그래프를 생성하기에 데이터가 충분하지 않습니다.", + "no_profiles_found": "프로필을 찾을 수 없습니다.", + "cycle_info_fmt": "사이클: {start}, {duration}분, 현재: {label}", + "top_candidates_header": "### 상위 후보", + "tbl_profile": "프로필", + "tbl_confidence": "신뢰도", + "tbl_correlation": "상관관계", + "tbl_duration_match": "기간 일치", + "detected_profile": "감지된 프로필", + "estimated_duration": "예상 기간", + "actual_duration": "실제 기간", + "choose_action": "__아래에서 작업을 선택하세요:__", + "tbl_count": "횟수", + "tbl_avg": "평균", + "tbl_min": "최소", + "tbl_max": "최대", + "tbl_energy_avg": "에너지 (평균)", + "tbl_energy_total": "에너지 (합계)", + "tbl_consistency": "일관성", + "tbl_last_run": "마지막 실행", + "graph_legend_title": "그래프 범례", + "graph_legend_body": "파란색 밴드는 관찰된 최소 및 최대 전력 소모 범위를 나타냅니다. 선은 평균 전력 곡선을 보여줍니다.", + "recording_preview": "기록 미리보기", + "trim_start": "트림 시작", + "trim_end": "트림 종료", + "split_preview_title": "분할 미리보기", + "split_preview_found_fmt": "{count}개의 세그먼트를 찾았습니다.", + "split_preview_confirm_fmt": "확인을 클릭하여 이 사이클을 {count}개의 개별 사이클로 분할합니다.", + "merge_preview_title": "병합 미리보기", + "merge_preview_joining_fmt": "{count}개의 사이클을 병합합니다. 간격은 0W 수치로 채워집니다.", + "editor_delete_preview_title": "삭제 미리보기", + "editor_delete_preview_intro": "선택한 사이클은 영구적으로 삭제됩니다:", + "editor_delete_preview_confirm": "이 사이클 기록을 영구적으로 삭제하려면 확인을 클릭하세요.", + "no_power_preview": "*미리보기에 사용할 수 있는 전력 데이터가 없습니다.*", + "profile_comparison": "프로필 비교", + "actual_cycle_label": "이 사이클 (실제)", + "storage_usage_header": "저장 공간 사용량", + "storage_file_size": "파일 크기", + "storage_cycles": "사이클", + "storage_profiles": "프로필", + "storage_debug_traces": "디버그 추적", + "overview_suffix": "개요", + "phase_preview_for_profile": "프로필의 단계 미리보기", + "current_ranges_header": "현재 범위", + "assign_phases_help": "범위를 추가, 편집, 삭제 또는 저장하려면 아래 작업을 사용하세요.", + "profile_summary_header": "프로필 요약", + "recent_cycles_header": "최근 사이클", + "trim_cycle_preview_title": "사이클 트림 미리보기", + "trim_cycle_preview_no_data": "이 사이클에 사용할 수 있는 전력 데이터가 없습니다.", + "trim_cycle_preview_kept_suffix": "유지됨", + "table_program": "프로그램", + "table_when": "시점", + "table_length": "길이", + "table_match": "일치", + "table_profile": "프로필", + "table_cycles": "사이클", + "table_avg_length": "평균 길이", + "table_last_run": "마지막 실행", + "table_avg_energy": "평균 에너지", + "table_detected_program": "감지된 프로그램", + "table_confidence": "신뢰도", + "table_reported": "보고됨", + "phase_builtin_suffix": "(내장)", + "phase_none_available": "사용 가능한 단계가 없습니다.", + "settings_deprecation_warning": "⚠️ **더 이상 사용되지 않는 장치 유형:** {device_type}은(는) 향후 릴리스에서 제거될 예정입니다. WashData의 일치 파이프라인은 이 어플라이언스 클래스에 대해 신뢰할 수 있는 결과를 생성하지 않습니다. 통합은 지원 중단 기간 동안 계속 작동합니다. 이 경고를 끄려면 아래의 **장치 유형**을 지원되는 유형(세탁기, 건조기, 세탁기-건조기 콤보, 식기세척기, 에어프라이어, 제빵기 또는 펌프) 중 하나로 전환하거나 가전제품이 지원되는 유형과 일치하지 않는 경우 **기타(고급)**로 전환하세요. **기타(고급)**은 특정 어플라이언스에 맞게 조정되지 않은 의도적으로 일반 기본값을 제공하므로 임계값, 시간 초과 및 일치 매개변수를 직접 구성해야 합니다. 전환하면 모든 기존 설정이 유지됩니다.", + "phase_other_device_types": "기타 장치 유형:" + } + }, + "interactive_editor_action": { + "options": { + "split": "사이클 분할 (간격 찾기)", + "merge": "사이클 병합 (조각 결합)", + "delete": "사이클 삭제" + } + }, + "split_mode": { + "options": { + "auto": "유휴 간격 자동 감지", + "manual": "수동 타임스탬프" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "유지보수: 데이터 재처리 및 최적화", + "clear_debug_data": "디버그 데이터 지우기 (공간 확보)", + "wipe_history": "이 장치의 모든 데이터 삭제 (되돌릴 수 없음)", + "export_import": "설정 포함 JSON 내보내기/가져오기 (복사/붙여넣기)" + } + }, + "export_import_mode": { + "options": { + "export": "모든 데이터 내보내기", + "import": "데이터 가져오기/병합" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "기존 사이클 자동 라벨 지정", + "select_cycle_to_label": "특정 사이클 라벨 지정", + "select_cycle_to_delete": "사이클 삭제", + "interactive_editor": "병합/분할 대화형 편집기", + "trim_cycle": "사이클 데이터 트림" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "새 프로필 만들기", + "edit_profile": "프로필 편집/이름 바꾸기", + "delete_profile": "프로필 삭제", + "profile_stats": "프로필 통계", + "cleanup_profile": "기록 정리 - 그래프 및 삭제", + "assign_phases": "단계 범위 할당" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "새 단계 만들기", + "edit_custom_phase": "단계 편집", + "delete_custom_phase": "단계 삭제" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "단계 범위 추가", + "edit_range": "단계 범위 편집", + "delete_range": "단계 범위 삭제", + "clear_ranges": "모든 범위 지우기", + "auto_detect_ranges": "단계 자동 감지", + "save_ranges": "저장하고 돌아가기" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "상태 새로 고침", + "stop_recording": "기록 중지 (저장 및 처리)", + "start_recording": "새 기록 시작", + "process_recording": "마지막 기록 처리 (트림 및 저장)", + "discard_recording": "마지막 기록 취소" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "새 프로필 만들기", + "existing_profile": "기존 프로필에 추가", + "discard": "기록 취소" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "확인 - 올바른 감지", + "correct": "수정 - 프로그램/기간 선택", + "ignore": "무시 - 오탐/노이즈 사이클", + "delete": "삭제 - 기록에서 제거" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "단계 이름 지정 및 적용", + "cancel": "변경사항 없이 돌아가기" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "시작점 설정", + "set_end": "종료점 설정", + "reset": "전체 기간으로 초기화", + "apply": "트림 적용", + "cancel": "취소" + } + }, + "device_type": { + "options": { + "washing_machine": "세탁기", + "dryer": "건조기", + "washer_dryer": "세탁기-건조기 콤보", + "dishwasher": "식기세척기", + "coffee_machine": "커피 머신(더 이상 사용되지 않음)", + "ev": "전기 자동차(더 이상 사용되지 않음)", + "air_fryer": "에어프라이어", + "heat_pump": "열 펌프(더 이상 사용되지 않음)", + "bread_maker": "빵 제조기", + "pump": "펌프 / 배수 펌프", + "oven": "오븐(지원 중단됨)", + "other": "기타(고급)" + } + } + }, + "services": { + "label_cycle": { + "name": "사이클 라벨 지정", + "description": "이전 사이클에 기존 프로필을 할당하거나 라벨을 제거합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "라벨을 지정할 WashData 장치입니다." + }, + "cycle_id": { + "name": "사이클 ID", + "description": "라벨을 지정할 사이클의 ID입니다." + }, + "profile_name": { + "name": "프로필 이름", + "description": "기존 프로필의 이름입니다(프로필 관리 메뉴에서 프로필 생성). 라벨을 제거하려면 비워두세요." + } + } + }, + "create_profile": { + "name": "프로필 만들기", + "description": "새 프로필을 만듭니다(독립형 또는 참조 사이클 기반).", + "fields": { + "device_id": { + "name": "장치", + "description": "WashData 장치입니다." + }, + "profile_name": { + "name": "프로필 이름", + "description": "새 프로필의 이름입니다(예: 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "참조 사이클 ID", + "description": "이 프로필의 기반이 될 선택적 사이클 ID입니다." + } + } + }, + "delete_profile": { + "name": "프로필 삭제", + "description": "프로필을 삭제하고 선택적으로 이를 사용하는 사이클의 라벨을 해제합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "WashData 장치입니다." + }, + "profile_name": { + "name": "프로필 이름", + "description": "삭제할 프로필입니다." + }, + "unlabel_cycles": { + "name": "사이클 라벨 해제", + "description": "이 프로필을 사용하는 사이클에서 프로필 라벨을 제거합니다." + } + } + }, + "auto_label_cycles": { + "name": "기존 사이클 자동 라벨 지정", + "description": "프로필 일치를 사용하여 라벨 없는 사이클에 소급하여 라벨을 지정합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "WashData 장치입니다." + }, + "confidence_threshold": { + "name": "신뢰도 임계값", + "description": "라벨을 적용하기 위한 최소 일치 신뢰도(0.50-0.95)입니다." + } + } + }, + "export_config": { + "name": "구성 내보내기", + "description": "이 장치의 프로필, 사이클 및 설정을 JSON 파일로 내보냅니다(장치별).", + "fields": { + "device_id": { + "name": "장치", + "description": "내보낼 WashData 장치입니다." + }, + "path": { + "name": "경로", + "description": "쓸 선택적 절대 파일 경로입니다(기본값: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "구성 가져오기", + "description": "JSON 내보내기 파일에서 이 장치의 프로필, 사이클 및 설정을 가져옵니다(설정이 덮어써질 수 있음).", + "fields": { + "device_id": { + "name": "장치", + "description": "가져올 WashData 장치입니다." + }, + "path": { + "name": "경로", + "description": "가져올 내보내기 JSON 파일의 절대 경로입니다." + } + } + }, + "submit_cycle_feedback": { + "name": "사이클 피드백 제출", + "description": "사이클이 완료된 후 자동 감지된 프로그램을 확인하거나 수정합니다. `entry_id`(고급) 또는 `device_id`(권장)를 제공하세요.", + "fields": { + "device_id": { + "name": "장치", + "description": "WashData 장치입니다(entry_id 대신 권장)." + }, + "entry_id": { + "name": "항목 ID", + "description": "장치의 구성 항목 ID입니다(device_id의 대안)." + }, + "cycle_id": { + "name": "사이클 ID", + "description": "피드백 알림/로그에 표시된 사이클 ID입니다." + }, + "user_confirmed": { + "name": "감지된 프로그램 확인", + "description": "감지된 프로그램이 맞으면 true로 설정하세요." + }, + "corrected_profile": { + "name": "수정된 프로필", + "description": "확인되지 않은 경우 올바른 프로필/프로그램 이름을 제공하세요." + }, + "corrected_duration": { + "name": "수정된 기간 (초)", + "description": "선택적 수정 기간(초)입니다." + }, + "notes": { + "name": "메모", + "description": "이 사이클에 대한 선택적 메모입니다." + } + } + }, + "record_start": { + "name": "사이클 기록 시작", + "description": "클린 사이클을 수동으로 기록하기 시작합니다(모든 일치 로직을 우회합니다).", + "fields": { + "device_id": { + "name": "장치", + "description": "기록할 WashData 장치입니다." + } + } + }, + "record_stop": { + "name": "사이클 기록 중지", + "description": "수동 기록을 중지합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "기록을 중지할 WashData 장치입니다." + } + } + }, + "trim_cycle": { + "name": "사이클 트림", + "description": "이전 사이클의 전력 데이터를 특정 시간 창으로 트림합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "WashData 장치입니다." + }, + "cycle_id": { + "name": "사이클 ID", + "description": "트림할 사이클의 ID입니다." + }, + "trim_start_s": { + "name": "트림 시작 (초)", + "description": "이 오프셋(초)부터 데이터를 유지합니다. 기본값 0." + }, + "trim_end_s": { + "name": "트림 종료 (초)", + "description": "이 오프셋(초)까지 데이터를 유지합니다. 기본값 = 전체 기간." + } + } + }, + "pause_cycle": { + "name": "일시정지 주기", + "description": "WashData 장치의 활성 주기를 일시 중지합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "일시중지할 WashData 장치입니다." + } + } + }, + "resume_cycle": { + "name": "재개주기", + "description": "WashData 장치에 대해 일시 중지된 주기를 재개합니다.", + "fields": { + "device_id": { + "name": "장치", + "description": "재개할 WashData 장치입니다." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "실행 중" + }, + "match_ambiguity": { + "name": "일치 모호도" + } + }, + "sensor": { + "washer_state": { + "name": "상태", + "state": { + "off": "꺼짐", + "idle": "대기 중", + "starting": "시작 중", + "running": "실행 중", + "paused": "일시 정지됨", + "user_paused": "사용자가 일시 정지함", + "ending": "종료 중", + "finished": "완료됨", + "anti_wrinkle": "주름 방지", + "delay_wait": "시작을 기다리는 중", + "interrupted": "중단됨", + "force_stopped": "강제 정지됨", + "rinse": "헹굼", + "unknown": "알 수 없음", + "clean": "깨끗한" + } + }, + "washer_program": { + "name": "프로그램" + }, + "time_remaining": { + "name": "남은 시간" + }, + "total_duration": { + "name": "총 기간" + }, + "cycle_progress": { + "name": "진행 상황" + }, + "current_power": { + "name": "현재 전력" + }, + "elapsed_time": { + "name": "경과 시간" + }, + "debug_info": { + "name": "디버그 정보" + }, + "match_confidence": { + "name": "일치 신뢰도" + }, + "top_candidates": { + "name": "상위 후보", + "state": { + "none": "없음" + } + }, + "current_phase": { + "name": "현재 단계" + }, + "wash_phase": { + "name": "단계" + }, + "profile_cycle_count": { + "name": "프로필 {profile_name} 횟수", + "unit_of_measurement": "사이클" + }, + "suggestions": { + "name": "이용 가능한 제안 설정" + }, + "pump_runs_today": { + "name": "펌프 작동(지난 24시간)" + }, + "cycle_count": { + "name": "사이클 수" + } + }, + "select": { + "program_select": { + "name": "사이클 프로그램", + "state": { + "auto_detect": "자동 감지" + } + } + }, + "button": { + "force_end_cycle": { + "name": "사이클 강제 종료" + }, + "pause_cycle": { + "name": "일시정지 주기" + }, + "resume_cycle": { + "name": "재개주기" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "유효한 device_id가 필요합니다." + }, + "cycle_id_required": { + "message": "유효한 Cycle_id가 필요합니다." + }, + "profile_name_required": { + "message": "유효한 profile_name이 필요합니다." + }, + "device_not_found": { + "message": "장치를 찾을 수 없습니다." + }, + "no_config_entry": { + "message": "장치에 대한 구성 항목을 찾을 수 없습니다." + }, + "integration_not_loaded": { + "message": "이 장치에 대한 통합이 로드되지 않았습니다." + }, + "cycle_not_found_or_no_power": { + "message": "사이클을 찾을 수 없거나 전력 데이터가 없습니다." + }, + "trim_failed_empty_window": { + "message": "트림 실패 - 사이클을 찾을 수 없거나 전력 데이터가 없거나 결과 창이 비어 있습니다." + }, + "trim_invalid_range": { + "message": "trim_end_s는 trim_start_s보다 커야 합니다." + } + } +} diff --git a/custom_components/ha_washdata/translations/lb.json b/custom_components/ha_washdata/translations/lb.json new file mode 100644 index 0000000..483d9a1 --- /dev/null +++ b/custom_components/ha_washdata/translations/lb.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Astellung", + "description": "Konfiguréiert Är Wäschmaschinn oder aneren Apparat.\n\nPower Sensor ass néideg.\n\n**Nächst gëtt Dir gefrot ob Dir Äre éischte Profil wëllt erstellen.**", + "data": { + "name": "Apparat Numm", + "device_type": "Apparat Typ", + "power_sensor": "Power Sensor", + "min_power": "Minimum Power Schwell (W)" + }, + "data_description": { + "name": "E frëndlechen Numm fir dësen Apparat (zB 'Wäschmaschinn', 'Spullmaschinn').", + "device_type": "Wéi eng Zort Apparat ass dëst? Hëlleft d'Erkennung an d'Etikettéieren unzepassen.", + "power_sensor": "D'Sensorentitéit déi Echtzäit Stroumverbrauch (a Watt) vun Ärem Smart Plug bericht.", + "min_power": "Power Liesungen iwwer dës Schwell (a Watt) weisen datt den Apparat leeft. Start mat 2W fir déi meescht Apparater." + } + }, + "first_profile": { + "title": "Éischte Profil erstellen", + "description": "E **Zyklus** ass e komplette Laf vun Ärem Apparat (z.B. eng Laascht Wäsch). E **Profil** gruppéiert dës Zyklen no Typ (z.B. 'Cotton', 'Quick Wash'). E Profil elo erstellen hëlleft der Integratioun direkt d'Dauer ze schätzen.\n\nDir kënnt dëse Profil manuell an den Apparat Kontrollen auswielen wann en net automatesch erkannt gëtt.\n\n**Loosst 'Profilnumm' eidel fir dëse Schrëtt iwwerzespringen.**", + "data": { + "profile_name": "Profilnumm (fakultativ)", + "manual_duration": "Geschätzte Dauer (Minuten)" + } + } + }, + "error": { + "cannot_connect": "Konnt net konnektéieren", + "invalid_auth": "Ongülteg Authentifikatioun", + "unknown": "Onerwaarte Feeler" + }, + "abort": { + "already_configured": "Den Apparat ass scho konfiguréiert" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Léieren Feedbacks iwwerpréiwen", + "settings": "Astellungen", + "notifications": "Notifikatiounen", + "manage_cycles": "Zyklen verwalten", + "manage_profiles": "Profiler verwalten", + "manage_phase_catalog": "Phase Katalog verwalten", + "record_cycle": "Zyklus ophuelen (manuell)", + "diagnostics": "Diagnostik & Ënnerhalt" + } + }, + "apply_suggestions": { + "title": "Proposéiert Wäerter kopéieren", + "description": "Dëst kopéiert déi aktuell proposéiert Wäerter an Är gespäichert Astellungen. Dëst ass eng eemoleg Aktioun an aktivéiert keng automatesch Iwwerschreiwungen.\n\nProposéiert Wäerter fir ze kopéieren:\n{suggested}", + "data": { + "confirm": "Kopieraktioun bestätegen" + } + }, + "apply_suggestions_confirm": { + "title": "Proposéiert Ännerungen iwwerpréiwen", + "description": "WashData fonnt **{pending_count}** proposéiert Wäert(e) fir ze bewerben.\n\nÄnnerungen:\n{changes}\n\n✅ Kuckt d'Këscht hei drënner a klickt op **Send** fir dës Ännerungen direkt z'applizéieren an ze späicheren.\n❌ Loosst et net ausgezeechent a klickt op **Soumissioun** fir ze annuléieren an zréck ze goen.", + "data": { + "confirm_apply_suggestions": "Gëlle a späicheren dës Ännerungen" + } + }, + "settings": { + "title": "Astellungen", + "description": "{deprecation_warning}Tune Detektiounsschwellen, Léieren a fortgeschratt Verhalen. Standardwäerter funktionnéieren gutt fir déi meescht Setups.\n\nProposéiert Astellungen aktuell verfügbar: {suggestions_count}.\nWann dëst iwwer 0 ass, maacht Advanced Astellungen op a benotzt 'Proposéiert Wäerter uwenden' fir Recommandatiounen z'iwwerpréiwen.", + "data": { + "apply_suggestions": "Proposéiert Wäerter uwenden", + "device_type": "Apparat Typ", + "power_sensor": "Power Sensor Entitéit", + "min_power": "Minimum Muecht (W)", + "off_delay": "Zyklus Enn Verzögerung (s)", + "notify_actions": "Notifikatioun Aktiounen", + "notify_people": "Liwwerung verzögeren bis dës Leit doheem sinn", + "notify_only_when_home": "Notifikatiounen verzögeren bis déi gewielte Persoun doheem ass", + "notify_fire_events": "Automatisatioun Eventer ausléisen", + "notify_start_services": "Cycle Start - Notifikatiounsziler", + "notify_finish_services": "Cycle Finish - Notifikatiounsziler", + "notify_live_services": "Live Progress - Notifikatiounsziler", + "notify_before_end_minutes": "Virof-Notifikatioun (Minutten virum Enn)", + "notify_title": "Notifikatioun Titel", + "notify_icon": "Notifikatioun Ikon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Fäerdeg Message Format", + "notify_pre_complete_message": "Virof-Ofschloss Message Format", + "show_advanced": "Advanced Astellungen änneren (nächste Schrëtt)" + }, + "data_description": { + "apply_suggestions": "Kuckt dës Këscht fir Wäerter ze kopéieren, déi vum Léieralgorithmus an d'Form proposéiert ginn. Iwwerpréift ier Dir späichert.\n\nProposéiert (vum Léieren):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Wielt den Typ vum Apparat (Wäschmaschinn, Trockner, Spullmaschinn, Kaffismaschinn, EV). Dëse Tag gëtt mat all Zyklus gespäichert fir eng besser Organisatioun.", + "power_sensor": "D'Sensor Entitéit bericht Echtzäit Kraaft (a Watt). Dir kënnt dëst zu all Moment änneren ouni historesch Daten ze verléieren - nëtzlech wann Dir e Smart Plug ersetzt.", + "min_power": "Power Liesungen iwwer dës Schwell (a Watt) weisen datt den Apparat leeft. Start mat 2W fir déi meescht Apparater.", + "off_delay": "Sekonnen muss déi glat Kraaft ënner der Stoppschwell bleiwen fir d'Fäerdegstellung ze markéieren. Standard: 120s.", + "notify_actions": "Fakultativ Home Assistant Aktiounssequenz fir Notifikatiounen ze lafen (Skripte, Notify, TTS, etc.). Wann agestallt, lafen Aktiounen virum Fallback Notify Service.", + "notify_people": "Leit Entitéite benotzt fir Präsenz-Gating. Wann aktivéiert, gëtt d'Notifikatiounsliwwerung verspéit bis op d'mannst eng gewielte Persoun doheem ass.", + "notify_only_when_home": "Wann aktivéiert, Notifikatiounen verzögeren bis op d'mannst eng gewielte Persoun doheem ass.", + "notify_fire_events": "Wann aktivéiert, Home Assistant Eventer fir Zyklusstart an Enn ausléisen (ha_washdata_cycle_started an ha_washdata_cycle_ended) fir Automatisatiounen ze féieren.", + "notify_start_services": "Een oder méi Notifikatiounsservicer fir ze alarméieren wann en Zyklus ufänkt. Loosst eidel fir Start Notifikatiounen ze sprangen.", + "notify_finish_services": "Een oder méi Notifikatiounsservicer fir ze alarméieren wann en Zyklus fäerdeg ass oder bal fäerdeg ass. Loosst eidel fir fäerdeg Notifikatiounen ze sprangen.", + "notify_live_services": "Een oder méi Notifikatiounsservicer fir Live Fortschrëtterupdates ze kréien wärend den Zyklus leeft (nëmmen mobil Begleeder App). Loosst eidel fir Live Updates ze sprangen.", + "notify_before_end_minutes": "Minutten virum geschätzte Enn vum Zyklus fir eng Notifikatioun auszeléisen. Op 0 setzen fir auszeschalten. Standard: 0.", + "notify_title": "Benotzerdefinéierte Titel fir Notifikatiounen. Ënnerstëtzt {device} Plazhalter.", + "notify_icon": "Ikon fir Notifikatiounen ze benotzen (zB mdi:washing-machine). Eidel loossen fir ouni Ikon ze schécken.", + "notify_start_message": "Message geschéckt wann en Zyklus ufänkt. Ënnerstëtzt {device} Plazhalter.", + "notify_finish_message": "Message geschéckt wann en Zyklus fäerdeg ass. Ënnerstëtzt {device}, {duration}, {program}, {energy_kwh}, {cost} Plazhalter.", + "notify_pre_complete_message": "Text vun de widderhuelende Live Fortschrëtterupdates wärend den Zyklus leeft. Ënnerstëtzt {device}, {minutes}, {program} Plazhalter." + } + }, + "notifications": { + "title": "Notifikatiounen", + "description": "Konfiguréiert Notifikatiounskanäl, Templates an fakultativ Live Mobile Fortschrëtterupdates.", + "data": { + "notify_actions": "Notifikatioun Aktiounen", + "notify_people": "Liwwerung verzögeren bis dës Leit doheem sinn", + "notify_only_when_home": "Notifikatiounen verzögeren bis déi gewielte Persoun doheem ass", + "notify_fire_events": "Automatisatioun Eventer ausléisen", + "notify_start_services": "Cycle Start - Notifikatiounsziler", + "notify_finish_services": "Cycle Finish - Notifikatiounsziler", + "notify_live_services": "Live Progress - Notifikatiounsziler", + "notify_before_end_minutes": "Virof-Notifikatioun (Minutten virum Enn)", + "notify_live_interval_seconds": "Live Update Intervall (Sekonnen)", + "notify_live_overrun_percent": "Live Update Iwwerlaf Toleranz (%)", + "notify_live_chronometer": "Chronometer Countdown Timer", + "notify_title": "Notifikatioun Titel", + "notify_icon": "Notifikatioun Ikon", + "notify_start_message": "Start Message Format", + "notify_finish_message": "Fäerdeg Message Format", + "notify_pre_complete_message": "Virof-Ofschloss Message Format", + "energy_price_entity": "Energiepräis - Entitéit (optional)", + "energy_price_static": "Energiepräis - Statesche Wäert (optional)", + "go_back": "Zréckgoen ouni ze späicheren", + "notify_reminder_message": "Erënnerung Message Format", + "notify_timeout_seconds": "Auto Entlooss No (Sekonnen)", + "notify_channel": "Notifikatiounskanal (Status / Live / Erënnerung)", + "notify_finish_channel": "Fäerdeg Notifikatioun Channel" + }, + "data_description": { + "go_back": "Hakt dëst un a klickt op Ofschécken, fir all Ännerungen hei uewen ze verwerfen an an den Haaptmenu zréckzegoen.", + "notify_actions": "Fakultativ Home Assistant Aktiounssequenz fir Notifikatiounen ze lafen (Skripte, Notify, TTS, etc.). Wann agestallt, lafen Aktiounen virum Fallback Notify Service.", + "notify_people": "Leit Entitéite benotzt fir Präsenz-Gating. Wann aktivéiert, gëtt d'Notifikatiounsliwwerung verspéit bis op d'mannst eng gewielte Persoun doheem ass.", + "notify_only_when_home": "Wann aktivéiert, Notifikatiounen verzögeren bis op d'mannst eng gewielte Persoun doheem ass.", + "notify_fire_events": "Wann aktivéiert, Home Assistant Eventer fir Zyklusstart an Enn ausléisen (ha_washdata_cycle_started an ha_washdata_cycle_ended) fir Automatisatiounen ze féieren.", + "notify_start_services": "Een oder méi Notifikatiounsservicer fir ze alarméieren wann en Zyklus ufänkt (z.B. notify.mobile_app_my_phone). Loosst eidel fir Start Notifikatiounen ze sprangen.", + "notify_finish_services": "Een oder méi Notifikatiounsservicer fir ze alarméieren wann en Zyklus fäerdeg ass oder bal fäerdeg ass. Loosst eidel fir fäerdeg Notifikatiounen ze sprangen.", + "notify_live_services": "Een oder méi Notifikatiounsservicer fir Live Fortschrëtterupdates ze kréien wärend den Zyklus leeft (nëmmen mobil Begleeder App). Loosst eidel fir Live Updates ze sprangen.", + "notify_before_end_minutes": "Minutten virum geschätzte Enn vum Zyklus fir eng Notifikatioun auszeléisen. Op 0 setzen fir auszeschalten. Standard: 0.", + "notify_live_interval_seconds": "Wéi oft Fortschrëtterupdates während dem Lafen geschéckt ginn. Méi niddereg Wäerter si méi reaktiounsfäeger awer konsuméieren méi Notifikatiounen. E Minimum vun 30 Sekonnen gëtt duerchgesat - Wäerter ënner 30 Sekonnen ginn automatesch op 30 Sekonnen ofgerënnt.", + "notify_live_overrun_percent": "Sécherheetsmarge iwwer de geschätzte Updatezuel fir laang / iwwerlafend Zyklen (zum Beispill 20% erlaabt 1,2x geschätzte Updates).", + "notify_live_chronometer": "Wann aktivéiert, enthält all Live Update e Chronometer Countdown op déi geschätzte Schlusszäit. Den Notifikatiounstimer tickt automatesch op den Apparat tëscht Updates (nëmmen Android Begleeder App).", + "notify_title": "Benotzerdefinéierte Titel fir Notifikatiounen. Ënnerstëtzt {device} Plazhalter.", + "notify_icon": "Ikon fir Notifikatiounen ze benotzen (zB mdi:washing-machine). Eidel loossen fir ouni Ikon ze schécken.", + "notify_start_message": "Message geschéckt wann en Zyklus ufänkt. Ënnerstëtzt {device} Plazhalter.", + "notify_finish_message": "Message geschéckt wann en Zyklus fäerdeg ass. Ënnerstëtzt {device}, {duration}, {program}, {energy_kwh}, {cost} Plazhalter.", + "energy_price_entity": "Wielt eng numeresch Entitéit déi den aktuellen Stroumpräis hält (zB Sensor.electricity_price, input_number.energy_rate). Wann agestallt, aktivéiert den {cost} Plazhalter. Huelt Virrang iwwer de statesche Wäert wa béid konfiguréiert sinn.", + "energy_price_static": "Fixe Stroumpräis pro kWh. Wann agestallt, aktivéiert den {cost} Plazhalter. Ignoréiert wann eng Entitéit uewen konfiguréiert ass. Füügt Äert Währungssymbol an de Message Schabloun, z.B. {cost} €.", + "notify_reminder_message": "Den Text vun der eemoleger Erënnerung huet déi konfiguréiert Zuel vu Minutte virum geschätzte Enn geschéckt. Ënnerscheed vun de widderhuelende Live Updates hei uewen. Ënnerstëtzt {device}, {minutes}, {program} Plazhalter.", + "notify_timeout_seconds": "Entlooss automatesch Notifikatiounen no dëse ville Sekonnen (virgeschriwwe wéi d'Begleeder App 'Timeout'). Setzt op 0 fir se ze halen bis se entlooss ginn. Standard: 0.", + "notify_channel": "Android Begleeder App Notifikatiounskanal fir Status, Live Fortschrëtter, an Erënnerung Notifikatiounen. Den Sound an d'Wichtegkeet vun engem Kanal sinn an der Begleeder App konfiguréiert déi éischte Kéier de Kanalnumm benotzt gëtt - WashData setzt nëmmen de Kanalnumm. Loosst eidel fir d'App Default.", + "notify_finish_channel": "Separat Android Kanal fir de fäerdegen Alarm (och benotzt vun der Erënnerung an der Wäsch-Waart Nag) sou datt et säin eegene Sound kann hunn. Loosst eidel fir de Kanal uewen ze benotzen.", + "notify_pre_complete_message": "Text vun de widderhuelende Live Fortschrëtterupdates wärend den Zyklus leeft. Ënnerstëtzt {device}, {minutes}, {program} Plazhalter." + } + }, + "advanced_settings": { + "title": "Advanced Astellungen", + "description": "Stëmmt Detektiounsschwellen, Léieren a fortgeschratt Verhalen of. Standardwäerter funktionéieren gutt fir déi meescht Astellungen.", + "sections": { + "suggestions_section": { + "name": "Virgeschloen Astellungen", + "description": "{suggestions_count} geléiert Virschléi verfügbar. Hakt Proposéiert Wäerter uwenden un, fir déi virgeschloen Ännerunge virum Späicheren ze iwwerpréiwen.", + "data": { + "apply_suggestions": "Proposéiert Wäerter uwenden" + }, + "data_description": { + "apply_suggestions": "Kuckt dës Këscht un fir e Iwwerpréiwungsschrëtt opzemaachen fir proposéiert Wäerter vum Léieralgorithmus. Näischt gëtt automatesch gespäichert.\n\nProposéiert (vum Léieren):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detektioun & Leeschtungsschwellen", + "description": "Wéini den Apparat als UN oder AUS gëllt, Energie-Grenzen an Timing fir Zoustandswiessel.", + "data": { + "start_duration_threshold": "Start Debounce Dauer (s)", + "start_energy_threshold": "Start Energie Gate (Wh)", + "completion_min_seconds": "Ofschloss Minimum Lafzäit (Sekonnen)", + "start_threshold_w": "Startschwell (W)", + "stop_threshold_w": "Stoppschwell (W)", + "end_energy_threshold": "Enn Energie Gate (Wh)", + "running_dead_zone": "Lafen Dead Zone (Sekonnen)", + "end_repeat_count": "Enn Widderhuelen Zuel", + "min_off_gap": "Min Paus tëscht Zyklen (s)", + "sampling_interval": "Sampling Intervall (s)" + }, + "data_description": { + "start_duration_threshold": "Kuerz Power Spikes méi kuerz wéi dës Dauer (Sekonnen) ignoréieren. Filtert Boot-Spikes (zB 60W fir 2s) ier de richtege Zyklus ufänkt. Standard: 5s.", + "start_energy_threshold": "Zyklus muss op d'mannst esou vill Energie (Wh) während der START Phase accumuléieren fir ze bestätegen. Standard: 0,005 Wh.", + "completion_min_seconds": "Zyklen méi kuerz wéi dëst ginn als 'ënnerbrach' markéiert, och wa se natierlech fäerdeg sinn. Standard: 600s.", + "start_threshold_w": "D'Kraaft muss IWWER dës Schwell eropgoen fir en Zyklus ze STARTEN. Méi héich setzen wéi Är Standby Kraaft. Standard: min_power + 1 W.", + "stop_threshold_w": "D'Kraaft muss ËNNER dës Schwell falen fir en Zyklus ze STOPPEN. Knapp iwwer Null setzen fir Geräischer z'evitéieren déi falsch Enn ausléisen. Standard: 0,6 * min_power.", + "end_energy_threshold": "De Zyklus endet nëmmen wann d'Energie an der leschter Off-Delay Fënster ënner dësem Wäert (Wh) ass. Verhënnert falsch Enn wann d'Kraaft no bei Null schwankt. Standard: 0,05 Wh.", + "running_dead_zone": "Nodeems de Zyklus ufänkt, Power Dips fir dës vill Sekonnen ignoréieren fir falsch Enn-Detektioun während der initialer Anlafphase z'evitéieren. Op 0 setzen fir auszeschalten. Standard: 0s.", + "end_repeat_count": "Unzuel vun Mol d'Endbedingung (Kraaft ënner der Schwell fir off_delay) konsekutiv erfëllt sinn muss ier de Zyklus ofgeschloss gëtt. Méi héich Wäerter verhënneren falsch Enn während Pausen. Standard: 1.", + "min_off_gap": "Wann en Zyklus bannent dëse ville Sekonnen vum viregten Enn ufänkt, gi se an engem eenzege Zyklus fusionéiert.", + "sampling_interval": "Minimum Sampling Intervall a Sekonnen. Updates méi séier wéi dësen Taux ginn ignoréiert. Standard: 30s (2s fir Wäschmaschinnen, Wäschmaschinn-Trockner, an Spullmaschinnen)." + } + }, + "matching_section": { + "name": "Profil-Matching & Léieren", + "description": "Wéi aggressiv WashData lafend Zyklen mat geléierte Profiller ofgläicht a wéini no Feedback gefrot gëtt.", + "data": { + "profile_match_min_duration_ratio": "Profil Match Min Dauer Verhältnis (0,1-1,0)", + "profile_match_interval": "Profil Match Intervall (Sekonnen)", + "profile_match_threshold": "Profil Match Schwell", + "profile_unmatch_threshold": "Profil Unmatch Schwell", + "auto_label_confidence": "Auto-Etikett Vertrauen (0-1)", + "learning_confidence": "Feedback Ufro Vertrauen (0-1)", + "suppress_feedback_notifications": "Ënnerdréckt Feedback Notifikatiounen", + "duration_tolerance": "Dauer Toleranz (0-0,5)", + "smoothing_window": "Glättungsfenster (Proben)", + "profile_duration_tolerance": "Profil Match Dauer Toleranz (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum Dauererhältnis fir Profilmatching. Lafen Zyklus muss op d'mannst dës Fraktioun vun der Profildauer sinn fir ze matchen. Manner = méi fréi matchen. Standard: 0,50 (50%).", + "profile_match_interval": "Wéi dacks (a Sekonnen) Profilmatching während engem Zyklus versicht gëtt. Méi niddereg Wäerter bidden méi séier Detektioun awer benotzen méi CPU. Standard: 300s (5 Minutten).", + "profile_match_threshold": "Minimum DTW Ähnlechkeet Score (0,0-1,0) néideg fir e Profil als Match ze betrueweg. Méi héich = méi streng Matching, manner falsch Positiver. Standard: 0,4.", + "profile_unmatch_threshold": "DTW Ähnlechkeet Score (0,0-1,0) ënner deem e virdru gematchten Profil ofgeleent gëtt. Sollte méi niddereg wéi d'Matchschwell sinn fir Flackeren z'evitéieren. Standard: 0,35.", + "auto_label_confidence": "Zyklen automatesch etikettéieren op oder iwwer dëst Vertrauen beim Ofschloss (0,0-1,0).", + "learning_confidence": "Minimum Vertraue fir Benotzerverifikatioun iwwer en Event unzefroen (0,0–1,0).", + "suppress_feedback_notifications": "Wann et aktivéiert ass, wäert WashData nach ëmmer intern verfollegen an e Feedback froen, awer net persistent Notifikatiounen weisen, déi Iech froen Zyklen ze bestätegen. Nëtzlech wann Zyklen ëmmer korrekt festgestallt ginn an Dir fannt d'Uweisungen oflenken.", + "duration_tolerance": "Toleranz fir Restzäitschätzungen während engem Laf (0,0-0,5 entsprécht 0-50%).", + "smoothing_window": "Zuel vun de rezente Power Liesungen déi fir glidderende Duerchschnëtt Glättung benotzt ginn.", + "profile_duration_tolerance": "Toleranz benotzt duerch Profilmatching fir total Dauervarianz (0,0-0,5). Standardverhalen: ±25%." + } + }, + "timing_section": { + "name": "Timing, Ënnerhalt & Debug", + "description": "Watchdog, Fortschrëtt zrécksetzen, automateschen Ënnerhalt an d'Visibilitéit vu Debug-Entitéiten.", + "data": { + "watchdog_interval": "Watchdog Intervall (Sekonnen)", + "no_update_active_timeout": "No-Update Timeout (s)", + "progress_reset_delay": "Fortschrëtt Reset Verzögerung (Sekonnen)", + "auto_maintenance": "Auto-Maintenance aktivéieren", + "expose_debug_entities": "Debug Entitéiten uleën", + "save_debug_traces": "Debug Spuren späicheren" + }, + "data_description": { + "watchdog_interval": "Sekonnen tëscht Watchdog-Kontrollen beim Lafen. Standard: 5s. OPGEPASST: Vergewëssert Iech datt dëst MÉI HÉICH ass wéi den Aktualiséierungsintervall vun Ärem Sensor fir falsch Stops z'evitéieren (zB wann de Sensor all 60s aktualiséiert, benotzt 65s+).", + "no_update_active_timeout": "Fir Publish-on-Change Sensoren: nëmmen en AKTIVE Zyklus beenden wann keng Updates fir dës vill Sekonnen ukommen. Low-Power Ofschloss benotzt nach ëmmer den Off Delay.", + "progress_reset_delay": "Nodeems en Zyklus fäerdeg ass (100%), dës vill Sekonnen Idle waarden ier de Fortschrëtt op 0% zréckgesat gëtt. Standard: 300s.", + "auto_maintenance": "Auto-Maintenance aktivéieren (Proben reparéieren, Fragmenter fusionéieren).", + "expose_debug_entities": "Advanced Sensoren weisen (Vertrauen, Phase, Ambiguitéit) fir Debugging.", + "save_debug_traces": "Detailléiert Ranking an Power Trace Daten an der Geschicht späicheren (erhéicht d'Späicherverbrauch)." + } + }, + "anti_wrinkle_section": { + "name": "Knuetterschutz (Dréchner)", + "description": "Ignoréiert Trommel-Beweegungen nom Zyklus, fir Geeschterzyklen ze verhënneren. Nëmmen Dréchner / Wäschdréchner.", + "data": { + "anti_wrinkle_enabled": "Anti-Falten Schutz (nëmmen Trockner/Wäsch-Trockner)", + "anti_wrinkle_max_power": "Anti-Falten Max Power (W) (nëmmen Trockner/Wäsch-Trockner)", + "anti_wrinkle_max_duration": "Anti-Falten Max Dauer (s) (nëmmen Trockner/Wäsch-Trockner)", + "anti_wrinkle_exit_power": "Anti-Falten Exit Power (W) (nëmmen Trockner/Wäsch-Trockner)" + }, + "data_description": { + "anti_wrinkle_enabled": "Kuerz Low-Power Trommelrotatiounen no engem Zyklus ignoréieren fir Geeschterzyklen z'evitéieren (nëmmen Trockner/Wäsch-Trockner).", + "anti_wrinkle_max_power": "Bursts op oder ënner dëser Kraaft ginn als Anti-Falten Rotatiounen behandelt anstatt nei Zyklen. Gëllt nëmmen wann Anti-Falten Schutz aktivéiert ass.", + "anti_wrinkle_max_duration": "Maximal Burst Längt fir als Anti-Falten Rotatioun behandelt ze ginn. Gëllt nëmmen wann Anti-Falten Schutz aktivéiert ass.", + "anti_wrinkle_exit_power": "Wann d'Kraaft ënner dëse Wäert fällt, gëtt de Anti-Falten direkt verlooss (richteg aus). Gëllt nëmmen wann Anti-Falten Schutz aktivéiert ass." + } + }, + "delay_start_section": { + "name": "Verspéit-Start-Erkennung", + "description": "Behandelt dauerhaft niddreg Leeschtungs-Standby als 'Waart op Start', bis de Zyklus wierklech ufänkt.", + "data": { + "delay_start_detect_enabled": "Aktivéiert Delayed Start Detection", + "delay_confirm_seconds": "Verspéite Start: Standby-Bestätegungszäit (s)", + "delay_timeout_hours": "Verspéiten Start: Max Waardezäit (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Wann aktivéiert, gëtt e kuerze Low-Power Drain Spike gefollegt vun nohalteger Standby Kraaft als verspéiten Start festgestallt. De Staatssensor weist 'Waart op de Start' wärend der Verzögerung. Kee Programmzäit oder Fortschrëtter gëtt verfollegt bis den Zyklus tatsächlech ufänkt. Verlaangt start_threshold_w iwwer d'Drain Spike Muecht gesat ginn.", + "delay_confirm_seconds": "D'Leeschtung muss esou vill Sekonnen tëscht Stoppschwelle (W) a Startschwelle (W) bleiwen, éier WashData an de Status 'Waart op Start' geet. Filtert kuerz Spëtzen bei der Menü-Navigatioun eraus. Standard: 60 s.", + "delay_timeout_hours": "Sécherheetstimeout: wann d'Maschinn nach ëmmer am 'Waart op Start' no dëse ville Stonnen ass, setzt WashData op Off. Standard: 8 h." + } + }, + "external_triggers_section": { + "name": "Extern Triggeren, Dier & Paus", + "description": "Optionalen externen Enn-Trigger-Sensor, Dier-Sensor fir Paus/propper-Erkennung, a Schalter fir de Stroum bei Paus auszeschalten.", + "data": { + "external_end_trigger_enabled": "Externen Zyklus Enn Ausléiser aktivéieren", + "external_end_trigger": "Externen Zyklus Enn Sensor", + "external_end_trigger_inverted": "Trigger Logik invertéieren (Trigger op OFF)", + "door_sensor_entity": "Dier Sensor", + "pause_cuts_power": "Maacht Kraaft beim Pausen", + "switch_entity": "Switch Entity (fir Pause Power Cut)", + "notify_unload_delay_minutes": "Wäschwart Notifikatioun Verspéidung (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Iwwerwaachung vun engem externen Binärsensor aktivéieren fir den Zyklus ofzeschléissen.", + "external_end_trigger": "Eng binär Sensor Entitéit auswielen. Wann dëse Sensor ausléist, wäert den aktuellen Zyklus mat dem Status 'ofgeschloss' ophalen.", + "external_end_trigger_inverted": "Par défaut léist den Trigger aus wann de Sensor op ON dréit. Dës Këscht unklicken fir z'ausléisen wann de Sensor amplaz op OFF dréit.", + "door_sensor_entity": "Optional Binärsensor fir d'Maschinntür (an = oppen, aus = zou). Wann d'Dier während engem aktive Zyklus opmécht, wäert WashData den Zyklus bestätegen als bewosst gepaust. Nodeems den Zyklus eriwwer ass, wann d'Dier nach ëmmer zou ass, ännert den Zoustand op 'Clean' bis d'Dier opgemaach gëtt. Bemierkung: D'Zoumaache vun der Dier hëlt NET automatesch e gepauzten Zyklus weider - Resume muss manuell iwwer de Resume Cycle Knäppchen oder Service ausgeléist ginn.", + "pause_cuts_power": "Wann Dir iwwer de Pause Cycle Knäppchen oder Service pauséiert, schalt och déi konfiguréiert Schalter Entitéit aus. Gëtt nëmme Kraaft wann eng Schalter Entitéit fir dësen Apparat konfiguréiert ass.", + "switch_entity": "D'Schalter Entitéit fir ze wiesselen wann Dir Paus oder weider geet. Obligatoresch wann 'Kraaft schneiden beim Pausen' aktivéiert ass.", + "notify_unload_delay_minutes": "Schéckt eng Notifikatioun iwwer d'Finish Notifikatiounskanal nodeems den Zyklus eriwwer ass an d'Dier fir dës vill Minutten net opgemaach gouf (erfuerdert Door Sensor). Setzt op 0 fir auszeschalten. Standard: 60 min." + } + }, + "pump_section": { + "name": "Pompel-Iwwerwaachung", + "description": "Nëmmen fir den Apparattyp Pompel. Léisst en Event aus, wann e Pompelzyklus méi laang wéi dës Dauer leeft.", + "data": { + "pump_stuck_duration": "Pompel Stuck Alarmschwell (en) (nëmmen Pompel)" + }, + "data_description": { + "pump_stuck_duration": "Wann e Pompel Zyklus nach laafen no dëse ville Sekonnen, engem ha_washdata_pump_stuck Event eemol gebrannt. Benotzt dëst fir e verstoppte Motor z'entdecken (typesch Sumppompellaf ass ënner 60 s). Standard: 1800 s (30 min). Pompel Apparat Typ nëmmen." + } + }, + "device_link_section": { + "name": "Apparat Link", + "description": "Optional verbënnt dësen WashData Apparat mat engem existente Home Assistant Apparat (zum Beispill de Smart Plug oder den Apparat selwer). De WashData-Apparat gëtt dann als \"Connected via\" dat Apparat gewisen. Loosst eidel fir WashData als Standalone Apparat ze halen.", + "data": { + "linked_device": "Verknëppelt Gerät" + }, + "data_description": { + "linked_device": "Wielt den Apparat fir WashData ze verbannen. Dëst füügt eng 'Connected via' Referenz an der Apparatregistrierung; WashData hält seng eege Apparat Kaart an Entitéite. D'Auswiel läschen fir de Link ze läschen." + } + } + } + }, + "diagnostics": { + "title": "Diagnostik & Ënnerhalt", + "description": "Ënnerhalt Aktiounen lafen wéi fragmentéiert Zyklen fusionéieren oder gespäichert Daten migréieren.\n\n**Späicherverbrauch**\n\n- Dateigréisst: {file_size_kb} KB\n- Zyklen: {cycle_count}\n- Profiler: {profile_count}\n- Debug Spuren: {debug_count}", + "menu_options": { + "reprocess_history": "Ënnerhalt: Daten nei veraarbecht & optimiséieren", + "clear_debug_data": "Debug Daten läschen (Plaz befreien)", + "wipe_history": "ALL Daten fir dësen Apparat läschen (irreversibel)", + "export_import": "Export / Import JSON mat Astellungen (kopéieren/ansetzen)", + "menu_back": "← Zréck" + } + }, + "clear_debug_data": { + "title": "Debug Daten läschen", + "description": "Sidd Dir sécher, datt Dir all gespäichert Debug-Spuren läschen wëllt? Dëst befreit Plaz awer läscht detailléiert Rankinginformatioun aus vergaangene Zyklen." + }, + "manage_cycles": { + "title": "Zyklen verwalten", + "description": "Rezent Zyklen:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Al Zyklen automatesch etikettéieren", + "select_cycle_to_label": "Spezifesche Zyklus etikettéieren", + "select_cycle_to_delete": "Zyklus läschen", + "interactive_editor": "Fusionéieren / Split Interaktiven Editor", + "trim_cycle_select": "Zyklus Daten trimmen", + "menu_back": "← Zréck" + } + }, + "manage_cycles_empty": { + "title": "Zyklen verwalten", + "description": "Nach keng Zyklen opgeholl.", + "menu_options": { + "auto_label_cycles": "Al Zyklen automatesch etikettéieren", + "menu_back": "← Zréck" + } + }, + "manage_profiles": { + "title": "Profiler verwalten", + "description": "Profil Resumé:\n{profile_summary}", + "menu_options": { + "create_profile": "Neie Profil erstellen", + "edit_profile": "Profil änneren / ëmbenennen", + "delete_profile_select": "Profil läschen", + "profile_stats": "Profil Statistiken", + "cleanup_profile": "Geschicht botzen - Grafik & Läschen", + "assign_profile_phases_select": "Phase Beräicher zouweisen", + "menu_back": "← Zréck" + } + }, + "manage_profiles_empty": { + "title": "Profiler verwalten", + "description": "Nach keng Profiler erstallt.", + "menu_options": { + "create_profile": "Neie Profil erstellen", + "menu_back": "← Zréck" + } + }, + "manage_phase_catalog": { + "title": "Phase Katalog verwalten", + "description": "Phase Katalog (Standard fir dësen Apparat + Benotzerdefinéiert Phasen):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Nei Phase erstellen", + "phase_catalog_edit_select": "Phase änneren", + "phase_catalog_delete": "Phase läschen", + "menu_back": "← Zréck" + } + }, + "phase_catalog_create": { + "title": "Benotzerdefinéiert Phase erstellen", + "description": "Erstellt e personaliséierte Phasennumm an eng Beschreiwung, a wielt op wéi eng Apparat Aart et gëllt.", + "data": { + "target_device_type": "Zil Apparat Typ", + "phase_name": "Phase Numm", + "phase_description": "Phase Beschreiwung" + } + }, + "phase_catalog_edit_select": { + "title": "Benotzerdefinéiert Phase änneren", + "description": "Wielt eng personaliséiert Phase fir z'änneren.", + "data": { + "phase_name": "Benotzerdefinéiert Phase" + } + }, + "phase_catalog_edit": { + "title": "Benotzerdefinéiert Phase änneren", + "description": "Phase änneren: {phase_name}", + "data": { + "phase_name": "Phase Numm", + "phase_description": "Phase Beschreiwung" + } + }, + "phase_catalog_delete": { + "title": "Benotzerdefinéiert Phase läschen", + "description": "Wielt eng personaliséiert Phase fir ze läschen. All zougewisene Beräicher mat dëser Phase ginn ewechgeholl.", + "data": { + "phase_name": "Benotzerdefinéiert Phase" + } + }, + "assign_profile_phases": { + "title": "Phase Beräicher zouweisen", + "description": "Phase Virschau fir Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuelle Beräicher:**\n{current_ranges}\n\nBenotzt d'Aktiounen hei drënner fir Beräicher ze addéieren, z'änneren, ze läschen oder ze späicheren.", + "menu_options": { + "assign_profile_phases_add": "Phase Beräich addéieren", + "assign_profile_phases_edit_select": "Phase Beräich änneren", + "assign_profile_phases_delete": "Phase Beräich läschen", + "phase_ranges_clear": "All Beräicher läschen", + "assign_profile_phases_auto_detect": "Phasen auto-detektéieren", + "phase_ranges_save": "Späicheren an zréck", + "menu_back": "← Zréck" + } + }, + "assign_profile_phases_add": { + "title": "Phase Beräich addéieren", + "description": "Eng Phaseberäich zum aktuellen Entworf addéieren.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minutt", + "end_min": "Enn Minutt" + } + }, + "assign_profile_phases_edit_select": { + "title": "Phase Beräich änneren", + "description": "Eng Rei fir z'änneren auswielen.", + "data": { + "range_index": "Phase Beräich" + } + }, + "assign_profile_phases_edit": { + "title": "Phase Beräich änneren", + "description": "Den ausgewielten Phaseberäich aktualiséieren.", + "data": { + "phase_name": "Phase", + "start_min": "Start Minutt", + "end_min": "Enn Minutt" + } + }, + "assign_profile_phases_delete": { + "title": "Phase Beräich läschen", + "description": "Eng Rei fir aus dem Entworf ze läschen auswielen.", + "data": { + "range_index": "Phase Beräich" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Auto-detektéiert Phasen", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} Phase(n) automatesch festgestallt.** Wielt eng Aktioun hei drënner.", + "data": { + "action": "Aktioun" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Entdeckt Phasen benennen", + "description": "Profil: **{profile_name}**\n\n**{detected_count} Phase(n) entdeckt:**\n{ranges_summary}\n\nGitt all Phase en Numm." + }, + "assign_profile_phases_select": { + "title": "Phase Beräicher zouweisen", + "description": "Wielt e Profil fir Phaseberäicher ze konfiguréieren.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profil Statistiken", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Neie Profil erstellen", + "description": "Erstellt en neie Profil manuell oder aus engem vergaangene Zyklus.\n\nBeispiller vum Profilnumm: 'Delicates', 'Heavy Duty', 'Quick Wash'", + "data": { + "profile_name": "Profil Numm", + "reference_cycle": "Referenz Zyklus (fakultativ)", + "manual_duration": "Manuell Dauer (Minuten)" + } + }, + "edit_profile": { + "title": "Profil änneren", + "description": "Wielt e Profil fir ëmbenennen", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Profil Details änneren", + "description": "Aktuellen Profil: {current_name}", + "data": { + "new_name": "Profil Numm", + "manual_duration": "Manuell Dauer (Minuten)" + } + }, + "delete_profile_select": { + "title": "Profil läschen", + "description": "Wielt e Profil fir ze läschen", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Profil läschen bestätegen", + "description": "⚠️ Dëst wäert de Profil permanent läschen: {profile_name}", + "data": { + "unlabel_cycles": "Label aus Zyklen mat dësem Profil ewechhuelen" + } + }, + "auto_label_cycles": { + "title": "Al Zyklen automatesch etikettéieren", + "description": "{total_count} Gesamtzyklen fonnt. Profiler: {profiles}", + "data": { + "confidence_threshold": "Minimum Vertrauen (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Zyklus fir Etikettéierung auswielen", + "description": "✓ = ofgeschloss, ⚠ = opgeholl, ✗ = ënnerbrach", + "data": { + "cycle_id": "Zyklus" + } + }, + "select_cycle_to_delete": { + "title": "Zyklus läschen", + "description": "⚠️ Dëst wäert den ausgewielten Zyklus permanent läschen", + "data": { + "cycle_id": "Zyklus fir ze läschen" + } + }, + "label_cycle": { + "title": "Zyklus etikettéieren", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Neie Profilnumm (wann erstellt)" + } + }, + "post_process": { + "title": "Geschicht veraarbecht (Fusionéieren / Split)", + "description": "Propper Zyklusgeschicht andeems se fragmentéiert Runen fusionéieren an falsch fusionéierte Zyklen bannent enger Zäitfenster opzedeelen. Gitt d'Zuel vun Stonnen un fir zréck ze kucken (benotzt 999999 fir all).", + "data": { + "time_range": "Réckbléckfenster (Stonnen)", + "gap_seconds": "Fusionéieren / Split Gap (Sekonnen)" + }, + "data_description": { + "time_range": "Unzuel vun Stonnen fir ze scannen fir fragmentéiert Zyklen ze fusionéieren. Benotzt 999999 fir all historesch Daten ze veraarbecht.", + "gap_seconds": "Schwell fir Split géint Fusioun z'entscheeden. Lücken KÜRZER wéi dëst ginn fusionéiert. Lücken MÉI LAANG wéi dëst ginn opgedeelt. Méi niddereg setzen fir en Zyklus opzedeelen deen falsch fusionéiert gouf." + } + }, + "cleanup_profile": { + "title": "Geschicht botzen - Profil auswielen", + "description": "Wielt e Profil fir ze visualiséieren. Dëst generéiert eng Grafik déi all vergaangen Zyklen fir dëse Profil weist fir Auslänner z'identifizéieren.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Geschicht botzen - Grafik & Läschen", + "description": "![Grafik]({graph_url})\n\n**Visualiséiert {profile_name}**\n\nD'Grafik weist eenzel Zyklen als faarweg Linnen. Identifizéiert Auslänner (Linnen wäit vun der Grupp) a passt hir Faarf an der Lëscht hei drënner un fir se ze läschen.\n\n**Wielt Zyklen fir PERMANENT ze läschen:**", + "data": { + "cycles_to_delete": "Zyklen fir ze läschen (passende Faarwen kucken)" + } + }, + "interactive_editor": { + "title": "Fusionéieren / Split Interaktiven Editor", + "description": "Wielt eng manuell Aktioun fir Är Zyklusgeschicht auszeféieren.", + "menu_options": { + "editor_split": "Zyklus opdeelen (Lücken fannen)", + "editor_merge": "Zyklen fusionéieren (Fragmenter verbinden)", + "editor_delete": "Zyklus(en) läschen", + "menu_back": "← Zréck" + } + }, + "editor_select": { + "title": "Zyklen auswielen", + "description": "Den Zyklus (en) fir ze veraarbecht auswielen.\n\n{info_text}", + "data": { + "selected_cycles": "Zyklen" + } + }, + "editor_configure": { + "title": "Konfiguréieren & uwenden", + "description": "{preview_md}", + "data": { + "confirm_action": "Aktioun", + "merged_profile": "Resultéierende Profil", + "new_profile_name": "Neie Profil Numm", + "confirm_commit": "Jo, ech bestätegen dës Aktioun", + "segment_0_profile": "Segment 1 Profil", + "segment_1_profile": "Segment 2 Profil", + "segment_2_profile": "Segment 3 Profil", + "segment_3_profile": "Segment 4 Profil", + "segment_4_profile": "Segment 5 Profil", + "segment_5_profile": "Segment 6 Profil", + "segment_6_profile": "Segment 7 Profil", + "segment_7_profile": "Segment 8 Profil", + "segment_8_profile": "Segment 9 Profil", + "segment_9_profile": "Segment 10 Profil" + } + }, + "editor_split_params": { + "title": "Split Konfiguratioun", + "description": "D'Sensibilitéit fir dëse Zyklus opzedeelen upasssen. Jidderee Idle Spalt méi laang wéi dëst wäert eng Opdelung verursaachen.", + "data": { + "split_mode": "Split-Method" + } + }, + "editor_split_auto_params": { + "title": "Automatesch Split-Konfiguratioun", + "description": "Passt d'Sensibilitéit un fir dëse Zyklus ze splécken. All Idle-Lück déi méi laang ass wéi dëse Wäert verursaacht eng Split.", + "data": { + "min_gap_seconds": "Schwellwäert fir Split-Lück (Sekonnen)" + } + }, + "editor_split_manual_params": { + "title": "Manuell Split-Zäitstempelen", + "description": "{preview_md}Zyklusfënster: **{cycle_start_wallclock} → {cycle_end_wallclock}** den {cycle_date}.\n\nGitt een oder méi Split-Zäitstempelen an (`HH:MM` oder `HH:MM:SS`), een pro Zeil. De Zyklus gëtt bei all Zäitstempel geschnidden — N Zäitstempelen erginn N+1 Segmenter.", + "data": { + "split_timestamps": "Split-Zäitstempelen" + } + }, + "reprocess_history": { + "title": "Ënnerhalt: Daten nei veraarbecht & optimiséieren", + "description": "Féiert eng déif Botze vun historeschen Donnéeën duerch:\n1. Null-Power Liesungen trimmen (Stëllen).\n2. Zyklus Timing / Dauer fixéieren.\n3. All statistesch Modeller nei berechnen (Enveloppen).\n\n**Dëst lafen fir Datenproblemer ze fixéieren oder nei Algorithmen unzesetzen.**" + }, + "wipe_history": { + "title": "Geschicht läschen (Nëmmen fir Testen)", + "description": "⚠️ Dëst wäert ALL gespäichert Zyklen a Profiler fir dësen Apparat permanent läschen. Dëst kann net réckgängeg gemaach ginn!" + }, + "export_import": { + "title": "Export / Import JSON", + "description": "Zyklen, Profiler AN Astellungen fir dësen Apparat kopéieren/ansetzen. Hei drënner exportéieren oder JSON ansetzen fir z'importéieren.", + "data": { + "mode": "Aktioun", + "json_payload": "Komplett Konfiguratioun JSON" + }, + "data_description": { + "mode": "Export auswielen fir aktuell Donnéeën an Astellungen ze kopéieren, oder Import fir exportéiert Daten vun engem aneren WashData Apparat anzesetzten.", + "json_payload": "Fir Export: dëse JSON kopéieren (enthält Zyklen, Profiler an all fein gestëmmten Astellungen). Fir Import: JSON aus WashData exportéiert ansetzen." + } + }, + "record_cycle": { + "title": "Zyklus ophuelen", + "description": "Manuell en Zyklus ophuelen fir e proppere Profil ouni Stéierung ze kreéieren.\n\nStatus: **{status}**\nDauer: {duration}s\nProben: {samples}", + "menu_options": { + "record_refresh": "Status erfrëschen", + "record_stop": "Opnam stoppen (Späicheren & Veraarbecht)", + "record_start": "Nei Opnam starten", + "record_process": "Lescht Opnam veraarbecht (Trim & Späicheren)", + "record_discard": "Lescht Opnam verworf", + "menu_back": "← Zréck" + } + }, + "record_process": { + "title": "Opnam veraarbecht", + "description": "![Grafik]({graph_url})\n\n**Grafik weist rau Opnam.** Blo = Behalen, Rout = Proposéiert Trim.\n*Grafik ass statesch an aktualiséiert net mat Form Ännerungen.*\n\nOpnam gestoppt. Trimmen si op déi detektéiert Sampling-Rate (~{sampling_rate}s) ausgeriicht.\n\nRau Dauer: {duration}s\nProben: {samples}", + "data": { + "head_trim": "Trim Start (Sekonnen)", + "tail_trim": "Trim Enn (Sekonnen)", + "save_mode": "Späicherzil", + "profile_name": "Profil Numm" + } + }, + "learning_feedbacks": { + "title": "Léieren Feedbacks", + "description": "Pendent Feedbacks: {count}\n\n{pending_table}\n\nWielt eng Ufro fir eng Zyklus-Iwwerpréiwung fir ze veraarbechten.", + "menu_options": { + "learning_feedbacks_pick": "Pendent Feedback iwwerpréiwen", + "learning_feedbacks_dismiss_all": "All pendent Feedbacks verwerfen", + "menu_back": "← Zréck" + } + }, + "learning_feedbacks_pick": { + "title": "Feedback fir Iwwerpréiwung auswielen", + "description": "Wielt eng Ufro fir Zyklus-Iwwerpréiwung fir opzemaachen.", + "data": { + "selected_feedback": "Zyklus fir Iwwerpréiwung auswielen" + } + }, + "learning_feedbacks_empty": { + "title": "Léieren Feedbacks", + "description": "Keng pendend Feedback Ufroe fonnt.", + "menu_options": { + "menu_back": "← Zréck" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "All waarden Feedbacken ofweisen", + "description": "Dir sidd amgaang **{count}** waarden Feedback-Ufroen ofzeweisen. Ofgewisene Zyklen bleiwen an Ärer Geschicht, mee droen net zu engem neie Léiersignal bäi. Dat kann net réckgängeg gemaach ginn.\n\n✅ Kuckt d'Këscht hei drënner a klickt op **Send** fir alles ofzeweisen.\n❌ Loosst se net ugekräizt a klickt op **Send** fir ze annuléieren an zréck ze goen.", + "data": { + "confirm_dismiss_all": "Bestätegen: all pendent Feedback-Ufroen verwerfen" + } + }, + "resolve_feedback": { + "title": "Feedback léisen", + "description": "{comparison_img}{candidates_table}\n**Detektéierte Profil**: {detected_profile} ({confidence_pct}%)\n**Geschätzte Dauer**: {est_duration_min} min\n**Tatsächlech Dauer**: {act_duration_min} min\n\n__Wielt eng Aktioun hei drënner:__", + "data": { + "action": "Wat géift Dir gären maachen?", + "corrected_profile": "Richtege Programm (wann Dir korrigéiert)", + "corrected_duration": "Richteg Dauer a Sekonnen (wann Dir korrigéiert)" + } + }, + "trim_cycle_select": { + "title": "Zyklus Trim - Zyklus auswielen", + "description": "Wielt en Zyklus fir ze trimmen. ✓ = ofgeschloss, ⚠ = opgeholl, ✗ = ënnerbrach", + "data": { + "cycle_id": "Zyklus" + } + }, + "trim_cycle": { + "title": "Zyklus trimmen", + "description": "Aktuell Fënster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aktioun" + } + }, + "trim_cycle_start": { + "title": "Trim Start setzen", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuelle Start: **{current_wallclock}** (+{current_offset_min} min vum Zyklusstart)\n\nWielt eng nei Startzäit mat der Auer Picker hei drënner.", + "data": { + "trim_start_time": "Nei Startzäit", + "trim_start_min": "Neie Start (Minuten vum Zyklusstart)" + } + }, + "trim_cycle_end": { + "title": "Trim Enn setzen", + "description": "Zyklusfenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuellen Enn: **{current_wallclock}** (+{current_offset_min} min vum Zyklusstart)\n\nWielt eng nei Ennzäit mat der Auer Picker hei drënner.", + "data": { + "trim_end_time": "Nei Ennzäit", + "trim_end_min": "Neien Enn (Minuten vum Zyklusstart)" + } + } + }, + "error": { + "import_failed": "Import gescheitert. W.e.g. e gültege WashData Export JSON ansetzen.", + "select_exactly_one": "Wielt genee ee Zyklus fir dës Aktioun.", + "select_at_least_one": "Wielt op d'mannst ee Zyklus fir dës Aktioun.", + "select_at_least_two": "Wielt op d'mannst zwee Zyklen fir dës Aktioun.", + "profile_exists": "E Profil mat dësem Numm gëtt et schonn", + "rename_failed": "Profil ëmbenennen gescheitert. De Profil existéiert vläicht net oder den neie Numm ass scho benotzt.", + "assignment_failed": "Profil dem Zyklus zouweisen gescheitert", + "duplicate_phase": "Eng Phase mat dësem Numm gëtt et schonn.", + "phase_not_found": "Ausgewielt Phase gouf net fonnt.", + "invalid_phase_name": "Phasennumm kann net eidel sinn.", + "phase_name_too_long": "Phasennumm ass ze laang.", + "invalid_phase_range": "Phaseberäich ass ongëlteg. Enn muss méi grouss sinn wéi den Ufank.", + "invalid_phase_timestamp": "Zäitstempel ass ongëlteg. JJJJ-MM-DD HH:MM oder HH:MM Format benotzen.", + "overlapping_phase_ranges": "Phaseberäicher iwwerlappe sech. D'Gammen upassen a nach eng Kéier probéieren.", + "incomplete_phase_row": "All Phase Zeil muss eng Phase plus komplett Start-/Ennwäerter fir de gewielte Modus enthalen.", + "timestamp_mode_cycle_required": "W.e.g. e Quell Zyklus auswielen wann Dir Zäitstempel Modus benotzt.", + "no_phase_ranges": "Fir dës Aktioun sinn nach keng Phaseberäicher verfügbar.", + "feedback_notification_title": "WashData: Zyklus verifizéieren ({device})", + "feedback_notification_message": "**Apparat**: {device}\n**Programm**: {program} ({confidence}% Vertrauen)\n**Zäit**: {time}\n\nWashData brauch Är Hëllef fir dësen entdeckten Zyklus z'iwwerpréiwen.\n\nW.e.g. op **Astellungen > Apparater & Servicer > WashData > Konfiguréieren > Léieren Feedbacks** goen fir dëst Resultat ze bestätegen oder ze korrigéieren.", + "suggestions_ready_notification_title": "WashData: Proposéiert Astellungen bereet ({device})", + "suggestions_ready_notification_message": "De Sensor **Proposéiert Astellungen** bericht elo **{count}** handlungsfäeg Empfehlungen.\n\nFir se z'iwwerpréiwen an unzewendeden: **Astellungen > Apparater & Servicer > WashData > Konfiguréieren > Advanced Astellungen > Proposéiert Wäerter uwenden**.\n\nVirschléi si fakultativ a ginn fir Iwwerpréiwung gewisen ier Dir späichert.", + "trim_range_invalid": "Start muss virum Enn sinn. W.e.g. Är Trim Punkte upassen.", + "trim_failed": "Trim gescheitert. De Zyklus existéiert vläicht net méi oder d'Fënster ass eidel.", + "invalid_split_timestamp": "Zäitstempel ass ongëlteg oder läit ausserhalb vum Zyklusfënster. Benotzt HH:MM oder HH:MM:SS am Zyklusfënster.", + "no_split_segments_found": "Et konnten aus dësen Zäitstempelen keng gülteg Segmenter erstallt ginn. Gitt sécher, datt all Zäitstempel am Zyklusfënster läit an d'Segmenter op d'mannst 1 Minutt laang sinn.", + "auto_tune_suggestion": "{device_type} {device_title} entdeckt Geeschterzyklen. Proposéiert min_power Ännerung: {current_min}W -> {new_min}W (net automatesch ugewannt).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} entdeckt Geeschterzyklen. Proposéiert min_power Ännerung: {current_min}W -> {new_min}W (net automatesch ugewannt)." + }, + "abort": { + "no_cycles_found": "Keng Zyklen fonnt", + "no_split_segments_found": "Keng segmentéierbar Deeler am gewielte Zyklus fonnt.", + "cycle_not_found": "De gewielte Zyklus konnt net geluede ginn.", + "no_power_data": "Keng Power Spurdaten verfügbar fir den ausgewielten Zyklus.", + "no_profiles_found": "Keng Profiler fonnt. Éischt e Profil erstellen.", + "no_profiles_for_matching": "Keng Profiler fir Matching verfügbar. Éischt Profiler erstellen.", + "no_unlabeled_cycles": "All Zyklen sinn scho markéiert", + "no_suggestions": "Nach keng proposéiert Wäerter verfügbar. E puer Zyklen lafen loossen a nach eng Kéier probéieren.", + "no_predictions": "Keng Prognosen", + "no_custom_phases": "Keng personaliséiert Phasen fonnt.", + "reprocess_success": "{count} Zyklen erfollegräich nei veraarbecht.", + "debug_data_cleared": "Debug Daten aus {count} Zyklen erfollegräich geläscht." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Zyklus Start", + "cycle_finish": "Zyklus fäerdeg", + "cycle_live": "Live Fortschrëtt" + } + }, + "common_text": { + "options": { + "unlabeled": "(net markéiert)", + "create_new_profile": "Neie Profil erstellen", + "remove_label": "Label ewechhuelen", + "no_reference_cycle": "(Kee Referenz Zyklus)", + "all_device_types": "All Apparat Zorte", + "current_suffix": "(aktuell)", + "editor_select_info": "1 Zyklus auswielen fir opzedeelen, oder 2+ Zyklen fir ze fusionéieren.", + "editor_select_info_split": "Wielt 1 Zyklus fir opzedeelen.", + "editor_select_info_merge": "Wielt 2+ Zyklen fir zesummenzeféieren.", + "editor_select_info_delete": "Wielt 1+ Zyklus/Zyklen fir ze läschen.", + "no_cycles_recorded": "Nach keng Zyklen opgeholl.", + "profile_summary_line": "- **{name}**: {count} Zyklen, {avg}m Duerchschnëtt", + "no_profiles_created": "Nach keng Profiler erstallt.", + "phase_preview": "Phase Virschau", + "phase_preview_no_curve": "Duerchschnëttlech Profilkurve ass nach net verfügbar. Méi Zyklen fir dëse Profil lafen/etikettéieren.", + "average_power_curve": "Duerchschnëttlech Power Kurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} Zyklen, ~{duration}m Duerchschnëtt)", + "profile_option_short_fmt": "{name} ({count} Zyklen, ~{duration}m)", + "deleted_cycles_from_profile": "{count} Zyklen aus {profile} geläscht.", + "not_enough_data_graph": "Net genuch Daten fir eng Grafik ze generéieren.", + "no_profiles_found": "Keng Profiler fonnt.", + "cycle_info_fmt": "Zyklus: {start}, {duration}m, Aktuell: {label}", + "top_candidates_header": "### Top Kandidaten", + "tbl_profile": "Profil", + "tbl_confidence": "Vertrauen", + "tbl_correlation": "Korrelatioun", + "tbl_duration_match": "Dauer Match", + "detected_profile": "Detektéierte Profil", + "estimated_duration": "Geschätzte Dauer", + "actual_duration": "Tatsächlech Dauer", + "choose_action": "__Wielt eng Aktioun hei drënner:__", + "tbl_count": "Zuel", + "tbl_avg": "Avg", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energie (Duerchschnëtt)", + "tbl_energy_total": "Energie (Gesamt)", + "tbl_consistency": "Konsistenz", + "tbl_last_run": "Leschte Laf", + "graph_legend_title": "Grafik Legende", + "graph_legend_body": "D'Blo Band stellt de Minimum a Maximum Power Verbrauch Beräich duer deen observéiert gouf. D'Linn weist d'Duerchschnëttlech Power Kurve.", + "recording_preview": "Opnam Virschau", + "trim_start": "Trim Start", + "trim_end": "Trim Enn", + "split_preview_title": "Split Virschau", + "split_preview_found_fmt": "{count} Segmenter fonnt.", + "split_preview_confirm_fmt": "Klickt Bestätegen fir dëse Zyklus an {count} separat Zyklen opzedeelen.", + "merge_preview_title": "Fusioun Virschau", + "merge_preview_joining_fmt": "{count} Zyklen zesummefoueren. Lücken ginn mat 0W Liesungen gefëllt.", + "editor_delete_preview_title": "Läsch-Virschau", + "editor_delete_preview_intro": "Déi gewielte Zykle ginn permanent geläscht:", + "editor_delete_preview_confirm": "Klickt op Bestätegen, fir dës Zyklus-Opzeechnunge permanent ze läschen.", + "no_power_preview": "*Keng Power Daten verfügbar fir Virschau.*", + "profile_comparison": "Profil Verglach", + "actual_cycle_label": "Dëse Zyklus (tatsächlech)", + "storage_usage_header": "Späicherverbrauch", + "storage_file_size": "Dateigréisst", + "storage_cycles": "Zyklen", + "storage_profiles": "Profiler", + "storage_debug_traces": "Debug Spuren", + "overview_suffix": "Iwwersiicht", + "phase_preview_for_profile": "Phase Virschau fir Profil", + "current_ranges_header": "Aktuelle Beräicher", + "assign_phases_help": "Benotzt d'Aktiounen hei drënner fir Beräicher ze addéieren, z'änneren, ze läschen oder ze späicheren.", + "profile_summary_header": "Profil Resumé", + "recent_cycles_header": "Rezent Zyklen", + "trim_cycle_preview_title": "Zyklus Trim Virschau", + "trim_cycle_preview_no_data": "Keng Power Daten verfügbar fir dëse Zyklus.", + "trim_cycle_preview_kept_suffix": "behalen", + "table_program": "Programm", + "table_when": "Wéini", + "table_length": "Längt", + "table_match": "Treffer", + "table_profile": "Profil", + "table_cycles": "Zyklen", + "table_avg_length": "Duerchschn. Längt", + "table_last_run": "Leschte Laf", + "table_avg_energy": "Duerchschn. Energie", + "table_detected_program": "Erkannt Programm", + "table_confidence": "Vertrauen", + "table_reported": "Gemellt", + "phase_builtin_suffix": "(Built-in)", + "phase_none_available": "Keng Phasen verfügbar.", + "settings_deprecation_warning": "⚠️ **Deprecéiert Apparattyp:** {device_type} ass geplangt fir an enger zukünfteg Verëffentlechung ze läschen. WashData's passende Pipeline produzéiert keng zouverlässeg Resultater fir dës Apparatklass. Är Integratioun schafft weider duerch d'Deprecatiounsperiod; fir dës Warnung z'ënnerhalen, wiesselt ** Apparat Typ** hei ënnen op eng vun den ënnerstëtzten Typen (Wäschmaschinn, Dryer, Wäschmaschinn Combo, Spullmaschinn, Air Fryer, Broutmaschinn oder Pompel), oder op **Aner (Fortgeschratt)** wann Ären Apparat net mat enger vun den ënnerstëtzten Typen entsprécht. ** Aner (Fortgeschratt) ** Schëffer bewosst generesch Defaults déi net fir all spezifescht Apparat ofgestëmmt sinn, sou datt Dir selwer Schwellen, Timeouts a passende Parameteren konfiguréiere musst; all Är bestehend Astellunge sinn preservéiert wann Dir wiesselt.", + "phase_other_device_types": "Aner Zorte vun Apparater:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Zyklus opdeelen (Lücken fannen)", + "merge": "Zyklen fusionéieren (Fragmenter verbinden)", + "delete": "Zyklus(en) läschen" + } + }, + "split_mode": { + "options": { + "auto": "Idle-Lücken automatesch erkennen", + "manual": "Manuelle Zäitstempel(en)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Ënnerhalt: Daten nei veraarbecht & optimiséieren", + "clear_debug_data": "Debug Daten läschen (Plaz befreien)", + "wipe_history": "ALL Daten fir dësen Apparat läschen (irreversibel)", + "export_import": "Export / Import JSON mat Astellungen (kopéieren/ansetzen)" + } + }, + "export_import_mode": { + "options": { + "export": "All Daten exportéieren", + "import": "Daten importéieren / fusionéieren" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Al Zyklen automatesch etikettéieren", + "select_cycle_to_label": "Spezifesche Zyklus etikettéieren", + "select_cycle_to_delete": "Zyklus läschen", + "interactive_editor": "Fusionéieren / Split Interaktiven Editor", + "trim_cycle": "Zyklus Daten trimmen" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Neie Profil erstellen", + "edit_profile": "Profil änneren / ëmbenennen", + "delete_profile": "Profil läschen", + "profile_stats": "Profil Statistiken", + "cleanup_profile": "Geschicht botzen - Grafik & Läschen", + "assign_phases": "Phase Beräicher zouweisen" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Nei Phase erstellen", + "edit_custom_phase": "Phase änneren", + "delete_custom_phase": "Phase läschen" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Phase Beräich addéieren", + "edit_range": "Phase Beräich änneren", + "delete_range": "Phase Beräich läschen", + "clear_ranges": "All Beräicher läschen", + "auto_detect_ranges": "Phasen auto-detektéieren", + "save_ranges": "Späicheren an zréck" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Status erfrëschen", + "stop_recording": "Opnam stoppen (Späicheren & Veraarbecht)", + "start_recording": "Nei Opnam starten", + "process_recording": "Lescht Opnam veraarbecht (Trim & Späicheren)", + "discard_recording": "Lescht Opnam verworf" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Neie Profil erstellen", + "existing_profile": "Zu bestehenden Profil addéieren", + "discard": "Opnam verworf" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bestätegen - Richteg Detektioun", + "correct": "Korrigéieren - Programm / Dauer auswielen", + "ignore": "Ignoréieren - Falsch Positiv / Noise Zyklus", + "delete": "Läschen - Aus der Geschicht ewechhuelen" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Phasen benennen & uwenden", + "cancel": "Zréck ouni Ännerungen" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Startpunkt setzen", + "set_end": "Endpunkt setzen", + "reset": "Op voll Dauer zrécksetzen", + "apply": "Trim uwenden", + "cancel": "Ofbriechen" + } + }, + "device_type": { + "options": { + "washing_machine": "Wäschmaschinn", + "dryer": "Dryer", + "washer_dryer": "Wäschmaschinn-Trockner Combo", + "dishwasher": "Spullmaschinn", + "coffee_machine": "Kaffismaschinn (ofgeschaaft)", + "ev": "Elektresch Gefier (ofgeschaaft)", + "air_fryer": "Air Fryer", + "heat_pump": "Wärmepompel (deprecéiert)", + "bread_maker": "Brout Maker", + "pump": "Pompel / Pompel Pompel", + "oven": "Uewen (ofgeschaaft)", + "other": "Aner (fortgeschratt)" + } + } + }, + "services": { + "label_cycle": { + "name": "Zyklus etikettéieren", + "description": "Engem vergaangene Zyklus e bestehende Profil zouweisen, oder de Label läschen.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat fir z'etikettéieren." + }, + "cycle_id": { + "name": "Zyklus ID", + "description": "D'ID vum Zyklus fir z'etikettéieren." + }, + "profile_name": { + "name": "Profil Numm", + "description": "Den Numm vun engem bestehende Profil (Profiler am Menü Profiler verwalten erstellen). Eidel loossen fir de Label ze läschen." + } + } + }, + "create_profile": { + "name": "Profil erstellen", + "description": "Erstellt en neie Profil (standalone oder baséiert op engem Referenz Zyklus).", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat." + }, + "profile_name": { + "name": "Profil Numm", + "description": "Numm fir den neie Profil (zB 'Heavy Duty', 'Delicates')." + }, + "reference_cycle_id": { + "name": "Referenz Zyklus ID", + "description": "Fakultativ Zyklus ID op där dëse Profil baséiert." + } + } + }, + "delete_profile": { + "name": "Profil läschen", + "description": "E Profil läschen an fakultativ Zyklen déi et benotzen net markéieren.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat." + }, + "profile_name": { + "name": "Profil Numm", + "description": "De Profil fir ze läschen." + }, + "unlabel_cycles": { + "name": "Zyklen net markéieren", + "description": "Profil Label aus Zyklen mat dësem Profil ewechhuelen." + } + } + }, + "auto_label_cycles": { + "name": "Al Zyklen automatesch etikettéieren", + "description": "Net markéiert Zyklen retroaktiv mat Profil Matching etikettéieren.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat." + }, + "confidence_threshold": { + "name": "Vertrauensgrenz", + "description": "Minimum Match Vertrauen (0,50-0,95) fir Etiketten unzewendeden." + } + } + }, + "export_config": { + "name": "Konfiguratioun exportéieren", + "description": "D'Profiler, Zyklen an Astellunge vun dësem Apparat an eng JSON Datei exportéieren (pro Apparat).", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat fir ze exportéieren." + }, + "path": { + "name": "Pfad", + "description": "Fakultativ absoluten Dateipfad fir ze schreiwen (Standard: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Konfiguratioun importéieren", + "description": "Profiler, Zyklen an Astellunge fir dësen Apparat aus enger JSON Export Datei importéieren (Astellunge kënnen iwwerschriwwe ginn).", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat fir z'importéieren." + }, + "path": { + "name": "Pfad", + "description": "Absoluten Pfad zur Export JSON Datei fir z'importéieren." + } + } + }, + "submit_cycle_feedback": { + "name": "Zyklus Feedback ofginn", + "description": "En auto-detektéierte Programm no engem ofgeschlossene Zyklus bestätegen oder korrigéieren. Entweder `entry_id` (fortgeschratt) oder `device_id` (recommandéiert) uginn.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat (recommandéiert amplaz vun entry_id)." + }, + "entry_id": { + "name": "Entrée ID", + "description": "D'Config Entrée ID fir den Apparat (Alternativ zu device_id)." + }, + "cycle_id": { + "name": "Zyklus ID", + "description": "D'Zyklus ID déi an der Feedback Notifikatioun / Logbicher gewisen gëtt." + }, + "user_confirmed": { + "name": "Detektéierte Programm bestätegen", + "description": "Op richteg setzen wann de festgestallte Programm korrekt ass." + }, + "corrected_profile": { + "name": "Korrigéierte Profil", + "description": "Wann net bestätegt, den richtege Profil / Programmnumm uginn." + }, + "corrected_duration": { + "name": "Korrigéiert Dauer (Sekonnen)", + "description": "Fakultativ korrigéiert Dauer a Sekonnen." + }, + "notes": { + "name": "Notizen", + "description": "Fakultativ Notizen iwwer dëse Zyklus." + } + } + }, + "record_start": { + "name": "Zyklus Opnam starten", + "description": "Manuell eng proppere Zyklus Opnam starten (ëmgeet all Matching Logik).", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat fir opzehuelen." + } + } + }, + "record_stop": { + "name": "Zyklus Opnam stoppen", + "description": "Manuell Opnam stoppen.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat fir d'Opnam ze stoppen." + } + } + }, + "trim_cycle": { + "name": "Zyklus trimmen", + "description": "D'Power Daten vun engem vergaangene Zyklus op eng spezifesch Zäitfenster trimmen.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "Den WashData Apparat." + }, + "cycle_id": { + "name": "Zyklus ID", + "description": "D'ID vum Zyklus fir ze trimmen." + }, + "trim_start_s": { + "name": "Trim Start (Sekonnen)", + "description": "Daten aus dësem Offset a Sekonnen behalen. Standard 0." + }, + "trim_end_s": { + "name": "Trim Enn (Sekonnen)", + "description": "Daten bis zu dësem Offset a Sekonnen behalen. Standard = voll Dauer." + } + } + }, + "pause_cycle": { + "name": "Paus Zyklus", + "description": "Paus den aktive Zyklus fir e WashData Apparat.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "De WashData Apparat fir ze pausen." + } + } + }, + "resume_cycle": { + "name": "Zyklus erëmfannen", + "description": "Fuert e pauséierten Zyklus fir e WashData Apparat erëm.", + "fields": { + "device_id": { + "name": "Apparat", + "description": "De WashData Apparat fir weiderzeféieren." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Leeft" + }, + "match_ambiguity": { + "name": "Match Ambiguitéit" + } + }, + "sensor": { + "washer_state": { + "name": "Zoustand", + "state": { + "off": "Aus", + "idle": "Idle", + "starting": "Ufänken", + "running": "Leeft", + "paused": "Pauséiert", + "user_paused": "Vum Benotzer pauséiert", + "ending": "Ofschléissen", + "finished": "Fäerdeg", + "anti_wrinkle": "Anti-Falten", + "delay_wait": "Waart op Start", + "interrupted": "Ënnerbrach", + "force_stopped": "Manuell gestoppt", + "rinse": "Spülen", + "unknown": "Onbekannt", + "clean": "Propper" + } + }, + "washer_program": { + "name": "Programm" + }, + "time_remaining": { + "name": "Restzäit" + }, + "total_duration": { + "name": "Total Dauer" + }, + "cycle_progress": { + "name": "Fortschrëtt" + }, + "current_power": { + "name": "Aktuell Power" + }, + "elapsed_time": { + "name": "Vergaangen Zäit" + }, + "debug_info": { + "name": "Debug Info" + }, + "match_confidence": { + "name": "Match Vertrauen" + }, + "top_candidates": { + "name": "Top Kandidaten", + "state": { + "none": "Keen" + } + }, + "current_phase": { + "name": "Aktuell Phase" + }, + "wash_phase": { + "name": "Phase" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Zuel", + "unit_of_measurement": "Zyklen" + }, + "suggestions": { + "name": "Verfügbar proposéiert Astellungen" + }, + "pump_runs_today": { + "name": "Pompel leeft (läscht 24 h)" + }, + "cycle_count": { + "name": "Cycle Grof" + } + }, + "select": { + "program_select": { + "name": "Zyklus Programm", + "state": { + "auto_detect": "Auto-Detektioun" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Zyklus manuell stoppen" + }, + "pause_cycle": { + "name": "Paus Zyklus" + }, + "resume_cycle": { + "name": "Zyklus erëmfannen" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Eng valabel device_id ass erfuerderlech." + }, + "cycle_id_required": { + "message": "Eng valabel cycle_id ass erfuerderlech." + }, + "profile_name_required": { + "message": "E gültege Profilnumm ass erfuerderlech." + }, + "device_not_found": { + "message": "Apparat net fonnt." + }, + "no_config_entry": { + "message": "Kee Configuratiounseintrag fir den Apparat fonnt." + }, + "integration_not_loaded": { + "message": "Integratioun net gelueden fir dësen Apparat." + }, + "cycle_not_found_or_no_power": { + "message": "Zyklus net fonnt oder huet keng Power Daten." + }, + "trim_failed_empty_window": { + "message": "Trim gescheitert - Zyklus net fonnt, keng Power Daten oder déi resultéierend Fënster ass eidel." + }, + "trim_invalid_range": { + "message": "trim_end_s muss méi grouss sinn wéi trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/lt.json b/custom_components/ha_washdata/translations/lt.json new file mode 100644 index 0000000..6f79394 --- /dev/null +++ b/custom_components/ha_washdata/translations/lt.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData sąranka", + "description": "Sukonfigūruokite skalbimo mašiną ar kitą prietaisą.\n\nReikalingas galios jutiklis.\n\n**Toliau jūsų paklaus, ar norite susikurti pirmąjį profilį.**", + "data": { + "name": "Įrenginio pavadinimas", + "device_type": "Įrenginio tipas", + "power_sensor": "Galios jutiklis", + "min_power": "Minimalios galios slenkstis (W)" + }, + "data_description": { + "name": "Patogus šio įrenginio pavadinimas (pvz., 'Skalbimo mašina', 'Indaplovė').", + "device_type": "Kokio tipo tai prietaisas? Padeda pritaikyti aptikimą ir ženklinimą.", + "power_sensor": "Jutiklio objektas, kuris realiuoju laiku praneša energijos suvartojimą (vatais) iš jūsų išmaniojo kištuko.", + "min_power": "Galios rodmenys, viršijantys šią ribą (vatais), rodo, kad prietaisas veikia. Pradėkite nuo 2 W daugeliui įrenginių." + } + }, + "first_profile": { + "title": "Sukurkite pirmąjį profilį", + "description": "**Ciklas** yra visas jūsų prietaiso veikimas (pvz., skalbinių įkrovimas). **Profilis** sugrupuoja šiuos ciklus pagal tipą (pvz., 'Medvilnė', 'Greitas skalbimas'). Sukūrus profilį dabar galima nedelsiant apskaičiuoti integracijos trukmę.\n\nGalite rankiniu būdu pasirinkti šį profilį įrenginio valdikliuose, jei jis neaptinkamas automatiškai.\n\n**Jei norite praleisti šį veiksmą, palikite 'Profilio pavadinimas' tuščią.**", + "data": { + "profile_name": "Profilio pavadinimas (neprivaloma)", + "manual_duration": "Numatoma trukmė (min.)" + } + } + }, + "error": { + "cannot_connect": "Nepavyko prisijungti", + "invalid_auth": "Neteisingas autentifikavimas", + "unknown": "Netikėta klaida" + }, + "abort": { + "already_configured": "Įrenginys jau sukonfigūruotas" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Peržiūrėti mokymosi atsiliepimus", + "settings": "Nustatymai", + "notifications": "Pranešimai", + "manage_cycles": "Tvarkyti ciklus", + "manage_profiles": "Tvarkyti profilius", + "manage_phase_catalog": "Tvarkyti fazių katalogą", + "record_cycle": "Ciklo įrašymas (rankinis)", + "diagnostics": "Diagnostika ir priežiūra" + } + }, + "apply_suggestions": { + "title": "Nukopijuokite siūlomas reikšmes", + "description": "Dabartinės siūlomos reikšmės bus nukopijuotos į išsaugotus nustatymus. Tai yra vienkartinis veiksmas ir neįjungia automatinio perrašymo.\n\nSiūlomos kopijuoti vertės:\n{suggested}", + "data": { + "confirm": "Patvirtinkite kopijavimo veiksmą" + } + }, + "apply_suggestions_confirm": { + "title": "Peržiūrėkite siūlomus pakeitimus", + "description": "Rasta „WashData“ **{pending_count}** siūloma (-ių) taikyti vertė (-ės).\n\nPakeitimai:\n{changes}\n\n✅ Pažymėkite toliau esantį laukelį ir spustelėkite **Pateikti**, kad pritaikytumėte ir išsaugotumėte šiuos pakeitimus nedelsiant.\n❌ Palikite jį nepažymėtą ir spustelėkite **Pateikti**, kad atšauktumėte ir grįžtumėte atgal.", + "data": { + "confirm_apply_suggestions": "Taikykite ir išsaugokite šiuos pakeitimus" + } + }, + "settings": { + "title": "Nustatymai", + "description": "{deprecation_warning}Nustatykite aptikimo slenksčius, mokymąsi ir išplėstinį elgesį. Numatytieji nustatymai gerai veikia daugumoje sąrankų.\n\nŠiuo metu galimų siūlomų nustatymų: {suggestions_count}.\nJei šis skaičius viršija 0, atidarykite Išplėstinius nustatymus ir naudokite 'Taikyti siūlomas reikšmes' rekomendacijoms peržiūrėti.", + "data": { + "apply_suggestions": "Taikyti siūlomas reikšmes", + "device_type": "Įrenginio tipas", + "power_sensor": "Galios jutiklio objektas", + "min_power": "Minimali galia (W)", + "off_delay": "Ciklo pabaigos delsa (s)", + "notify_actions": "Pranešimų veiksmai", + "notify_people": "Atidėti pristatymą, kol šie žmonės grįš namo", + "notify_only_when_home": "Atidėti pranešimus, kol pasirinktas asmuo bus namuose", + "notify_fire_events": "Paleisti automatizavimo įvykius", + "notify_start_services": "Ciklo pradžia – pranešimų tikslai", + "notify_finish_services": "Ciklo pabaiga – pranešimų tikslai", + "notify_live_services": "Tiesioginė eiga – pranešimų tikslai", + "notify_before_end_minutes": "Pranešimas prieš pabaigą (min. iki pabaigos)", + "notify_title": "Pranešimo pavadinimas", + "notify_icon": "Pranešimo piktograma", + "notify_start_message": "Pradžios pranešimo formatas", + "notify_finish_message": "Pabaigos pranešimo formatas", + "notify_pre_complete_message": "Išankstinio pabaigos pranešimo formatas", + "show_advanced": "Redaguoti išplėstinius nustatymus (kitas žingsnis)" + }, + "data_description": { + "apply_suggestions": "Pažymėkite šį laukelį, jei norite kopijuoti mokymosi algoritmo siūlomas vertes į formą. Peržiūrėkite prieš išsaugodami.\n\nSiūloma (iš mokymosi):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Pasirinkite prietaiso tipą (skalbimo mašina, džiovintuvas, indaplovė, kavos aparatas, elektromobilis). Ši žyma saugoma su kiekvienu ciklu, kad būtų geriau organizuota.", + "power_sensor": "Jutiklio objektas, pranešantis apie galią realiuoju laiku (vatais). Tai galite bet kada pakeisti neprarasdami istorinių duomenų – tai naudinga, jei pakeisite išmanųjį kištuką.", + "min_power": "Galios rodmenys, viršijantys šią ribą (vatais), rodo, kad prietaisas veikia. Pradėkite nuo 2 W daugeliui įrenginių.", + "off_delay": "Sekundžių skaičius, kurį išlyginta galia turi išlikti žemiau stabdymo slenksčio, kad būtų pažymėta pabaiga. Numatytasis: 120 s.", + "notify_actions": "Pasirenkama Home Assistant veiksmų seka pranešimams (scenarijai, pranešimas, TTS ir kt.). Jei nustatyta, veiksmai atliekami prieš atsarginę pranešimų paslaugą.", + "notify_people": "Žmonių objektai, naudojami buvimo vietai patikrinti. Kai įjungta, pranešimų pristatymas atidedamas, kol bent vienas pasirinktas asmuo yra namuose.", + "notify_only_when_home": "Jei įjungta, atideda pranešimus, kol bent vienas pasirinktas asmuo yra namuose.", + "notify_fire_events": "Jei įjungta, paleidžia Home Assistant ciklo pradžios ir pabaigos įvykius (ha_washdata_cycle_started ir ha_washdata_cycle_ended) automatizavimams valdyti.", + "notify_start_services": "Viena ar kelios tarnybos praneša, kai prasideda ciklas. Palikite tuščią, kad praleistumėte pradžios pranešimus.", + "notify_finish_services": "Viena ar kelios tarnybos įspėja, kai ciklas baigiasi arba artėja prie pabaigos. Palikite tuščią, kad praleistumėte pabaigos pranešimus.", + "notify_live_services": "Viena ar daugiau pranešimų paslaugų, kad gautų tiesioginius eigos atnaujinimus, kai ciklas vyksta (tik mobiliajai programai). Palikite tuščią, kad praleistumėte tiesioginius atnaujinimus.", + "notify_before_end_minutes": "Minutės iki numatomos ciklo pabaigos, kad būtų suaktyvintas pranešimas. Norėdami išjungti, nustatykite 0. Numatytasis: 0.", + "notify_title": "Pasirinktinis pranešimų pavadinimas. Palaikomas {device} rezervuotos vietos žymeklis.", + "notify_icon": "Pranešimams naudojama piktograma (pvz., mdi:washing-machine). Palikite tuščią, kad siųstumėte be piktogramos.", + "notify_start_message": "Pranešimas siunčiamas, kai prasideda ciklas. Palaikomas {device} rezervuotos vietos žymeklis.", + "notify_finish_message": "Pranešimas siunčiamas, kai ciklas baigiasi. Palaikomi {device}, {duration}, {program}, {energy_kwh}, {cost} rezervuotų vietų žymekliai.", + "notify_pre_complete_message": "Pasikartojančios tiesioginės eigos tekstas atnaujinamas, kol vyksta ciklas. Palaikomos {device}, {minutes}, {program} rezervuotos vietos." + } + }, + "notifications": { + "title": "Pranešimai", + "description": "Konfigūruokite pranešimų kanalus, šablonus ir pasirenkamus tiesioginius mobiliojo ryšio eigos naujinius.", + "data": { + "notify_actions": "Pranešimų veiksmai", + "notify_people": "Atidėti pristatymą, kol šie žmonės grįš namo", + "notify_only_when_home": "Atidėti pranešimus, kol pasirinktas asmuo bus namuose", + "notify_fire_events": "Paleisti automatizavimo įvykius", + "notify_start_services": "Ciklo pradžia – pranešimų tikslai", + "notify_finish_services": "Ciklo pabaiga – pranešimų tikslai", + "notify_live_services": "Tiesioginė eiga – pranešimų tikslai", + "notify_before_end_minutes": "Pranešimas prieš pabaigą (min. iki pabaigos)", + "notify_live_interval_seconds": "Tiesioginio atnaujinimo intervalas (sekundėmis)", + "notify_live_overrun_percent": "Tiesioginio atnaujinimo viršijimo leistina norma (%)", + "notify_live_chronometer": "Chronometro atgalinės atskaitos laikmatis", + "notify_title": "Pranešimo pavadinimas", + "notify_icon": "Pranešimo piktograma", + "notify_start_message": "Pradžios pranešimo formatas", + "notify_finish_message": "Pabaigos pranešimo formatas", + "notify_pre_complete_message": "Išankstinio pabaigos pranešimo formatas", + "energy_price_entity": "Energijos kaina – subjektas (neprivaloma)", + "energy_price_static": "Energijos kaina – statinė vertė (neprivaloma)", + "go_back": "Grįžti neišsaugant", + "notify_reminder_message": "Priminimo pranešimo formatas", + "notify_timeout_seconds": "Automatiškai atsisakyti po (sekundėmis)", + "notify_channel": "Pranešimų kanalas (būsena / tiesioginis / priminimas)", + "notify_finish_channel": "Pranešimų kanalas baigtas" + }, + "data_description": { + "go_back": "Pažymėkite tai ir spustelėkite Pateikti, kad atmestumėte visus aukščiau pateiktus pakeitimus ir grįžtumėte į pagrindinį meniu.", + "notify_actions": "Pasirenkama „Home Assistant” veiksmų seka pranešimams (scenarijai, pranešimas, TTS ir kt.). Jei nustatyta, veiksmai atliekami prieš atsarginę pranešimų paslaugą.", + "notify_people": "Žmonių objektai, naudojami buvimo vietai patikrinti. Kai įjungta, pranešimų pristatymas atidedamas, kol bent vienas pasirinktas asmuo yra namuose.", + "notify_only_when_home": "Jei įjungta, atideda pranešimus, kol bent vienas pasirinktas asmuo yra namuose.", + "notify_fire_events": "Jei įjungta, paleidžia „Home Assistant” ciklo pradžios ir pabaigos įvykius (ha_washdata_cycle_started ir ha_washdata_cycle_ended) automatizavimams valdyti.", + "notify_start_services": "Viena ar daugiau pranešimų paslaugų, kurios įspėtų, kai ciklas prasideda (pvz., notify.mobile_app_my_phone). Palikite tuščią, kad praleistumėte pradžios pranešimus.", + "notify_finish_services": "Viena ar kelios tarnybos įspėja, kai ciklas baigiasi arba artėja prie pabaigos. Palikite tuščią, kad praleistumėte pabaigos pranešimus.", + "notify_live_services": "Viena ar daugiau pranešimų paslaugų gauti tiesioginius eigos naujinius, kai ciklas vyksta (tik mobilioji programa). Palikite tuščią, kad praleistumėte tiesioginius atnaujinimus.", + "notify_before_end_minutes": "Minutės iki numatomos ciklo pabaigos, kad būtų suaktyvintas pranešimas. Norėdami išjungti, nustatykite 0. Numatytasis: 0.", + "notify_live_interval_seconds": "Kaip dažnai veikiant siunčiami eigos naujiniai. Mažesnės reikšmės reaguoja greičiau, bet sunaudoja daugiau pranešimų. Taikoma ne mažiau kaip 30 sekundžių – mažesnės nei 30 s reikšmės automatiškai suapvalinamos iki 30 s.", + "notify_live_overrun_percent": "Saugos atsarga, viršijanti numatomą naujinimų skaičių ilgiems / per ilgiems ciklams (pvz., 20 % leidžia 1,2 karto apskaičiuotus naujinius).", + "notify_live_chronometer": "Kai įgalinta, į kiekvieną tiesioginį atnaujinimą įtraukiamas chronometro atgalinis skaičiavimas iki numatomo pabaigos laiko. Pranešimų laikmatis automatiškai įsijungia įrenginyje tarp atnaujinimų (tik „Android“ papildomai programai).", + "notify_title": "Pasirinktinis pranešimų pavadinimas. Palaikomas {device} rezervuotos vietos žymeklis.", + "notify_icon": "Pranešimams naudojama piktograma (pvz., mdi:washing-machine). Palikite tuščią, kad siųstumėte be piktogramos.", + "notify_start_message": "Pranešimas siunčiamas, kai prasideda ciklas. Palaikomas {device} rezervuotos vietos žymeklis.", + "notify_finish_message": "Pranešimas siunčiamas, kai ciklas baigiasi. Palaikomi {device}, {duration}, {program}, {energy_kwh}, {cost} rezervuotų vietų žymekliai.", + "energy_price_entity": "Pasirinkite skaitmeninį objektą, kuriame yra dabartinė elektros kaina (pvz., jutiklis.elektros_kaina, įvesties_numeris.energijos_norma). Kai nustatyta, įgalina rezervuotą vietą {cost}. Turi viršenybę prieš statinę vertę, jei sukonfigūruotos abi.", + "energy_price_static": "Fiksuota elektros kaina už kWh. Kai nustatyta, įgalina rezervuotą vietą {cost}. Nepaisoma, jei subjektas sukonfigūruotas aukščiau. Pranešimo šablone pridėkite savo valiutos simbolį, pvz. {cost} €.", + "notify_reminder_message": "Vienkartinio priminimo tekstas išsiuntė sukonfigūruotą minučių skaičių iki numatomos pabaigos. Skirtingai nuo pasikartojančių tiesioginių atnaujinimų aukščiau. Palaikomos {device}, {minutes}, {program} rezervuotos vietos.", + "notify_timeout_seconds": "Automatiškai atsisakykite pranešimų po tiek sekundžių (persiunčiami kaip papildomos programos laikas). Nustatykite 0, kad jie liktų, kol bus atmesti. Numatytasis: 0.", + "notify_channel": "Android papildomos programos pranešimų kanalas, skirtas būsenos, tiesioginės eigos ir priminimų pranešimams. Kanalo garsas ir svarba sukonfigūruojami papildomoje programoje pirmą kartą naudojant kanalo pavadinimą – WashData nustato tik kanalo pavadinimą. Palikite tuščią, kad nustatytumėte numatytąją programą.", + "notify_finish_channel": "Atskiras Android kanalas baigtam perspėjimui (taip pat naudojamas priminimui ir skalbinių laukimo ragavimui), kad jis galėtų turėti savo garsą. Palikite tuščią, jei norite pakartotinai naudoti aukščiau esantį kanalą.", + "notify_pre_complete_message": "Pasikartojančios tiesioginės eigos tekstas atnaujinamas, kol vyksta ciklas. Palaikomos {device}, {minutes}, {program} rezervuotos vietos." + } + }, + "advanced_settings": { + "title": "Išplėstiniai nustatymai", + "description": "Nustatykite aptikimo slenksčius, mokymąsi ir išplėstinį elgesį. Numatytieji nustatymai gerai veikia daugumoje konfigūracijų.", + "sections": { + "suggestions_section": { + "name": "Siūlomi nustatymai", + "description": "Yra {suggestions_count} išmoktų pasiūlymų. Pažymėkite 'Taikyti siūlomas reikšmes', kad prieš išsaugodami peržiūrėtumėte siūlomus pakeitimus.", + "data": { + "apply_suggestions": "Taikyti siūlomas reikšmes" + }, + "data_description": { + "apply_suggestions": "Pažymėkite šį laukelį, kad atidarytumėte siūlomų verčių iš mokymosi algoritmo peržiūros žingsnį. Niekas neišsaugoma automatiškai.\n\nSiūloma (iš mokymosi):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Aptikimas ir galios slenksčiai", + "description": "Nustato, kada prietaisas laikomas ĮJUNGTU arba IŠJUNGTU, energijos slenksčius ir būsenų perėjimų laiką.", + "data": { + "start_duration_threshold": "Pradžios atmetimo trukmė (s)", + "start_energy_threshold": "Pradžios energijos vartai (Wh)", + "completion_min_seconds": "Min. užbaigimo veikimo laikas (sekundėmis)", + "start_threshold_w": "Pradžios slenkstis (W)", + "stop_threshold_w": "Stabdymo slenkstis (W)", + "end_energy_threshold": "Pabaigos energijos vartai (Wh)", + "running_dead_zone": "Veikimo negyva zona (sekundėmis)", + "end_repeat_count": "Pabaigos pakartojimų skaičius", + "min_off_gap": "Minimalus tarpas tarp ciklų (s)", + "sampling_interval": "Mėginių ėmimo intervalas (s)" + }, + "data_description": { + "start_duration_threshold": "Ignoruokite trumpus galios šuolius, trumpesnius nei ši trukmė (sekundėmis). Filtruoja įkrovos šuolius (pvz., 60 W 2 s) prieš prasidedant tikram ciklui. Numatytasis: 5 s.", + "start_energy_threshold": "Kad būtų patvirtinta, ciklas turi sukaupti bent tiek energijos (Wh) START fazės metu. Numatytasis: 0,005 Wh.", + "completion_min_seconds": "Ciklai, trumpesni nei šis, bus pažymėti kaip „nutrūkę”, net jei jie baigiasi natūraliai. Numatytasis: 600 s.", + "start_threshold_w": "Galia turi VIRŠYTI šį slenkstį, kad būtų PRADĖTAS ciklas. Nustatykite aukščiau nei budėjimo galia. Numatytasis: min_power + 1 W.", + "stop_threshold_w": "Galia turi NUKRISTI ŽEMIAU šio slenksčio, kad BAIGTŲ ciklą. Nustatykite šiek tiek virš nulio, kad išvengtumėte klaidingų pabaigų dėl triukšmo. Numatytasis: 0,6 * min_power.", + "end_energy_threshold": "Ciklas baigiasi tik tuo atveju, jei energija paskutiniame išjungimo delsos lange yra mažesnė nei ši vertė (Wh). Apsaugo nuo klaidingų pabaigų, jei galia svyruoja arti nulio. Numatytasis: 0,05 Wh.", + "running_dead_zone": "Prasidėjus ciklui, tiek sekundžių ignoruoti galios sumažėjimus, kad išvengtumėte klaidingo pabaigos aptikimo pradinėje sūkavimosi fazėje. Norėdami išjungti, nustatykite 0. Numatytasis: 0 s.", + "end_repeat_count": "Skaičius, kiek kartų iš eilės turi būti įvykdyta pabaigos sąlyga (galia mažesnė už slenkstį off_delay laiku) prieš baigiant ciklą. Didesnės vertės apsaugo nuo klaidingų galų pauzių metu. Numatytasis: 1.", + "min_off_gap": "Jei ciklas prasideda per tiek sekundžių nuo ankstesnio pabaigos, jie bus SUJUNGTI į vieną ciklą.", + "sampling_interval": "Minimalus mėginių ėmimo intervalas sekundėmis. Atnaujinimai, greitesni nei šis rodiklis, bus ignoruojami. Numatytasis: 30 s (2 s skalbimo mašinoms, skalbimo mašinoms-džiovykloms ir indaplovėms)." + } + }, + "matching_section": { + "name": "Profilių atitikimas ir mokymasis", + "description": "Nustato, kaip agresyviai WashData lygina vykstančius ciklus su išmoktais profiliais ir kada prašyti atsiliepimo.", + "data": { + "profile_match_min_duration_ratio": "Profilio atitikties min. trukmės santykis (0,1–1,0)", + "profile_match_interval": "Profilio atitikties intervalas (sekundėmis)", + "profile_match_threshold": "Profilio atitikties slenkstis", + "profile_unmatch_threshold": "Profilio neatitikties slenkstis", + "auto_label_confidence": "Automatinio žymėjimo patikimumas (0–1)", + "learning_confidence": "Atsiliepimo užklausos patikimumas (0–1)", + "suppress_feedback_notifications": "Neleiskite atsiliepimų pranešimų", + "duration_tolerance": "Trukmės tolerancija (0–0,5)", + "smoothing_window": "Išlyginimo langas (mėginiai)", + "profile_duration_tolerance": "Profilio atitikties trukmės tolerancija (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimalus profilio derinimo trukmės santykis. Veikimo ciklas turi būti bent ši profilio trukmės dalis, kad atitiktų. Mažesnis = ankstesnis atitikimas. Numatytasis: 0,50 (50%).", + "profile_match_interval": "Kaip dažnai (sekundėmis) bandyti suderinti profilį ciklo metu. Mažesnės vertės užtikrina greitesnį aptikimą, bet naudoja daugiau procesoriaus. Numatytasis: 300 s (5 minutės).", + "profile_match_threshold": "Minimalus DTW panašumo balas (0,0–1,0), reikalingas norint profilį laikyti atitinkančiu. Didesnis = griežtesnis atitikimas, mažiau klaidingų teigiamų rezultatų. Numatytasis: 0,4.", + "profile_unmatch_threshold": "DTW panašumo balas (0,0–1,0), žemiau kurio anksčiau suderintas profilis atmetamas. Turi būti mažesnis už atitikties slenkstį, kad būtų išvengta mirgėjimo. Numatytasis: 0,35.", + "auto_label_confidence": "Automatiškai pažymėti ciklus, kurių patikimumas yra lygus arba didesnis baigiantis (0,0–1,0).", + "learning_confidence": "Minimalus patikimumas, norint paprašyti naudotojo patvirtinimo per įvykį (0,0–1,0).", + "suppress_feedback_notifications": "Kai įjungta, WashData vis tiek stebės ir prašys atsiliepimų viduje, bet nerodys nuolatinių pranešimų, kuriuose prašoma patvirtinti ciklus. Naudinga, kai ciklai visada aptinkami teisingai, o raginimai blaško dėmesį.", + "duration_tolerance": "Likusio laiko įverčių tolerancija veikimo metu (0,0–0,5 atitinka 0–50%).", + "smoothing_window": "Naujausių galios rodmenų, naudojamų slankiojo vidurkio išlyginimui, skaičius.", + "profile_duration_tolerance": "Tolerancija, naudojama profilių atitikimui bendrai trukmės dispersijai (0,0–0,5). Numatytasis elgesys: ±25%." + } + }, + "timing_section": { + "name": "Laikas, priežiūra ir derinimas", + "description": "Watchdog, eigos nustatymas iš naujo, automatinė priežiūra ir derinimo objektų rodymas.", + "data": { + "watchdog_interval": "„Watchdog” intervalas (sekundėmis)", + "no_update_active_timeout": "Neatnaujinimo skirtasis laikas (s)", + "progress_reset_delay": "Eigos atstatymo delsa (sekundėmis)", + "auto_maintenance": "Įjungti automatinę priežiūrą", + "expose_debug_entities": "Rodyti derinimo objektus", + "save_debug_traces": "Išsaugoti derinimo pėdsakus" + }, + "data_description": { + "watchdog_interval": "Sekundžių skaičius tarp „watchdog” patikrinimų veikimo metu. Numatytasis: 5 s. ĮSPĖJIMAS: Įsitikinkite, kad tai yra DAUGIAU nei jūsų jutiklio atnaujinimo intervalas, kad išvengtumėte klaidingų sustojimų (pvz., jei jutiklis atnaujinamas kas 60 s, naudokite 65 s+).", + "no_update_active_timeout": "Paskelbimo keičiant jutikliams: priverstinai baigti AKTYVŲ ciklą tik tuo atveju, jei tiek sekundžių negaunama jokių naujinių. Mažos galios pabaiga vis dar naudoja išjungimo delsą.", + "progress_reset_delay": "Pasibaigus ciklui (100%), palaukite tiek tuščiosios eigos sekundžių, kad iš naujo nustatytumėte eigą į 0%. Numatytasis: 300 s.", + "auto_maintenance": "Įjungti automatinę priežiūrą (remontuoti mėginius, sujungti fragmentus).", + "expose_debug_entities": "Rodyti išplėstinius jutiklius (patikimumas, fazė, dviprasmiškumas) derinimui.", + "save_debug_traces": "Išsaugoti išsamius reitingavimo ir galios sekimo duomenis istorijoje (padidina saugyklos naudojimą)." + } + }, + "anti_wrinkle_section": { + "name": "Apsauga nuo raukšlių (džiovyklėms)", + "description": "Ignoruokite būgno pasukimus po ciklo pabaigos, kad išvengtumėte vaiduoklinių ciklų. Tik džiovintuvas / skalbyklė-džiovyklė.", + "data": { + "anti_wrinkle_enabled": "Apsauga nuo raukšlių (tik džiovintuvas / skalbimo mašina-džiovintuvas)", + "anti_wrinkle_max_power": "Maks. apsaugos nuo raukšlių galia (W) (tik džiovintuvas / skalbimo mašina-džiovintuvas)", + "anti_wrinkle_max_duration": "Maks. apsaugos nuo raukšlių trukmė (s) (tik džiovintuvas / skalbimo mašina-džiovintuvas)", + "anti_wrinkle_exit_power": "Apsaugos nuo raukšlių išėjimo galia (W) (tik džiovintuvas / skalbimo mašina-džiovintuvas)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignoruoti trumpus mažos galios būgno sukimus pasibaigus ciklui, kad išvengtumėte šešėlinių ciklų (tik džiovintuvas / skalbimo mašina-džiovintuvas).", + "anti_wrinkle_max_power": "Sprogimai, kurių galia yra lygi arba mažesnė, traktuojami kaip sukimai nuo raukšlių, o ne nauji ciklai. Taikoma tik tada, kai įjungta apsauga nuo raukšlių.", + "anti_wrinkle_max_duration": "Maksimalus sprogimo ilgis, traktuojamas kaip sukimas nuo raukšlių. Taikoma tik tada, kai įjungta apsauga nuo raukšlių.", + "anti_wrinkle_exit_power": "Jei galia nukrenta žemiau šio lygio, iškart išjungiama apsauga nuo raukšlių (tikras išjungimas). Taikoma tik tada, kai įjungta apsauga nuo raukšlių." + } + }, + "delay_start_section": { + "name": "Atidėto paleidimo aptikimas", + "description": "Nuolatinį budėjimo režimą su maža galia laikykite būsena 'Laukiama pradžios', kol ciklas iš tikrųjų prasidės.", + "data": { + "delay_start_detect_enabled": "Įgalinti atidėto paleidimo aptikimą", + "delay_confirm_seconds": "Atidėtas paleidimas: budėjimo patvirtinimo laikas (s)", + "delay_timeout_hours": "Atidėtas startas: maksimalus laukimo laikas (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kai įjungta, trumpas mažos galios nutekėjimo šuolis, po kurio eina nuolatinis budėjimo režimas, aptinkamas kaip atidėtas paleidimas. Vėlavimo metu būsenos jutiklis rodo pranešimą „Laukiama pradžios“. Joks programos laikas ar eiga nesekami tol, kol ciklas iš tikrųjų neprasideda. Reikalinga, kad start_threshold_w būtų nustatytas virš nutekėjimo smaigalio galios.", + "delay_confirm_seconds": "Galia turi išlikti tarp sustabdymo slenksčio (W) ir paleidimo slenksčio (W) tiek sekundžių, kol WashData pereis į būseną 'Laukiama pradžios'. Taip atfiltruojami trumpi šuoliai naršant meniu. Numatytasis: 60 s.", + "delay_timeout_hours": "Saugos skirtasis laikas: jei aparatas po tiek valandų vis dar yra „Laukiama, kol bus paleista“, „WashData“ iš naujo nustatoma į „Išjungta“. Numatytasis: 8 val." + } + }, + "external_triggers_section": { + "name": "Išoriniai aktyvikliai, durys ir pauzė", + "description": "Pasirenkamas išorinis ciklo pabaigos jutiklis, durų jutiklis pauzei / iškrovimui aptikti ir maitinimo išjungimo jungiklis pauzės metu.", + "data": { + "external_end_trigger_enabled": "Įjungti išorinį ciklo pabaigos aktyviklį", + "external_end_trigger": "Išorinis ciklo pabaigos jutiklis", + "external_end_trigger_inverted": "Apversti aktyviklio logiką (aktyvinti išjungus)", + "door_sensor_entity": "Durų jutiklis", + "pause_cuts_power": "Išjunkite maitinimą pristabdydami", + "switch_entity": "Jungiklio objektas (pristabdyti maitinimo nutraukimą)", + "notify_unload_delay_minutes": "Pranešimo apie skalbinių laukimą delsa (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Įjungti išorinio dvejetainio jutiklio stebėjimą, kad suaktyvintumėte ciklo pabaigą.", + "external_end_trigger": "Pasirinkite dvejetainio jutiklio objektą. Kai šis jutiklis suveikia, dabartinis ciklas baigsis su būsena „baigta”.", + "external_end_trigger_inverted": "Pagal numatytuosius nustatymus aktyviklis suveikia, kai jutiklis įjungiamas. Pažymėkite šį laukelį, kad suveiktų, kai jutiklis išjungiamas.", + "door_sensor_entity": "Pasirenkamas dvejetainis mašinos durelių jutiklis (įjungta = atidaryta, išjungta = uždaryta). Kai durelės atsidaro aktyvaus ciklo metu, WashData patvirtins, kad ciklas buvo tyčia pristabdytas. Pasibaigus ciklui, jei durelės vis dar uždarytos, būsena pasikeičia į „Clean“, kol durys bus atidarytos. Pastaba: uždarius dureles automatiškai NEatnaujinamas pristabdytas ciklas – atnaujinimas turi būti suaktyvintas rankiniu būdu, naudojant mygtuką Tęsti ciklą arba paslauga.", + "pause_cuts_power": "Pristabdydami naudodami mygtuką Pause Cycle arba paslaugą, taip pat išjunkite sukonfigūruotą jungiklio objektą. Įsigalioja tik tada, jei šiam įrenginiui sukonfigūruotas jungiklio objektas.", + "switch_entity": "Perjungimo objektas, perjungiamas pristabdant arba tęsiant. Reikalingas, kai įjungta parinktis „Išjungti maitinimą pristabdant“.", + "notify_unload_delay_minutes": "Pasibaigus ciklui ir durims neatidarius tiek minučių, išsiųskite pranešimą per pranešimo apie pabaigą kanalą (reikalingas durų jutiklis). Norėdami išjungti, nustatykite 0. Numatytasis: 60 min." + } + }, + "pump_section": { + "name": "Siurblio stebėjimas", + "description": "Tik siurblio tipo įrenginiams. Suaktyvina įvykį, jei siurblio ciklas trunka ilgiau nei šis laikas.", + "data": { + "pump_stuck_duration": "Siurblio užstrigimo įspėjimo slenkstis (-ai) (tik siurblys)" + }, + "data_description": { + "pump_stuck_duration": "Jei po tiek sekundžių vis dar veikia siurblio ciklas, vieną kartą suaktyvinamas įvykis ha_washdata_pump_stuck. Naudokite tai norėdami aptikti užstrigusį variklį (įprastas karterio siurblio veikimas yra trumpesnis nei 60 s). Numatytasis: 1800 s (30 min.). Tik siurblio įrenginio tipas." + } + }, + "device_link_section": { + "name": "Įrenginio nuoroda", + "description": "Pasirinktinai susiekite šį „WashData“ įrenginį su esamu Home Assistant įrenginiu (pvz., išmaniuoju kištuku arba pačiu prietaisu). Tada „WashData“ įrenginys rodomas kaip „Prisijungta per tą įrenginį“. Palikite tuščią, kad WashData liktų kaip atskiras įrenginys.", + "data": { + "linked_device": "Susietas įrenginys" + }, + "data_description": { + "linked_device": "Pasirinkite įrenginį, prie kurio norite prijungti „WashData“. Tai prideda nuorodą „Prisijungta per“ įrenginių registre; „WashData“ saugo savo įrenginio kortelę ir objektus. Išvalykite pasirinkimą, kad pašalintumėte nuorodą." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika ir priežiūra", + "description": "Vykdykite priežiūros veiksmus, pvz., suskaidytų ciklų sujungimą arba saugomų duomenų perkėlimą.\n\n**Saugyklos naudojimas**\n\n– Failo dydis: {file_size_kb} KB\n– Ciklai: {cycle_count}\n– Profiliai: {profile_count}\n– Derinimo pėdsakai: {debug_count}", + "menu_options": { + "reprocess_history": "Priežiūra: iš naujo apdoroti ir optimizuoti duomenis", + "clear_debug_data": "Išvalyti derinimo duomenis (atlaisvinti vietos)", + "wipe_history": "Išvalyti VISUS šio įrenginio duomenis (negrįžtama)", + "export_import": "Eksportuoti / importuoti JSON su nustatymais (kopijuoti / įklijuoti)", + "menu_back": "← Atgal" + } + }, + "clear_debug_data": { + "title": "Išvalyti derinimo duomenis", + "description": "Ar tikrai norite ištrinti visus saugomus derinimo pėdsakus? Tai atlaisvins vietos, bet pašalins išsamią reitingavimo informaciją iš ankstesnių ciklų." + }, + "manage_cycles": { + "title": "Tvarkyti ciklus", + "description": "Naujausi ciklai:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatinis senų ciklų žymėjimas", + "select_cycle_to_label": "Pažymėti konkretų ciklą", + "select_cycle_to_delete": "Ištrinti ciklą", + "interactive_editor": "Sujungimo / padalijimo interaktyvus redaktorius", + "trim_cycle_select": "Apkarpyti ciklo duomenis", + "menu_back": "← Atgal" + } + }, + "manage_cycles_empty": { + "title": "Tvarkyti ciklus", + "description": "Dar nėra užfiksuotų ciklų.", + "menu_options": { + "auto_label_cycles": "Automatinis senų ciklų žymėjimas", + "menu_back": "← Atgal" + } + }, + "manage_profiles": { + "title": "Tvarkyti profilius", + "description": "Profilio santrauka:\n{profile_summary}", + "menu_options": { + "create_profile": "Sukurti naują profilį", + "edit_profile": "Redaguoti / pervardyti profilį", + "delete_profile_select": "Ištrinti profilį", + "profile_stats": "Profilio statistika", + "cleanup_profile": "Išvalyti istoriją – diagrama ir ištrynimas", + "assign_profile_phases_select": "Priskirti fazių diapazonus", + "menu_back": "← Atgal" + } + }, + "manage_profiles_empty": { + "title": "Tvarkyti profilius", + "description": "Dar nesukurta jokių profilių.", + "menu_options": { + "create_profile": "Sukurti naują profilį", + "menu_back": "← Atgal" + } + }, + "manage_phase_catalog": { + "title": "Tvarkyti fazių katalogą", + "description": "Fazių katalogas (numatytieji šio įrenginio nustatymai + tinkintos fazės):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Sukurti naują fazę", + "phase_catalog_edit_select": "Redaguoti fazę", + "phase_catalog_delete": "Ištrinti fazę", + "menu_back": "← Atgal" + } + }, + "phase_catalog_create": { + "title": "Sukurti pasirinktinę fazę", + "description": "Sukurkite pasirinktinį fazės pavadinimą ir aprašą ir pasirinkite, kuriam įrenginio tipui jis taikomas.", + "data": { + "target_device_type": "Tikslinio įrenginio tipas", + "phase_name": "Fazės pavadinimas", + "phase_description": "Fazės aprašymas" + } + }, + "phase_catalog_edit_select": { + "title": "Redaguoti pasirinktinę fazę", + "description": "Pasirinkite tinkintą fazę, kurią norite redaguoti.", + "data": { + "phase_name": "Pasirinktinė fazė" + } + }, + "phase_catalog_edit": { + "title": "Redaguoti pasirinktinę fazę", + "description": "Redaguojama fazė: {phase_name}", + "data": { + "phase_name": "Fazės pavadinimas", + "phase_description": "Fazės aprašymas" + } + }, + "phase_catalog_delete": { + "title": "Ištrinti pasirinktinę fazę", + "description": "Pasirinkite tinkintą fazę, kurią norite ištrinti. Visi priskirti diapazonai naudojant šią fazę bus pašalinti.", + "data": { + "phase_name": "Pasirinktinė fazė" + } + }, + "assign_profile_phases": { + "title": "Priskirti fazių diapazonus", + "description": "Profilio fazės peržiūra: **{profile_name}**\n\n{timeline_svg}\n\n**Dabartiniai diapazonai:**\n{current_ranges}\n\nNorėdami pridėti, redaguoti, ištrinti arba išsaugoti diapazonus, naudokite toliau nurodytus veiksmus.", + "menu_options": { + "assign_profile_phases_add": "Pridėti fazių diapazoną", + "assign_profile_phases_edit_select": "Redaguoti fazių diapazoną", + "assign_profile_phases_delete": "Ištrinti fazių diapazoną", + "phase_ranges_clear": "Išvalyti visus diapazonus", + "assign_profile_phases_auto_detect": "Automatiškai aptikti fazes", + "phase_ranges_save": "Išsaugoti ir grąžinti", + "menu_back": "← Atgal" + } + }, + "assign_profile_phases_add": { + "title": "Pridėti fazių diapazoną", + "description": "Pridėkite vieną fazių diapazoną prie esamo juodraščio.", + "data": { + "phase_name": "Fazė", + "start_min": "Pradžios minutė", + "end_min": "Pabaigos minutė" + } + }, + "assign_profile_phases_edit_select": { + "title": "Redaguoti fazių diapazoną", + "description": "Pasirinkite norimą redaguoti diapazoną.", + "data": { + "range_index": "Fazių diapazonas" + } + }, + "assign_profile_phases_edit": { + "title": "Redaguoti fazių diapazoną", + "description": "Atnaujinkite pasirinktą fazių diapazoną.", + "data": { + "phase_name": "Fazė", + "start_min": "Pradžios minutė", + "end_min": "Pabaigos minutė" + } + }, + "assign_profile_phases_delete": { + "title": "Ištrinti fazių diapazoną", + "description": "Pasirinkite diapazoną, kurį norite pašalinti iš juodraščio.", + "data": { + "range_index": "Fazių diapazonas" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatiškai aptiktos fazės", + "description": "Profilis: **{profile_name}**\n\n{timeline_svg}\n\n**Automatiškai aptikta {detected_count} fazė (-ės).** Toliau pasirinkite veiksmą.", + "data": { + "action": "Veiksmas" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Pavadinkite aptiktas fazes", + "description": "Profilis: **{profile_name}**\n\n**Aptikta {detected_count} fazė (-ės):**\n{ranges_summary}\n\nKiekvienai fazei priskirkite pavadinimą." + }, + "assign_profile_phases_select": { + "title": "Priskirti fazių diapazonus", + "description": "Pasirinkite profilį, kad sukonfigūruotumėte fazių diapazonus.", + "data": { + "profile": "Profilis" + } + }, + "profile_stats": { + "title": "Profilio statistika", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Sukurti naują profilį", + "description": "Sukurkite naują profilį rankiniu būdu arba iš praėjusio ciklo.\n\nProfilio pavadinimo pavyzdžiai: „Smulkūs audiniai”, „Intensyvus skalbimas”, „Greitas skalbimas”", + "data": { + "profile_name": "Profilio pavadinimas", + "reference_cycle": "Etaloninis ciklas (neprivaloma)", + "manual_duration": "Rankinė trukmė (min.)" + } + }, + "edit_profile": { + "title": "Redaguoti profilį", + "description": "Pasirinkite profilį, kurį norite pervardyti", + "data": { + "profile": "Profilis" + } + }, + "rename_profile": { + "title": "Redaguoti profilio informaciją", + "description": "Dabartinis profilis: {current_name}", + "data": { + "new_name": "Profilio pavadinimas", + "manual_duration": "Rankinė trukmė (min.)" + } + }, + "delete_profile_select": { + "title": "Ištrinti profilį", + "description": "Pasirinkite profilį, kurį norite ištrinti", + "data": { + "profile": "Profilis" + } + }, + "delete_profile_confirm": { + "title": "Patvirtinkite profilio ištrynimą", + "description": "⚠️ Tai visam laikui ištrins profilį: {profile_name}", + "data": { + "unlabel_cycles": "Pašalinti etiketę nuo ciklų, naudojančių šį profilį" + } + }, + "auto_label_cycles": { + "title": "Automatinis senų ciklų žymėjimas", + "description": "Iš viso rasta {total_count} ciklai. Profiliai: {profiles}", + "data": { + "confidence_threshold": "Minimalus patikimumas (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Pasirinkite ciklą žymėjimui", + "description": "✓ = baigta, ⚠ = atnaujinta, ✗ = nutraukta", + "data": { + "cycle_id": "Ciklas" + } + }, + "select_cycle_to_delete": { + "title": "Ištrinti ciklą", + "description": "⚠️ Tai visam laikui ištrins pasirinktą ciklą", + "data": { + "cycle_id": "Ciklas ištrinimui" + } + }, + "label_cycle": { + "title": "Pažymėti ciklą", + "description": "{cycle_info}", + "data": { + "profile_name": "Profilis", + "new_profile_name": "Naujas profilio pavadinimas (jei kuriamas)" + } + }, + "post_process": { + "title": "Apdoroti istoriją (sujungti / išskaidyti)", + "description": "Išvalykite ciklo istoriją sujungdami suskaidytus paleidimus ir išskaidydami neteisingai sujungtus ciklus per laiko langą. Įveskite valandų skaičių, kiek norite peržiūrėti (visiems naudokite 999999).", + "data": { + "time_range": "Peržiūros langas (valandomis)", + "gap_seconds": "Sujungimo / padalijimo tarpas (sekundėmis)" + }, + "data_description": { + "time_range": "Valandų skaičius, per kurį reikia nuskaityti suskaidytus ciklus, kad būtų sujungti. Norėdami apdoroti visus istorinius duomenis, naudokite 999999.", + "gap_seconds": "Slenkstis, kada sujungti, o kada skaidyti. Tarpai TRUMPESNI nei šis bus sujungti. Tarpai ILGESNI nei šis bus išskaidyti. Sumažinkite, kad priverstinai padalintumėte neteisingai sujungtą ciklą." + } + }, + "cleanup_profile": { + "title": "Išvalyti istoriją – pasirinkite profilį", + "description": "Pasirinkite profilį, kurį norite vizualizuoti. Tai sugeneruos diagramą, rodančią visus praėjusius šio profilio ciklus, kad būtų lengviau nustatyti nuokrypius.", + "data": { + "profile": "Profilis" + } + }, + "cleanup_select": { + "title": "Išvalyti istoriją – diagrama ir ištrynimas", + "description": "![Diagrama]({graph_url})\n\n**Vizualizuojamas {profile_name}**\n\nDiagramoje atskiri ciklai rodomi spalvotomis linijomis. Nustatykite nuokrypius (linijos, esančios toli nuo grupės) ir suderinkite jų spalvas toliau pateiktame sąraše, kad jas ištrintumėte.\n\n**Pasirinkite ciklus, kuriuos norite VISAM LAIKUI ištrinti:**", + "data": { + "cycles_to_delete": "Ciklai ištrinimui (patikrinkite atitinkančias spalvas)" + } + }, + "interactive_editor": { + "title": "Sujungimo / padalijimo interaktyvus redaktorius", + "description": "Pasirinkite rankinį veiksmą, kurį norite atlikti ciklų istorijoje.", + "menu_options": { + "editor_split": "Padalinti ciklą (rasti tarpus)", + "editor_merge": "Sujungti ciklus (sujungti fragmentus)", + "editor_delete": "Ištrinti ciklą (-us)", + "menu_back": "← Atgal" + } + }, + "editor_select": { + "title": "Pasirinkite ciklus", + "description": "Pasirinkite ciklą (-us), kurį (-iuos) norite apdoroti.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklai" + } + }, + "editor_configure": { + "title": "Konfigūruoti ir taikyti", + "description": "{preview_md}", + "data": { + "confirm_action": "Veiksmas", + "merged_profile": "Gautas profilis", + "new_profile_name": "Naujas profilio pavadinimas", + "confirm_commit": "Taip, patvirtinu šį veiksmą", + "segment_0_profile": "1 segmento profilis", + "segment_1_profile": "2 segmento profilis", + "segment_2_profile": "3 segmento profilis", + "segment_3_profile": "4 segmento profilis", + "segment_4_profile": "5 segmento profilis", + "segment_5_profile": "6 segmento profilis", + "segment_6_profile": "7 segmento profilis", + "segment_7_profile": "8 segmento profilis", + "segment_8_profile": "9 segmento profilis", + "segment_9_profile": "10 segmento profilis" + } + }, + "editor_split_params": { + "title": "Padalijimo konfigūracija", + "description": "Sureguliuokite šio ciklo padalijimo jautrumą. Bet koks tuščiosios eigos tarpas, ilgesnis nei šis, sukels padalijimą.", + "data": { + "split_mode": "Padalijimo metodas" + } + }, + "editor_split_auto_params": { + "title": "Automatinio padalijimo konfigūracija", + "description": "Sureguliuokite šio ciklo padalijimo jautrumą. Bet koks ilgesnis neveikos tarpas sukels padalijimą.", + "data": { + "min_gap_seconds": "Padalijimo tarpo slenkstis (sekundės)" + } + }, + "editor_split_manual_params": { + "title": "Rankinės padalijimo laiko žymos", + "description": "{preview_md}Ciklo langas: **{cycle_start_wallclock} → {cycle_end_wallclock}** {cycle_date}.\n\nĮveskite vieną ar daugiau padalijimo laiko žymų (`HH:MM` arba `HH:MM:SS`), po vieną eilutėje. Ciklas bus perkirstas ties kiekviena laiko žyma — N laiko žymų sukuria N+1 segmentą.", + "data": { + "split_timestamps": "Padalijimo laiko žymos" + } + }, + "reprocess_history": { + "title": "Priežiūra: iš naujo apdoroti ir optimizuoti duomenis", + "description": "Atlieka gilų istorinių duomenų valymą:\n1. Apkarpo nulinės galios rodmenis (tyla).\n2. Ištaiso ciklo laiką / trukmę.\n3. Perskaičiuoja visus statistinius modelius (vokus).\n\n**Paleiskite tai, kad ištaisytumėte duomenų problemas arba pritaikytumėte naujus algoritmus.**" + }, + "wipe_history": { + "title": "Išvalyti istoriją (tik testavimui)", + "description": "⚠️ Tai visam laikui ištrins VISUS išsaugotus šio įrenginio ciklus ir profilius. To negalima anuliuoti!" + }, + "export_import": { + "title": "Eksportuoti / importuoti JSON", + "description": "Kopijuoti / įklijuoti šio įrenginio ciklus, profilius IR nustatymus. Eksportuokite žemiau arba įklijuokite JSON importavimui.", + "data": { + "mode": "Veiksmas", + "json_payload": "Pilna konfigūracijos JSON" + }, + "data_description": { + "mode": "Pasirinkite Eksportuoti, kad nukopijuotumėte esamus duomenis ir nustatymus, arba Importuoti, kad įklijuotumėte eksportuotus duomenis iš kito WashData įrenginio.", + "json_payload": "Eksportuojant: nukopijuokite šį JSON (apima ciklus, profilius ir visus patikslintus nustatymus). Importuojant: įklijuokite JSON, eksportuotą iš WashData." + } + }, + "record_cycle": { + "title": "Įrašyti ciklą", + "description": "Rankiniu būdu įrašykite ciklą, kad sukurtumėte švarų profilį be trukdžių.\n\nBūsena: **{status}**\nTrukmė: {duration} sek\nMėginiai: {samples}", + "menu_options": { + "record_refresh": "Atnaujinti būseną", + "record_stop": "Sustabdyti įrašymą (išsaugoti ir apdoroti)", + "record_start": "Pradėti naują įrašymą", + "record_process": "Apdoroti paskutinį įrašą (apkarpyti ir išsaugoti)", + "record_discard": "Atmesti paskutinį įrašą", + "menu_back": "← Atgal" + } + }, + "record_process": { + "title": "Apdoroti įrašymą", + "description": "![Diagrama]({graph_url})\n\n**Diagramoje rodomas neapdorotas įrašas.** Mėlyna = išlaikyti, raudona = siūlomas apkarpymas.\n*Diagrama yra statinė ir neatnaujinama keičiantis formai.*\n\nĮrašymas sustabdytas. Apkarpymai sulygiuojami pagal aptiktą mėginių ėmimo dažnį (~{sampling_rate} s).\n\nNeapdorota trukmė: {duration} sek\nMėginiai: {samples}", + "data": { + "head_trim": "Apkarpymo pradžia (sekundėmis)", + "tail_trim": "Apkarpymo pabaiga (sekundėmis)", + "save_mode": "Išsaugojimo vieta", + "profile_name": "Profilio pavadinimas" + } + }, + "learning_feedbacks": { + "title": "Mokymosi atsiliepimai", + "description": "Laukiantys atsiliepimai: {count}\n\n{pending_table}\n\nPasirinkite ciklo peržiūros užklausą, kurią norite apdoroti.", + "menu_options": { + "learning_feedbacks_pick": "Peržiūrėti laukiantį atsiliepimą", + "learning_feedbacks_dismiss_all": "Atmesti visus laukiančius atsiliepimus", + "menu_back": "← Atgal" + } + }, + "learning_feedbacks_pick": { + "title": "Pasirinkite atsiliepimą peržiūrai", + "description": "Pasirinkite ciklo peržiūros užklausą, kurią norite atidaryti.", + "data": { + "selected_feedback": "Pasirinkite ciklą peržiūrai" + } + }, + "learning_feedbacks_empty": { + "title": "Mokymosi atsiliepimai", + "description": "Laukiančių atsiliepimų užklausų nerasta.", + "menu_options": { + "menu_back": "← Atgal" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Atmesti visus laukiančius atsiliepimus", + "description": "Ketinate atmesti **{count}** laukiančias atsiliepimų užklausas. Atmesti ciklai liks jūsų istorijoje, tačiau neprisidės nauju mokymosi signalu. To negalima atšaukti.\n\n✅ Pažymėkite toliau esantį laukelį ir spustelėkite **Pateikti**, kad atmestumėte viską.\n❌ Palikite jį nepažymėtą ir spustelėkite **Pateikti**, kad atšauktumėte ir grįžtumėte atgal.", + "data": { + "confirm_dismiss_all": "Patvirtinti: atmesti visas laukiančias atsiliepimų užklausas" + } + }, + "resolve_feedback": { + "title": "Išspręsti atsiliepimą", + "description": "{comparison_img}{candidates_table}\n**Aptiktas profilis**: {detected_profile} ({confidence_pct}%)\n**Numatoma trukmė**: {est_duration_min} min\n**Faktinė trukmė**: {act_duration_min} min\n\n__Pasirinkite veiksmą toliau:__", + "data": { + "action": "Ką norėtumėte daryti?", + "corrected_profile": "Teisinga programa (jei taisoma)", + "corrected_duration": "Teisinga trukmė sekundėmis (jei taisoma)" + } + }, + "trim_cycle_select": { + "title": "Ciklo apkarpymas - pasirinkite ciklą", + "description": "Pasirinkite ciklą, kurį norite apkarpyti. ✓ = baigta, ⚠ = atnaujinta, ✗ = nutraukta", + "data": { + "cycle_id": "Ciklas" + } + }, + "trim_cycle": { + "title": "Apkarpyti ciklą", + "description": "Dabartinis langas: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Veiksmas" + } + }, + "trim_cycle_start": { + "title": "Nustatyti apkarpymo pradžią", + "description": "Ciklo langas: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nDabartinė pradžia: **{current_wallclock}** (+{current_offset_min} min. nuo ciklo pradžios)\n\nPasirinkite naują pradžios laiką naudodami toliau pateiktą laikrodžio parinkiklį.", + "data": { + "trim_start_time": "Naujas pradžios laikas", + "trim_start_min": "Nauja pradžia (minutės nuo ciklo pradžios)" + } + }, + "trim_cycle_end": { + "title": "Nustatyti apkarpymo pabaigą", + "description": "Ciklo langas: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nDabartinė pabaiga: **{current_wallclock}** (+{current_offset_min} min. nuo ciklo pradžios)\n\nPasirinkite naują pabaigos laiką naudodami toliau pateiktą laikrodžio parinkiklį.", + "data": { + "trim_end_time": "Naujas pabaigos laikas", + "trim_end_min": "Nauja pabaiga (minutės nuo ciklo pradžios)" + } + } + }, + "error": { + "import_failed": "Importuoti nepavyko. Įklijuokite tinkamą „WashData” eksportavimo JSON.", + "select_exactly_one": "Pasirinkite tiksliai vieną ciklą šiam veiksmui.", + "select_at_least_one": "Pasirinkite bent vieną ciklą šiam veiksmui.", + "select_at_least_two": "Pasirinkite bent du ciklus šiam veiksmui.", + "profile_exists": "Profilis tokiu pavadinimu jau yra", + "rename_failed": "Nepavyko pervardyti profilio. Profilio gali nebūti arba naujas pavadinimas jau užimtas.", + "assignment_failed": "Nepavyko priskirti profilio ciklui", + "duplicate_phase": "Fazė tokiu pavadinimu jau yra.", + "phase_not_found": "Pasirinkta fazė nerasta.", + "invalid_phase_name": "Fazės pavadinimas negali būti tuščias.", + "phase_name_too_long": "Fazės pavadinimas per ilgas.", + "invalid_phase_range": "Fazių diapazonas netinkamas. Pabaiga turi būti didesnė už pradžią.", + "invalid_phase_timestamp": "Laiko žyma neteisinga. Naudokite MMMM-MM-DD VV:MM arba VV:MM formatą.", + "overlapping_phase_ranges": "Fazių diapazonai sutampa. Sureguliuokite diapazonus ir bandykite dar kartą.", + "incomplete_phase_row": "Kiekvienoje fazių eilutėje turi būti fazė ir visos pasirinkto režimo pradžios / pabaigos reikšmės.", + "timestamp_mode_cycle_required": "Naudodami laiko žymos režimą pasirinkite šaltinio ciklą.", + "no_phase_ranges": "Dar nėra šiam veiksmui skirtų fazių diapazonų.", + "feedback_notification_title": "„WashData”: patikrinti ciklą ({device})", + "feedback_notification_message": "**Įrenginys**: {device}\n**Programa**: {program} ({confidence}% patikimumas)\n**Laikas**: {time}\n\n„WashData” reikia jūsų pagalbos patikrinti šį aptiktą ciklą.\n\nEikite į **Nustatymai > Įrenginiai ir paslaugos > WashData > Konfigūruoti > Mokymosi atsiliepimai**, kad patvirtintumėte arba pataisytumėte šį rezultatą.", + "suggestions_ready_notification_title": "„WashData”: siūlomi nustatymai paruošti ({device})", + "suggestions_ready_notification_message": "**Siūlomų nustatymų** jutiklis dabar praneša apie **{count}** įgyvendinamas rekomendacijas.\n\nNorėdami juos peržiūrėti ir pritaikyti: **Nustatymai > Įrenginiai ir paslaugos > „WashData” > Konfigūruoti > Išplėstiniai nustatymai > Taikyti siūlomas reikšmes**.\n\nPasiūlymai yra neprivalomi ir rodomi peržiūrėti prieš išsaugant.", + "trim_range_invalid": "Pradžia turi būti prieš pabaigą. Sureguliuokite apkarpymo taškus.", + "trim_failed": "Apkarpyti nepavyko. Ciklo gali nebebūti arba langas tuščias.", + "invalid_split_timestamp": "Laiko žyma netinkama arba nepatenka į ciklo langą. Naudokite HH:MM arba HH:MM:SS ciklo lange.", + "no_split_segments_found": "Iš šių laiko žymų nepavyko sukurti tinkamų segmentų. Įsitikinkite, kad kiekviena laiko žyma yra ciklo lange ir segmentai trunka bent 1 minutę.", + "auto_tune_suggestion": "{device_type} {device_title} aptikta vaiduoklio ciklų. Siūlomas min_galios pokytis: {current_min}W -> {new_min}W (netaikomas automatiškai).", + "auto_tune_title": "„WashData“ automatinis derinimas", + "auto_tune_fallback": "{device_type} {device_title} aptikta vaiduoklio ciklų. Siūlomas min_galios pokytis: {current_min}W -> {new_min}W (netaikomas automatiškai)." + }, + "abort": { + "no_cycles_found": "Ciklų nerasta", + "no_split_segments_found": "Pasirinktame cikle nerasta segmentų, kuriuos būtų galima padalyti.", + "cycle_not_found": "Nepavyko įkelti pasirinkto ciklo.", + "no_power_data": "Nėra pasirinkto ciklo galios sekimo duomenų.", + "no_profiles_found": "Nerasta jokių profilių. Pirmiausia sukurkite profilį.", + "no_profiles_for_matching": "Nėra profilių atitikimui. Pirmiausia sukurkite profilius.", + "no_unlabeled_cycles": "Visi ciklai jau pažymėti", + "no_suggestions": "Siūlomų verčių dar nėra. Paleiskite kelis ciklus ir bandykite dar kartą.", + "no_predictions": "Jokių prognozių", + "no_custom_phases": "Nerasta tinkintų fazių.", + "reprocess_success": "Sėkmingai iš naujo apdoroti {count} ciklai.", + "debug_data_cleared": "Derinimo duomenys sėkmingai išvalyti iš {count} ciklų." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Ciklo pradžia", + "cycle_finish": "Ciklo pabaiga", + "cycle_live": "Tiesioginė eiga" + } + }, + "common_text": { + "options": { + "unlabeled": "(be etiketės)", + "create_new_profile": "Sukurti naują profilį", + "remove_label": "Pašalinti etiketę", + "no_reference_cycle": "(Nėra etaloninio ciklo)", + "all_device_types": "Visi įrenginių tipai", + "current_suffix": "(dabartinis)", + "editor_select_info": "Pasirinkite 1 ciklą padalijimui arba 2+ ciklus sujungimui.", + "editor_select_info_split": "Pasirinkite 1 ciklą padalijimui.", + "editor_select_info_merge": "Pasirinkite 2+ ciklus sujungimui.", + "editor_select_info_delete": "Pasirinkite 1+ ciklus ištrynimui.", + "no_cycles_recorded": "Dar nėra užfiksuotų ciklų.", + "profile_summary_line": "– **{name}**: {count} ciklų, {avg} m vid.", + "no_profiles_created": "Dar nesukurta jokių profilių.", + "phase_preview": "Fazės peržiūra", + "phase_preview_no_curve": "Vidutinė profilio kreivė dar nepasiekiama. Vykdykite / žymėkite daugiau šio profilio ciklų.", + "average_power_curve": "Vidutinės galios kreivė", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciklų, ~{duration} m vid.)", + "profile_option_short_fmt": "{name} ({count} ciklų, ~{duration} m)", + "deleted_cycles_from_profile": "Ištrinta {count} ciklų iš {profile}.", + "not_enough_data_graph": "Nepakanka duomenų diagramai sukurti.", + "no_profiles_found": "Nerasta jokių profilių.", + "cycle_info_fmt": "Ciklas: {start}, {duration} m, dabartinė: {label}", + "top_candidates_header": "### Geriausi kandidatai", + "tbl_profile": "Profilis", + "tbl_confidence": "Patikimumas", + "tbl_correlation": "Koreliacija", + "tbl_duration_match": "Trukmės atitikimas", + "detected_profile": "Aptiktas profilis", + "estimated_duration": "Numatoma trukmė", + "actual_duration": "Faktinė trukmė", + "choose_action": "__Pasirinkite veiksmą toliau:__", + "tbl_count": "Skaičius", + "tbl_avg": "Vid.", + "tbl_min": "Min.", + "tbl_max": "Maks.", + "tbl_energy_avg": "Energija (vid.)", + "tbl_energy_total": "Energija (iš viso)", + "tbl_consistency": "Nuoseklumas", + "tbl_last_run": "Paskutinis paleidimas", + "graph_legend_title": "Diagramos legenda", + "graph_legend_body": "Mėlyna juosta rodo stebėtą minimalų ir maksimalų galios suvartojimo diapazoną. Linija rodo vidutinės galios kreivę.", + "recording_preview": "Įrašymo peržiūra", + "trim_start": "Apkarpymo pradžia", + "trim_end": "Apkarpymo pabaiga", + "split_preview_title": "Padalijimo peržiūra", + "split_preview_found_fmt": "Rasta {count} segmentų.", + "split_preview_confirm_fmt": "Spustelėkite Patvirtinti, kad padalintumėte šį ciklą į {count} atskirus ciklus.", + "merge_preview_title": "Sujungimo peržiūra", + "merge_preview_joining_fmt": "Sujungiama {count} ciklų. Tarpai bus užpildyti 0 W rodmenimis.", + "editor_delete_preview_title": "Trynimo peržiūra", + "editor_delete_preview_intro": "Pasirinkti ciklai bus ištrinti visam laikui:", + "editor_delete_preview_confirm": "Spustelėkite Patvirtinti, kad visam laikui ištrintumėte šiuos ciklų įrašus.", + "no_power_preview": "*Peržiūrai galios duomenų nėra.*", + "profile_comparison": "Profilių palyginimas", + "actual_cycle_label": "Šis ciklas (faktinis)", + "storage_usage_header": "Saugyklos naudojimas", + "storage_file_size": "Failo dydis", + "storage_cycles": "Ciklai", + "storage_profiles": "Profiliai", + "storage_debug_traces": "Derinimo pėdsakai", + "overview_suffix": "Apžvalga", + "phase_preview_for_profile": "Fazės peržiūra profiliui", + "current_ranges_header": "Dabartiniai diapazonai", + "assign_phases_help": "Norėdami pridėti, redaguoti, ištrinti arba išsaugoti diapazonus, naudokite toliau nurodytus veiksmus.", + "profile_summary_header": "Profilio santrauka", + "recent_cycles_header": "Naujausi ciklai", + "trim_cycle_preview_title": "Ciklo apkarpymo peržiūra", + "trim_cycle_preview_no_data": "Nėra šio ciklo galios duomenų.", + "trim_cycle_preview_kept_suffix": "išlaikyta", + "table_program": "Programa", + "table_when": "Kada", + "table_length": "Trukmė", + "table_match": "Atitikimas", + "table_profile": "Profilis", + "table_cycles": "Ciklai", + "table_avg_length": "Vid. trukmė", + "table_last_run": "Paskutinis paleidimas", + "table_avg_energy": "Vid. energija", + "table_detected_program": "Aptikta programa", + "table_confidence": "Patikimumas", + "table_reported": "Pranešta", + "phase_builtin_suffix": "(Įtaisytas)", + "phase_none_available": "Fazių nėra.", + "settings_deprecation_warning": "⚠️ **Nebenaudojamo įrenginio tipas:** {device_type} planuojama pašalinti būsimame leidime. „WashData“ atitikimo vamzdynas neduoda patikimų rezultatų šiai prietaisų klasei. Jūsų integracija veikia per nusidėvėjimo laikotarpį; Norėdami nutildyti šį įspėjimą, perjunkite toliau pateiktą **Įrenginio tipą** į vieną iš palaikomų tipų (skalbimo mašina, džiovyklė, skalbimo mašina-džiovyklė kombinuota, indaplovė, oro gruzdintuvė, duonos gaminimo mašina arba siurblys) arba į **Kita (išplėstinė)**, jei jūsų prietaisas neatitinka nė vieno iš palaikomų tipų. **Kita (išplėstinė)** tyčia pristato bendruosius numatytuosius nustatymus, kurie nėra pritaikyti jokiam konkrečiam įrenginiui, todėl turėsite patys sukonfigūruoti slenksčius, skirtąjį laiką ir atitinkamus parametrus; perjungus išsaugomi visi esami nustatymai.", + "phase_other_device_types": "Kiti įrenginių tipai:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Padalinti ciklą (rasti tarpus)", + "merge": "Sujungti ciklus (sujungti fragmentus)", + "delete": "Ištrinti ciklą (-us)" + } + }, + "split_mode": { + "options": { + "auto": "Automatiškai aptikti neveiklos tarpus", + "manual": "Rankinės laiko žymos" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Priežiūra: iš naujo apdoroti ir optimizuoti duomenis", + "clear_debug_data": "Išvalyti derinimo duomenis (atlaisvinti vietos)", + "wipe_history": "Išvalyti VISUS šio įrenginio duomenis (negrįžtama)", + "export_import": "Eksportuoti / importuoti JSON su nustatymais (kopijuoti / įklijuoti)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksportuoti visus duomenis", + "import": "Importuoti / sujungti duomenis" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatinis senų ciklų žymėjimas", + "select_cycle_to_label": "Pažymėti konkretų ciklą", + "select_cycle_to_delete": "Ištrinti ciklą", + "interactive_editor": "Sujungimo / padalijimo interaktyvus redaktorius", + "trim_cycle": "Apkarpyti ciklo duomenis" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Sukurti naują profilį", + "edit_profile": "Redaguoti / pervardyti profilį", + "delete_profile": "Ištrinti profilį", + "profile_stats": "Profilio statistika", + "cleanup_profile": "Išvalyti istoriją – diagrama ir ištrynimas", + "assign_phases": "Priskirti fazių diapazonus" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Sukurti naują fazę", + "edit_custom_phase": "Redaguoti fazę", + "delete_custom_phase": "Ištrinti fazę" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Pridėti fazių diapazoną", + "edit_range": "Redaguoti fazių diapazoną", + "delete_range": "Ištrinti fazių diapazoną", + "clear_ranges": "Išvalyti visus diapazonus", + "auto_detect_ranges": "Automatiškai aptikti fazes", + "save_ranges": "Išsaugoti ir grąžinti" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Atnaujinti būseną", + "stop_recording": "Sustabdyti įrašymą (išsaugoti ir apdoroti)", + "start_recording": "Pradėti naują įrašymą", + "process_recording": "Apdoroti paskutinį įrašą (apkarpyti ir išsaugoti)", + "discard_recording": "Atmesti paskutinį įrašą" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Sukurti naują profilį", + "existing_profile": "Pridėti prie esamo profilio", + "discard": "Atmesti įrašą" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Patvirtinti – teisingas aptikimas", + "correct": "Taisyti – pasirinkite programą / trukmę", + "ignore": "Ignoruoti – klaidingas teigiamas / triukšmingas ciklas", + "delete": "Ištrinti – pašalinti iš istorijos" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Pavadinti ir pritaikyti fazes", + "cancel": "Grįžti be pakeitimų" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Nustatyti pradžios tašką", + "set_end": "Nustatyti pabaigos tašką", + "reset": "Iš naujo nustatyti visą trukmę", + "apply": "Taikyti apkarpymą", + "cancel": "Atšaukti" + } + }, + "device_type": { + "options": { + "washing_machine": "Skalbimo mašina", + "dryer": "Džiovintuvas", + "washer_dryer": "Skalbimo-džiovinimo kombinacija", + "dishwasher": "Indaplovė", + "coffee_machine": "Kavos aparatas (nebenaudojamas)", + "ev": "Elektrinė transporto priemonė (nebenaudojama)", + "air_fryer": "Oro gruzdintuvė", + "heat_pump": "Šilumos siurblys (nebenaudojamas)", + "bread_maker": "Duonkepė", + "pump": "Siurblys / Drenažinis siurblys", + "oven": "Orkaitė (nebenaudojama)", + "other": "Kita (išplėstinė)" + } + } + }, + "services": { + "label_cycle": { + "name": "Pažymėti ciklą", + "description": "Priskirti esamą profilį praėjusiam ciklui arba pašalinti etiketę.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys žymėjimui." + }, + "cycle_id": { + "name": "Ciklo ID", + "description": "Ciklo, kurį reikia pažymėti, ID." + }, + "profile_name": { + "name": "Profilio pavadinimas", + "description": "Esamo profilio pavadinimas (sukurkite profilius meniu Tvarkyti profilius). Palikite tuščią, kad pašalintumėte etiketę." + } + } + }, + "create_profile": { + "name": "Sukurti profilį", + "description": "Sukurti naują profilį (atskirą arba pagal etaloninį ciklą).", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys." + }, + "profile_name": { + "name": "Profilio pavadinimas", + "description": "Naujojo profilio pavadinimas (pvz., „Intensyvus skalbimas”, „Smulkūs audiniai”)." + }, + "reference_cycle_id": { + "name": "Etaloninio ciklo ID", + "description": "Pasirenkamas ciklo ID, kuriuo remiantis bus sukurtas šis profilis." + } + } + }, + "delete_profile": { + "name": "Ištrinti profilį", + "description": "Ištrinti profilį ir pasirinktinai pašalinti etiketes iš ciklų, naudojančių jį.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys." + }, + "profile_name": { + "name": "Profilio pavadinimas", + "description": "Profilis, kurį norite ištrinti." + }, + "unlabel_cycles": { + "name": "Pašalinti ciklų etiketes", + "description": "Pašalinti profilio etiketę nuo ciklų, naudojančių šį profilį." + } + } + }, + "auto_label_cycles": { + "name": "Automatinis senų ciklų žymėjimas", + "description": "Atgaline data pažymėti nepažymėtus ciklus naudojant profilio atitikimą.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys." + }, + "confidence_threshold": { + "name": "Patikimumo slenkstis", + "description": "Minimalus atitikties patikimumas (0,50–0,95) etiketėms taikyti." + } + } + }, + "export_config": { + "name": "Eksportuoti konfigūraciją", + "description": "Eksportuoti šio įrenginio profilius, ciklus ir nustatymus į JSON failą (kiekvienam įrenginiui).", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys eksportavimui." + }, + "path": { + "name": "Kelias", + "description": "Pasirenkamas absoliutus failo kelias įrašymui (numatytasis: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importuoti konfigūraciją", + "description": "Importuoti šio įrenginio profilius, ciklus ir nustatymus iš JSON eksporto failo (nustatymai gali būti perrašyti).", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys importavimui." + }, + "path": { + "name": "Kelias", + "description": "Absoliutus kelias į eksporto JSON failą importavimui." + } + } + }, + "submit_cycle_feedback": { + "name": "Pateikti ciklo atsiliepimą", + "description": "Patvirtinti arba pataisyti automatiškai aptiktą programą po baigto ciklo. Pateikite `entry_id` (išplėstinis) arba `device_id` (rekomenduojama).", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys (rekomenduojamas vietoj entry_id)." + }, + "entry_id": { + "name": "Įrašo ID", + "description": "Įrenginio konfigūracijos įrašo ID (alternatyva device_id)." + }, + "cycle_id": { + "name": "Ciklo ID", + "description": "Ciklo ID, rodomas atsiliepimo pranešime / žurnaluose." + }, + "user_confirmed": { + "name": "Patvirtinti aptiktą programą", + "description": "Nustatykite teisingą, jei aptikta programa yra teisinga." + }, + "corrected_profile": { + "name": "Pataisytas profilis", + "description": "Jei nepatvirtinta, pateikite teisingą profilio / programos pavadinimą." + }, + "corrected_duration": { + "name": "Pataisyta trukmė (sekundėmis)", + "description": "Pasirenkama pataisyta trukmė sekundėmis." + }, + "notes": { + "name": "Pastabos", + "description": "Pasirenkamos pastabos apie šį ciklą." + } + } + }, + "record_start": { + "name": "Pradėti ciklo įrašymą", + "description": "Rankiniu būdu pradėti švaraus ciklo įrašymą (apeina visą atitikimo logiką).", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys įrašymui." + } + } + }, + "record_stop": { + "name": "Sustabdyti ciklo įrašymą", + "description": "Sustabdyti rankinį įrašymą.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys įrašymo stabdymui." + } + } + }, + "trim_cycle": { + "name": "Apkarpyti ciklą", + "description": "Apkarpyti praėjusio ciklo galios duomenis iki konkretaus laiko lango.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "WashData įrenginys." + }, + "cycle_id": { + "name": "Ciklo ID", + "description": "Ciklo, kurį reikia apkarpyti, ID." + }, + "trim_start_s": { + "name": "Apkarpymo pradžia (sekundėmis)", + "description": "Išsaugoti duomenis nuo šio poslinkio sekundėmis. Numatytasis: 0." + }, + "trim_end_s": { + "name": "Apkarpymo pabaiga (sekundėmis)", + "description": "Išsaugoti duomenis iki šio poslinkio sekundėmis. Numatytasis = visa trukmė." + } + } + }, + "pause_cycle": { + "name": "Pristabdyti ciklą", + "description": "Pristabdykite aktyvų WashData įrenginio ciklą.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "„WashData“ įrenginį pristabdyti." + } + } + }, + "resume_cycle": { + "name": "Tęsti ciklą", + "description": "Tęsti pristabdytą WashData įrenginio ciklą.", + "fields": { + "device_id": { + "name": "Įrenginys", + "description": "„WashData“ įrenginys, kad būtų atnaujintas." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Veikia" + }, + "match_ambiguity": { + "name": "Atitikties dviprasmiškumas" + } + }, + "sensor": { + "washer_state": { + "name": "Būsena", + "state": { + "off": "Išjungta", + "idle": "Tuščia eiga", + "starting": "Pradedama", + "running": "Veikia", + "paused": "Pristabdyta", + "user_paused": "Vartotojo pristabdyta", + "ending": "Baigiama", + "finished": "Baigta", + "anti_wrinkle": "Apsauga nuo raukšlių", + "delay_wait": "Laukiama pradžios", + "interrupted": "Nutraukta", + "force_stopped": "Priverstinai sustabdyta", + "rinse": "Skalavimas", + "unknown": "Nežinoma", + "clean": "Švarus" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Likęs laikas" + }, + "total_duration": { + "name": "Bendra trukmė" + }, + "cycle_progress": { + "name": "Eiga" + }, + "current_power": { + "name": "Dabartinė galia" + }, + "elapsed_time": { + "name": "Praėjęs laikas" + }, + "debug_info": { + "name": "Derinimo informacija" + }, + "match_confidence": { + "name": "Atitikties patikimumas" + }, + "top_candidates": { + "name": "Geriausi kandidatai", + "state": { + "none": "Nėra" + } + }, + "current_phase": { + "name": "Dabartinė fazė" + }, + "wash_phase": { + "name": "Fazė" + }, + "profile_cycle_count": { + "name": "Profilio {profile_name} ciklų skaičius", + "unit_of_measurement": "ciklai" + }, + "suggestions": { + "name": "Prieinami siūlomi nustatymai" + }, + "pump_runs_today": { + "name": "Siurblys veikia (paskutines 24 val.)" + }, + "cycle_count": { + "name": "Ciklų skaičius" + } + }, + "select": { + "program_select": { + "name": "Ciklo programa", + "state": { + "auto_detect": "Automatinis aptikimas" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Priverstinai baigti ciklą" + }, + "pause_cycle": { + "name": "Pristabdyti ciklą" + }, + "resume_cycle": { + "name": "Tęsti ciklą" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Būtinas galiojantis įrenginio_id." + }, + "cycle_id_required": { + "message": "Būtinas galiojantis ciklo_id." + }, + "profile_name_required": { + "message": "Reikalingas galiojantis profile_name." + }, + "device_not_found": { + "message": "Įrenginys nerastas." + }, + "no_config_entry": { + "message": "Įrenginio konfigūracijos įrašas nerastas." + }, + "integration_not_loaded": { + "message": "Šio įrenginio integracija neįkelta." + }, + "cycle_not_found_or_no_power": { + "message": "Ciklas nerastas arba neturi galios duomenų." + }, + "trim_failed_empty_window": { + "message": "Apkarpyti nepavyko – ciklas nerastas, nėra galios duomenų arba gautas langas tuščias." + }, + "trim_invalid_range": { + "message": "trim_end_s turi būti didesnis nei trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/lv.json b/custom_components/ha_washdata/translations/lv.json new file mode 100644 index 0000000..2ffe89a --- /dev/null +++ b/custom_components/ha_washdata/translations/lv.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData iestatīšana", + "description": "Konfigurējiet veļas mašīnu vai citu ierīci.\n\nNepieciešams jaudas sensors.\n\n**Pēc tam jums tiks jautāts, vai vēlaties izveidot savu pirmo profilu.**", + "data": { + "name": "Ierīces nosaukums", + "device_type": "Ierīces veids", + "power_sensor": "Jaudas sensors", + "min_power": "Minimālās jaudas slieksnis (W)" + }, + "data_description": { + "name": "Šīs ierīces draudzīgs nosaukums (piemēram, “Veļas mašīna”, “Trauku mazgājamā mašīna”).", + "device_type": "Kāda veida ierīce šī ir? Palīdz pielāgot noteikšanu un marķēšanu.", + "power_sensor": "Sensora entītija, kas ziņo reāllaika enerģijas patēriņu (vatos) no jūsu viedā spraudņa.", + "min_power": "Jaudas rādījumi, kas pārsniedz šo slieksni (vatos), norāda, ka ierīce darbojas. Lielākajai daļai ierīču sāciet ar 2 W." + } + }, + "first_profile": { + "title": "Izveidojiet pirmo profilu", + "description": "**Cikls** ir jūsu ierīces pilnīga darbība (piemēram, veļas slodze). **Profils** grupē šos ciklus pēc veida (piemēram, “Kokvilna”, “Ātrā mazgāšana”). Profila izveide tagad palīdz nekavējoties aprēķināt integrācijas ilgumu.\n\nVarat manuāli atlasīt šo profilu ierīces vadīklās, ja tas netiek atklāts automātiski.\n\n**Atstājiet “Profila nosaukums” tukšu, lai izlaistu šo darbību.**", + "data": { + "profile_name": "Profila nosaukums (neobligāti)", + "manual_duration": "Paredzamais ilgums (minūtes)" + } + } + }, + "error": { + "cannot_connect": "Neizdevās izveidot savienojumu", + "invalid_auth": "Nederīga autentifikācija", + "unknown": "Negaidīta kļūda" + }, + "abort": { + "already_configured": "Ierīce jau ir konfigurēta" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Pārskatiet mācību atsauksmes", + "settings": "Iestatījumi", + "notifications": "Paziņojumi", + "manage_cycles": "Pārvaldīt ciklus", + "manage_profiles": "Pārvaldīt profilus", + "manage_phase_catalog": "Pārvaldīt fāzes katalogu", + "record_cycle": "Ierakstīšanas cikls (manuāls)", + "diagnostics": "Diagnostika un apkope" + } + }, + "apply_suggestions": { + "title": "Kopējiet ieteiktās vērtības", + "description": "Tādējādi pašreizējās ieteiktās vērtības tiks kopētas jūsu saglabātajos iestatījumos. Šī ir vienreizēja darbība un neiespējo automātisko pārrakstīšanu.\n\nIeteiktās vērtības kopēt:\n{suggested}", + "data": { + "confirm": "Apstipriniet kopēšanas darbību" + } + }, + "apply_suggestions_confirm": { + "title": "Pārskatiet ieteiktās izmaiņas", + "description": "Atrasti WashData **{pending_count}** ieteicamā(-ās) vērtība(-as).\n\nIzmaiņas:\n{changes}\n\n✅ Atzīmējiet tālāk esošo izvēles rūtiņu un noklikšķiniet uz **Iesniegt**, lai nekavējoties piemērotu un saglabātu šīs izmaiņas.\n❌ Atstājiet to neatzīmētu un noklikšķiniet uz **Iesniegt**, lai atceltu un atgrieztos.", + "data": { + "confirm_apply_suggestions": "Lietot un saglabāt šīs izmaiņas" + } + }, + "settings": { + "title": "Iestatījumi", + "description": "{deprecation_warning}Pielāgojiet noteikšanas sliekšņus, mācīšanos un uzlabotu darbību. Noklusējumi darbojas labi lielākajai daļai iestatījumu.\n\nPašlaik pieejamie ieteiktie iestatījumi: {suggestions_count}.\nJa tas ir lielāks par 0, atveriet papildu iestatījumus un izmantojiet 'Lietojiet ieteiktās vērtības', lai pārskatītu ieteikumus.", + "data": { + "apply_suggestions": "Lietojiet ieteiktās vērtības", + "device_type": "Ierīces veids", + "power_sensor": "Jaudas sensora entītija", + "min_power": "Minimālā jauda (W)", + "off_delay": "Cikla beigu aizkave (s)", + "notify_actions": "Paziņojumu darbības", + "notify_people": "Atlikt piegādi, līdz šie cilvēki ir mājās", + "notify_only_when_home": "Aizkavēt paziņojumus, līdz izvēlētā persona ir mājās", + "notify_fire_events": "Ugunsgrēka automatizācijas pasākumi", + "notify_start_services": "Cikla sākums - paziņojumu mērķi", + "notify_finish_services": "Cikla pabeigšana - paziņojumu mērķi", + "notify_live_services": "Tiešraides norise - paziņojumu mērķi", + "notify_before_end_minutes": "Paziņojums pirms pabeigšanas (minūtes pirms beigām)", + "notify_title": "Paziņojuma nosaukums", + "notify_icon": "Paziņojuma ikona", + "notify_start_message": "Sāciet ziņojuma formātu", + "notify_finish_message": "Pabeigt ziņojuma formātu", + "notify_pre_complete_message": "Pirmspabeigšanas ziņojuma formāts", + "show_advanced": "Rediģēt papildu iestatījumus (nākamā darbība)" + }, + "data_description": { + "apply_suggestions": "Atzīmējiet šo izvēles rūtiņu, lai kopētu veidlapā mācību algoritma ieteiktās vērtības. Pārskatiet pirms saglabāšanas.\n\nIeteikts (no mācīšanās):\n- min_jauda: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_ trust: {suggested_auto_label_confidence}\n- ilgums_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- sākuma_slieksnis_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- darbojas_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Izvēlieties ierīces veidu (veļas mašīna, žāvētājs, trauku mazgājamā mašīna, kafijas automāts, EV). Šis tags tiek saglabāts kopā ar katru ciklu, lai nodrošinātu labāku organizēšanu.", + "power_sensor": "Sensora entītija, kas ziņo par reāllaika jaudu (vatos). Varat to mainīt jebkurā laikā, nezaudējot vēsturiskos datus - tas ir noderīgi, ja nomaināt viedo spraudni.", + "min_power": "Jaudas rādījumi, kas pārsniedz šo slieksni (vatos), norāda, ka ierīce darbojas. Lielākajai daļai ierīču sāciet ar 2 W.", + "off_delay": "Sekundes izlīdzinātajai jaudai jāpaliek zem apturēšanas sliekšņa, lai atzīmētu pabeigšanu. Noklusējums: 120 s.", + "notify_actions": "Izvēles Home Assistant darbību secība, lai izpildītu paziņojumus (skripti, paziņojums, TTS utt.). Ja tas ir iestatīts, darbības tiek izpildītas pirms rezerves paziņojumu pakalpojuma.", + "notify_people": "Personu entītijas, ko izmanto klātbūtnes noteikšanai. Ja tas ir iespējots, paziņojumu piegāde tiek aizkavēta, līdz vismaz viena atlasītā persona ir mājās.", + "notify_only_when_home": "Ja tas ir iespējots, aizkavējiet paziņojumus, līdz vismaz viena atlasītā persona ir mājās.", + "notify_fire_events": "Ja tas ir iespējots, aktivizējiet Home Assistant notikumus cikla sākumam un beigām (ha_washdata_cycle_started un ha_washdata_cycle_ended), lai veicinātu automatizāciju.", + "notify_start_services": "Viens vai vairāki paziņo pakalpojumus, lai brīdinātu, kad cikls sākas. Atstājiet tukšu, lai izlaistu sākuma paziņojumus.", + "notify_finish_services": "Viens vai vairāki paziņo pakalpojumus, lai brīdinātu, kad cikls beidzas vai tuvojas beigām. Atstājiet tukšu, lai izlaistu paziņojumus par pabeigšanu.", + "notify_live_services": "Viens vai vairāki paziņošanas pakalpojumi, lai cikla darbības laikā saņemtu tiešsaistes progresa atjauninājumus (tikai mobilā pavadošā lietotne). Atstājiet tukšu, lai izlaistu tiešraides atjauninājumus.", + "notify_before_end_minutes": "Minūtes pirms paredzamā cikla beigām, lai aktivizētu paziņojumu. Iestatiet uz 0, lai atspējotu. Noklusējums: 0.", + "notify_title": "Paziņojumu pielāgots nosaukums. Atbalsta vietturi {device}.", + "notify_icon": "Paziņojumiem izmantojamā ikona (piemēram, mdi:veļas mašīna). Atstājiet tukšu, lai sūtītu bez ikonas.", + "notify_start_message": "Ziņojums nosūtīts, kad sākas cikls. Atbalsta vietturi {device}.", + "notify_finish_message": "Ziņojums nosūtīts, kad cikls beidzas. Atbalsta vietturus {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Cikla darbības laikā tiek atjaunināts periodisko tiešraides norises teksts. Atbalsta vietturus {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Paziņojumi", + "description": "Konfigurējiet paziņojumu kanālus, veidnes un izvēles tiešsaistes mobilā progresa atjauninājumus.", + "data": { + "notify_actions": "Paziņojumu darbības", + "notify_people": "Atlikt piegādi, līdz šie cilvēki ir mājās", + "notify_only_when_home": "Aizkavēt paziņojumus, līdz izvēlētā persona ir mājās", + "notify_fire_events": "Ugunsgrēka automatizācijas pasākumi", + "notify_start_services": "Cikla sākums - paziņojumu mērķi", + "notify_finish_services": "Cikla pabeigšana - paziņojumu mērķi", + "notify_live_services": "Tiešraides norise - paziņojumu mērķi", + "notify_before_end_minutes": "Paziņojums pirms pabeigšanas (minūtes pirms beigām)", + "notify_live_interval_seconds": "Tiešraides atjaunināšanas intervāls (sekundēs)", + "notify_live_overrun_percent": "Tiešraides atjauninājumu pārsniegšanas pielaide (%)", + "notify_live_chronometer": "Hronometra atpakaļskaitīšanas taimeris", + "notify_title": "Paziņojuma nosaukums", + "notify_icon": "Paziņojuma ikona", + "notify_start_message": "Sāciet ziņojuma formātu", + "notify_finish_message": "Pabeigt ziņojuma formātu", + "notify_pre_complete_message": "Pirmspabeigšanas ziņojuma formāts", + "energy_price_entity": "Enerģijas cena - vienība (pēc izvēles)", + "energy_price_static": "Enerģijas cena - statiskā vērtība (pēc izvēles)", + "go_back": "Atgriezties bez saglabāšanas", + "notify_reminder_message": "Atgādinājuma ziņojuma formāts", + "notify_timeout_seconds": "Automātiski noraidīt pēc (sekundes)", + "notify_channel": "Paziņojumu kanāls (statuss/tiešraide/atgādinājums)", + "notify_finish_channel": "Pabeigts paziņojumu kanāls" + }, + "data_description": { + "go_back": "Atzīmējiet šo un noklikšķiniet uz Iesniegt, lai atmestu visas iepriekš minētās izmaiņas un atgrieztos galvenajā izvēlnē.", + "notify_actions": "Izvēles Home Assistant darbību secība, lai izpildītu paziņojumus (skripti, paziņojums, TTS utt.). Ja tas ir iestatīts, darbības tiek izpildītas pirms rezerves paziņojumu pakalpojuma.", + "notify_people": "Personu entītijas, ko izmanto klātbūtnes noteikšanai. Ja tas ir iespējots, paziņojumu piegāde tiek aizkavēta, līdz vismaz viena atlasītā persona ir mājās.", + "notify_only_when_home": "Ja tas ir iespējots, aizkavējiet paziņojumus, līdz vismaz viena atlasītā persona ir mājās.", + "notify_fire_events": "Ja tas ir iespējots, aktivizējiet Home Assistant notikumus cikla sākumam un beigām (ha_washdata_cycle_started un ha_washdata_cycle_ended), lai veicinātu automatizāciju.", + "notify_start_services": "Viens vai vairāki paziņošanas pakalpojumi, lai brīdinātu, kad cikls sākas (piemēram, notify.mobile_app_my_phone). Atstājiet tukšu, lai izlaistu sākuma paziņojumus.", + "notify_finish_services": "Viens vai vairāki paziņo pakalpojumus, lai brīdinātu, kad cikls beidzas vai tuvojas beigām. Atstājiet tukšu, lai izlaistu paziņojumus par pabeigšanu.", + "notify_live_services": "Viens vai vairāki paziņošanas pakalpojumi, lai cikla darbības laikā saņemtu tiešsaistes progresa atjauninājumus (tikai mobilā pavadošā lietotne). Atstājiet tukšu, lai izlaistu tiešraides atjauninājumus.", + "notify_before_end_minutes": "Minūtes pirms paredzamā cikla beigām, lai aktivizētu paziņojumu. Iestatiet uz 0, lai atspējotu. Noklusējums: 0.", + "notify_live_interval_seconds": "Cik bieži darbības laikā tiek nosūtīti progresa atjauninājumi. Zemākas vērtības ir atsaucīgākas, taču patērē vairāk paziņojumu. Tiek ieviests vismaz 30 s - vērtības, kas ir mazākas par 30 s, tiek automātiski noapaļotas līdz 30 s.", + "notify_live_overrun_percent": "Drošības rezerve pārsniedz aptuveno atjauninājumu skaitu gariem/pārsniegšanas cikliem (piemēram, 20% pieļauj 1,2 reizes aptuveno atjauninājumu skaitu).", + "notify_live_chronometer": "Kad tas ir iespējots, katrs tiešraides atjauninājums ietver hronometra atpakaļskaitīšanu līdz aptuvenajam beigu laikam. Paziņojumu taimeris ierīcē automātiski ieslēdzas starp atjauninājumiem (tikai Android pavadošajai lietotnei).", + "notify_title": "Paziņojumu pielāgots nosaukums. Atbalsta vietturi {device}.", + "notify_icon": "Paziņojumiem izmantojamā ikona (piemēram, mdi:veļas mašīna). Atstājiet tukšu, lai sūtītu bez ikonas.", + "notify_start_message": "Ziņojums nosūtīts, kad sākas cikls. Atbalsta vietturi {device}.", + "notify_finish_message": "Ziņojums nosūtīts, kad cikls beidzas. Atbalsta vietturus {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Atlasiet skaitlisku vienību, kurā ir norādīta pašreizējā elektroenerģijas cena (piemēram, sensors.electricity_price, input_number.energy_rate). Kad tas ir iestatīts, tiek iespējots vietturis {cost}. Ja abi ir konfigurēti, ir prioritāte pār statisko vērtību.", + "energy_price_static": "Fiksēta elektroenerģijas cena par kWh. Kad tas ir iestatīts, tiek iespējots vietturis {cost}. Ignorē, ja iepriekš ir konfigurēta entītija. Pievienojiet savas valūtas simbolu ziņojuma veidnē, piem. {cost} €.", + "notify_reminder_message": "Vienreizējā atgādinājuma teksts tika nosūtīts konfigurēto minūšu skaitu pirms paredzamās beigām. Atšķiras no iepriekš minētajiem regulāriem tiešraides atjauninājumiem. Atbalsta vietturus {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Automātiski noraidīt paziņojumus pēc šīm sekundēm (tiek pārsūtīti kā pavadošās lietotnes noildze). Iestatiet vērtību 0, lai tās saglabātu līdz atlaišanai. Noklusējums: 0.", + "notify_channel": "Android pavadošās lietotnes paziņojumu kanāls statusa, reāllaika norises un atgādinājumu paziņojumiem. Kanāla skaņa un svarīgums tiek konfigurēti pavadošajā lietotnē, kad pirmo reizi tiek izmantots kanāla nosaukums — WashData iestata tikai kanāla nosaukumu. Atstājiet tukšu lietotnes noklusējuma iestatīšanai.", + "notify_finish_channel": "Atsevišķs Android kanāls gatavajam brīdinājumam (to izmanto arī atgādinājums un veļas gaidīšanas zvans), lai tam būtu savs skaņa. Atstājiet tukšu, lai atkārtoti izmantotu iepriekš minēto kanālu.", + "notify_pre_complete_message": "Cikla darbības laikā tiek atjaunināts periodisko tiešraides norises teksts. Atbalsta vietturus {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Papildu iestatījumi", + "description": "Pielāgojiet noteikšanas sliekšņus, mācīšanos un uzlabotu darbību. Noklusējuma iestatījumi labi darbojas lielākajai daļai konfigurāciju.", + "sections": { + "suggestions_section": { + "name": "Ieteiktie iestatījumi", + "description": "Ir pieejami {suggestions_count} iemācīti ieteikumi. Atzīmējiet 'Lietot ieteiktās vērtības', lai pirms saglabāšanas pārskatītu piedāvātās izmaiņas.", + "data": { + "apply_suggestions": "Lietojiet ieteiktās vērtības" + }, + "data_description": { + "apply_suggestions": "Atzīmējiet šo izvēles rūtiņu, lai atvērtu mācību algoritma ieteikto vērtību pārskatīšanas darbību. Nekas netiek saglabāts automātiski.\n\nIeteikts (no mācīšanās):\n- min_jauda: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_ trust: {suggested_auto_label_confidence}\n- ilgums_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- sākuma_slieksnis_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- darbojas_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Noteikšana un jaudas sliekšņi", + "description": "Nosaka, kad ierīce tiek uzskatīta par IESLĒGTU vai IZSLĒGTU, enerģijas sliekšņus un stāvokļu pāreju laiku.", + "data": { + "start_duration_threshold": "Atkāpšanās sākuma ilgums (-i)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Minimālais izpildes laiks (sekundēs)", + "start_threshold_w": "Sākuma slieksnis (W)", + "stop_threshold_w": "Apstāšanās slieksnis (W)", + "end_energy_threshold": "Beigu enerģijas vārti (Wh)", + "running_dead_zone": "Skriešanas mirušā zona (sekundes)", + "end_repeat_count": "Beigt atkārtojumu skaitīšanu", + "min_off_gap": "Minimālais attālums starp cikliem (s)", + "sampling_interval": "Paraugu ņemšanas intervāls (-i)" + }, + "data_description": { + "start_duration_threshold": "Ignorējiet īsus jaudas palielinājumus, kas ir īsāki par šo ilgumu (sekundēs). Pirms reālā cikla sākuma filtrē sāknēšanas tapas (piemēram, 60 W uz 2 s). Noklusējums: 5s.", + "start_energy_threshold": "Ciklam START fāzes laikā ir jāuzkrāj vismaz tik daudz enerģijas (Wh), lai tas tiktu apstiprināts. Noklusējums: 0,005 Wh.", + "completion_min_seconds": "Cikli, kas ir īsāki par šo, tiks atzīmēti kā “pārtraukti”, pat ja tie beidzas dabiski. Noklusējums: 600s.", + "start_threshold_w": "Lai sāktu ciklu, jaudai ir jāpalielinās PIRMS šī sliekšņa. Iestatiet augstāku par gaidstāves jaudu. Noklusējums: min_power + 1 W.", + "stop_threshold_w": "Lai BEIGTU ciklu, jaudai ir jāsamazinās ZEMĀK šī sliekšņa. Iestatiet nedaudz virs nulles, lai izvairītos no trokšņa, kas izraisa viltus galus. Noklusējums: 0,6 * min_power.", + "end_energy_threshold": "Cikls beidzas tikai tad, ja enerģija pēdējā Off-Delay logā ir zem šīs vērtības (Wh). Novērš viltus galus, ja jauda svārstās tuvu nullei. Noklusējums: 0,05 Wh.", + "running_dead_zone": "Pēc cikla sākuma ignorējiet jaudas kritumus tik sekundes, lai novērstu nepareizu beigu noteikšanu sākotnējās vērpšanas fāzes laikā. Iestatiet uz 0, lai atspējotu. Noklusējums: 0s.", + "end_repeat_count": "Beigu nosacījums (jauda zem off_delay sliekšņa) ir jāizpilda secīgi pirms cikla beigām. Augstākas vērtības novērš viltus galus paužu laikā. Noklusējums: 1.", + "min_off_gap": "Ja cikls sākas šo sekunžu laikā pēc iepriekšējā cikla beigām, tie tiks APVIENOTI vienā ciklā.", + "sampling_interval": "Minimālais paraugu ņemšanas intervāls sekundēs. Atjauninājumi, kas ir ātrāki par šo ātrumu, tiks ignorēti. Noklusējums: 30 s (2 s veļas mašīnām, veļas mazgājamām-žāvējamām mašīnām un trauku mazgājamām mašīnām)." + } + }, + "matching_section": { + "name": "Profilu saskaņošana un mācīšanās", + "description": "Nosaka, cik agresīvi WashData saskaņo darbībā esošos ciklus ar iemācītajiem profiliem un kad prasīt atsauksmi.", + "data": { + "profile_match_min_duration_ratio": "Profila atbilstības minimālā ilguma attiecība (0,1–1,0)", + "profile_match_interval": "Profila atbilstības intervāls (sekundēs)", + "profile_match_threshold": "Profila atbilstības slieksnis", + "profile_unmatch_threshold": "Profila neatbilstības slieksnis", + "auto_label_confidence": "Automātiskās etiķetes uzticamība (0-1)", + "learning_confidence": "Atsauksmju pieprasījuma pārliecība (0-1)", + "suppress_feedback_notifications": "Izslēdziet atsauksmju paziņojumus", + "duration_tolerance": "Ilguma pielaide (0–0,5)", + "smoothing_window": "Izlīdzinošais logs (paraugi)", + "profile_duration_tolerance": "Profila atbilstības ilguma pielaide (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimālā ilguma attiecība profila saskaņošanai. Darbības ciklam ir jābūt vismaz šai profila ilguma daļai, lai tas atbilstu. Zemāks = agrāka atbilstība. Noklusējums: 0,50 (50%).", + "profile_match_interval": "Cik bieži (sekundēs) cikla laikā mēģināt izveidot profila saskaņošanu. Zemākas vērtības nodrošina ātrāku noteikšanu, bet izmanto vairāk CPU. Noklusējums: 300 s (5 minūtes).", + "profile_match_threshold": "Minimālais DTW līdzības rādītājs (0,0–1,0), kas nepieciešams, lai profilu uzskatītu par atbilstošu. Augstāks = stingrāka atbilstība, mazāk viltus pozitīvu. Noklusējums: 0,4.", + "profile_unmatch_threshold": "DTW līdzības rādītājs (0,0–1,0), zem kura iepriekš saskaņotais profils tiek noraidīts. Tam jābūt zemākam par atbilstības slieksni, lai novērstu mirgošanu. Noklusējums: 0,35.", + "auto_label_confidence": "Automātiski marķējiet ciklus ar šo pārliecību vai augstāku pēc pabeigšanas (0,0–1,0).", + "learning_confidence": "Minimālā pārliecība, lai pieprasītu lietotāja verifikāciju, izmantojot notikumu (0,0–1,0).", + "suppress_feedback_notifications": "Kad tas ir iespējots, WashData joprojām izsekos un pieprasīs atgriezenisko saiti iekšēji, bet nerādīs pastāvīgus paziņojumus ar lūgumu apstiprināt ciklus. Noderīgi, ja cikli vienmēr tiek noteikti pareizi un uzvednes novērš uzmanību.", + "duration_tolerance": "Pielaide atlikušā laika aprēķiniem skrējiena laikā (0,0–0,5 atbilst 0–50%).", + "smoothing_window": "Neseno jaudas rādījumu skaits, kas izmantots mainīgā vidējā izlīdzināšanai.", + "profile_duration_tolerance": "Pielaide, ko izmanto profila saskaņošanai kopējā ilguma dispersijai (0,0–0,5). Noklusējuma darbība: ±25%." + } + }, + "timing_section": { + "name": "Laiks, apkope un atkļūdošana", + "description": "Watchdog, progresa atiestatīšana, automātiskā apkope un atkļūdošanas entītiju rādīšana.", + "data": { + "watchdog_interval": "Sargsuņa intervāls (sekundēs)", + "no_update_active_timeout": "Noildze bez atjaunināšanas (-i)", + "progress_reset_delay": "Progresa atiestatīšanas aizkave (sekundēs)", + "auto_maintenance": "Iespējot automātisko apkopi", + "expose_debug_entities": "Atklājiet atkļūdošanas entītijas", + "save_debug_traces": "Saglabājiet atkļūdošanas pēdas" + }, + "data_description": { + "watchdog_interval": "Sekundes starp sargsuņa pārbaudēm skriešanas laikā. Noklusējums: 5s. BRĪDINĀJUMS. Pārliecinieties, ka tas ir AUGSTĀKS par sensora atjaunināšanas intervālu, lai izvairītos no viltus apstāšanās (piem., ja sensors tiek atjaunināts ik pēc 60 sekundēm, izmantojiet 65 s+).", + "no_update_active_timeout": "Sensoriem, kas tiek publicēti pēc maiņas: piespiedu kārtā pārtrauciet ciklu AKTĪVS tikai tad, ja tik daudz sekunžu laikā netiek saņemti atjauninājumi. Mazjaudas pabeigšanai joprojām tiek izmantota izslēgšanas aizkave.", + "progress_reset_delay": "Kad cikls ir pabeigts (100%), pagaidiet tik daudz dīkstāves sekunžu, pirms atiestatāt progresu uz 0%. Noklusējums: 300 s.", + "auto_maintenance": "Iespējot automātisko apkopi (labot paraugus, sapludināt fragmentus).", + "expose_debug_entities": "Rādīt uzlabotos sensorus (pārliecība, fāze, neskaidrība) atkļūdošanai.", + "save_debug_traces": "Saglabājiet detalizētus ranga un jaudas izsekošanas datus vēsturē (palielina krātuves lietojumu)." + } + }, + "anti_wrinkle_section": { + "name": "Pretgrumbu aizsargs (žāvētājiem)", + "description": "Ignorējiet trumuļa pagriezienus pēc cikla beigām, lai novērstu spoku ciklus. Tikai žāvētājs / veļasmašīna-žāvētājs.", + "data": { + "anti_wrinkle_enabled": "Pretgrumbu aizsargs (tikai žāvētājam)", + "anti_wrinkle_max_power": "Pretgrumbu maksimālā jauda (W)", + "anti_wrinkle_max_duration": "Pretgrumbu maksimālais ilgums (s)", + "anti_wrinkle_exit_power": "Pretgrumbu izejas jauda (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorējiet īsas mazjaudas trumuļa rotācijas pēc cikla beigām, lai novērstu spoku ciklus (tikai žāvētājs/mazgāšanas-žāvētājs).", + "anti_wrinkle_max_power": "Uzliesmojumi ar šo jaudu vai zem tā tiek uzskatīti par pretgrumbu apgriezieniem, nevis jauniem cikliem. Attiecas tikai tad, ja ir iespējots pretgrumbu aizsargs.", + "anti_wrinkle_max_duration": "Maksimālais sprādziena garums, lai to uzskatītu par pretgrumbu rotāciju. Attiecas tikai tad, ja ir iespējots pretgrumbu aizsargs.", + "anti_wrinkle_exit_power": "Ja jauda nokrītas zem šī līmeņa, nekavējoties izejiet no pretgrumbu (true off). Attiecas tikai tad, ja ir iespējots pretgrumbu aizsargs." + } + }, + "delay_start_section": { + "name": "Atliktā starta noteikšana", + "description": "Uztveriet ilgstošu zemas jaudas gaidstāvi kā stāvokli 'Gaida palaišanu', līdz cikls patiešām sākas.", + "data": { + "delay_start_detect_enabled": "Iespējot atliktā starta noteikšanu", + "delay_confirm_seconds": "Atliktais starts: gaidstāves apstiprināšanas laiks (s)", + "delay_timeout_hours": "Atliktais starts: maksimālais gaidīšanas laiks (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kad tas ir iespējots, īss mazjaudas aizplūšana, kam seko ilgstoša gaidstāves barošana, tiek noteikts kā aizkavēts starts. Stāvokļa sensors aizkaves laikā parāda ziņojumu “Gaida palaišanu”. Kamēr cikls nav sācies, netiek izsekots neviens programmas laiks vai norise. Nepieciešams, lai start_threshold_w būtu iestatīts virs drenāžas smailes jaudas.", + "delay_confirm_seconds": "Jaudai jāpaliek starp apturēšanas slieksni (W) un starta slieksni (W) tik sekundes, pirms WashData pāriet stāvoklī 'Gaida palaišanu'. Tas filtrē īsus pīķus, pārvietojoties pa izvēlni. Noklusējums: 60 s.", + "delay_timeout_hours": "Drošības taimauts: ja iekārta joprojām atrodas režīmā “Gaida palaišanu” pēc tik daudzām stundām, WashData tiek atiestatīta uz Off. Noklusējums: 8 h." + } + }, + "external_triggers_section": { + "name": "Ārējie aktivizētāji, durvis un pauze", + "description": "Izvēles ārējais cikla beigu sensors, durvju sensors pauzes / izkraušanas noteikšanai un strāvas atslēgšanas slēdzis pauzes laikā.", + "data": { + "external_end_trigger_enabled": "Iespējot ārējā cikla beigu aktivizētāju", + "external_end_trigger": "Ārējais cikla beigu sensors", + "external_end_trigger_inverted": "Invertēt trigera loģiku (trigeris ir izslēgts)", + "door_sensor_entity": "Durvju sensors", + "pause_cuts_power": "Apturot, pārtrauciet strāvu", + "switch_entity": "Slēdža entītija (pauzēt strāvas padevei)", + "notify_unload_delay_minutes": "Veļas gaidīšanas paziņojuma aizkave (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Iespējot ārējā binārā sensora uzraudzību, lai aktivizētu cikla pabeigšanu.", + "external_end_trigger": "Atlasiet bināro sensoru entītiju. Kad šis sensors iedarbinās, pašreizējais cikls beigsies ar statusu “pabeigts”.", + "external_end_trigger_inverted": "Pēc noklusējuma trigeris tiek aktivizēts, kad sensors tiek IESLĒGTS. Atzīmējiet šo izvēles rūtiņu, lai aktivizētu, kad sensors tiek IZSLĒGTS.", + "door_sensor_entity": "Izvēles binārais sensors mašīnas durvīm (ieslēgts = atvērts, izslēgts = aizvērts). Kad durvis atveras aktīvā cikla laikā, WashData apstiprinās ciklu kā apzināti apturētu. Pēc cikla beigām, ja durvis joprojām ir aizvērtas, stāvoklis mainās uz 'Clean', līdz durvis tiek atvērtas. Piezīme: durvju aizvēršana NAV automātiski atsākta apturēts cikls - atsākšana ir jāaktivizē manuāli, izmantojot pogu Resume Cycle vai pakalpojumu.", + "pause_cuts_power": "Apturot, izmantojot pogu Pause Cycle vai pakalpojumu, izslēdziet arī konfigurēto slēdža entītiju. Stājas spēkā tikai tad, ja šai ierīcei ir konfigurēta slēdža entītija.", + "switch_entity": "Slēdža entītija, lai pārslēgtos, kad tiek apturēta vai atsākta. Nepieciešams, ja ir iespējota funkcija “Izslēdziet strāvu, kad tiek apturēta”.", + "notify_unload_delay_minutes": "Nosūtiet paziņojumu pa finiša paziņojumu kanālu pēc tam, kad cikls ir beidzies un durvis nav atvērtas tik daudzas minūtes (nepieciešams durvju sensors). Iestatiet uz 0, lai atspējotu. Noklusējums: 60 min." + } + }, + "pump_section": { + "name": "Sūkņa uzraudzība", + "description": "Tikai sūkņa tipa ierīcēm. Izsauc notikumu, ja sūkņa cikls ilgst ilgāk par šo laiku.", + "data": { + "pump_stuck_duration": "Sūkņa iestrēgšanas brīdinājuma slieksnis (-i) (tikai sūknis)" + }, + "data_description": { + "pump_stuck_duration": "Ja sūkņa cikls joprojām darbojas pēc šīm sekundēm, vienreiz tiek aktivizēts notikums ha_washdata_pump_stuck. Izmantojiet to, lai noteiktu iestrēgušu motoru (parasti sūkņa darbības laiks ir mazāks par 60 s). Noklusējums: 1800 s (30 min). Tikai sūkņa ierīces tips." + } + }, + "device_link_section": { + "name": "Ierīces saite", + "description": "Pēc izvēles saistiet šo WashData ierīci ar esošu Home Assistant ierīci (piemēram, viedspraudni vai pašu ierīci). Pēc tam WashData ierīce tiek parādīta kā \"Savienots, izmantojot šo ierīci\". Atstājiet tukšu, lai WashData saglabātu kā atsevišķu ierīci.", + "data": { + "linked_device": "Saistītā ierīce" + }, + "data_description": { + "linked_device": "Atlasiet ierīci, ar kuru savienot WashData. Tādējādi ierīces reģistrā tiek pievienota atsauce “Savienots, izmantojot”; WashData saglabā savu ierīces karti un entītijas. Notīriet atlasi, lai noņemtu saiti." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika un apkope", + "description": "Veiciet uzturēšanas darbības, piemēram, sadrumstalotu ciklu sapludināšanu vai saglabāto datu migrēšanu.\n\n**Krātuves lietojums**\n\n- Faila lielums: {file_size_kb} KB\n- Cikli: {cycle_count}\n- Profili: {profile_count}\n- Atkļūdošanas pēdas: {debug_count}", + "menu_options": { + "reprocess_history": "Apkope: atkārtoti apstrādājiet un optimizējiet datus", + "clear_debug_data": "Notīrīt atkļūdošanas datus (atbrīvojiet vietu)", + "wipe_history": "Dzēst VISUS šīs ierīces datus (neatgriezeniski)", + "export_import": "Eksportēt/importēt JSON ar iestatījumiem (kopēt/ielīmēt)", + "menu_back": "← Atpakaļ" + } + }, + "clear_debug_data": { + "title": "Notīrīt atkļūdošanas datus", + "description": "Vai tiešām vēlaties dzēst visas saglabātās atkļūdošanas pēdas? Tas atbrīvos vietu, bet noņems detalizētu informāciju par rangu no iepriekšējiem cikliem." + }, + "manage_cycles": { + "title": "Pārvaldīt ciklus", + "description": "Pēdējie cikli:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automātiski iezīmējiet vecos ciklus", + "select_cycle_to_label": "Etiķetes specifiskais cikls", + "select_cycle_to_delete": "Dzēst ciklu", + "interactive_editor": "Apvienot/sadalīt interaktīvo redaktoru", + "trim_cycle_select": "Apgriešanas cikla dati", + "menu_back": "← Atpakaļ" + } + }, + "manage_cycles_empty": { + "title": "Pārvaldīt ciklus", + "description": "Vēl nav reģistrēts neviens cikls.", + "menu_options": { + "auto_label_cycles": "Automātiski iezīmējiet vecos ciklus", + "menu_back": "← Atpakaļ" + } + }, + "manage_profiles": { + "title": "Pārvaldīt profilus", + "description": "Profila kopsavilkums:\n{profile_summary}", + "menu_options": { + "create_profile": "Izveidot jaunu profilu", + "edit_profile": "Rediģēt/pārdēvēt profilu", + "delete_profile_select": "Dzēst profilu", + "profile_stats": "Profila statistika", + "cleanup_profile": "Vēstures tīrīšana - diagramma un dzēšana", + "assign_profile_phases_select": "Piešķiriet fāzes diapazonus", + "menu_back": "← Atpakaļ" + } + }, + "manage_profiles_empty": { + "title": "Pārvaldīt profilus", + "description": "Vēl nav izveidots neviens profils.", + "menu_options": { + "create_profile": "Izveidot jaunu profilu", + "menu_back": "← Atpakaļ" + } + }, + "manage_phase_catalog": { + "title": "Pārvaldīt fāzes katalogu", + "description": "Fāžu katalogs (šīs ierīces noklusējuma iestatījumi + pielāgotas fāzes):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Izveidot jaunu fāzi", + "phase_catalog_edit_select": "Rediģēšanas fāze", + "phase_catalog_delete": "Dzēst fāzi", + "menu_back": "← Atpakaļ" + } + }, + "phase_catalog_create": { + "title": "Izveidojiet pielāgotu fāzi", + "description": "Izveidojiet pielāgotu fāzes nosaukumu un aprakstu un izvēlieties, uz kuru ierīces tipu tas attiecas.", + "data": { + "target_device_type": "Mērķa ierīces veids", + "phase_name": "Fāzes nosaukums", + "phase_description": "Fāzes apraksts" + } + }, + "phase_catalog_edit_select": { + "title": "Rediģēt pielāgoto fāzi", + "description": "Atlasiet pielāgotu fāzi rediģēšanai.", + "data": { + "phase_name": "Pielāgota fāze" + } + }, + "phase_catalog_edit": { + "title": "Rediģēt pielāgoto fāzi", + "description": "Rediģēšanas posms: {phase_name}", + "data": { + "phase_name": "Fāzes nosaukums", + "phase_description": "Fāzes apraksts" + } + }, + "phase_catalog_delete": { + "title": "Dzēst pielāgoto fāzi", + "description": "Atlasiet pielāgotu fāzi, ko dzēst. Visi piešķirtie diapazoni, kas izmanto šo fāzi, tiks noņemti.", + "data": { + "phase_name": "Pielāgota fāze" + } + }, + "assign_profile_phases": { + "title": "Piešķiriet fāzes diapazonus", + "description": "Profila fāzes priekšskatījums: **{profile_name}**\n\n{timeline_svg}\n\n**Pašreizējie diapazoni:**\n{current_ranges}\n\nIzmantojiet tālāk norādītās darbības, lai pievienotu, rediģētu, dzēstu vai saglabātu diapazonus.", + "menu_options": { + "assign_profile_phases_add": "Pievienot fāzes diapazonu", + "assign_profile_phases_edit_select": "Rediģēt fāzes diapazonu", + "assign_profile_phases_delete": "Dzēst fāzes diapazonu", + "phase_ranges_clear": "Notīrīt visus diapazonus", + "assign_profile_phases_auto_detect": "Automātiski noteikt fāzes", + "phase_ranges_save": "Saglabāt un atgriezt", + "menu_back": "← Atpakaļ" + } + }, + "assign_profile_phases_add": { + "title": "Pievienot fāzes diapazonu", + "description": "Pievienojiet pašreizējam melnrakstam vienu fāzes diapazonu.", + "data": { + "phase_name": "Fāze", + "start_min": "Sākuma minūte", + "end_min": "Beigu minūte" + } + }, + "assign_profile_phases_edit_select": { + "title": "Rediģēt fāzes diapazonu", + "description": "Atlasiet rediģējamo diapazonu.", + "data": { + "range_index": "Fāzes diapazons" + } + }, + "assign_profile_phases_edit": { + "title": "Rediģēt fāzes diapazonu", + "description": "Atjauniniet atlasīto fāzes diapazonu.", + "data": { + "phase_name": "Fāze", + "start_min": "Sākuma minūte", + "end_min": "Beigu minūte" + } + }, + "assign_profile_phases_delete": { + "title": "Dzēst fāzes diapazonu", + "description": "Atlasiet diapazonu, ko noņemt no melnraksta.", + "data": { + "range_index": "Fāzes diapazons" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automātiski noteiktas fāzes", + "description": "Profils: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fāze(-s) noteikta(s) automātiski.** Izvēlieties darbību tālāk.", + "data": { + "action": "Darbība" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nosaukums Atklātās fāzes", + "description": "Profils: **{profile_name}**\n\n**Noteikta {detected_count} fāze(s):**\n{ranges_summary}\n\nKatrai fāzei piešķiriet nosaukumu." + }, + "assign_profile_phases_select": { + "title": "Piešķiriet fāzes diapazonus", + "description": "Izvēlieties profilu, lai konfigurētu fāzes diapazonus.", + "data": { + "profile": "Profils" + } + }, + "profile_stats": { + "title": "Profila statistika", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Izveidot jaunu profilu", + "description": "Izveidojiet jaunu profilu manuāli vai no iepriekšējā cikla.\n\nProfila nosaukumu piemēri: “Delikāti”, “Lielas noslodzes”, “Ātrā mazgāšana”", + "data": { + "profile_name": "Profila nosaukums", + "reference_cycle": "Atsauces cikls (pēc izvēles)", + "manual_duration": "Manuālais ilgums (minūtes)" + } + }, + "edit_profile": { + "title": "Rediģēt profilu", + "description": "Izvēlieties profilu, ko pārdēvēt", + "data": { + "profile": "Profils" + } + }, + "rename_profile": { + "title": "Rediģēt profila informāciju", + "description": "Pašreizējais profils: {current_name}", + "data": { + "new_name": "Profila nosaukums", + "manual_duration": "Manuālais ilgums (minūtes)" + } + }, + "delete_profile_select": { + "title": "Dzēst profilu", + "description": "Izvēlieties dzēšamo profilu", + "data": { + "profile": "Profils" + } + }, + "delete_profile_confirm": { + "title": "Apstipriniet profila dzēšanu", + "description": "⚠️ Tādējādi tiks neatgriezeniski izdzēsts profils: {profile_name}", + "data": { + "unlabel_cycles": "Noņemiet etiķeti no cikliem, izmantojot šo profilu" + } + }, + "auto_label_cycles": { + "title": "Automātiski iezīmējiet vecos ciklus", + "description": "Kopā atrasti {total_count} cikli. Profili: {profiles}", + "data": { + "confidence_threshold": "Minimālā uzticamība (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Atlasiet Cycle to Label", + "description": "✓ = pabeigts, ⚠ = atsākts, ✗ = pārtraukts", + "data": { + "cycle_id": "Cikls" + } + }, + "select_cycle_to_delete": { + "title": "Dzēst ciklu", + "description": "⚠️ Tādējādi atlasītais cikls tiks neatgriezeniski dzēsts", + "data": { + "cycle_id": "Pārejiet uz dzēšanu" + } + }, + "label_cycle": { + "title": "Etiķetes cikls", + "description": "{cycle_info}", + "data": { + "profile_name": "Profils", + "new_profile_name": "Jauns profila nosaukums (ja tiek izveidots)" + } + }, + "post_process": { + "title": "Procesu vēsture (apvienošana/sadaļa)", + "description": "Notīriet ciklu vēsturi, sapludinot sadrumstalotas darbības un sadalot nepareizi sapludinātos ciklus laika logā. Ievadiet stundu skaitu, lai atskatītos atpakaļ (visiem izmantojiet 999999).", + "data": { + "time_range": "Atskata logs (stundas)", + "gap_seconds": "Apvienot/sadalīt intervālu (sekundēs)" + }, + "data_description": { + "time_range": "Stundu skaits, lai meklētu sadrumstalotos ciklus, lai sapludinātu. Izmantojiet 999999, lai apstrādātu visus vēsturiskos datus.", + "gap_seconds": "Slieksnis, lai izlemtu par sadalīšanu vai apvienošanu. Atstarpes, kas ir ĪSĀKAS par šo, ir apvienotas. Atšķirības, kas ir garākas par šo, ir sadalītas. Nolaidiet to, lai piespiestu sadalīt ciklu, kas tika sapludināts nepareizi." + } + }, + "cleanup_profile": { + "title": "Notīrīt vēsturi - atlasiet profilu", + "description": "Izvēlieties vizualizējamo profilu. Tādējādi tiks ģenerēts grafiks, kurā būs redzami visi iepriekšējie cikli šim profilam, lai palīdzētu noteikt novirzes.", + "data": { + "profile": "Profils" + } + }, + "cleanup_select": { + "title": "Vēstures tīrīšana - diagramma un dzēšana", + "description": "![Grafs]({graph_url})\n\n**Vizualizācija {profile_name}**\n\nDiagrammā atsevišķi cikli parādīti kā krāsainas līnijas. Tālāk esošajā sarakstā norādiet novirzes (līnijas, kas atrodas tālu no grupas) un to krāsu, lai tās izdzēstu.\n\n**Atlasiet ciklus, lai neatgriezeniski dzēstu:**", + "data": { + "cycles_to_delete": "Cikli dzēšanai (pārbaudiet atbilstošās krāsas)" + } + }, + "interactive_editor": { + "title": "Apvienot/sadalīt interaktīvo redaktoru", + "description": "Atlasiet manuālu darbību, kas jāveic cikla vēsturē.", + "menu_options": { + "editor_split": "Sadaliet ciklu (atrodiet nepilnības)", + "editor_merge": "Apvienot ciklus (apvienot fragmentus)", + "editor_delete": "Dzēst ciklu(s)", + "menu_back": "← Atpakaļ" + } + }, + "editor_select": { + "title": "Atlasiet cikli", + "description": "Atlasiet apstrādājamo ciklu(-us).\n\n{info_text}", + "data": { + "selected_cycles": "Cikli" + } + }, + "editor_configure": { + "title": "Konfigurēt un lietot", + "description": "{preview_md}", + "data": { + "confirm_action": "Darbība", + "merged_profile": "Iegūtais profils", + "new_profile_name": "Jauns profila nosaukums", + "confirm_commit": "Jā, es apstiprinu šo darbību", + "segment_0_profile": "1. segmenta profils", + "segment_1_profile": "2. segmenta profils", + "segment_2_profile": "3. segmenta profils", + "segment_3_profile": "4. segmenta profils", + "segment_4_profile": "5. segmenta profils", + "segment_5_profile": "6. segmenta profils", + "segment_6_profile": "7. segmenta profils", + "segment_7_profile": "8. segmenta profils", + "segment_8_profile": "9. segmenta profils", + "segment_9_profile": "10. segmenta profils" + } + }, + "editor_split_params": { + "title": "Sadalītā konfigurācija", + "description": "Pielāgojiet šī cikla sadalīšanas jutību. Jebkura tukšgaitas sprauga, kas ir garāka par šo, izraisīs šķelšanos.", + "data": { + "split_mode": "Sadalīšanas metode" + } + }, + "editor_split_auto_params": { + "title": "Automātiskās sadalīšanas konfigurācija", + "description": "Pielāgojiet šī cikla sadalīšanas jutību. Jebkura neaktivitātes pauze, kas ir garāka par šo, izraisīs sadalīšanu.", + "data": { + "min_gap_seconds": "Sadalīšanas atstarpes slieksnis (sekundes)" + } + }, + "editor_split_manual_params": { + "title": "Manuālās sadalīšanas laikspiedoli", + "description": "{preview_md}Cikla logs: **{cycle_start_wallclock} → {cycle_end_wallclock}** datumā {cycle_date}.\n\nIevadiet vienu vai vairākus sadalīšanas laikspiedolus (`HH:MM` vai `HH:MM:SS`), pa vienam katrā rindā. Cikls tiks pārgriezts pie katra laikspiedola — N laikspiedoli rada N+1 segmentus.", + "data": { + "split_timestamps": "Sadalīšanas laika zīmogi" + } + }, + "reprocess_history": { + "title": "Apkope: atkārtoti apstrādājiet un optimizējiet datus", + "description": "Veic dziļu vēsturisko datu tīrīšanu:\n1. Apgriež nulles jaudas rādījumus (klusums).\n2. Nosaka cikla laiku/ilgumu.\n3. Pārrēķina visus statistikas modeļus (aploksnes).\n\n**Izpildiet šo, lai novērstu datu problēmas vai lietotu jaunus algoritmus.**" + }, + "wipe_history": { + "title": "Vēstures dzēšana (tikai testēšanai)", + "description": "⚠️ Tādējādi tiks neatgriezeniski izdzēsti VISI šīs ierīces saglabātie cikli un profili. Šo darbību nevar atsaukt!" + }, + "export_import": { + "title": "Eksportēt/importēt JSON", + "description": "Kopējiet/ielīmējiet šīs ierīces ciklus, profilus un iestatījumus. Eksportējiet tālāk vai ielīmējiet JSON, lai importētu.", + "data": { + "mode": "Darbība", + "json_payload": "Pilnīga konfigurācija JSON" + }, + "data_description": { + "mode": "Izvēlieties Eksportēt, lai kopētu pašreizējos datus un iestatījumus, vai Importēt, lai ielīmētu eksportētos datus no citas WashData ierīces.", + "json_payload": "Eksportēšanai: kopējiet šo JSON (ietver ciklus, profilus un visus precizētos iestatījumus). Importēšanai: ielīmējiet JSON, kas eksportēts no WashData." + } + }, + "record_cycle": { + "title": "Ierakstu cikls", + "description": "Manuāli ierakstiet ciklu, lai izveidotu tīru profilu bez traucējumiem.\n\nStatuss: **{status}**\nIlgums: {duration} s\nParaugi: {samples}", + "menu_options": { + "record_refresh": "Atsvaidzināt statusu", + "record_stop": "Pārtraukt ierakstīšanu (saglabāt un apstrādāt)", + "record_start": "Sāciet jaunu ierakstu", + "record_process": "Apstrādāt pēdējo ierakstu (apgriezt un saglabāt)", + "record_discard": "Atmest pēdējo ierakstu", + "menu_back": "← Atpakaļ" + } + }, + "record_process": { + "title": "Procesa ierakstīšana", + "description": "![Grafs]({graph_url})\n\n**Grafiks parāda neapstrādātu ierakstu.** Zils = saglabāt, sarkans = ierosinātā apgriešana.\n*Grafiks ir statisks un netiek atjaunināts līdz ar veidlapas izmaiņām.*\n\nIerakstīšana pārtraukta. Apgriešana tiek saskaņota ar noteikto iztveršanas ātrumu (~{sampling_rate} s).\n\nNeapstrādāts ilgums: {duration} s\nParaugi: {samples}", + "data": { + "head_trim": "Apgriešanas sākums (sekundēs)", + "tail_trim": "Apgriešanas beigas (sekundēs)", + "save_mode": "Saglabāt galamērķi", + "profile_name": "Profila nosaukums" + } + }, + "learning_feedbacks": { + "title": "Mācīšanās atsauksmes", + "description": "Gaidošās atsauksmes: {count}\n\n{pending_table}\n\nAtlasiet apstrādājamo cikla pārskatīšanas pieprasījumu.", + "menu_options": { + "learning_feedbacks_pick": "Pārskatīt gaidošu atsauksmi", + "learning_feedbacks_dismiss_all": "Noraidīt visas gaidošās atsauksmes", + "menu_back": "← Atpakaļ" + } + }, + "learning_feedbacks_pick": { + "title": "Izvēlieties atsauksmi pārskatīšanai", + "description": "Izvēlieties cikla pārskatīšanas pieprasījumu, ko atvērt.", + "data": { + "selected_feedback": "Izvēlieties ciklu pārskatīšanai" + } + }, + "learning_feedbacks_empty": { + "title": "Mācīšanās atsauksmes", + "description": "Netika atrasts neviens neapstiprināts atsauksmju pieprasījums.", + "menu_options": { + "menu_back": "← Atpakaļ" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Noraidīt visas gaidošās atsauksmes", + "description": "Jūs gatavojaties noraidīt **{count}** gaidošus atsauksmju pieprasījumus. Noraidītie cikli paliks jūsu vēsturē, taču tie neveidos jaunu mācīšanās signālu. To nevar atsaukt.\n\n✅ Atzīmējiet tālāk esošo izvēles rūtiņu un noklikšķiniet uz **Iesniegt**, lai noraidītu visu.\n❌ Atstājiet to neatzīmētu un noklikšķiniet uz **Iesniegt**, lai atceltu un atgrieztos.", + "data": { + "confirm_dismiss_all": "Apstiprināt: noraidīt visus gaidošos atsauksmju pieprasījumus" + } + }, + "resolve_feedback": { + "title": "Atrisiniet atsauksmes", + "description": "{comparison_img}{candidates_table}\n**Atklātais profils**: {detected_profile} ({confidence_pct}%)\n**Paredzamais ilgums**: {est_duration_min} min\n**Faktiskais ilgums**: {act_duration_min} min\n\n__Izvēlieties darbību tālāk:__", + "data": { + "action": "Ko jūs vēlētos darīt?", + "corrected_profile": "Pareiza programma (ja tiek labota)", + "corrected_duration": "Pareizais ilgums sekundēs (ja tiek labots)" + } + }, + "trim_cycle_select": { + "title": "Apgriešanas cikls - atlasiet ciklu", + "description": "Izvēlieties apgriešanas ciklu. ✓ = pabeigts, ⚠ = atsākts, ✗ = pārtraukts", + "data": { + "cycle_id": "Cikls" + } + }, + "trim_cycle": { + "title": "Apgriešanas cikls", + "description": "Pašreizējais logs: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Darbība" + } + }, + "trim_cycle_start": { + "title": "Iestatiet apgriešanas sākumu", + "description": "Cikla logs: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nPašreizējais sākums: **{current_wallclock}** (+{current_offset_min} min no cikla sākuma)\n\nIzvēlieties jaunu sākuma laiku, izmantojot tālāk esošo pulksteņa atlasītāju.", + "data": { + "trim_start_time": "Jauns sākuma laiks", + "trim_start_min": "Jauns sākums (minūtes no cikla sākuma)" + } + }, + "trim_cycle_end": { + "title": "Iestatiet Apgriešanas beigas", + "description": "Cikla logs: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nPašreizējās beigas: **{current_wallclock}** (+{current_offset_min} min no cikla sākuma)\n\nIzvēlieties jaunu beigu laiku, izmantojot tālāk esošo pulksteņa atlasītāju.", + "data": { + "trim_end_time": "Jauns beigu laiks", + "trim_end_min": "Jaunas beigas (minūtes no cikla sākuma)" + } + } + }, + "error": { + "import_failed": "Importēšana neizdevās. Lūdzu, ielīmējiet derīgu WashData eksportēšanas JSON.", + "select_exactly_one": "Atlasiet tieši vienu ciklu šai darbībai.", + "select_at_least_one": "Atlasiet vismaz vienu ciklu šai darbībai.", + "select_at_least_two": "Atlasiet vismaz divus ciklus šai darbībai.", + "profile_exists": "Profils ar šādu nosaukumu jau pastāv", + "rename_failed": "Neizdevās pārdēvēt profilu. Profils var neeksistēt vai jau ir pieņemts jauns vārds.", + "assignment_failed": "Neizdevās piešķirt profilu ciklam", + "duplicate_phase": "Fāze ar šādu nosaukumu jau pastāv.", + "phase_not_found": "Atlasītā fāze netika atrasta.", + "invalid_phase_name": "Fāzes nosaukuma lauks nedrīkst būt tukšs.", + "phase_name_too_long": "Fāzes nosaukums ir pārāk garš.", + "invalid_phase_range": "Fāzes diapazons nav derīgs. Beigas ir lielākas par sākumu.", + "invalid_phase_timestamp": "Laikspiedols nav derīgs. Izmantojiet formātu GGGG-MM-DD HH:MM vai HH:MM.", + "overlapping_phase_ranges": "Fāžu diapazoni pārklājas. Pielāgojiet diapazonus un mēģiniet vēlreiz.", + "incomplete_phase_row": "Katrā fāzes rindā ir jāiekļauj fāze un visas atlasītā režīma sākuma/beigu vērtības.", + "timestamp_mode_cycle_required": "Lūdzu, atlasiet avota ciklu, izmantojot laikspiedola režīmu.", + "no_phase_ranges": "Šai darbībai vēl nav pieejams neviens fāzes diapazons.", + "feedback_notification_title": "WashData: Pārbaudīt ciklu ({device})", + "feedback_notification_message": "**Ierīce**: {device}\n**Programma**: {program} ({confidence}% ticamība)\n**Laiks**: {time}\n\nWashData ir nepieciešama jūsu palīdzība, lai pārbaudītu šo noteikto ciklu.\n\nLai apstiprinātu vai labotu šo rezultātu, lūdzu, dodieties uz sadaļu **Iestatījumi > Ierīces un pakalpojumi > WashData > Konfigurēt > Mācību atsauksmes**.", + "suggestions_ready_notification_title": "WashData: ieteicamie iestatījumi ir gatavi ({device})", + "suggestions_ready_notification_message": "Sensors **Ieteiktie iestatījumi** tagad ziņo par **{count}** praktiski izmantojamiem ieteikumiem.\n\nLai tos pārskatītu un lietotu: **Iestatījumi > Ierīces un pakalpojumi > WashData > Konfigurēt > Papildu iestatījumi > Lietot ieteiktās vērtības**.\n\nIeteikumi nav obligāti un tiek parādīti pārskatīšanai pirms saglabāšanas.", + "trim_range_invalid": "Sākumam jābūt pirms beigām. Lūdzu, pielāgojiet apgriešanas punktus.", + "trim_failed": "Apgriešana neizdevās. Cikls var vairs nepastāvēt vai logs ir tukšs.", + "invalid_split_timestamp": "Laika zīmogs nav derīgs vai atrodas ārpus cikla loga. Izmantojiet HH:MM vai HH:MM:SS cikla loga ietvaros.", + "no_split_segments_found": "No šiem laika zīmogiem nevarēja izveidot nevienu derīgu segmentu. Pārliecinieties, ka katrs laika zīmogs ir cikla logā un segmenti ir vismaz 1 minūti gari.", + "auto_tune_suggestion": "{device_type} {device_title} konstatēja spoku ciklus. Ieteicamās minimālās jaudas izmaiņas: {current_min}W -> {new_min}W (nav lietots automātiski).", + "auto_tune_title": "WashData automātiskā noregulēšana", + "auto_tune_fallback": "{device_type} {device_title} konstatēja spoku ciklus. Ieteicamās minimālās jaudas izmaiņas: {current_min}W -> {new_min}W (nav lietots automātiski)." + }, + "abort": { + "no_cycles_found": "Nav atrasts neviens cikls", + "no_split_segments_found": "Atlasītajā ciklā netika atrasti sadalāmi segmenti.", + "cycle_not_found": "Neizdevās ielādēt atlasīto ciklu.", + "no_power_data": "Atlasītajam ciklam nav pieejami jaudas izsekošanas dati.", + "no_profiles_found": "Nav atrasts neviens profils. Vispirms izveidojiet profilu.", + "no_profiles_for_matching": "Atbilstībai nav pieejams neviens profils. Vispirms izveidojiet profilus.", + "no_unlabeled_cycles": "Visi cikli jau ir marķēti", + "no_suggestions": "Vēl nav pieejama neviena ieteiktā vērtība. Palaidiet dažus ciklus un mēģiniet vēlreiz.", + "no_predictions": "Nekādu prognožu", + "no_custom_phases": "Nav atrasta neviena pielāgota fāze.", + "reprocess_success": "Veiksmīgi atkārtoti apstrādāti {count} cikli.", + "debug_data_cleared": "Atkļūdošanas dati ir veiksmīgi notīrīti no {count} cikliem." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cikla sākums", + "cycle_finish": "Cikla finišs", + "cycle_live": "Tiešraides norise" + } + }, + "common_text": { + "options": { + "unlabeled": "(bez etiķetes)", + "create_new_profile": "Izveidot jaunu profilu", + "remove_label": "Noņemt etiķeti", + "no_reference_cycle": "(Nav atskaites cikla)", + "all_device_types": "Visi ierīču veidi", + "current_suffix": "(pašreizējais)", + "editor_select_info": "Atlasiet 1 ciklu, lai sadalītu, vai 2+ ciklus, lai sapludinātu.", + "editor_select_info_split": "Atlasiet 1 ciklu sadalīšanai.", + "editor_select_info_merge": "Atlasiet 2+ ciklus apvienošanai.", + "editor_select_info_delete": "Atlasiet 1+ ciklus dzēšanai.", + "no_cycles_recorded": "Vēl nav reģistrēts neviens cikls.", + "profile_summary_line": "- **{name}**: {count} cikli, {avg} min vid", + "no_profiles_created": "Vēl nav izveidots neviens profils.", + "phase_preview": "Fāzes priekšskatījums", + "phase_preview_no_curve": "Vidējā profila līkne vēl nav pieejama. Izpildīt/iezīmēt vairāk ciklu šim profilam.", + "average_power_curve": "Vidējās jaudas līkne", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cikli, vidēji ~{duration} min)", + "profile_option_short_fmt": "{name} ({count} cikli, ~{duration} min)", + "deleted_cycles_from_profile": "Izdzēsti {count} cikli no {profile}.", + "not_enough_data_graph": "Nav pietiekami daudz datu, lai izveidotu grafiku.", + "no_profiles_found": "Nav atrasts neviens profils.", + "cycle_info_fmt": "Cikls: {start}, {duration} m, pašreizējais: {label}", + "top_candidates_header": "### Labākie kandidāti", + "tbl_profile": "Profils", + "tbl_confidence": "Pārliecība", + "tbl_correlation": "Korelācija", + "tbl_duration_match": "Ilgums atbilst", + "detected_profile": "Atklāts profils", + "estimated_duration": "Paredzamais ilgums", + "actual_duration": "Faktiskais ilgums", + "choose_action": "__Izvēlieties darbību tālāk:__", + "tbl_count": "Skaitīt", + "tbl_avg": "Vid", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Enerģija (vid.)", + "tbl_energy_total": "Enerģija (kopā)", + "tbl_consistency": "Sastāv.", + "tbl_last_run": "Pēdējais skrējiens", + "graph_legend_title": "Grafika leģenda", + "graph_legend_body": "Zilā josla apzīmē novēroto minimālo un maksimālo jaudas patēriņa diapazonu. Līnija parāda vidējo jaudas līkni.", + "recording_preview": "Ieraksta priekšskatījums", + "trim_start": "Apgriešanas sākums", + "trim_end": "Apgriešanas beigas", + "split_preview_title": "Sadalīts priekšskatījums", + "split_preview_found_fmt": "Atrasti {count} segmenti.", + "split_preview_confirm_fmt": "Noklikšķiniet uz Apstiprināt, lai sadalītu šo ciklu {count} atsevišķos ciklos.", + "merge_preview_title": "Apvienot priekšskatījums", + "merge_preview_joining_fmt": "Notiek pievienošanās {count} cikliem. Atstarpes tiks aizpildītas ar 0W rādījumiem.", + "editor_delete_preview_title": "Dzēšanas priekšskatījums", + "editor_delete_preview_intro": "Atlasītie cikli tiks neatgriezeniski dzēsti:", + "editor_delete_preview_confirm": "Noklikšķiniet uz Apstiprināt, lai neatgriezeniski dzēstu šos ciklu ierakstus.", + "no_power_preview": "*Priekšskatīšanai nav pieejami jaudas dati.*", + "profile_comparison": "Profilu salīdzinājums", + "actual_cycle_label": "Šis cikls (faktiskais)", + "storage_usage_header": "Krātuves izmantošana", + "storage_file_size": "Faila lielums", + "storage_cycles": "Cikli", + "storage_profiles": "Profili", + "storage_debug_traces": "Atkļūdošanas pēdas", + "overview_suffix": "Pārskats", + "phase_preview_for_profile": "Profila fāzes priekšskatījums", + "current_ranges_header": "Pašreizējie diapazoni", + "assign_phases_help": "Izmantojiet tālāk norādītās darbības, lai pievienotu, rediģētu, dzēstu vai saglabātu diapazonus.", + "profile_summary_header": "Profila kopsavilkums", + "recent_cycles_header": "Pēdējie cikli", + "trim_cycle_preview_title": "Cikla apgriešanas priekšskatījums", + "trim_cycle_preview_no_data": "Šim ciklam nav pieejami dati par jaudu.", + "trim_cycle_preview_kept_suffix": "paturēja", + "table_program": "Programma", + "table_when": "Kad", + "table_length": "Ilgums", + "table_match": "Sakritība", + "table_profile": "Profils", + "table_cycles": "Cikli", + "table_avg_length": "Vid. ilgums", + "table_last_run": "Pēdējais skrējiens", + "table_avg_energy": "Vid. enerģija", + "table_detected_program": "Atklātā programma", + "table_confidence": "Pārliecība", + "table_reported": "Ziņots", + "phase_builtin_suffix": "(Iebūvēts)", + "phase_none_available": "Nav pieejamas fāzes.", + "settings_deprecation_warning": "⚠️ **Novecojis ierīces veids:** {device_type} ir plānots noņemt nākamajā laidienā. WashData atbilstības cauruļvads nesniedz ticamus rezultātus šai ierīču klasei. Jūsu integrācija turpinās darboties nolietojuma periodā; lai izslēgtu šo brīdinājumu, tālāk pārslēdziet **Ierīces veidu** uz kādu no atbalstītajiem veidiem (veļas mazgājamā mašīna, žāvētājs, veļas mazgājamās mašīnas-žāvēšanas mašīna, trauku mazgājamā mašīna, gaisa cepeškrāsns, maizes automāts vai sūknis) vai **Cits (uzlabots)**, ja jūsu ierīce neatbilst nevienam no atbalstītajiem veidiem. **Other (Advanced)** apzināti piegādā vispārīgus noklusējuma iestatījumus, kas nav pielāgoti nevienai konkrētai ierīcei, tāpēc jums pašam būs jākonfigurē sliekšņi, noildzes un atbilstošie parametri; pārslēdzoties, visi esošie iestatījumi tiek saglabāti.", + "phase_other_device_types": "Citi ierīču veidi:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Sadaliet ciklu (atrodiet nepilnības)", + "merge": "Apvienot ciklus (apvienot fragmentus)", + "delete": "Dzēst ciklu(s)" + } + }, + "split_mode": { + "options": { + "auto": "Automātiski noteikt dīkstāves spraugas", + "manual": "Manuāli laika zīmogi" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Apkope: atkārtoti apstrādājiet un optimizējiet datus", + "clear_debug_data": "Notīrīt atkļūdošanas datus (atbrīvojiet vietu)", + "wipe_history": "Dzēst VISUS šīs ierīces datus (neatgriezeniski)", + "export_import": "Eksportēt/importēt JSON ar iestatījumiem (kopēt/ielīmēt)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksportēt visus datus", + "import": "Importēt/apvienot datus" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automātiski iezīmējiet vecos ciklus", + "select_cycle_to_label": "Etiķetes specifiskais cikls", + "select_cycle_to_delete": "Dzēst ciklu", + "interactive_editor": "Apvienot/sadalīt interaktīvo redaktoru", + "trim_cycle": "Apgriešanas cikla dati" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Izveidot jaunu profilu", + "edit_profile": "Rediģēt/pārdēvēt profilu", + "delete_profile": "Dzēst profilu", + "profile_stats": "Profila statistika", + "cleanup_profile": "Vēstures tīrīšana - diagramma un dzēšana", + "assign_phases": "Piešķiriet fāzes diapazonus" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Izveidot jaunu fāzi", + "edit_custom_phase": "Rediģēšanas fāze", + "delete_custom_phase": "Dzēst fāzi" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Pievienot fāzes diapazonu", + "edit_range": "Rediģēt fāzes diapazonu", + "delete_range": "Dzēst fāzes diapazonu", + "clear_ranges": "Notīrīt visus diapazonus", + "auto_detect_ranges": "Automātiski noteikt fāzes", + "save_ranges": "Saglabāt un atgriezt" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Atsvaidzināt statusu", + "stop_recording": "Pārtraukt ierakstīšanu (saglabāt un apstrādāt)", + "start_recording": "Sāciet jaunu ierakstu", + "process_recording": "Apstrādāt pēdējo ierakstu (apgriezt un saglabāt)", + "discard_recording": "Atmest pēdējo ierakstu" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Izveidot jaunu profilu", + "existing_profile": "Pievienot esošajam profilam", + "discard": "Atmest ierakstu" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Apstiprināt - Pareiza noteikšana", + "correct": "Pareizi - izvēlieties programmu/ilgumu", + "ignore": "Ignorēt - viltus pozitīvs/trokšņains cikls", + "delete": "Dzēst - noņemt no vēstures" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nosauciet un piemērojiet fāzes", + "cancel": "Dodieties atpakaļ bez izmaiņām" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Iestatiet sākuma punktu", + "set_end": "Iestatiet beigu punktu", + "reset": "Atiestatīt uz pilnu ilgumu", + "apply": "Uzklājiet apgriešanu", + "cancel": "Atcelt" + } + }, + "device_type": { + "options": { + "washing_machine": "Veļas mašīna", + "dryer": "Žāvētājs", + "washer_dryer": "Kombinētais veļas mazgājamās mašīnas-žāvētājs", + "dishwasher": "Trauku mazgājamā mašīna", + "coffee_machine": "Kafijas automāts (novecojis)", + "ev": "Elektriskais transportlīdzeklis (novecojis)", + "air_fryer": "Gaisa cepeškrāsns", + "heat_pump": "Siltumsūknis (novecojis)", + "bread_maker": "Maizes gatavotājs", + "pump": "Sūknis / Sūknis", + "oven": "Cepeškrāsns (novecojusi)", + "other": "Cits (uzlabots)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiķetes cikls", + "description": "Piešķiriet esošu profilu iepriekšējam ciklam vai noņemiet iezīmi.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce uz etiķetes." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "Apzīmējamā cikla ID." + }, + "profile_name": { + "name": "Profila nosaukums", + "description": "Esoša profila nosaukums (izveidojiet profilus izvēlnē Pārvaldīt profilus). Atstājiet tukšu, lai noņemtu etiķeti." + } + } + }, + "create_profile": { + "name": "Izveidot profilu", + "description": "Izveidojiet jaunu profilu (savrupu vai pamatojoties uz atsauces ciklu).", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce." + }, + "profile_name": { + "name": "Profila nosaukums", + "description": "Jaunā profila nosaukums (piemēram, \"Lielas slodzes\", \"Delikateses\")." + }, + "reference_cycle_id": { + "name": "Atsauces cikla ID", + "description": "Izvēles cikla ID šī profila pamatā." + } + } + }, + "delete_profile": { + "name": "Dzēst profilu", + "description": "Dzēsiet profilu un pēc izvēles noņemiet etiķetes cikliem, izmantojot to.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce." + }, + "profile_name": { + "name": "Profila nosaukums", + "description": "Profils, kas jāizdzēš." + }, + "unlabel_cycles": { + "name": "Ciklu atcelšana", + "description": "Noņemiet profila etiķeti no cikliem, izmantojot šo profilu." + } + } + }, + "auto_label_cycles": { + "name": "Automātiski iezīmējiet vecos ciklus", + "description": "Ar atpakaļejošu datumu iezīmējiet nemarķētos ciklus, izmantojot profila saskaņošanu.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce." + }, + "confidence_threshold": { + "name": "Pārliecības slieksnis", + "description": "Minimālā atbilstības ticamība (0,50–0,95), lai lietotu etiķetes." + } + } + }, + "export_config": { + "name": "Eksportēt konfigurāciju", + "description": "Eksportējiet šīs ierīces profilus, ciklus un iestatījumus JSON failā (katrai ierīcei).", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Eksportējamā veļas mašīnas ierīce." + }, + "path": { + "name": "Ceļš", + "description": "Izvēles absolūtais rakstāmā faila ceļš (pēc noklusējuma /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importēt konfigurāciju", + "description": "Importējiet šīs ierīces profilus, ciklus un iestatījumus no JSON eksporta faila (iestatījumi var tikt pārrakstīti).", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce, kurā importēt." + }, + "path": { + "name": "Ceļš", + "description": "Absolūtais ceļš uz importējamo eksportējamo JSON failu." + } + } + }, + "submit_cycle_feedback": { + "name": "Iesniedziet cikla atsauksmes", + "description": "Pēc cikla pabeigšanas apstipriniet vai labojiet automātiski noteikto programmu. Norādiet vai nu “entry_id” (papildu) vai “device_id” (ieteicams).", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "WashData ierīce (ieteicama enter_id vietā)." + }, + "entry_id": { + "name": "Ieejas ID", + "description": "Ierīces konfigurācijas ieraksta ID (alternatīva ierīces_id)." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "Cikla ID, kas parādīts atsauksmju paziņojumā / žurnālos." + }, + "user_confirmed": { + "name": "Apstipriniet noteikto programmu", + "description": "Iestatiet patiesu, ja noteiktā programma ir pareiza." + }, + "corrected_profile": { + "name": "Labots profils", + "description": "Ja tas nav apstiprināts, norādiet pareizo profila/programmas nosaukumu." + }, + "corrected_duration": { + "name": "Izlabotais ilgums (sekundēs)", + "description": "Pēc izvēles koriģētais ilgums sekundēs." + }, + "notes": { + "name": "Piezīmes", + "description": "Izvēles piezīmes par šo ciklu." + } + } + }, + "record_start": { + "name": "Ierakstīšanas cikla sākums", + "description": "Sāciet manuāli ierakstīt tīru ciklu (apiet visu atbilstošo loģiku).", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce ierakstīšanai." + } + } + }, + "record_stop": { + "name": "Ierakstīšanas cikla apturēšana", + "description": "Pārtrauciet manuālu ierakstīšanu.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "Veļas mašīnas ierīce ierakstīšanas pārtraukšanai." + } + } + }, + "trim_cycle": { + "name": "Apgriešanas cikls", + "description": "Apgrieziet iepriekšējā cikla jaudas datus līdz noteiktam laika logam.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "WashData ierīce." + }, + "cycle_id": { + "name": "Cikla ID", + "description": "Apgriežamā cikla ID." + }, + "trim_start_s": { + "name": "Apgriešanas sākums (sekundēs)", + "description": "Saglabājiet datus no šīs nobīdes sekundēs. Noklusējums 0." + }, + "trim_end_s": { + "name": "Apgriešanas beigas (sekundēs)", + "description": "Saglabājiet datus līdz šai nobīdei sekundēs. Noklusējums = pilns ilgums." + } + } + }, + "pause_cycle": { + "name": "Apturēt ciklu", + "description": "Apturiet WashData ierīces aktīvo ciklu.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "WashData ierīce, lai apturētu." + } + } + }, + "resume_cycle": { + "name": "Atsākt ciklu", + "description": "Atsāciet WashData ierīces apturēto ciklu.", + "fields": { + "device_id": { + "name": "Ierīce", + "description": "WashData ierīce, lai atsāktu." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Skriešana" + }, + "match_ambiguity": { + "name": "Atbilstības neskaidrība" + } + }, + "sensor": { + "washer_state": { + "name": "valsts", + "state": { + "off": "Izslēgts", + "idle": "Dīkstāvē", + "starting": "Sākas", + "running": "Skriešana", + "paused": "Pauzēts", + "user_paused": "Lietotāja apturēts", + "ending": "Beigas", + "finished": "Pabeigts", + "anti_wrinkle": "Pretgrumbu", + "delay_wait": "Gaida sākumu", + "interrupted": "Pārtrauca", + "force_stopped": "Piespiedu kārtā apturēta", + "rinse": "Noskalo", + "unknown": "Nezināms", + "clean": "Tīrs" + } + }, + "washer_program": { + "name": "Programma" + }, + "time_remaining": { + "name": "Atlikušais laiks" + }, + "total_duration": { + "name": "Kopējais ilgums" + }, + "cycle_progress": { + "name": "Progress" + }, + "current_power": { + "name": "Pašreizējā jauda" + }, + "elapsed_time": { + "name": "Pagājušais laiks" + }, + "debug_info": { + "name": "Atkļūdošanas informācija" + }, + "match_confidence": { + "name": "Atbilstības pārliecība" + }, + "top_candidates": { + "name": "Labākie kandidāti", + "state": { + "none": "Nav" + } + }, + "current_phase": { + "name": "Pašreizējā fāze" + }, + "wash_phase": { + "name": "Fāze" + }, + "profile_cycle_count": { + "name": "Profila {profile_name} skaits", + "unit_of_measurement": "cikli" + }, + "suggestions": { + "name": "Ieteiktie iestatījumi" + }, + "pump_runs_today": { + "name": "Sūknis darbojas (pēdējās 24 h)" + }, + "cycle_count": { + "name": "Ciklu skaits" + } + }, + "select": { + "program_select": { + "name": "Cikla programma", + "state": { + "auto_detect": "Automātiska noteikšana" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Piespiedu beigu cikls" + }, + "pause_cycle": { + "name": "Apturēt ciklu" + }, + "resume_cycle": { + "name": "Atsākt ciklu" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Nepieciešams derīgs ierīces_id." + }, + "cycle_id_required": { + "message": "Nepieciešams derīgs cikla_id." + }, + "profile_name_required": { + "message": "Nepieciešams derīgs profila_nosaukums." + }, + "device_not_found": { + "message": "Ierīce nav atrasta." + }, + "no_config_entry": { + "message": "Ierīcei nav atrasts neviens konfigurācijas ieraksts." + }, + "integration_not_loaded": { + "message": "Integrācija šai ierīcei nav ielādēta." + }, + "cycle_not_found_or_no_power": { + "message": "Cikls nav atrasts vai tam nav datu par barošanu." + }, + "trim_failed_empty_window": { + "message": "Apgriešana neizdevās - cikls nav atrasts, nav strāvas datu vai iegūtais logs ir tukšs." + }, + "trim_invalid_range": { + "message": "trim_end_s ir jābūt lielākam par trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/mk.json b/custom_components/ha_washdata/translations/mk.json new file mode 100644 index 0000000..df3796e --- /dev/null +++ b/custom_components/ha_washdata/translations/mk.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Поставување податоци за перење", + "description": "Конфигурирајте ја вашата машина за перење или друг уред.\n\nПотребен е сензор за напојување.\n\n**Следно, ќе бидете прашани дали сакате да го креирате вашиот прв профил.**", + "data": { + "name": "Име на уред", + "device_type": "Тип на уред", + "power_sensor": "Сензор за напојување", + "min_power": "Праг на минимална моќност (W)" + }, + "data_description": { + "name": "Пријателско име за овој уред (на пр., „Машина за перење“, „Миење садови“).", + "device_type": "Каков тип на апарат е ова? Помага при прилагодување на откривањето и етикетирањето.", + "power_sensor": "Сензорскиот ентитет кој известува за потрошувачката на енергија во реално време (во вати) од вашиот паметен приклучок.", + "min_power": "Читањата на моќноста над овој праг (во вати) покажуваат дека апаратот работи. Започнете со 2W за повеќето уреди." + } + }, + "first_profile": { + "title": "Направете прв профил", + "description": "**Циклус** е целосна работа на вашиот апарат (на пр., товар алишта). **Профил** ги групира овие циклуси по тип (на пр., „Памук“, „Брзо перење“). Создавањето профил сега помага веднаш да се процени времетраењето на интеграцијата.\n\nМожете рачно да го изберете овој профил во контролите на уредот ако не се открие автоматски.\n\n**Оставете „Име на профилот“ празно за да го прескокнете овој чекор.**", + "data": { + "profile_name": "Име на профилот (изборно)", + "manual_duration": "Проценето времетраење (минути)" + } + } + }, + "error": { + "cannot_connect": "Не успеа да се поврзе", + "invalid_auth": "Неважечка автентикација", + "unknown": "Неочекувана грешка" + }, + "abort": { + "already_configured": "Уредот е веќе конфигуриран" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Прегледајте ги повратните информации за учењето", + "settings": "Поставки", + "notifications": "Известувања", + "manage_cycles": "Управување со циклуси", + "manage_profiles": "Управувајте со профили", + "manage_phase_catalog": "Управувајте со фазен каталог", + "record_cycle": "Циклус на снимање (прирачник)", + "diagnostics": "Дијагностика и одржување" + } + }, + "apply_suggestions": { + "title": "Копирајте ги предложените вредности", + "description": "Ова ќе ги копира тековните предложени вредности во вашите зачувани поставки. Ова е еднократно дејство и не овозможува автоматско презапишување.\n\nПредложени вредности за копирање:\n{suggested}", + "data": { + "confirm": "Потврдете го дејството за копирање" + } + }, + "apply_suggestions_confirm": { + "title": "Прегледајте ги предложените промени", + "description": "Пронајдени се WashData **{pending_count}** предложени вредности за примена.\n\nПромени:\n{changes}\n\n✅ Проверете го полето подолу и кликнете **Поднеси** за да ги примените и веднаш да ги зачувате овие промени.\n❌ Оставете го нештиклирано и кликнете **Поднеси** за да откажете и да се вратите назад.", + "data": { + "confirm_apply_suggestions": "Применете ги и зачувајте ги овие промени" + } + }, + "settings": { + "title": "Поставки", + "description": "{deprecation_warning}Прилагодете ги праговите за откривање, учењето и напредното однесување. Стандардните поставки добро функционираат за повеќето поставки.\n\nМоментално се достапни предложени поставки: {suggestions_count}.\nАко ова е над 0, отворете ги напредните поставки и користете 'Примени ги предложените вредности' за да ги прегледате препораките.", + "data": { + "apply_suggestions": "Примени ги предложените вредности", + "device_type": "Тип на уред", + "power_sensor": "Ентитетот на сензор за напојување", + "min_power": "Минимална моќност (W)", + "off_delay": "Доцнење (и) за завршување на циклусот", + "notify_actions": "Акции за известување", + "notify_people": "Одложете ја испораката додека овие луѓе не се дома", + "notify_only_when_home": "Одложување на известувањата додека избраното лице не е дома", + "notify_fire_events": "Настани за автоматизација на пожар", + "notify_start_services": "Почеток на циклус - Цели за известување", + "notify_finish_services": "Завршување на циклусот - Цели за известување", + "notify_live_services": "Напредок во живо - Цели за известување", + "notify_before_end_minutes": "Известување пред завршување (минути пред крајот)", + "notify_title": "Наслов на известување", + "notify_icon": "Икона за известување", + "notify_start_message": "Започнете со формат на порака", + "notify_finish_message": "Заврши формат на порака", + "notify_pre_complete_message": "Формат на порака пред завршување", + "show_advanced": "Уредете ги напредните поставки (следен чекор)" + }, + "data_description": { + "apply_suggestions": "Обележете го ова поле за да ги копирате вредностите предложени од алгоритмот за учење во формуларот. Прегледајте пред да зачувате.\n\nПрепорачано (од учење):\n- мин_моќност: {suggested_min_power} В\n- off_delay: {suggested_off_delay} с\n- чувар_интервал: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- профил_совпаѓање_интервал: {suggested_profile_match_interval} с\n- автоматска_етикета_доверба: {suggested_auto_label_confidence}\n- времетраење_толеранција: {suggested_duration_tolerance}\n- профил_траење_толеранција: {suggested_profile_duration_tolerance}\n- Сооднос_профил_совпаѓање_мин. времетраење: {suggested_profile_match_min_duration_ratio}\n- Сооднос_профил_совпаѓање_макс_времетраење: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} В\n- Краен_енергетски_праг: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Изберете го типот на апаратот (Машина за перење, машина за сушење, машина за миење садови, машина за кафе, EV). Оваа ознака се чува со секој циклус за подобра организација.", + "power_sensor": "Ентитетот на сензорот што известува за моќност во реално време (во вати). Ова може да го промените во секое време без да изгубите историски податоци - корисно ако замените паметен приклучок.", + "min_power": "Читањата на моќноста над овој праг (во вати) покажуваат дека апаратот работи. Започнете со 2W за повеќето уреди.", + "off_delay": "Секунди, измазнетата моќност мора да остане под прагот на запирање за да се означи завршувањето. Стандардно: 120 сек.", + "notify_actions": "Изборна секвенца на дејствија на Home Assistant за извршување на известувања (скрипти, известување, TTS, итн.). Ако е поставено, дејствата се извршуваат пред повторно да известува за услугата.", + "notify_people": "Луѓе ентитети кои се користат за присуство. Кога е овозможено, доставувањето на известувањата се одложува додека барем едно избрано лице не е дома.", + "notify_only_when_home": "Ако е овозможено, одложете ги известувањата додека барем едно избрано лице не е дома.", + "notify_fire_events": "Ако е овозможено, активирајте настани на Home Assistant за почеток и крај на циклусот (ha_washdata_cycle_started и ha_washdata_cycle_ended) за да се вози автоматизација.", + "notify_start_services": "Една или повеќе ги извести услугите за да известат кога започнува циклус. Оставете празно за да ги прескокнете известувањата за почеток.", + "notify_finish_services": "Една или повеќе ги известуваат службите за да известат кога циклусот завршува или се приближува до завршување. Оставете празно за да ги прескокнете известувањата за завршување.", + "notify_live_services": "Една или повеќе услуги за известување да добиваат ажурирања за напредокот во живо додека трае циклусот (само придружна апликација за мобилни телефони). Оставете празно за да прескокнете ажурирања во живо.", + "notify_before_end_minutes": "Неколку минути пред проценетиот крај на циклусот за да се активира известување. Поставете на 0 за да се оневозможи. Стандардно: 0.", + "notify_title": "Прилагоден наслов за известувања. Поддржува место за место {device}.", + "notify_icon": "Икона за користење за известувања (на пр. mdi:wash-machine). Оставете празно за испраќање без икона.", + "notify_start_message": "Пораката е испратена кога ќе започне циклусот. Поддржува место за место {device}.", + "notify_finish_message": "Пораката е испратена кога ќе заврши циклусот. Поддржува местенка за {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Текст на повторливите ажурирања на напредокот во живо додека трае циклусот. Поддржува местенка за {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Известувања", + "description": "Конфигурирајте ги каналите за известување, шаблоните и опционалните ажурирања за напредокот на мобилниот во живо.", + "data": { + "notify_actions": "Акции за известување", + "notify_people": "Одложете ја испораката додека овие луѓе не се дома", + "notify_only_when_home": "Одложување на известувањата додека избраното лице не е дома", + "notify_fire_events": "Настани за автоматизација на пожар", + "notify_start_services": "Почеток на циклус - Цели за известување", + "notify_finish_services": "Завршување на циклусот - Цели за известување", + "notify_live_services": "Напредок во живо - Цели за известување", + "notify_before_end_minutes": "Известување пред завршување (минути пред крајот)", + "notify_live_interval_seconds": "Интервал за ажурирање во живо (секунди)", + "notify_live_overrun_percent": "Додаток за надминување на ажурирањата во живо (%)", + "notify_live_chronometer": "Тајмер за одбројување на хронометар", + "notify_title": "Наслов на известување", + "notify_icon": "Икона за известување", + "notify_start_message": "Започнете со формат на порака", + "notify_finish_message": "Заврши формат на порака", + "notify_pre_complete_message": "Формат на порака пред завршување", + "energy_price_entity": "Цена на енергија - ентитет (опционално)", + "energy_price_static": "Цена на енергија - Статичка вредност (опционално)", + "go_back": "Врати се без зачувување", + "notify_reminder_message": "Формат на порака за потсетување", + "notify_timeout_seconds": "Автоматско отфрли По (секунди)", + "notify_channel": "Канал за известување (статус/во живо/потсетник)", + "notify_finish_channel": "Завршен канал за известување" + }, + "data_description": { + "go_back": "Означете го ова и кликнете Испрати за да ги отфрлите сите промени погоре и да се вратите во главното мени.", + "notify_actions": "Изборна секвенца на дејствија на Home Assistant за извршување на известувања (скрипти, известување, TTS, итн.). Ако е поставено, дејствата се извршуваат пред повторно да известува за услугата.", + "notify_people": "Луѓе ентитети кои се користат за присуство. Кога е овозможено, доставувањето на известувањата се одложува додека барем едно избрано лице не е дома.", + "notify_only_when_home": "Ако е овозможено, одложете ги известувањата додека барем едно избрано лице не е дома.", + "notify_fire_events": "Ако е овозможено, активирајте настани на Home Assistant за почеток и крај на циклусот (ha_washdata_cycle_started и ha_washdata_cycle_ended) за да се вози автоматизација.", + "notify_start_services": "Една или повеќе служби за известување за да известат кога започнува циклус (на пр. notify.mobile_app_my_phone). Оставете празно за да ги прескокнете известувањата за почеток.", + "notify_finish_services": "Една или повеќе ги известуваат службите за да известат кога циклусот завршува или се приближува до завршување. Оставете празно за да ги прескокнете известувањата за завршување.", + "notify_live_services": "Една или повеќе услуги за известување да добиваат ажурирања за напредокот во живо додека трае циклусот (само придружна апликација за мобилни телефони). Оставете празно за да прескокнете ажурирања во живо.", + "notify_before_end_minutes": "Неколку минути пред проценетиот крај на циклусот за да се активира известување. Поставете на 0 за да се оневозможи. Стандардно: 0.", + "notify_live_interval_seconds": "Колку често се испраќаат ажурирања за напредок додека работи. Пониските вредности се повеќе одговорни, но трошат повеќе известувања. Се наметнува минимум 30 секунди - вредностите под 30 секунди автоматски се заокружуваат на 30 секунди.", + "notify_live_overrun_percent": "Безбедносна маржа над проценетиот број на ажурирања за долги/пречекорени циклуси (на пример 20% дозволува 1,2 пати проценети ажурирања).", + "notify_live_chronometer": "Кога е овозможено, секое ажурирање во живо вклучува одбројување на хронометарот до проценетото време на завршување. Тајмерот за известување автоматски отчукува на уредот помеѓу ажурирањата (само придружна апликација за Android).", + "notify_title": "Прилагоден наслов за известувања. Поддржува место за место {device}.", + "notify_icon": "Икона за користење за известувања (на пр. mdi:wash-machine). Оставете празно за испраќање без икона.", + "notify_start_message": "Пораката е испратена кога ќе започне циклусот. Поддржува место за место {device}.", + "notify_finish_message": "Пораката е испратена кога ќе заврши циклусот. Поддржува местенка за {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Изберете нумерички ентитет што ја држи моменталната цена на електричната енергија (на пр. сензор.цена_електрична енергија, влезен_број.стапка на енергија). Кога е поставено, го овозможува заштитното место {cost}. Има предност над статичката вредност ако и двете се конфигурирани.", + "energy_price_static": "Фиксна цена на струја за kWh. Кога е поставено, го овозможува заштитното место {cost}. Игнорирано ако некој ентитет е конфигуриран погоре. Додајте го симболот на вашата валута во шаблонот за пораки, на пр. {cost} €.", + "notify_reminder_message": "Текстот на еднократниот потсетник го испрати конфигурираниот број минути пред проценетиот крај. Различно од повторливите ажурирања во живо погоре. Поддржува местенка за {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Автоматски отфрлете ги известувањата по овие многу секунди (препратени како придружна апликација „времето“). Поставете на 0 за да ги задржите додека не се отфрли. Стандардно: 0.", + "notify_channel": "Android канал за известување за придружна апликација за статус, напредок во живо и известувања за потсетници. Звукот и важноста на каналот се конфигурираат во придружната апликација при првото користење на името на каналот - WashData го поставува само името на каналот. Оставете празно за стандардната апликација.", + "notify_finish_channel": "Одделете го каналот Android за готовиот аларм (исто така го користат потсетникот и тагата за чекање алишта) за да може да има свој звук. Оставете празно за повторно да го користите каналот погоре.", + "notify_pre_complete_message": "Текст на повторливите ажурирања на напредокот во живо додека трае циклусот. Поддржува местенка за {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Напредни поставки", + "description": "Прилагодете ги праговите за откривање, учењето и напредното однесување. Стандардните поставки добро функционираат за повеќето конфигурации.", + "sections": { + "suggestions_section": { + "name": "Предложени поставки", + "description": "Достапни се {suggestions_count} научени предлози. Означете 'Примени ги предложените вредности' за да ги прегледате предложените промени пред зачувување.", + "data": { + "apply_suggestions": "Примени ги предложените вредности" + }, + "data_description": { + "apply_suggestions": "Обележете го ова поле за да отворите чекор за преглед за предложените вредности од алгоритмот за учење. Ништо не се зачувува автоматски.\n\nПрепорачано (од учење):\n- мин_моќност: {suggested_min_power} В\n- off_delay: {suggested_off_delay} с\n- чувар_интервал: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- профил_совпаѓање_интервал: {suggested_profile_match_interval} с\n- автоматска_етикета_доверба: {suggested_auto_label_confidence}\n- времетраење_толеранција: {suggested_duration_tolerance}\n- профил_траење_толеранција: {suggested_profile_duration_tolerance}\n- Сооднос_профил_совпаѓање_мин. времетраење: {suggested_profile_match_min_duration_ratio}\n- Сооднос_профил_совпаѓање_макс_времетраење: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} В\n- Краен_енергетски_праг: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Откривање и прагови на моќност", + "description": "Определува кога уредот се смета за ВКЛУЧЕН или ИСКЛУЧЕН, енергетските прагови и времето на премини меѓу состојбите.", + "data": { + "start_duration_threshold": "Започнете времетраење (и) на отфрлање", + "start_energy_threshold": "Стартување на енергијата порта (Wh)", + "completion_min_seconds": "Минимално време за завршување (секунди)", + "start_threshold_w": "Почетен праг (Ш)", + "stop_threshold_w": "Праг на запирање (Ш)", + "end_energy_threshold": "Крајна енергетска порта (Wh)", + "running_dead_zone": "Трчање мртва зона (секунди)", + "end_repeat_count": "Крај на повторено броење", + "min_off_gap": "Минимум јаз помеѓу циклуси (и)", + "sampling_interval": "Интервал (и) на земање примероци" + }, + "data_description": { + "start_duration_threshold": "Игнорирајте ги кратките скокови на напојувањето пократки од ова времетраење (секунди). Ги филтрира врвовите на багажникот (на пр., 60 W за 2 секунди) пред да започне вистинскиот циклус. Стандардно: 5 секунди.", + "start_energy_threshold": "Циклусот мора да акумулира барем толку енергија (Wh) за време на фазата СТАРТ за да се потврди. Стандардно: 0,005 Wh.", + "completion_min_seconds": "Циклусите пократки од ова ќе бидат означени како „прекинати“ дури и ако завршат природно. Стандардно: 600s.", + "start_threshold_w": "Моќта мора да се искачи НАД овој праг за да започне циклус. Поставете повисоко од вашата моќност во мирување. Стандардна: мин_моќ + 1 W.", + "stop_threshold_w": "Моќта мора да падне ПОД овој праг за да заврши циклусот. Поставете нешто над нулата за да избегнете бучава што предизвикува лажни краеви. Стандардно: 0,6 * мин_моќ.", + "end_energy_threshold": "Циклусот завршува само ако енергијата во последниот прозорец за исклучување е под оваа вредност (Wh). Спречува лажни краеви ако напојувањето флуктуира близу нула. Стандардно: 0,05 Wh.", + "running_dead_zone": "Откако ќе започне циклусот, игнорирајте ги падовите на струјата за толку секунди за да спречите откривање на лажни краеви за време на почетната фаза на центрифугирање. Поставете на 0 за да се оневозможи. Стандардно: 0 секунди.", + "end_repeat_count": "Пред завршувањето на циклусот, мора да се исполни последователно бројот на пати на крајниот услов (напојување под прагот за off_delay). Повисоките вредности спречуваат лажни краеви за време на паузите. Стандардно: 1.", + "min_off_gap": "Ако циклусот започне во рок од толку секунди од завршувањето на претходниот, тие ќе се спојат во еден циклус.", + "sampling_interval": "Минимален интервал на земање примероци во секунди. Ажурирањата побрзи од оваа стапка ќе бидат игнорирани. Стандардно: 30 секунди (2 секунди за машини за перење, машини за сушење и машини за миење садови)." + } + }, + "matching_section": { + "name": "Совпаѓање на профили и учење", + "description": "Определува колку агресивно WashData ги споредува активните циклуси со научените профили и кога да побара повратни информации.", + "data": { + "profile_match_min_duration_ratio": "Сооднос на минимално времетраење на совпаѓање на профилот (0,1-1,0)", + "profile_match_interval": "Интервал на совпаѓање на профилот (секунди)", + "profile_match_threshold": "Праг на совпаѓање на профилот", + "profile_unmatch_threshold": "Праг за несовпаѓање на профилот", + "auto_label_confidence": "Доверба за автоматска етикета (0-1)", + "learning_confidence": "Доверба барање за повратни информации (0-1)", + "suppress_feedback_notifications": "Потиснете ги известувањата за повратни информации", + "duration_tolerance": "Толеранција на времетраење (0-0,5)", + "smoothing_window": "Прозорец за измазнување (примероци)", + "profile_duration_tolerance": "Толеранција за времетраење на совпаѓање на профилот (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Сооднос на минимално времетраење за совпаѓање на профилот. Циклусот на трчање мора да биде барем овој дел од времетраењето на профилот за да се совпадне. Пониско = претходно совпаѓање. Стандардно: 0,50 (50%).", + "profile_match_interval": "Колку често (во секунди) да се обиде да се совпадне профилот во текот на циклусот. Пониските вредности обезбедуваат побрзо откривање, но користат повеќе процесор. Стандардно: 300 секунди (5 минути).", + "profile_match_threshold": "Потребен е минимален резултат за сличност DTW (0,0–1,0) за да се смета профилот како совпаѓање. Повисоко = построго совпаѓање, помалку лажни позитиви. Стандардно: 0,4.", + "profile_unmatch_threshold": "Резултат за сличност на DTW (0,0–1,0) под кој претходно совпаднат профил е одбиен. Треба да биде пониско од прагот на натпревар за да се спречи треперење. Стандардно: 0,35.", + "auto_label_confidence": "Автоматски означете ги циклусите на или над оваа доверба по завршувањето (0,0–1,0).", + "learning_confidence": "Минимална доверба за барање потврда од корисникот преку настан (0,0–1,0).", + "suppress_feedback_notifications": "Кога е овозможено, WashData сè уште ќе следи и ќе бара повратни информации внатрешно, но нема да прикажува постојани известувања со кои ќе барате да ги потврдите циклусите. Корисно кога циклусите секогаш се откриваат правилно и сметате дека известувањата ви го одвлекуваат вниманието.", + "duration_tolerance": "Толеранција за проценките на преостанатото време за време на трчање (0,0-0,5 одговара на 0-50%).", + "smoothing_window": "Број на неодамнешни отчитувања на моќноста користени за измазнување со просечно движење.", + "profile_duration_tolerance": "Толеранција што се користи со совпаѓање на профилот за варијансата на вкупното времетраење (0,0–0,5). Стандардно однесување: ±25%." + } + }, + "timing_section": { + "name": "Време, одржување и дебагирање", + "description": "Watchdog, ресетирање на напредокот, автоматско одржување и изложување на дебаг ентитети.", + "data": { + "watchdog_interval": "Интервал на чувар (секунди)", + "no_update_active_timeout": "Истекување (и) без ажурирање", + "progress_reset_delay": "Доцнење со ресетирање на напредокот (секунди)", + "auto_maintenance": "Овозможете автоматско одржување", + "expose_debug_entities": "Изложете ги ентитетите за отстранување грешки", + "save_debug_traces": "Зачувај траги за отстранување грешки" + }, + "data_description": { + "watchdog_interval": "Секунди помеѓу проверките на чуварот додека трчате. Стандардно: 5 секунди. ПРЕДУПРЕДУВАЊЕ: Проверете дали ова е ПОВИСОКО од интервалот за ажурирање на вашиот сензор за да избегнете лажни запирања (на пр., ако сензорот се ажурира на секои 60-ти, користете 65 секунди+).", + "no_update_active_timeout": "За сензорите за објавување при промена: само присилно завршувајте АКТИВЕН циклус ако не пристигнат ажурирања за толку секунди. Завршувањето со мала моќност сè уште го користи доцнењето за исклучување.", + "progress_reset_delay": "Откако ќе заврши циклусот (100%), почекајте толку секунди во мирување пред да го ресетирате напредокот на 0%. Стандардно: 300s.", + "auto_maintenance": "Овозможете автоматско одржување (поправка на примероци, спојување фрагменти).", + "expose_debug_entities": "Прикажи напредни сензори (доверба, фаза, двосмисленост) за дебагирање.", + "save_debug_traces": "Складирајте детални податоци за рангирање и моќна трага во историјата (Ја зголемува употребата на складирање)." + } + }, + "anti_wrinkle_section": { + "name": "Штит против брчки (машини за сушење)", + "description": "Игнорирајте ги вртењата на барабанот по завршување на циклусот за да спречите фантомски циклуси. Само машина за сушење / машина за перење-сушење.", + "data": { + "anti_wrinkle_enabled": "Штит против брчки (само машина за сушење)", + "anti_wrinkle_max_power": "Максимална моќност против брчки (W)", + "anti_wrinkle_max_duration": "Максимално времетраење против брчки (и)", + "anti_wrinkle_exit_power": "Излезна моќност против брчки (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Игнорирајте ги кратките ротации на барабанот со мала моќност по завршувањето на циклусот за да спречите циклуси на духови (само машина за сушење/миење-сушење).", + "anti_wrinkle_max_power": "Пукнатините на или под оваа моќност се третираат како ротации против брчки наместо нови циклуси. Се применува само кога е овозможен штитот против брчки.", + "anti_wrinkle_max_duration": "Максимална должина на пукање за лекување како ротација против брчки. Се применува само кога е овозможен штитот против брчки.", + "anti_wrinkle_exit_power": "Ако напојувањето падне под ова ниво, веднаш излезете од анти-навремено (исклучено). Се применува само кога е овозможен штитот против брчки." + } + }, + "delay_start_section": { + "name": "Откривање одложен старт", + "description": "Третирајте го трајниот standby со мала потрошувачка како состојба 'Чека да започне' додека циклусот навистина не почне.", + "data": { + "delay_start_detect_enabled": "Овозможете откривање одложен почеток", + "delay_confirm_seconds": "Одложен старт: време за потврда на standby режим (s)", + "delay_timeout_hours": "Одложен почеток: Максимално време на чекање (ч)" + }, + "data_description": { + "delay_start_detect_enabled": "Кога е овозможено, краток скок на одвод со мала моќност проследен со одржливо напојување во мирување се открива како одложен почеток. Сензорот за состојбата покажува „Чека да започне“ за време на доцнењето. Не се следи времето на програмата или напредокот додека циклусот всушност не започне. Потребно е start_threshold_w да се постави над моќноста на одводниот шил.", + "delay_confirm_seconds": "Моќноста мора да остане меѓу прагот за запирање (W) и прагот за старт (W) толку секунди пред WashData да премине во состојба 'Чека да започне'. Ова ги филтрира кратките врвови при навигација низ менито. Стандардно: 60 s.", + "delay_timeout_hours": "Безбедносен истек: ако машината сè уште е во „Чека да стартува“ по толку часови, WashData се ресетира на Исклучено. Стандардно: 8 ч." + } + }, + "external_triggers_section": { + "name": "Надворешни активатори, врата и пауза", + "description": "Опционален надворешен сензор за крај на циклус, сензор за врата за откривање пауза / празнење и прекинувач за исклучување на напојувањето при пауза.", + "data": { + "external_end_trigger_enabled": "Овозможете Активирање за завршување на надворешниот циклус", + "external_end_trigger": "Надворешен сензор за крај на циклусот", + "external_end_trigger_inverted": "Инвертирај ја логиката на активирањето (активирањето е исклучено)", + "door_sensor_entity": "Сензор за врата", + "pause_cuts_power": "Исклучете го напојувањето кога паузирате", + "switch_entity": "Префрли ентитет (за паузирање на прекин на струја)", + "notify_unload_delay_minutes": "Доцнење на известувањето за чекање перење (мин.)" + }, + "data_description": { + "external_end_trigger_enabled": "Овозможете следење на надворешен бинарен сензор за да го активирате завршувањето на циклусот.", + "external_end_trigger": "Изберете ентитет на бинарен сензор. Кога ќе се активира овој сензор, тековниот циклус ќе заврши со статусот „завршен“.", + "external_end_trigger_inverted": "Стандардно, активирањето се вклучува кога сензорот ќе се вклучи. Проверете го ова поле да се активира кога наместо тоа сензорот ќе се исклучи.", + "door_sensor_entity": "Изборен бинарен сензор за вратата на машината (вклучено = отворено, исклучено = затворено). Кога вратата ќе се отвори за време на активен циклус, WashData ќе го потврди циклусот како намерно паузиран. По завршувањето на циклусот, ако вратата е сè уште затворена, состојбата се менува во 'Clean' додека вратата не се отвори. Забелешка: затворањето на вратата НЕ автоматско продолжување на паузиран циклус - продолжувањето мора да се активира рачно преку копчето или услугата Продолжи циклус.", + "pause_cuts_power": "Кога паузирате преку копчето или услугата циклус на пауза, исклучете го и конфигурираниот ентитет на прекинувачот. Станува на сила само ако е конфигуриран ентитет на прекинувач за овој уред.", + "switch_entity": "Ентитетот на прекинувачот да се префрли кога се паузира или продолжува. Потребно е кога е овозможено „Исклучи напојување при паузирање“.", + "notify_unload_delay_minutes": "Испратете известување преку каналот за известување за финиш откако циклусот ќе заврши и вратата не е отворена толку минути (потребен е сензор за врата). Поставете на 0 за да се оневозможи. Стандардно: 60 мин." + } + }, + "pump_section": { + "name": "Надзор на пумпа", + "description": "Само за уреди од тип пумпа. Активира настан ако циклусот на пумпата трае подолго од ова време.", + "data": { + "pump_stuck_duration": "Праг (и) на предупредување за заглавена пумпа (само за пумпата)" + }, + "data_description": { + "pump_stuck_duration": "Ако циклусот на пумпата сè уште работи по толку секунди, настанот ha_washdata_pump_stuck се активира еднаш. Користете го ова за да откриете заглавен мотор (вообичаената работа на пумпата е под 60 секунди). Стандардно: 1800 с (30 мин). Само тип на уред на пумпа." + } + }, + "device_link_section": { + "name": "Врска на уредот", + "description": "Изборно, поврзете го овој уред WashData со постоечки Home Assistant уред (на пример, паметниот приклучок или самиот уред). Уредот WashData потоа се прикажува како „Поврзан преку“ тој уред. Оставете празно за да го задржите WashData како самостоен уред.", + "data": { + "linked_device": "Поврзан уред" + }, + "data_description": { + "linked_device": "Изберете го уредот со кој ќе го поврзете WashData. Ова додава референца „Поврзано преку“ во регистарот на уреди; WashData чува сопствена картичка и ентитети на уредот. Исчистете го изборот за да ја отстраните врската." + } + } + } + }, + "diagnostics": { + "title": "Дијагностика и одржување", + "description": "Извршете дејства за одржување, како што се спојување фрагментирани циклуси или мигрирање на складирани податоци.\n\n**Употреба на простор за складирање**\n\n- Големина на датотека: {file_size_kb} KB\n- Циклуси: {cycle_count}\n- Профили: {profile_count}\n- Траги за отстранување грешки: {debug_count}", + "menu_options": { + "reprocess_history": "Одржување: Повторна обработка и оптимизирање на податоците", + "clear_debug_data": "Исчистете ги податоците за отстранување грешки (ослободете простор)", + "wipe_history": "Избришете ги СИТЕ податоци за овој уред (неповратно)", + "export_import": "Извезете/Увезете JSON со поставки (копирајте/залепете)", + "menu_back": "← Назад" + } + }, + "clear_debug_data": { + "title": "Исчистете ги податоците за отстранување грешки", + "description": "Дали сте сигурни дека сакате да ги избришете сите зачувани траги за отстранување грешки? Ова ќе ослободи простор, но ќе ги отстрани деталните информации за рангирањето од минатите циклуси." + }, + "manage_cycles": { + "title": "Управување со циклуси", + "description": "Неодамнешни циклуси:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Автоматско обележување на старите циклуси", + "select_cycle_to_label": "Специфичен циклус на етикетата", + "select_cycle_to_delete": "Избриши циклус", + "interactive_editor": "Спој/Сплит интерактивен уредник", + "trim_cycle_select": "Податоци за скратување на циклусот", + "menu_back": "← Назад" + } + }, + "manage_cycles_empty": { + "title": "Управување со циклуси", + "description": "Сè уште нема снимени циклуси.", + "menu_options": { + "auto_label_cycles": "Автоматско обележување на старите циклуси", + "menu_back": "← Назад" + } + }, + "manage_profiles": { + "title": "Управувајте со профили", + "description": "Резиме на профилот:\n{profile_summary}", + "menu_options": { + "create_profile": "Креирај нов профил", + "edit_profile": "Уреди/Преименувајте го профилот", + "delete_profile_select": "Избришете го профилот", + "profile_stats": "Статистика на профилот", + "cleanup_profile": "Исчистете ја историјата - графикон и избришете", + "assign_profile_phases_select": "Доделете опсези на фази", + "menu_back": "← Назад" + } + }, + "manage_profiles_empty": { + "title": "Управувајте со профили", + "description": "Сè уште нема создадени профили.", + "menu_options": { + "create_profile": "Креирај нов профил", + "menu_back": "← Назад" + } + }, + "manage_phase_catalog": { + "title": "Управувајте со фазен каталог", + "description": "Каталог на фази (стандардно за овој уред + приспособени фази):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Креирај нова фаза", + "phase_catalog_edit_select": "Фаза на уредување", + "phase_catalog_delete": "Избриши фаза", + "menu_back": "← Назад" + } + }, + "phase_catalog_create": { + "title": "Креирајте прилагодена фаза", + "description": "Направете приспособено име и опис на фазата и изберете на кој тип уред се однесува.", + "data": { + "target_device_type": "Целен тип на уред", + "phase_name": "Име на фаза", + "phase_description": "Опис на фаза" + } + }, + "phase_catalog_edit_select": { + "title": "Уреди приспособена фаза", + "description": "Изберете приспособена фаза за уредување.", + "data": { + "phase_name": "Прилагодена фаза" + } + }, + "phase_catalog_edit": { + "title": "Уреди приспособена фаза", + "description": "Фаза на уредување: {phase_name}", + "data": { + "phase_name": "Име на фаза", + "phase_description": "Опис на фаза" + } + }, + "phase_catalog_delete": { + "title": "Избришете ја прилагодената фаза", + "description": "Изберете приспособена фаза за бришење. Сите доделени опсези што ја користат оваа фаза ќе бидат отстранети.", + "data": { + "phase_name": "Прилагодена фаза" + } + }, + "assign_profile_phases": { + "title": "Доделете опсези на фази", + "description": "Фазен преглед за профил: **{profile_name}**\n\n{timeline_svg}\n\n** Тековни опсези: **\n{current_ranges}\n\nКористете ги дејствата подолу за додавање, уредување, бришење или зачувување опсези.", + "menu_options": { + "assign_profile_phases_add": "Додадете опсег на фази", + "assign_profile_phases_edit_select": "Уреди опсег на фази", + "assign_profile_phases_delete": "Избришете опсег на фази", + "phase_ranges_clear": "Исчистете ги сите опсези", + "assign_profile_phases_auto_detect": "Фази за автоматско откривање", + "phase_ranges_save": "Зачувај и Врати", + "menu_back": "← Назад" + } + }, + "assign_profile_phases_add": { + "title": "Додадете опсег на фази", + "description": "Додадете еден фазен опсег на тековниот нацрт.", + "data": { + "phase_name": "Фаза", + "start_min": "Почетна минута", + "end_min": "Крајна минута" + } + }, + "assign_profile_phases_edit_select": { + "title": "Уреди опсег на фази", + "description": "Изберете опсег за уредување.", + "data": { + "range_index": "Опсег на фази" + } + }, + "assign_profile_phases_edit": { + "title": "Уреди опсег на фази", + "description": "Ажурирајте го избраниот опсег на фази.", + "data": { + "phase_name": "Фаза", + "start_min": "Почетна минута", + "end_min": "Крајна минута" + } + }, + "assign_profile_phases_delete": { + "title": "Избришете опсег на фази", + "description": "Изберете опсег за отстранување од нацртот.", + "data": { + "range_index": "Опсег на фази" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Автоматски откриени фази", + "description": "Профил: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} фази се откриени автоматски.** Изберете дејство подолу.", + "data": { + "action": "Акција" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Откриени фази со име", + "description": "Профил: **{profile_name}**\n\n**{detected_count} откриени фази:**\n{ranges_summary}\n\nДоделете име на секоја фаза." + }, + "assign_profile_phases_select": { + "title": "Доделете опсези на фази", + "description": "Изберете профил за да ги конфигурирате опсезите на фази.", + "data": { + "profile": "Профил" + } + }, + "profile_stats": { + "title": "Статистика на профилот", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Креирај нов профил", + "description": "Направете нов профил рачно или од минатиот циклус.\n\nПримери за имиња на профили: „Деликатни производи“, „Тешка работа“, „Брзо перење“", + "data": { + "profile_name": "Име на профилот", + "reference_cycle": "Референтен циклус (опционално)", + "manual_duration": "Рачно времетраење (минути)" + } + }, + "edit_profile": { + "title": "Уредете го профилот", + "description": "Изберете профил за преименување", + "data": { + "profile": "Профил" + } + }, + "rename_profile": { + "title": "Уредете ги деталите за профилот", + "description": "Тековен профил: {current_name}", + "data": { + "new_name": "Име на профилот", + "manual_duration": "Рачно времетраење (минути)" + } + }, + "delete_profile_select": { + "title": "Избришете го профилот", + "description": "Изберете профил за бришење", + "data": { + "profile": "Профил" + } + }, + "delete_profile_confirm": { + "title": "Потврдете го бришењето на профилот", + "description": "⚠️ Ова трајно ќе го избрише профилот: {profile_name}", + "data": { + "unlabel_cycles": "Отстранете ја етикетата од циклусите што го користат овој профил" + } + }, + "auto_label_cycles": { + "title": "Автоматско обележување на старите циклуси", + "description": "Најдени се вкупно {total_count} циклуси. Профили: {profiles}", + "data": { + "confidence_threshold": "Минимална самодоверба (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Изберете Циклус за означување", + "description": "✓ = завршен, ⚠ = продолжи, ✗ = прекинат", + "data": { + "cycle_id": "Циклус" + } + }, + "select_cycle_to_delete": { + "title": "Избриши циклус", + "description": "⚠️ Ова трајно ќе го избрише избраниот циклус", + "data": { + "cycle_id": "Циклус за бришење" + } + }, + "label_cycle": { + "title": "Циклус на етикети", + "description": "{cycle_info}", + "data": { + "profile_name": "Профил", + "new_profile_name": "Ново име на профил (ако се создава)" + } + }, + "post_process": { + "title": "Историја на процеси (спој/сплит)", + "description": "Исчистете ја историјата на циклусот со спојување на фрагментирани работи и разделување на неправилно споени циклуси во временски прозорец. Внесете број на часови за да погледнете назад (користете 999999 за сите).", + "data": { + "time_range": "Прозорец за поглед (часови)", + "gap_seconds": "Спој/Сплит јаз (секунди)" + }, + "data_description": { + "time_range": "Број на часови за скенирање за спојување на фрагментирани циклуси. Користете 999999 за да ги обработите сите историски податоци.", + "gap_seconds": "Праг за одлучување за поделба наспроти спојување. Празнините ПОКРАТКИ од ова се споени. Подолгите празнини од ова се поделени. Намалете го ова за да принудите поделба на циклус што е погрешно споен." + } + }, + "cleanup_profile": { + "title": "Исчистете ја историјата - изберете Профил", + "description": "Изберете профил за визуелизација. Ова ќе генерира графикон што ги прикажува сите минати циклуси за овој профил за да помогне во идентификувањето на оддалечените.", + "data": { + "profile": "Профил" + } + }, + "cleanup_select": { + "title": "Исчистете ја историјата - графикон и избришете", + "description": "![Графикон]({graph_url})\n\n**Визуелизирање на {profile_name}**\n\nГрафиконот покажува поединечни циклуси како обоени линии. Идентификувајте оддалечени (линии далеку од групата) и совпаѓање со нивната боја во списокот подолу за да ги избришете.\n\n**Изберете циклуси за ТРАЈНО бришење:**", + "data": { + "cycles_to_delete": "Циклуси за бришење (проверете соодветни бои)" + } + }, + "interactive_editor": { + "title": "Спој/Сплит интерактивен уредник", + "description": "Изберете рачно дејство за извршување на историјата на вашиот циклус.", + "menu_options": { + "editor_split": "Поделете циклус (најдете празнини)", + "editor_merge": "Циклуси на спојување (спојување на фрагменти)", + "editor_delete": "Избриши циклус(и)", + "menu_back": "← Назад" + } + }, + "editor_select": { + "title": "Изберете циклуси", + "description": "Изберете го циклусот(ите) за обработка.\n\n{info_text}", + "data": { + "selected_cycles": "Циклуси" + } + }, + "editor_configure": { + "title": "Конфигурирај и примени", + "description": "{preview_md}", + "data": { + "confirm_action": "Акција", + "merged_profile": "Како резултат на профилот", + "new_profile_name": "Ново име на профил", + "confirm_commit": "Да, ја потврдувам оваа акција", + "segment_0_profile": "Профил на сегментот 1", + "segment_1_profile": "Профил на сегментот 2", + "segment_2_profile": "Профил на сегментот 3", + "segment_3_profile": "Профил на сегментот 4", + "segment_4_profile": "Профил на сегментот 5", + "segment_5_profile": "Профил на сегментот 6", + "segment_6_profile": "Профил на сегментот 7", + "segment_7_profile": "Профил на сегментот 8", + "segment_8_profile": "Профил на сегментот 9", + "segment_9_profile": "Профил на сегментот 10" + } + }, + "editor_split_params": { + "title": "Сплит конфигурација", + "description": "Прилагодете ја чувствителноста за разделување на овој циклус. Секоја празнина во мирување подолга од ова ќе предизвика расцеп.", + "data": { + "split_mode": "Метод на поделба" + } + }, + "editor_split_auto_params": { + "title": "Конфигурација на автоматско разделување", + "description": "Прилагодете ја чувствителноста за разделување на овој циклус. Секоја пауза на мирување подолга од ова ќе предизвика разделување.", + "data": { + "min_gap_seconds": "Праг на јазот за разделување (секунди)" + } + }, + "editor_split_manual_params": { + "title": "Рачни временски ознаки за разделување", + "description": "{preview_md}Прозорец на циклусот: **{cycle_start_wallclock} → {cycle_end_wallclock}** на {cycle_date}.\n\nВнесете една или повеќе временски ознаки за разделување (`HH:MM` или `HH:MM:SS`), по една во секој ред. Циклусот ќе се пресече на секоја временска ознака — N временски ознаки создаваат N+1 сегменти.", + "data": { + "split_timestamps": "Временски ознаки за поделба" + } + }, + "reprocess_history": { + "title": "Одржување: Повторна обработка и оптимизирање на податоците", + "description": "Врши длабоко чистење на историски податоци:\n1. Ги намалува отчитувањата со нулта моќност (тишина).\n2. Го поправа времето/траењето на циклусот.\n3. Ги пресметува повторно сите статистички модели (пликови).\n\n**Стартувај го ова за да ги поправиш проблемите со податоците или да примениш нови алгоритми.**" + }, + "wipe_history": { + "title": "Избришете ја историјата (само за тестирање)", + "description": "⚠️ Ова трајно ќе ги избрише СИТЕ зачувани циклуси и профили за овој уред. Ова не може да се врати!" + }, + "export_import": { + "title": "Извези/Увези JSON", + "description": "Копирајте/залепете циклуси, профили и поставки за овој уред. Извезете подолу или залепете JSON за увоз.", + "data": { + "mode": "Акција", + "json_payload": "Комплетна конфигурација JSON" + }, + "data_description": { + "mode": "Изберете Извоз за да ги копирате тековните податоци и поставки или Внеси за да ги залепите извезените податоци од друг уред WashData.", + "json_payload": "За извоз: копирајте го овој JSON (вклучува циклуси, профили и сите фино подесени поставки). За увоз: залепете JSON извезен од WashData." + } + }, + "record_cycle": { + "title": "Циклус за снимање", + "description": "Рачно снимајте циклус за да создадете чист профил без пречки.\n\nСтатус: **{status}**\nВреметраење: {duration} с\nПримери: {samples}", + "menu_options": { + "record_refresh": "Освежи статус", + "record_stop": "Стоп за снимање (Зачувај и обработувај)", + "record_start": "Започнете ново снимање", + "record_process": "Обработете го последното снимање (Скратете и зачувајте)", + "record_discard": "Отфрли ја последната снимка", + "menu_back": "← Назад" + } + }, + "record_process": { + "title": "Процес на снимање", + "description": "![Графикон]({graph_url})\n\n**Графиконот покажува необработено снимање.** Сино = Задржи, црвено = Предложено намалување.\n*Графиконот е статичен и не се ажурира со промени во формата.*\n\nСнимањето прекина. Кастрите се порамнети со откриената стапка на земање примероци (~{sampling_rate}s).\n\nНеобработено времетраење: {duration} с\nПримероци: {samples}", + "data": { + "head_trim": "Почеток на скратување (секунди)", + "tail_trim": "Намали крајот (секунди)", + "save_mode": "Зачувај дестинација", + "profile_name": "Име на профилот" + } + }, + "learning_feedbacks": { + "title": "Повратни информации за учење", + "description": "Повратни информации на чекање: {count}\n\n{pending_table}\n\nИзберете барање за преглед на циклус за обработка.", + "menu_options": { + "learning_feedbacks_pick": "Прегледај повратна информација на чекање", + "learning_feedbacks_dismiss_all": "Отфрли ги сите повратни информации на чекање", + "menu_back": "← Назад" + } + }, + "learning_feedbacks_pick": { + "title": "Изберете повратни информации за преглед", + "description": "Изберете барање за преглед на циклус за отворање.", + "data": { + "selected_feedback": "Изберете циклус за преглед" + } + }, + "learning_feedbacks_empty": { + "title": "Повратни информации за учење", + "description": "Не се пронајдени барања за повратни информации што чекаат.", + "menu_options": { + "menu_back": "← Назад" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Отфрли ги сите преостанати повратни информации", + "description": "Ќе отфрлите **{count}** барања за повратни информации што чекаат. Отфрлените циклуси остануваат во вашата историја, но нема да придонесуваат нов сигнал за учење. Ова не може да се врати.\n\n✅ Проверете го полето подолу и кликнете **Поднеси** за да отфрлите сè.\n❌ Оставете го нештиклирано и кликнете **Поднеси** за да откажете и да се вратите назад.", + "data": { + "confirm_dismiss_all": "Потврди: отфрли ги сите барања за повратни информации на чекање" + } + }, + "resolve_feedback": { + "title": "Решете ги повратните информации", + "description": "{comparison_img}{candidates_table}\n**Откриен профил**: {detected_profile} ({confidence_pct}%)\n**Проценето времетраење**: {est_duration_min} мин\n**Вистинско времетраење**: {act_duration_min} мин\n\n__Изберете дејство подолу:__", + "data": { + "action": "Што би сакале да правите?", + "corrected_profile": "Точна програма (ако се коригира)", + "corrected_duration": "Точно времетраење во секунди (ако се коригира)" + } + }, + "trim_cycle_select": { + "title": "Скратување на циклусот - изберете циклус", + "description": "Изберете циклус за скратување. ✓ = завршен, ⚠ = продолжи, ✗ = прекинат", + "data": { + "cycle_id": "Циклус" + } + }, + "trim_cycle": { + "title": "Циклус на скратување", + "description": "Тековен прозорец: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Акција" + } + }, + "trim_cycle_start": { + "title": "Поставете почеток на скратувањето", + "description": "Прозорец на циклус: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТековен почеток: **{current_wallclock}** (+{current_offset_min} мин. од почетокот на циклусот)\n\nИзберете ново време за почеток користејќи го избирачот на часовникот подолу.", + "data": { + "trim_start_time": "Ново време за почеток", + "trim_start_min": "Нов почеток (минути од почетокот на циклусот)" + } + }, + "trim_cycle_end": { + "title": "Поставете крај на скратувањето", + "description": "Прозорец на циклус: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТековен крај: **{current_wallclock}** (+{current_offset_min} мин. од почетокот на циклусот)\n\nИзберете ново време на завршување користејќи го избирачот на часовникот подолу.", + "data": { + "trim_end_time": "Ново крајно време", + "trim_end_min": "Нов крај (минути од почетокот на циклусот)" + } + } + }, + "error": { + "import_failed": "Увозот не успеа. Залепете важечки JSON за извоз на WashData.", + "select_exactly_one": "Изберете точно еден циклус за ова дејство.", + "select_at_least_one": "Изберете најмалку еден циклус за ова дејство.", + "select_at_least_two": "Изберете најмалку два циклуса за ова дејство.", + "profile_exists": "Профил со ова име веќе постои", + "rename_failed": "Не успеа да се преименува профилот. Профилот можеби не постои или веќе е земено ново име.", + "assignment_failed": "Не успеа да се додели профил на циклус", + "duplicate_phase": "Фаза со ова име веќе постои.", + "phase_not_found": "Избраната фаза не беше пронајдена.", + "invalid_phase_name": "Името на фазата не може да биде празно.", + "phase_name_too_long": "Името на фазата е предолго.", + "invalid_phase_range": "Опсегот на фази е неважечки. Крајот мора да биде поголем од почетокот.", + "invalid_phase_timestamp": "Временскиот печат е неважечки. Користете ГГГГ-ММ-ДД Ч:ММ или ГГ:ММ формат.", + "overlapping_phase_ranges": "Опсегот на фази се преклопуваат. Прилагодете ги опсезите и обидете се повторно.", + "incomplete_phase_row": "Секој фазен ред мора да вклучува фаза плус целосни вредности за почеток/крај за избраниот режим.", + "timestamp_mode_cycle_required": "Изберете изворен циклус кога користите режим на временска ознака.", + "no_phase_ranges": "Сè уште нема достапни фазни опсези за тоа дејство.", + "feedback_notification_title": "Податоци за перење: потврдете го циклусот ({device})", + "feedback_notification_message": "**Уред**: {device}\n**Програма**: {program} ({confidence}% доверба)\n**Време**: {time}\n\nНа WashData му треба вашата помош за да го потврди овој откриен циклус.\n\nОдете во **Поставки > Уреди и услуги > WashData > Конфигурирај > Повратни информации за учење** за да го потврдите или поправите овој резултат.", + "suggestions_ready_notification_title": "WashData: Предложените поставки се подготвени ({device})", + "suggestions_ready_notification_message": "Сензорот **Предложени поставки** сега известува **{count}** активни препораки.\n\nЗа да ги прегледате и примените: **Поставки > Уреди и услуги > WashData > Конфигурирај > Напредни поставки > Примени ги предложените вредности**.\n\nПредлозите се опционални и се прикажани за преглед пред да зачувате.", + "trim_range_invalid": "Почетокот мора да биде пред крајот. Ве молиме прилагодете ги точките за дотерување.", + "trim_failed": "Скратувањето не успеа. Циклусот можеби веќе не постои или прозорецот е празен.", + "invalid_split_timestamp": "Временската ознака е невалидна или е надвор од прозорецот на циклусот. Користете HH:MM или HH:MM:SS во рамките на прозорецот на циклусот.", + "no_split_segments_found": "Од овие временски ознаки не можеа да се создадат валидни сегменти. Осигурајте се дека секоја временска ознака е во рамките на прозорецот на циклусот и дека сегментите траат најмалку 1 минута.", + "auto_tune_suggestion": "{device_type} {device_title} открија циклуси на духови. Предложена промена на мин_моќноста: {current_min}W -> {new_min}W (не се применува автоматски).", + "auto_tune_title": "Автоматско подесување на WashData", + "auto_tune_fallback": "{device_type} {device_title} открија циклуси на духови. Предложена промена на мин_моќноста: {current_min}W -> {new_min}W (не се применува автоматски)." + }, + "abort": { + "no_cycles_found": "Не се пронајдени циклуси", + "no_split_segments_found": "Во избраниот циклус не беа пронајдени сегменти што може да се поделат.", + "cycle_not_found": "Избраниот циклус не можеше да се вчита.", + "no_power_data": "Нема достапни податоци за трага на енергија за избраниот циклус.", + "no_profiles_found": "Не се пронајдени профили. Прво креирајте профил.", + "no_profiles_for_matching": "Нема достапни профили за совпаѓање. Прво креирајте профили.", + "no_unlabeled_cycles": "Сите циклуси се веќе означени", + "no_suggestions": "Сè уште нема достапни предложени вредности. Извршете неколку циклуси и обидете се повторно.", + "no_predictions": "Нема предвидувања", + "no_custom_phases": "Не се пронајдени приспособени фази.", + "reprocess_success": "Успешно повторно обработени {count} циклуси.", + "debug_data_cleared": "Успешно се исчистени податоците за отстранување грешки од {count} циклуси." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Почеток на циклусот", + "cycle_finish": "Завршување на циклусот", + "cycle_live": "Прогрес во живо" + } + }, + "common_text": { + "options": { + "unlabeled": "(Неозначено)", + "create_new_profile": "Креирај нов профил", + "remove_label": "Отстранете ја етикетата", + "no_reference_cycle": "(Без референтен циклус)", + "all_device_types": "Сите типови уреди", + "current_suffix": "(тековно)", + "editor_select_info": "Изберете 1 циклус за разделување или 2+ циклуси за спојување.", + "editor_select_info_split": "Изберете 1 циклус за поделба.", + "editor_select_info_merge": "Изберете 2+ циклуси за спојување.", + "editor_select_info_delete": "Изберете 1+ циклуси за бришење.", + "no_cycles_recorded": "Сè уште нема снимени циклуси.", + "profile_summary_line": "- **{name}**: {count} циклуси, во просек {avg} м", + "no_profiles_created": "Сè уште нема создадени профили.", + "phase_preview": "Преглед на фаза", + "phase_preview_no_curve": "Просечната крива на профилот сè уште не е достапна. Стартувај/означи повеќе циклуси за овој профил.", + "average_power_curve": "Крива на просечна моќност", + "unit_min": "мин", + "profile_option_fmt": "{name} ({count} циклуси, ~{duration}m просечно)", + "profile_option_short_fmt": "{name} ({count} циклуси, ~{duration} м)", + "deleted_cycles_from_profile": "Избришани се {count} циклуси од {profile}.", + "not_enough_data_graph": "Нема доволно податоци за генерирање графикон.", + "no_profiles_found": "Не се пронајдени профили.", + "cycle_info_fmt": "Циклус: {start}, {duration}m, Тековен: {label}", + "top_candidates_header": "### Топ кандидати", + "tbl_profile": "Профил", + "tbl_confidence": "Доверба", + "tbl_correlation": "Корелација", + "tbl_duration_match": "Времетраење натпревар", + "detected_profile": "Откриен профил", + "estimated_duration": "Проценето времетраење", + "actual_duration": "Реално времетраење", + "choose_action": "__Изберете дејство подолу:__", + "tbl_count": "брои", + "tbl_avg": "Просечно", + "tbl_min": "мин", + "tbl_max": "Макс", + "tbl_energy_avg": "Енергија (просечно)", + "tbl_energy_total": "Енергија (вкупно)", + "tbl_consistency": "Се состои.", + "tbl_last_run": "Последно трчање", + "graph_legend_title": "Графикон Легенда", + "graph_legend_body": "Сината лента го претставува забележаниот опсег на минимална и максимална моќност. Линијата ја покажува просечната крива на моќност.", + "recording_preview": "Преглед на снимање", + "trim_start": "Намали го стартот", + "trim_end": "Намали крајот", + "split_preview_title": "Сплит преглед", + "split_preview_found_fmt": "Најдени се {count} сегменти.", + "split_preview_confirm_fmt": "Кликнете Потврди за да го поделите овој циклус на {count} посебни циклуси.", + "merge_preview_title": "Преглед на спојување", + "merge_preview_joining_fmt": "Се придружува на {count} циклуси. Празнините ќе се пополнат со отчитувања од 0W.", + "editor_delete_preview_title": "Преглед на бришење", + "editor_delete_preview_intro": "Избраните циклуси ќе бидат трајно избришани:", + "editor_delete_preview_confirm": "Кликнете Потврди за трајно да ги избришете овие записи за циклуси.", + "no_power_preview": "*Нема достапни податоци за напојување за преглед.*", + "profile_comparison": "Споредба на профили", + "actual_cycle_label": "Овој циклус (вистински)", + "storage_usage_header": "Употреба на складиште", + "storage_file_size": "Големина на датотека", + "storage_cycles": "Циклуси", + "storage_profiles": "Профили", + "storage_debug_traces": "Дебагирање траги", + "overview_suffix": "Преглед", + "phase_preview_for_profile": "Фазен преглед за профилот", + "current_ranges_header": "Тековни опсези", + "assign_phases_help": "Користете ги дејствата подолу за додавање, уредување, бришење или зачувување опсези.", + "profile_summary_header": "Резиме на профилот", + "recent_cycles_header": "Неодамнешни циклуси", + "trim_cycle_preview_title": "Преглед на циклус на трим", + "trim_cycle_preview_no_data": "Нема достапни податоци за напојувањето за овој циклус.", + "trim_cycle_preview_kept_suffix": "се чуваат", + "table_program": "Програма", + "table_when": "Кога", + "table_length": "Траење", + "table_match": "Совпаѓање", + "table_profile": "Профил", + "table_cycles": "Циклуси", + "table_avg_length": "Прос. траење", + "table_last_run": "Последно трчање", + "table_avg_energy": "Прос. енергија", + "table_detected_program": "Откриена програма", + "table_confidence": "Доверба", + "table_reported": "Пријавено", + "phase_builtin_suffix": "(Вградено)", + "phase_none_available": "Нема достапни фази.", + "settings_deprecation_warning": "⚠️ **Застарен тип на уред:** {device_type} е закажано за отстранување во идното издание. Соодветниот цевковод на WashData не дава сигурни резултати за оваа класа на апарати. Вашата интеграција продолжува да работи во текот на периодот на застарување; за да го исклучите ова предупредување, префрлете го **Тип на уред** подолу на еден од поддржаните типови (Машина за перење, машина за сушење, комбо машина за перење-сушач, машина за миење садови, фризер со воздух, производител на леб или пумпа) или на **Друго (Напредно)** ако вашиот уред не одговара на ниту еден од поддржаните типови. **Другите (напредни)** испраќаат намерно генерички стандардни вредности кои не се прилагодени за кој било специфичен апарат, така што ќе треба сами да ги конфигурирате праговите, временските рокови и соодветните параметри; сите ваши постоечки поставки се зачувани кога ќе се префрлите.", + "phase_other_device_types": "Други типови уреди:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Поделете циклус (најдете празнини)", + "merge": "Циклуси на спојување (спојување на фрагменти)", + "delete": "Избриши циклус(и)" + } + }, + "split_mode": { + "options": { + "auto": "Автоматски откриј неактивни празнини", + "manual": "Рачни временски ознаки" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Одржување: Повторна обработка и оптимизирање на податоците", + "clear_debug_data": "Исчистете ги податоците за отстранување грешки (ослободете простор)", + "wipe_history": "Избришете ги СИТЕ податоци за овој уред (неповратно)", + "export_import": "Извезете/Увезете JSON со поставки (копирајте/залепете)" + } + }, + "export_import_mode": { + "options": { + "export": "Извези ги сите податоци", + "import": "Увези/Спојувај податоци" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Автоматско обележување на старите циклуси", + "select_cycle_to_label": "Специфичен циклус на етикетата", + "select_cycle_to_delete": "Избриши циклус", + "interactive_editor": "Спој/Сплит интерактивен уредник", + "trim_cycle": "Податоци за скратување на циклусот" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Креирај нов профил", + "edit_profile": "Уреди/Преименувајте го профилот", + "delete_profile": "Избришете го профилот", + "profile_stats": "Статистика на профилот", + "cleanup_profile": "Исчистете ја историјата - графикон и избришете", + "assign_phases": "Доделете опсези на фази" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Креирај нова фаза", + "edit_custom_phase": "Фаза на уредување", + "delete_custom_phase": "Избриши фаза" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Додадете опсег на фази", + "edit_range": "Уреди опсег на фази", + "delete_range": "Избришете опсег на фази", + "clear_ranges": "Исчистете ги сите опсези", + "auto_detect_ranges": "Фази за автоматско откривање", + "save_ranges": "Зачувај и Врати" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Освежи статус", + "stop_recording": "Стоп за снимање (Зачувај и обработувај)", + "start_recording": "Започнете ново снимање", + "process_recording": "Обработете го последното снимање (Скратете и зачувајте)", + "discard_recording": "Отфрли ја последната снимка" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Креирај нов профил", + "existing_profile": "Додај во постоечкиот профил", + "discard": "Отфрли го снимањето" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Потврди - Правилно откривање", + "correct": "Точно - Изберете Програма/Времетраење", + "ignore": "Игнорирај - Лажно позитивен/бучен циклус", + "delete": "Избриши - Отстрани од историјата" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Именувајте и применувајте фази", + "cancel": "Вратете се назад без промени" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Поставете почетна точка", + "set_end": "Поставете крајна точка", + "reset": "Ресетирање на целосно времетраење", + "apply": "Примени скратување", + "cancel": "Откажи" + } + }, + "device_type": { + "options": { + "washing_machine": "Машина за перење", + "dryer": "Фен за сушење", + "washer_dryer": "Комбинација на машина за перење и сушење", + "dishwasher": "Машина за миење садови", + "coffee_machine": "Кафе машина (застарена)", + "ev": "Електрично возило (застарено)", + "air_fryer": "Воздушен фризер", + "heat_pump": "Топлинска пумпа (застарена)", + "bread_maker": "Креатор на леб", + "pump": "Пумпа / Спојна пумпа", + "oven": "Рерна (застарена)", + "other": "Друго (Напредно)" + } + } + }, + "services": { + "label_cycle": { + "name": "Циклус на етикети", + "description": "Доделете постоечки профил на минатиот циклус или отстранете ја етикетата.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење за етикетирање." + }, + "cycle_id": { + "name": "ID на циклус", + "description": "ID на циклусот за означување." + }, + "profile_name": { + "name": "Име на профилот", + "description": "Името на постоечки профил (создајте профили во менито Управување со профили). Оставете празно за да ја отстраните етикетата." + } + } + }, + "create_profile": { + "name": "Креирај профил", + "description": "Создадете нов профил (самостоен или врз основа на референтен циклус).", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење." + }, + "profile_name": { + "name": "Име на профилот", + "description": "Име за новиот профил (на пр. „Heavy Duty“, „Delicates“)." + }, + "reference_cycle_id": { + "name": "ID на референтен циклус", + "description": "Изборен ID на циклус на кој ќе се заснова овој профил." + } + } + }, + "delete_profile": { + "name": "Избришете го профилот", + "description": "Избришете профил и опционално отштиклирајте ги циклусите користејќи го.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење." + }, + "profile_name": { + "name": "Име на профилот", + "description": "Профилот за бришење." + }, + "unlabel_cycles": { + "name": "Циклуси од означување", + "description": "Отстранете ја етикетата на профилот од циклусите што го користат овој профил." + } + } + }, + "auto_label_cycles": { + "name": "Автоматско обележување на старите циклуси", + "description": "Ретроактивно означете ги неозначените циклуси користејќи совпаѓање на профилот.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење." + }, + "confidence_threshold": { + "name": "Праг на доверба", + "description": "Минимална сигурност за совпаѓање (0,50-0,95) за примена на етикети." + } + } + }, + "export_config": { + "name": "Извези конфигурација", + "description": "Извезете ги профилите, циклусите и поставките на овој уред во датотека JSON (по уред).", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење за извоз." + }, + "path": { + "name": "Пат", + "description": "Изборна апсолутна патека на датотеката за запишување (стандардно на /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Увези конфигурација", + "description": "Увезете профили, циклуси и поставки за овој уред од датотека за извоз на JSON (поставките може да бидат препишани).", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење за увоз во." + }, + "path": { + "name": "Пат", + "description": "Апсолутна патека до извозната JSON-датотека за увоз." + } + } + }, + "submit_cycle_feedback": { + "name": "Испратете повратни информации за циклусот", + "description": "Потврдете или поправете програма што е автоматски откриена по завршен циклус. Наведете „idry_id“ (напредно) или „device_id“ (препорачано).", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот WashData (се препорачува наместо entry_id)." + }, + "entry_id": { + "name": "ИД за влез", + "description": "ИД за влез во конфигурација за уредот (алтернатива на device_id)." + }, + "cycle_id": { + "name": "ID на циклус", + "description": "Идентификаторот на циклусот е прикажан во известувањето/дневниците за повратни информации." + }, + "user_confirmed": { + "name": "Потврдете ја откриената програма", + "description": "Поставете точно ако откриената програма е точна." + }, + "corrected_profile": { + "name": "Поправен профил", + "description": "Ако не е потврдено, наведете го точното име на профилот/програмата." + }, + "corrected_duration": { + "name": "Поправено времетраење (секунди)", + "description": "Изборно поправено времетраење во секунди." + }, + "notes": { + "name": "Белешки", + "description": "Изборни белешки за овој циклус." + } + } + }, + "record_start": { + "name": "Започнување на циклусот на снимање", + "description": "Започнете рачно да снимате чист циклус (ја заобиколува целата соодветна логика).", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење за снимање." + } + } + }, + "record_stop": { + "name": "Запирање на циклусот на снимање", + "description": "Престанете со рачно снимање.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот на машината за перење да престане да снима." + } + } + }, + "trim_cycle": { + "name": "Циклус на скратување", + "description": "Скратете ги податоците за напојувањето од минатиот циклус на одреден временски прозорец.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот WashData." + }, + "cycle_id": { + "name": "ID на циклус", + "description": "ИД на циклусот за отсекување." + }, + "trim_start_s": { + "name": "Почеток на скратување (секунди)", + "description": "Чувајте ги податоците од ова поместување во секунди. Стандардно 0." + }, + "trim_end_s": { + "name": "Намали крајот (секунди)", + "description": "Чувајте ги податоците до ова поместување за неколку секунди. Стандардно = целосно времетраење." + } + } + }, + "pause_cycle": { + "name": "Циклус на пауза", + "description": "Паузирајте го активниот циклус за уредот WashData.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот WashData да се паузира." + } + } + }, + "resume_cycle": { + "name": "Циклус за продолжување", + "description": "Продолжете со паузиран циклус за уред WashData.", + "fields": { + "device_id": { + "name": "Уред", + "description": "Уредот WashData да продолжи." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Трчање" + }, + "match_ambiguity": { + "name": "Нејасност на совпаѓање" + } + }, + "sensor": { + "washer_state": { + "name": "држава", + "state": { + "off": "Исклучено", + "idle": "Неактивен", + "starting": "Почнувајќи", + "running": "Трчање", + "paused": "Паузирано", + "user_paused": "Паузирано од корисникот", + "ending": "Завршува", + "finished": "Завршено", + "anti_wrinkle": "Против брчки", + "delay_wait": "Се чека да започне", + "interrupted": "Прекинато", + "force_stopped": "Сила запре", + "rinse": "Исплакнете", + "unknown": "Непознат", + "clean": "Чиста" + } + }, + "washer_program": { + "name": "Програма" + }, + "time_remaining": { + "name": "Преостанато време" + }, + "total_duration": { + "name": "Вкупно времетраење" + }, + "cycle_progress": { + "name": "Напредок" + }, + "current_power": { + "name": "Тековна моќност" + }, + "elapsed_time": { + "name": "Поминато време" + }, + "debug_info": { + "name": "Информации за отстранување грешки" + }, + "match_confidence": { + "name": "Натпревар на доверба" + }, + "top_candidates": { + "name": "Топ кандидати", + "state": { + "none": "Никој" + } + }, + "current_phase": { + "name": "Тековна фаза" + }, + "wash_phase": { + "name": "Фаза" + }, + "profile_cycle_count": { + "name": "Број на профил {profile_name}", + "unit_of_measurement": "циклуси" + }, + "suggestions": { + "name": "Предложени поставки" + }, + "pump_runs_today": { + "name": "Работи на пумпата (последни 24 часа)" + }, + "cycle_count": { + "name": "Број на циклуси" + } + }, + "select": { + "program_select": { + "name": "Програма за циклус", + "state": { + "auto_detect": "Автоматско откривање" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Присилно завршување на циклусот" + }, + "pause_cycle": { + "name": "Циклус на пауза" + }, + "resume_cycle": { + "name": "Циклус за продолжување" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Потребен е валиден уред_id." + }, + "cycle_id_required": { + "message": "Потребен е валиден cycle_id." + }, + "profile_name_required": { + "message": "Потребно е важечко име на профил." + }, + "device_not_found": { + "message": "Уредот не е пронајден." + }, + "no_config_entry": { + "message": "Не е пронајден запис за конфигурација за уредот." + }, + "integration_not_loaded": { + "message": "Интеграцијата не е вчитана за овој уред." + }, + "cycle_not_found_or_no_power": { + "message": "Циклусот не е пронајден или нема податоци за напојување." + }, + "trim_failed_empty_window": { + "message": "Скратувањето не успеа - циклусот не е пронајден, нема податоци за напојување или прозорецот што произлегува е празен." + }, + "trim_invalid_range": { + "message": "trim_end_s мора да биде поголем од trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ml.json b/custom_components/ha_washdata/translations/ml.json new file mode 100644 index 0000000..4a89ee1 --- /dev/null +++ b/custom_components/ha_washdata/translations/ml.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData സജ്ജീകരണം", + "description": "നിങ്ങളുടെ വാഷിംഗ് മെഷീനോ മറ്റ് ഉപകരണമോ കോൺഫിഗർ ചെയ്യുക.\n\nപവർ സെൻസർ ആവശ്യമാണ്.\n\n**അടുത്തതായി, നിങ്ങളുടെ ആദ്യ പ്രൊഫൈൽ സൃഷ്‌ടിക്കണോ എന്ന് നിങ്ങളോട് ചോദിക്കും.**", + "data": { + "name": "ഉപകരണത്തിൻ്റെ പേര്", + "device_type": "ഉപകരണ തരം", + "power_sensor": "പവർ സെൻസർ", + "min_power": "മിനിമം പവർ ത്രെഷോൾഡ് (W)" + }, + "data_description": { + "name": "ഈ ഉപകരണത്തിന് ഒരു സൗഹൃദ നാമം (ഉദാ. 'വാഷിംഗ് മെഷീൻ', 'ഡിഷ്വാഷർ').", + "device_type": "ഇത് ഏത് തരത്തിലുള്ള ഉപകരണമാണ്? തയ്യൽക്കാരനെ കണ്ടെത്താനും ലേബൽ ചെയ്യാനും സഹായിക്കുന്നു.", + "power_sensor": "നിങ്ങളുടെ സ്മാർട്ട് പ്ലഗിൽ നിന്ന് തത്സമയ വൈദ്യുതി ഉപഭോഗം (വാട്ടിൽ) റിപ്പോർട്ട് ചെയ്യുന്ന സെൻസർ എൻ്റിറ്റി.", + "min_power": "ഈ പരിധിക്ക് മുകളിലുള്ള പവർ റീഡിംഗുകൾ (വാട്ടിൽ) ഉപകരണം പ്രവർത്തിക്കുന്നുവെന്ന് സൂചിപ്പിക്കുന്നു. മിക്ക ഉപകരണങ്ങൾക്കും 2W ഉപയോഗിച്ച് ആരംഭിക്കുക." + } + }, + "first_profile": { + "title": "ആദ്യ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "description": "ഒരു **സൈക്കിൾ** എന്നത് നിങ്ങളുടെ ഉപകരണത്തിൻ്റെ പൂർണ്ണമായ ഓട്ടമാണ് (ഉദാ. ഒരു ലോഡ് അലക്ക്). ഒരു **പ്രൊഫൈൽ** ഈ സൈക്കിളുകളെ തരം അനുസരിച്ച് ഗ്രൂപ്പുചെയ്യുന്നു (ഉദാ. 'കോട്ടൺ', 'ക്വിക്ക് വാഷ്'). ഇപ്പോൾ ഒരു പ്രൊഫൈൽ സൃഷ്‌ടിക്കുന്നത് സംയോജന എസ്റ്റിമേറ്റ് ദൈർഘ്യത്തെ ഉടനടി സഹായിക്കുന്നു.\n\nഈ പ്രൊഫൈൽ സ്വയമേവ കണ്ടെത്തിയില്ലെങ്കിൽ, ഉപകരണ നിയന്ത്രണങ്ങളിൽ നിങ്ങൾക്ക് സ്വയം തിരഞ്ഞെടുക്കാനാകും.\n\n**ഈ ഘട്ടം ഒഴിവാക്കാൻ 'പ്രൊഫൈൽ നെയിം' ശൂന്യമായി വിടുക.**", + "data": { + "profile_name": "പ്രൊഫൈൽ പേര് (ഓപ്ഷണൽ)", + "manual_duration": "കണക്കാക്കിയ ദൈർഘ്യം (മിനിറ്റുകൾ)" + } + } + }, + "error": { + "cannot_connect": "ബന്ധിപ്പിക്കുന്നതിൽ പരാജയപ്പെട്ടു", + "invalid_auth": "അസാധുവായ പ്രാമാണീകരണം", + "unknown": "അപ്രതീക്ഷിത പിശക്" + }, + "abort": { + "already_configured": "ഉപകരണം ഇതിനകം കോൺഫിഗർ ചെയ്തിട്ടുണ്ട്" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "പഠന പ്രതികരണങ്ങൾ അവലോകനം ചെയ്യുക", + "settings": "ക്രമീകരണങ്ങൾ", + "notifications": "അറിയിപ്പുകൾ", + "manage_cycles": "സൈക്കിളുകൾ നിയന്ത്രിക്കുക", + "manage_profiles": "പ്രൊഫൈലുകൾ നിയന്ത്രിക്കുക", + "manage_phase_catalog": "ഘട്ടം കാറ്റലോഗ് കൈകാര്യം ചെയ്യുക", + "record_cycle": "റെക്കോർഡ് സൈക്കിൾ (മാനുവൽ)", + "diagnostics": "ഡയഗ്നോസ്റ്റിക്സ് & മെയിൻ്റനൻസ്" + } + }, + "apply_suggestions": { + "title": "നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പകർത്തുക", + "description": "ഇത് നിങ്ങളുടെ സംരക്ഷിച്ച ക്രമീകരണങ്ങളിലേക്ക് നിലവിലെ നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പകർത്തും. ഇതൊരു ഒറ്റത്തവണ പ്രവർത്തനമാണ് കൂടാതെ സ്വയമേവ പുനരാലേഖനം പ്രാപ്തമാക്കുന്നില്ല.\n\nപകർത്താൻ നിർദ്ദേശിച്ച മൂല്യങ്ങൾ:\n{suggested}", + "data": { + "confirm": "പകർപ്പ് പ്രവർത്തനം സ്ഥിരീകരിക്കുക" + } + }, + "apply_suggestions_confirm": { + "title": "നിർദ്ദേശിച്ച മാറ്റങ്ങൾ അവലോകനം ചെയ്യുക", + "description": "WashData കണ്ടെത്തി **{pending_count}** പ്രയോഗിക്കാൻ നിർദ്ദേശിച്ച മൂല്യം(ങ്ങൾ).\n\nമാറ്റങ്ങൾ:\n{changes}\n\n✅ ഈ മാറ്റങ്ങൾ ഉടനടി പ്രയോഗിക്കാനും സംരക്ഷിക്കാനും ചുവടെയുള്ള ബോക്‌സ് പരിശോധിച്ച് **സമർപ്പിക്കുക** ക്ലിക്ക് ചെയ്യുക.\n❌ ഇത് അൺചെക്ക് ചെയ്യാതെ വിട്ട് റദ്ദാക്കി തിരികെ പോകുന്നതിന് **സമർപ്പിക്കുക** ക്ലിക്ക് ചെയ്യുക.", + "data": { + "confirm_apply_suggestions": "ഈ മാറ്റങ്ങൾ പ്രയോഗിക്കുകയും സംരക്ഷിക്കുകയും ചെയ്യുക" + } + }, + "settings": { + "title": "ക്രമീകരണങ്ങൾ", + "description": "{deprecation_warning}ട്യൂൺ കണ്ടെത്തൽ പരിധികൾ, പഠനം, വിപുലമായ പെരുമാറ്റം. മിക്ക സജ്ജീകരണങ്ങൾക്കും ഡിഫോൾട്ടുകൾ നന്നായി പ്രവർത്തിക്കുന്നു.\n\nനിലവിൽ ലഭ്യമായ നിർദ്ദേശിച്ച ക്രമീകരണങ്ങൾ: {suggestions_count}.\nഇത് 0-ൽ കൂടുതലാണെങ്കിൽ, വിപുലമായ ക്രമീകരണങ്ങൾ തുറന്ന് ശുപാർശകൾ അവലോകനം ചെയ്യാൻ 'നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പ്രയോഗിക്കുക' ഉപയോഗിക്കുക.", + "data": { + "apply_suggestions": "നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പ്രയോഗിക്കുക", + "device_type": "ഉപകരണ തരം", + "power_sensor": "പവർ സെൻസർ എൻ്റിറ്റി", + "min_power": "മിനിമം പവർ (W)", + "off_delay": "സൈക്കിൾ അവസാനിക്കുന്ന കാലതാമസം (കൾ)", + "notify_actions": "അറിയിപ്പ് പ്രവർത്തനങ്ങൾ", + "notify_people": "ഈ ആളുകൾ വീട്ടിൽ എത്തുന്നതുവരെ ഡെലിവറി വൈകുക", + "notify_only_when_home": "തിരഞ്ഞെടുത്ത വ്യക്തി വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പുകൾ വൈകുക", + "notify_fire_events": "ഫയർ ഓട്ടോമേഷൻ ഇവൻ്റുകൾ", + "notify_start_services": "സൈക്കിൾ ആരംഭം - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_finish_services": "സൈക്കിൾ ഫിനിഷ് - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_live_services": "തത്സമയ പുരോഗതി - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_before_end_minutes": "പൂർത്തീകരണത്തിന് മുമ്പുള്ള അറിയിപ്പ് (അവസാനത്തിന് മിനിറ്റ് മുമ്പ്)", + "notify_title": "അറിയിപ്പ് ശീർഷകം", + "notify_icon": "അറിയിപ്പ് ഐക്കൺ", + "notify_start_message": "സന്ദേശ ഫോർമാറ്റ് ആരംഭിക്കുക", + "notify_finish_message": "സന്ദേശ ഫോർമാറ്റ് പൂർത്തിയാക്കുക", + "notify_pre_complete_message": "പൂർത്തീകരണത്തിന് മുമ്പുള്ള സന്ദേശ ഫോർമാറ്റ്", + "show_advanced": "വിപുലമായ ക്രമീകരണങ്ങൾ എഡിറ്റ് ചെയ്യുക (അടുത്ത ഘട്ടം)" + }, + "data_description": { + "apply_suggestions": "പഠന അൽഗോരിതം നിർദ്ദേശിച്ച മൂല്യങ്ങൾ ഫോമിലേക്ക് പകർത്താൻ ഈ ബോക്സ് പരിശോധിക്കുക. സംരക്ഷിക്കുന്നതിന് മുമ്പ് അവലോകനം ചെയ്യുക.\n\nനിർദ്ദേശിച്ചത് (പഠനത്തിൽ നിന്ന്):\n- മിനി_പവർ: {suggested_min_power} W\n- ഓഫ്_ഡെലേ: {suggested_off_delay} സെ\n- watchdog_interval: {suggested_watchdog_interval} സെ\n- no_update_active_timeout: {suggested_no_update_active_timeout} സെ\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} സെ\n{suggested_reason}", + "device_type": "ഉപകരണത്തിൻ്റെ തരം തിരഞ്ഞെടുക്കുക (വാഷിംഗ് മെഷീൻ, ഡ്രയർ, ഡിഷ്വാഷർ, കോഫി മെഷീൻ, ഇവി). മികച്ച ഓർഗനൈസേഷനായി ഈ ടാഗ് ഓരോ സൈക്കിളിലും സംഭരിച്ചിരിക്കുന്നു.", + "power_sensor": "സെൻസർ എൻ്റിറ്റി തത്സമയ പവർ റിപ്പോർട്ട് ചെയ്യുന്നു (വാട്ടിൽ). ചരിത്രപരമായ ഡാറ്റ നഷ്‌ടപ്പെടാതെ നിങ്ങൾക്ക് ഇത് എപ്പോൾ വേണമെങ്കിലും മാറ്റാനാകും-നിങ്ങൾ ഒരു സ്‌മാർട്ട് പ്ലഗ് മാറ്റിസ്ഥാപിച്ചാൽ ഉപയോഗപ്രദമാണ്.", + "min_power": "ഈ പരിധിക്ക് മുകളിലുള്ള പവർ റീഡിംഗുകൾ (വാട്ടിൽ) ഉപകരണം പ്രവർത്തിക്കുന്നുവെന്ന് സൂചിപ്പിക്കുന്നു. മിക്ക ഉപകരണങ്ങൾക്കും 2W ഉപയോഗിച്ച് ആരംഭിക്കുക.", + "off_delay": "സെക്കൻഡുകൾ പൂർത്തിയാക്കിയതായി അടയാളപ്പെടുത്തുന്നതിന് മിനുസപ്പെടുത്തിയ പവർ സ്റ്റോപ്പ് ത്രെഷോൾഡിന് താഴെയായിരിക്കണം. സ്ഥിരസ്ഥിതി: 120സെ.", + "notify_actions": "അറിയിപ്പുകൾക്കായി റൺ ചെയ്യാനുള്ള ഓപ്ഷണൽ ഹോം അസിസ്റ്റൻ്റ് ആക്ഷൻ സീക്വൻസ് (സ്ക്രിപ്റ്റുകൾ, അറിയിപ്പ്, ടിടിഎസ് മുതലായവ). സജ്ജീകരിച്ചാൽ, ഫാൾബാക്ക് അറിയിപ്പ് സേവനത്തിന് മുമ്പായി പ്രവർത്തനങ്ങൾ നടക്കുന്നു.", + "notify_people": "സാന്നിധ്യം ഗേറ്റിംഗിനായി ഉപയോഗിക്കുന്ന ആളുകളുടെ സ്ഥാപനങ്ങൾ. പ്രവർത്തനക്ഷമമാക്കിയാൽ, തിരഞ്ഞെടുത്ത ഒരാളെങ്കിലും വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പ് ഡെലിവറി വൈകും.", + "notify_only_when_home": "പ്രവർത്തനക്ഷമമാക്കിയാൽ, തിരഞ്ഞെടുത്ത ഒരാളെങ്കിലും വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പുകൾ വൈകുക.", + "notify_fire_events": "പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ, ഓട്ടോമേഷനുകൾ പ്രവർത്തിപ്പിക്കുന്നതിന് സൈക്കിൾ ആരംഭത്തിനും അവസാനത്തിനും (ha_washdata_cycle_started, ha_washdata_cycle_ended) ഹോം അസിസ്റ്റൻ്റ് ഇവൻ്റുകൾ ഫയർ ചെയ്യുക.", + "notify_start_services": "സൈക്കിൾ ആരംഭിക്കുമ്പോൾ മുന്നറിയിപ്പ് നൽകാൻ ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക. ആരംഭ അറിയിപ്പുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_finish_services": "സൈക്കിൾ പൂർത്തിയാകുമ്പോഴോ പൂർത്തിയാകുമ്പോഴോ മുന്നറിയിപ്പ് നൽകാൻ ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക. ഫിനിഷ് അറിയിപ്പുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_live_services": "സൈക്കിൾ പ്രവർത്തിക്കുമ്പോൾ തത്സമയ പുരോഗതി അപ്‌ഡേറ്റുകൾ ലഭിക്കുന്നതിന് ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക (മൊബൈൽ കമ്പാനിയൻ ആപ്പ് മാത്രം). തത്സമയ അപ്‌ഡേറ്റുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_before_end_minutes": "ഒരു അറിയിപ്പ് പ്രവർത്തനക്ഷമമാക്കാൻ സൈക്കിൾ കണക്കാക്കിയ അവസാനത്തിന് മിനിറ്റുകൾക്ക് മുമ്പ്. പ്രവർത്തനരഹിതമാക്കാൻ 0 ആയി സജ്ജമാക്കുക. സ്ഥിരസ്ഥിതി: 0.", + "notify_title": "അറിയിപ്പുകൾക്കുള്ള ഇഷ്‌ടാനുസൃത ശീർഷകം. {device} പ്ലെയ്‌സ്‌ഹോൾഡറിനെ പിന്തുണയ്ക്കുന്നു.", + "notify_icon": "അറിയിപ്പുകൾക്കായി ഉപയോഗിക്കേണ്ട ഐക്കൺ (ഉദാ. mdi:washing-machine). ഐക്കൺ ഇല്ലാതെ അയയ്ക്കാൻ ശൂന്യമായി വിടുക.", + "notify_start_message": "ഒരു സൈക്കിൾ ആരംഭിക്കുമ്പോൾ സന്ദേശം അയച്ചു. {device} പ്ലെയ്‌സ്‌ഹോൾഡറിനെ പിന്തുണയ്ക്കുന്നു.", + "notify_finish_message": "ഒരു സൈക്കിൾ പൂർത്തിയാകുമ്പോൾ സന്ദേശം അയച്ചു. {device}, {duration}, {program}, {energy_kwh}, {cost} പ്ലെയ്‌സ്‌ഹോൾഡറുകൾ പിന്തുണയ്ക്കുന്നു.", + "notify_pre_complete_message": "സൈക്കിൾ പ്രവർത്തിക്കുമ്പോൾ ആവർത്തിച്ചുള്ള തത്സമയ പുരോഗതി അപ്‌ഡേറ്റുകളുടെ വാചകം. {device}, {minutes}, {program} പ്ലെയ്‌സ്‌ഹോൾഡറുകൾ പിന്തുണയ്ക്കുന്നു." + } + }, + "notifications": { + "title": "അറിയിപ്പുകൾ", + "description": "അറിയിപ്പ് ചാനലുകൾ, ടെംപ്ലേറ്റുകൾ, ഓപ്ഷണൽ തത്സമയ മൊബൈൽ പുരോഗതി അപ്ഡേറ്റുകൾ എന്നിവ കോൺഫിഗർ ചെയ്യുക.", + "data": { + "notify_actions": "അറിയിപ്പ് പ്രവർത്തനങ്ങൾ", + "notify_people": "ഈ ആളുകൾ വീട്ടിൽ എത്തുന്നതുവരെ ഡെലിവറി വൈകുക", + "notify_only_when_home": "തിരഞ്ഞെടുത്ത വ്യക്തി വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പുകൾ വൈകുക", + "notify_fire_events": "ഫയർ ഓട്ടോമേഷൻ ഇവൻ്റുകൾ", + "notify_start_services": "സൈക്കിൾ ആരംഭം - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_finish_services": "സൈക്കിൾ ഫിനിഷ് - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_live_services": "തത്സമയ പുരോഗതി - അറിയിപ്പ് ലക്ഷ്യങ്ങൾ", + "notify_before_end_minutes": "പൂർത്തീകരണത്തിന് മുമ്പുള്ള അറിയിപ്പ് (അവസാനത്തിന് മിനിറ്റ് മുമ്പ്)", + "notify_live_interval_seconds": "തത്സമയ അപ്‌ഡേറ്റ് ഇടവേള (സെക്കൻഡ്)", + "notify_live_overrun_percent": "തത്സമയ അപ്‌ഡേറ്റ് ഓവർറൺ അലവൻസ് (%)", + "notify_live_chronometer": "ക്രോണോമീറ്റർ കൗണ്ട്ഡൗൺ ടൈമർ", + "notify_title": "അറിയിപ്പ് ശീർഷകം", + "notify_icon": "അറിയിപ്പ് ഐക്കൺ", + "notify_start_message": "സന്ദേശ ഫോർമാറ്റ് ആരംഭിക്കുക", + "notify_finish_message": "സന്ദേശ ഫോർമാറ്റ് പൂർത്തിയാക്കുക", + "notify_pre_complete_message": "പൂർത്തീകരണത്തിന് മുമ്പുള്ള സന്ദേശ ഫോർമാറ്റ്", + "energy_price_entity": "ഊർജ്ജ വില - എൻ്റിറ്റി (ഓപ്ഷണൽ)", + "energy_price_static": "ഊർജ്ജ വില - സ്റ്റാറ്റിക് മൂല്യം (ഓപ്ഷണൽ)", + "go_back": "സംരക്ഷിക്കാതെ മടങ്ങുക", + "notify_reminder_message": "ഓർമ്മപ്പെടുത്തൽ സന്ദേശ ഫോർമാറ്റ്", + "notify_timeout_seconds": "ശേഷം (സെക്കൻഡ്) സ്വയമേവ ഡിസ്മിസ് ചെയ്യുക", + "notify_channel": "അറിയിപ്പ് ചാനൽ (നില/തത്സമയ/ഓർമ്മപ്പെടുത്തൽ)", + "notify_finish_channel": "അറിയിപ്പ് ചാനൽ പൂർത്തിയായി" + }, + "data_description": { + "go_back": "മുകളിലുള്ള മാറ്റങ്ങൾ ഉപേക്ഷിച്ച് പ്രധാന മെനുവിലേക്ക് മടങ്ങാൻ ഇത് ടിക്ക് ചെയ്ത് സമർപ്പിക്കുക ക്ലിക്ക് ചെയ്യുക.", + "notify_actions": "അറിയിപ്പുകൾക്കായി റൺ ചെയ്യാനുള്ള ഓപ്ഷണൽ ഹോം അസിസ്റ്റൻ്റ് ആക്ഷൻ സീക്വൻസ് (സ്ക്രിപ്റ്റുകൾ, അറിയിപ്പ്, ടിടിഎസ് മുതലായവ). സജ്ജീകരിച്ചാൽ, ഫാൾബാക്ക് അറിയിപ്പ് സേവനത്തിന് മുമ്പായി പ്രവർത്തനങ്ങൾ നടക്കുന്നു.", + "notify_people": "സാന്നിധ്യം ഗേറ്റിംഗിനായി ഉപയോഗിക്കുന്ന ആളുകളുടെ സ്ഥാപനങ്ങൾ. പ്രവർത്തനക്ഷമമാക്കിയാൽ, തിരഞ്ഞെടുത്ത ഒരാളെങ്കിലും വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പ് ഡെലിവറി വൈകും.", + "notify_only_when_home": "പ്രവർത്തനക്ഷമമാക്കിയാൽ, തിരഞ്ഞെടുത്ത ഒരാളെങ്കിലും വീട്ടിൽ എത്തുന്നതുവരെ അറിയിപ്പുകൾ വൈകുക.", + "notify_fire_events": "പ്രവർത്തനക്ഷമമാക്കിയിട്ടുണ്ടെങ്കിൽ, ഓട്ടോമേഷനുകൾ പ്രവർത്തിപ്പിക്കുന്നതിന് സൈക്കിൾ ആരംഭത്തിനും അവസാനത്തിനും (ha_washdata_cycle_started, ha_washdata_cycle_ended) ഹോം അസിസ്റ്റൻ്റ് ഇവൻ്റുകൾ ഫയർ ചെയ്യുക.", + "notify_start_services": "സൈക്കിൾ ആരംഭിക്കുമ്പോൾ മുന്നറിയിപ്പ് നൽകാൻ ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക (ഉദാ. notify.mobile_app_my_phone). ആരംഭ അറിയിപ്പുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_finish_services": "സൈക്കിൾ പൂർത്തിയാകുമ്പോഴോ പൂർത്തിയാകുമ്പോഴോ മുന്നറിയിപ്പ് നൽകാൻ ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക. ഫിനിഷ് അറിയിപ്പുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_live_services": "സൈക്കിൾ പ്രവർത്തിക്കുമ്പോൾ തത്സമയ പുരോഗതി അപ്‌ഡേറ്റുകൾ ലഭിക്കുന്നതിന് ഒന്നോ അതിലധികമോ സേവനങ്ങളെ അറിയിക്കുക (മൊബൈൽ കമ്പാനിയൻ ആപ്പ് മാത്രം). തത്സമയ അപ്‌ഡേറ്റുകൾ ഒഴിവാക്കാൻ ശൂന്യമായി വിടുക.", + "notify_before_end_minutes": "ഒരു അറിയിപ്പ് പ്രവർത്തനക്ഷമമാക്കാൻ സൈക്കിൾ കണക്കാക്കിയ അവസാനത്തിന് മിനിറ്റുകൾക്ക് മുമ്പ്. പ്രവർത്തനരഹിതമാക്കാൻ 0 ആയി സജ്ജമാക്കുക. സ്ഥിരസ്ഥിതി: 0.", + "notify_live_interval_seconds": "പ്രവർത്തിപ്പിക്കുമ്പോൾ എത്ര തവണ പുരോഗതി അപ്‌ഡേറ്റുകൾ അയയ്ക്കുന്നു. താഴ്ന്ന മൂല്യങ്ങൾ കൂടുതൽ പ്രതികരിക്കുന്നതാണ്, പക്ഷേ കൂടുതൽ അറിയിപ്പുകൾ ഉപയോഗിക്കുന്നു. കുറഞ്ഞത് 30 സെക്കൻഡ് നിർബന്ധമാക്കിയിരിക്കുന്നു - 30 സെക്കൻഡിൽ താഴെയുള്ള മൂല്യങ്ങൾ സ്വയമേവ 30 സെക്കൻഡ് വരെ റൗണ്ട് ചെയ്യപ്പെടും.", + "notify_live_overrun_percent": "ദൈർഘ്യമേറിയ/ഓവർറൺ ചെയ്യുന്ന സൈക്കിളുകൾക്കായി കണക്കാക്കിയ അപ്‌ഡേറ്റ് എണ്ണത്തിന് മുകളിലുള്ള സുരക്ഷാ മാർജിൻ (ഉദാഹരണത്തിന് 20% 1.2x കണക്കാക്കിയ അപ്‌ഡേറ്റുകൾ അനുവദിക്കുന്നു).", + "notify_live_chronometer": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഓരോ തത്സമയ അപ്‌ഡേറ്റും കണക്കാക്കിയ ഫിനിഷ് സമയത്തിലേക്കുള്ള ഒരു ക്രോണോമീറ്റർ കൗണ്ട്ഡൗൺ ഉൾപ്പെടുന്നു. അപ്‌ഡേറ്റുകൾക്കിടയിൽ ഉപകരണത്തിൽ അറിയിപ്പ് ടൈമർ സ്വയമേവ കുറയുന്നു (Android കമ്പാനിയൻ ആപ്പ് മാത്രം).", + "notify_title": "അറിയിപ്പുകൾക്കുള്ള ഇഷ്‌ടാനുസൃത ശീർഷകം. {device} പ്ലെയ്‌സ്‌ഹോൾഡറിനെ പിന്തുണയ്ക്കുന്നു.", + "notify_icon": "അറിയിപ്പുകൾക്കായി ഉപയോഗിക്കേണ്ട ഐക്കൺ (ഉദാ. mdi:washing-machine). ഐക്കൺ ഇല്ലാതെ അയയ്ക്കാൻ ശൂന്യമായി വിടുക.", + "notify_start_message": "ഒരു സൈക്കിൾ ആരംഭിക്കുമ്പോൾ സന്ദേശം അയച്ചു. {device} പ്ലെയ്‌സ്‌ഹോൾഡറിനെ പിന്തുണയ്ക്കുന്നു.", + "notify_finish_message": "ഒരു സൈക്കിൾ പൂർത്തിയാകുമ്പോൾ സന്ദേശം അയച്ചു. {device}, {duration}, {program}, {energy_kwh}, {cost} പ്ലെയ്‌സ്‌ഹോൾഡറുകൾ പിന്തുണയ്ക്കുന്നു.", + "energy_price_entity": "നിലവിലെ വൈദ്യുതി വില (ഉദാ. sensor.electricity_price, input_number.energy_rate) കൈവശമുള്ള ഒരു സംഖ്യാ എൻ്റിറ്റി തിരഞ്ഞെടുക്കുക. സജ്ജമാക്കുമ്പോൾ, {cost} പ്ലെയ്‌സ്‌ഹോൾഡർ പ്രവർത്തനക്ഷമമാക്കുന്നു. രണ്ടും ക്രമീകരിച്ചിട്ടുണ്ടെങ്കിൽ സ്റ്റാറ്റിക് മൂല്യത്തേക്കാൾ മുൻഗണന നൽകുന്നു.", + "energy_price_static": "ഒരു kWh-ന് നിശ്ചിത വൈദ്യുതി വില. സജ്ജമാക്കുമ്പോൾ, {cost} പ്ലെയ്‌സ്‌ഹോൾഡർ പ്രവർത്തനക്ഷമമാക്കുന്നു. ഒരു എൻ്റിറ്റി മുകളിൽ കോൺഫിഗർ ചെയ്‌തിട്ടുണ്ടെങ്കിൽ അവഗണിച്ചു. സന്ദേശ ടെംപ്ലേറ്റിൽ നിങ്ങളുടെ കറൻസി ചിഹ്നം ചേർക്കുക, ഉദാ. {cost} €.", + "notify_reminder_message": "ഒറ്റത്തവണ ഓർമ്മപ്പെടുത്തലിൻ്റെ വാചകം, കണക്കാക്കിയ അവസാനത്തിന് മുമ്പ് കോൺഫിഗർ ചെയ്‌ത എണ്ണം അയച്ചു. മുകളിലെ ആവർത്തിച്ചുള്ള തത്സമയ അപ്‌ഡേറ്റുകളിൽ നിന്ന് വ്യത്യസ്തമാണ്. {device}, {minutes}, {program} പ്ലെയ്‌സ്‌ഹോൾഡറുകൾ പിന്തുണയ്ക്കുന്നു.", + "notify_timeout_seconds": "ഇത്രയും സെക്കൻ്റുകൾക്ക് ശേഷം അറിയിപ്പുകൾ സ്വയമേവ നിരസിക്കുക (സഹചാരി ആപ്പ് 'ടൈമൗട്ട്' ആയി ഫോർവേഡ് ചെയ്യുന്നു). പിരിച്ചുവിടുന്നത് വരെ നിലനിർത്താൻ 0 ആയി സജ്ജീകരിക്കുക. സ്ഥിരസ്ഥിതി: 0.", + "notify_channel": "സ്റ്റാറ്റസ്, തത്സമയ പുരോഗതി, ഓർമ്മപ്പെടുത്തൽ അറിയിപ്പുകൾ എന്നിവയ്‌ക്കായുള്ള Android കമ്പാനിയൻ ആപ്പ് അറിയിപ്പ് ചാനൽ. ചാനൽ പേര് ആദ്യമായി ഉപയോഗിക്കുമ്പോൾ ഒരു ചാനലിൻ്റെ ശബ്ദവും പ്രാധാന്യവും കമ്പാനിയൻ ആപ്പിൽ കോൺഫിഗർ ചെയ്യപ്പെടുന്നു - WashData ചാനലിൻ്റെ പേര് മാത്രമേ സജ്ജീകരിക്കൂ. ആപ്പ് ഡിഫോൾട്ടായി ശൂന്യമായി വിടുക.", + "notify_finish_channel": "പൂർത്തിയായ അലേർട്ടിനായി Android ചാനൽ വേർതിരിക്കുക (ഓർമ്മപ്പെടുത്തലും അലക്ക്-കാത്തിരിപ്പ് നാഗും ഉപയോഗിക്കുന്നു) അതുവഴി അതിന് അതിൻ്റേതായ ശബ്ദമുണ്ടാകും. മുകളിലെ ചാനൽ വീണ്ടും ഉപയോഗിക്കുന്നതിന് ശൂന്യമായി വിടുക.", + "notify_pre_complete_message": "സൈക്കിൾ പ്രവർത്തിക്കുമ്പോൾ ആവർത്തിച്ചുള്ള തത്സമയ പുരോഗതി അപ്‌ഡേറ്റുകളുടെ വാചകം. {device}, {minutes}, {program} പ്ലെയ്‌സ്‌ഹോൾഡറുകൾ പിന്തുണയ്ക്കുന്നു." + } + }, + "advanced_settings": { + "title": "വിപുലമായ ക്രമീകരണങ്ങൾ", + "description": "കണ്ടെത്തൽ പരിധികൾ, പഠനം, കൂടാതെ ഉയർന്നതല പെരുമാറ്റം ക്രമീകരിക്കുക. ഡീഫോൾട്ടുകൾ മിക്ക സജ്ജീകരണങ്ങൾക്കും നന്നായി പ്രവർത്തിക്കുന്നു.", + "sections": { + "suggestions_section": { + "name": "നിർദ്ദേശിച്ച ക്രമീകരണങ്ങൾ", + "description": "{suggestions_count} പഠനത്തിൽ നിന്നുള്ള നിർദ്ദേശങ്ങൾ ലഭ്യമാണ്. സംരക്ഷിക്കുന്നതിന് മുമ്പ് നിർദ്ദേശിച്ച മാറ്റങ്ങൾ പരിശോധിക്കാൻ 'നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പ്രയോഗിക്കുക' ടിക്ക് ചെയ്യുക.", + "data": { + "apply_suggestions": "നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പ്രയോഗിക്കുക" + }, + "data_description": { + "apply_suggestions": "പഠന അൽഗോരിതത്തിൽ നിന്ന് നിർദ്ദേശിച്ച മൂല്യങ്ങൾക്കായി ഒരു അവലോകന ഘട്ടം തുറക്കാൻ ഈ ബോക്സ് പരിശോധിക്കുക. യാന്ത്രികമായി ഒന്നും സംരക്ഷിക്കപ്പെടുന്നില്ല.\n\nനിർദ്ദേശിച്ചത് (പഠനത്തിൽ നിന്ന്):\n- മിനി_പവർ: {suggested_min_power} W\n- ഓഫ്_ഡെലേ: {suggested_off_delay} സെ\n- watchdog_interval: {suggested_watchdog_interval} സെ\n- no_update_active_timeout: {suggested_no_update_active_timeout} സെ\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} സെ\n{suggested_reason}" + } + }, + "detection_section": { + "name": "കണ്ടെത്തലും പവർ പരിധികളും", + "description": "ഉപകരണം എപ്പോൾ ON അല്ലെങ്കിൽ OFF ആയി കണക്കാക്കണം, എനർജി ഗേറ്റുകൾ, കൂടാതെ സ്റ്റേറ്റ് ട്രാൻസിഷനുകളുടെ സമയക്രമവും.", + "data": { + "start_duration_threshold": "ഡീബൗൺസ് ദൈർഘ്യം (കൾ) ആരംഭിക്കുക", + "start_energy_threshold": "എനർജി ഗേറ്റ് ആരംഭിക്കുക (Wh)", + "completion_min_seconds": "പൂർത്തിയാക്കൽ കുറഞ്ഞ പ്രവർത്തനസമയം (സെക്കൻഡ്)", + "start_threshold_w": "ആരംഭ പരിധി (W)", + "stop_threshold_w": "സ്റ്റോപ്പ് ത്രെഷോൾഡ് (W)", + "end_energy_threshold": "എൻഡ് എനർജി ഗേറ്റ് (Wh)", + "running_dead_zone": "റണ്ണിംഗ് ഡെഡ് സോൺ (സെക്കൻഡ്)", + "end_repeat_count": "എൻഡ് ആവർത്തന എണ്ണം", + "min_off_gap": "സൈക്കിളുകൾ (കൾ) തമ്മിലുള്ള ഏറ്റവും കുറഞ്ഞ വിടവ്", + "sampling_interval": "സാമ്പിൾ ഇടവേള (കൾ)" + }, + "data_description": { + "start_duration_threshold": "ഈ കാലയളവിനേക്കാൾ (സെക്കൻഡ്) കുറഞ്ഞ പവർ സ്പൈക്കുകൾ അവഗണിക്കുക. യഥാർത്ഥ സൈക്കിൾ ആരംഭിക്കുന്നതിന് മുമ്പ് ബൂട്ട് സ്പൈക്കുകൾ ഫിൽട്ടർ ചെയ്യുന്നു (ഉദാ. 2 സെക്കൻഡിന് 60W). സ്ഥിരസ്ഥിതി: 5സെ.", + "start_energy_threshold": "സ്ഥിരീകരിക്കാൻ START ഘട്ടത്തിൽ സൈക്കിൾ കുറഞ്ഞത് ഇത്രയും ഊർജ്ജം (Wh) ശേഖരിക്കണം. സ്ഥിരസ്ഥിതി: 0.005 Wh.", + "completion_min_seconds": "ഇതിനേക്കാൾ ചെറുതായ സൈക്കിളുകൾ സ്വാഭാവികമായി അവസാനിച്ചാലും 'ഇൻ്ററപ്റ്റഡ്' എന്ന് അടയാളപ്പെടുത്തും. സ്ഥിരസ്ഥിതി: 600സെ.", + "start_threshold_w": "ഒരു ചക്രം ആരംഭിക്കുന്നതിന് ഈ പരിധിക്ക് മുകളിൽ ശക്തി ഉയരണം. നിങ്ങളുടെ സ്റ്റാൻഡ്‌ബൈ പവറിനേക്കാൾ ഉയർന്നത് സജ്ജമാക്കുക. ഡിഫോൾട്ട്: min_power + 1 W.", + "stop_threshold_w": "ഒരു ചക്രം അവസാനിപ്പിക്കാൻ ഈ പരിധിക്ക് താഴെയായി ശക്തി കുറയണം. തെറ്റായ അറ്റങ്ങൾ ട്രിഗർ ചെയ്യുന്ന ശബ്‌ദം ഒഴിവാക്കാൻ പൂജ്യത്തിന് മുകളിൽ സജ്ജീകരിക്കുക. സ്ഥിരസ്ഥിതി: 0.6 * min_power.", + "end_energy_threshold": "അവസാനത്തെ ഓഫ്-ഡിലേ വിൻഡോയിലെ ഊർജ്ജം ഈ മൂല്യത്തിന് (Wh) താഴെയാണെങ്കിൽ മാത്രമേ സൈക്കിൾ അവസാനിക്കൂ. പൂജ്യത്തിനടുത്തായി വൈദ്യുതി ചാഞ്ചാടുകയാണെങ്കിൽ തെറ്റായ അറ്റങ്ങൾ തടയുന്നു. സ്ഥിരസ്ഥിതി: 0.05 Wh.", + "running_dead_zone": "സൈക്കിൾ ആരംഭിച്ചതിന് ശേഷം, പ്രാരംഭ സ്പിൻ-അപ്പ് ഘട്ടത്തിൽ തെറ്റായ എൻഡ് കണ്ടെത്തൽ തടയാൻ ഇത്രയും സെക്കൻഡ് പവർ ഡിപ്പുകൾ അവഗണിക്കുക. പ്രവർത്തനരഹിതമാക്കാൻ 0 ആയി സജ്ജമാക്കുക. സ്ഥിരസ്ഥിതി: 0സെ.", + "end_repeat_count": "സൈക്കിൾ അവസാനിപ്പിക്കുന്നതിന് മുമ്പ് അവസാന അവസ്ഥയുടെ എണ്ണം (ഓഫ്_ഡിലേയ്‌ക്ക് ത്രെഷോൾഡിന് താഴെയുള്ള പവർ) തുടർച്ചയായി പാലിക്കേണ്ടതുണ്ട്. ഉയർന്ന മൂല്യങ്ങൾ താൽക്കാലികമായി നിർത്തുമ്പോൾ തെറ്റായ അറ്റങ്ങൾ തടയുന്നു. സ്ഥിരസ്ഥിതി: 1.", + "min_off_gap": "മുമ്പത്തേത് അവസാനിച്ചതിൻ്റെ ഇത്രയും സെക്കൻഡുകൾക്കുള്ളിൽ ഒരു ചക്രം ആരംഭിക്കുകയാണെങ്കിൽ, അവ ഒരൊറ്റ സൈക്കിളിലേക്ക് ലയിപ്പിക്കും.", + "sampling_interval": "സെക്കൻ്റുകൾക്കുള്ളിൽ ഏറ്റവും കുറഞ്ഞ സാമ്പിൾ ഇടവേള. ഈ നിരക്കിനേക്കാൾ വേഗത്തിലുള്ള അപ്‌ഡേറ്റുകൾ അവഗണിക്കപ്പെടും. ഡിഫോൾട്ട്: 30s (വാഷിംഗ് മെഷീനുകൾ, വാഷർ-ഡ്രയറുകൾ, ഡിഷ്വാഷറുകൾ എന്നിവയ്ക്കുള്ള 2സെ)." + } + }, + "matching_section": { + "name": "പ്രൊഫൈൽ പൊരുത്തപ്പെടുത്തലും പഠനവും", + "description": "പ്രവർത്തിക്കുന്ന സൈക്കിളുകളെ പഠിച്ച പ്രൊഫൈലുകളോട് WashData എത്ര ആക്രമണാത്മകമായി പൊരുത്തപ്പെടുത്തണം, ഫീഡ്ബാക്ക് എപ്പോൾ ചോദിക്കണം എന്നിവ.", + "data": { + "profile_match_min_duration_ratio": "പ്രൊഫൈൽ പൊരുത്തം കുറഞ്ഞ ദൈർഘ്യ അനുപാതം (0.1-1.0)", + "profile_match_interval": "പ്രൊഫൈൽ പൊരുത്ത ഇടവേള (സെക്കൻഡ്)", + "profile_match_threshold": "പ്രൊഫൈൽ മാച്ച് ത്രെഷോൾഡ്", + "profile_unmatch_threshold": "പ്രൊഫൈൽ അൺമാച്ച് ത്രെഷോൾഡ്", + "auto_label_confidence": "യാന്ത്രിക-ലേബൽ ആത്മവിശ്വാസം (0-1)", + "learning_confidence": "ഫീഡ്ബാക്ക് അഭ്യർത്ഥന ആത്മവിശ്വാസം (0-1)", + "suppress_feedback_notifications": "ഫീഡ്ബാക്ക് അറിയിപ്പുകൾ അടിച്ചമർത്തുക", + "duration_tolerance": "ദൈർഘ്യം സഹിഷ്ണുത (0-0.5)", + "smoothing_window": "മിനുസപ്പെടുത്തുന്ന വിൻഡോ (സാമ്പിളുകൾ)", + "profile_duration_tolerance": "പ്രൊഫൈൽ മാച്ച് ഡ്യൂറേഷൻ ടോളറൻസ് (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "പ്രൊഫൈൽ പൊരുത്തപ്പെടുത്തലിനുള്ള ഏറ്റവും കുറഞ്ഞ ദൈർഘ്യ അനുപാതം. റണ്ണിംഗ് സൈക്കിൾ പൊരുത്തപ്പെടുത്തുന്നതിന് പ്രൊഫൈൽ ദൈർഘ്യത്തിൻ്റെ ഈ അംശമെങ്കിലും ആയിരിക്കണം. ലോവർ = നേരത്തെ പൊരുത്തം. സ്ഥിരസ്ഥിതി: 0.50 (50%).", + "profile_match_interval": "ഒരു സൈക്കിളിൽ പ്രൊഫൈൽ പൊരുത്തപ്പെടുത്തൽ എത്ര തവണ (സെക്കൻഡുകൾക്കുള്ളിൽ) ശ്രമിക്കണം. കുറഞ്ഞ മൂല്യങ്ങൾ വേഗത്തിൽ കണ്ടെത്തൽ നൽകുന്നു, എന്നാൽ കൂടുതൽ CPU ഉപയോഗിക്കുന്നു. സ്ഥിരസ്ഥിതി: 300സെ (5 മിനിറ്റ്).", + "profile_match_threshold": "ഒരു പ്രൊഫൈലിനെ പൊരുത്തമായി കണക്കാക്കാൻ ആവശ്യമായ ഏറ്റവും കുറഞ്ഞ DTW സമാനത സ്കോർ (0.0–1.0). ഉയർന്നത് = കർശനമായ പൊരുത്തപ്പെടുത്തൽ, കുറച്ച് തെറ്റായ പോസിറ്റീവുകൾ. സ്ഥിരസ്ഥിതി: 0.4.", + "profile_unmatch_threshold": "DTW സാമ്യത സ്കോർ (0.0–1.0) അതിനു താഴെ മുമ്പ് പൊരുത്തപ്പെട്ട പ്രൊഫൈൽ നിരസിക്കപ്പെട്ടു. ഫ്ലിക്കറിംഗ് തടയാൻ മാച്ച് ത്രെഷോൾഡിനേക്കാൾ കുറവായിരിക്കണം. സ്ഥിരസ്ഥിതി: 0.35.", + "auto_label_confidence": "പൂർത്തിയാകുമ്പോൾ ഈ ആത്മവിശ്വാസത്തിലോ അതിനു മുകളിലോ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക (0.0–1.0).", + "learning_confidence": "ഒരു ഇവൻ്റ് വഴി ഉപയോക്തൃ പരിശോധന അഭ്യർത്ഥിക്കുന്നതിനുള്ള ഏറ്റവും കുറഞ്ഞ ആത്മവിശ്വാസം (0.0–1.0).", + "suppress_feedback_notifications": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, WashData തുടർന്നും ട്രാക്ക് ചെയ്യുകയും ഫീഡ്‌ബാക്ക് ആന്തരികമായി അഭ്യർത്ഥിക്കുകയും ചെയ്യും, എന്നാൽ സൈക്കിളുകൾ സ്ഥിരീകരിക്കാൻ ആവശ്യപ്പെടുന്ന സ്ഥിരമായ അറിയിപ്പുകൾ കാണിക്കില്ല. സൈക്കിളുകൾ എല്ലായ്പ്പോഴും ശരിയായി കണ്ടെത്തുകയും നിർദ്ദേശങ്ങൾ ശ്രദ്ധ തിരിക്കുന്നതായി നിങ്ങൾ കണ്ടെത്തുകയും ചെയ്യുമ്പോൾ ഉപയോഗപ്രദമാണ്.", + "duration_tolerance": "ഒരു ഓട്ടത്തിനിടയിൽ ശേഷിക്കുന്ന സമയം കണക്കാക്കുന്നതിനുള്ള സഹിഷ്ണുത (0.0-0.5 0-50% ന് തുല്യമാണ്).", + "smoothing_window": "ചലിക്കുന്ന ശരാശരി സ്മൂത്തിംഗിനായി ഉപയോഗിക്കുന്ന സമീപകാല പവർ റീഡിംഗുകളുടെ എണ്ണം.", + "profile_duration_tolerance": "ടോളറൻസ് വേരിയൻസിനായി (0.0–0.5) പ്രൊഫൈൽ പൊരുത്തപ്പെടുത്തൽ ഉപയോഗിക്കുന്ന ടോളറൻസ്. സ്ഥിര സ്വഭാവം: ± 25%." + } + }, + "timing_section": { + "name": "ടൈമിംഗ്, പരിപാലനം & ഡീബഗ്", + "description": "വാച്ച്‌ഡോഗ്, പുരോഗതി റീസെറ്റ്, സ്വയമേവ പരിപാലനം, കൂടാതെ ഡീബഗ് എന്റിറ്റികളുടെ പ്രദർശനം.", + "data": { + "watchdog_interval": "വാച്ച്ഡോഗ് ഇടവേള (സെക്കൻഡ്)", + "no_update_active_timeout": "നോ-അപ്‌ഡേറ്റ് ടൈംഔട്ട് (കൾ)", + "progress_reset_delay": "പുരോഗതി പുനഃസജ്ജമാക്കൽ കാലതാമസം (സെക്കൻഡ്)", + "auto_maintenance": "യാന്ത്രിക പരിപാലനം പ്രവർത്തനക്ഷമമാക്കുക", + "expose_debug_entities": "ഡീബഗ് എൻ്റിറ്റികൾ വെളിപ്പെടുത്തുക", + "save_debug_traces": "ഡീബഗ് ട്രെയ്‌സുകൾ സംരക്ഷിക്കുക" + }, + "data_description": { + "watchdog_interval": "ഓടുമ്പോൾ വാച്ച്ഡോഗ് പരിശോധനകൾക്കിടയിൽ സെക്കൻഡുകൾ. സ്ഥിരസ്ഥിതി: 5സെ. മുന്നറിയിപ്പ്: തെറ്റായ സ്റ്റോപ്പുകൾ ഒഴിവാക്കാൻ ഇത് നിങ്ങളുടെ സെൻസറിൻ്റെ അപ്‌ഡേറ്റ് ഇടവേളയേക്കാൾ ഉയർന്നതാണെന്ന് ഉറപ്പാക്കുക (ഉദാ. ഓരോ 60-കളിലും സെൻസർ അപ്‌ഡേറ്റ് ചെയ്യുകയാണെങ്കിൽ, 65 സെ+ ഉപയോഗിക്കുക).", + "no_update_active_timeout": "പബ്ലിഷ്-ഓൺ-ചേഞ്ച് സെൻസറുകൾക്ക്: ഇത്രയും സെക്കൻഡുകൾക്കുള്ളിൽ അപ്‌ഡേറ്റുകളൊന്നും വന്നില്ലെങ്കിൽ മാത്രം ഒരു ആക്റ്റീവ് സൈക്കിൾ നിർബന്ധിതമായി അവസാനിപ്പിക്കുക. ലോ-പവർ പൂർത്തിയാക്കൽ ഇപ്പോഴും ഓഫ് ഡിലേ ഉപയോഗിക്കുന്നു.", + "progress_reset_delay": "ഒരു സൈക്കിൾ പൂർത്തിയായതിന് ശേഷം (100%), പുരോഗതി 0% ആയി പുനഃസജ്ജമാക്കുന്നതിന് മുമ്പ് നിഷ്‌ക്രിയമായി ഇത്രയും സെക്കൻഡ് കാത്തിരിക്കുക. സ്ഥിരസ്ഥിതി: 300സെ.", + "auto_maintenance": "യാന്ത്രിക പരിപാലനം പ്രവർത്തനക്ഷമമാക്കുക (സാമ്പിളുകൾ നന്നാക്കുക, ശകലങ്ങൾ ലയിപ്പിക്കുക).", + "expose_debug_entities": "ഡീബഗ്ഗിംഗിനായി വിപുലമായ സെൻസറുകൾ (ആത്മവിശ്വാസം, ഘട്ടം, അവ്യക്തത) കാണിക്കുക.", + "save_debug_traces": "ചരിത്രത്തിൽ വിശദമായ റാങ്കിംഗും പവർ ട്രേസ് ഡാറ്റയും സംഭരിക്കുക (സ്റ്റോറേജ് ഉപയോഗം വർദ്ധിപ്പിക്കുന്നു)." + } + }, + "anti_wrinkle_section": { + "name": "ആൻ്റി-റിങ്കിൾ ഷീൽഡ് (ഡ്രയറുകൾ)", + "description": "ഗോസ്റ്റ് സൈക്കിളുകൾ തടയാൻ സൈക്കിൾ കഴിഞ്ഞ ശേഷമുള്ള ഡ്രം ഭ്രമണങ്ങൾ അവഗണിക്കുക. ഡ്രയർ / വാഷർ-ഡ്രയർ മാത്രം.", + "data": { + "anti_wrinkle_enabled": "ആൻ്റി റിങ്കിൾ ഷീൽഡ് (ഡ്രയർ മാത്രം)", + "anti_wrinkle_max_power": "ആൻ്റി റിങ്കിൾ മാക്സ് പവർ (W)", + "anti_wrinkle_max_duration": "ആൻ്റി റിങ്കിൾ മാക്സ് ദൈർഘ്യം (കൾ)", + "anti_wrinkle_exit_power": "ആൻ്റി റിങ്കിൾ എക്സിറ്റ് പവർ (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "ഗോസ്റ്റ് സൈക്കിളുകൾ തടയാൻ (ഡ്രയർ/വാഷർ-ഡ്രയർ മാത്രം) ഒരു സൈക്കിൾ അവസാനിച്ചതിന് ശേഷം ഹ്രസ്വമായ ലോ-പവർ ഡ്രം റൊട്ടേഷനുകൾ അവഗണിക്കുക.", + "anti_wrinkle_max_power": "ഈ ശക്തിയിലോ അതിനു താഴെയോ ഉള്ള പൊട്ടിത്തെറികൾ പുതിയ സൈക്കിളുകൾക്ക് പകരം ചുളിവുകൾ തടയുന്ന ഭ്രമണങ്ങളായി കണക്കാക്കുന്നു. ആൻ്റി റിങ്കിൾ ഷീൽഡ് പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ മാത്രമേ ഇത് ബാധകമാകൂ.", + "anti_wrinkle_max_duration": "ആൻ്റി റിങ്കിൾ റൊട്ടേഷനായി കണക്കാക്കാൻ പരമാവധി ബർസ്റ്റ് ദൈർഘ്യം. ആൻ്റി റിങ്കിൾ ഷീൽഡ് പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ മാത്രമേ ഇത് ബാധകമാകൂ.", + "anti_wrinkle_exit_power": "ഈ നിലയ്ക്ക് താഴെ പവർ കുറയുകയാണെങ്കിൽ, ഉടനടി ആൻ്റി റിങ്കിൽ നിന്ന് പുറത്തുകടക്കുക (ട്രൂ ഓഫ്). ആൻ്റി റിങ്കിൾ ഷീൽഡ് പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ മാത്രമേ ഇത് ബാധകമാകൂ." + } + }, + "delay_start_section": { + "name": "വൈകിയാരംഭം കണ്ടെത്തൽ", + "description": "സൈക്കിൾ യഥാർത്ഥത്തിൽ തുടങ്ങുന്നതുവരെ സ്ഥിരമായ കുറഞ്ഞ-പവർ സ്റ്റാൻഡ്‌ബൈയെ 'ആരംഭിക്കാൻ കാത്തിരിക്കുന്നു' എന്ന നിലയായി കണക്കാക്കുക.", + "data": { + "delay_start_detect_enabled": "വൈകി ആരംഭിക്കൽ കണ്ടെത്തൽ പ്രവർത്തനക്ഷമമാക്കുക", + "delay_confirm_seconds": "വൈകിയാരംഭം: സ്റ്റാൻഡ്‌ബൈ സ്ഥിരീകരണ സമയം (സെ)", + "delay_timeout_hours": "വൈകിയുള്ള ആരംഭം: പരമാവധി കാത്തിരിപ്പ് സമയം (മണിക്കൂർ)" + }, + "data_description": { + "delay_start_detect_enabled": "പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ, ഒരു ഹ്രസ്വമായ ലോ-പവർ ഡ്രെയിൻ സ്പൈക്കും തുടർന്ന് സുസ്ഥിര സ്റ്റാൻഡ്‌ബൈ പവറും കാലതാമസം നേരിട്ട ആരംഭമായി കണ്ടെത്തും. കാലതാമസത്തിനിടയിൽ സ്റ്റേറ്റ് സെൻസർ 'വെയ്റ്റിംഗ് ടു സ്റ്റാർട്ട്' കാണിക്കുന്നു. സൈക്കിൾ യഥാർത്ഥത്തിൽ ആരംഭിക്കുന്നത് വരെ പ്രോഗ്രാമിൻ്റെ സമയമോ പുരോഗതിയോ ട്രാക്ക് ചെയ്യപ്പെടുന്നില്ല. ഡ്രെയിൻ സ്പൈക്ക് പവറിന് മുകളിൽ സജ്ജീകരിക്കാൻ start_threshold_w ആവശ്യമാണ്.", + "delay_confirm_seconds": "WashData 'ആരംഭിക്കാൻ കാത്തിരിക്കുന്നു' നിലയിലേക്ക് പ്രവേശിക്കുന്നതിന് മുമ്പ് പവർ സ്റ്റോപ്പ് ത്രെഷോൾഡ് (W) നും സ്റ്റാർട്ട് ത്രെഷോൾഡ് (W) നും ഇടയിൽ ഇത്രയും സെക്കൻഡുകൾ നിലനിൽക്കണം. മെനു നാവിഗേഷനിലെ ചെറു സ്പൈക്കുകൾ ഫിൽറ്റർ ചെയ്യുന്നു. ഡീഫോൾട്ട്: 60 സെ.", + "delay_timeout_hours": "സുരക്ഷാ കാലഹരണപ്പെടൽ: ഇത്രയും മണിക്കൂറുകൾക്ക് ശേഷവും മെഷീൻ 'ആരംഭിക്കാൻ കാത്തിരിക്കുന്നു' എന്നതാണെങ്കിൽ, WashData ഓഫിലേക്ക് പുനഃസജ്ജമാക്കും. സ്ഥിരസ്ഥിതി: 8 മണിക്കൂർ." + } + }, + "external_triggers_section": { + "name": "ബാഹ്യ ട്രിഗറുകൾ, വാതിൽ & താൽക്കാലിക നിർത്തൽ", + "description": "ഓപ്ഷണൽ ബാഹ്യ അവസാന-ട്രിഗർ സെൻസർ, താൽക്കാലിക നിർത്തൽ/ക്ലീൻ കണ്ടെത്തലിന് വാതിൽ സെൻസർ, കൂടാതെ pause-power-cut സ്വിച്ച്.", + "data": { + "external_end_trigger_enabled": "ബാഹ്യ സൈക്കിൾ എൻഡ് ട്രിഗർ പ്രവർത്തനക്ഷമമാക്കുക", + "external_end_trigger": "ബാഹ്യ സൈക്കിൾ എൻഡ് സെൻസർ", + "external_end_trigger_inverted": "വിപരീത ട്രിഗർ ലോജിക് (ട്രിഗർ ഓൺ ഓഫ്)", + "door_sensor_entity": "ഡോർ സെൻസർ", + "pause_cuts_power": "താൽക്കാലികമായി നിർത്തുമ്പോൾ പവർ കട്ട് ചെയ്യുക", + "switch_entity": "എൻ്റിറ്റി മാറുക (താൽക്കാലികമായി പവർ കട്ടിനായി)", + "notify_unload_delay_minutes": "അലക്കൽ കാത്തിരിപ്പ് അറിയിപ്പ് കാലതാമസം (മിനിറ്റ്)" + }, + "data_description": { + "external_end_trigger_enabled": "സൈക്കിൾ പൂർത്തീകരണം ട്രിഗർ ചെയ്യുന്നതിന് ഒരു ബാഹ്യ ബൈനറി സെൻസറിൻ്റെ നിരീക്ഷണം പ്രവർത്തനക്ഷമമാക്കുക.", + "external_end_trigger": "ഒരു ബൈനറി സെൻസർ എൻ്റിറ്റി തിരഞ്ഞെടുക്കുക. ഈ സെൻസർ ട്രിഗർ ചെയ്യുമ്പോൾ, നിലവിലെ സൈക്കിൾ 'പൂർത്തിയായി' എന്ന നിലയോടെ അവസാനിക്കും.", + "external_end_trigger_inverted": "ഡിഫോൾട്ടായി, സെൻസർ ഓണാകുമ്പോൾ ട്രിഗർ ഫയർ ചെയ്യുന്നു. പകരം സെൻസർ ഓഫാകുമ്പോൾ ഈ ബോക്‌സിൽ ചെക്ക് ചെയ്യുക.", + "door_sensor_entity": "മെഷീൻ ഡോറിനുള്ള ഓപ്ഷണൽ ബൈനറി സെൻസർ (ഓൺ = ഓപ്പൺ, ഓഫ് = ക്ലോസ്). ഒരു സജീവ സൈക്കിൾ സമയത്ത് വാതിൽ തുറക്കുമ്പോൾ, WashData സൈക്കിൾ മനഃപൂർവ്വം താൽക്കാലികമായി നിർത്തിയതായി സ്ഥിരീകരിക്കും. സൈക്കിൾ അവസാനിച്ചതിന് ശേഷവും, വാതിൽ അടഞ്ഞിരിക്കുകയാണെങ്കിൽ, വാതിൽ തുറക്കുന്നത് വരെ 'ക്ലീൻ' എന്ന അവസ്ഥയിലേക്ക് മാറുന്നു. ശ്രദ്ധിക്കുക: വാതിൽ അടയ്ക്കുന്നത് താൽക്കാലികമായി നിർത്തിയ സൈക്കിൾ സ്വയമേവ പുനരാരംഭിക്കില്ല - റെസ്യൂം സൈക്കിൾ ബട്ടൺ അല്ലെങ്കിൽ സേവനം വഴി സ്വമേധയാ പ്രവർത്തനക്ഷമമാക്കണം.", + "pause_cuts_power": "പോസ് സൈക്കിൾ ബട്ടണിലൂടെയോ സേവനത്തിലൂടെയോ താൽക്കാലികമായി നിർത്തുമ്പോൾ, കോൺഫിഗർ ചെയ്‌ത സ്വിച്ച് എൻ്റിറ്റിയും ഓഫാക്കുക. ഈ ഉപകരണത്തിനായി ഒരു സ്വിച്ച് എൻ്റിറ്റി കോൺഫിഗർ ചെയ്‌തിട്ടുണ്ടെങ്കിൽ മാത്രമേ പ്രാബല്യത്തിൽ വരികയുള്ളൂ.", + "switch_entity": "താൽക്കാലികമായി നിർത്തുമ്പോഴോ പുനരാരംഭിക്കുമ്പോഴോ ടോഗിൾ ചെയ്യാനുള്ള സ്വിച്ച് എൻ്റിറ്റി. 'താൽക്കാലികമായി നിർത്തുമ്പോൾ കട്ട് പവർ' പ്രവർത്തനക്ഷമമാക്കുമ്പോൾ ആവശ്യമാണ്.", + "notify_unload_delay_minutes": "സൈക്കിൾ അവസാനിച്ചതിന് ശേഷം ഫിനിഷ് അറിയിപ്പ് ചാനൽ വഴി ഒരു അറിയിപ്പ് അയയ്‌ക്കുക, ഇത്രയും മിനിറ്റായിട്ടും വാതിൽ തുറന്നിട്ടില്ല (ഡോർ സെൻസർ ആവശ്യമാണ്). പ്രവർത്തനരഹിതമാക്കാൻ 0 ആയി സജ്ജമാക്കുക. സ്ഥിരസ്ഥിതി: 60 മിനിറ്റ്." + } + }, + "pump_section": { + "name": "പമ്പ് മോണിറ്റർ", + "description": "പമ്പ് ഡിവൈസ് തരം മാത്രം. ഒരു പമ്പ് സൈക്കിൾ ഈ ദൈർഘ്യം കടന്നാൽ ഒരു ഇവന്റ് ഫയർ ചെയ്യും.", + "data": { + "pump_stuck_duration": "പമ്പ് സ്റ്റക്ക് അലേർട്ട് ത്രെഷോൾഡ് (കൾ) (പമ്പ് മാത്രം)" + }, + "data_description": { + "pump_stuck_duration": "ഇത്രയും സെക്കൻ്റുകൾക്ക് ശേഷവും ഒരു പമ്പ് സൈക്കിൾ പ്രവർത്തിക്കുന്നുണ്ടെങ്കിൽ, ഒരു ha_washdata_pump_stuck ഇവൻ്റ് ഒരിക്കൽ ഫയർ ചെയ്യപ്പെടും. ഒരു തടസ്സപ്പെട്ട മോട്ടോർ കണ്ടെത്താൻ ഇത് ഉപയോഗിക്കുക (സാധാരണ സംപ് പമ്പ് റൺ 60 സെക്കൻഡിൽ താഴെയാണ്). സ്ഥിരസ്ഥിതി: 1800 സെക്കൻഡ് (30 മിനിറ്റ്). പമ്പ് ഉപകരണത്തിൻ്റെ തരം മാത്രം." + } + }, + "device_link_section": { + "name": "ഉപകരണ ലിങ്ക്", + "description": "ഓപ്ഷണലായി ഈ WashData ഉപകരണം നിലവിലുള്ള ഒരു Home Assistant ഉപകരണത്തിലേക്ക് ലിങ്ക് ചെയ്യുക (ഉദാഹരണത്തിന് സ്മാർട്ട് പ്ലഗ് അല്ലെങ്കിൽ ഉപകരണം തന്നെ). WashData ഉപകരണം ആ ഉപകരണത്തിൽ \"കണക്‌റ്റഡ് വഴി\" എന്ന് കാണിക്കും. WashData ഒരു ഒറ്റപ്പെട്ട ഉപകരണമായി നിലനിർത്താൻ ശൂന്യമായി വിടുക.", + "data": { + "linked_device": "ലിങ്ക് ചെയ്‌ത ഉപകരണം" + }, + "data_description": { + "linked_device": "WashData-ലേക്ക് കണക്റ്റ് ചെയ്യാനുള്ള ഉപകരണം തിരഞ്ഞെടുക്കുക. ഇത് ഉപകരണ രജിസ്ട്രിയിൽ ഒരു 'കണക്‌റ്റഡ് വഴി' റഫറൻസ് ചേർക്കുന്നു; WashData അതിൻ്റെ സ്വന്തം ഉപകരണ കാർഡും എൻ്റിറ്റികളും സൂക്ഷിക്കുന്നു. ലിങ്ക് നീക്കംചെയ്യാൻ തിരഞ്ഞെടുപ്പ് മായ്‌ക്കുക." + } + } + } + }, + "diagnostics": { + "title": "ഡയഗ്നോസ്റ്റിക്സ് & മെയിൻ്റനൻസ്", + "description": "വിഘടിച്ച സൈക്കിളുകൾ ലയിപ്പിക്കുന്നതോ സംഭരിച്ച ഡാറ്റ മൈഗ്രേറ്റുചെയ്യുന്നതോ പോലുള്ള പരിപാലന പ്രവർത്തനങ്ങൾ പ്രവർത്തിപ്പിക്കുക.\n\n**സംഭരണ ഉപയോഗം**\n\n- ഫയൽ വലുപ്പം: {file_size_kb} KB\n- സൈക്കിളുകൾ: {cycle_count}\n- പ്രൊഫൈലുകൾ: {profile_count}\n- ഡീബഗ് ട്രെയ്‌സ്: {debug_count}", + "menu_options": { + "reprocess_history": "മെയിൻ്റനൻസ്: റീപ്രോസസ് & ഡാറ്റ ഒപ്റ്റിമൈസ്", + "clear_debug_data": "ഡീബഗ് ഡാറ്റ മായ്‌ക്കുക (ഇടം ശൂന്യമാക്കുക)", + "wipe_history": "ഈ ഉപകരണത്തിനായുള്ള എല്ലാ ഡാറ്റയും മായ്‌ക്കുക (മാറ്റാനാകാത്തത്)", + "export_import": "ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് JSON കയറ്റുമതി/ഇറക്കുമതി ചെയ്യുക (പകർത്തുക/ഒട്ടിക്കുക)", + "menu_back": "← മടങ്ങുക" + } + }, + "clear_debug_data": { + "title": "ഡീബഗ് ഡാറ്റ മായ്‌ക്കുക", + "description": "സംഭരിച്ച എല്ലാ ഡീബഗ് ട്രെയ്‌സുകളും ഇല്ലാതാക്കണമെന്ന് തീർച്ചയാണോ? ഇത് ഇടം ശൂന്യമാക്കും എന്നാൽ കഴിഞ്ഞ സൈക്കിളുകളിൽ നിന്ന് വിശദമായ റാങ്കിംഗ് വിവരങ്ങൾ നീക്കം ചെയ്യും." + }, + "manage_cycles": { + "title": "സൈക്കിളുകൾ നിയന്ത്രിക്കുക", + "description": "സമീപകാല ചക്രങ്ങൾ:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "പഴയ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക", + "select_cycle_to_label": "നിർദ്ദിഷ്ട സൈക്കിൾ ലേബൽ ചെയ്യുക", + "select_cycle_to_delete": "സൈക്കിൾ ഇല്ലാതാക്കുക", + "interactive_editor": "ഇൻ്ററാക്ടീവ് എഡിറ്റർ ലയിപ്പിക്കുക/വിഭജിക്കുക", + "trim_cycle_select": "സൈക്കിൾ ഡാറ്റ ട്രിം ചെയ്യുക", + "menu_back": "← മടങ്ങുക" + } + }, + "manage_cycles_empty": { + "title": "സൈക്കിളുകൾ നിയന്ത്രിക്കുക", + "description": "ഇതുവരെ സൈക്കിളുകളൊന്നും രേഖപ്പെടുത്തിയിട്ടില്ല.", + "menu_options": { + "auto_label_cycles": "പഴയ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക", + "menu_back": "← മടങ്ങുക" + } + }, + "manage_profiles": { + "title": "പ്രൊഫൈലുകൾ നിയന്ത്രിക്കുക", + "description": "പ്രൊഫൈൽ സംഗ്രഹം:\n{profile_summary}", + "menu_options": { + "create_profile": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "edit_profile": "പ്രൊഫൈൽ എഡിറ്റ് ചെയ്യുക/പേരുമാറ്റുക", + "delete_profile_select": "പ്രൊഫൈൽ ഇല്ലാതാക്കുക", + "profile_stats": "പ്രൊഫൈൽ സ്ഥിതിവിവരക്കണക്കുകൾ", + "cleanup_profile": "ചരിത്രം വൃത്തിയാക്കുക - ഗ്രാഫ് & ഇല്ലാതാക്കുക", + "assign_profile_phases_select": "ഘട്ടം ശ്രേണികൾ നിയോഗിക്കുക", + "menu_back": "← മടങ്ങുക" + } + }, + "manage_profiles_empty": { + "title": "പ്രൊഫൈലുകൾ നിയന്ത്രിക്കുക", + "description": "ഇതുവരെ പ്രൊഫൈലുകളൊന്നും സൃഷ്ടിച്ചിട്ടില്ല.", + "menu_options": { + "create_profile": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "menu_back": "← മടങ്ങുക" + } + }, + "manage_phase_catalog": { + "title": "ഘട്ടം കാറ്റലോഗ് കൈകാര്യം ചെയ്യുക", + "description": "ഘട്ട കാറ്റലോഗ് (ഈ ഉപകരണത്തിനായുള്ള ഡിഫോൾട്ടുകൾ + ഇഷ്‌ടാനുസൃത ഘട്ടങ്ങൾ):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "പുതിയ ഘട്ടം സൃഷ്ടിക്കുക", + "phase_catalog_edit_select": "എഡിറ്റ് ഘട്ടം", + "phase_catalog_delete": "ഘട്ടം ഇല്ലാതാക്കുക", + "menu_back": "← മടങ്ങുക" + } + }, + "phase_catalog_create": { + "title": "ഇഷ്ടാനുസൃത ഘട്ടം സൃഷ്ടിക്കുക", + "description": "ഒരു ഇഷ്‌ടാനുസൃത ഫേസ് പേരും വിവരണവും സൃഷ്‌ടിക്കുക, അത് ഏത് ഉപകരണത്തിനാണ് ബാധകമെന്ന് തിരഞ്ഞെടുക്കുക.", + "data": { + "target_device_type": "ടാർഗെറ്റ് ഉപകരണ തരം", + "phase_name": "ഘട്ടത്തിൻ്റെ പേര്", + "phase_description": "ഘട്ട വിവരണം" + } + }, + "phase_catalog_edit_select": { + "title": "ഇഷ്‌ടാനുസൃത ഘട്ടം എഡിറ്റ് ചെയ്യുക", + "description": "എഡിറ്റ് ചെയ്യാൻ ഒരു ഇഷ്‌ടാനുസൃത ഘട്ടം തിരഞ്ഞെടുക്കുക.", + "data": { + "phase_name": "ഇഷ്ടാനുസൃത ഘട്ടം" + } + }, + "phase_catalog_edit": { + "title": "ഇഷ്‌ടാനുസൃത ഘട്ടം എഡിറ്റ് ചെയ്യുക", + "description": "എഡിറ്റിംഗ് ഘട്ടം: {phase_name}", + "data": { + "phase_name": "ഘട്ടത്തിൻ്റെ പേര്", + "phase_description": "ഘട്ട വിവരണം" + } + }, + "phase_catalog_delete": { + "title": "ഇഷ്‌ടാനുസൃത ഘട്ടം ഇല്ലാതാക്കുക", + "description": "ഇല്ലാതാക്കാൻ ഒരു ഇഷ്‌ടാനുസൃത ഘട്ടം തിരഞ്ഞെടുക്കുക. ഈ ഘട്ടം ഉപയോഗിക്കുന്ന എല്ലാ നിയുക്ത ശ്രേണികളും നീക്കം ചെയ്യപ്പെടും.", + "data": { + "phase_name": "ഇഷ്ടാനുസൃത ഘട്ടം" + } + }, + "assign_profile_phases": { + "title": "ഘട്ടം ശ്രേണികൾ നിയോഗിക്കുക", + "description": "പ്രൊഫൈലിനായുള്ള ഘട്ട പ്രിവ്യൂ: **{profile_name}**\n\n{timeline_svg}\n\n**നിലവിലെ ശ്രേണികൾ:**\n{current_ranges}\n\nശ്രേണികൾ ചേർക്കാനോ എഡിറ്റ് ചെയ്യാനോ ഇല്ലാതാക്കാനോ സംരക്ഷിക്കാനോ ചുവടെയുള്ള പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കുക.", + "menu_options": { + "assign_profile_phases_add": "ഘട്ട ശ്രേണി ചേർക്കുക", + "assign_profile_phases_edit_select": "ഘട്ട ശ്രേണി എഡിറ്റ് ചെയ്യുക", + "assign_profile_phases_delete": "ഘട്ട ശ്രേണി ഇല്ലാതാക്കുക", + "phase_ranges_clear": "എല്ലാ ശ്രേണികളും മായ്‌ക്കുക", + "assign_profile_phases_auto_detect": "ഘട്ടങ്ങൾ സ്വയം കണ്ടെത്തുക", + "phase_ranges_save": "സംരക്ഷിച്ച് മടങ്ങുക", + "menu_back": "← മടങ്ങുക" + } + }, + "assign_profile_phases_add": { + "title": "ഘട്ട ശ്രേണി ചേർക്കുക", + "description": "നിലവിലെ ഡ്രാഫ്റ്റിലേക്ക് ഒരു ഘട്ട ശ്രേണി ചേർക്കുക.", + "data": { + "phase_name": "ഘട്ടം", + "start_min": "ആരംഭ മിനിറ്റ്", + "end_min": "അവസാന മിനിറ്റ്" + } + }, + "assign_profile_phases_edit_select": { + "title": "ഘട്ട ശ്രേണി എഡിറ്റ് ചെയ്യുക", + "description": "എഡിറ്റുചെയ്യാൻ ഒരു ശ്രേണി തിരഞ്ഞെടുക്കുക.", + "data": { + "range_index": "ഘട്ടം ശ്രേണി" + } + }, + "assign_profile_phases_edit": { + "title": "ഘട്ട ശ്രേണി എഡിറ്റ് ചെയ്യുക", + "description": "തിരഞ്ഞെടുത്ത ഘട്ട ശ്രേണി അപ്ഡേറ്റ് ചെയ്യുക.", + "data": { + "phase_name": "ഘട്ടം", + "start_min": "ആരംഭ മിനിറ്റ്", + "end_min": "അവസാന മിനിറ്റ്" + } + }, + "assign_profile_phases_delete": { + "title": "ഘട്ട ശ്രേണി ഇല്ലാതാക്കുക", + "description": "ഡ്രാഫ്റ്റിൽ നിന്ന് നീക്കം ചെയ്യാൻ ഒരു ശ്രേണി തിരഞ്ഞെടുക്കുക.", + "data": { + "range_index": "ഘട്ടം ശ്രേണി" + } + }, + "assign_profile_phases_auto_detect": { + "title": "സ്വയം കണ്ടെത്തിയ ഘട്ടങ്ങൾ", + "description": "പ്രൊഫൈൽ: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} ഘട്ടം(കൾ) സ്വയമേവ കണ്ടെത്തി.** താഴെ ഒരു പ്രവർത്തനം തിരഞ്ഞെടുക്കുക.", + "data": { + "action": "ആക്ഷൻ" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "കണ്ടെത്തിയ ഘട്ടങ്ങളുടെ പേര്", + "description": "പ്രൊഫൈൽ: **{profile_name}**\n\n**{detected_count} ഘട്ടം(കൾ) കണ്ടെത്തി:**\n{ranges_summary}\n\nഓരോ ഘട്ടത്തിനും ഒരു പേര് നൽകുക." + }, + "assign_profile_phases_select": { + "title": "ഘട്ടം ശ്രേണികൾ നിയോഗിക്കുക", + "description": "ഘട്ട ശ്രേണികൾ കോൺഫിഗർ ചെയ്യാൻ ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക.", + "data": { + "profile": "പ്രൊഫൈൽ" + } + }, + "profile_stats": { + "title": "പ്രൊഫൈൽ സ്ഥിതിവിവരക്കണക്കുകൾ", + "description": "{stats_table}" + }, + "create_profile": { + "title": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "description": "സ്വമേധയാ അല്ലെങ്കിൽ കഴിഞ്ഞ സൈക്കിളിൽ നിന്ന് ഒരു പുതിയ പ്രൊഫൈൽ സൃഷ്‌ടിക്കുക.\n\nപ്രൊഫൈൽ നാമത്തിൻ്റെ ഉദാഹരണങ്ങൾ: 'ഡെലിക്കേറ്റ്സ്', 'ഹെവി ഡ്യൂട്ടി', 'ക്വിക്ക് വാഷ്'", + "data": { + "profile_name": "പ്രൊഫൈൽ പേര്", + "reference_cycle": "റഫറൻസ് സൈക്കിൾ (ഓപ്ഷണൽ)", + "manual_duration": "മാനുവൽ ദൈർഘ്യം (മിനിറ്റുകൾ)" + } + }, + "edit_profile": { + "title": "പ്രൊഫൈൽ എഡിറ്റ് ചെയ്യുക", + "description": "പേരുമാറ്റാൻ ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക", + "data": { + "profile": "പ്രൊഫൈൽ" + } + }, + "rename_profile": { + "title": "പ്രൊഫൈൽ വിശദാംശങ്ങൾ എഡിറ്റ് ചെയ്യുക", + "description": "നിലവിലെ പ്രൊഫൈൽ: {current_name}", + "data": { + "new_name": "പ്രൊഫൈൽ പേര്", + "manual_duration": "മാനുവൽ ദൈർഘ്യം (മിനിറ്റുകൾ)" + } + }, + "delete_profile_select": { + "title": "പ്രൊഫൈൽ ഇല്ലാതാക്കുക", + "description": "ഇല്ലാതാക്കാൻ ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക", + "data": { + "profile": "പ്രൊഫൈൽ" + } + }, + "delete_profile_confirm": { + "title": "പ്രൊഫൈൽ ഇല്ലാതാക്കുന്നത് സ്ഥിരീകരിക്കുക", + "description": "⚠️ ഇത് പ്രൊഫൈൽ ശാശ്വതമായി ഇല്ലാതാക്കും: {profile_name}", + "data": { + "unlabel_cycles": "ഈ പ്രൊഫൈൽ ഉപയോഗിച്ച് സൈക്കിളുകളിൽ നിന്ന് ലേബൽ നീക്കം ചെയ്യുക" + } + }, + "auto_label_cycles": { + "title": "പഴയ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക", + "description": "ആകെ {total_count} സൈക്കിളുകൾ കണ്ടെത്തി. പ്രൊഫൈലുകൾ: {profiles}", + "data": { + "confidence_threshold": "കുറഞ്ഞ ആത്മവിശ്വാസം (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "ലേബലിലേക്ക് സൈക്കിൾ തിരഞ്ഞെടുക്കുക", + "description": "✓ = പൂർത്തിയായി, ⚠ = പുനരാരംഭിച്ചു, ✗ = തടസ്സപ്പെട്ടു", + "data": { + "cycle_id": "സൈക്കിൾ" + } + }, + "select_cycle_to_delete": { + "title": "സൈക്കിൾ ഇല്ലാതാക്കുക", + "description": "⚠️ ഇത് തിരഞ്ഞെടുത്ത സൈക്കിളിനെ ശാശ്വതമായി ഇല്ലാതാക്കും", + "data": { + "cycle_id": "ഇല്ലാതാക്കാൻ സൈക്കിൾ ചെയ്യുക" + } + }, + "label_cycle": { + "title": "ലേബൽ സൈക്കിൾ", + "description": "{cycle_info}", + "data": { + "profile_name": "പ്രൊഫൈൽ", + "new_profile_name": "പുതിയ പ്രൊഫൈൽ പേര് (സൃഷ്ടിക്കുകയാണെങ്കിൽ)" + } + }, + "post_process": { + "title": "പ്രക്രിയ ചരിത്രം (ലയിപ്പിക്കുക/വിഭജിക്കുക)", + "description": "ഛിന്നഭിന്നമായ റണ്ണുകൾ ലയിപ്പിച്ചും തെറ്റായി ലയിപ്പിച്ച സൈക്കിളുകൾ സമയ ജാലകത്തിനുള്ളിൽ വിഭജിച്ചും സൈക്കിൾ ചരിത്രം വൃത്തിയാക്കുക. തിരിഞ്ഞു നോക്കാൻ മണിക്കൂറുകളുടെ എണ്ണം നൽകുക (എല്ലാവർക്കും 999999 ഉപയോഗിക്കുക).", + "data": { + "time_range": "ലുക്ക്ബാക്ക് വിൻഡോ (മണിക്കൂറുകൾ)", + "gap_seconds": "ലയിപ്പിക്കുക/സ്പ്ലിറ്റ് വിടവ് (സെക്കൻഡ്)" + }, + "data_description": { + "time_range": "വിഘടിച്ച സൈക്കിളുകൾ ലയിപ്പിക്കാൻ സ്കാൻ ചെയ്യേണ്ട മണിക്കൂറുകളുടെ എണ്ണം. എല്ലാ ചരിത്ര ഡാറ്റയും പ്രോസസ്സ് ചെയ്യുന്നതിന് 999999 ഉപയോഗിക്കുക.", + "gap_seconds": "വിഭജനവും ലയനവും തീരുമാനിക്കാനുള്ള പരിധി. ഇതിലും ചെറിയ വിടവുകൾ ലയിപ്പിച്ചിരിക്കുന്നു. ഇതിലും ദൈർഘ്യമേറിയ വിടവുകൾ വിഭജിക്കപ്പെട്ടിരിക്കുന്നു. തെറ്റായി ലയിപ്പിച്ച ഒരു സൈക്കിളിൽ ഒരു വിഭജനം നിർബന്ധിതമാക്കാൻ ഇത് താഴ്ത്തുക." + } + }, + "cleanup_profile": { + "title": "ചരിത്രം വൃത്തിയാക്കുക - പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക", + "description": "ദൃശ്യവൽക്കരിക്കാൻ ഒരു പ്രൊഫൈൽ തിരഞ്ഞെടുക്കുക. ഔട്ട്‌ലറുകൾ തിരിച്ചറിയാൻ സഹായിക്കുന്നതിന് ഈ പ്രൊഫൈലിനായി കഴിഞ്ഞ എല്ലാ സൈക്കിളുകളും കാണിക്കുന്ന ഒരു ഗ്രാഫ് ഇത് സൃഷ്ടിക്കും.", + "data": { + "profile": "പ്രൊഫൈൽ" + } + }, + "cleanup_select": { + "title": "ചരിത്രം വൃത്തിയാക്കുക - ഗ്രാഫ് & ഇല്ലാതാക്കുക", + "description": "![ഗ്രാഫ്]({graph_url})\n\n**ദൃശ്യമാക്കൽ {profile_name}**\n\nഗ്രാഫ് വ്യക്തിഗത സൈക്കിളുകളെ നിറമുള്ള വരകളായി കാണിക്കുന്നു. പുറത്തുള്ളവരെ (ഗ്രൂപ്പിൽ നിന്ന് വളരെ അകലെയുള്ള വരികൾ) തിരിച്ചറിയുകയും അവ ഇല്ലാതാക്കുന്നതിന് ചുവടെയുള്ള പട്ടികയിൽ അവയുടെ വർണ്ണവുമായി പൊരുത്തപ്പെടുകയും ചെയ്യുക.\n\n**ശാശ്വതമായി ഇല്ലാതാക്കാൻ സൈക്കിളുകൾ തിരഞ്ഞെടുക്കുക:**", + "data": { + "cycles_to_delete": "ഇല്ലാതാക്കാനുള്ള സൈക്കിളുകൾ (പൊരുത്തമുള്ള നിറങ്ങൾ പരിശോധിക്കുക)" + } + }, + "interactive_editor": { + "title": "ഇൻ്ററാക്ടീവ് എഡിറ്റർ ലയിപ്പിക്കുക/വിഭജിക്കുക", + "description": "നിങ്ങളുടെ സൈക്കിൾ ചരിത്രത്തിൽ നടപ്പിലാക്കാൻ ഒരു മാനുവൽ പ്രവർത്തനം തിരഞ്ഞെടുക്കുക.", + "menu_options": { + "editor_split": "ഒരു സൈക്കിൾ വിഭജിക്കുക (വിടവുകൾ കണ്ടെത്തുക)", + "editor_merge": "സൈക്കിളുകൾ ലയിപ്പിക്കുക (ശകലങ്ങൾ കൂട്ടിച്ചേർക്കുക)", + "editor_delete": "സൈക്കിൾ(കൾ) ഇല്ലാതാക്കുക", + "menu_back": "← മടങ്ങുക" + } + }, + "editor_select": { + "title": "സൈക്കിളുകൾ തിരഞ്ഞെടുക്കുക", + "description": "പ്രോസസ്സ് ചെയ്യുന്നതിന് സൈക്കിൾ(കൾ) തിരഞ്ഞെടുക്കുക.\n\n{info_text}", + "data": { + "selected_cycles": "സൈക്കിളുകൾ" + } + }, + "editor_configure": { + "title": "കോൺഫിഗർ ചെയ്യുക & പ്രയോഗിക്കുക", + "description": "{preview_md}", + "data": { + "confirm_action": "ആക്ഷൻ", + "merged_profile": "ഫലമായ പ്രൊഫൈൽ", + "new_profile_name": "പുതിയ പ്രൊഫൈൽ പേര്", + "confirm_commit": "അതെ, ഞാൻ ഈ പ്രവർത്തനം സ്ഥിരീകരിക്കുന്നു", + "segment_0_profile": "സെഗ്മെൻ്റ് 1 പ്രൊഫൈൽ", + "segment_1_profile": "സെഗ്മെൻ്റ് 2 പ്രൊഫൈൽ", + "segment_2_profile": "സെഗ്മെൻ്റ് 3 പ്രൊഫൈൽ", + "segment_3_profile": "സെഗ്മെൻ്റ് 4 പ്രൊഫൈൽ", + "segment_4_profile": "സെഗ്മെൻ്റ് 5 പ്രൊഫൈൽ", + "segment_5_profile": "സെഗ്മെൻ്റ് 6 പ്രൊഫൈൽ", + "segment_6_profile": "സെഗ്മെൻ്റ് 7 പ്രൊഫൈൽ", + "segment_7_profile": "സെഗ്മെൻ്റ് 8 പ്രൊഫൈൽ", + "segment_8_profile": "സെഗ്മെൻ്റ് 9 പ്രൊഫൈൽ", + "segment_9_profile": "സെഗ്മെൻ്റ് 10 പ്രൊഫൈൽ" + } + }, + "editor_split_params": { + "title": "സ്പ്ലിറ്റ് കോൺഫിഗറേഷൻ", + "description": "ഈ ചക്രം വിഭജിക്കുന്നതിനുള്ള സെൻസിറ്റിവിറ്റി ക്രമീകരിക്കുക. ഇതിലും ദൈർഘ്യമുള്ള ഏതെങ്കിലും നിഷ്‌ക്രിയ വിടവ് ഒരു വിഭജനത്തിന് കാരണമാകും.", + "data": { + "split_mode": "സ്പ്ലിറ്റ് രീതി" + } + }, + "editor_split_auto_params": { + "title": "ഓട്ടോ-സ്പ്ലിറ്റ് ക്രമീകരണം", + "description": "ഈ സൈക്കിൾ വിഭജിക്കുന്നതിനുള്ള സെൻസിറ്റിവിറ്റി ക്രമീകരിക്കുക. ഇതിലും ദൈർഘ്യമുള്ള ഏത് നിർജ്ജീവ ഇടവേളയും വിഭജനത്തിന് കാരണമാകും.", + "data": { + "min_gap_seconds": "വിഭജന ഇടവേള പരിധി (സെക്കൻഡ്)" + } + }, + "editor_split_manual_params": { + "title": "മാനുവൽ വിഭജന ടൈംസ്റ്റാമ്പുകൾ", + "description": "{preview_md}സൈക്കിൾ വിൻഡോ: **{cycle_start_wallclock} → {cycle_end_wallclock}** {cycle_date}-ന്.\n\nഒരു അല്ലെങ്കിൽ അതിലധികം വിഭജന ടൈംസ്റ്റാമ്പുകൾ (`HH:MM` അല്ലെങ്കിൽ `HH:MM:SS`) നൽകുക, ഓരോ വരിയിലും ഒന്ന് വീതം. ഓരോ ടൈംസ്റ്റാമ്പിലും സൈക്കിൾ മുറിക്കപ്പെടും — N ടൈംസ്റ്റാമ്പുകൾ N+1 സെഗ്മെന്റുകൾ സൃഷ്ടിക്കും.", + "data": { + "split_timestamps": "വിഭജന ടൈംസ്റ്റാമ്പുകൾ" + } + }, + "reprocess_history": { + "title": "മെയിൻ്റനൻസ്: റീപ്രോസസ് & ഡാറ്റ ഒപ്റ്റിമൈസ്", + "description": "ചരിത്രപരമായ ഡാറ്റയുടെ ആഴത്തിലുള്ള വൃത്തിയാക്കൽ നടത്തുന്നു:\n1. സീറോ-പവർ റീഡിംഗുകൾ (നിശബ്ദത) ട്രിം ചെയ്യുന്നു.\n2. സൈക്കിൾ സമയം/ദൈർഘ്യം പരിഹരിക്കുന്നു.\n3. എല്ലാ സ്റ്റാറ്റിസ്റ്റിക്കൽ മോഡലുകളും (എൻവലപ്പുകൾ) വീണ്ടും കണക്കാക്കുന്നു.\n\n**ഡാറ്റ പ്രശ്നങ്ങൾ പരിഹരിക്കുന്നതിനോ പുതിയ അൽഗോരിതങ്ങൾ പ്രയോഗിക്കുന്നതിനോ ഇത് പ്രവർത്തിപ്പിക്കുക.**" + }, + "wipe_history": { + "title": "ചരിത്രം മായ്‌ക്കുക (ടെസ്റ്റിംഗ് മാത്രം)", + "description": "⚠️ ഇത് ഈ ഉപകരണത്തിനായി സംഭരിച്ചിരിക്കുന്ന എല്ലാ സൈക്കിളുകളും പ്രൊഫൈലുകളും ശാശ്വതമായി ഇല്ലാതാക്കും. ഇത് പഴയപടിയാക്കാനാകില്ല!" + }, + "export_import": { + "title": "JSON കയറ്റുമതി/ഇറക്കുമതി ചെയ്യുക", + "description": "ഈ ഉപകരണത്തിനായുള്ള സൈക്കിളുകൾ, പ്രൊഫൈലുകൾ, ക്രമീകരണങ്ങൾ എന്നിവ പകർത്തുക/ഒട്ടിക്കുക. ഇറക്കുമതി ചെയ്യാൻ താഴെ കയറ്റുമതി ചെയ്യുക അല്ലെങ്കിൽ JSON ഒട്ടിക്കുക.", + "data": { + "mode": "ആക്ഷൻ", + "json_payload": "പൂർണ്ണമായ കോൺഫിഗറേഷൻ JSON" + }, + "data_description": { + "mode": "നിലവിലെ ഡാറ്റയും ക്രമീകരണങ്ങളും പകർത്താൻ എക്‌സ്‌പോർട്ട് തിരഞ്ഞെടുക്കുക അല്ലെങ്കിൽ മറ്റൊരു WashData ഉപകരണത്തിൽ നിന്ന് എക്‌സ്‌പോർട്ട് ചെയ്‌ത ഡാറ്റ ഒട്ടിക്കാൻ ഇറക്കുമതി ചെയ്യുക.", + "json_payload": "കയറ്റുമതിക്കായി: ഈ JSON പകർത്തുക (സൈക്കിളുകളും പ്രൊഫൈലുകളും എല്ലാ മികച്ച ക്രമീകരണങ്ങളും ഉൾപ്പെടുന്നു). ഇറക്കുമതി ചെയ്യുന്നതിന്: WashData-ൽ നിന്ന് കയറ്റുമതി ചെയ്ത JSON ഒട്ടിക്കുക." + } + }, + "record_cycle": { + "title": "റെക്കോർഡ് സൈക്കിൾ", + "description": "തടസ്സങ്ങളില്ലാതെ ക്ലീൻ പ്രൊഫൈൽ സൃഷ്‌ടിക്കാൻ ഒരു സൈക്കിൾ സ്വമേധയാ റെക്കോർഡ് ചെയ്യുക.\n\nനില: **{status}**\nദൈർഘ്യം: {duration}സെ\nസാമ്പിളുകൾ: {samples}", + "menu_options": { + "record_refresh": "സ്റ്റാറ്റസ് പുതുക്കുക", + "record_stop": "റെക്കോർഡിംഗ് നിർത്തുക (സംരക്ഷിച്ച് പ്രോസസ്സ് ചെയ്യുക)", + "record_start": "പുതിയ റെക്കോർഡിംഗ് ആരംഭിക്കുക", + "record_process": "അവസാന റെക്കോർഡിംഗ് പ്രോസസ്സ് ചെയ്യുക (ട്രിം & സേവ്)", + "record_discard": "അവസാന റെക്കോർഡിംഗ് നിരസിക്കുക", + "menu_back": "← മടങ്ങുക" + } + }, + "record_process": { + "title": "പ്രോസസ്സ് റെക്കോർഡിംഗ്", + "description": "![ഗ്രാഫ്]({graph_url})\n\n**ഗ്രാഫ് റോ റെക്കോർഡിംഗ് കാണിക്കുന്നു.** നീല = സൂക്ഷിക്കുക, ചുവപ്പ് = നിർദ്ദേശിച്ച ട്രിം.\n*ഗ്രാഫ് സ്റ്റാറ്റിക് ആണ്, ഫോം മാറ്റങ്ങളോടെ അപ്ഡേറ്റ് ചെയ്യുന്നില്ല.*\n\nറെക്കോർഡിംഗ് നിർത്തി. കണ്ടെത്തിയ സാംപ്ലിംഗ് നിരക്കിന് (~{sampling_rate}സെ) ട്രിമ്മുകൾ വിന്യസിച്ചിരിക്കുന്നു.\n\nഅസംസ്കൃത ദൈർഘ്യം: {duration}സെ\nസാമ്പിളുകൾ: {samples}", + "data": { + "head_trim": "ട്രിം സ്റ്റാർട്ട് (സെക്കൻഡ്)", + "tail_trim": "ട്രിം എൻഡ് (സെക്കൻഡ്)", + "save_mode": "ലക്ഷ്യസ്ഥാനം സംരക്ഷിക്കുക", + "profile_name": "പ്രൊഫൈൽ പേര്" + } + }, + "learning_feedbacks": { + "title": "ഫീഡ്‌ബാക്കുകൾ പഠിക്കുന്നു", + "description": "തീർപ്പാക്കാത്ത ഫീഡ്‌ബാക്കുകൾ: {count}\n\n{pending_table}\n\nപ്രോസസ്സ് ചെയ്യാൻ ഒരു സൈക്കിൾ അവലോകന അഭ്യർത്ഥന തിരഞ്ഞെടുക്കുക.", + "menu_options": { + "learning_feedbacks_pick": "തീർപ്പാക്കാത്ത ഒരു ഫീഡ്ബാക്ക് അവലോകനം ചെയ്യുക", + "learning_feedbacks_dismiss_all": "തീർപ്പാക്കാത്ത എല്ലാ ഫീഡ്ബാക്കുകളും തള്ളിക്കളയുക", + "menu_back": "← മടങ്ങുക" + } + }, + "learning_feedbacks_pick": { + "title": "അവലോകനത്തിനായി ഫീഡ്ബാക്ക് തിരഞ്ഞെടുക്കുക", + "description": "തുറക്കാൻ ഒരു സൈക്കിൾ അവലോകന അഭ്യർത്ഥന തിരഞ്ഞെടുക്കുക.", + "data": { + "selected_feedback": "അവലോകനത്തിനായി സൈക്കിൾ തിരഞ്ഞെടുക്കുക" + } + }, + "learning_feedbacks_empty": { + "title": "ഫീഡ്‌ബാക്കുകൾ പഠിക്കുന്നു", + "description": "തീർപ്പാക്കാത്ത ഫീഡ്‌ബാക്ക് അഭ്യർത്ഥനകളൊന്നും കണ്ടെത്തിയില്ല.", + "menu_options": { + "menu_back": "← മടങ്ങുക" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "കാത്തിരിക്കുന്ന എല്ലാ ഫീഡ്ബാക്കുകളും തള്ളിക്കളയുക", + "description": "നിങ്ങൾ **{count}** എണ്ണം കാത്തിരിക്കുന്ന ഫീഡ്ബാക്ക് അഭ്യർത്ഥനകൾ തള്ളിക്കളയാനിരിക്കുകയാണ്. തള്ളിയ സൈക്കിളുകൾ നിങ്ങളുടെ ചരിത്രത്തിൽ തുടരും, പക്ഷേ പുതിയ പഠന സിഗ്നലിലേക്ക് സംഭാവന ചെയ്യില്ല. ഇത് തിരിച്ചു കൊണ്ടുവരാനാകില്ല.\n\n✅ എല്ലാം തള്ളാൻ താഴെയുള്ള ബോക്‌സ് പരിശോധിച്ച് **സമർപ്പിക്കുക** ക്ലിക്ക് ചെയ്യുക.\n❌ അത് പരിശോധിക്കാതെ വിട്ട് റദ്ദാക്കി തിരികെ പോകാൻ **സമർപ്പിക്കുക** ക്ലിക്ക് ചെയ്യുക.", + "data": { + "confirm_dismiss_all": "സ്ഥിരീകരിക്കുക: തീർപ്പാക്കാത്ത എല്ലാ ഫീഡ്ബാക്ക് അഭ്യർത്ഥനകളും തള്ളിക്കളയുക" + } + }, + "resolve_feedback": { + "title": "ഫീഡ്ബാക്ക് പരിഹരിക്കുക", + "description": "{comparison_img}{candidates_table}\n**കണ്ടെത്തിയ പ്രൊഫൈൽ**: {detected_profile} ({confidence_pct}%)\n**കണക്കാക്കിയ ദൈർഘ്യം**: {est_duration_min} മിനിറ്റ്\n**യഥാർത്ഥ ദൈർഘ്യം**: {act_duration_min} മിനിറ്റ്\n\n__ചുവടെ ഒരു പ്രവർത്തനം തിരഞ്ഞെടുക്കുക:__", + "data": { + "action": "നിങ്ങൾ എന്താണ് ചെയ്യാൻ ആഗ്രഹിക്കുന്നത്?", + "corrected_profile": "ശരിയായ പ്രോഗ്രാം (തിരുത്തുകയാണെങ്കിൽ)", + "corrected_duration": "സെക്കൻഡുകളിൽ ശരിയായ ദൈർഘ്യം (തിരുത്തുകയാണെങ്കിൽ)" + } + }, + "trim_cycle_select": { + "title": "ട്രിം സൈക്കിൾ - സൈക്കിൾ തിരഞ്ഞെടുക്കുക", + "description": "ട്രിം ചെയ്യാൻ ഒരു സൈക്കിൾ തിരഞ്ഞെടുക്കുക. ✓ = പൂർത്തിയായി, ⚠ = പുനരാരംഭിച്ചു, ✗ = തടസ്സപ്പെട്ടു", + "data": { + "cycle_id": "സൈക്കിൾ" + } + }, + "trim_cycle": { + "title": "ട്രിം സൈക്കിൾ", + "description": "നിലവിലെ വിൻഡോ: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "ആക്ഷൻ" + } + }, + "trim_cycle_start": { + "title": "ട്രിം ആരംഭം സജ്ജമാക്കുക", + "description": "സൈക്കിൾ വിൻഡോ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nനിലവിലെ തുടക്കം: **{current_wallclock}** (സൈക്കിൾ ആരംഭത്തിൽ നിന്ന് +{current_offset_min} മിനിറ്റ്)\n\nചുവടെയുള്ള ക്ലോക്ക് പിക്കർ ഉപയോഗിച്ച് ഒരു പുതിയ ആരംഭ സമയം തിരഞ്ഞെടുക്കുക.", + "data": { + "trim_start_time": "പുതിയ ആരംഭ സമയം", + "trim_start_min": "പുതിയ തുടക്കം (സൈക്കിൾ ആരംഭിച്ച് മിനിറ്റുകൾ)" + } + }, + "trim_cycle_end": { + "title": "ട്രിം എൻഡ് സജ്ജീകരിക്കുക", + "description": "സൈക്കിൾ വിൻഡോ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nനിലവിലെ അവസാനം: **{current_wallclock}** (സൈക്കിൾ ആരംഭത്തിൽ നിന്ന് +{current_offset_min} മിനിറ്റ്)\n\nചുവടെയുള്ള ക്ലോക്ക് പിക്കർ ഉപയോഗിച്ച് ഒരു പുതിയ അവസാന സമയം തിരഞ്ഞെടുക്കുക.", + "data": { + "trim_end_time": "പുതിയ അവസാന സമയം", + "trim_end_min": "പുതിയ അവസാനം (സൈക്കിൾ ആരംഭിച്ച് മിനിറ്റുകൾ)" + } + } + }, + "error": { + "import_failed": "ഇറക്കുമതി പരാജയപ്പെട്ടു. ഒരു സാധുവായ WashData എക്സ്പോർട്ട് JSON ഒട്ടിക്കുക.", + "select_exactly_one": "ഈ പ്രവർത്തനത്തിനായി കൃത്യമായി ഒരു സൈക്കിൾ തിരഞ്ഞെടുക്കുക.", + "select_at_least_one": "ഈ പ്രവർത്തനത്തിനായി കുറഞ്ഞത് ഒരു സൈക്കിൾ എങ്കിലും തിരഞ്ഞെടുക്കുക.", + "select_at_least_two": "ഈ പ്രവർത്തനത്തിനായി കുറഞ്ഞത് രണ്ട് സൈക്കിളുകൾ എങ്കിലും തിരഞ്ഞെടുക്കുക.", + "profile_exists": "ഈ പേരിലുള്ള ഒരു പ്രൊഫൈൽ ഇതിനകം നിലവിലുണ്ട്", + "rename_failed": "പ്രൊഫൈലിൻ്റെ പേരുമാറ്റുന്നതിൽ പരാജയപ്പെട്ടു. പ്രൊഫൈൽ നിലവിലില്ലായിരിക്കാം അല്ലെങ്കിൽ പുതിയ പേര് ഇതിനകം എടുത്തതാണ്.", + "assignment_failed": "സൈക്കിളിലേക്ക് പ്രൊഫൈൽ അസൈൻ ചെയ്യുന്നതിൽ പരാജയപ്പെട്ടു", + "duplicate_phase": "ഈ പേരിൽ ഒരു ഘട്ടം ഇതിനകം നിലവിലുണ്ട്.", + "phase_not_found": "തിരഞ്ഞെടുത്ത ഘട്ടം കണ്ടെത്തിയില്ല.", + "invalid_phase_name": "ഘട്ടത്തിൻ്റെ പേര് ശൂന്യമാക്കാൻ കഴിയില്ല.", + "phase_name_too_long": "ഘട്ടത്തിൻ്റെ പേര് വളരെ വലുതാണ്.", + "invalid_phase_range": "ഘട്ട ശ്രേണി അസാധുവാണ്. തുടക്കത്തേക്കാൾ വലുതായിരിക്കണം അവസാനം.", + "invalid_phase_timestamp": "ടൈംസ്റ്റാമ്പ് അസാധുവാണ്. YYYY-MM-DD HH:MM അല്ലെങ്കിൽ HH:MM ഫോർമാറ്റ് ഉപയോഗിക്കുക.", + "overlapping_phase_ranges": "ഘട്ട ശ്രേണികൾ ഓവർലാപ്പ് ചെയ്യുന്നു. ശ്രേണികൾ ക്രമീകരിച്ച് വീണ്ടും ശ്രമിക്കുക.", + "incomplete_phase_row": "തിരഞ്ഞെടുത്ത മോഡിനായി ഓരോ ഘട്ടം വരിയിലും ഒരു ഘട്ടവും സമ്പൂർണ്ണ ആരംഭ/അവസാന മൂല്യങ്ങളും ഉൾപ്പെടുത്തണം.", + "timestamp_mode_cycle_required": "ടൈംസ്റ്റാമ്പ് മോഡ് ഉപയോഗിക്കുമ്പോൾ ഒരു സോഴ്സ് സൈക്കിൾ തിരഞ്ഞെടുക്കുക.", + "no_phase_ranges": "ആ പ്രവർത്തനത്തിന് ഇതുവരെ ഘട്ട ശ്രേണികളൊന്നും ലഭ്യമല്ല.", + "feedback_notification_title": "WashData: സൈക്കിൾ പരിശോധിക്കുക ({device})", + "feedback_notification_message": "**ഉപകരണം**: {device}\n**പ്രോഗ്രാം**: {program} ({confidence}% ആത്മവിശ്വാസം)\n**സമയം**: {time}\n\nകണ്ടെത്തിയ ഈ സൈക്കിൾ പരിശോധിക്കാൻ WashData-യ്ക്ക് നിങ്ങളുടെ സഹായം ആവശ്യമാണ്.\n\nഈ ഫലം സ്ഥിരീകരിക്കുന്നതിനോ ശരിയാക്കുന്നതിനോ ദയവായി **ക്രമീകരണങ്ങൾ > ഉപകരണങ്ങളും സേവനങ്ങളും > WashData > കോൺഫിഗർ > പഠന ഫീഡ്ബാക്കുകൾ** എന്നതിലേക്ക് പോകുക.", + "suggestions_ready_notification_title": "WashData: നിർദ്ദേശിച്ച ക്രമീകരണങ്ങൾ തയ്യാറാണ് ({device})", + "suggestions_ready_notification_message": "**നിർദ്ദേശിച്ച ക്രമീകരണങ്ങൾ** സെൻസർ ഇപ്പോൾ **{count}** പ്രവർത്തനക്ഷമമായ ശുപാർശകൾ റിപ്പോർട്ട് ചെയ്യുന്നു.\n\nഅവ അവലോകനം ചെയ്യുന്നതിനും പ്രയോഗിക്കുന്നതിനും: **ക്രമീകരണങ്ങൾ > ഉപകരണങ്ങളും സേവനങ്ങളും > WashData > കോൺഫിഗർ ചെയ്യുക > വിപുലമായ ക്രമീകരണങ്ങൾ > നിർദ്ദേശിച്ച മൂല്യങ്ങൾ പ്രയോഗിക്കുക**.\n\nനിർദ്ദേശങ്ങൾ ഓപ്ഷണൽ ആണ്, നിങ്ങൾ സംരക്ഷിക്കുന്നതിന് മുമ്പ് അവലോകനത്തിനായി കാണിക്കും.", + "trim_range_invalid": "തുടക്കം അവസാനിക്കുന്നതിന് മുമ്പായിരിക്കണം. നിങ്ങളുടെ ട്രിം പോയിൻ്റുകൾ ക്രമീകരിക്കുക.", + "trim_failed": "ട്രിം പരാജയപ്പെട്ടു. സൈക്കിൾ നിലവിലില്ലായിരിക്കാം അല്ലെങ്കിൽ വിൻഡോ ശൂന്യമാണ്.", + "invalid_split_timestamp": "ടൈംസ്റ്റാമ്പ് അസാധുവാണ് അല്ലെങ്കിൽ സൈക്കിളിന്റെ വിൻഡോയ്ക്ക് പുറത്താണ്. സൈക്കിൾ വിൻഡോയ്ക്കുള്ളിലെ HH:MM അല്ലെങ്കിൽ HH:MM:SS ഉപയോഗിക്കുക.", + "no_split_segments_found": "ഈ ടൈംസ്റ്റാമ്പുകളിൽ നിന്ന് സാധുവായ സെഗ്മെന്റുകളൊന്നും സൃഷ്ടിക്കാനായില്ല. ഓരോ ടൈംസ്റ്റാമ്പും സൈക്കിൾ വിൻഡോയ്ക്കുള്ളിലാണെന്നും സെഗ്മെന്റുകൾ കുറഞ്ഞത് 1 മിനിറ്റ് ദൈർഘ്യമുണ്ടെന്നുമുറപ്പാക്കുക.", + "auto_tune_suggestion": "{device_type} {device_title} ഗോസ്റ്റ് സൈക്കിളുകൾ കണ്ടെത്തി. നിർദ്ദേശിച്ച മിനിട്ട്_പവർ മാറ്റം: {current_min}W -> {new_min}W (സ്വയമേവ പ്രയോഗിക്കില്ല).", + "auto_tune_title": "WashData ഓട്ടോ-ട്യൂൺ", + "auto_tune_fallback": "{device_type} {device_title} ഗോസ്റ്റ് സൈക്കിളുകൾ കണ്ടെത്തി. നിർദ്ദേശിച്ച മിനിട്ട്_പവർ മാറ്റം: {current_min}W -> {new_min}W (സ്വയമേവ പ്രയോഗിക്കില്ല)." + }, + "abort": { + "no_cycles_found": "സൈക്കിളുകളൊന്നും കണ്ടെത്തിയില്ല", + "no_split_segments_found": "തിരഞ്ഞെടുത്ത സൈക്കിളിൽ വിഭജിക്കാവുന്ന സെഗ്മെന്റുകളൊന്നും കണ്ടെത്തിയില്ല.", + "cycle_not_found": "തിരഞ്ഞെടുത്ത സൈക്കിൾ ലോഡ് ചെയ്യാനായില്ല.", + "no_power_data": "തിരഞ്ഞെടുത്ത സൈക്കിളിന് പവർ ട്രേസ് ഡാറ്റ ലഭ്യമല്ല.", + "no_profiles_found": "പ്രൊഫൈലുകളൊന്നും കണ്ടെത്തിയില്ല. ആദ്യം ഒരു പ്രൊഫൈൽ ഉണ്ടാക്കുക.", + "no_profiles_for_matching": "പൊരുത്തപ്പെടുത്തുന്നതിന് പ്രൊഫൈലുകളൊന്നും ലഭ്യമല്ല. ആദ്യം പ്രൊഫൈലുകൾ സൃഷ്ടിക്കുക.", + "no_unlabeled_cycles": "എല്ലാ സൈക്കിളുകളും ഇതിനകം ലേബൽ ചെയ്തിട്ടുണ്ട്", + "no_suggestions": "നിർദ്ദേശിച്ച മൂല്യങ്ങളൊന്നും ഇതുവരെ ലഭ്യമല്ല. കുറച്ച് സൈക്കിളുകൾ പ്രവർത്തിപ്പിച്ച് വീണ്ടും ശ്രമിക്കുക.", + "no_predictions": "പ്രവചനങ്ങളൊന്നുമില്ല", + "no_custom_phases": "ഇഷ്‌ടാനുസൃത ഘട്ടങ്ങളൊന്നും കണ്ടെത്തിയില്ല.", + "reprocess_success": "{count} സൈക്കിളുകൾ വീണ്ടും പ്രോസസ്സ് ചെയ്തു.", + "debug_data_cleared": "{count} സൈക്കിളുകളിൽ നിന്ന് ഡീബഗ് ഡാറ്റ മായ്‌ച്ചു." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "സൈക്കിൾ ആരംഭം", + "cycle_finish": "സൈക്കിൾ ഫിനിഷ്", + "cycle_live": "തത്സമയ പുരോഗതി" + } + }, + "common_text": { + "options": { + "unlabeled": "(ലേബൽ ചെയ്യാത്തത്)", + "create_new_profile": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "remove_label": "ലേബൽ നീക്കം ചെയ്യുക", + "no_reference_cycle": "(റഫറൻസ് സൈക്കിൾ ഇല്ല)", + "all_device_types": "എല്ലാ ഉപകരണ തരങ്ങളും", + "current_suffix": "(നിലവിലെ)", + "editor_select_info": "വിഭജിക്കാൻ 1 സൈക്കിൾ അല്ലെങ്കിൽ ലയിപ്പിക്കാൻ 2+ സൈക്കിളുകൾ തിരഞ്ഞെടുക്കുക.", + "editor_select_info_split": "വിഭജിക്കാൻ 1 സൈക്കിൾ തിരഞ്ഞെടുക്കുക.", + "editor_select_info_merge": "ലയിപ്പിക്കാൻ 2+ സൈക്കിളുകൾ തിരഞ്ഞെടുക്കുക.", + "editor_select_info_delete": "ഇല്ലാതാക്കാൻ 1+ സൈക്കിൾ(കൾ) തിരഞ്ഞെടുക്കുക.", + "no_cycles_recorded": "ഇതുവരെ സൈക്കിളുകളൊന്നും രേഖപ്പെടുത്തിയിട്ടില്ല.", + "profile_summary_line": "- **{name}**: {count} സൈക്കിളുകൾ, {avg}മീറ്റർ ശരാശരി", + "no_profiles_created": "ഇതുവരെ പ്രൊഫൈലുകളൊന്നും സൃഷ്ടിച്ചിട്ടില്ല.", + "phase_preview": "ഘട്ട പ്രിവ്യൂ", + "phase_preview_no_curve": "ശരാശരി പ്രൊഫൈൽ കർവ് ഇതുവരെ ലഭ്യമല്ല. ഈ പ്രൊഫൈലിനായി കൂടുതൽ സൈക്കിളുകൾ പ്രവർത്തിപ്പിക്കുക/ലേബൽ ചെയ്യുക.", + "average_power_curve": "ശരാശരി പവർ കർവ്", + "unit_min": "മിനിറ്റ്", + "profile_option_fmt": "{name} ({count} സൈക്കിളുകൾ, ~{duration}മീറ്റർ ശരാശരി)", + "profile_option_short_fmt": "{name} ({count} സൈക്കിളുകൾ, ~{duration}മി)", + "deleted_cycles_from_profile": "{profile} മുതൽ {count} സൈക്കിളുകൾ ഇല്ലാതാക്കി.", + "not_enough_data_graph": "ഗ്രാഫ് സൃഷ്ടിക്കാൻ മതിയായ ഡാറ്റയില്ല.", + "no_profiles_found": "പ്രൊഫൈലുകളൊന്നും കണ്ടെത്തിയില്ല.", + "cycle_info_fmt": "സൈക്കിൾ: {start}, {duration}മി, നിലവിലെ: {label}", + "top_candidates_header": "### മുൻനിര സ്ഥാനാർത്ഥികൾ", + "tbl_profile": "പ്രൊഫൈൽ", + "tbl_confidence": "ആത്മവിശ്വാസം", + "tbl_correlation": "പരസ്പരബന്ധം", + "tbl_duration_match": "ദൈർഘ്യ മത്സരം", + "detected_profile": "പ്രൊഫൈൽ കണ്ടെത്തി", + "estimated_duration": "കണക്കാക്കിയ ദൈർഘ്യം", + "actual_duration": "യഥാർത്ഥ ദൈർഘ്യം", + "choose_action": "__ചുവടെ ഒരു പ്രവർത്തനം തിരഞ്ഞെടുക്കുക:__", + "tbl_count": "എണ്ണുക", + "tbl_avg": "ശരാശരി", + "tbl_min": "മിനി", + "tbl_max": "പരമാവധി", + "tbl_energy_avg": "ഊർജ്ജം (ശരാശരി)", + "tbl_energy_total": "ഊർജ്ജം (ആകെ)", + "tbl_consistency": "ഉൾക്കൊള്ളുന്നു.", + "tbl_last_run": "അവസാന ഓട്ടം", + "graph_legend_title": "ഗ്രാഫ് ലെജൻഡ്", + "graph_legend_body": "നീല ബാൻഡ് നിരീക്ഷിച്ച ഏറ്റവും കുറഞ്ഞതും കൂടിയതുമായ പവർ ഡ്രോ ശ്രേണിയെ പ്രതിനിധീകരിക്കുന്നു. ലൈൻ ശരാശരി പവർ കർവ് കാണിക്കുന്നു.", + "recording_preview": "റെക്കോർഡിംഗ് പ്രിവ്യൂ", + "trim_start": "ട്രിം സ്റ്റാർട്ട്", + "trim_end": "ട്രിം എൻഡ്", + "split_preview_title": "സ്പ്ലിറ്റ് പ്രിവ്യൂ", + "split_preview_found_fmt": "{count} സെഗ്‌മെൻ്റുകൾ കണ്ടെത്തി.", + "split_preview_confirm_fmt": "ഈ സൈക്കിൾ {count} പ്രത്യേക സൈക്കിളുകളായി വിഭജിക്കാൻ സ്ഥിരീകരിക്കുക ക്ലിക്കുചെയ്യുക.", + "merge_preview_title": "പ്രിവ്യൂ ലയിപ്പിക്കുക", + "merge_preview_joining_fmt": "{count} സൈക്കിളുകളിൽ ചേരുന്നു. വിടവുകൾ 0W റീഡിംഗുകൾ കൊണ്ട് നിറയും.", + "editor_delete_preview_title": "ഇല്ലാതാക്കൽ മുൻകാഴ്ച", + "editor_delete_preview_intro": "തിരഞ്ഞെടുത്ത സൈക്കിളുകൾ സ്ഥിരമായി ഇല്ലാതാക്കപ്പെടും:", + "editor_delete_preview_confirm": "ഈ സൈക്കിൾ രേഖകൾ സ്ഥിരമായി ഇല്ലാതാക്കാൻ സ്ഥിരീകരിക്കുക ക്ലിക്ക് ചെയ്യുക.", + "no_power_preview": "*പ്രിവ്യൂവിന് പവർ ഡാറ്റയൊന്നും ലഭ്യമല്ല.*", + "profile_comparison": "പ്രൊഫൈൽ താരതമ്യം", + "actual_cycle_label": "ഈ ചക്രം (യഥാർത്ഥം)", + "storage_usage_header": "സംഭരണ ​​ഉപയോഗം", + "storage_file_size": "ഫയൽ വലിപ്പം", + "storage_cycles": "സൈക്കിളുകൾ", + "storage_profiles": "പ്രൊഫൈലുകൾ", + "storage_debug_traces": "ഡീബഗ് ട്രെയ്സ്", + "overview_suffix": "അവലോകനം", + "phase_preview_for_profile": "പ്രൊഫൈലിനായുള്ള ഘട്ട പ്രിവ്യൂ", + "current_ranges_header": "നിലവിലെ ശ്രേണികൾ", + "assign_phases_help": "ശ്രേണികൾ ചേർക്കാനോ എഡിറ്റ് ചെയ്യാനോ ഇല്ലാതാക്കാനോ സംരക്ഷിക്കാനോ ചുവടെയുള്ള പ്രവർത്തനങ്ങൾ ഉപയോഗിക്കുക.", + "profile_summary_header": "പ്രൊഫൈൽ സംഗ്രഹം", + "recent_cycles_header": "സമീപകാല ചക്രങ്ങൾ", + "trim_cycle_preview_title": "സൈക്കിൾ ട്രിം പ്രിവ്യൂ", + "trim_cycle_preview_no_data": "ഈ സൈക്കിളിന് പവർ ഡാറ്റയൊന്നും ലഭ്യമല്ല.", + "trim_cycle_preview_kept_suffix": "സൂക്ഷിച്ചു", + "table_program": "പ്രോഗ്രാം", + "table_when": "എപ്പോൾ", + "table_length": "ദൈർഘ്യം", + "table_match": "പൊരുത്തം", + "table_profile": "പ്രൊഫൈൽ", + "table_cycles": "സൈക്കിളുകൾ", + "table_avg_length": "ശരാശരി ദൈർഘ്യം", + "table_last_run": "അവസാന ഓട്ടം", + "table_avg_energy": "ശരാശരി ഊർജം", + "table_detected_program": "കണ്ടെത്തിയ പ്രോഗ്രാം", + "table_confidence": "ആത്മവിശ്വാസം", + "table_reported": "റിപ്പോർട്ട് ചെയ്തത്", + "phase_builtin_suffix": "(ബിൽറ്റ്-ഇൻ)", + "phase_none_available": "ഘട്ടങ്ങളൊന്നും ലഭ്യമല്ല.", + "settings_deprecation_warning": "⚠️ **ഒഴിവാക്കപ്പെട്ട ഉപകരണ തരം:** {device_type} എന്നത് ഭാവിയിലെ റിലീസിൽ നീക്കം ചെയ്യുന്നതിനായി ഷെഡ്യൂൾ ചെയ്‌തിരിക്കുന്നു. WashData-യുടെ മാച്ചിംഗ് പൈപ്പ്‌ലൈൻ ഈ അപ്ലയൻസ് ക്ലാസിന് വിശ്വസനീയമായ ഫലങ്ങൾ നൽകുന്നില്ല. ഒഴിവാക്കൽ കാലയളവിൽ നിങ്ങളുടെ സംയോജനം പ്രവർത്തിക്കുന്നു; ഈ മുന്നറിയിപ്പ് നിശബ്‌ദമാക്കാൻ, പിന്തുണയ്‌ക്കുന്ന തരങ്ങളിലൊന്നിലേക്ക് (വാഷിംഗ് മെഷീൻ, ഡ്രയർ, വാഷർ-ഡ്രയർ കോംബോ, ഡിഷ്‌വാഷർ, എയർ ഫ്രയർ, ബ്രെഡ് മേക്കർ, അല്ലെങ്കിൽ പമ്പ്) അല്ലെങ്കിൽ നിങ്ങളുടെ ഉപകരണം പിന്തുണയ്‌ക്കുന്ന തരങ്ങളുമായി പൊരുത്തപ്പെടുന്നില്ലെങ്കിൽ **ഉപകരണ തരം** മാറ്റുക. **മറ്റ് (വിപുലമായത്)** മനഃപൂർവം ഏതെങ്കിലും പ്രത്യേക ഉപകരണത്തിനായി ട്യൂൺ ചെയ്യാത്ത ജനറിക് ഡിഫോൾട്ടുകൾ അയയ്ക്കുന്നു, അതിനാൽ നിങ്ങൾ പരിധികൾ, സമയപരിധികൾ, പൊരുത്തപ്പെടുന്ന പാരാമീറ്ററുകൾ എന്നിവ സ്വയം ക്രമീകരിക്കേണ്ടതുണ്ട്; നിങ്ങൾ മാറുമ്പോൾ നിലവിലുള്ള എല്ലാ ക്രമീകരണങ്ങളും സംരക്ഷിക്കപ്പെടും.", + "phase_other_device_types": "മറ്റ് ഉപകരണ തരങ്ങൾ:" + } + }, + "interactive_editor_action": { + "options": { + "split": "ഒരു സൈക്കിൾ വിഭജിക്കുക (വിടവുകൾ കണ്ടെത്തുക)", + "merge": "സൈക്കിളുകൾ ലയിപ്പിക്കുക (ശകലങ്ങൾ കൂട്ടിച്ചേർക്കുക)", + "delete": "സൈക്കിൾ(കൾ) ഇല്ലാതാക്കുക" + } + }, + "split_mode": { + "options": { + "auto": "ഐഡിൽ ഗ്യാപ്പുകൾ സ്വയമേവ കണ്ടെത്തുക", + "manual": "മാനുവൽ ടൈംസ്റ്റാമ്പ്(കൾ)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "മെയിൻ്റനൻസ്: റീപ്രോസസ് & ഡാറ്റ ഒപ്റ്റിമൈസ്", + "clear_debug_data": "ഡീബഗ് ഡാറ്റ മായ്‌ക്കുക (ഇടം ശൂന്യമാക്കുക)", + "wipe_history": "ഈ ഉപകരണത്തിനായുള്ള എല്ലാ ഡാറ്റയും മായ്‌ക്കുക (മാറ്റാനാകാത്തത്)", + "export_import": "ക്രമീകരണങ്ങൾ ഉപയോഗിച്ച് JSON കയറ്റുമതി/ഇറക്കുമതി ചെയ്യുക (പകർത്തുക/ഒട്ടിക്കുക)" + } + }, + "export_import_mode": { + "options": { + "export": "എല്ലാ ഡാറ്റയും കയറ്റുമതി ചെയ്യുക", + "import": "ഡാറ്റ ഇറക്കുമതി ചെയ്യുക/ലയിപ്പിക്കുക" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "പഴയ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക", + "select_cycle_to_label": "നിർദ്ദിഷ്ട സൈക്കിൾ ലേബൽ ചെയ്യുക", + "select_cycle_to_delete": "സൈക്കിൾ ഇല്ലാതാക്കുക", + "interactive_editor": "ഇൻ്ററാക്ടീവ് എഡിറ്റർ ലയിപ്പിക്കുക/വിഭജിക്കുക", + "trim_cycle": "സൈക്കിൾ ഡാറ്റ ട്രിം ചെയ്യുക" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "edit_profile": "പ്രൊഫൈൽ എഡിറ്റ് ചെയ്യുക/പേരുമാറ്റുക", + "delete_profile": "പ്രൊഫൈൽ ഇല്ലാതാക്കുക", + "profile_stats": "പ്രൊഫൈൽ സ്ഥിതിവിവരക്കണക്കുകൾ", + "cleanup_profile": "ചരിത്രം വൃത്തിയാക്കുക - ഗ്രാഫ് & ഇല്ലാതാക്കുക", + "assign_phases": "ഘട്ടം ശ്രേണികൾ നിയോഗിക്കുക" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "പുതിയ ഘട്ടം സൃഷ്ടിക്കുക", + "edit_custom_phase": "എഡിറ്റ് ഘട്ടം", + "delete_custom_phase": "ഘട്ടം ഇല്ലാതാക്കുക" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "ഘട്ട ശ്രേണി ചേർക്കുക", + "edit_range": "ഘട്ട ശ്രേണി എഡിറ്റ് ചെയ്യുക", + "delete_range": "ഘട്ട ശ്രേണി ഇല്ലാതാക്കുക", + "clear_ranges": "എല്ലാ ശ്രേണികളും മായ്‌ക്കുക", + "auto_detect_ranges": "ഘട്ടങ്ങൾ സ്വയം കണ്ടെത്തുക", + "save_ranges": "സംരക്ഷിച്ച് മടങ്ങുക" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "സ്റ്റാറ്റസ് പുതുക്കുക", + "stop_recording": "റെക്കോർഡിംഗ് നിർത്തുക (സംരക്ഷിച്ച് പ്രോസസ്സ് ചെയ്യുക)", + "start_recording": "പുതിയ റെക്കോർഡിംഗ് ആരംഭിക്കുക", + "process_recording": "അവസാന റെക്കോർഡിംഗ് പ്രോസസ്സ് ചെയ്യുക (ട്രിം & സേവ്)", + "discard_recording": "അവസാന റെക്കോർഡിംഗ് നിരസിക്കുക" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "existing_profile": "നിലവിലുള്ള പ്രൊഫൈലിലേക്ക് ചേർക്കുക", + "discard": "റെക്കോർഡിംഗ് നിരസിക്കുക" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "സ്ഥിരീകരിക്കുക - ശരിയായ കണ്ടെത്തൽ", + "correct": "ശരി - പ്രോഗ്രാം/ദൈർഘ്യം തിരഞ്ഞെടുക്കുക", + "ignore": "അവഗണിക്കുക - തെറ്റായ പോസിറ്റീവ്/ശബ്ദ ചക്രം", + "delete": "ഇല്ലാതാക്കുക - ചരിത്രത്തിൽ നിന്ന് നീക്കം ചെയ്യുക" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "ഘട്ടങ്ങൾക്ക് പേര് നൽകുകയും പ്രയോഗിക്കുകയും ചെയ്യുക", + "cancel": "മാറ്റങ്ങളില്ലാതെ മടങ്ങുക" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "ആരംഭ പോയിൻ്റ് സജ്ജമാക്കുക", + "set_end": "എൻഡ് പോയിൻ്റ് സജ്ജമാക്കുക", + "reset": "പൂർണ്ണ ദൈർഘ്യത്തിലേക്ക് പുനഃസജ്ജമാക്കുക", + "apply": "ട്രിം പ്രയോഗിക്കുക", + "cancel": "റദ്ദാക്കുക" + } + }, + "device_type": { + "options": { + "washing_machine": "വാഷിംഗ് മെഷീൻ", + "dryer": "ഡ്രയർ", + "washer_dryer": "വാഷർ-ഡ്രയർ കോംബോ", + "dishwasher": "ഡിഷ്വാഷർ", + "coffee_machine": "കോഫി മെഷീൻ (ഒഴിവാക്കിയത്)", + "ev": "ഇലക്ട്രിക് വാഹനം (ഒഴിവാക്കിയത്)", + "air_fryer": "എയർ ഫ്രയർ", + "heat_pump": "ഹീറ്റ് പമ്പ് (ഒഴിവാക്കിയത്)", + "bread_maker": "ബ്രെഡ് മേക്കർ", + "pump": "പമ്പ് / സംമ്പ് പമ്പ്", + "oven": "ഓവൻ (ഒഴിവാക്കിയത്)", + "other": "മറ്റുള്ളവ (വിപുലമായത്)" + } + } + }, + "services": { + "label_cycle": { + "name": "ലേബൽ സൈക്കിൾ", + "description": "നിലവിലുള്ള ഒരു പ്രൊഫൈൽ കഴിഞ്ഞ സൈക്കിളിലേക്ക് അസൈൻ ചെയ്യുക അല്ലെങ്കിൽ ലേബൽ നീക്കം ചെയ്യുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "ലേബൽ ചെയ്യാനുള്ള വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "cycle_id": { + "name": "സൈക്കിൾ ഐഡി", + "description": "ലേബൽ ചെയ്യേണ്ട സൈക്കിളിൻ്റെ ഐഡി." + }, + "profile_name": { + "name": "പ്രൊഫൈൽ പേര്", + "description": "നിലവിലുള്ള ഒരു പ്രൊഫൈലിൻ്റെ പേര് (മാനേജ് പ്രൊഫൈലുകൾ മെനുവിൽ പ്രൊഫൈലുകൾ സൃഷ്ടിക്കുക). ലേബൽ നീക്കം ചെയ്യാൻ ശൂന്യമായി വിടുക." + } + } + }, + "create_profile": { + "name": "പ്രൊഫൈൽ സൃഷ്ടിക്കുക", + "description": "ഒരു പുതിയ പ്രൊഫൈൽ സൃഷ്ടിക്കുക (സ്വന്തമായി അല്ലെങ്കിൽ ഒരു റഫറൻസ് സൈക്കിൾ അടിസ്ഥാനമാക്കി).", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "profile_name": { + "name": "പ്രൊഫൈൽ പേര്", + "description": "പുതിയ പ്രൊഫൈലിൻ്റെ പേര് (ഉദാ. 'ഹെവി ഡ്യൂട്ടി', 'ഡെലിക്കേറ്റ്സ്')." + }, + "reference_cycle_id": { + "name": "റഫറൻസ് സൈക്കിൾ ഐഡി", + "description": "ഈ പ്രൊഫൈൽ അടിസ്ഥാനമാക്കിയുള്ള ഓപ്ഷണൽ സൈക്കിൾ ഐഡി." + } + } + }, + "delete_profile": { + "name": "പ്രൊഫൈൽ ഇല്ലാതാക്കുക", + "description": "ഒരു പ്രൊഫൈൽ ഇല്ലാതാക്കി അത് ഉപയോഗിച്ച് സൈക്കിളുകൾ ഓപ്ഷണലായി അൺലേബൽ ചെയ്യുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "profile_name": { + "name": "പ്രൊഫൈൽ പേര്", + "description": "ഡിലീറ്റ് ചെയ്യേണ്ട പ്രൊഫൈൽ." + }, + "unlabel_cycles": { + "name": "സൈക്കിളുകൾ ലേബൽ ചെയ്യുക", + "description": "ഈ പ്രൊഫൈൽ ഉപയോഗിച്ച് സൈക്കിളുകളിൽ നിന്ന് പ്രൊഫൈൽ ലേബൽ നീക്കം ചെയ്യുക." + } + } + }, + "auto_label_cycles": { + "name": "പഴയ സൈക്കിളുകൾ സ്വയമേവ ലേബൽ ചെയ്യുക", + "description": "പ്രൊഫൈൽ പൊരുത്തപ്പെടുത്തൽ ഉപയോഗിച്ച് ലേബൽ ചെയ്യാത്ത സൈക്കിളുകൾ മുൻകാല ലേബൽ ചെയ്യുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "confidence_threshold": { + "name": "ആത്മവിശ്വാസത്തിൻ്റെ പരിധി", + "description": "ലേബലുകൾ പ്രയോഗിക്കുന്നതിനുള്ള ഏറ്റവും കുറഞ്ഞ പൊരുത്ത ആത്മവിശ്വാസം (0.50-0.95)." + } + } + }, + "export_config": { + "name": "കോൺഫിഗറേഷൻ കയറ്റുമതി ചെയ്യുക", + "description": "ഈ ഉപകരണത്തിൻ്റെ പ്രൊഫൈലുകൾ, സൈക്കിളുകൾ, ക്രമീകരണങ്ങൾ എന്നിവ ഒരു JSON ഫയലിലേക്ക് എക്‌സ്‌പോർട്ടുചെയ്യുക (ഓരോ ഉപകരണത്തിനും).", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "കയറ്റുമതി ചെയ്യാനുള്ള വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "path": { + "name": "പാത", + "description": "എഴുതാനുള്ള ഓപ്ഷണൽ സമ്പൂർണ്ണ ഫയൽ പാത്ത് (/config/ha_washdata_export_{entry}.json-ലേക്കുള്ള ഡിഫോൾട്ടുകൾ)." + } + } + }, + "import_config": { + "name": "കോൺഫിഗറേഷൻ ഇറക്കുമതി ചെയ്യുക", + "description": "ഒരു JSON എക്‌സ്‌പോർട്ട് ഫയലിൽ നിന്ന് ഈ ഉപകരണത്തിനായുള്ള പ്രൊഫൈലുകൾ, സൈക്കിളുകൾ, ക്രമീകരണങ്ങൾ എന്നിവ ഇറക്കുമതി ചെയ്യുക (ക്രമീകരണങ്ങൾ തിരുത്തിയെഴുതപ്പെട്ടേക്കാം).", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "ഇറക്കുമതി ചെയ്യാനുള്ള വാഷിംഗ് മെഷീൻ ഉപകരണം." + }, + "path": { + "name": "പാത", + "description": "ഇറക്കുമതി ചെയ്യുന്നതിനുള്ള കയറ്റുമതി JSON ഫയലിലേക്കുള്ള സമ്പൂർണ്ണ പാത." + } + } + }, + "submit_cycle_feedback": { + "name": "സൈക്കിൾ ഫീഡ്ബാക്ക് സമർപ്പിക്കുക", + "description": "പൂർത്തിയാക്കിയ സൈക്കിളിന് ശേഷം യാന്ത്രികമായി കണ്ടെത്തിയ പ്രോഗ്രാം സ്ഥിരീകരിക്കുക അല്ലെങ്കിൽ ശരിയാക്കുക. ഒന്നുകിൽ `എൻട്രി_ഐഡി` (വിപുലമായത്) അല്ലെങ്കിൽ `ഡിവൈസ്_ഐഡി` (ശുപാർശചെയ്യുന്നത്) നൽകുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "WashData ഉപകരണം (entry_id-ന് പകരം ശുപാർശ ചെയ്യുന്നത്)." + }, + "entry_id": { + "name": "എൻട്രി ഐഡി", + "description": "ഉപകരണത്തിനായുള്ള കോൺഫിഗറേഷൻ എൻട്രി ഐഡി (device_id-ന് പകരമായി)." + }, + "cycle_id": { + "name": "സൈക്കിൾ ഐഡി", + "description": "ഫീഡ്‌ബാക്ക് അറിയിപ്പിൽ / ലോഗുകളിൽ കാണിച്ചിരിക്കുന്ന സൈക്കിൾ ഐഡി." + }, + "user_confirmed": { + "name": "കണ്ടെത്തിയ പ്രോഗ്രാം സ്ഥിരീകരിക്കുക", + "description": "കണ്ടെത്തിയ പ്രോഗ്രാം ശരിയാണെങ്കിൽ ട്രൂ സെറ്റ് ചെയ്യുക." + }, + "corrected_profile": { + "name": "തിരുത്തിയ പ്രൊഫൈൽ", + "description": "സ്ഥിരീകരിച്ചിട്ടില്ലെങ്കിൽ, ശരിയായ പ്രൊഫൈൽ/പ്രോഗ്രാമിൻ്റെ പേര് നൽകുക." + }, + "corrected_duration": { + "name": "തിരുത്തിയ ദൈർഘ്യം (സെക്കൻഡ്)", + "description": "നിമിഷങ്ങൾക്കുള്ളിൽ ഓപ്ഷണൽ തിരുത്തിയ ദൈർഘ്യം." + }, + "notes": { + "name": "കുറിപ്പുകൾ", + "description": "ഈ സൈക്കിളിനെക്കുറിച്ചുള്ള ഓപ്ഷണൽ കുറിപ്പുകൾ." + } + } + }, + "record_start": { + "name": "റെക്കോർഡ് സൈക്കിൾ ആരംഭം", + "description": "ഒരു ക്ലീൻ സൈക്കിൾ സ്വമേധയാ റെക്കോർഡ് ചെയ്യാൻ ആരംഭിക്കുക (പൊരുത്തപ്പെടുന്ന എല്ലാ ലോജിക്കും മറികടക്കുന്നു).", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "റെക്കോർഡ് ചെയ്യാനുള്ള വാഷിംഗ് മെഷീൻ ഉപകരണം." + } + } + }, + "record_stop": { + "name": "സൈക്കിൾ സ്റ്റോപ്പ് രേഖപ്പെടുത്തുക", + "description": "മാനുവൽ റെക്കോർഡിംഗ് നിർത്തുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "റെക്കോർഡിംഗ് നിർത്താനുള്ള വാഷിംഗ് മെഷീൻ ഉപകരണം." + } + } + }, + "trim_cycle": { + "name": "ട്രിം സൈക്കിൾ", + "description": "കഴിഞ്ഞ സൈക്കിളിൻ്റെ പവർ ഡാറ്റ ഒരു നിർദ്ദിഷ്ട സമയ വിൻഡോയിലേക്ക് ട്രിം ചെയ്യുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "WashData ഉപകരണം." + }, + "cycle_id": { + "name": "സൈക്കിൾ ഐഡി", + "description": "ട്രിം ചെയ്യേണ്ട സൈക്കിളിൻ്റെ ഐഡി." + }, + "trim_start_s": { + "name": "ട്രിം സ്റ്റാർട്ട് (സെക്കൻഡ്)", + "description": "ഈ ഓഫ്‌സെറ്റിൽ നിന്നുള്ള ഡാറ്റ നിമിഷങ്ങൾക്കുള്ളിൽ സൂക്ഷിക്കുക. സ്ഥിരസ്ഥിതി 0." + }, + "trim_end_s": { + "name": "ട്രിം എൻഡ് (സെക്കൻഡ്)", + "description": "സെക്കൻ്റുകൾക്കുള്ളിൽ ഈ ഓഫ്‌സെറ്റ് വരെ ഡാറ്റ നിലനിർത്തുക. ഡീഫോൾറ്റ് = മുഴുവൻ ദൈർഘ്യം." + } + } + }, + "pause_cycle": { + "name": "സൈക്കിൾ താൽക്കാലികമായി നിർത്തുക", + "description": "ഒരു WashData ഉപകരണത്തിനായി സജീവമായ സൈക്കിൾ താൽക്കാലികമായി നിർത്തുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "WashData ഉപകരണം താൽക്കാലികമായി നിർത്താൻ." + } + } + }, + "resume_cycle": { + "name": "സൈക്കിൾ പുനരാരംഭിക്കുക", + "description": "WashData ഉപകരണത്തിനായി താൽക്കാലികമായി നിർത്തിയ സൈക്കിൾ പുനരാരംഭിക്കുക.", + "fields": { + "device_id": { + "name": "ഉപകരണം", + "description": "WashData ഉപകരണം പുനരാരംഭിക്കും." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "ഓടുന്നു" + }, + "match_ambiguity": { + "name": "പൊരുത്തം അവ്യക്തത" + } + }, + "sensor": { + "washer_state": { + "name": "സംസ്ഥാനം", + "state": { + "off": "ഓഫ്", + "idle": "നിഷ്ക്രിയ", + "starting": "ആരംഭിക്കുന്നു", + "running": "ഓടുന്നു", + "paused": "താൽക്കാലികമായി നിർത്തി", + "user_paused": "ഉപയോക്താവ് താൽക്കാലികമായി നിർത്തി", + "ending": "അവസാനിക്കുന്നു", + "finished": "തീർന്നു", + "anti_wrinkle": "വിരുദ്ധ ചുളിവുകൾ", + "delay_wait": "ആരംഭിക്കാൻ കാത്തിരിക്കുന്നു", + "interrupted": "തടസ്സപ്പെട്ടു", + "force_stopped": "ബലപ്രയോഗം നിർത്തി", + "rinse": "കഴുകിക്കളയുക", + "unknown": "അജ്ഞാതം", + "clean": "വൃത്തിയാക്കുക" + } + }, + "washer_program": { + "name": "പ്രോഗ്രാം" + }, + "time_remaining": { + "name": "ശേഷിക്കുന്ന സമയം" + }, + "total_duration": { + "name": "ആകെ ദൈർഘ്യം" + }, + "cycle_progress": { + "name": "പുരോഗതി" + }, + "current_power": { + "name": "നിലവിലെ ശക്തി" + }, + "elapsed_time": { + "name": "കടന്നുപോയ സമയം" + }, + "debug_info": { + "name": "ഡീബഗ് വിവരം" + }, + "match_confidence": { + "name": "പൊരുത്തം ആത്മവിശ്വാസം" + }, + "top_candidates": { + "name": "മുൻനിര സ്ഥാനാർത്ഥികൾ", + "state": { + "none": "ഒന്നുമില്ല" + } + }, + "current_phase": { + "name": "നിലവിലെ ഘട്ടം" + }, + "wash_phase": { + "name": "ഘട്ടം" + }, + "profile_cycle_count": { + "name": "പ്രൊഫൈൽ {profile_name} എണ്ണം", + "unit_of_measurement": "ചക്രങ്ങൾ" + }, + "suggestions": { + "name": "നിർദ്ദേശിച്ച ക്രമീകരണങ്ങൾ" + }, + "pump_runs_today": { + "name": "പമ്പ് റണ്ണുകൾ (അവസാന 24 മണിക്കൂർ)" + }, + "cycle_count": { + "name": "സൈക്കിൾ എണ്ണം" + } + }, + "select": { + "program_select": { + "name": "സൈക്കിൾ പ്രോഗ്രാം", + "state": { + "auto_detect": "സ്വയമേവ കണ്ടെത്തുക" + } + } + }, + "button": { + "force_end_cycle": { + "name": "ഫോഴ്സ് എൻഡ് സൈക്കിൾ" + }, + "pause_cycle": { + "name": "സൈക്കിൾ താൽക്കാലികമായി നിർത്തുക" + }, + "resume_cycle": { + "name": "സൈക്കിൾ പുനരാരംഭിക്കുക" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "സാധുവായ ഒരു device_id ആവശ്യമാണ്." + }, + "cycle_id_required": { + "message": "സാധുവായ സൈക്കിൾ_ഐഡി ആവശ്യമാണ്." + }, + "profile_name_required": { + "message": "ഒരു സാധുവായ profile_name ആവശ്യമാണ്." + }, + "device_not_found": { + "message": "ഉപകരണം കണ്ടെത്തിയില്ല." + }, + "no_config_entry": { + "message": "ഉപകരണത്തിനായി കോൺഫിഗറേഷൻ എൻട്രി കണ്ടെത്തിയില്ല." + }, + "integration_not_loaded": { + "message": "ഈ ഉപകരണത്തിന് ഇൻ്റഗ്രേഷൻ ലോഡ് ചെയ്തിട്ടില്ല." + }, + "cycle_not_found_or_no_power": { + "message": "സൈക്കിൾ കണ്ടെത്തിയില്ല അല്ലെങ്കിൽ പവർ ഡാറ്റ ഇല്ല." + }, + "trim_failed_empty_window": { + "message": "ട്രിം പരാജയപ്പെട്ടു - സൈക്കിൾ കണ്ടെത്തിയില്ല, പവർ ഡാറ്റയില്ല, അല്ലെങ്കിൽ തത്ഫലമായുണ്ടാകുന്ന വിൻഡോ ശൂന്യമാണ്." + }, + "trim_invalid_range": { + "message": "trim_end_s trim_start_s-നേക്കാൾ വലുതായിരിക്കണം." + } + } +} diff --git a/custom_components/ha_washdata/translations/nb.json b/custom_components/ha_washdata/translations/nb.json new file mode 100644 index 0000000..796ded1 --- /dev/null +++ b/custom_components/ha_washdata/translations/nb.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-oppsett", + "description": "Konfigurer vaskemaskinen eller annet apparat.\n\nStrømsensor er nødvendig.\n\n**Deretter vil du bli spurt om du vil opprette din første profil.**", + "data": { + "name": "Enhetsnavn", + "device_type": "Enhetstype", + "power_sensor": "Strømsensor", + "min_power": "Minimum effektterskel (W)" + }, + "data_description": { + "name": "Et vennlig navn for denne enheten (f.eks. \"Vaskemaskin\", \"Oppvaskmaskin\").", + "device_type": "Hva slags apparat er dette? Hjelper med å skreddersy deteksjon og merking.", + "power_sensor": "Sensorenheten som rapporterer strømforbruk i sanntid (i watt) fra smartpluggen din.", + "min_power": "Effektavlesninger over denne terskelen (i watt) indikerer at apparatet er i gang. Start med 2W for de fleste enheter." + } + }, + "first_profile": { + "title": "Opprett første profil", + "description": "En **Syklus** er en komplett kjøring av apparatet ditt (f.eks. en mengde klesvask). En **Profil** grupperer disse syklusene etter type (f.eks. \"Bomull\", \"Hurtigvask\"). Oppretting av en profil hjelper nå integrasjonen å estimere varigheten umiddelbart.\n\nDu kan velge denne profilen manuelt i enhetskontrollene hvis den ikke oppdages automatisk.\n\n**La «Profilnavn» stå tomt for å hoppe over dette trinnet.**", + "data": { + "profile_name": "Profilnavn (valgfritt)", + "manual_duration": "Estimert varighet (minutter)" + } + } + }, + "error": { + "cannot_connect": "Kunne ikke koble til", + "invalid_auth": "Ugyldig autentisering", + "unknown": "Uventet feil" + }, + "abort": { + "already_configured": "Enheten er allerede konfigurert" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Gjennomgå lærings-tilbakemeldinger", + "settings": "Innstillinger", + "notifications": "Varsler", + "manage_cycles": "Administrer sykluser", + "manage_profiles": "Administrer profiler", + "manage_phase_catalog": "Administrer fasekatalog", + "record_cycle": "Opptakssyklus (manuell)", + "diagnostics": "Diagnostikk og vedlikehold" + } + }, + "apply_suggestions": { + "title": "Kopier foreslåtte verdier", + "description": "Dette vil kopiere de gjeldende foreslåtte verdiene til dine lagrede innstillinger. Dette er en engangshandling og aktiverer ikke automatisk overskriving.\n\nForeslåtte verdier å kopiere:\n{suggested}", + "data": { + "confirm": "Bekreft kopihandling" + } + }, + "apply_suggestions_confirm": { + "title": "Gjennomgå foreslåtte endringer", + "description": "WashData funnet **{pending_count}** foreslåtte verdi(er) å bruke.\n\nEndringer:\n{changes}\n\n✅ Merk av i boksen nedenfor og klikk på **Send** for å bruke og lagre disse endringene umiddelbart.\n❌ La det være umerket og klikk på **Send** for å avbryte og gå tilbake.", + "data": { + "confirm_apply_suggestions": "Bruk og lagre disse endringene" + } + }, + "settings": { + "title": "Innstillinger", + "description": "{deprecation_warning}Juster deteksjonsterskler, læring og avansert atferd. Standarder fungerer bra for de fleste oppsett.\n\nForeslåtte innstillinger tilgjengelig nå: {suggestions_count}.\nHvis dette er over 0, åpner du avanserte innstillinger og bruker 'Bruk foreslåtte verdier' for å gå gjennom anbefalingene.", + "data": { + "apply_suggestions": "Bruk foreslåtte verdier", + "device_type": "Enhetstype", + "power_sensor": "Strømsensorenhet", + "min_power": "Minimum effekt (W)", + "off_delay": "Syklussluttforsinkelse (r)", + "notify_actions": "Varslingshandlinger", + "notify_people": "Utsett levering til disse menneskene er hjemme", + "notify_only_when_home": "Utsett varsler til valgt person er hjemme", + "notify_fire_events": "Brannautomatiseringshendelser", + "notify_start_services": "Syklusstart - varslingsmål", + "notify_finish_services": "Cycle Finish - Varslingsmål", + "notify_live_services": "Live Progress - Varslingsmål", + "notify_before_end_minutes": "Varsling før fullføring (minutter før slutt)", + "notify_title": "Meldingstittel", + "notify_icon": "Varslingsikon", + "notify_start_message": "Start meldingsformat", + "notify_finish_message": "Fullfør meldingsformat", + "notify_pre_complete_message": "Meldingsformat før fullføring", + "show_advanced": "Rediger avanserte innstillinger (neste trinn)" + }, + "data_description": { + "apply_suggestions": "Kryss av i denne boksen for å kopiere verdier foreslått av læringsalgoritmen til skjemaet. Se gjennom før du lagrer.\n\nForeslått (fra læring):\n- min_effekt: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Velg type apparat (vaskemaskin, tørketrommel, oppvaskmaskin, kaffemaskin, elbil). Denne taggen lagres med hver syklus for bedre organisering.", + "power_sensor": "Sensorenheten rapporterer sanntidseffekt (i watt). Du kan endre dette når som helst uten å miste historiske data – nyttig hvis du bytter ut en smartplugg.", + "min_power": "Effektavlesninger over denne terskelen (i watt) indikerer at apparatet er i gang. Start med 2W for de fleste enheter.", + "off_delay": "Sekunder må den utjevnede effekten holde seg under stoppterskelen for å markere fullføring. Standard: 120s.", + "notify_actions": "Valgfri Home Assistant-handlingssekvens for å kjøre for varsler (skript, varsling, TTS, etc.). Hvis angitt, kjøres handlinger før reservevarslingstjenesten.", + "notify_people": "Personenheter som brukes til tilstedeværelsesport. Når aktivert, forsinkes varslingsleveringen til minst én valgt person er hjemme.", + "notify_only_when_home": "Hvis aktivert, utsett varsler til minst én valgt person er hjemme.", + "notify_fire_events": "Hvis aktivert, utløs Home Assistant-hendelser for syklusstart og -slutt (ha_washdata_cycle_started og ha_washdata_cycle_ended) for å drive automatisering.", + "notify_start_services": "En eller flere varslingstjenester for å varsle når en syklus begynner. La stå tomt for å hoppe over startvarsler.", + "notify_finish_services": "En eller flere varslingstjenester for å varsle når en syklus avsluttes eller nærmer seg fullføring. La stå tomt for å hoppe over ferdigvarsler.", + "notify_live_services": "En eller flere varslingstjenester for å motta live fremdriftsoppdateringer mens syklusen kjører (kun mobilappen). La stå tomt for å hoppe over live-oppdateringer.", + "notify_before_end_minutes": "Minutter før beregnet slutt på syklusen for å utløse et varsel. Sett til 0 for å deaktivere. Standard: 0.", + "notify_title": "Egendefinert tittel for varsler. Støtter {device} plassholder.", + "notify_icon": "Ikon som skal brukes for varsler (f.eks. mdi:vaskemaskin). La stå tomt for å sende uten ikon.", + "notify_start_message": "Melding sendt når en syklus starter. Støtter {device} plassholder.", + "notify_finish_message": "Melding sendt når en syklus er ferdig. Støtter {device}, {duration}, {program}, {energy_kwh}, {cost} plassholdere.", + "notify_pre_complete_message": "Tekst til gjentakende fremdriftsoppdateringer mens syklusen kjører. Støtter {device}, {minutes}, {program} plassholdere." + } + }, + "notifications": { + "title": "Varsler", + "description": "Konfigurer varslingskanaler, maler og valgfrie live-fremdriftsoppdateringer for mobil.", + "data": { + "notify_actions": "Varslingshandlinger", + "notify_people": "Utsett levering til disse menneskene er hjemme", + "notify_only_when_home": "Utsett varsler til valgt person er hjemme", + "notify_fire_events": "Brannautomatiseringshendelser", + "notify_start_services": "Syklusstart - varslingsmål", + "notify_finish_services": "Cycle Finish - Varslingsmål", + "notify_live_services": "Live Progress - Varslingsmål", + "notify_before_end_minutes": "Varsling før fullføring (minutter før slutt)", + "notify_live_interval_seconds": "Live Update Interval (sekunder)", + "notify_live_overrun_percent": "Live Update Overrun Allowance (%)", + "notify_live_chronometer": "Kronometer nedtellingstimer", + "notify_title": "Meldingstittel", + "notify_icon": "Varslingsikon", + "notify_start_message": "Start meldingsformat", + "notify_finish_message": "Fullfør meldingsformat", + "notify_pre_complete_message": "Meldingsformat før fullføring", + "energy_price_entity": "Energipris – enhet (valgfritt)", + "energy_price_static": "Energipris – Statisk verdi (valgfritt)", + "go_back": "Gå tilbake uten å lagre", + "notify_reminder_message": "Format for påminnelsesmelding", + "notify_timeout_seconds": "Avvis automatisk etter (sekunder)", + "notify_channel": "Varslingskanal (status/live/påminnelse)", + "notify_finish_channel": "Fullført varslingskanal" + }, + "data_description": { + "go_back": "Kryss av her og klikk på Send inn for å forkaste eventuelle endringer ovenfor og gå tilbake til hovedmenyen.", + "notify_actions": "Valgfri Home Assistant-handlingssekvens for å kjøre for varsler (skript, varsling, TTS, etc.). Hvis angitt, kjøres handlinger før reservevarslingstjenesten.", + "notify_people": "Personenheter som brukes til tilstedeværelsesport. Når aktivert, forsinkes varslingsleveringen til minst én valgt person er hjemme.", + "notify_only_when_home": "Hvis aktivert, utsett varsler til minst én valgt person er hjemme.", + "notify_fire_events": "Hvis aktivert, utløs Home Assistant-hendelser for syklusstart og -slutt (ha_washdata_cycle_started og ha_washdata_cycle_ended) for å drive automatisering.", + "notify_start_services": "En eller flere varslingstjenester for å varsle når en syklus begynner (f.eks. notify.mobile_app_my_phone). La stå tomt for å hoppe over startvarsler.", + "notify_finish_services": "En eller flere varslingstjenester for å varsle når en syklus avsluttes eller nærmer seg fullføring. La stå tomt for å hoppe over ferdigvarsler.", + "notify_live_services": "En eller flere varslingstjenester for å motta live fremdriftsoppdateringer mens syklusen kjører (kun mobilappen). La stå tomt for å hoppe over live-oppdateringer.", + "notify_before_end_minutes": "Minutter før beregnet slutt på syklusen for å utløse et varsel. Sett til 0 for å deaktivere. Standard: 0.", + "notify_live_interval_seconds": "Hvor ofte fremdriftsoppdateringer sendes mens du kjører. Lavere verdier er mer responsive, men bruker flere varsler. Minimum 30 sekunder håndheves – verdier under 30 s rundes automatisk av opp til 30 s.", + "notify_live_overrun_percent": "Sikkerhetsmargin over det estimerte oppdateringsantallet for lange/overløpende sykluser (for eksempel tillater 20 % 1,2x estimerte oppdateringer).", + "notify_live_chronometer": "Når den er aktivert, inkluderer hver liveoppdatering en kronometernedtelling til estimert sluttid. Varslingstidtakeren tikker automatisk ned på enheten mellom oppdateringer (kun Android-appen).", + "notify_title": "Egendefinert tittel for varsler. Støtter {device} plassholder.", + "notify_icon": "Ikon som skal brukes for varsler (f.eks. mdi:vaskemaskin). La stå tomt for å sende uten ikon.", + "notify_start_message": "Melding sendt når en syklus starter. Støtter {device} plassholder.", + "notify_finish_message": "Melding sendt når en syklus er ferdig. Støtter {device}, {duration}, {program}, {energy_kwh}, {cost} plassholdere.", + "energy_price_entity": "Velg en numerisk enhet som har gjeldende strømpris (f.eks. sensor.elektrisitetspris, input_number.energy_rate). Aktiverer plassholderen {cost} når den er angitt. Har forrang over den statiske verdien hvis begge er konfigurert.", + "energy_price_static": "Fast strømpris per kWh. Aktiverer plassholderen {cost} når den er angitt. Ignorert hvis en enhet er konfigurert ovenfor. Legg til ditt valutasymbol i meldingsmalen, f.eks. {cost} €.", + "notify_reminder_message": "Teksten til engangspåminnelsen sendte det konfigurerte antallet minutter før beregnet slutt. Forskjellig fra de gjentatte liveoppdateringene ovenfor. Støtter {device}, {minutes}, {program} plassholdere.", + "notify_timeout_seconds": "Avvis automatisk varslinger etter så mange sekunder (videresendt som «timeout» for følgeappen). Sett til 0 for å beholde dem til de avvises. Standard: 0.", + "notify_channel": "Android varslingskanal for følgeapper for status, fremdrift og påminnelsesvarsler. Lyden og viktigheten til en kanal konfigureres i følgeappen første gang kanalnavnet brukes - WashData angir kun kanalnavnet. La stå tomt for appens standard.", + "notify_finish_channel": "Separat Android-kanal for det ferdige varselet (brukes også av påminnelsen og tøyet som venter), slik at det kan ha sin egen lyd. La stå tomt for å gjenbruke kanalen ovenfor.", + "notify_pre_complete_message": "Tekst til gjentakende fremdriftsoppdateringer mens syklusen kjører. Støtter {device}, {minutes}, {program} plassholdere." + } + }, + "advanced_settings": { + "title": "Avanserte innstillinger", + "description": "Juster deteksjonsterskler, læring og avansert atferd. Standarder fungerer bra for de fleste oppsett.", + "sections": { + "suggestions_section": { + "name": "Foreslåtte innstillinger", + "description": "{suggestions_count} innlærte forslag tilgjengelig. Kryss av for Bruk foreslåtte verdier for å gjennomgå foreslåtte endringer før lagring.", + "data": { + "apply_suggestions": "Bruk foreslåtte verdier" + }, + "data_description": { + "apply_suggestions": "Merk av i denne boksen for å åpne et gjennomgangstrinn for foreslåtte verdier fra læringsalgoritmen. Ingenting lagres automatisk.\n\nForeslått (fra læring):\n- min_effekt: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Deteksjon og effektgrenser", + "description": "Når apparatet regnes som PÅ eller AV, energigrenser og tidsstyring for tilstandsoverganger.", + "data": { + "start_duration_threshold": "Start avvisningsvarighet (r)", + "start_energy_threshold": "Start Energy Gate (Wh)", + "completion_min_seconds": "Fullføring Minimum kjøretid (sekunder)", + "start_threshold_w": "Startterskel (W)", + "stop_threshold_w": "Stoppterskel (W)", + "end_energy_threshold": "End Energy Gate (Wh)", + "running_dead_zone": "Running Dead Zone (sekunder)", + "end_repeat_count": "Avslutt gjentakelse", + "min_off_gap": "Minste gap mellom sykluser (r)", + "sampling_interval": "Samplingsintervall (er)" + }, + "data_description": { + "start_duration_threshold": "Ignorer korte krafttopper som er kortere enn denne varigheten (sekunder). Filtrerer ut støveltopper (f.eks. 60W i 2 sekunder) før den virkelige syklusen starter. Standard: 5s.", + "start_energy_threshold": "Syklusen må akkumulere minst så mye energi (Wh) under START-fasen for å bekreftes. Standard: 0,005 Wh.", + "completion_min_seconds": "Sykluser som er kortere enn dette vil bli merket som \"avbrutt\" selv om de avsluttes naturlig. Standard: 600s.", + "start_threshold_w": "Strømmen må stige OVER denne terskelen for å STARTE en syklus. Still inn høyere enn standby-styrken. Standard: min_power + 1 W.", + "stop_threshold_w": "Strømmen må falle UNDER denne terskelen for å AVSLUTE en syklus. Sett like over null for å unngå støy som utløser falske ender. Standard: 0,6 * min_power.", + "end_energy_threshold": "Syklusen avsluttes bare hvis energien i det siste Av-forsinkelse-vinduet er under denne verdien (Wh). Forhindrer falske slutter hvis effekten svinger nær null. Standard: 0,05 Wh.", + "running_dead_zone": "Etter at syklusen starter, ignorer strømfall i så mange sekunder for å forhindre falsk sluttdeteksjon under den første spin-up-fasen. Sett til 0 for å deaktivere. Standard: 0s.", + "end_repeat_count": "Antall ganger sluttbetingelsen (effekt under terskelen for off_delay) må oppfylles fortløpende før syklusen avsluttes. Høyere verdier forhindrer falske slutter under pauser. Standard: 1.", + "min_off_gap": "Hvis en syklus starter innen så mange sekunder etter at den forrige slutter, vil de bli slått sammen til en enkelt syklus.", + "sampling_interval": "Minimum prøvetakingsintervall i sekunder. Oppdateringer raskere enn denne hastigheten vil bli ignorert. Standard: 30s (2s for vaskemaskiner, kombimaskiner og oppvaskmaskiner)." + } + }, + "matching_section": { + "name": "Profilmatching og læring", + "description": "Hvor aggressivt WashData matcher kjørende sykluser mot innlærte profiler, og når det skal bes om tilbakemelding.", + "data": { + "profile_match_min_duration_ratio": "Min varighetsforhold for profilmatch (0,1–1,0)", + "profile_match_interval": "Profilmatchintervall (sekunder)", + "profile_match_threshold": "Terskel for profilmatch", + "profile_unmatch_threshold": "Profil Unmatch Threshold", + "auto_label_confidence": "Auto-label Confidence (0-1)", + "learning_confidence": "Tilbakemeldingsforespørsel om tillit (0-1)", + "suppress_feedback_notifications": "Undertrykk tilbakemeldingsvarsler", + "duration_tolerance": "Varighetstoleranse (0-0,5)", + "smoothing_window": "Utjevningsvindu (prøver)", + "profile_duration_tolerance": "Toleranse for profilsamsvarsvarighet (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimum varighetsforhold for profiltilpasning. Løpsyklusen må være minst denne brøkdelen av profilvarigheten for å matche. Lavere = tidligere matching. Standard: 0,50 (50 %).", + "profile_match_interval": "Hvor ofte (i sekunder) du skal forsøke profiltilpasning i løpet av en syklus. Lavere verdier gir raskere deteksjon, men bruker mer CPU. Standard: 300s (5 minutter).", + "profile_match_threshold": "Minimum DTW likhetspoeng (0,0–1,0) kreves for å vurdere en profil som en match. Høyere = strengere matching, færre falske positive. Standard: 0,4.", + "profile_unmatch_threshold": "DTW likhetspoeng (0,0–1,0) under som en tidligere matchet profil avvises. Bør være lavere enn matchterskel for å forhindre flimring. Standard: 0,35.", + "auto_label_confidence": "Merk sykluser automatisk ved eller over denne konfidensen ved fullføring (0,0–1,0).", + "learning_confidence": "Minimum tillit for å be om brukerverifisering via en hendelse (0.0–1.0).", + "suppress_feedback_notifications": "Når aktivert, vil WashData fortsatt spore og be om tilbakemelding internt, men vil ikke vise vedvarende varsler som ber deg om å bekrefte sykluser. Nyttig når sykluser alltid oppdages på riktig måte og du synes meldingen er distraherende.", + "duration_tolerance": "Toleranse for gjenværende estimater under en kjøring (0,0–0,5 tilsvarer 0–50 %).", + "smoothing_window": "Antall nylige effektavlesninger brukt for glidende gjennomsnittlig utjevning.", + "profile_duration_tolerance": "Toleranse brukt av profiltilpasning for total varighetsvarians (0,0–0,5). Standard oppførsel: ±25 %." + } + }, + "timing_section": { + "name": "Tidsstyring, vedlikehold og feilsøking", + "description": "Watchdog, tilbakestilling av fremdrift, automatisk vedlikehold og eksponering av feilsøkingsenheter.", + "data": { + "watchdog_interval": "Watchdog-intervall (sekunder)", + "no_update_active_timeout": "Tidsavbrudd uten oppdatering (er)", + "progress_reset_delay": "Forsinkelse for tilbakestilling av fremdrift (sekunder)", + "auto_maintenance": "Aktiver automatisk vedlikehold", + "expose_debug_entities": "Vis feilsøkingsenheter", + "save_debug_traces": "Lagre feilsøkingsspor" + }, + "data_description": { + "watchdog_interval": "Sekunder mellom vakthundkontroller mens du løper. Standard: 5s. ADVARSEL: Sørg for at dette er HØYERE enn sensorens oppdateringsintervall for å unngå falske stopp (f.eks. hvis sensoren oppdateres hver 60. s, bruk 65s+).", + "no_update_active_timeout": "For publiser-ved-endringssensorer: tving kun til å avslutte en AKTIV syklus hvis ingen oppdateringer kommer i løpet av så mange sekunder. Lavstrømsfullføring bruker fortsatt Off Delay.", + "progress_reset_delay": "Etter at en syklus er fullført (100 %), vent så mange sekunder med tomgang før du tilbakestiller fremdriften til 0 %. Standard: 300s.", + "auto_maintenance": "Aktiver automatisk vedlikehold (reparere prøver, slå sammen fragmenter).", + "expose_debug_entities": "Vis avanserte sensorer (Confidence, Phase, Ambiguity) for feilsøking.", + "save_debug_traces": "Lagre detaljert rangering og kraftsporingsdata i historien (øker lagringsbruken)." + } + }, + "anti_wrinkle_section": { + "name": "Antikrøll-beskyttelse (tørketromler)", + "description": "Ignorer trommelrotasjoner etter syklusen for å forhindre spøkelsessykluser. Kun tørketrommel / kombi vask/tørk.", + "data": { + "anti_wrinkle_enabled": "Anti-rynkeskjold (kun tørketrommel)", + "anti_wrinkle_max_power": "Anti-rynke maksimal effekt (W)", + "anti_wrinkle_max_duration": "Maksimal varighet mot rynker (r)", + "anti_wrinkle_exit_power": "Anti-rynke utgangseffekt (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorer korte trommelrotasjoner med lav effekt etter at en syklus er ferdig for å forhindre spøkelsessykluser (bare tørketrommel/vaskemaskin-tørketrommel).", + "anti_wrinkle_max_power": "Utbrudd ved eller under denne kraften behandles som anti-rynke rotasjoner i stedet for nye sykluser. Gjelder bare når Anti-Wrinkle Shield er aktivert.", + "anti_wrinkle_max_duration": "Maksimal sprengningslengde å behandle som anti-rynke rotasjon. Gjelder bare når Anti-Wrinkle Shield er aktivert.", + "anti_wrinkle_exit_power": "Hvis strømmen faller under dette nivået, gå ut av anti-rynke umiddelbart (true off). Gjelder bare når Anti-Wrinkle Shield er aktivert." + } + }, + "delay_start_section": { + "name": "Deteksjon av forsinket start", + "description": "Behandle vedvarende lav-effekt standby som 'Venter på start' til syklusen faktisk begynner.", + "data": { + "delay_start_detect_enabled": "Aktiver forsinket startdeteksjon", + "delay_confirm_seconds": "Forsinket start: bekreftelsestid for standby (s)", + "delay_timeout_hours": "Forsinket start: Maks ventetid (t)" + }, + "data_description": { + "delay_start_detect_enabled": "Når den er aktivert, oppdages en kort dreneringstopp med lav effekt etterfulgt av vedvarende standby-strøm som en forsinket start. Statussensoren viser 'Venter på å starte' under forsinkelsen. Ingen programtid eller fremgang spores før syklusen faktisk begynner. Krever at start_threshold_w settes over dreneringsspikekraften.", + "delay_confirm_seconds": "Effekten må holde seg mellom Stoppterskel (W) og Startterskel (W) i så mange sekunder før WashData går til 'Venter på start'. Filtrerer bort korte topper fra menynavigering. Standard: 60 s.", + "delay_timeout_hours": "Sikkerhetstidsavbrudd: Hvis maskinen fortsatt er i 'Venter på å starte' etter disse mange timene, tilbakestilles WashData til Av. Standard: 8 timer." + } + }, + "external_triggers_section": { + "name": "Eksterne utløsere, dør og pause", + "description": "Valgfri ekstern sensor for syklusslutt, dørsensor for pause-/ren-deteksjon og bryter for å kutte strøm ved pause.", + "data": { + "external_end_trigger_enabled": "Aktiver ekstern syklussluttrigger", + "external_end_trigger": "Ekstern syklusavslutningssensor", + "external_end_trigger_inverted": "Inverter triggerlogikk (trigger på AV)", + "door_sensor_entity": "Dørsensor", + "pause_cuts_power": "Kutt strømmen under pause", + "switch_entity": "Bryterenhet (for Pause Power Cut)", + "notify_unload_delay_minutes": "Forsinkelse for varsel om klesvask (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktiver overvåking av en ekstern binær sensor for å utløse syklusfullføring.", + "external_end_trigger": "Velg en binær sensorenhet. Når denne sensoren utløses, vil gjeldende syklus avsluttes med status \"fullført\".", + "external_end_trigger_inverted": "Som standard utløses utløseren når sensoren slås PÅ. Merk av i denne boksen for å utløses når sensoren slås AV i stedet.", + "door_sensor_entity": "Valgfri binær sensor for maskindøren (på = åpen, av = lukket). Når døren åpnes under en aktiv syklus, vil WashData bekrefte at syklusen er satt på pause. Etter at syklusen er ferdig, hvis døren fortsatt er lukket, endres tilstanden til 'Rengjør' til døren åpnes. Merk: lukking av døren gjenopptar IKKE en midlertidig stoppet syklus – gjenoppta må utløses manuelt via Gjenoppta syklus-knappen eller tjenesten.", + "pause_cuts_power": "Når du setter på pause via pausesyklus-knappen eller tjenesten, må du også slå av den konfigurerte bryterenheten. Trer bare i kraft hvis en bryterenhet er konfigurert for denne enheten.", + "switch_entity": "Bytteenheten som skal veksles når du setter på pause eller gjenopptar. Nødvendig når 'Kutt strøm ved pause' er aktivert.", + "notify_unload_delay_minutes": "Send et varsel via sluttmeldingskanalen etter at syklusen er ferdig og døren ikke har vært åpnet på så mange minutter (krever dørsensor). Sett til 0 for å deaktivere. Standard: 60 min." + } + }, + "pump_section": { + "name": "Pumpeovervåking", + "description": "Kun for enhetstypen pumpe. Utløser en hendelse hvis en pumpesyklus varer lenger enn denne varigheten.", + "data": { + "pump_stuck_duration": "Varselterskel (er) for pumpe fastlåst (kun pumpe)" + }, + "data_description": { + "pump_stuck_duration": "Hvis en pumpesyklus fortsatt kjører etter så mange sekunder, utløses en ha_washdata_pump_stuck-hendelse én gang. Bruk denne til å oppdage en motor som har satt seg fast (typisk pumpekjøring er under 60 s). Standard: 1800 s (30 min). Kun type pumpeenhet." + } + }, + "device_link_section": { + "name": "Enhetskobling", + "description": "Koble eventuelt denne WashData-enheten til en eksisterende Home Assistant-enhet (for eksempel smartpluggen eller selve apparatet). WashData-enheten vises da som \"Tilkoblet via\" den enheten. La det stå tomt for å beholde WashData som en frittstående enhet.", + "data": { + "linked_device": "Koblet enhet" + }, + "data_description": { + "linked_device": "Velg enheten du vil koble WashData til. Dette legger til en 'Tilkoblet via'-referanse i enhetsregisteret; WashData beholder sitt eget enhetskort og enheter. Fjern valget for å fjerne koblingen." + } + } + } + }, + "diagnostics": { + "title": "Diagnostikk og vedlikehold", + "description": "Kjør vedlikeholdshandlinger som å slå sammen fragmenterte sykluser eller migrere lagrede data.\n\n**Lagringsbruk**\n\n- Filstørrelse: {file_size_kb} KB\n- Sykluser: {cycle_count}\n- Profiler: {profile_count}\n- Feilsøkingsspor: {debug_count}", + "menu_options": { + "reprocess_history": "Vedlikehold: Bearbeid og optimaliser data", + "clear_debug_data": "Fjern feilsøkingsdata (frigjør plass)", + "wipe_history": "Tørk ALLE data for denne enheten (irreversibel)", + "export_import": "Eksporter/importer JSON med innstillinger (kopier/lim inn)", + "menu_back": "← Tilbake" + } + }, + "clear_debug_data": { + "title": "Fjern feilsøkingsdata", + "description": "Er du sikker på at du vil slette alle lagrede feilsøkingsspor? Dette vil frigjøre plass, men fjerne detaljert rangeringsinformasjon fra tidligere sykluser." + }, + "manage_cycles": { + "title": "Administrer sykluser", + "description": "Nylige sykluser:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Auto-merk gamle sykluser", + "select_cycle_to_label": "Etikettspesifikk syklus", + "select_cycle_to_delete": "Slett syklus", + "interactive_editor": "Slå sammen/splitt interaktiv editor", + "trim_cycle_select": "Trim syklusdata", + "menu_back": "← Tilbake" + } + }, + "manage_cycles_empty": { + "title": "Administrer sykluser", + "description": "Ingen sykluser registrert ennå.", + "menu_options": { + "auto_label_cycles": "Auto-merk gamle sykluser", + "menu_back": "← Tilbake" + } + }, + "manage_profiles": { + "title": "Administrer profiler", + "description": "Profilsammendrag:\n{profile_summary}", + "menu_options": { + "create_profile": "Opprett ny profil", + "edit_profile": "Rediger/gi nytt navn til profil", + "delete_profile_select": "Slett profil", + "profile_stats": "Profilstatistikk", + "cleanup_profile": "Rydd opp historikk - Graf og slett", + "assign_profile_phases_select": "Tilordne faseområder", + "menu_back": "← Tilbake" + } + }, + "manage_profiles_empty": { + "title": "Administrer profiler", + "description": "Ingen profiler opprettet ennå.", + "menu_options": { + "create_profile": "Opprett ny profil", + "menu_back": "← Tilbake" + } + }, + "manage_phase_catalog": { + "title": "Administrer fasekatalog", + "description": "Fasekatalog (standard for denne enheten + tilpassede faser):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Opprett ny fase", + "phase_catalog_edit_select": "Rediger fase", + "phase_catalog_delete": "Slett fase", + "menu_back": "← Tilbake" + } + }, + "phase_catalog_create": { + "title": "Opprett egendefinert fase", + "description": "Lag et tilpasset fasenavn og beskrivelse, og velg hvilken enhetstype det gjelder.", + "data": { + "target_device_type": "Målenhetstype", + "phase_name": "Fasenavn", + "phase_description": "Fasebeskrivelse" + } + }, + "phase_catalog_edit_select": { + "title": "Rediger egendefinert fase", + "description": "Velg en egendefinert fase å redigere.", + "data": { + "phase_name": "Egendefinert fase" + } + }, + "phase_catalog_edit": { + "title": "Rediger egendefinert fase", + "description": "Redigeringsfase: {phase_name}", + "data": { + "phase_name": "Fasenavn", + "phase_description": "Fasebeskrivelse" + } + }, + "phase_catalog_delete": { + "title": "Slett egendefinert fase", + "description": "Velg en egendefinert fase for å slette. Eventuelle tildelte områder som bruker denne fasen vil bli fjernet.", + "data": { + "phase_name": "Egendefinert fase" + } + }, + "assign_profile_phases": { + "title": "Tilordne faseområder", + "description": "Faseforhåndsvisning for profil: **{profile_name}**\n\n{timeline_svg}\n\n**Gjeldende områder:**\n{current_ranges}\n\nBruk handlingene nedenfor for å legge til, redigere, slette eller lagre områder.", + "menu_options": { + "assign_profile_phases_add": "Legg til faseområde", + "assign_profile_phases_edit_select": "Rediger faseområde", + "assign_profile_phases_delete": "Slett faseområde", + "phase_ranges_clear": "Fjern alle områder", + "assign_profile_phases_auto_detect": "Automatisk oppdage faser", + "phase_ranges_save": "Lagre og returner", + "menu_back": "← Tilbake" + } + }, + "assign_profile_phases_add": { + "title": "Legg til faseområde", + "description": "Legg til ett faseområde til gjeldende utkast.", + "data": { + "phase_name": "Fase", + "start_min": "Startminutt", + "end_min": "Sluttminutt" + } + }, + "assign_profile_phases_edit_select": { + "title": "Rediger faseområde", + "description": "Velg et område du vil redigere.", + "data": { + "range_index": "Faseområde" + } + }, + "assign_profile_phases_edit": { + "title": "Rediger faseområde", + "description": "Oppdater det valgte faseområdet.", + "data": { + "phase_name": "Fase", + "start_min": "Startminutt", + "end_min": "Sluttminutt" + } + }, + "assign_profile_phases_delete": { + "title": "Slett faseområde", + "description": "Velg et område du vil fjerne fra utkastet.", + "data": { + "range_index": "Faseområde" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatisk oppdagede faser", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(r) oppdaget automatisk.** Velg en handling nedenfor.", + "data": { + "action": "Handling" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Navn oppdaget faser", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fase(r) oppdaget:**\n{ranges_summary}\n\nGi hver fase et navn." + }, + "assign_profile_phases_select": { + "title": "Tilordne faseområder", + "description": "Velg en profil for å konfigurere faseområder.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profilstatistikk", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Opprett ny profil", + "description": "Opprett en ny profil manuelt eller fra en tidligere syklus.\n\nEksempler på profilnavn: «Delicates», «Heavy Duty», «Quick Wash»", + "data": { + "profile_name": "Profilnavn", + "reference_cycle": "Referansesyklus (valgfritt)", + "manual_duration": "Manuell varighet (minutter)" + } + }, + "edit_profile": { + "title": "Rediger profil", + "description": "Velg en profil for å gi nytt navn", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Rediger profildetaljer", + "description": "Gjeldende profil: {current_name}", + "data": { + "new_name": "Profilnavn", + "manual_duration": "Manuell varighet (minutter)" + } + }, + "delete_profile_select": { + "title": "Slett profil", + "description": "Velg en profil du vil slette", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Bekreft Slett profil", + "description": "⚠️ Dette sletter profilen permanent: {profile_name}", + "data": { + "unlabel_cycles": "Fjern etiketten fra sykluser med denne profilen" + } + }, + "auto_label_cycles": { + "title": "Auto-merk gamle sykluser", + "description": "Fant {total_count} totale sykluser. Profiler: {profiles}", + "data": { + "confidence_threshold": "Minimum konfidens (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Velg Gå til etikett", + "description": "✓ = fullført, ⚠ = gjenopptatt, ✗ = avbrutt", + "data": { + "cycle_id": "Syklus" + } + }, + "select_cycle_to_delete": { + "title": "Slett syklus", + "description": "⚠️ Dette vil slette den valgte syklusen permanent", + "data": { + "cycle_id": "Bla for å slette" + } + }, + "label_cycle": { + "title": "Etikettsyklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nytt profilnavn (hvis opprettet)" + } + }, + "post_process": { + "title": "Prosesshistorikk (slå sammen/delt)", + "description": "Rydd opp i syklushistorikk ved å slå sammen fragmenterte kjøringer og dele opp feil sammenslåtte sykluser innenfor et tidsvindu. Angi antall timer for å se tilbake (bruk 999999 for alle).", + "data": { + "time_range": "Tilbakeblikk-vindu (timer)", + "gap_seconds": "Slå sammen/delt gap (sekunder)" + }, + "data_description": { + "time_range": "Antall timer å skanne etter fragmenterte sykluser for å slå sammen. Bruk 999999 til å behandle alle historiske data.", + "gap_seconds": "Terskel for å bestemme splitt vs sammenslåing. Mellomrom KORTERE enn dette slås sammen. Mellomrom LENGER enn dette deles. Senk denne for å tvinge frem en splittelse på en syklus som ble slått sammen feil." + } + }, + "cleanup_profile": { + "title": "Rydd opp historikk - Velg profil", + "description": "Velg en profil du vil visualisere. Dette vil generere en graf som viser alle tidligere sykluser for denne profilen for å hjelpe med å identifisere uteliggere.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Rydd opp historikk - Graf og slett", + "description": "![Graph]({graph_url})\n\n**Visualiserer {profile_name}**\n\nGrafen viser individuelle sykluser som fargede linjer. Identifiser uteliggere (linjer langt fra gruppen) og match fargen deres i listen nedenfor for å slette dem.\n\n**Velg sykluser som skal slettes PERMANENT:**", + "data": { + "cycles_to_delete": "Sykluser for å slette (sjekk samsvarende farger)" + } + }, + "interactive_editor": { + "title": "Slå sammen/splitt interaktiv editor", + "description": "Velg en manuell handling som skal utføres på syklushistorikken.", + "menu_options": { + "editor_split": "Del en syklus (finn hull)", + "editor_merge": "Slå sammen sykluser (Sett sammen fragmenter)", + "editor_delete": "Slett syklus(er)", + "menu_back": "← Tilbake" + } + }, + "editor_select": { + "title": "Velg Sykluser", + "description": "Velg syklusen(e) som skal behandles.\n\n{info_text}", + "data": { + "selected_cycles": "Sykluser" + } + }, + "editor_configure": { + "title": "Konfigurer og bruk", + "description": "{preview_md}", + "data": { + "confirm_action": "Handling", + "merged_profile": "Resulterende profil", + "new_profile_name": "Nytt profilnavn", + "confirm_commit": "Ja, jeg bekrefter denne handlingen", + "segment_0_profile": "Segment 1-profil", + "segment_1_profile": "Segment 2-profil", + "segment_2_profile": "Segment 3-profil", + "segment_3_profile": "Segment 4-profil", + "segment_4_profile": "Segment 5-profil", + "segment_5_profile": "Segment 6-profil", + "segment_6_profile": "Segment 7-profil", + "segment_7_profile": "Segment 8-profil", + "segment_8_profile": "Segment 9-profil", + "segment_9_profile": "Segment 10-profil" + } + }, + "editor_split_params": { + "title": "Delt konfigurasjon", + "description": "Juster følsomheten for å dele denne syklusen. Ethvert tomgangsgap lenger enn dette vil føre til en splittelse.", + "data": { + "split_mode": "Delingsmetode" + } + }, + "editor_split_auto_params": { + "title": "Konfigurasjon for automatisk oppdeling", + "description": "Juster følsomheten for å dele denne syklusen. Enhver inaktiv pause som er lengre enn dette vil føre til en oppdeling.", + "data": { + "min_gap_seconds": "Terskel for oppdelingsgap (sekunder)" + } + }, + "editor_split_manual_params": { + "title": "Manuelle oppdelings-tidsstempler", + "description": "{preview_md}Syklusvindu: **{cycle_start_wallclock} → {cycle_end_wallclock}** den {cycle_date}.\n\nSkriv inn ett eller flere oppdelings-tidsstempler (`HH:MM` eller `HH:MM:SS`), ett per linje. Syklusen blir delt ved hvert tidsstempel — N tidsstempler gir N+1 segmenter.", + "data": { + "split_timestamps": "Delingstidsstempler" + } + }, + "reprocess_history": { + "title": "Vedlikehold: Bearbeid og optimaliser data", + "description": "Utfører dyprensing av historiske data:\n1. Trimmer nulleffektavlesninger (stillhet).\n2. Retter syklustiming/-varighet.\n3. Beregner alle statistiske modeller (konvolutter).\n\n**Kjør dette for å fikse dataproblemer eller bruke nye algoritmer.**" + }, + "wipe_history": { + "title": "Tørk historikk (kun testing)", + "description": "⚠️ Dette vil permanent slette ALLE lagrede sykluser og profiler for denne enheten. Dette kan ikke angres!" + }, + "export_import": { + "title": "Eksporter/importer JSON", + "description": "Kopier/lim inn sykluser, profiler OG innstillinger for denne enheten. Eksporter nedenfor eller lim inn JSON for å importere.", + "data": { + "mode": "Handling", + "json_payload": "Fullfør konfigurasjon JSON" + }, + "data_description": { + "mode": "Velg Eksporter for å kopiere gjeldende data og innstillinger, eller Importer for å lime inn eksporterte data fra en annen WashData-enhet.", + "json_payload": "For eksport: kopier denne JSON-en (inkluderer sykluser, profiler og alle finjusterte innstillinger). For import: lim inn JSON eksportert fra WashData." + } + }, + "record_cycle": { + "title": "Opptakssyklus", + "description": "Ta opp en syklus manuelt for å lage en ren profil uten forstyrrelser.\n\nStatus: **{status}**\nVarighet: {duration}s\nEksempler: {samples}", + "menu_options": { + "record_refresh": "Oppdater status", + "record_stop": "Stopp opptak (lagre og behandle)", + "record_start": "Start nytt opptak", + "record_process": "Behandle siste opptak (trim og lagre)", + "record_discard": "Kast siste opptak", + "menu_back": "← Tilbake" + } + }, + "record_process": { + "title": "Prosessopptak", + "description": "![Graph]({graph_url})\n\n**Graf viser råopptak.** Blå = Behold, Rød = Foreslått trim.\n*Graf er statisk og oppdateres ikke med skjemaendringer.*\n\nOpptaket stoppet. Trimmene er justert til den detekterte samplingsfrekvensen (~{sampling_rate}s).\n\nRå varighet: {duration}s\nEksempler: {samples}", + "data": { + "head_trim": "Trimstart (sekunder)", + "tail_trim": "Trim slutt (sekunder)", + "save_mode": "Lagre destinasjon", + "profile_name": "Profilnavn" + } + }, + "learning_feedbacks": { + "title": "Lærings tilbakemeldinger", + "description": "Ventende tilbakemelding: {count}\n\n{pending_table}\n\nVelg en forespørsel om syklusgjennomgang som skal behandles.", + "menu_options": { + "learning_feedbacks_pick": "Gjennomgå ventende tilbakemelding", + "learning_feedbacks_dismiss_all": "Avvis all ventende tilbakemelding", + "menu_back": "← Tilbake" + } + }, + "learning_feedbacks_pick": { + "title": "Velg tilbakemelding for gjennomgang", + "description": "Velg en syklusgjennomgangsforespørsel som skal åpnes.", + "data": { + "selected_feedback": "Velg syklus for gjennomgang" + } + }, + "learning_feedbacks_empty": { + "title": "Lærings tilbakemeldinger", + "description": "Fant ingen ventende tilbakemeldingsforespørsler.", + "menu_options": { + "menu_back": "← Tilbake" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Avvis alle ventende tilbakemeldinger", + "description": "Du er i ferd med å avvise **{count}** ventende tilbakemeldingsforespørsler. Avviste sykluser forblir i historikken din, men vil ikke bidra med nytt læringssignal. Dette kan ikke angres.\n\n✅ Merk av i boksen nedenfor og klikk på **Send** for å avvise alle.\n❌ La den være umerket og klikk på **Send** for å avbryte og gå tilbake.", + "data": { + "confirm_dismiss_all": "Bekreft: avvis alle ventende tilbakemeldingsforespørsler" + } + }, + "resolve_feedback": { + "title": "Løs tilbakemelding", + "description": "{comparison_img}{candidates_table}\n**Oppdaget profil**: {detected_profile} ({confidence_pct}%)\n**Estimert varighet**: {est_duration_min} min\n**Faktisk varighet**: {act_duration_min} min\n\n__Velg en handling nedenfor:__", + "data": { + "action": "Hva vil du gjøre?", + "corrected_profile": "Riktig program (hvis korrigering)", + "corrected_duration": "Riktig varighet i sekunder (hvis korrigering)" + } + }, + "trim_cycle_select": { + "title": "Trim Cycle - Velg Cycle", + "description": "Velg en syklus å trimme. ✓ = fullført, ⚠ = gjenopptatt, ✗ = avbrutt", + "data": { + "cycle_id": "Syklus" + } + }, + "trim_cycle": { + "title": "Trim syklus", + "description": "Gjeldende vindu: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Handling" + } + }, + "trim_cycle_start": { + "title": "Still inn trimstart", + "description": "Syklusvindu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNåværende start: **{current_wallclock}** (+{current_offset_min} min fra syklusstart)\n\nVelg et nytt starttidspunkt ved å bruke klokkevelgeren nedenfor.", + "data": { + "trim_start_time": "Ny starttid", + "trim_start_min": "Ny start (minutter fra syklusstart)" + } + }, + "trim_cycle_end": { + "title": "Sett Trim End", + "description": "Syklusvindu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNåværende slutt: **{current_wallclock}** (+{current_offset_min} min fra syklusstart)\n\nVelg en ny sluttid ved å bruke klokkevelgeren nedenfor.", + "data": { + "trim_end_time": "Ny sluttid", + "trim_end_min": "Ny slutt (minutter fra syklusstart)" + } + } + }, + "error": { + "import_failed": "Import mislyktes. Vennligst lim inn en gyldig WashData-eksport-JSON.", + "select_exactly_one": "Velg nøyaktig én syklus for denne handlingen.", + "select_at_least_one": "Velg minst én syklus for denne handlingen.", + "select_at_least_two": "Velg minst to sykluser for denne handlingen.", + "profile_exists": "En profil med dette navnet eksisterer allerede", + "rename_failed": "Kunne ikke endre navn på profilen. Det kan hende at profilen ikke eksisterer eller et nytt navn er allerede tatt.", + "assignment_failed": "Kunne ikke tilordne profilen til å sykle", + "duplicate_phase": "En fase med dette navnet eksisterer allerede.", + "phase_not_found": "Den valgte fasen ble ikke funnet.", + "invalid_phase_name": "Fasenavnet kan ikke være tomt.", + "phase_name_too_long": "Fasenavnet er for langt.", + "invalid_phase_range": "Faseområdet er ugyldig. Slutten må være større enn starten.", + "invalid_phase_timestamp": "Tidsstempelet er ugyldig. Bruk formatet ÅÅÅÅ-MM-DD TT:MM eller TT:MM.", + "overlapping_phase_ranges": "Faseområder overlapper hverandre. Juster områdene og prøv igjen.", + "incomplete_phase_row": "Hver faserad må inkludere en fase pluss komplette start-/sluttverdier for den valgte modusen.", + "timestamp_mode_cycle_required": "Velg en kildesyklus når du bruker tidsstempelmodus.", + "no_phase_ranges": "Ingen faseområder er tilgjengelige for den handlingen ennå.", + "feedback_notification_title": "WashData: Bekreft syklus ({device})", + "feedback_notification_message": "**Enhet**: {device}\n**Program**: {program} ({confidence} % konfidens)\n**Tid**: {time}\n\nWashData trenger din hjelp for å bekrefte denne oppdagede syklusen.\n\nGå til **Innstillinger > Enheter og tjenester > WashData > Konfigurer > Læretilbakemeldinger** for å bekrefte eller korrigere dette resultatet.", + "suggestions_ready_notification_title": "WashData: Foreslåtte innstillinger klar ({device})", + "suggestions_ready_notification_message": "Sensoren **Foreslåtte innstillinger** rapporterer nå **{count}** nyttige anbefalinger.\n\nSlik ser du og bruker dem: **Innstillinger > Enheter og tjenester > WashData > Konfigurer > Avanserte innstillinger > Bruk foreslåtte verdier**.\n\nForslag er valgfrie og vises for vurdering før du lagrer.", + "trim_range_invalid": "Start må være før slutt. Juster trimpunktene dine.", + "trim_failed": "Trim mislyktes. Syklusen eksisterer kanskje ikke lenger eller vinduet er tomt.", + "invalid_split_timestamp": "Tidsstempelet er ugyldig eller utenfor syklusvinduet. Bruk HH:MM eller HH:MM:SS innenfor syklusvinduet.", + "no_split_segments_found": "Ingen gyldige segmenter kunne opprettes fra disse tidsstemplene. Sørg for at hvert tidsstempel er innenfor syklusvinduet og at segmentene er minst 1 minutt lange.", + "auto_tune_suggestion": "{device_type} {device_title} oppdaget spøkelsessykluser. Foreslått endring av min_effekt: {current_min}W -> {new_min}W (ikke brukt automatisk).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} oppdaget spøkelsessykluser. Foreslått endring av min_effekt: {current_min}W -> {new_min}W (ikke brukt automatisk)." + }, + "abort": { + "no_cycles_found": "Ingen sykluser funnet", + "no_split_segments_found": "Ingen delbare segmenter ble funnet i den valgte syklusen.", + "cycle_not_found": "Den valgte syklusen kunne ikke lastes inn.", + "no_power_data": "Ingen kraftsporingsdata tilgjengelig for den valgte syklusen.", + "no_profiles_found": "Ingen profiler funnet. Opprett en profil først.", + "no_profiles_for_matching": "Ingen profiler tilgjengelig for matching. Opprett profiler først.", + "no_unlabeled_cycles": "Alle sykluser er allerede merket", + "no_suggestions": "Ingen foreslåtte verdier tilgjengelig ennå. Kjør noen sykluser og prøv igjen.", + "no_predictions": "Ingen spådommer", + "no_custom_phases": "Fant ingen egendefinerte faser.", + "reprocess_success": "{count} sykluser er behandlet på nytt.", + "debug_data_cleared": "Fjernet feilsøkingsdata fra {count} sykluser." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Syklusstart", + "cycle_finish": "Syklus ferdig", + "cycle_live": "Live fremgang" + } + }, + "common_text": { + "options": { + "unlabeled": "(Umerket)", + "create_new_profile": "Opprett ny profil", + "remove_label": "Fjern etikett", + "no_reference_cycle": "(Ingen referansesyklus)", + "all_device_types": "Alle enhetstyper", + "current_suffix": "(nåværende)", + "editor_select_info": "Velg 1 syklus for å dele, eller 2+ sykluser for å slå sammen.", + "editor_select_info_split": "Velg 1 syklus som skal deles.", + "editor_select_info_merge": "Velg 2 eller flere sykluser som skal slås sammen.", + "editor_select_info_delete": "Velg 1 eller flere sykluser som skal slettes.", + "no_cycles_recorded": "Ingen sykluser registrert ennå.", + "profile_summary_line": "- **{name}**: {count} sykluser, {avg}m gj.sn", + "no_profiles_created": "Ingen profiler opprettet ennå.", + "phase_preview": "Forhåndsvisning av fase", + "phase_preview_no_curve": "Gjennomsnittlig profilkurve er ikke tilgjengelig ennå. Kjør/merk flere sykluser for denne profilen.", + "average_power_curve": "Gjennomsnittlig kraftkurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} sykluser, ~{duration}m snitt)", + "profile_option_short_fmt": "{name} ({count} sykluser, ~{duration}m)", + "deleted_cycles_from_profile": "Slettet {count} sykluser fra {profile}.", + "not_enough_data_graph": "Ikke nok data til å generere graf.", + "no_profiles_found": "Ingen profiler funnet.", + "cycle_info_fmt": "Syklus: {start}, {duration}m, strøm: {label}", + "top_candidates_header": "### Toppkandidater", + "tbl_profile": "Profil", + "tbl_confidence": "Selvtillit", + "tbl_correlation": "Korrelasjon", + "tbl_duration_match": "Varighet Match", + "detected_profile": "Oppdaget profil", + "estimated_duration": "Estimert varighet", + "actual_duration": "Faktisk varighet", + "choose_action": "__Velg en handling nedenfor:__", + "tbl_count": "Telle", + "tbl_avg": "Gj.sn", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energi (Gj.sn.)", + "tbl_energy_total": "Energi (totalt)", + "tbl_consistency": "Bestå.", + "tbl_last_run": "Siste løp", + "graph_legend_title": "Graflegende", + "graph_legend_body": "Det blå båndet representerer minimum og maksimum strømforbruk som er observert. Linjen viser gjennomsnittlig effektkurve.", + "recording_preview": "Forhåndsvisning av opptak", + "trim_start": "Trim Start", + "trim_end": "Trim ende", + "split_preview_title": "Delt forhåndsvisning", + "split_preview_found_fmt": "Fant {count} segmenter.", + "split_preview_confirm_fmt": "Klikk på Bekreft for å dele denne syklusen i {count} separate sykluser.", + "merge_preview_title": "Slå sammen forhåndsvisning", + "merge_preview_joining_fmt": "Blir med i {count} sykluser. Hullene vil bli fylt med 0W-avlesninger.", + "editor_delete_preview_title": "Forhåndsvisning av sletting", + "editor_delete_preview_intro": "De valgte syklusene blir slettet permanent:", + "editor_delete_preview_confirm": "Klikk på Bekreft for å slette disse sykluspostene permanent.", + "no_power_preview": "*Ingen strømdata tilgjengelig for forhåndsvisning.*", + "profile_comparison": "Profilsammenligning", + "actual_cycle_label": "Denne syklusen (faktisk)", + "storage_usage_header": "Lagringsbruk", + "storage_file_size": "Filstørrelse", + "storage_cycles": "Sykluser", + "storage_profiles": "Profiler", + "storage_debug_traces": "Feilsøk spor", + "overview_suffix": "Oversikt", + "phase_preview_for_profile": "Faseforhåndsvisning for profil", + "current_ranges_header": "Gjeldende områder", + "assign_phases_help": "Bruk handlingene nedenfor for å legge til, redigere, slette eller lagre områder.", + "profile_summary_header": "Profilsammendrag", + "recent_cycles_header": "Nylige sykluser", + "trim_cycle_preview_title": "Forhåndsvisning av syklustrim", + "trim_cycle_preview_no_data": "Ingen strømdata tilgjengelig for denne syklusen.", + "trim_cycle_preview_kept_suffix": "beholdt", + "table_program": "Program", + "table_when": "Når", + "table_length": "Lengde", + "table_match": "Treff", + "table_profile": "Profil", + "table_cycles": "Sykluser", + "table_avg_length": "Gj.sn. lengde", + "table_last_run": "Siste løp", + "table_avg_energy": "Gj.sn. energi", + "table_detected_program": "Oppdaget program", + "table_confidence": "Selvtillit", + "table_reported": "Rapportert", + "phase_builtin_suffix": "(innebygd)", + "phase_none_available": "Ingen faser tilgjengelig.", + "settings_deprecation_warning": "⚠️ **Utviklet enhetstype:** {device_type} er planlagt for fjerning i en fremtidig utgivelse. WashDatas matchende rørledning gir ikke pålitelige resultater for denne apparatklassen. Integrasjonen din fortsetter å fungere gjennom avskrivningsperioden; for å dempe denne advarselen, bytt **Enhetstype** nedenfor til en av de støttede typene (vaskemaskin, tørketrommel, vaskemaskin-tørketrommel, oppvaskmaskin, luftfrityrkoker, brødbakemaskin eller pumpe), eller til **Annet (avansert)** hvis apparatet ditt ikke samsvarer med noen av de støttede typene. **Annet (avansert)** sender med vilje generiske standardinnstillinger som ikke er innstilt for noen spesifikke enheter, så du må konfigurere terskler, tidsavbrudd og samsvarende parametere selv; alle eksisterende innstillinger bevares når du bytter.", + "phase_other_device_types": "Andre enhetstyper:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Del en syklus (finn hull)", + "merge": "Slå sammen sykluser (Sett sammen fragmenter)", + "delete": "Slett syklus(er)" + } + }, + "split_mode": { + "options": { + "auto": "Oppdag inaktive mellomrom automatisk", + "manual": "Manuelle tidsstempler" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Vedlikehold: Bearbeid og optimaliser data", + "clear_debug_data": "Fjern feilsøkingsdata (frigjør plass)", + "wipe_history": "Tørk ALLE data for denne enheten (irreversibel)", + "export_import": "Eksporter/importer JSON med innstillinger (kopier/lim inn)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksporter alle data", + "import": "Importer/slå sammen data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Auto-merk gamle sykluser", + "select_cycle_to_label": "Etikettspesifikk syklus", + "select_cycle_to_delete": "Slett syklus", + "interactive_editor": "Slå sammen/splitt interaktiv editor", + "trim_cycle": "Trim syklusdata" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Opprett ny profil", + "edit_profile": "Rediger/gi nytt navn til profil", + "delete_profile": "Slett profil", + "profile_stats": "Profilstatistikk", + "cleanup_profile": "Rydd opp historikk - Graf og slett", + "assign_phases": "Tilordne faseområder" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Opprett ny fase", + "edit_custom_phase": "Rediger fase", + "delete_custom_phase": "Slett fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Legg til faseområde", + "edit_range": "Rediger faseområde", + "delete_range": "Slett faseområde", + "clear_ranges": "Fjern alle områder", + "auto_detect_ranges": "Automatisk oppdage faser", + "save_ranges": "Lagre og returner" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Oppdater status", + "stop_recording": "Stopp opptak (lagre og behandle)", + "start_recording": "Start nytt opptak", + "process_recording": "Behandle siste opptak (trim og lagre)", + "discard_recording": "Kast siste opptak" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Opprett ny profil", + "existing_profile": "Legg til i eksisterende profil", + "discard": "Forkast opptak" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bekreft - Korrekt deteksjon", + "correct": "Riktig - Velg Program/Varighet", + "ignore": "Ignorer - falsk positiv/støyende syklus", + "delete": "Slett - Fjern fra loggen" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Navngi og bruk faser", + "cancel": "Gå tilbake uten endringer" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Sett startpunkt", + "set_end": "Angi sluttpunkt", + "reset": "Tilbakestill til full varighet", + "apply": "Påfør Trim", + "cancel": "Kansellere" + } + }, + "device_type": { + "options": { + "washing_machine": "Vaskemaskin", + "dryer": "Tørketrommel", + "washer_dryer": "Vaske-tørketrommel kombi", + "dishwasher": "Oppvaskmaskin", + "coffee_machine": "Kaffemaskin (avviklet)", + "ev": "Elektrisk kjøretøy (avviklet)", + "air_fryer": "Air Fryer", + "heat_pump": "Varmepumpe (avviklet)", + "bread_maker": "Brødbaker", + "pump": "Pumpe / Sump Pump", + "oven": "Ovn (avviklet)", + "other": "Annet (avansert)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etikettsyklus", + "description": "Tilordne en eksisterende profil til en tidligere syklus, eller fjern etiketten.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen enhet for å merke." + }, + "cycle_id": { + "name": "Syklus ID", + "description": "ID-en til syklusen som skal merkes." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Navnet på en eksisterende profil (opprett profiler i Administrer profiler-menyen). La stå tomt for å fjerne etiketten." + } + } + }, + "create_profile": { + "name": "Opprett profil", + "description": "Opprett en ny profil (frittstående eller basert på en referansesyklus).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Navn på den nye profilen (f.eks. \"Heavy Duty\", \"Delicates\")." + }, + "reference_cycle_id": { + "name": "Referansesyklus-ID", + "description": "Valgfri syklus-ID å basere denne profilen på." + } + } + }, + "delete_profile": { + "name": "Slett profil", + "description": "Slett en profil og fjern etiketten for sykluser ved å bruke den.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen." + }, + "profile_name": { + "name": "Profilnavn", + "description": "Profilen som skal slettes." + }, + "unlabel_cycles": { + "name": "Fjern etikett på sykluser", + "description": "Fjern profiletiketten fra sykluser som bruker denne profilen." + } + } + }, + "auto_label_cycles": { + "name": "Auto-merk gamle sykluser", + "description": "Merk umerkede sykluser med tilbakevirkende kraft ved hjelp av profiltilpasning.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen." + }, + "confidence_threshold": { + "name": "Confidence Threshold", + "description": "Minste matchkonfidens (0,50–0,95) for å bruke etiketter." + } + } + }, + "export_config": { + "name": "Eksporter konfig", + "description": "Eksporter denne enhetens profiler, sykluser og innstillinger til en JSON-fil (per enhet).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen enhet for å eksportere." + }, + "path": { + "name": "Sti", + "description": "Valgfri absolutt filbane å skrive (standard til /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importer konfig", + "description": "Importer profiler, sykluser og innstillinger for denne enheten fra en JSON-eksportfil (innstillingene kan bli overskrevet).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen som skal importeres til." + }, + "path": { + "name": "Sti", + "description": "Absolutt bane til eksport-JSON-filen som skal importeres." + } + } + }, + "submit_cycle_feedback": { + "name": "Send tilbakemelding om syklus", + "description": "Bekreft eller korriger et automatisk oppdaget program etter en fullført syklus. Oppgi enten «entry_id» (avansert) eller «device_id» (anbefalt).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten (anbefalt i stedet for entry_id)." + }, + "entry_id": { + "name": "Inngangs-ID", + "description": "Konfigurasjonsoppførings-IDen for enheten (alternativ til device_id)." + }, + "cycle_id": { + "name": "Syklus ID", + "description": "Syklus-ID-en vist i tilbakemeldingsvarslingen/loggene." + }, + "user_confirmed": { + "name": "Bekreft oppdaget program", + "description": "Sett sann hvis det oppdagede programmet er riktig." + }, + "corrected_profile": { + "name": "Korrigert profil", + "description": "Hvis ikke bekreftet, oppgi riktig profil/programnavn." + }, + "corrected_duration": { + "name": "Korrigert varighet (sekunder)", + "description": "Valgfri korrigert varighet i sekunder." + }, + "notes": { + "name": "Notater", + "description": "Valgfrie merknader om denne syklusen." + } + } + }, + "record_start": { + "name": "Opptakssyklusstart", + "description": "Begynn å registrere en ren syklus manuelt (omgår all samsvarende logikk).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen enhet for å ta opp." + } + } + }, + "record_stop": { + "name": "Opptakssyklusstopp", + "description": "Stopp manuelt opptak.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "Vaskemaskinen for å stoppe opptaket." + } + } + }, + "trim_cycle": { + "name": "Trim syklus", + "description": "Trim strømdataene for en tidligere syklus til et spesifikt tidsvindu.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten." + }, + "cycle_id": { + "name": "Syklus ID", + "description": "ID-en til syklusen som skal trimmes." + }, + "trim_start_s": { + "name": "Trimstart (sekunder)", + "description": "Hold data fra denne forskyvningen på sekunder. Standard 0." + }, + "trim_end_s": { + "name": "Trim slutt (sekunder)", + "description": "Hold data opp til denne forskyvningen på sekunder. Standard = full varighet." + } + } + }, + "pause_cycle": { + "name": "Pause syklus", + "description": "Sett den aktive syklusen på pause for en WashData-enhet.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten skal settes på pause." + } + } + }, + "resume_cycle": { + "name": "Fortsett syklus", + "description": "Gjenoppta en midlertidig stoppet syklus for en WashData-enhet.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten for å gjenoppta." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Løper" + }, + "match_ambiguity": { + "name": "Match Tvetydighet" + } + }, + "sensor": { + "washer_state": { + "name": "Tilstand", + "state": { + "off": "Av", + "idle": "Uvirksom", + "starting": "Starter", + "running": "Løper", + "paused": "Pause", + "user_paused": "Satt på pause av brukeren", + "ending": "Slutt", + "finished": "Ferdig", + "anti_wrinkle": "Anti-rynke", + "delay_wait": "Venter på å starte", + "interrupted": "Avbrutt", + "force_stopped": "Tving stoppet", + "rinse": "Skylle", + "unknown": "Ukjent", + "clean": "Rengjøre" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Gjenstående tid" + }, + "total_duration": { + "name": "Total varighet" + }, + "cycle_progress": { + "name": "Framgang" + }, + "current_power": { + "name": "Nåværende strøm" + }, + "elapsed_time": { + "name": "Forløpt tid" + }, + "debug_info": { + "name": "Feilsøkingsinformasjon" + }, + "match_confidence": { + "name": "Match selvtillit" + }, + "top_candidates": { + "name": "Toppkandidater", + "state": { + "none": "Ingen" + } + }, + "current_phase": { + "name": "Nåværende fase" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Antall profil {profile_name}", + "unit_of_measurement": "sykluser" + }, + "suggestions": { + "name": "Foreslåtte innstillinger" + }, + "pump_runs_today": { + "name": "Pumpen går (siste 24 timer)" + }, + "cycle_count": { + "name": "Syklustelling" + } + }, + "select": { + "program_select": { + "name": "Syklusprogram", + "state": { + "auto_detect": "Automatisk oppdagelse" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Tving sluttsyklus" + }, + "pause_cycle": { + "name": "Pause syklus" + }, + "resume_cycle": { + "name": "Fortsett syklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "En gyldig device_id kreves." + }, + "cycle_id_required": { + "message": "En gyldig cycle_id kreves." + }, + "profile_name_required": { + "message": "Et gyldig profilnavn kreves." + }, + "device_not_found": { + "message": "Finner ikke enheten." + }, + "no_config_entry": { + "message": "Fant ingen konfigurasjonsoppføring for enheten." + }, + "integration_not_loaded": { + "message": "Integrering er ikke lastet inn for denne enheten." + }, + "cycle_not_found_or_no_power": { + "message": "Syklus ikke funnet eller har ingen strømdata." + }, + "trim_failed_empty_window": { + "message": "Trim mislyktes – syklusen ble ikke funnet, ingen strømdata eller det resulterende vinduet er tomt." + }, + "trim_invalid_range": { + "message": "trim_end_s må være større enn trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/nl.json b/custom_components/ha_washdata/translations/nl.json new file mode 100644 index 0000000..c8530e0 --- /dev/null +++ b/custom_components/ha_washdata/translations/nl.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData-instellingen", + "description": "Configureer uw wasmachine of ander apparaat.\n\nVermogenssensor is vereist.\n\n**Vervolgens wordt u gevraagd of u uw eerste profiel wilt aanmaken.**", + "data": { + "name": "Apparaatnaam", + "device_type": "Apparaattype", + "power_sensor": "Vermogenssensor", + "min_power": "Minimale vermogensdrempel (W)" + }, + "data_description": { + "name": "Een beschrijvende naam voor dit apparaat (bijvoorbeeld 'Wasmachine', 'Vaatwasser').", + "device_type": "Welk type apparaat is dit? Helpt bij het op maat maken van detectie en labeling.", + "power_sensor": "De sensorentiteit die het realtime energieverbruik (in watt) van uw slimme stekker rapporteert.", + "min_power": "Vermogensmetingen boven deze drempel (in watt) geven aan dat het apparaat werkt. Begin met 2W voor de meeste apparaten." + } + }, + "first_profile": { + "title": "Maak het eerste profiel aan", + "description": "Een **Cyclus** is een volledige cyclus van uw apparaat (bijvoorbeeld een lading wasgoed). Een **Profiel** groepeert deze cycli op type (bijvoorbeeld 'Katoen', 'Snelwas'). Door nu een profiel aan te maken, kan de integratieduur direct worden ingeschat.\n\nU kunt dit profiel handmatig selecteren in de apparaatbediening als het niet automatisch wordt gedetecteerd.\n\n**Laat 'Profielnaam' leeg om deze stap over te slaan.**", + "data": { + "profile_name": "Profielnaam (optioneel)", + "manual_duration": "Geschatte duur (minuten)" + } + } + }, + "error": { + "cannot_connect": "Kan geen verbinding maken", + "invalid_auth": "Ongeldige authenticatie", + "unknown": "Onverwachte fout" + }, + "abort": { + "already_configured": "Apparaat is al geconfigureerd" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Leerfeedback bekijken", + "settings": "Instellingen", + "notifications": "Meldingen", + "manage_cycles": "Cycli beheren", + "manage_profiles": "Profielen beheren", + "manage_phase_catalog": "Fasecatalogus beheren", + "record_cycle": "Cyclus opnemen (handmatig)", + "diagnostics": "Diagnostiek & Onderhoud" + } + }, + "apply_suggestions": { + "title": "Voorgestelde waarden overnemen", + "description": "Hiermee worden de huidige voorgestelde waarden naar uw opgeslagen instellingen gekopieerd. Dit is een eenmalige actie en maakt automatisch overschrijven niet mogelijk.\n\nVoorgestelde waarden om over te nemen:\n{suggested}", + "data": { + "confirm": "Bevestig de kopieeractie" + } + }, + "apply_suggestions_confirm": { + "title": "Voorgestelde wijzigingen bekijken", + "description": "WashData heeft **{pending_count}** voorgestelde waarde(n) gevonden om toe te passen.\n\nVeranderingen:\n{changes}\n\n✅ Vink het onderstaande vakje aan en klik op **Verzenden** om deze wijzigingen onmiddellijk toe te passen en op te slaan.\n❌ Laat dit uitgeschakeld en klik op **Verzenden** om te annuleren en terug te gaan.", + "data": { + "confirm_apply_suggestions": "Pas deze wijzigingen toe en sla ze op" + } + }, + "settings": { + "title": "Instellingen", + "description": "{deprecation_warning}Stem detectiedrempels, leerprocessen en geavanceerd gedrag af. Standaardinstellingen werken goed voor de meeste configuraties.\n\nBeschikbare voorgestelde instellingen: {suggestions_count}.\nAls dit boven 0 is, open dan Geavanceerde instellingen en gebruik 'Voorgestelde waarden toepassen' om aanbevelingen te bekijken.", + "data": { + "apply_suggestions": "Voorgestelde waarden toepassen", + "device_type": "Apparaattype", + "power_sensor": "Vermogenssensor-entiteit", + "min_power": "Minimaal vermogen (W)", + "off_delay": "Vertraging cycluseinde (s)", + "notify_actions": "Meldingsacties", + "notify_people": "Bezorging uitstellen totdat deze personen thuis zijn", + "notify_only_when_home": "Meldingen uitstellen totdat geselecteerde persoon thuis is", + "notify_fire_events": "Automatiseringsgebeurtenissen activeren", + "notify_start_services": "Cyclusstart: meldingsdoelen", + "notify_finish_services": "Einde cyclus: meldingsdoelen", + "notify_live_services": "Live voortgang: meldingsdoelen", + "notify_before_end_minutes": "Melding vóór voltooiing (minuten voor einde)", + "notify_title": "Meldingstitel", + "notify_icon": "Meldingspictogram", + "notify_start_message": "Startberichtopmaak", + "notify_finish_message": "Voltooiingsberichtopmaak", + "notify_pre_complete_message": "Berichtopmaak vóór voltooiing", + "show_advanced": "Geavanceerde instellingen bewerken (volgende stap)" + }, + "data_description": { + "apply_suggestions": "Vink dit vakje aan om de door het leeralgoritme voorgestelde waarden naar het formulier te kopiëren. Controleer voordat u opslaat.\n\nVoorgesteld (door leeralgoritme):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selecteer het type apparaat (wasmachine, droger, vaatwasser, koffiezetapparaat, EV). Deze tag wordt bij elke cyclus opgeslagen voor een betere organisatie.", + "power_sensor": "De sensorentiteit die realtime vermogen rapporteert (in watt). U kunt dit op elk moment wijzigen zonder dat historische gegevens verloren gaan - handig als u een slimme stekker vervangt.", + "min_power": "Vermogensmetingen boven deze drempel (in watt) geven aan dat het apparaat werkt. Begin met 2W voor de meeste apparaten.", + "off_delay": "Seconden dat het afgevlakte vermogen onder de stopdrempel moet blijven om voltooiing te markeren. Standaard: 120s.", + "notify_actions": "Optionele Home Assistant-actiereeks voor meldingen (scripts, meldingen, TTS, enz.). Indien ingesteld, worden acties uitgevoerd vóór de terugvalmeldingsservice.", + "notify_people": "Persoonsentiteiten voor aanwezigheidscontrole. Indien ingeschakeld, wordt de bezorging van meldingen uitgesteld totdat ten minste één geselecteerde persoon thuis is.", + "notify_only_when_home": "Indien ingeschakeld, worden meldingen uitgesteld totdat ten minste één geselecteerde persoon thuis is.", + "notify_fire_events": "Indien ingeschakeld, worden Home Assistant-gebeurtenissen geactiveerd voor het begin en einde van de cyclus (ha_washdata_cycle_started en ha_washdata_cycle_ended) om automatiseringen aan te sturen.", + "notify_start_services": "Een of meer meldingsservices om te waarschuwen wanneer een cyclus begint. Laat leeg om startmeldingen over te slaan.", + "notify_finish_services": "Een of meer waarschuwingsservices om te waarschuwen wanneer een cyclus is voltooid of bijna voltooid is. Laat leeg om finishmeldingen over te slaan.", + "notify_live_services": "Een of meer meldingsdiensten om live voortgangsupdates te ontvangen terwijl de cyclus loopt (alleen mobiele begeleidende app). Laat leeg om live updates over te slaan.", + "notify_before_end_minutes": "Minuten vóór het geschatte einde van de cyclus om een melding te activeren. Stel in op 0 om uit te schakelen. Standaard: 0.", + "notify_title": "Aangepaste titel voor meldingen. Ondersteunt de tijdelijke aanduiding {device}.", + "notify_icon": "Pictogram voor meldingen (bijv. mdi:washing-machine). Laat leeg om zonder pictogram te verzenden.", + "notify_start_message": "Bericht verzonden wanneer een cyclus start. Ondersteunt de tijdelijke aanduiding {device}.", + "notify_finish_message": "Bericht verzonden wanneer een cyclus is voltooid. Ondersteunt de tijdelijke aanduidingen {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Tekst van de terugkerende live voortgangsupdates terwijl de cyclus loopt. Ondersteunt tijdelijke aanduidingen voor {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Meldingen", + "description": "Configureer meldingskanalen, sjablonen en optionele live voortgangsupdates op mobiel.", + "data": { + "notify_actions": "Meldingsacties", + "notify_people": "Bezorging uitstellen totdat deze personen thuis zijn", + "notify_only_when_home": "Meldingen uitstellen totdat geselecteerde persoon thuis is", + "notify_fire_events": "Automatiseringsgebeurtenissen activeren", + "notify_start_services": "Cyclusstart: meldingsdoelen", + "notify_finish_services": "Einde cyclus: meldingsdoelen", + "notify_live_services": "Live voortgang: meldingsdoelen", + "notify_before_end_minutes": "Melding vóór voltooiing (minuten voor einde)", + "notify_live_interval_seconds": "Live-updateinterval (seconden)", + "notify_live_overrun_percent": "Overschrijdingstoeslag live updates (%)", + "notify_live_chronometer": "Chronometer afteltimer", + "notify_title": "Meldingstitel", + "notify_icon": "Meldingspictogram", + "notify_start_message": "Startberichtopmaak", + "notify_finish_message": "Voltooiingsberichtopmaak", + "notify_pre_complete_message": "Berichtopmaak vóór voltooiing", + "energy_price_entity": "Energieprijs - Entiteit (optioneel)", + "energy_price_static": "Energieprijs - Statische waarde (optioneel)", + "go_back": "Terug zonder op te slaan", + "notify_reminder_message": "Herinneringsberichtformaat", + "notify_timeout_seconds": "Automatisch sluiten na (seconden)", + "notify_channel": "Meldingskanaal (status/live/herinnering)", + "notify_finish_channel": "Meldingskanaal voltooid" + }, + "data_description": { + "go_back": "Vink dit aan en klik op Verzenden om bovenstaande wijzigingen te verwerpen en terug te keren naar het hoofdmenu.", + "notify_actions": "Optionele Home Assistant-actiereeks voor meldingen (scripts, meldingen, TTS, enz.). Indien ingesteld, worden acties uitgevoerd vóór de terugvalmeldingsservice.", + "notify_people": "Persoonsentiteiten voor aanwezigheidscontrole. Indien ingeschakeld, wordt de bezorging van meldingen uitgesteld totdat ten minste één geselecteerde persoon thuis is.", + "notify_only_when_home": "Indien ingeschakeld, worden meldingen uitgesteld totdat ten minste één geselecteerde persoon thuis is.", + "notify_fire_events": "Indien ingeschakeld, worden Home Assistant-gebeurtenissen geactiveerd voor het begin en einde van de cyclus (ha_washdata_cycle_started en ha_washdata_cycle_ended) om automatiseringen aan te sturen.", + "notify_start_services": "Een of meer meldingsservices die waarschuwen wanneer een cyclus begint (bijvoorbeeld notificatie.mobile_app_my_phone). Laat leeg om startmeldingen over te slaan.", + "notify_finish_services": "Een of meer waarschuwingsservices om te waarschuwen wanneer een cyclus is voltooid of bijna voltooid is. Laat leeg om finishmeldingen over te slaan.", + "notify_live_services": "Een of meer meldingsdiensten om live voortgangsupdates te ontvangen terwijl de cyclus loopt (alleen mobiele begeleidende app). Laat leeg om live updates over te slaan.", + "notify_before_end_minutes": "Minuten vóór het geschatte einde van de cyclus om een melding te activeren. Stel in op 0 om uit te schakelen. Standaard: 0.", + "notify_live_interval_seconds": "Hoe vaak voortgangsupdates worden verzonden tijdens een actieve cyclus. Lagere waarden reageren beter, maar verbruiken meer meldingen. Er wordt een minimum van 30 seconden gehanteerd - waarden onder de 30 seconden worden automatisch afgerond naar 30 seconden.", + "notify_live_overrun_percent": "Veiligheidsmarge boven het geschatte aantal updates voor lange of uitlopende cycli (20% staat bijvoorbeeld 1,2× het geschatte aantal updates toe).", + "notify_live_chronometer": "Indien ingeschakeld, bevat elke live update een aftelling van de chronometer naar de geschatte eindtijd. De meldingstimer tikt automatisch af op het apparaat tussen updates door (alleen Android-app).", + "notify_title": "Aangepaste titel voor meldingen. Ondersteunt de tijdelijke aanduiding {device}.", + "notify_icon": "Pictogram voor meldingen (bijv. mdi:washing-machine). Laat leeg om zonder pictogram te verzenden.", + "notify_start_message": "Bericht verzonden wanneer een cyclus start. Ondersteunt de tijdelijke aanduiding {device}.", + "notify_finish_message": "Bericht verzonden wanneer een cyclus is voltooid. Ondersteunt de tijdelijke aanduidingen {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Selecteer een numerieke entiteit die de huidige elektriciteitsprijs bevat (bijvoorbeeld sensor.elektriciteitsprijs, invoernummer.energietarief). Indien ingesteld, wordt de tijdelijke aanduiding {cost} ingeschakeld. Heeft voorrang op de statische waarde als beide zijn geconfigureerd.", + "energy_price_static": "Vaste elektriciteitsprijs per kWh. Indien ingesteld, wordt de tijdelijke aanduiding {cost} ingeschakeld. Genegeerd als hierboven een entiteit is geconfigureerd. Voeg uw valutasymbool toe aan de berichtsjabloon, b.v. {cost} €.", + "notify_reminder_message": "De tekst van de eenmalige herinnering verzendt het geconfigureerde aantal minuten vóór het geschatte einde. Anders dan de terugkerende live-updates hierboven. Ondersteunt tijdelijke aanduidingen voor {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Meldingen na dit aantal seconden automatisch negeren (doorgestuurd als 'time-out' van de bijbehorende app). Stel deze in op 0 om ze te behouden totdat ze worden verwijderd. Standaard: 0.", + "notify_channel": "Android meldingskanaal voor de begeleidende app voor status, live voortgang en herinneringsmeldingen. Het geluid en het belang van een kanaal worden geconfigureerd in de bijbehorende app wanneer de kanaalnaam voor het eerst wordt gebruikt - WashData stelt alleen de kanaalnaam in. Laat leeg voor de app-standaard.", + "notify_finish_channel": "Apart kanaal Android voor de klaar-waarschuwing (wordt ook gebruikt door de herinnering en het was-wacht-gezeur) zodat het een eigen geluid kan hebben. Laat leeg om het bovenstaande kanaal opnieuw te gebruiken.", + "notify_pre_complete_message": "Tekst van de terugkerende live voortgangsupdates terwijl de cyclus loopt. Ondersteunt tijdelijke aanduidingen voor {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Geavanceerde instellingen", + "description": "Stem detectiedrempels, leerprocessen en geavanceerd gedrag af. Standaardinstellingen werken goed voor de meeste configuraties.", + "sections": { + "suggestions_section": { + "name": "Voorgestelde instellingen", + "description": "{suggestions_count} geleerde suggesties beschikbaar. Vink Voorgestelde waarden toepassen aan om voorgestelde wijzigingen te bekijken voordat u opslaat.", + "data": { + "apply_suggestions": "Voorgestelde waarden toepassen" + }, + "data_description": { + "apply_suggestions": "Vink dit vakje aan om een beoordelingsstap te openen voor voorgestelde waarden van het leeralgoritme. Er wordt niets automatisch opgeslagen.\n\nVoorgesteld (door leeralgoritme):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detectie- en vermogensdrempels", + "description": "Wanneer het apparaat als AAN of UIT wordt beschouwd, energiedrempels en timing voor statusovergangen.", + "data": { + "start_duration_threshold": "Startdebounce-duur (s)", + "start_energy_threshold": "Startenergiegrens (Wh)", + "completion_min_seconds": "Voltooiing: minimale looptijd (seconden)", + "start_threshold_w": "Startdrempel (W)", + "stop_threshold_w": "Stopdrempel (W)", + "end_energy_threshold": "Eindenergiegrens (Wh)", + "running_dead_zone": "Dode zone bij starten (seconden)", + "end_repeat_count": "Einde herhalingsaantal", + "min_off_gap": "Min. interval tussen cycli (s)", + "sampling_interval": "Bemonsteringsinterval (s)" + }, + "data_description": { + "start_duration_threshold": "Negeer korte vermogenspieken korter dan deze duur (seconden). Filtert opstartpieken (bijv. 60 W gedurende 2 s) vóór de echte cyclusstart. Standaard: 5s.", + "start_energy_threshold": "De cyclus moet ten minste zoveel energie (Wh) accumuleren tijdens de STARTFASE om te worden bevestigd. Standaard: 0,005 Wh.", + "completion_min_seconds": "Cycli korter dan dit worden gemarkeerd als 'onderbroken', ook als ze op natuurlijke wijze eindigen. Standaard: 600s.", + "start_threshold_w": "Vermogen moet BOVEN deze drempel stijgen om een cyclus te STARTEN. Stel hoger in dan uw stand-byvermogen. Standaard: min_power + 1 W.", + "stop_threshold_w": "Vermogen moet ONDER deze drempel dalen om een cyclus te BEËINDIGEN. Stel net boven nul in om te voorkomen dat ruis valse eindpunten veroorzaakt. Standaard: 0,6 × min_power.", + "end_energy_threshold": "De cyclus eindigt alleen als de energie in het laatste uitschakelvertragingvenster onder deze waarde (Wh) ligt. Voorkomt valse eindpunten als het vermogen dicht bij nul schommelt. Standaard: 0,05 Wh.", + "running_dead_zone": "Nadat de cyclus is gestart, worden vermogensdips gedurende dit aantal seconden genegeerd om valse einddetectie tijdens de initiële opstartfase te voorkomen. Stel in op 0 om uit te schakelen. Standaard: 0s.", + "end_repeat_count": "Aantal keren dat de eindconditie (vermogen onder de drempel gedurende de uitschakelvertraging) achtereenvolgens moet worden bereikt voordat de cyclus eindigt. Hogere waarden voorkomen valse eindpunten tijdens pauzes. Standaard: 1.", + "min_off_gap": "Als een cyclus binnen dit aantal seconden na het einde van de vorige begint, worden ze SAMENGEVOEGD tot één cyclus.", + "sampling_interval": "Minimaal bemonsteringsinterval in seconden. Updates sneller dan dit tempo worden genegeerd. Standaard: 30s (2s voor wasmachines, was-droogcombinaties en vaatwassers)." + } + }, + "matching_section": { + "name": "Profielovereenkomst en leren", + "description": "Hoe agressief WashData actieve cycli vergelijkt met geleerde profielen en wanneer om feedback wordt gevraagd.", + "data": { + "profile_match_min_duration_ratio": "Min. duurverhouding profielmatch (0,1-1,0)", + "profile_match_interval": "Profielmatchinterval (seconden)", + "profile_match_threshold": "Profielmatchdrempel", + "profile_unmatch_threshold": "Profiel-geen-matchdrempel", + "auto_label_confidence": "Vertrouwen automatisch labelen (0-1)", + "learning_confidence": "Vertrouwen feedbackverzoek (0-1)", + "suppress_feedback_notifications": "Feedbackmeldingen onderdrukken", + "duration_tolerance": "Duurtolerantie (0-0,5)", + "smoothing_window": "Afvlakkingsvenster (samples)", + "profile_duration_tolerance": "Duurtolerantie profielmatch (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimale duurverhouding voor profielmatching. De lopende cyclus moet ten minste dit deel van de profielduur bedragen om te matchen. Lager = eerder matchen. Standaard: 0,50 (50%).", + "profile_match_interval": "Hoe vaak (in seconden) profielmatching wordt geprobeerd tijdens een cyclus. Lagere waarden zorgen voor snellere detectie maar gebruiken meer CPU. Standaard: 300s (5 minuten).", + "profile_match_threshold": "Minimale DTW-gelijkenisscore (0,0–1,0) vereist om een profiel als match te beschouwen. Hoger = strengere matching, minder valse positieven. Standaard: 0,4.", + "profile_unmatch_threshold": "DTW-gelijkenisscore (0,0–1,0) waaronder een eerder gematchd profiel wordt afgewezen. Moet lager zijn dan de matchdrempel om flikkering te voorkomen. Standaard: 0,35.", + "auto_label_confidence": "Label cycli automatisch bij dit vertrouwensniveau of hoger na voltooiing (0,0–1,0).", + "learning_confidence": "Minimaal vertrouwen om gebruikersverificatie aan te vragen via een gebeurtenis (0,0–1,0).", + "suppress_feedback_notifications": "Indien ingeschakeld, zal WashData nog steeds intern feedback volgen en om feedback vragen, maar geen permanente meldingen weergeven waarin u wordt gevraagd cycli te bevestigen. Handig als cycli altijd correct worden gedetecteerd en u de aanwijzingen afleidend vindt.", + "duration_tolerance": "Tolerantie voor schattingen van de resterende tijd tijdens een cyclus (0,0–0,5 komt overeen met 0–50%).", + "smoothing_window": "Aantal recente vermogensmetingen gebruikt voor voortschrijdend-gemiddelde afvlakking.", + "profile_duration_tolerance": "Tolerantie voor totale duurvariatie bij profielmatching (0,0–0,5). Standaardgedrag: ±25%." + } + }, + "timing_section": { + "name": "Timing, onderhoud en debug", + "description": "Watchdog, voortgangsreset, automatisch onderhoud en zichtbaarheid van debug-entiteiten.", + "data": { + "watchdog_interval": "Watchdog-interval (seconden)", + "no_update_active_timeout": "Time-out zonder updates (s)", + "progress_reset_delay": "Vertraging voortgangsreset (seconden)", + "auto_maintenance": "Automatisch onderhoud inschakelen", + "expose_debug_entities": "Debug-entiteiten zichtbaar maken", + "save_debug_traces": "Debugsporen opslaan" + }, + "data_description": { + "watchdog_interval": "Seconden tussen watchdog-controles tijdens een actieve cyclus. Standaard: 5s. WAARSCHUWING: Zorg dat dit HOGER is dan het update-interval van uw sensor om valse stops te voorkomen (als de sensor bijv. elke 60s bijwerkt, gebruik dan 65s+).", + "no_update_active_timeout": "Voor sensoren die alleen bij wijziging publiceren: beëindig een ACTIEVE cyclus pas geforceerd als er gedurende dit aantal seconden geen updates binnenkomen. Voltooiing bij laag vermogen gebruikt nog steeds de uitschakelvertraging.", + "progress_reset_delay": "Nadat een cyclus is voltooid (100%), wacht dit aantal seconden inactief voordat de voortgang wordt teruggezet naar 0%. Standaard: 300s.", + "auto_maintenance": "Automatisch onderhoud inschakelen (samples herstellen, fragmenten samenvoegen).", + "expose_debug_entities": "Geavanceerde sensoren weergeven (betrouwbaarheid, fase, dubbelzinnigheid) voor foutopsporing.", + "save_debug_traces": "Gedetailleerde rangschikkings- en vermogensspoorgegevens bewaren in de geschiedenis (verhoogt opslaggebruik)." + } + }, + "anti_wrinkle_section": { + "name": "Antikreukbescherming (drogers)", + "description": "Negeer trommelrotaties na de cyclus om spookcycli te voorkomen. Alleen droger / was-droogcombinatie.", + "data": { + "anti_wrinkle_enabled": "Anti-kreukschild (alleen droger/was-droogcombinatie)", + "anti_wrinkle_max_power": "Anti-kreuk max. vermogen (W) (alleen droger/was-droogcombinatie)", + "anti_wrinkle_max_duration": "Anti-kreuk max. duur (s) (alleen droger/was-droogcombinatie)", + "anti_wrinkle_exit_power": "Anti-kreuk uitgangsvermogen (W) (alleen droger/was-droogcombinatie)" + }, + "data_description": { + "anti_wrinkle_enabled": "Korte trommelrotaties met laag vermogen na afloop van een cyclus negeren om spookcycli te voorkomen (alleen droger/was-droogcombinatie).", + "anti_wrinkle_max_power": "Pieken op of onder dit vermogen worden behandeld als anti-kreukrotaties in plaats van nieuwe cycli. Geldt alleen als het Anti-kreukschild is ingeschakeld.", + "anti_wrinkle_max_duration": "Maximale piekduur om als anti-kreukrotatie te beschouwen. Geldt alleen als het Anti-kreukschild is ingeschakeld.", + "anti_wrinkle_exit_power": "Als het vermogen onder dit niveau zakt, wordt de anti-kreukfunctie onmiddellijk beëindigd (echte uitschakeling). Geldt alleen als het Anti-kreukschild is ingeschakeld." + } + }, + "delay_start_section": { + "name": "Detectie van uitgestelde start", + "description": "Behandel aanhoudende stand-by met laag vermogen als 'Wachten om te starten' totdat de cyclus echt begint.", + "data": { + "delay_start_detect_enabled": "Schakel detectie van vertraagde start in", + "delay_confirm_seconds": "Uitgestelde start: bevestigingstijd voor stand-by (s)", + "delay_timeout_hours": "Uitgestelde start: maximale wachttijd (u)" + }, + "data_description": { + "delay_start_detect_enabled": "Indien ingeschakeld, wordt een korte stroompiek bij laag energieverbruik, gevolgd door aanhoudend stand-byvermogen, gedetecteerd als een vertraagde start. De statussensor geeft tijdens de vertraging 'Wachten op start' weer. Er wordt geen programmatijd of voortgang bijgehouden totdat de cyclus daadwerkelijk begint. Vereist dat start_threshold_w wordt ingesteld boven het drain-piekvermogen.", + "delay_confirm_seconds": "Het vermogen moet gedurende zoveel seconden tussen Stopdrempel (W) en Startdrempel (W) blijven voordat WashData naar 'Wachten om te starten' gaat. Filtert korte pieken door menunavigatie weg. Standaard: 60 s.", + "delay_timeout_hours": "Veiligheidstime-out: als de machine na dit aantal uren nog steeds in 'Wachten om te starten' staat, wordt WashData gereset naar Uit. Standaard: 8 uur." + } + }, + "external_triggers_section": { + "name": "Externe triggers, deur en pauze", + "description": "Optionele externe sensor voor cycluseinde, deursensor voor pauze-/schoondetectie en schakelaar om stroom te onderbreken bij pauzeren.", + "data": { + "external_end_trigger_enabled": "Externe cycluseinde-trigger inschakelen", + "external_end_trigger": "Externe cycluseindesensor", + "external_end_trigger_inverted": "Triggerlogica omkeren (activeren bij UIT)", + "door_sensor_entity": "Deursensor", + "pause_cuts_power": "Schakel de stroom uit tijdens het pauzeren", + "switch_entity": "Schakelentiteit (voor pauze stroomuitval)", + "notify_unload_delay_minutes": "Vertraging wasgoed wachtmelding (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Bewaking van een externe binaire sensor inschakelen om cyclusvoltooiing te activeren.", + "external_end_trigger": "Selecteer een binaire sensorentiteit. Wanneer deze sensor activeert, eindigt de huidige cyclus met de status 'voltooid'.", + "external_end_trigger_inverted": "Standaard wordt de trigger geactiveerd wanneer de sensor AAN gaat. Vink dit vakje aan om te activeren wanneer de sensor UIT gaat.", + "door_sensor_entity": "Optionele binaire sensor voor de machinedeur (aan = open, uit = gesloten). Wanneer de deur opengaat tijdens een actieve cyclus, bevestigt WashData dat de cyclus opzettelijk is onderbroken. Nadat de cyclus is afgelopen en de deur nog steeds gesloten is, verandert de status in 'Reinigen' totdat de deur wordt geopend. Let op: als u de deur sluit, wordt een gepauzeerde cyclus NIET automatisch hervat; het hervatten moet handmatig worden geactiveerd via de knop Cyclus hervatten of via de service.", + "pause_cuts_power": "Wanneer u pauzeert via de knop Pause Cycle of service, schakelt u ook de geconfigureerde switch-entiteit uit. Wordt alleen van kracht als er voor dit apparaat een switch-entiteit is geconfigureerd.", + "switch_entity": "De schakelentiteit die moet worden omgeschakeld tijdens het pauzeren of hervatten. Vereist wanneer 'Cut Power When Pausing' is ingeschakeld.", + "notify_unload_delay_minutes": "Stuur een melding via het finishmeldingskanaal nadat de cyclus is afgelopen en de deur al zoveel minuten niet is geopend (vereist Door Sensor). Stel in op 0 om uit te schakelen. Standaard: 60 minuten." + } + }, + "pump_section": { + "name": "Pompmonitor", + "description": "Alleen voor apparaattype pomp. Genereert een gebeurtenis als een pompcyclus langer dan deze duur loopt.", + "data": { + "pump_stuck_duration": "Waarschuwingsdrempel(s) voor pomp vastgelopen (alleen pomp)" + }, + "data_description": { + "pump_stuck_duration": "Als een pompcyclus na dit aantal seconden nog steeds actief is, wordt er één keer een ha_washdata_pump_stuck-gebeurtenis geactiveerd. Gebruik dit om een ​​vastgelopen motor te detecteren (de typische pomplooptijd van de pomp bedraagt ​​minder dan 60 s). Standaard: 1800 s (30 min). Alleen type pompapparaat." + } + }, + "device_link_section": { + "name": "Apparaatkoppeling", + "description": "Koppel dit WashData-apparaat optioneel aan een bestaand Home Assistant-apparaat (bijvoorbeeld de slimme stekker of het apparaat zelf). Het WashData-apparaat wordt dan weergegeven als \"Verbonden via\" dat apparaat. Laat dit leeg om WashData als zelfstandig apparaat te behouden.", + "data": { + "linked_device": "Gekoppeld apparaat" + }, + "data_description": { + "linked_device": "Selecteer het apparaat waarmee u WashData wilt verbinden. Dit voegt een 'Verbonden via'-referentie toe in het apparaatregister; WashData behoudt zijn eigen apparaatkaart en entiteiten. Wis de selectie om de link te verwijderen." + } + } + } + }, + "diagnostics": { + "title": "Diagnostiek & Onderhoud", + "description": "Voer onderhoudsacties uit, zoals het samenvoegen van gefragmenteerde cycli of het migreren van opgeslagen gegevens.\n\n**Opslaggebruik**\n\n- Bestandsgrootte: {file_size_kb} KB\n- Cycli: {cycle_count}\n- Profielen: {profile_count}\n- Debugsporen: {debug_count}", + "menu_options": { + "reprocess_history": "Onderhoud: gegevens opnieuw verwerken en optimaliseren", + "clear_debug_data": "Debuggegevens wissen (ruimte vrijmaken)", + "wipe_history": "ALLE gegevens voor dit apparaat wissen (onomkeerbaar)", + "export_import": "JSON exporteren/importeren met instellingen (kopiëren/plakken)", + "menu_back": "← Terug" + } + }, + "clear_debug_data": { + "title": "Debuggegevens wissen", + "description": "Weet u zeker dat u alle opgeslagen debugsporen wilt verwijderen? Hierdoor wordt ruimte vrijgemaakt, maar worden gedetailleerde rangschikkingsgegevens uit eerdere cycli verwijderd." + }, + "manage_cycles": { + "title": "Cycli beheren", + "description": "Recente cycli:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Oude cycli automatisch labelen", + "select_cycle_to_label": "Specifieke cyclus labelen", + "select_cycle_to_delete": "Cyclus verwijderen", + "interactive_editor": "Interactieve editor samenvoegen/splitsen", + "trim_cycle_select": "Cyclusgegevens trimmen", + "menu_back": "← Terug" + } + }, + "manage_cycles_empty": { + "title": "Cycli beheren", + "description": "Er zijn nog geen cycli geregistreerd.", + "menu_options": { + "auto_label_cycles": "Oude cycli automatisch labelen", + "menu_back": "← Terug" + } + }, + "manage_profiles": { + "title": "Profielen beheren", + "description": "Profieloverzicht:\n{profile_summary}", + "menu_options": { + "create_profile": "Nieuw profiel maken", + "edit_profile": "Profiel bewerken/hernoemen", + "delete_profile_select": "Profiel verwijderen", + "profile_stats": "Profielstatistieken", + "cleanup_profile": "Geschiedenis opschonen - Grafiek & verwijderen", + "assign_profile_phases_select": "Fasebereiken toewijzen", + "menu_back": "← Terug" + } + }, + "manage_profiles_empty": { + "title": "Profielen beheren", + "description": "Er zijn nog geen profielen aangemaakt.", + "menu_options": { + "create_profile": "Nieuw profiel maken", + "menu_back": "← Terug" + } + }, + "manage_phase_catalog": { + "title": "Fasecatalogus beheren", + "description": "Fasecatalogus (standaardinstellingen voor dit apparaat + aangepaste fasen):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Nieuwe fase maken", + "phase_catalog_edit_select": "Fase bewerken", + "phase_catalog_delete": "Fase verwijderen", + "menu_back": "← Terug" + } + }, + "phase_catalog_create": { + "title": "Aangepaste fase maken", + "description": "Maak een aangepaste fasenaam en beschrijving en kies op welk apparaattype deze van toepassing is.", + "data": { + "target_device_type": "Doeltype apparaat", + "phase_name": "Fasenaam", + "phase_description": "Fasebeschrijving" + } + }, + "phase_catalog_edit_select": { + "title": "Aangepaste fase bewerken", + "description": "Selecteer een aangepaste fase om te bewerken.", + "data": { + "phase_name": "Aangepaste fase" + } + }, + "phase_catalog_edit": { + "title": "Aangepaste fase bewerken", + "description": "Fase bewerken: {phase_name}", + "data": { + "phase_name": "Fasenaam", + "phase_description": "Fasebeschrijving" + } + }, + "phase_catalog_delete": { + "title": "Aangepaste fase verwijderen", + "description": "Selecteer een aangepaste fase om te verwijderen. Alle toegewezen bereiken die deze fase gebruiken, worden verwijderd.", + "data": { + "phase_name": "Aangepaste fase" + } + }, + "assign_profile_phases": { + "title": "Fasebereiken toewijzen", + "description": "Faseoverzicht voor profiel: **{profile_name}**\n\n{timeline_svg}\n\n**Huidige bereiken:**\n{current_ranges}\n\nGebruik de onderstaande acties om bereiken toe te voegen, te bewerken, te verwijderen of op te slaan.", + "menu_options": { + "assign_profile_phases_add": "Fasebereik toevoegen", + "assign_profile_phases_edit_select": "Fasebereik bewerken", + "assign_profile_phases_delete": "Fasebereik verwijderen", + "phase_ranges_clear": "Alle bereiken wissen", + "assign_profile_phases_auto_detect": "Fasen automatisch detecteren", + "phase_ranges_save": "Opslaan en terug", + "menu_back": "← Terug" + } + }, + "assign_profile_phases_add": { + "title": "Fasebereik toevoegen", + "description": "Voeg één fasebereik toe aan het huidige concept.", + "data": { + "phase_name": "Fase", + "start_min": "Startminuut", + "end_min": "Eindminuut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Fasebereik bewerken", + "description": "Selecteer een bereik om te bewerken.", + "data": { + "range_index": "Fasebereik" + } + }, + "assign_profile_phases_edit": { + "title": "Fasebereik bewerken", + "description": "Werk het geselecteerde fasebereik bij.", + "data": { + "phase_name": "Fase", + "start_min": "Startminuut", + "end_min": "Eindminuut" + } + }, + "assign_profile_phases_delete": { + "title": "Fasebereik verwijderen", + "description": "Selecteer een bereik om uit het concept te verwijderen.", + "data": { + "range_index": "Fasebereik" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatisch gedetecteerde fasen", + "description": "Profiel: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) automatisch gedetecteerd.** Kies hieronder een actie.", + "data": { + "action": "Actie" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Gedetecteerde fasen een naam geven", + "description": "Profiel: **{profile_name}**\n\n**{detected_count} fase(s) gedetecteerd:**\n{ranges_summary}\n\nGeef elke fase een naam." + }, + "assign_profile_phases_select": { + "title": "Fasebereiken toewijzen", + "description": "Selecteer een profiel om fasebereiken te configureren.", + "data": { + "profile": "Profiel" + } + }, + "profile_stats": { + "title": "Profielstatistieken", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Nieuw profiel maken", + "description": "Maak handmatig of op basis van een eerdere cyclus een nieuw profiel aan.\n\nVoorbeelden van profielnamen: 'Fijne was', 'Intensief', 'Snelwas'", + "data": { + "profile_name": "Profielnaam", + "reference_cycle": "Referentiecyclus (optioneel)", + "manual_duration": "Handmatige duur (minuten)" + } + }, + "edit_profile": { + "title": "Profiel bewerken", + "description": "Selecteer een profiel om de naam van te wijzigen", + "data": { + "profile": "Profiel" + } + }, + "rename_profile": { + "title": "Profielgegevens bewerken", + "description": "Huidig profiel: {current_name}", + "data": { + "new_name": "Profielnaam", + "manual_duration": "Handmatige duur (minuten)" + } + }, + "delete_profile_select": { + "title": "Profiel verwijderen", + "description": "Selecteer een profiel om te verwijderen", + "data": { + "profile": "Profiel" + } + }, + "delete_profile_confirm": { + "title": "Verwijderen van profiel bevestigen", + "description": "⚠️ Dit verwijdert het profiel permanent: {profile_name}", + "data": { + "unlabel_cycles": "Label verwijderen van cycli die dit profiel gebruiken" + } + }, + "auto_label_cycles": { + "title": "Oude cycli automatisch labelen", + "description": "In totaal {total_count} cycli gevonden. Profielen: {profiles}", + "data": { + "confidence_threshold": "Minimaal vertrouwen (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Selecteer cyclus om te labelen", + "description": "✓ = voltooid, ⚠ = hervat, ✗ = onderbroken", + "data": { + "cycle_id": "Cyclus" + } + }, + "select_cycle_to_delete": { + "title": "Cyclus verwijderen", + "description": "⚠️ Dit verwijdert de geselecteerde cyclus permanent", + "data": { + "cycle_id": "Te verwijderen cyclus" + } + }, + "label_cycle": { + "title": "Cyclus labelen", + "description": "{cycle_info}", + "data": { + "profile_name": "Profiel", + "new_profile_name": "Nieuwe profielnaam (indien aan te maken)" + } + }, + "post_process": { + "title": "Geschiedenis verwerken (samenvoegen/splitsen)", + "description": "Ruim de cyclusgeschiedenis op door gefragmenteerde runs samen te voegen en onjuist samengevoegde cycli binnen een tijdsvenster op te splitsen. Voer het aantal uren in dat u wilt terugkijken (gebruik 999999 voor alles).", + "data": { + "time_range": "Terugkijkperiode (uren)", + "gap_seconds": "Samenvoeg-/splitsingsdrempel (seconden)" + }, + "data_description": { + "time_range": "Aantal uren dat moet worden gescand om gefragmenteerde cycli samen te voegen. Gebruik 999999 om alle historische gegevens te verwerken.", + "gap_seconds": "Drempel om te beslissen of cycli worden gesplitst of samengevoegd. Gaten KORTER dan dit worden samengevoegd. Gaten LANGER dan dit worden gesplitst. Verlaag dit om een splitsing te forceren voor een onjuist samengevoegde cyclus." + } + }, + "cleanup_profile": { + "title": "Geschiedenis opschonen - Profiel selecteren", + "description": "Selecteer een profiel om te visualiseren. Er wordt een grafiek gegenereerd met alle eerdere cycli voor dit profiel om uitschieters te kunnen identificeren.", + "data": { + "profile": "Profiel" + } + }, + "cleanup_select": { + "title": "Geschiedenis opschonen - Grafiek & verwijderen", + "description": "![Grafiek]({graph_url})\n\n**{profile_name} wordt gevisualiseerd**\n\nDe grafiek toont individuele cycli als gekleurde lijnen. Identificeer uitschieters (lijnen ver van de groep) en match hun kleur in de onderstaande lijst om ze te verwijderen.\n\n**Selecteer cycli om PERMANENT te verwijderen:**", + "data": { + "cycles_to_delete": "Te verwijderen cycli (controleer overeenkomende kleuren)" + } + }, + "interactive_editor": { + "title": "Interactieve editor samenvoegen/splitsen", + "description": "Selecteer een handmatige actie om uit te voeren op uw cyclusgeschiedenis.", + "menu_options": { + "editor_split": "Cyclus splitsen (gaten zoeken)", + "editor_merge": "Cycli samenvoegen (fragmenten combineren)", + "editor_delete": "Cyclus(sen) verwijderen", + "menu_back": "← Terug" + } + }, + "editor_select": { + "title": "Cycli selecteren", + "description": "Selecteer de cyclus(sen) om te verwerken.\n\n{info_text}", + "data": { + "selected_cycles": "Cycli" + } + }, + "editor_configure": { + "title": "Configureren en toepassen", + "description": "{preview_md}", + "data": { + "confirm_action": "Actie", + "merged_profile": "Resulterend profiel", + "new_profile_name": "Nieuwe profielnaam", + "confirm_commit": "Ja, ik bevestig deze actie", + "segment_0_profile": "Segment 1-profiel", + "segment_1_profile": "Segment 2-profiel", + "segment_2_profile": "Segment 3-profiel", + "segment_3_profile": "Segment 4-profiel", + "segment_4_profile": "Segment 5-profiel", + "segment_5_profile": "Segment 6-profiel", + "segment_6_profile": "Segment 7-profiel", + "segment_7_profile": "Segment 8-profiel", + "segment_8_profile": "Segment 9-profiel", + "segment_9_profile": "Segment 10-profiel" + } + }, + "editor_split_params": { + "title": "Splitsingsconfiguratie", + "description": "Pas de gevoeligheid aan voor het splitsen van deze cyclus. Elke inactieve opening langer dan dit veroorzaakt een splitsing.", + "data": { + "split_mode": "Splitsingsmethode" + } + }, + "editor_split_auto_params": { + "title": "Configuratie voor automatisch splitsen", + "description": "Pas de gevoeligheid voor het splitsen van deze cyclus aan. Elke inactieve onderbreking die langer is dan dit veroorzaakt een splitsing.", + "data": { + "min_gap_seconds": "Drempel voor splitsingspauze (seconden)" + } + }, + "editor_split_manual_params": { + "title": "Handmatige splitsingstijdstempels", + "description": "{preview_md}Cyclusvenster: **{cycle_start_wallclock} → {cycle_end_wallclock}** op {cycle_date}.\n\nVoer een of meer splitsingstijdstempels in (`HH:MM` of `HH:MM:SS`), één per regel. De cyclus wordt bij elk tijdstempel gesplitst — N tijdstempels leveren N+1 segmenten op.", + "data": { + "split_timestamps": "Splitsingstijdstempels" + } + }, + "reprocess_history": { + "title": "Onderhoud: gegevens opnieuw verwerken en optimaliseren", + "description": "Voert een grondige opschoning van historische gegevens uit:\n1. Trimt nulvermogenmetingen (stilte).\n2. Herstelt cyclustigming/-duur.\n3. Herberekent alle statistische modellen (enveloppen).\n\n**Voer dit uit om gegevensproblemen op te lossen of nieuwe algoritmen toe te passen.**" + }, + "wipe_history": { + "title": "Geschiedenis wissen (alleen voor testen)", + "description": "⚠️ Dit verwijdert ALLE opgeslagen cycli en profielen voor dit apparaat permanent. Dit kan niet ongedaan worden gemaakt!" + }, + "export_import": { + "title": "JSON exporteren/importeren", + "description": "Kopieer/plak cycli, profielen en instellingen voor dit apparaat. Exporteer hieronder of plak JSON om te importeren.", + "data": { + "mode": "Actie", + "json_payload": "Volledige configuratie-JSON" + }, + "data_description": { + "mode": "Kies Exporteren om de huidige gegevens en instellingen te kopiëren, of Importeren om geëxporteerde gegevens van een ander WashData-apparaat te plakken.", + "json_payload": "Voor exporteren: kopieer deze JSON (inclusief cycli, profielen en alle verfijnde instellingen). Voor importeren: plak JSON geëxporteerd vanuit WashData." + } + }, + "record_cycle": { + "title": "Cyclus opnemen", + "description": "Neem handmatig een cyclus op om een schoon profiel zonder interferentie te maken.\n\nStatus: **{status}**\nDuur: {duration}s\nSamples: {samples}", + "menu_options": { + "record_refresh": "Status vernieuwen", + "record_stop": "Opname stoppen (opslaan en verwerken)", + "record_start": "Nieuwe opname starten", + "record_process": "Laatste opname verwerken (trimmen en opslaan)", + "record_discard": "Laatste opname verwijderen", + "menu_back": "← Terug" + } + }, + "record_process": { + "title": "Opname verwerken", + "description": "![Grafiek]({graph_url})\n\n**Grafiek toont ruwe opname.** Blauw = bewaren, rood = voorgestelde trim.\n*Grafiek is statisch en wordt niet bijgewerkt bij formulierwijzigingen.*\n\nOpname gestopt. Trims worden uitgelijnd op de gedetecteerde bemonsteringsfrequentie (~{sampling_rate}s).\n\nRuwe duur: {duration}s\nSamples: {samples}", + "data": { + "head_trim": "Trim begin (seconden)", + "tail_trim": "Trim einde (seconden)", + "save_mode": "Opslagbestemming", + "profile_name": "Profielnaam" + } + }, + "learning_feedbacks": { + "title": "Leerfeedback", + "description": "Openstaande feedback: {count}\n\n{pending_table}\n\nSelecteer een cyclusbeoordelingsverzoek om te verwerken.", + "menu_options": { + "learning_feedbacks_pick": "Openstaande feedback beoordelen", + "learning_feedbacks_dismiss_all": "Alle openstaande feedback negeren", + "menu_back": "← Terug" + } + }, + "learning_feedbacks_pick": { + "title": "Feedback selecteren om te bekijken", + "description": "Selecteer een cyclusbeoordelingsverzoek om te openen.", + "data": { + "selected_feedback": "Cyclus selecteren om te bekijken" + } + }, + "learning_feedbacks_empty": { + "title": "Leerfeedback", + "description": "Geen openstaande feedbackverzoeken gevonden.", + "menu_options": { + "menu_back": "← Terug" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Alle openstaande feedback afwijzen", + "description": "U staat op het punt **{count}** openstaande feedbackverzoeken af te wijzen. Afgewezen cycli blijven in uw geschiedenis staan, maar leveren geen nieuw leersignaal op. Dit kan niet ongedaan worden gemaakt.\n\n✅ Vink het onderstaande vakje aan en klik op **Verzenden** om alles af te wijzen.\n❌ Laat het uitgeschakeld en klik op **Verzenden** om te annuleren en terug te gaan.", + "data": { + "confirm_dismiss_all": "Bevestigen: alle openstaande feedbackverzoeken negeren" + } + }, + "resolve_feedback": { + "title": "Feedback verwerken", + "description": "{comparison_img}{candidates_table}\n**Gedetecteerd profiel**: {detected_profile} ({confidence_pct}%)\n**Geschatte duur**: {est_duration_min} min\n**Werkelijke duur**: {act_duration_min} min\n\n__Kies hieronder een actie:__", + "data": { + "action": "Wat wilt u doen?", + "corrected_profile": "Correct programma (indien gecorrigeerd)", + "corrected_duration": "Gecorrigeerde duur in seconden (indien gecorrigeerd)" + } + }, + "trim_cycle_select": { + "title": "Cyclus trimmen - Cyclus selecteren", + "description": "Selecteer een cyclus om te trimmen. ✓ = voltooid, ⚠ = hervat, ✗ = onderbroken", + "data": { + "cycle_id": "Cyclus" + } + }, + "trim_cycle": { + "title": "Cyclus trimmen", + "description": "Huidig venster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Actie" + } + }, + "trim_cycle_start": { + "title": "Trimbegin instellen", + "description": "Cyclusvenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nHuidige start: **{current_wallclock}** (+{current_offset_min} min vanaf cyclusstart)\n\nKies een nieuwe starttijd met behulp van de klokkiezer hieronder.", + "data": { + "trim_start_time": "Nieuwe starttijd", + "trim_start_min": "Nieuwe start (minuten vanaf het begin van de cyclus)" + } + }, + "trim_cycle_end": { + "title": "Trimeinde instellen", + "description": "Cyclusvenster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nHuidig einde: **{current_wallclock}** (+{current_offset_min} min vanaf cyclusstart)\n\nKies een nieuwe eindtijd met behulp van de klokkiezer hieronder.", + "data": { + "trim_end_time": "Nieuwe eindtijd", + "trim_end_min": "Nieuw einde (minuten vanaf het begin van de cyclus)" + } + } + }, + "error": { + "import_failed": "Importeren mislukt. Plak een geldige WashData-export-JSON.", + "select_exactly_one": "Selecteer precies één cyclus voor deze actie.", + "select_at_least_one": "Selecteer minimaal één cyclus voor deze actie.", + "select_at_least_two": "Selecteer minimaal twee cycli voor deze actie.", + "profile_exists": "Er bestaat al een profiel met deze naam", + "rename_failed": "Naam van profiel wijzigen mislukt. Het profiel bestaat mogelijk niet of de nieuwe naam is al in gebruik.", + "assignment_failed": "Toewijzen van profiel aan cyclus mislukt", + "duplicate_phase": "Er bestaat al een fase met deze naam.", + "phase_not_found": "Geselecteerde fase niet gevonden.", + "invalid_phase_name": "Fasenaam mag niet leeg zijn.", + "phase_name_too_long": "Fasenaam is te lang.", + "invalid_phase_range": "Fasebereik is ongeldig. Het einde moet na het begin liggen.", + "invalid_phase_timestamp": "Tijdstempel is ongeldig. Gebruik het formaat JJJJ-MM-DD UU:MM of UU:MM.", + "overlapping_phase_ranges": "Fasebereiken overlappen. Pas de bereiken aan en probeer opnieuw.", + "incomplete_phase_row": "Elke faserij moet een fase en volledige start-/eindwaarden voor de geselecteerde modus bevatten.", + "timestamp_mode_cycle_required": "Selecteer een broncyclus wanneer u de tijdstempelmodus gebruikt.", + "no_phase_ranges": "Er zijn nog geen fasebereiken beschikbaar voor die actie.", + "feedback_notification_title": "WashData: cyclus verifiëren ({device})", + "feedback_notification_message": "**Apparaat**: {device}\n**Programma**: {program} ({confidence}% betrouwbaarheid)\n**Tijd**: {time}\n\nWashData heeft uw hulp nodig om deze gedetecteerde cyclus te verifiëren.\n\nGa naar **Instellingen > Apparaten en services > WashData > Configureren > Leerfeedback** om dit resultaat te bevestigen of te corrigeren.", + "suggestions_ready_notification_title": "WashData: voorgestelde instellingen gereed ({device})", + "suggestions_ready_notification_message": "De sensor **Voorgestelde instellingen** rapporteert nu **{count}** bruikbare aanbevelingen.\n\nOm ze te bekijken en toe te passen: **Instellingen > Apparaten en services > WashData > Configureren > Geavanceerde instellingen > Voorgestelde waarden toepassen**.\n\nSuggesties zijn optioneel en worden ter beoordeling getoond voordat u ze opslaat.", + "trim_range_invalid": "Begin moet vóór het einde liggen. Pas uw trimpunten aan.", + "trim_failed": "Trimmen mislukt. De cyclus bestaat mogelijk niet meer of het venster is leeg.", + "invalid_split_timestamp": "Tijdstempel is ongeldig of valt buiten het cyclusvenster. Gebruik HH:MM of HH:MM:SS binnen het cyclusvenster.", + "no_split_segments_found": "Er konden geen geldige segmenten uit deze tijdstempels worden gemaakt. Zorg ervoor dat elke tijdstempel binnen het cyclusvenster valt en dat segmenten minstens 1 minuut lang zijn.", + "auto_tune_suggestion": "{device_type} {device_title} heeft spookcycli gedetecteerd. Voorgestelde min_power wijziging: {current_min}W -> {new_min}W (niet automatisch toegepast).", + "auto_tune_title": "WashData automatisch afstemmen", + "auto_tune_fallback": "{device_type} {device_title} heeft spookcycli gedetecteerd. Voorgestelde min_power wijziging: {current_min}W -> {new_min}W (niet automatisch toegepast)." + }, + "abort": { + "no_cycles_found": "Geen cycli gevonden", + "no_split_segments_found": "Er zijn geen splitsbare segmenten gevonden in de geselecteerde cyclus.", + "cycle_not_found": "De geselecteerde cyclus kon niet worden geladen.", + "no_power_data": "Geen vermogensspoorgegevens beschikbaar voor de geselecteerde cyclus.", + "no_profiles_found": "Geen profielen gevonden. Maak eerst een profiel aan.", + "no_profiles_for_matching": "Geen profielen beschikbaar voor matching. Maak eerst profielen aan.", + "no_unlabeled_cycles": "Alle cycli zijn al gelabeld", + "no_suggestions": "Nog geen voorgestelde waarden beschikbaar. Voer een paar cycli uit en probeer opnieuw.", + "no_predictions": "Geen voorspellingen", + "no_custom_phases": "Geen aangepaste fasen gevonden.", + "reprocess_success": "{count} cycli opnieuw verwerkt.", + "debug_data_cleared": "Debuggegevens van {count} cycli gewist." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cyclus gestart", + "cycle_finish": "Cyclus voltooid", + "cycle_live": "Live voortgang" + } + }, + "common_text": { + "options": { + "unlabeled": "(Ongelabeld)", + "create_new_profile": "Nieuw profiel maken", + "remove_label": "Label verwijderen", + "no_reference_cycle": "(Geen referentiecyclus)", + "all_device_types": "Alle apparaattypen", + "current_suffix": "(huidig)", + "editor_select_info": "Selecteer 1 cyclus om te splitsen, of 2+ cycli om samen te voegen.", + "editor_select_info_split": "Selecteer 1 cyclus om te splitsen.", + "editor_select_info_merge": "Selecteer 2 of meer cycli om samen te voegen.", + "editor_select_info_delete": "Selecteer 1 of meer cycli om te verwijderen.", + "no_cycles_recorded": "Er zijn nog geen cycli geregistreerd.", + "profile_summary_line": "- **{name}**: {count} cycli, {avg}m gem", + "no_profiles_created": "Er zijn nog geen profielen aangemaakt.", + "phase_preview": "Faseoverzicht", + "phase_preview_no_curve": "De gemiddelde profielcurve is nog niet beschikbaar. Voer meer cycli uit of label ze voor dit profiel.", + "average_power_curve": "Gemiddelde vermogenscurve", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cycli, ~{duration} min gem)", + "profile_option_short_fmt": "{name} ({count} cycli, ~{duration} min)", + "deleted_cycles_from_profile": "{count} cycli verwijderd van {profile}.", + "not_enough_data_graph": "Onvoldoende gegevens om grafiek te genereren.", + "no_profiles_found": "Geen profielen gevonden.", + "cycle_info_fmt": "Cyclus: {start}, {duration} min, Huidig: {label}", + "top_candidates_header": "### Topkandidaten", + "tbl_profile": "Profiel", + "tbl_confidence": "Betrouwbaarheid", + "tbl_correlation": "Correlatie", + "tbl_duration_match": "Duurovereenkomst", + "detected_profile": "Gedetecteerd profiel", + "estimated_duration": "Geschatte duur", + "actual_duration": "Werkelijke duur", + "choose_action": "__Kies hieronder een actie:__", + "tbl_count": "Aantal", + "tbl_avg": "Gem", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energie (gem)", + "tbl_energy_total": "Energie (totaal)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Laatste cyclus", + "graph_legend_title": "Grafieklegenda", + "graph_legend_body": "De blauwe band vertegenwoordigt het waargenomen minimale en maximale vermogensbereik. De lijn toont de gemiddelde vermogenscurve.", + "recording_preview": "Opnameoverzicht", + "trim_start": "Trim begin", + "trim_end": "Trim einde", + "split_preview_title": "Splitsingsoverzicht", + "split_preview_found_fmt": "{count} segmenten gevonden.", + "split_preview_confirm_fmt": "Klik op Bevestigen om deze cyclus op te splitsen in {count} afzonderlijke cycli.", + "merge_preview_title": "Samenvoegingsoverzicht", + "merge_preview_joining_fmt": "{count} cycli samenvoegen. Gaten worden gevuld met 0W-metingen.", + "editor_delete_preview_title": "Voorbeeld verwijderen", + "editor_delete_preview_intro": "De geselecteerde cycli worden permanent verwijderd:", + "editor_delete_preview_confirm": "Klik op Bevestigen om deze cyclusrecords permanent te verwijderen.", + "no_power_preview": "*Geen vermogensgegevens beschikbaar voor overzicht.*", + "profile_comparison": "Profielvergelijking", + "actual_cycle_label": "Deze cyclus (werkelijk)", + "storage_usage_header": "Opslaggebruik", + "storage_file_size": "Bestandsgrootte", + "storage_cycles": "Cycli", + "storage_profiles": "Profielen", + "storage_debug_traces": "Debugsporen", + "overview_suffix": "Overzicht", + "phase_preview_for_profile": "Faseoverzicht voor profiel", + "current_ranges_header": "Huidige bereiken", + "assign_phases_help": "Gebruik de onderstaande acties om bereiken toe te voegen, te bewerken, te verwijderen of op te slaan.", + "profile_summary_header": "Profieloverzicht", + "recent_cycles_header": "Recente cycli", + "trim_cycle_preview_title": "Cyclustrimoverzicht", + "trim_cycle_preview_no_data": "Geen vermogensgegevens beschikbaar voor deze cyclus.", + "trim_cycle_preview_kept_suffix": "bewaard", + "table_program": "Programma", + "table_when": "Wanneer", + "table_length": "Duur", + "table_match": "Overeenkomst", + "table_profile": "Profiel", + "table_cycles": "Cycli", + "table_avg_length": "Gem. duur", + "table_last_run": "Laatste cyclus", + "table_avg_energy": "Gem. energie", + "table_detected_program": "Gedetecteerd programma", + "table_confidence": "Betrouwbaarheid", + "table_reported": "Gemeld", + "phase_builtin_suffix": "(ingebouwd)", + "phase_none_available": "Geen fasen beschikbaar.", + "settings_deprecation_warning": "⚠️ **Verouderd apparaattype:** {device_type} zal in een toekomstige release worden verwijderd. De matchingpijplijn van WashData levert geen betrouwbare resultaten op voor deze apparaatklasse. Uw integratie blijft gedurende de beëindigingsperiode werken; Om deze waarschuwing uit te zetten, schakelt u **Apparaattype** hieronder over naar een van de ondersteunde typen (wasmachine, droger, was-droogcombinatie, vaatwasser, luchtfriteuse, broodbakmachine of pomp), of naar **Overig (geavanceerd)** als uw apparaat niet overeenkomt met een van de ondersteunde typen. **Overig (geavanceerd)** levert opzettelijk generieke standaardwaarden die niet zijn afgestemd op een specifiek apparaat, dus u zult zelf drempels, time-outs en overeenkomende parameters moeten configureren; al uw bestaande instellingen blijven behouden wanneer u overstapt.", + "phase_other_device_types": "Andere apparaattypen:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Cyclus splitsen (gaten zoeken)", + "merge": "Cycli samenvoegen (fragmenten combineren)", + "delete": "Cyclus(sen) verwijderen" + } + }, + "split_mode": { + "options": { + "auto": "Inactieve gaten automatisch detecteren", + "manual": "Handmatige tijdstempels" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Onderhoud: gegevens opnieuw verwerken en optimaliseren", + "clear_debug_data": "Debuggegevens wissen (ruimte vrijmaken)", + "wipe_history": "ALLE gegevens voor dit apparaat wissen (onomkeerbaar)", + "export_import": "JSON exporteren/importeren met instellingen (kopiëren/plakken)" + } + }, + "export_import_mode": { + "options": { + "export": "Alle gegevens exporteren", + "import": "Gegevens importeren/samenvoegen" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Oude cycli automatisch labelen", + "select_cycle_to_label": "Specifieke cyclus labelen", + "select_cycle_to_delete": "Cyclus verwijderen", + "interactive_editor": "Interactieve editor samenvoegen/splitsen", + "trim_cycle": "Cyclusgegevens trimmen" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Nieuw profiel maken", + "edit_profile": "Profiel bewerken/hernoemen", + "delete_profile": "Profiel verwijderen", + "profile_stats": "Profielstatistieken", + "cleanup_profile": "Geschiedenis opschonen - Grafiek & verwijderen", + "assign_phases": "Fasebereiken toewijzen" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Nieuwe fase maken", + "edit_custom_phase": "Fase bewerken", + "delete_custom_phase": "Fase verwijderen" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Fasebereik toevoegen", + "edit_range": "Fasebereik bewerken", + "delete_range": "Fasebereik verwijderen", + "clear_ranges": "Alle bereiken wissen", + "auto_detect_ranges": "Fasen automatisch detecteren", + "save_ranges": "Opslaan en terug" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Status vernieuwen", + "stop_recording": "Opname stoppen (opslaan en verwerken)", + "start_recording": "Nieuwe opname starten", + "process_recording": "Laatste opname verwerken (trimmen en opslaan)", + "discard_recording": "Laatste opname verwijderen" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Nieuw profiel maken", + "existing_profile": "Aan bestaand profiel toevoegen", + "discard": "Opname verwijderen" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bevestigen - Detectie correct", + "correct": "Corrigeren - Programma/duur kiezen", + "ignore": "Negeren - Vals positief/ruizige cyclus", + "delete": "Verwijderen - Uit geschiedenis verwijderen" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Fasen een naam geven en toepassen", + "cancel": "Terug zonder wijzigingen" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Beginpunt instellen", + "set_end": "Eindpunt instellen", + "reset": "Terugzetten naar volledige duur", + "apply": "Trim toepassen", + "cancel": "Annuleren" + } + }, + "device_type": { + "options": { + "washing_machine": "Wasmachine", + "dryer": "Droger", + "washer_dryer": "Was-droog combinatie", + "dishwasher": "Afwasmachine", + "coffee_machine": "Koffiezetapparaat (verouderd)", + "ev": "Elektrisch voertuig (verouderd)", + "air_fryer": "Lucht friteuse", + "heat_pump": "Warmtepomp (verouderd)", + "bread_maker": "Broodbakmachine", + "pump": "Pomp / Sumppomp", + "oven": "Oven (verouderd)", + "other": "Anders (geavanceerd)" + } + } + }, + "services": { + "label_cycle": { + "name": "Cyclus labelen", + "description": "Wijs een bestaand profiel toe aan een eerdere cyclus of verwijder het label.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om te labelen." + }, + "cycle_id": { + "name": "Cyclus-ID", + "description": "De ID van de te labelen cyclus." + }, + "profile_name": { + "name": "Profielnaam", + "description": "De naam van een bestaand profiel (maak profielen aan in het menu Profielen beheren). Laat leeg om het label te verwijderen." + } + } + }, + "create_profile": { + "name": "Profiel aanmaken", + "description": "Maak een nieuw profiel aan (zelfstandig of op basis van een referentiecyclus).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat." + }, + "profile_name": { + "name": "Profielnaam", + "description": "Naam voor het nieuwe profiel (bijv. 'Intensief', 'Fijne was')." + }, + "reference_cycle_id": { + "name": "Referentiecyclus-ID", + "description": "Optioneel cyclus-ID om dit profiel op te baseren." + } + } + }, + "delete_profile": { + "name": "Profiel verwijderen", + "description": "Verwijder een profiel en verwijder optioneel het label van cycli die het gebruiken.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat." + }, + "profile_name": { + "name": "Profielnaam", + "description": "Het te verwijderen profiel." + }, + "unlabel_cycles": { + "name": "Labels van cycli verwijderen", + "description": "Verwijder het profiellabel van cycli die dit profiel gebruiken." + } + } + }, + "auto_label_cycles": { + "name": "Oude cycli automatisch labelen", + "description": "Label ongelabelde cycli achteraf met behulp van profielmatching.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat." + }, + "confidence_threshold": { + "name": "Betrouwbaarheidsdrempel", + "description": "Minimale matchbetrouwbaarheid (0,50-0,95) om labels toe te passen." + } + } + }, + "export_config": { + "name": "Configuratie exporteren", + "description": "Exporteer de profielen, cycli en instellingen van dit apparaat naar een JSON-bestand (per apparaat).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om te exporteren." + }, + "path": { + "name": "Pad", + "description": "Optioneel absoluut bestandspad om naar te schrijven (standaard: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Configuratie importeren", + "description": "Importeer profielen, cycli en instellingen voor dit apparaat vanuit een JSON-exportbestand (instellingen kunnen worden overschreven).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om in te importeren." + }, + "path": { + "name": "Pad", + "description": "Absoluut pad naar het te importeren JSON-exportbestand." + } + } + }, + "submit_cycle_feedback": { + "name": "Cyclusfeedback indienen", + "description": "Bevestig of corrigeer een automatisch gedetecteerd programma na een voltooide cyclus. Geef `entry_id` (geavanceerd) of `device_id` (aanbevolen) op.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat (aanbevolen boven entry_id)." + }, + "entry_id": { + "name": "Invoer-ID", + "description": "De configuratie-invoer-ID voor het apparaat (alternatief voor device_id)." + }, + "cycle_id": { + "name": "Cyclus-ID", + "description": "De cyclus-ID zoals weergegeven in de feedbackmelding/logboeken." + }, + "user_confirmed": { + "name": "Gedetecteerd programma bevestigen", + "description": "Stel 'true' in als het gedetecteerde programma correct is." + }, + "corrected_profile": { + "name": "Gecorrigeerd profiel", + "description": "Als u niet bevestigt, geef dan de juiste profiel-/programmanaam op." + }, + "corrected_duration": { + "name": "Gecorrigeerde duur (seconden)", + "description": "Optioneel gecorrigeerde duur in seconden." + }, + "notes": { + "name": "Notities", + "description": "Optionele notities over deze cyclus." + } + } + }, + "record_start": { + "name": "Cyclusstart opnemen", + "description": "Begin handmatig een schone cyclus op te nemen (omzeilt alle matchinglogica).", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om op te nemen." + } + } + }, + "record_stop": { + "name": "Cyclusstop opnemen", + "description": "Handmatige opname stoppen.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om de opname te stoppen." + } + } + }, + "trim_cycle": { + "name": "Cyclus trimmen", + "description": "Trim de vermogensgegevens van een eerdere cyclus tot een specifiek tijdvenster.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat." + }, + "cycle_id": { + "name": "Cyclus-ID", + "description": "De ID van de te trimmen cyclus." + }, + "trim_start_s": { + "name": "Trim begin (seconden)", + "description": "Gegevens bewaren vanaf deze offset in seconden. Standaard 0." + }, + "trim_end_s": { + "name": "Trim einde (seconden)", + "description": "Gegevens bewaren tot deze offset in seconden. Standaard = volledige duur." + } + } + }, + "pause_cycle": { + "name": "Pauzeer cyclus", + "description": "Pauzeer de actieve cyclus voor een WashData-apparaat.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om te pauzeren." + } + } + }, + "resume_cycle": { + "name": "Hervat cyclus", + "description": "Hervat een gepauzeerde cyclus voor een WashData-apparaat.", + "fields": { + "device_id": { + "name": "Apparaat", + "description": "Het WashData-apparaat om te hervatten." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Actief" + }, + "match_ambiguity": { + "name": "Match-dubbelzinnigheid" + } + }, + "sensor": { + "washer_state": { + "name": "Status", + "state": { + "off": "Uit", + "idle": "Inactief", + "starting": "Opstarten", + "running": "Actief", + "paused": "Gepauzeerd", + "user_paused": "Gepauzeerd door gebruiker", + "ending": "Eindigen", + "finished": "Voltooid", + "anti_wrinkle": "Anti-kreuk", + "delay_wait": "Wachten om te beginnen", + "interrupted": "Onderbroken", + "force_stopped": "Geforceerd gestopt", + "rinse": "Spoelen", + "unknown": "Onbekend", + "clean": "Schoon" + } + }, + "washer_program": { + "name": "Programma" + }, + "time_remaining": { + "name": "Resterende tijd" + }, + "total_duration": { + "name": "Totale duur" + }, + "cycle_progress": { + "name": "Voortgang" + }, + "current_power": { + "name": "Huidig vermogen" + }, + "elapsed_time": { + "name": "Verstreken tijd" + }, + "debug_info": { + "name": "Debuginformatie" + }, + "match_confidence": { + "name": "Matchbetrouwbaarheid" + }, + "top_candidates": { + "name": "Topkandidaten", + "state": { + "none": "Geen" + } + }, + "current_phase": { + "name": "Huidige fase" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Profiel {profile_name} aantal", + "unit_of_measurement": "cycli" + }, + "suggestions": { + "name": "Beschikbare voorgestelde instellingen" + }, + "pump_runs_today": { + "name": "Pomp draait (laatste 24 uur)" + }, + "cycle_count": { + "name": "Cyclustelling" + } + }, + "select": { + "program_select": { + "name": "Cyclusprogramma", + "state": { + "auto_detect": "Automatisch detecteren" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Cyclus geforceerd beëindigen" + }, + "pause_cycle": { + "name": "Pauzeer cyclus" + }, + "resume_cycle": { + "name": "Hervat cyclus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Een geldige device_id is vereist." + }, + "cycle_id_required": { + "message": "Een geldige cyclus_id is vereist." + }, + "profile_name_required": { + "message": "Een geldige profielnaam is vereist." + }, + "device_not_found": { + "message": "Apparaat niet gevonden." + }, + "no_config_entry": { + "message": "Geen configuratie-invoer gevonden voor apparaat." + }, + "integration_not_loaded": { + "message": "Integratie niet geladen voor dit apparaat." + }, + "cycle_not_found_or_no_power": { + "message": "Cyclus niet gevonden of heeft geen vermogensgegevens." + }, + "trim_failed_empty_window": { + "message": "Trimmen mislukt - cyclus niet gevonden, geen vermogensgegevens of het resulterende venster is leeg." + }, + "trim_invalid_range": { + "message": "trim_end_s moet groter zijn dan trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/pl.json b/custom_components/ha_washdata/translations/pl.json new file mode 100644 index 0000000..5da1e2f --- /dev/null +++ b/custom_components/ha_washdata/translations/pl.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Konfiguracja WashData", + "description": "Skonfiguruj swoją pralkę lub inne urządzenie.\n\nWymagany jest czujnik mocy.\n\n**Następnie zostaniesz zapytany, czy chcesz utworzyć swój pierwszy profil.**", + "data": { + "name": "Nazwa urządzenia", + "device_type": "Typ urządzenia", + "power_sensor": "Czujnik mocy", + "min_power": "Minimalny próg mocy (W)" + }, + "data_description": { + "name": "Przyjazna nazwa tego urządzenia (np. „Pralka”, „Zmywarka”).", + "device_type": "Jakiego typu jest to urządzenie? Pomaga dostosować wykrywanie i etykietowanie.", + "power_sensor": "Encja czujnika, która raportuje zużycie energii w czasie rzeczywistym (w watach) przez inteligentną wtyczkę.", + "min_power": "Odczyty mocy powyżej tego progu (w watach) wskazują, że urządzenie pracuje. Zacznij od 2 W dla większości urządzeń." + } + }, + "first_profile": { + "title": "Utwórz pierwszy profil", + "description": "**Cykl** to pełny przebieg urządzenia (np. załadowanie prania). **Profil** grupuje te cykle według rodzaju (np. „Bawełna”, „Pranie szybkie”). Utworzenie profilu teraz umożliwia natychmiastowe szacowanie czasu trwania.\n\nMożesz ręcznie wybrać ten profil w elementach sterujących urządzenia, jeśli nie zostanie wykryty automatycznie.\n\n**Pozostaw pole „Nazwa profilu” puste, aby pominąć ten krok.**", + "data": { + "profile_name": "Nazwa profilu (opcjonalnie)", + "manual_duration": "Szacowany czas trwania (minuty)" + } + } + }, + "error": { + "cannot_connect": "Nie udało się połączyć", + "invalid_auth": "Nieprawidłowe uwierzytelnienie", + "unknown": "Nieoczekiwany błąd" + }, + "abort": { + "already_configured": "Urządzenie jest już skonfigurowane" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Przejrzyj opinie dotyczące uczenia", + "settings": "Ustawienia", + "notifications": "Powiadomienia", + "manage_cycles": "Zarządzaj cyklami", + "manage_profiles": "Zarządzaj profilami", + "manage_phase_catalog": "Zarządzaj katalogiem faz", + "record_cycle": "Nagraj cykl (ręcznie)", + "diagnostics": "Diagnostyka i konserwacja" + } + }, + "apply_suggestions": { + "title": "Kopiuj sugerowane wartości", + "description": "Spowoduje to skopiowanie bieżących sugerowanych wartości do zapisanych ustawień. Jest to czynność jednorazowa i nie powoduje automatycznego nadpisywania.\n\nSugerowane wartości do skopiowania:\n{suggested}", + "data": { + "confirm": "Potwierdź operację kopiowania" + } + }, + "apply_suggestions_confirm": { + "title": "Przejrzyj sugerowane zmiany", + "description": "WashData znalazł **{pending_count}** sugerowane wartości do zastosowania.\n\nZmiany:\n{changes}\n\n✅ Zaznacz pole poniżej i kliknij **Prześlij**, aby natychmiast zastosować i zapisać zmiany.\n❌ Pozostaw tę opcję niezaznaczoną i kliknij **Wyślij**, aby anulować i wrócić.", + "data": { + "confirm_apply_suggestions": "Zastosuj i zapisz te zmiany" + } + }, + "settings": { + "title": "Ustawienia", + "description": "{deprecation_warning}Dostosuj progi wykrywania, uczenie i zaawansowane zachowanie. Ustawienia domyślne działają dobrze dla większości konfiguracji.\n\nDostępne sugerowane ustawienia: {suggestions_count}.\nJeśli wartość jest powyżej 0, otwórz Ustawienia zaawansowane i użyj opcji Zastosuj sugerowane wartości, aby przejrzeć zalecenia.", + "data": { + "apply_suggestions": "Zastosuj sugerowane wartości", + "device_type": "Typ urządzenia", + "power_sensor": "Encja czujnika mocy", + "min_power": "Minimalna moc (W)", + "off_delay": "Opóźnienie zakończenia cyklu (s)", + "notify_actions": "Akcje powiadomień", + "notify_people": "Opóźnij dostarczenie, aż te osoby wrócą do domu", + "notify_only_when_home": "Opóźnij powiadomienia, aż wybrana osoba będzie w domu", + "notify_fire_events": "Wyzwalaj zdarzenia automatyzacji", + "notify_start_services": "Rozpoczęcie cyklu - Cele powiadomień", + "notify_finish_services": "Zakończenie cyklu - Cele powiadomień", + "notify_live_services": "Postęp na żywo - cele powiadomień", + "notify_before_end_minutes": "Powiadomienie przed zakończeniem (minuty przed końcem)", + "notify_title": "Tytuł powiadomienia", + "notify_icon": "Ikona powiadomienia", + "notify_start_message": "Format wiadomości startowej", + "notify_finish_message": "Format wiadomości końcowej", + "notify_pre_complete_message": "Format wiadomości przed zakończeniem", + "show_advanced": "Edytuj ustawienia zaawansowane (następny krok)" + }, + "data_description": { + "apply_suggestions": "Zaznacz to pole, aby skopiować do formularza wartości sugerowane przez algorytm uczący. Przejrzyj przed zapisaniem.\n\nSugerowane (z algorytmu uczącego):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Wybierz typ urządzenia (pralka, suszarka, zmywarka, ekspres do kawy, EV). Ten znacznik jest zapisywany przy każdym cyklu dla lepszej organizacji.", + "power_sensor": "Encja czujnika raportująca moc w czasie rzeczywistym (w watach). Możesz to zmienić w dowolnym momencie bez utraty danych historycznych - przydatne przy wymianie inteligentnej wtyczki.", + "min_power": "Odczyty mocy powyżej tego progu (w watach) wskazują, że urządzenie pracuje. Zacznij od 2 W dla większości urządzeń.", + "off_delay": "Wygładzona moc musi przez tyle sekund pozostać poniżej progu zatrzymania, aby oznaczyć zakończenie. Domyślnie: 120 s.", + "notify_actions": "Opcjonalna sekwencja akcji Home Assistant do uruchomienia przy powiadomieniach (skrypty, notify, TTS itp.). Jeśli ustawione, akcje są uruchamiane przed zapasową usługą powiadomień.", + "notify_people": "Encje osób używane do kontroli obecności. Gdy włączone, dostarczanie powiadomień jest opóźniane do czasu, aż przynajmniej jedna wybrana osoba wróci do domu.", + "notify_only_when_home": "Jeśli włączone, opóźnia powiadomienia do czasu, aż przynajmniej jedna wybrana osoba będzie w domu.", + "notify_fire_events": "Jeśli włączone, wyzwala zdarzenia Home Assistant na początku i końcu cyklu (ha_washdata_cycle_started i ha_washdata_cycle_ended) do napędzania automatyzacji.", + "notify_start_services": "Jedna lub więcej usług powiadamiających o rozpoczęciu cyklu. Pozostaw puste, aby pominąć powiadomienia o uruchomieniu.", + "notify_finish_services": "Jedna lub więcej usług powiadamiających o zakończeniu cyklu lub jego zbliżaniu się do końca. Pozostaw puste, aby pominąć powiadomienia o zakończeniu.", + "notify_live_services": "Jedna lub więcej usług powiadamiania o konieczności otrzymywania aktualnych informacji o postępie podczas trwania cyklu (tylko aplikacja mobilna). Pozostaw puste, aby pominąć aktualizacje na żywo.", + "notify_before_end_minutes": "Minuty przed przewidywanym końcem cyklu, aby wywołać powiadomienie. Ustaw 0, aby wyłączyć. Domyślnie: 0.", + "notify_title": "Niestandardowy tytuł powiadomień. Obsługuje symbol zastępczy {device}.", + "notify_icon": "Ikona używana do powiadomień (np. mdi:washing-machine). Pozostaw puste, aby wysłać bez ikony.", + "notify_start_message": "Wiadomość wysyłana po rozpoczęciu cyklu. Obsługuje symbol zastępczy {device}.", + "notify_finish_message": "Wiadomość wysyłana po zakończeniu cyklu. Obsługuje symbole zastępcze {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Tekst cyklicznych aktualizacji postępu na żywo podczas trwania cyklu. Obsługuje symbole zastępcze {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Powiadomienia", + "description": "Skonfiguruj kanały powiadomień, szablony i opcjonalne aktualizacje postępu na żywo na urządzeniach mobilnych.", + "data": { + "notify_actions": "Akcje powiadomień", + "notify_people": "Opóźnij dostarczenie, aż te osoby wrócą do domu", + "notify_only_when_home": "Opóźnij powiadomienia, aż wybrana osoba będzie w domu", + "notify_fire_events": "Wyzwalaj zdarzenia automatyzacji", + "notify_start_services": "Rozpoczęcie cyklu - Cele powiadomień", + "notify_finish_services": "Zakończenie cyklu - Cele powiadomień", + "notify_live_services": "Postęp na żywo - cele powiadomień", + "notify_before_end_minutes": "Powiadomienie przed zakończeniem (minuty przed końcem)", + "notify_live_interval_seconds": "Interwał aktualizacji na żywo (sekundy)", + "notify_live_overrun_percent": "Dopuszczalne przekroczenie aktualizacji na żywo (%)", + "notify_live_chronometer": "Minutnik chronometru", + "notify_title": "Tytuł powiadomienia", + "notify_icon": "Ikona powiadomienia", + "notify_start_message": "Format wiadomości startowej", + "notify_finish_message": "Format wiadomości końcowej", + "notify_pre_complete_message": "Format wiadomości przed zakończeniem", + "energy_price_entity": "Cena energii - jednostka (opcjonalnie)", + "energy_price_static": "Cena energii - wartość statyczna (opcjonalnie)", + "go_back": "Wróć bez zapisywania", + "notify_reminder_message": "Format wiadomości przypomnienia", + "notify_timeout_seconds": "Automatyczne odrzucanie po (sekundach)", + "notify_channel": "Kanał powiadomień (status/na żywo/przypomnienie)", + "notify_finish_channel": "Zakończony kanał powiadomień" + }, + "data_description": { + "go_back": "Zaznacz to pole i kliknij Prześlij, aby odrzucić wszystkie powyższe zmiany i wrócić do menu głównego.", + "notify_actions": "Opcjonalna sekwencja akcji Home Assistant do uruchomienia przy powiadomieniach (skrypty, notify, TTS itp.). Jeśli ustawione, akcje są uruchamiane przed zapasową usługą powiadomień.", + "notify_people": "Encje osób używane do kontroli obecności. Gdy włączone, dostarczanie powiadomień jest opóźniane do czasu, aż przynajmniej jedna wybrana osoba wróci do domu.", + "notify_only_when_home": "Jeśli włączone, opóźnia powiadomienia do czasu, aż przynajmniej jedna wybrana osoba będzie w domu.", + "notify_fire_events": "Jeśli włączone, wyzwala zdarzenia Home Assistant na początku i końcu cyklu (ha_washdata_cycle_started i ha_washdata_cycle_ended) do napędzania automatyzacji.", + "notify_start_services": "Jedna lub więcej usług powiadamiających o rozpoczęciu cyklu (np. notify.mobile_app_my_phone). Pozostaw puste, aby pominąć powiadomienia o uruchomieniu.", + "notify_finish_services": "Jedna lub więcej usług powiadamiających o zakończeniu cyklu lub jego zbliżaniu się do końca. Pozostaw puste, aby pominąć powiadomienia o zakończeniu.", + "notify_live_services": "Jedna lub więcej usług powiadamiania o konieczności otrzymywania aktualnych informacji o postępie podczas trwania cyklu (tylko aplikacja mobilna). Pozostaw puste, aby pominąć aktualizacje na żywo.", + "notify_before_end_minutes": "Minuty przed przewidywanym końcem cyklu, aby wywołać powiadomienie. Ustaw 0, aby wyłączyć. Domyślnie: 0.", + "notify_live_interval_seconds": "Jak często aktualizacje postępu są wysyłane podczas pracy. Niższe wartości są bardziej responsywne, ale zużywają więcej powiadomień. Wymuszone jest minimum 30 sekund - wartości poniżej 30 s są automatycznie zaokrąglane do 30 s.", + "notify_live_overrun_percent": "Margines bezpieczeństwa powyżej szacowanej liczby aktualizacji dla długich lub przekraczających cykli (np. 20% zezwala na 1,2× szacowanych aktualizacji).", + "notify_live_chronometer": "Po włączeniu każda aktualizacja na żywo zawiera odliczanie chronometru do szacowanego czasu zakończenia. Licznik powiadomień odlicza automatycznie czas na urządzeniu pomiędzy aktualizacjami (tylko aplikacja towarzysząca na Androida).", + "notify_title": "Niestandardowy tytuł powiadomień. Obsługuje symbol zastępczy {device}.", + "notify_icon": "Ikona używana do powiadomień (np. mdi:washing-machine). Pozostaw puste, aby wysłać bez ikony.", + "notify_start_message": "Wiadomość wysyłana po rozpoczęciu cyklu. Obsługuje symbol zastępczy {device}.", + "notify_finish_message": "Wiadomość wysyłana po zakończeniu cyklu. Obsługuje symbole zastępcze {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Wybierz jednostkę liczbową zawierającą aktualną cenę energii elektrycznej (np. czujnik.cena_elektryczności, numer_wejściowy.stawka_energii). Po ustawieniu włącza symbol zastępczy {cost}. Ma pierwszeństwo przed wartością statyczną, jeśli obie są skonfigurowane.", + "energy_price_static": "Stała cena energii elektrycznej za kWh. Po ustawieniu włącza symbol zastępczy {cost}. Ignorowane, jeśli jednostka jest skonfigurowana powyżej. Dodaj symbol swojej waluty w szablonie wiadomości, np. {cost} €.", + "notify_reminder_message": "Tekst jednorazowego przypomnienia wysyłany na skonfigurowaną liczbę minut przed przewidywanym zakończeniem. Różni się od powtarzających się aktualizacji na żywo powyżej. Obsługuje symbole zastępcze {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Automatycznie odrzucaj powiadomienia po tylu sekundach (przekazywane jako „przekroczenie limitu czasu” aplikacji towarzyszącej). Ustaw na 0, aby zachować je do momentu zwolnienia. Wartość domyślna: 0.", + "notify_channel": "Kanał powiadomień aplikacji towarzyszącej Android, zawierający powiadomienia o statusie, postępie na żywo i przypomnieniach. Dźwięk i ważność kanału konfiguruje się w aplikacji towarzyszącej przy pierwszym użyciu nazwy kanału – WashData ustawia jedynie nazwę kanału. Pozostaw puste, aby ustawić domyślną aplikację.", + "notify_finish_channel": "Oddzielny kanał Android dla powiadomienia o zakończeniu (wykorzystywany również przez przypomnienie i sygnał oczekiwania na pranie), aby mógł mieć własny dźwięk. Pozostaw puste, aby ponownie wykorzystać powyższy kanał.", + "notify_pre_complete_message": "Tekst cyklicznych aktualizacji postępu na żywo podczas trwania cyklu. Obsługuje symbole zastępcze {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Ustawienia zaawansowane", + "description": "Dostosuj progi wykrywania, uczenie i zaawansowane zachowanie. Ustawienia domyślne działają dobrze dla większości konfiguracji.", + "sections": { + "suggestions_section": { + "name": "Sugerowane ustawienia", + "description": "Dostępnych jest {suggestions_count} wyuczonych sugestii. Zaznacz 'Zastosuj sugerowane wartości', aby przed zapisaniem przejrzeć proponowane zmiany.", + "data": { + "apply_suggestions": "Zastosuj sugerowane wartości" + }, + "data_description": { + "apply_suggestions": "Zaznacz to pole, aby otworzyć krok przeglądu sugerowanych wartości z algorytmu uczącego. Nic nie jest zapisywane automatycznie.\n\nSugerowane (z algorytmu uczącego):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Wykrywanie i progi mocy", + "description": "Określa, kiedy urządzenie jest uznawane za WŁĄCZONE lub WYŁĄCZONE, bramki energii oraz czasy przejść między stanami.", + "data": { + "start_duration_threshold": "Czas trwania debouncingu startu (s)", + "start_energy_threshold": "Brama energetyczna startu (Wh)", + "completion_min_seconds": "Zakończenie: minimalny czas pracy (sekundy)", + "start_threshold_w": "Próg startowy (W)", + "stop_threshold_w": "Próg zatrzymania (W)", + "end_energy_threshold": "Brama energetyczna końca (Wh)", + "running_dead_zone": "Martwa strefa po starcie (sekundy)", + "end_repeat_count": "Liczba powtórzeń warunku końca", + "min_off_gap": "Min. przerwa między cyklami (s)", + "sampling_interval": "Interwał próbkowania (s)" + }, + "data_description": { + "start_duration_threshold": "Ignoruj krótkie skoki mocy krótsze niż ten czas (sekundy). Filtruje skoki rozruchu (np. 60 W przez 2 s) przed rozpoczęciem właściwego cyklu. Domyślnie: 5 s.", + "start_energy_threshold": "Cykl musi zgromadzić co najmniej tyle energii (Wh) w fazie STARTU, aby zostać potwierdzony. Domyślnie: 0,005 Wh.", + "completion_min_seconds": "Cykle krótsze niż ten czas zostaną oznaczone jako „przerwane”, nawet jeśli zakończą się naturalnie. Domyślnie: 600 s.", + "start_threshold_w": "Moc musi wzrosnąć POWYŻEJ tego progu, aby ROZPOCZĄĆ cykl. Ustaw wyżej niż moc w trybie czuwania. Domyślnie: min_power + 1 W.", + "stop_threshold_w": "Moc musi spaść PONIŻEJ tego progu, aby ZAKOŃCZYĆ cykl. Ustaw nieco powyżej zera, aby szum nie wyzwalał fałszywych zakończeń. Domyślnie: 0,6 × min_power.", + "end_energy_threshold": "Cykl kończy się tylko wtedy, gdy energia w ostatnim oknie opóźnienia wyłączenia spadnie poniżej tej wartości (Wh). Zapobiega fałszywym zakończeniom, gdy moc waha się blisko zera. Domyślnie: 0,05 Wh.", + "running_dead_zone": "Po rozpoczęciu cyklu ignoruj spadki mocy przez tyle sekund, aby zapobiec fałszywemu wykryciu końca podczas fazy rozruchu. Ustaw 0, aby wyłączyć. Domyślnie: 0 s.", + "end_repeat_count": "Liczba kolejnych spełnień warunku końca (moc poniżej progu przez czas opóźnienia wyłączenia) wymagana przed zakończeniem cyklu. Wyższe wartości zapobiegają fałszywym zakończeniom podczas pauz. Domyślnie: 1.", + "min_off_gap": "Jeśli cykl rozpocznie się w ciągu tylu sekund od zakończenia poprzedniego, zostaną POŁĄCZONE w jeden cykl.", + "sampling_interval": "Minimalny interwał próbkowania w sekundach. Aktualizacje szybsze niż ta częstotliwość będą ignorowane. Domyślnie: 30 s (2 s dla pralek, pralko-suszarek i zmywarek)." + } + }, + "matching_section": { + "name": "Dopasowanie profili i uczenie", + "description": "Określa, jak agresywnie WashData dopasowuje trwające cykle do wyuczonych profili i kiedy prosić o opinię.", + "data": { + "profile_match_min_duration_ratio": "Minimalny współczynnik czasu trwania dopasowania profilu (0,1–1,0)", + "profile_match_interval": "Interwał dopasowania profilu (sekundy)", + "profile_match_threshold": "Próg dopasowania profilu", + "profile_unmatch_threshold": "Próg braku dopasowania profilu", + "auto_label_confidence": "Pewność automatycznego etykietowania (0-1)", + "learning_confidence": "Pewność żądania opinii (0-1)", + "suppress_feedback_notifications": "Pomiń powiadomienia o opiniach", + "duration_tolerance": "Tolerancja czasu trwania (0-0,5)", + "smoothing_window": "Okno wygładzania (próbki)", + "profile_duration_tolerance": "Tolerancja czasu trwania dopasowania profilu (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimalny współczynnik czasu trwania do dopasowania profilu. Bieżący cykl musi obejmować co najmniej tę część czasu trwania profilu, aby dopasować. Niższy = wcześniejsze dopasowanie. Domyślnie: 0,50 (50%).", + "profile_match_interval": "Jak często (w sekundach) próbować dopasowania profilu podczas cyklu. Niższe wartości zapewniają szybsze wykrywanie, ale zużywają więcej CPU. Domyślnie: 300 s (5 minut).", + "profile_match_threshold": "Minimalny wynik podobieństwa DTW (0,0–1,0) wymagany do uznania profilu za dopasowany. Wyższy = ściślejsze dopasowanie, mniej wyników fałszywie dodatnich. Domyślnie: 0,4.", + "profile_unmatch_threshold": "Wynik podobieństwa DTW (0,0–1,0), poniżej którego wcześniej dopasowany profil jest odrzucany. Powinien być niższy niż próg dopasowania, aby zapobiec migotaniu. Domyślnie: 0,35.", + "auto_label_confidence": "Po zakończeniu automatycznie etykietuj cykle na tym poziomie pewności lub wyższym (0,0–1,0).", + "learning_confidence": "Minimalna pewność do żądania weryfikacji użytkownika przez zdarzenie (0,0–1,0).", + "suppress_feedback_notifications": "Po włączeniu WashData będzie nadal wewnętrznie śledzić i żądać informacji zwrotnych, ale nie będzie wyświetlać trwałych powiadomień z prośbą o potwierdzenie cykli. Przydatne, gdy cykle są zawsze wykrywane prawidłowo, a monity rozpraszają.", + "duration_tolerance": "Tolerancja dla szacunków pozostałego czasu podczas cyklu (0,0–0,5 odpowiada 0–50%).", + "smoothing_window": "Liczba ostatnich odczytów mocy używanych do wygładzania średnią kroczącą.", + "profile_duration_tolerance": "Tolerancja stosowana przez dopasowanie profilu dla całkowitej wariancji czasu trwania (0,0–0,5). Domyślne zachowanie: ±25%." + } + }, + "timing_section": { + "name": "Czas, konserwacja i debugowanie", + "description": "Watchdog, reset postępu, automatyczna konserwacja i udostępnianie encji debugujących.", + "data": { + "watchdog_interval": "Interwał watchdoga (sekundy)", + "no_update_active_timeout": "Limit czasu braku aktualizacji (s)", + "progress_reset_delay": "Opóźnienie resetowania postępu (sekundy)", + "auto_maintenance": "Włącz automatyczną konserwację", + "expose_debug_entities": "Pokaż encje debugowania", + "save_debug_traces": "Zapisz ślady debugowania" + }, + "data_description": { + "watchdog_interval": "Sekundy między kontrolami watchdoga podczas pracy. Domyślnie: 5 s. OSTRZEŻENIE: Upewnij się, że jest WYŻSZY niż interwał aktualizacji czujnika, aby uniknąć fałszywych zatrzymań (np. jeśli czujnik aktualizuje się co 60 s, użyj 65 s+).", + "no_update_active_timeout": "Dla czujników publikujących tylko przy zmianie: wymuś zakończenie AKTYWNEGO cyklu tylko wtedy, gdy przez tyle sekund nie nadeszły żadne aktualizacje. Zakończenie przy niskiej mocy nadal korzysta z opóźnienia wyłączenia.", + "progress_reset_delay": "Po zakończeniu cyklu (100%) odczekaj tyle sekund bezczynności, zanim zresetujesz postęp do 0%. Domyślnie: 300 s.", + "auto_maintenance": "Włącz automatyczną konserwację (napraw próbki, scalaj fragmenty).", + "expose_debug_entities": "Pokaż zaawansowane czujniki (pewność, faza, niejednoznaczność) do debugowania.", + "save_debug_traces": "Przechowuj szczegółowe dane rankingowe i ślady mocy w historii (zwiększa zużycie pamięci)." + } + }, + "anti_wrinkle_section": { + "name": "Tarcza antygniotowa (suszarki)", + "description": "Ignoruj obroty bębna po zakończeniu cyklu, aby zapobiec cyklom-duchom. Tylko suszarka / pralko-suszarka.", + "data": { + "anti_wrinkle_enabled": "Tarcza antygniotowa (tylko suszarka/pralko-suszarka)", + "anti_wrinkle_max_power": "Maks. moc antygniotowa (W) (tylko suszarka/pralko-suszarka)", + "anti_wrinkle_max_duration": "Maks. czas trwania antygniotowy (s) (tylko suszarka/pralko-suszarka)", + "anti_wrinkle_exit_power": "Moc wyjścia z trybu antygniotowego (W) (tylko suszarka/pralko-suszarka)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignoruj krótkie obroty bębna przy niskiej mocy po zakończeniu cyklu, aby zapobiec cyklom-duchom (tylko suszarka/pralko-suszarka).", + "anti_wrinkle_max_power": "Impulsy na tym poziomie mocy lub poniżej są traktowane jako obroty antygniotowe, a nie nowe cykle. Dotyczy tylko wtedy, gdy Tarcza antygniotowa jest włączona.", + "anti_wrinkle_max_duration": "Maksymalna długość impulsu traktowanego jako obrót antygniotowy. Dotyczy tylko wtedy, gdy Tarcza antygniotowa jest włączona.", + "anti_wrinkle_exit_power": "Jeśli moc spadnie poniżej tego poziomu, natychmiast zakończ tryb antygniotowy (prawdziwe wyłączenie). Dotyczy tylko wtedy, gdy Tarcza antygniotowa jest włączona." + } + }, + "delay_start_section": { + "name": "Wykrywanie opóźnionego startu", + "description": "Traktuj utrzymujący się stan gotowości o niskim poborze mocy jako stan 'Oczekiwanie na start', dopóki cykl faktycznie się nie rozpocznie.", + "data": { + "delay_start_detect_enabled": "Włącz wykrywanie opóźnionego startu", + "delay_confirm_seconds": "Opóźniony start: czas potwierdzenia stanu gotowości (s)", + "delay_timeout_hours": "Opóźniony start: Maksymalny czas oczekiwania (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Po włączeniu krótki skok poboru mocy przy niskim poborze mocy, po którym następuje utrzymujące się zasilanie w trybie gotowości, jest wykrywany jako opóźniony start. Podczas opóźnienia czujnik stanu wyświetla komunikat „Oczekiwanie na start”. Do momentu faktycznego rozpoczęcia cyklu nie jest śledzony czas trwania programu ani jego postęp. Wymaga ustawienia start_threshold_w powyżej szczytowej mocy drenu.", + "delay_confirm_seconds": "Moc musi pozostawać między progiem zatrzymania (W) a progiem startu (W) przez tyle sekund, zanim WashData przejdzie do stanu 'Oczekiwanie na start'. Odfiltrowuje to krótkie skoki podczas nawigacji po menu. Domyślnie: 60 s.", + "delay_timeout_hours": "Limit czasu bezpieczeństwa: jeśli po upływie tylu godzin urządzenie nadal znajduje się w stanie „Oczekiwanie na uruchomienie”, funkcja WashData zostaje zresetowana do ustawienia Wyłączone. Wartość domyślna: 8 godz." + } + }, + "external_triggers_section": { + "name": "Zewnętrzne wyzwalacze, drzwi i pauza", + "description": "Opcjonalny zewnętrzny czujnik zakończenia cyklu, czujnik drzwi do wykrywania pauzy / opróżnienia oraz przełącznik odcinający zasilanie podczas pauzy.", + "data": { + "external_end_trigger_enabled": "Włącz zewnętrzny wyzwalacz zakończenia cyklu", + "external_end_trigger": "Zewnętrzny czujnik końca cyklu", + "external_end_trigger_inverted": "Odwróć logikę wyzwalania (wyzwalaj przy WYŁĄCZENIU)", + "door_sensor_entity": "Czujnik drzwi", + "pause_cuts_power": "Odetnij zasilanie podczas pauzy", + "switch_entity": "Jednostka przełączająca (w celu wstrzymania przerwy w dostawie prądu)", + "notify_unload_delay_minutes": "Opóźnienie powiadomienia o oczekiwaniu na pranie (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Włącz monitorowanie zewnętrznego czujnika binarnego, aby wyzwolić zakończenie cyklu.", + "external_end_trigger": "Wybierz encję czujnika binarnego. Po uruchomieniu tego czujnika bieżący cykl zakończy się ze statusem „zakończony”.", + "external_end_trigger_inverted": "Domyślnie wyzwalacz uruchamia się, gdy czujnik się WŁĄCZY. Zaznacz to pole, aby wyzwalać, gdy czujnik się WYŁĄCZY.", + "door_sensor_entity": "Opcjonalny czujnik binarny drzwi maszyny (wł. = otwarte, wył. = zamknięte). Kiedy drzwiczki zostaną otwarte podczas aktywnego cyklu, WashData potwierdzi, że cykl został celowo wstrzymany. Jeśli po zakończeniu cyklu drzwi są nadal zamknięte, stan zmienia się na „Czyszczenie” do czasu otwarcia drzwi. Uwaga: zamknięcie drzwi NIE powoduje automatycznego wznowienia wstrzymanego cyklu - wznowienie należy uruchomić ręcznie za pomocą przycisku Wznów cykl lub za pomocą usługi.", + "pause_cuts_power": "Wstrzymując się za pomocą przycisku lub usługi Wstrzymaj cykl, wyłącz także skonfigurowaną jednostkę przełączającą. Działa tylko wtedy, gdy dla tego urządzenia skonfigurowano jednostkę przełączającą.", + "switch_entity": "Jednostka przełączająca do przełączania podczas wstrzymywania lub wznawiania. Wymagane, gdy włączona jest opcja „Odetnij zasilanie podczas pauzy”.", + "notify_unload_delay_minutes": "Wyślij powiadomienie za pośrednictwem kanału powiadomień o zakończeniu, gdy cykl się zakończy, a drzwi nie zostaną otwarte przez tyle minut (wymaga czujnika drzwi). Ustaw na 0, aby wyłączyć. Domyślnie: 60 minut." + } + }, + "pump_section": { + "name": "Monitor pompy", + "description": "Tylko dla urządzeń typu pompa. Wywołuje zdarzenie, jeśli cykl pompy trwa dłużej niż ten czas.", + "data": { + "pump_stuck_duration": "Próg alarmu o zablokowaniu pompy (tylko pompa)" + }, + "data_description": { + "pump_stuck_duration": "Jeżeli po tylu sekundach cykl pompy nadal trwa, jednokrotnie uruchamiane jest zdarzenie ha_washdata_pump_stuck. Użyj tego, aby wykryć zablokowany silnik (typowy czas pracy pompy ściekowej wynosi mniej niż 60 s). Domyślnie: 1800 s (30 min). Tylko typ urządzenia pompującego." + } + }, + "device_link_section": { + "name": "Połączenie urządzenia", + "description": "Opcjonalnie połącz to urządzenie WashData z istniejącym urządzeniem Home Assistant (na przykład inteligentną wtyczką lub samym urządzeniem). Urządzenie WashData zostanie wówczas pokazane jako „Połączone przez” to urządzenie. Pozostaw puste, aby WashData pozostała samodzielnym urządzeniem.", + "data": { + "linked_device": "Połączone urządzenie" + }, + "data_description": { + "linked_device": "Wybierz urządzenie, z którym chcesz połączyć WashData. Spowoduje to dodanie odniesienia „Połączono przez” w rejestrze urządzeń; WashData utrzymuje własną kartę urządzenia i podmioty. Wyczyść zaznaczenie, aby usunąć łącze." + } + } + } + }, + "diagnostics": { + "title": "Diagnostyka i konserwacja", + "description": "Wykonuj czynności konserwacyjne, takie jak scalanie fragmentarycznych cykli lub migracja przechowywanych danych.\n\n**Wykorzystanie miejsca**\n\n- Rozmiar pliku: {file_size_kb} KB\n- Cykle: {cycle_count}\n- Profile: {profile_count}\n- Ślady debugowania: {debug_count}", + "menu_options": { + "reprocess_history": "Konserwacja: ponowne przetwarzanie i optymalizacja danych", + "clear_debug_data": "Wyczyść dane debugowania (zwolnij miejsce)", + "wipe_history": "Usuń WSZYSTKIE dane z tego urządzenia (nieodwracalne)", + "export_import": "Eksportuj/importuj JSON z ustawieniami (kopiuj/wklej)", + "menu_back": "← Wstecz" + } + }, + "clear_debug_data": { + "title": "Wyczyść dane debugowania", + "description": "Czy na pewno chcesz usunąć wszystkie zapisane ślady debugowania? Spowoduje to zwolnienie miejsca, ale usunie szczegółowe informacje rankingowe z poprzednich cykli." + }, + "manage_cycles": { + "title": "Zarządzaj cyklami", + "description": "Ostatnie cykle:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatyczne etykietowanie starych cykli", + "select_cycle_to_label": "Etykietuj konkretny cykl", + "select_cycle_to_delete": "Usuń cykl", + "interactive_editor": "Interaktywny edytor scalania/podziału", + "trim_cycle_select": "Przytnij dane cyklu", + "menu_back": "← Wstecz" + } + }, + "manage_cycles_empty": { + "title": "Zarządzaj cyklami", + "description": "Nie zapisano jeszcze żadnych cykli.", + "menu_options": { + "auto_label_cycles": "Automatyczne etykietowanie starych cykli", + "menu_back": "← Wstecz" + } + }, + "manage_profiles": { + "title": "Zarządzaj profilami", + "description": "Podsumowanie profili:\n{profile_summary}", + "menu_options": { + "create_profile": "Utwórz nowy profil", + "edit_profile": "Edytuj/zmień nazwę profilu", + "delete_profile_select": "Usuń profil", + "profile_stats": "Statystyki profilu", + "cleanup_profile": "Czyszczenie historii - wykres i usuwanie", + "assign_profile_phases_select": "Przypisz zakresy faz", + "menu_back": "← Wstecz" + } + }, + "manage_profiles_empty": { + "title": "Zarządzaj profilami", + "description": "Nie utworzono jeszcze żadnych profili.", + "menu_options": { + "create_profile": "Utwórz nowy profil", + "menu_back": "← Wstecz" + } + }, + "manage_phase_catalog": { + "title": "Zarządzaj katalogiem faz", + "description": "Katalog faz (domyślne dla tego urządzenia + fazy niestandardowe):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Utwórz nową fazę", + "phase_catalog_edit_select": "Edytuj fazę", + "phase_catalog_delete": "Usuń fazę", + "menu_back": "← Wstecz" + } + }, + "phase_catalog_create": { + "title": "Utwórz fazę niestandardową", + "description": "Utwórz niestandardową nazwę i opis fazy oraz wybierz typ urządzenia, do którego ma zastosowanie.", + "data": { + "target_device_type": "Docelowy typ urządzenia", + "phase_name": "Nazwa fazy", + "phase_description": "Opis fazy" + } + }, + "phase_catalog_edit_select": { + "title": "Edytuj fazę niestandardową", + "description": "Wybierz fazę niestandardową do edycji.", + "data": { + "phase_name": "Faza niestandardowa" + } + }, + "phase_catalog_edit": { + "title": "Edytuj fazę niestandardową", + "description": "Edycja fazy: {phase_name}", + "data": { + "phase_name": "Nazwa fazy", + "phase_description": "Opis fazy" + } + }, + "phase_catalog_delete": { + "title": "Usuń fazę niestandardową", + "description": "Wybierz fazę niestandardową do usunięcia. Wszelkie przypisane zakresy korzystające z tej fazy zostaną usunięte.", + "data": { + "phase_name": "Faza niestandardowa" + } + }, + "assign_profile_phases": { + "title": "Przypisz zakresy faz", + "description": "Podgląd fazy dla profilu: **{profile_name}**\n\n{timeline_svg}\n\n**Aktualne zakresy:**\n{current_ranges}\n\nUżyj poniższych akcji, aby dodawać, edytować, usuwać lub zapisywać zakresy.", + "menu_options": { + "assign_profile_phases_add": "Dodaj zakres fazy", + "assign_profile_phases_edit_select": "Edytuj zakres fazy", + "assign_profile_phases_delete": "Usuń zakres fazy", + "phase_ranges_clear": "Wyczyść wszystkie zakresy", + "assign_profile_phases_auto_detect": "Automatycznie wykryj fazy", + "phase_ranges_save": "Zapisz i wróć", + "menu_back": "← Wstecz" + } + }, + "assign_profile_phases_add": { + "title": "Dodaj zakres fazy", + "description": "Dodaj jeden zakres fazy do bieżącego szkicu.", + "data": { + "phase_name": "Faza", + "start_min": "Minuta rozpoczęcia", + "end_min": "Minuta zakończenia" + } + }, + "assign_profile_phases_edit_select": { + "title": "Edytuj zakres fazy", + "description": "Wybierz zakres do edycji.", + "data": { + "range_index": "Zakres fazy" + } + }, + "assign_profile_phases_edit": { + "title": "Edytuj zakres fazy", + "description": "Zaktualizuj wybrany zakres fazy.", + "data": { + "phase_name": "Faza", + "start_min": "Minuta rozpoczęcia", + "end_min": "Minuta zakończenia" + } + }, + "assign_profile_phases_delete": { + "title": "Usuń zakres fazy", + "description": "Wybierz zakres do usunięcia ze szkicu.", + "data": { + "range_index": "Zakres fazy" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatycznie wykryte fazy", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} faz(y) wykrytych automatycznie.** Wybierz akcję poniżej.", + "data": { + "action": "Akcja" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nadaj nazwy wykrytym fazom", + "description": "Profil: **{profile_name}**\n\n**Wykryto {detected_count} faz(y):**\n{ranges_summary}\n\nPrzypisz nazwę do każdej fazy." + }, + "assign_profile_phases_select": { + "title": "Przypisz zakresy faz", + "description": "Wybierz profil, aby skonfigurować zakresy faz.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statystyki profilu", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Utwórz nowy profil", + "description": "Utwórz nowy profil ręcznie lub z poprzedniego cyklu.\n\nPrzykłady nazw profili: „Delikatne”, „Intensywne”, „Szybkie pranie”", + "data": { + "profile_name": "Nazwa profilu", + "reference_cycle": "Cykl referencyjny (opcjonalnie)", + "manual_duration": "Ręczny czas trwania (minuty)" + } + }, + "edit_profile": { + "title": "Edytuj profil", + "description": "Wybierz profil do zmiany nazwy", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Edytuj szczegóły profilu", + "description": "Aktualny profil: {current_name}", + "data": { + "new_name": "Nazwa profilu", + "manual_duration": "Ręczny czas trwania (minuty)" + } + }, + "delete_profile_select": { + "title": "Usuń profil", + "description": "Wybierz profil do usunięcia", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potwierdź usunięcie profilu", + "description": "⚠️ Spowoduje to trwałe usunięcie profilu: {profile_name}", + "data": { + "unlabel_cycles": "Usuń etykietę z cykli korzystających z tego profilu" + } + }, + "auto_label_cycles": { + "title": "Automatyczne etykietowanie starych cykli", + "description": "Znaleziono łącznie {total_count} cykli. Profile: {profiles}", + "data": { + "confidence_threshold": "Minimalna pewność (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Wybierz cykl do etykietowania", + "description": "✓ = zakończone, ⚠ = wznowione, ✗ = przerwane", + "data": { + "cycle_id": "Cykl" + } + }, + "select_cycle_to_delete": { + "title": "Usuń cykl", + "description": "⚠️ Spowoduje to trwałe usunięcie wybranego cyklu", + "data": { + "cycle_id": "Cykl do usunięcia" + } + }, + "label_cycle": { + "title": "Etykietuj cykl", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nowa nazwa profilu (jeśli tworzysz)" + } + }, + "post_process": { + "title": "Przetwarzanie historii (scalanie/podział)", + "description": "Wyczyść historię cykli, łącząc fragmentaryczne przebiegi i dzieląc niepoprawnie połączone cykle w oknie czasowym. Wprowadź liczbę godzin do sprawdzenia (wpisz 999999 dla wszystkich).", + "data": { + "time_range": "Okres wsteczny (godziny)", + "gap_seconds": "Próg scalania/podziału (sekundy)" + }, + "data_description": { + "time_range": "Liczba godzin skanowania w celu połączenia fragmentarycznych cykli. Użyj 999999, aby przetworzyć wszystkie dane historyczne.", + "gap_seconds": "Próg decydujący o podziale lub scaleniu. Luki KRÓTSZE niż ten czas są scalane. Luki DŁUŻSZE są dzielone. Obniż tę wartość, aby wymusić podział cyklu, który został nieprawidłowo scalony." + } + }, + "cleanup_profile": { + "title": "Czyszczenie historii - wybierz profil", + "description": "Wybierz profil do wizualizacji. Spowoduje to wygenerowanie wykresu przedstawiającego wszystkie poprzednie cykle dla tego profilu, co pomoże zidentyfikować wartości odstające.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Czyszczenie historii - wykres i usuwanie", + "description": "![Wykres]({graph_url})\n\n**Wizualizacja: {profile_name}**\n\nWykres przedstawia poszczególne cykle w postaci kolorowych linii. Zidentyfikuj wartości odstające (linie oddalone od grupy) i dopasuj ich kolor na poniższej liście, aby je usunąć.\n\n**Wybierz cykle do TRWAŁEGO usunięcia:**", + "data": { + "cycles_to_delete": "Cykle do usunięcia (sprawdź pasujące kolory)" + } + }, + "interactive_editor": { + "title": "Interaktywny edytor scalania/podziału", + "description": "Wybierz ręczną akcję do wykonania na historii cykli.", + "menu_options": { + "editor_split": "Podziel cykl (znajdź przerwy)", + "editor_merge": "Scal cykle (połącz fragmenty)", + "editor_delete": "Usuń cykl(e)", + "menu_back": "← Wstecz" + } + }, + "editor_select": { + "title": "Wybierz cykle", + "description": "Wybierz cykl(e) do przetworzenia.\n\n{info_text}", + "data": { + "selected_cycles": "Cykle" + } + }, + "editor_configure": { + "title": "Konfiguruj i zastosuj", + "description": "{preview_md}", + "data": { + "confirm_action": "Akcja", + "merged_profile": "Wynikowy profil", + "new_profile_name": "Nowa nazwa profilu", + "confirm_commit": "Tak, potwierdzam tę akcję", + "segment_0_profile": "Profil segmentu 1", + "segment_1_profile": "Profil segmentu 2", + "segment_2_profile": "Profil segmentu 3", + "segment_3_profile": "Profil segmentu 4", + "segment_4_profile": "Profil segmentu 5", + "segment_5_profile": "Profil segmentu 6", + "segment_6_profile": "Profil segmentu 7", + "segment_7_profile": "Profil segmentu 8", + "segment_8_profile": "Profil segmentu 9", + "segment_9_profile": "Profil segmentu 10" + } + }, + "editor_split_params": { + "title": "Konfiguracja podziału", + "description": "Dostosuj czułość podziału tego cyklu. Każda przerwa bezczynności dłuższa niż ten czas spowoduje podział.", + "data": { + "split_mode": "Metoda podziału" + } + }, + "editor_split_auto_params": { + "title": "Konfiguracja automatycznego podziału", + "description": "Dostosuj czułość dzielenia tego cyklu. Każda przerwa bezczynności dłuższa niż ta spowoduje podział.", + "data": { + "min_gap_seconds": "Próg przerwy podziału (sekundy)" + } + }, + "editor_split_manual_params": { + "title": "Ręczne znaczniki czasu podziału", + "description": "{preview_md}Okno cyklu: **{cycle_start_wallclock} → {cycle_end_wallclock}** dnia {cycle_date}.\n\nWprowadź jeden lub więcej znaczników czasu podziału (`HH:MM` lub `HH:MM:SS`), po jednym w wierszu. Cykl zostanie przecięty przy każdym znaczniku czasu — N znaczników czasu tworzy N+1 segmentów.", + "data": { + "split_timestamps": "Znaczniki czasu podziału" + } + }, + "reprocess_history": { + "title": "Konserwacja: ponowne przetwarzanie i optymalizacja danych", + "description": "Wykonuje głębokie czyszczenie danych historycznych:\n1. Przycina odczyty mocy zerowej (cisza).\n2. Naprawia czas/czas trwania cyklu.\n3. Przelicza wszystkie modele statystyczne (koperty).\n\n**Uruchom, aby naprawić problemy z danymi lub zastosować nowe algorytmy.**" + }, + "wipe_history": { + "title": "Wyczyść historię (tylko do testów)", + "description": "⚠️ Spowoduje to trwałe usunięcie WSZYSTKICH zapisanych cykli i profili dla tego urządzenia. Tego nie można cofnąć!" + }, + "export_import": { + "title": "Eksportuj/importuj JSON", + "description": "Kopiuj/wklej cykle, profile ORAZ ustawienia dla tego urządzenia. Eksportuj poniżej lub wklej JSON, aby zaimportować.", + "data": { + "mode": "Akcja", + "json_payload": "Pełna konfiguracja JSON" + }, + "data_description": { + "mode": "Wybierz Eksportuj, aby skopiować aktualne dane i ustawienia, lub Importuj, aby wkleić dane wyeksportowane z innego urządzenia WashData.", + "json_payload": "Do eksportu: skopiuj ten JSON (zawiera cykle, profile i wszystkie dopracowane ustawienia). Do importu: wklej JSON wyeksportowany z WashData." + } + }, + "record_cycle": { + "title": "Nagraj cykl", + "description": "Ręcznie nagraj cykl, aby utworzyć czysty profil bez zakłóceń.\n\nStan: **{status}**\nCzas trwania: {duration} s\nPróbki: {samples}", + "menu_options": { + "record_refresh": "Odśwież stan", + "record_stop": "Zatrzymaj nagrywanie (zapisz i przetwórz)", + "record_start": "Rozpocznij nowe nagranie", + "record_process": "Przetwórz ostatnie nagranie (przytnij i zapisz)", + "record_discard": "Odrzuć ostatnie nagranie", + "menu_back": "← Wstecz" + } + }, + "record_process": { + "title": "Przetwarzanie nagrania", + "description": "![Wykres]({graph_url})\n\n**Wykres przedstawia surowe nagranie.** Niebieski = zachowaj, Czerwony = proponowane przycięcie.\n*Wykres jest statyczny i nie aktualizuje się przy zmianach formularza.*\n\nNagrywanie zatrzymane. Przycięcia są wyrównywane do wykrytej częstotliwości próbkowania (~{sampling_rate} s).\n\nSurowy czas trwania: {duration} s\nPróbki: {samples}", + "data": { + "head_trim": "Przycięcie początku (sekundy)", + "tail_trim": "Przycięcie końca (sekundy)", + "save_mode": "Miejsce docelowe zapisu", + "profile_name": "Nazwa profilu" + } + }, + "learning_feedbacks": { + "title": "Opinie dotyczące uczenia", + "description": "Oczekujące opinie: {count}\n\n{pending_table}\n\nWybierz prośbę o przegląd cyklu do przetworzenia.", + "menu_options": { + "learning_feedbacks_pick": "Przejrzyj oczekującą opinię", + "learning_feedbacks_dismiss_all": "Odrzuć wszystkie oczekujące opinie", + "menu_back": "← Wstecz" + } + }, + "learning_feedbacks_pick": { + "title": "Wybierz opinię do przejrzenia", + "description": "Wybierz żądanie przeglądu cyklu do otwarcia.", + "data": { + "selected_feedback": "Wybierz cykl do przejrzenia" + } + }, + "learning_feedbacks_empty": { + "title": "Opinie dotyczące uczenia", + "description": "Nie znaleziono żadnych oczekujących próśb o opinię.", + "menu_options": { + "menu_back": "← Wstecz" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Odrzuć wszystkie oczekujące opinie", + "description": "Zaraz odrzucisz **{count}** oczekujących żądań opinii. Odrzucone cykle pozostaną w historii, ale nie będą wnosić nowego sygnału uczenia. Tego nie można cofnąć.\n\n✅ Zaznacz pole poniżej i kliknij **Prześlij**, aby odrzucić wszystko.\n❌ Pozostaw tę opcję niezaznaczoną i kliknij **Prześlij**, aby anulować i wrócić.", + "data": { + "confirm_dismiss_all": "Potwierdź: odrzuć wszystkie oczekujące prośby o opinię" + } + }, + "resolve_feedback": { + "title": "Rozpatrz opinię", + "description": "{comparison_img}{candidates_table}\n**Wykryty profil**: {detected_profile} ({confidence_pct}%)\n**Szacowany czas trwania**: {est_duration_min} min\n**Rzeczywisty czas trwania**: {act_duration_min} min\n\n__Wybierz akcję poniżej:__", + "data": { + "action": "Co chcesz zrobić?", + "corrected_profile": "Prawidłowy program (jeśli korygujesz)", + "corrected_duration": "Prawidłowy czas trwania w sekundach (jeśli korygujesz)" + } + }, + "trim_cycle_select": { + "title": "Przytnij cykl - wybierz cykl", + "description": "Wybierz cykl do przycięcia. ✓ = zakończone, ⚠ = wznowione, ✗ = przerwane", + "data": { + "cycle_id": "Cykl" + } + }, + "trim_cycle": { + "title": "Przytnij cykl", + "description": "Bieżące okno: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akcja" + } + }, + "trim_cycle_start": { + "title": "Ustaw punkt początku przycięcia", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nBieżący początek: **{current_wallclock}** (+{current_offset_min} min od początku cyklu)\n\nWybierz nowy czas rozpoczęcia, korzystając z poniższego selektora zegara.", + "data": { + "trim_start_time": "Nowy czas rozpoczęcia", + "trim_start_min": "Nowy start (minuty od rozpoczęcia cyklu)" + } + }, + "trim_cycle_end": { + "title": "Ustaw punkt końca przycięcia", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nBieżący koniec: **{current_wallclock}** (+{current_offset_min} min od początku cyklu)\n\nWybierz nową godzinę zakończenia, korzystając z poniższego selektora zegara.", + "data": { + "trim_end_time": "Nowy czas zakończenia", + "trim_end_min": "Nowy koniec (minuty od rozpoczęcia cyklu)" + } + } + }, + "error": { + "import_failed": "Import nie powiódł się. Wklej prawidłowy plik JSON eksportu WashData.", + "select_exactly_one": "Wybierz dokładnie jeden cykl dla tej akcji.", + "select_at_least_one": "Wybierz co najmniej jeden cykl dla tej akcji.", + "select_at_least_two": "Wybierz co najmniej dwa cykle dla tej akcji.", + "profile_exists": "Profil o tej nazwie już istnieje", + "rename_failed": "Zmiana nazwy profilu nie powiodła się. Profil może nie istnieć lub nowa nazwa jest już zajęta.", + "assignment_failed": "Przypisanie profilu do cyklu nie powiodło się", + "duplicate_phase": "Faza o tej nazwie już istnieje.", + "phase_not_found": "Nie znaleziono wybranej fazy.", + "invalid_phase_name": "Nazwa fazy nie może być pusta.", + "phase_name_too_long": "Nazwa fazy jest za długa.", + "invalid_phase_range": "Zakres fazy jest nieprawidłowy. Koniec musi być większy niż początek.", + "invalid_phase_timestamp": "Sygnatura czasowa jest nieprawidłowa. Użyj formatu RRRR-MM-DD GG:MM lub GG:MM.", + "overlapping_phase_ranges": "Zakresy faz nakładają się. Dostosuj zakresy i spróbuj ponownie.", + "incomplete_phase_row": "Każdy wiersz fazy musi zawierać fazę oraz pełne wartości początkowe/końcowe dla wybranego trybu.", + "timestamp_mode_cycle_required": "Jeśli korzystasz z trybu sygnatury czasowej, wybierz cykl źródłowy.", + "no_phase_ranges": "Dla tej akcji nie są jeszcze dostępne żadne zakresy faz.", + "feedback_notification_title": "WashData: sprawdź cykl ({device})", + "feedback_notification_message": "**Urządzenie**: {device}\n**Program**: {program} ({confidence}% pewności)\n**Czas**: {time}\n\nWashData potrzebuje Twojej pomocy, aby zweryfikować wykryty cykl.\n\nPrzejdź do **Ustawienia > Urządzenia i usługi > WashData > Konfiguruj > Opinie dotyczące uczenia**, aby potwierdzić lub poprawić ten wynik.", + "suggestions_ready_notification_title": "WashData: sugerowane ustawienia gotowe ({device})", + "suggestions_ready_notification_message": "Czujnik **Sugerowane ustawienia** zgłasza teraz **{count}** możliwych do zastosowania zaleceń.\n\nAby je przejrzeć i zastosować: **Ustawienia > Urządzenia i usługi > WashData > Konfiguruj > Ustawienia zaawansowane > Zastosuj sugerowane wartości**.\n\nSugestie są opcjonalne i wyświetlane do przeglądu przed zapisaniem.", + "trim_range_invalid": "Początek musi być przed końcem. Dostosuj punkty przycięcia.", + "trim_failed": "Przycinanie nie powiodło się. Cykl może już nie istnieć lub okno jest puste.", + "invalid_split_timestamp": "Znacznik czasu jest nieprawidłowy lub poza oknem cyklu. Użyj HH:MM lub HH:MM:SS w obrębie okna cyklu.", + "no_split_segments_found": "Z tych znaczników czasu nie udało się utworzyć prawidłowych segmentów. Upewnij się, że każdy znacznik czasu mieści się w oknie cyklu, a segmenty mają co najmniej 1 minutę długości.", + "auto_tune_suggestion": "{device_type} {device_title} wykrył cykle duchów. Sugerowana zmiana min_power: {current_min}W -> {new_min}W (nie stosowana automatycznie).", + "auto_tune_title": "Automatyczne dostrajanie WashData", + "auto_tune_fallback": "{device_type} {device_title} wykrył cykle duchów. Sugerowana zmiana min_power: {current_min}W -> {new_min}W (nie stosowana automatycznie)." + }, + "abort": { + "no_cycles_found": "Nie znaleziono cykli", + "no_split_segments_found": "W wybranym cyklu nie znaleziono segmentów nadających się do podziału.", + "cycle_not_found": "Nie można było załadować wybranego cyklu.", + "no_power_data": "Brak danych śladu mocy dla wybranego cyklu.", + "no_profiles_found": "Nie znaleziono profili. Najpierw utwórz profil.", + "no_profiles_for_matching": "Brak dostępnych profili do dopasowania. Najpierw utwórz profile.", + "no_unlabeled_cycles": "Wszystkie cykle są już oetykietowane", + "no_suggestions": "Nie są jeszcze dostępne żadne sugerowane wartości. Wykonaj kilka cykli i spróbuj ponownie.", + "no_predictions": "Brak prognoz", + "no_custom_phases": "Nie znaleziono faz niestandardowych.", + "reprocess_success": "Pomyślnie przetworzono ponownie {count} cykli.", + "debug_data_cleared": "Pomyślnie wyczyszczono dane debugowania z {count} cykli." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Rozpoczęcie cyklu", + "cycle_finish": "Zakończenie cyklu", + "cycle_live": "Postęp na żywo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Bez etykiety)", + "create_new_profile": "Utwórz nowy profil", + "remove_label": "Usuń etykietę", + "no_reference_cycle": "(Brak cyklu referencyjnego)", + "all_device_types": "Wszystkie typy urządzeń", + "current_suffix": "(aktualny)", + "editor_select_info": "Wybierz 1 cykl do podziału lub 2+ cykli do scalenia.", + "editor_select_info_split": "Wybierz 1 cykl do podziału.", + "editor_select_info_merge": "Wybierz 2+ cykle do scalenia.", + "editor_select_info_delete": "Wybierz 1+ cykli do usunięcia.", + "no_cycles_recorded": "Nie zapisano jeszcze żadnych cykli.", + "profile_summary_line": "- **{name}**: {count} cykli, śr. {avg} min", + "no_profiles_created": "Nie utworzono jeszcze żadnych profili.", + "phase_preview": "Podgląd fazy", + "phase_preview_no_curve": "Krzywa średniego profilu nie jest jeszcze dostępna. Uruchom lub oetykietuj więcej cykli dla tego profilu.", + "average_power_curve": "Krzywa średniej mocy", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cykli, ~{duration} min śr.)", + "profile_option_short_fmt": "{name} ({count} cykli, ~{duration} min)", + "deleted_cycles_from_profile": "Usunięto {count} cykli z {profile}.", + "not_enough_data_graph": "Za mało danych, aby wygenerować wykres.", + "no_profiles_found": "Nie znaleziono profili.", + "cycle_info_fmt": "Cykl: {start}, {duration} min, aktualnie: {label}", + "top_candidates_header": "### Najlepsi kandydaci", + "tbl_profile": "Profil", + "tbl_confidence": "Pewność", + "tbl_correlation": "Korelacja", + "tbl_duration_match": "Dopasowanie czasu trwania", + "detected_profile": "Wykryty profil", + "estimated_duration": "Szacowany czas trwania", + "actual_duration": "Rzeczywisty czas trwania", + "choose_action": "__Wybierz akcję poniżej:__", + "tbl_count": "Liczba", + "tbl_avg": "Śr.", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energia (śr.)", + "tbl_energy_total": "Energia (łącznie)", + "tbl_consistency": "Spójn.", + "tbl_last_run": "Ostatni cykl", + "graph_legend_title": "Legenda wykresu", + "graph_legend_body": "Niebieski pasek przedstawia zaobserwowany minimalny i maksymalny zakres poboru mocy. Linia pokazuje krzywą średniej mocy.", + "recording_preview": "Podgląd nagrania", + "trim_start": "Przycięcie początku", + "trim_end": "Przycięcie końca", + "split_preview_title": "Podgląd podziału", + "split_preview_found_fmt": "Znaleziono {count} segmentów.", + "split_preview_confirm_fmt": "Kliknij Potwierdź, aby podzielić ten cykl na {count} oddzielnych cykli.", + "merge_preview_title": "Podgląd scalania", + "merge_preview_joining_fmt": "Scalanie {count} cykli. Luki zostaną wypełnione odczytami 0 W.", + "editor_delete_preview_title": "Podgląd usuwania", + "editor_delete_preview_intro": "Wybrane cykle zostaną trwale usunięte:", + "editor_delete_preview_confirm": "Kliknij Potwierdź, aby trwale usunąć te rekordy cykli.", + "no_power_preview": "*Brak danych mocy dostępnych do podglądu.*", + "profile_comparison": "Porównanie profili", + "actual_cycle_label": "Ten cykl (rzeczywisty)", + "storage_usage_header": "Wykorzystanie miejsca", + "storage_file_size": "Rozmiar pliku", + "storage_cycles": "Cykle", + "storage_profiles": "Profile", + "storage_debug_traces": "Ślady debugowania", + "overview_suffix": "Przegląd", + "phase_preview_for_profile": "Podgląd fazy dla profilu", + "current_ranges_header": "Aktualne zakresy", + "assign_phases_help": "Użyj poniższych akcji, aby dodawać, edytować, usuwać lub zapisywać zakresy.", + "profile_summary_header": "Podsumowanie profili", + "recent_cycles_header": "Ostatnie cykle", + "trim_cycle_preview_title": "Podgląd przycięcia cyklu", + "trim_cycle_preview_no_data": "Brak danych mocy dla tego cyklu.", + "trim_cycle_preview_kept_suffix": "zachowane", + "table_program": "Program", + "table_when": "Kiedy", + "table_length": "Długość", + "table_match": "Dopasowanie", + "table_profile": "Profil", + "table_cycles": "Cykle", + "table_avg_length": "Śr. długość", + "table_last_run": "Ostatni cykl", + "table_avg_energy": "Śr. energia", + "table_detected_program": "Wykryty program", + "table_confidence": "Pewność", + "table_reported": "Zgłoszono", + "phase_builtin_suffix": "(Wbudowany)", + "phase_none_available": "Brak dostępnych faz.", + "settings_deprecation_warning": "⚠️ **Wycofany typ urządzenia:** Urządzenie {device_type} ma zostać usunięte w przyszłej wersji. Potok dopasowujący WashData nie zapewnia wiarygodnych wyników dla tej klasy urządzeń. Twoja integracja będzie działać przez cały okres wycofania; aby wyciszyć to ostrzeżenie, przełącz **Typ urządzenia** poniżej na jeden z obsługiwanych typów (pralka, suszarka, pralko-suszarka, zmywarka, frytownica, wypiekacz do chleba lub pompa) lub na **Inny (zaawansowany)**, jeśli Twoje urządzenie nie pasuje do żadnego z obsługiwanych typów. **Inne (zaawansowane)** zawiera celowo ogólne ustawienia domyślne, które nie są dostrojone do żadnego konkretnego urządzenia, dlatego konieczne będzie samodzielne skonfigurowanie progów, limitów czasu i dopasowywania parametrów; wszystkie istniejące ustawienia zostaną zachowane po przełączeniu.", + "phase_other_device_types": "Inne typy urządzeń:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Podziel cykl (znajdź przerwy)", + "merge": "Scal cykle (połącz fragmenty)", + "delete": "Usuń cykl(e)" + } + }, + "split_mode": { + "options": { + "auto": "Automatycznie wykryj przerwy bezczynności", + "manual": "Ręczne znaczniki czasu" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Konserwacja: ponowne przetwarzanie i optymalizacja danych", + "clear_debug_data": "Wyczyść dane debugowania (zwolnij miejsce)", + "wipe_history": "Usuń WSZYSTKIE dane z tego urządzenia (nieodwracalne)", + "export_import": "Eksportuj/importuj JSON z ustawieniami (kopiuj/wklej)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksportuj wszystkie dane", + "import": "Importuj/scal dane" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatyczne etykietowanie starych cykli", + "select_cycle_to_label": "Etykietuj konkretny cykl", + "select_cycle_to_delete": "Usuń cykl", + "interactive_editor": "Interaktywny edytor scalania/podziału", + "trim_cycle": "Przytnij dane cyklu" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Utwórz nowy profil", + "edit_profile": "Edytuj/zmień nazwę profilu", + "delete_profile": "Usuń profil", + "profile_stats": "Statystyki profilu", + "cleanup_profile": "Czyszczenie historii - wykres i usuwanie", + "assign_phases": "Przypisz zakresy faz" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Utwórz nową fazę", + "edit_custom_phase": "Edytuj fazę", + "delete_custom_phase": "Usuń fazę" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Dodaj zakres fazy", + "edit_range": "Edytuj zakres fazy", + "delete_range": "Usuń zakres fazy", + "clear_ranges": "Wyczyść wszystkie zakresy", + "auto_detect_ranges": "Automatycznie wykryj fazy", + "save_ranges": "Zapisz i wróć" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Odśwież stan", + "stop_recording": "Zatrzymaj nagrywanie (zapisz i przetwórz)", + "start_recording": "Rozpocznij nowe nagranie", + "process_recording": "Przetwórz ostatnie nagranie (przytnij i zapisz)", + "discard_recording": "Odrzuć ostatnie nagranie" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Utwórz nowy profil", + "existing_profile": "Dodaj do istniejącego profilu", + "discard": "Odrzuć nagranie" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potwierdź - poprawne wykrycie", + "correct": "Popraw - wybierz program/czas trwania", + "ignore": "Ignoruj - fałszywy alarm/zakłócony cykl", + "delete": "Usuń - usuń z historii" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nadaj nazwy i zastosuj fazy", + "cancel": "Wróć bez zmian" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Ustaw punkt początku", + "set_end": "Ustaw punkt końca", + "reset": "Zresetuj do pełnego czasu trwania", + "apply": "Zastosuj przycięcie", + "cancel": "Anuluj" + } + }, + "device_type": { + "options": { + "washing_machine": "Pralka", + "dryer": "Suszarka", + "washer_dryer": "Zestaw pralko-suszarki", + "dishwasher": "Pomywaczka", + "coffee_machine": "Ekspres do kawy (przestarzały)", + "ev": "Pojazd elektryczny (przestarzałe)", + "air_fryer": "Frytkownica powietrzna", + "heat_pump": "Pompa ciepła (przestarzała)", + "bread_maker": "Wypiekacz chleba", + "pump": "Pompa/pompa ściekowa", + "oven": "Piekarnik (przestarzały)", + "other": "Inne (zaawansowane)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etykietuj cykl", + "description": "Przypisz istniejący profil do poprzedniego cyklu lub usuń etykietę.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do etykietowania." + }, + "cycle_id": { + "name": "Identyfikator cyklu", + "description": "Identyfikator cyklu do oetykietowania." + }, + "profile_name": { + "name": "Nazwa profilu", + "description": "Nazwa istniejącego profilu (utwórz profile w menu Zarządzaj profilami). Pozostaw puste, aby usunąć etykietę." + } + } + }, + "create_profile": { + "name": "Utwórz profil", + "description": "Utwórz nowy profil (samodzielny lub oparty na cyklu referencyjnym).", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData." + }, + "profile_name": { + "name": "Nazwa profilu", + "description": "Nazwa nowego profilu (np. „Intensywne”, „Delikatne”)." + }, + "reference_cycle_id": { + "name": "Identyfikator cyklu referencyjnego", + "description": "Opcjonalny identyfikator cyklu, na którym można oprzeć ten profil." + } + } + }, + "delete_profile": { + "name": "Usuń profil", + "description": "Usuń profil i opcjonalnie usuń etykietę z cykli go używających.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData." + }, + "profile_name": { + "name": "Nazwa profilu", + "description": "Profil do usunięcia." + }, + "unlabel_cycles": { + "name": "Usuń etykiety z cykli", + "description": "Usuń etykietę profilu z cykli korzystających z tego profilu." + } + } + }, + "auto_label_cycles": { + "name": "Automatyczne etykietowanie starych cykli", + "description": "Wstecznie etykietuj nieoetykietowane cykle za pomocą dopasowania profilu.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData." + }, + "confidence_threshold": { + "name": "Próg pewności", + "description": "Minimalna pewność dopasowania (0,50–0,95), aby zastosować etykiety." + } + } + }, + "export_config": { + "name": "Eksportuj konfigurację", + "description": "Eksportuj profile, cykle i ustawienia tego urządzenia do pliku JSON (na urządzenie).", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do eksportu." + }, + "path": { + "name": "Ścieżka", + "description": "Opcjonalna bezwzględna ścieżka pliku do zapisu (domyślnie: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importuj konfigurację", + "description": "Importuj profile, cykle i ustawienia dla tego urządzenia z pliku eksportu JSON (ustawienia mogą zostać nadpisane).", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData, do którego chcesz zaimportować." + }, + "path": { + "name": "Ścieżka", + "description": "Bezwzględna ścieżka do eksportowanego pliku JSON do zaimportowania." + } + } + }, + "submit_cycle_feedback": { + "name": "Prześlij opinię dotyczącą cyklu", + "description": "Potwierdź lub popraw automatycznie wykryty program po zakończeniu cyklu. Podaj `entry_id` (zaawansowane) lub `device_id` (zalecane).", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData (zalecane zamiast entry_id)." + }, + "entry_id": { + "name": "Identyfikator wpisu", + "description": "Identyfikator wpisu konfiguracji urządzenia (alternatywa dla device_id)." + }, + "cycle_id": { + "name": "Identyfikator cyklu", + "description": "Identyfikator cyklu pokazany w powiadomieniu/dziennikach opinii." + }, + "user_confirmed": { + "name": "Potwierdź wykryty program", + "description": "Ustaw true, jeśli wykryty program jest poprawny." + }, + "corrected_profile": { + "name": "Poprawiony profil", + "description": "Jeśli nie potwierdzasz, podaj prawidłową nazwę profilu/programu." + }, + "corrected_duration": { + "name": "Poprawiony czas trwania (sekundy)", + "description": "Opcjonalnie poprawiony czas trwania w sekundach." + }, + "notes": { + "name": "Notatki", + "description": "Opcjonalne uwagi dotyczące tego cyklu." + } + } + }, + "record_start": { + "name": "Nagraj start cyklu", + "description": "Rozpocznij ręczne nagrywanie czystego cyklu (pomija całą logikę dopasowania).", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do nagrywania." + } + } + }, + "record_stop": { + "name": "Nagraj zatrzymanie cyklu", + "description": "Zatrzymaj nagrywanie ręczne.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do zatrzymania nagrywania." + } + } + }, + "trim_cycle": { + "name": "Przytnij cykl", + "description": "Przytnij dane mocy z poprzedniego cyklu do określonego okna czasowego.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData." + }, + "cycle_id": { + "name": "Identyfikator cyklu", + "description": "Identyfikator cyklu do przycięcia." + }, + "trim_start_s": { + "name": "Przycięcie początku (sekundy)", + "description": "Zachowuj dane od tego przesunięcia w sekundach. Domyślnie 0." + }, + "trim_end_s": { + "name": "Przycięcie końca (sekundy)", + "description": "Zachowuj dane do tego przesunięcia w sekundach. Domyślnie = pełny czas trwania." + } + } + }, + "pause_cycle": { + "name": "Wstrzymaj cykl", + "description": "Wstrzymaj aktywny cykl urządzenia WashData.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do wstrzymania." + } + } + }, + "resume_cycle": { + "name": "Wznów cykl", + "description": "Wznów wstrzymany cykl dla urządzenia WashData.", + "fields": { + "device_id": { + "name": "Urządzenie", + "description": "Urządzenie WashData do wznowienia." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Pracuje" + }, + "match_ambiguity": { + "name": "Niejednoznaczność dopasowania" + } + }, + "sensor": { + "washer_state": { + "name": "Stan", + "state": { + "off": "Wyłączony", + "idle": "Bezczynny", + "starting": "Uruchamianie", + "running": "Pracuje", + "paused": "Wstrzymany", + "user_paused": "Wstrzymane przez użytkownika", + "ending": "Kończenie", + "finished": "Zakończony", + "anti_wrinkle": "Antygniotowy", + "delay_wait": "Oczekiwanie na start", + "interrupted": "Przerwany", + "force_stopped": "Wymuszone zatrzymanie", + "rinse": "Płukanie", + "unknown": "Nieznany", + "clean": "Czysty" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Pozostały czas" + }, + "total_duration": { + "name": "Całkowity czas trwania" + }, + "cycle_progress": { + "name": "Postęp" + }, + "current_power": { + "name": "Aktualna moc" + }, + "elapsed_time": { + "name": "Czas, który upłynął" + }, + "debug_info": { + "name": "Informacje debugowania" + }, + "match_confidence": { + "name": "Pewność dopasowania" + }, + "top_candidates": { + "name": "Najlepsi kandydaci", + "state": { + "none": "Brak" + } + }, + "current_phase": { + "name": "Bieżąca faza" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} - liczba", + "unit_of_measurement": "cykle" + }, + "suggestions": { + "name": "Dostępne sugerowane ustawienia" + }, + "pump_runs_today": { + "name": "Pompa pracuje (ostatnie 24 godziny)" + }, + "cycle_count": { + "name": "Liczba cykli" + } + }, + "select": { + "program_select": { + "name": "Program cyklu", + "state": { + "auto_detect": "Automatyczne wykrywanie" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Wymuś zakończenie cyklu" + }, + "pause_cycle": { + "name": "Wstrzymaj cykl" + }, + "resume_cycle": { + "name": "Wznów cykl" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Wymagany jest prawidłowy identyfikator urządzenia." + }, + "cycle_id_required": { + "message": "Wymagany jest prawidłowy identyfikator cyklu." + }, + "profile_name_required": { + "message": "Wymagana jest prawidłowa nazwa_profilu." + }, + "device_not_found": { + "message": "Nie znaleziono urządzenia." + }, + "no_config_entry": { + "message": "Nie znaleziono wpisu konfiguracji dla urządzenia." + }, + "integration_not_loaded": { + "message": "Integracja nie załadowana dla tego urządzenia." + }, + "cycle_not_found_or_no_power": { + "message": "Nie znaleziono cyklu lub brak danych mocy." + }, + "trim_failed_empty_window": { + "message": "Przycinanie nie powiodło się - nie znaleziono cyklu, brak danych mocy lub wynikowe okno jest puste." + }, + "trim_invalid_range": { + "message": "trim_end_s musi być większe niż trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/pt-BR.json b/custom_components/ha_washdata/translations/pt-BR.json new file mode 100644 index 0000000..e28e27f --- /dev/null +++ b/custom_components/ha_washdata/translations/pt-BR.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuração WashData", + "description": "Configure sua máquina de lavar ou outro eletrodoméstico.\n\nSensor de potência é necessário.\n\n**Em seguida, você será perguntado se deseja criar seu primeiro perfil.**", + "data": { + "name": "Nome do dispositivo", + "device_type": "Tipo de dispositivo", + "power_sensor": "Sensor de potência", + "min_power": "Limite Mínimo de Potência (W)" + }, + "data_description": { + "name": "Um nome amigável para este dispositivo (por exemplo, 'Máquina de lavar', 'Lava-louças').", + "device_type": "Que tipo de aparelho é esse? Ajuda a personalizar a detecção e a rotulagem.", + "power_sensor": "A entidade do sensor que informa o consumo de energia em tempo real (em watts) do seu plugue inteligente.", + "min_power": "As leituras de potência acima deste limite (em watts) indicam que o aparelho está funcionando. Comece com 2W para a maioria dos dispositivos." + } + }, + "first_profile": { + "title": "Crie o primeiro perfil", + "description": "Um **Ciclo** é uma operação completa do seu eletrodoméstico (por exemplo, uma carga de roupa). Um **Perfil** agrupa esses ciclos por tipo (por exemplo, 'Algodão', 'Lavagem Rápida'). A criação de um perfil agora ajuda a integração a estimar a duração imediatamente.\n\nVocê pode selecionar manualmente esse perfil nos controles do dispositivo se ele não for detectado automaticamente.\n\n**Deixe o 'Nome do perfil' vazio para pular esta etapa.**", + "data": { + "profile_name": "Nome do perfil (opcional)", + "manual_duration": "Duração estimada (minutos)" + } + } + }, + "error": { + "cannot_connect": "Falha ao conectar", + "invalid_auth": "Autenticação inválida", + "unknown": "Erro inesperado" + }, + "abort": { + "already_configured": "O dispositivo já está configurado" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Revisar feedbacks de aprendizagem", + "settings": "Configurações", + "notifications": "Notificações", + "manage_cycles": "Gerenciar ciclos", + "manage_profiles": "Gerenciar perfis", + "manage_phase_catalog": "Gerenciar catálogo de fases", + "record_cycle": "Gravar ciclo (manual)", + "diagnostics": "Diagnóstico e manutenção" + } + }, + "apply_suggestions": { + "title": "Copiar valores sugeridos", + "description": "Isso copiará os valores sugeridos atuais para as suas configurações salvas. Esta é uma ação única e não habilita substituições automáticas.\n\nValores sugeridos para copiar:\n{suggested}", + "data": { + "confirm": "Confirmar ação de cópia" + } + }, + "apply_suggestions_confirm": { + "title": "Revisar alterações sugeridas", + "description": "WashData encontrou **{pending_count}** valores sugeridos para aplicação.\n\nMudanças:\n{changes}\n\n✅ Marque a caixa abaixo e clique em **Enviar** para aplicar e salvar essas alterações imediatamente.\n❌ Deixe desmarcado e clique em **Enviar** para cancelar e voltar.", + "data": { + "confirm_apply_suggestions": "Aplicar e salvar essas alterações" + } + }, + "settings": { + "title": "Configurações", + "description": "{deprecation_warning}Ajuste limites de detecção, aprendizado e comportamento avançado. Os padrões funcionam bem para a maioria das configurações.\n\nConfigurações sugeridas disponíveis: {suggestions_count}.\nSe estiver acima de 0, abra as Configurações avançadas e use Aplicar valores sugeridos para revisar as recomendações.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos", + "device_type": "Tipo de dispositivo", + "power_sensor": "Entidade sensor de potência", + "min_power": "Potência mínima (W)", + "off_delay": "Atraso de fim de ciclo (s)", + "notify_actions": "Ações de notificação", + "notify_people": "Atrasar entrega até essas pessoas estarem em casa", + "notify_only_when_home": "Atrasar notificações até a pessoa selecionada estar em casa", + "notify_fire_events": "Acionar eventos de automação", + "notify_start_services": "Início do Ciclo - Alvos de Notificação", + "notify_finish_services": "Término do Ciclo - Alvos de Notificação", + "notify_live_services": "Progresso ao vivo – metas de notificação", + "notify_before_end_minutes": "Notificação antecipada (minutos antes do fim)", + "notify_title": "Título da notificação", + "notify_icon": "Ícone de notificação", + "notify_start_message": "Formato da mensagem de início", + "notify_finish_message": "Formato da mensagem de conclusão", + "notify_pre_complete_message": "Formato da mensagem pré-conclusão", + "show_advanced": "Editar configurações avançadas (próxima etapa)" + }, + "data_description": { + "apply_suggestions": "Marque esta caixa para copiar os valores sugeridos pelo algoritmo de aprendizagem no formulário. Revise antes de salvar.\n\nSugerido (pelo aprendizado):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selecione o tipo de aparelho (Máquina de lavar, Secadora, Lava-louças, Cafeteira, VE). Esta informação é armazenada em cada ciclo para melhor organização.", + "power_sensor": "A entidade do sensor que informa a potência em tempo real (em watts). Você pode alterar isso a qualquer momento sem perder dados históricos - útil se você trocar o plugue inteligente.", + "min_power": "As leituras de potência acima deste limite (em watts) indicam que o aparelho está funcionando. Comece com 2W para a maioria dos dispositivos.", + "off_delay": "Em segundos, a potência suavizada deve permanecer abaixo do limite de parada para marcar a conclusão. Padrão: 120s.", + "notify_actions": "Sequência de ações opcional do Home Assistant para notificações (scripts, notify, TTS, etc.). Se definido, as ações serão executadas antes do serviço de notificação de fallback.", + "notify_people": "Entidades de pessoas usadas para controle de presença. Quando ativado, a entrega da notificação é adiada até que pelo menos uma pessoa selecionada esteja em casa.", + "notify_only_when_home": "Se ativado, atrasa as notificações até que pelo menos uma pessoa selecionada esteja em casa.", + "notify_fire_events": "Se habilitado, dispara eventos do Home Assistant para início e fim de ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para acionar automações.", + "notify_start_services": "Um ou mais serviços de notificação para alertar quando um ciclo começa. Deixe em branco para pular as notificações de início.", + "notify_finish_services": "Um ou mais serviços de notificação para alertar quando um ciclo termina ou está próximo da conclusão. Deixe em branco para pular as notificações de conclusão.", + "notify_live_services": "Um ou mais serviços de notificação para receber atualizações de progresso ao vivo enquanto o ciclo é executado (somente aplicativo complementar móvel). Deixe em branco para pular as atualizações ao vivo.", + "notify_before_end_minutes": "Minutos antes do fim estimado do ciclo para acionar uma notificação. Defina como 0 para desativar. Padrão: 0.", + "notify_title": "Título personalizado para notificações. Suporta o marcador {device}.", + "notify_icon": "Ícone para usar nas notificações (ex.: mdi:washing-machine). Deixe em branco para enviar sem ícone.", + "notify_start_message": "Mensagem enviada quando um ciclo é iniciado. Suporta o marcador {device}.", + "notify_finish_message": "Mensagem enviada quando um ciclo termina. Suporta os marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texto das atualizações recorrentes do progresso ao vivo enquanto o ciclo é executado. Suporta marcadores de posição {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificações", + "description": "Configure canais de notificação, modelos e atualizações de progresso ao vivo no celular.", + "data": { + "notify_actions": "Ações de notificação", + "notify_people": "Atrasar entrega até essas pessoas estarem em casa", + "notify_only_when_home": "Atrasar notificações até a pessoa selecionada estar em casa", + "notify_fire_events": "Acionar eventos de automação", + "notify_start_services": "Início do Ciclo - Alvos de Notificação", + "notify_finish_services": "Término do Ciclo - Alvos de Notificação", + "notify_live_services": "Progresso ao vivo – metas de notificação", + "notify_before_end_minutes": "Notificação antecipada (minutos antes do fim)", + "notify_live_interval_seconds": "Intervalo de atualização ao vivo (segundos)", + "notify_live_overrun_percent": "Margem de sobrecarga nas atualizações ao vivo (%)", + "notify_live_chronometer": "Temporizador de contagem regressiva do cronômetro", + "notify_title": "Título da notificação", + "notify_icon": "Ícone de notificação", + "notify_start_message": "Formato da mensagem de início", + "notify_finish_message": "Formato da mensagem de conclusão", + "notify_pre_complete_message": "Formato da mensagem pré-conclusão", + "energy_price_entity": "Preço da Energia - Entidade (opcional)", + "energy_price_static": "Preço da Energia - Valor estático (opcional)", + "go_back": "Voltar sem salvar", + "notify_reminder_message": "Formato de mensagem de lembrete", + "notify_timeout_seconds": "Dispensar automaticamente após (segundos)", + "notify_channel": "Canal de notificação (status/ao vivo/lembrete)", + "notify_finish_channel": "Canal de Notificação Concluído" + }, + "data_description": { + "go_back": "Marque isto e clique em Enviar para descartar quaisquer alterações acima e voltar ao menu principal.", + "notify_actions": "Sequência de ações opcional do Home Assistant para notificações (scripts, notify, TTS, etc.). Se definido, as ações serão executadas antes do serviço de notificação de fallback.", + "notify_people": "Entidades de pessoas usadas para controle de presença. Quando ativado, a entrega da notificação é adiada até que pelo menos uma pessoa selecionada esteja em casa.", + "notify_only_when_home": "Se ativado, atrasa as notificações até que pelo menos uma pessoa selecionada esteja em casa.", + "notify_fire_events": "Se habilitado, dispara eventos do Home Assistant para início e fim de ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para acionar automações.", + "notify_start_services": "Um ou mais serviços de notificação para alertar quando um ciclo começa (por exemplo, notify.mobile_app_my_phone). Deixe em branco para pular as notificações de início.", + "notify_finish_services": "Um ou mais serviços de notificação para alertar quando um ciclo termina ou está próximo da conclusão. Deixe em branco para pular as notificações de conclusão.", + "notify_live_services": "Um ou mais serviços de notificação para receber atualizações de progresso ao vivo enquanto o ciclo é executado (somente aplicativo complementar móvel). Deixe em branco para pular as atualizações ao vivo.", + "notify_before_end_minutes": "Minutos antes do fim estimado do ciclo para acionar uma notificação. Defina como 0 para desativar. Padrão: 0.", + "notify_live_interval_seconds": "Com que frequência as atualizações de progresso são enviadas durante o ciclo. Valores mais baixos são mais responsivos, mas consomem mais notificações. Um mínimo de 30 segundos é aplicado - valores abaixo de 30s são arredondados automaticamente para 30s.", + "notify_live_overrun_percent": "Margem de segurança acima da contagem estimada de atualizações para ciclos longos (por exemplo, 20% permite 1,2x as atualizações estimadas).", + "notify_live_chronometer": "Quando ativado, cada atualização ao vivo inclui uma contagem regressiva do cronômetro até o tempo estimado de término. O cronômetro de notificação diminui automaticamente no dispositivo entre as atualizações (somente aplicativo complementar para Android).", + "notify_title": "Título personalizado para notificações. Suporta o marcador {device}.", + "notify_icon": "Ícone para usar nas notificações (ex.: mdi:washing-machine). Deixe em branco para enviar sem ícone.", + "notify_start_message": "Mensagem enviada quando um ciclo é iniciado. Suporta o marcador {device}.", + "notify_finish_message": "Mensagem enviada quando um ciclo termina. Suporta os marcadores {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Selecione uma entidade numérica que contenha o preço atual da eletricidade (por exemplo, sensor.electricity_price, input_number.energy_rate). Quando definido, ativa o espaço reservado {cost}. Tem precedência sobre o valor estático se ambos estiverem configurados.", + "energy_price_static": "Preço fixo da eletricidade por kWh. Quando definido, ativa o espaço reservado {cost}. Ignorado se uma entidade estiver configurada acima. Adicione o símbolo da sua moeda no modelo de mensagem, por exemplo. {cost} €.", + "notify_reminder_message": "O texto do lembrete único enviou o número configurado de minutos antes do término estimado. Diferente das atualizações ao vivo recorrentes acima. Suporta marcadores de posição {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Ignore automaticamente as notificações após esses segundos (encaminhadas como 'tempo limite' do aplicativo complementar). Defina como 0 para mantê-los até serem descartados. Padrão: 0.", + "notify_channel": "Android canal de notificação de aplicativo complementar para notificações de status, progresso ao vivo e lembretes. O som e a importância de um canal são configurados no aplicativo complementar na primeira vez que o nome do canal é usado - WashData define apenas o nome do canal. Deixe em branco para o padrão do aplicativo.", + "notify_finish_channel": "Canal Android separado para o alerta finalizado (também usado pelo lembrete e pelo aviso de espera da lavanderia) para que ele possa ter seu próprio som. Deixe em branco para reutilizar o canal acima.", + "notify_pre_complete_message": "Texto das atualizações recorrentes do progresso ao vivo enquanto o ciclo é executado. Suporta marcadores de posição {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Configurações avançadas", + "description": "Ajuste limites de detecção, aprendizado e comportamento avançado. Os padrões funcionam bem para a maioria das configurações.", + "sections": { + "suggestions_section": { + "name": "Configurações sugeridas", + "description": "{suggestions_count} sugestões aprendidas disponíveis. Marque Aplicar valores sugeridos para revisar as alterações propostas antes de salvar.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos" + }, + "data_description": { + "apply_suggestions": "Marque esta caixa para abrir uma etapa de revisão dos valores sugeridos pelo algoritmo de aprendizagem. Nada é salvo automaticamente.\n\nSugerido (pelo aprendizado):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detecção e limites de potência", + "description": "Quando o aparelho é considerado LIGADO ou DESLIGADO, limites de energia e temporização das transições de estado.", + "data": { + "start_duration_threshold": "Duração do debounce de início (s)", + "start_energy_threshold": "Porta de energia de início (Wh)", + "completion_min_seconds": "Tempo mínimo de execução para conclusão (segundos)", + "start_threshold_w": "Limite de início (W)", + "stop_threshold_w": "Limite de parada (W)", + "end_energy_threshold": "Porta de energia de fim (Wh)", + "running_dead_zone": "Zona morta de início (segundos)", + "end_repeat_count": "Contagem de repetições para fim", + "min_off_gap": "Intervalo mínimo entre ciclos (s)", + "sampling_interval": "Intervalo de amostragem (s)" + }, + "data_description": { + "start_duration_threshold": "Ignore breves picos de energia menores que esta duração (segundos). Filtra picos de inicialização (ex.: 60W por 2s) antes do início real do ciclo. Padrão: 5s.", + "start_energy_threshold": "O ciclo deve acumular pelo menos esta quantidade de energia (Wh) durante a fase de INÍCIO para ser confirmado. Padrão: 0,005 Wh.", + "completion_min_seconds": "Ciclos mais curtos que isso serão marcados como 'interrompidos', mesmo que terminem naturalmente. Padrão: 600s.", + "start_threshold_w": "A potência deve subir ACIMA deste limite para INICIAR um ciclo. Defina acima da sua potência em espera. Padrão: min_power + 1 W.", + "stop_threshold_w": "A potência deve cair ABAIXO deste limite para FINALIZAR um ciclo. Defina um pouco acima de zero para evitar falsos fins por ruído. Padrão: 0,6 × min_power.", + "end_energy_threshold": "O ciclo termina somente se a energia na última janela de atraso de fim estiver abaixo deste valor (Wh). Evita fins falsos se a potência flutuar perto de zero. Padrão: 0,05 Wh.", + "running_dead_zone": "Após o início do ciclo, ignore quedas de potência durante esses segundos para evitar detecção falsa de fim durante a fase inicial. Defina como 0 para desativar. Padrão: 0s.", + "end_repeat_count": "Número de vezes que a condição de fim (potência abaixo do limite durante off_delay) deve ser atendida consecutivamente antes de encerrar o ciclo. Valores mais altos evitam fins falsos durante pausas. Padrão: 1.", + "min_off_gap": "Se um ciclo começar poucos segundos após o término do anterior, eles serão MESCLADOS em um único ciclo.", + "sampling_interval": "Intervalo mínimo de amostragem em segundos. Atualizações mais rápidas que essa taxa serão ignoradas. Padrão: 30s (2s para máquinas de lavar, lavadoras e secadoras e lava-louças)." + } + }, + "matching_section": { + "name": "Correspondência de perfis e aprendizado", + "description": "Com que agressividade o WashData faz corresponder ciclos em execução a perfis aprendidos e quando pedir feedback.", + "data": { + "profile_match_min_duration_ratio": "Proporção mínima de duração para correspondência (0,1-1,0)", + "profile_match_interval": "Intervalo de correspondência de perfil (segundos)", + "profile_match_threshold": "Limite de correspondência de perfil", + "profile_unmatch_threshold": "Limite de não-correspondência de perfil", + "auto_label_confidence": "Confiança para rotulagem automática (0-1)", + "learning_confidence": "Confiança para solicitação de feedback (0-1)", + "suppress_feedback_notifications": "Suprimir notificações de feedback", + "duration_tolerance": "Tolerância de duração (0-0,5)", + "smoothing_window": "Janela de suavização (amostras)", + "profile_duration_tolerance": "Tolerância de duração na correspondência de perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Proporção mínima de duração para correspondência de perfil. O ciclo em execução deve ter pelo menos esta fração da duração do perfil para corresponder. Menor = correspondência mais cedo. Padrão: 0,50 (50%).", + "profile_match_interval": "Com que frequência (em segundos) tentar a correspondência de perfil durante um ciclo. Valores mais baixos fornecem detecção mais rápida, mas usam mais CPU. Padrão: 300s (5 minutos).", + "profile_match_threshold": "Pontuação mínima de similaridade DTW (0,0–1,0) necessária para considerar um perfil como correspondente. Maior = correspondência mais rigorosa, menos falsos positivos. Padrão: 0,4.", + "profile_unmatch_threshold": "Pontuação de similaridade DTW (0,0–1,0) abaixo da qual um perfil anteriormente correspondido é rejeitado. Deve ser menor que o limite de correspondência para evitar oscilações. Padrão: 0,35.", + "auto_label_confidence": "Rotule automaticamente os ciclos com essa confiança ou acima dela na conclusão (0,0–1,0).", + "learning_confidence": "Confiança mínima para solicitar verificação do usuário via evento (0,0–1,0).", + "suppress_feedback_notifications": "Quando ativado, o WashData ainda rastreará e solicitará feedback internamente, mas não mostrará notificações persistentes solicitando a confirmação dos ciclos. Útil quando os ciclos são sempre detectados corretamente e você acha que os avisos são uma distração.", + "duration_tolerance": "Tolerância para estimativas de tempo restante durante um ciclo (0,0–0,5 equivale a 0–50%).", + "smoothing_window": "Número de leituras de potência recentes usadas para suavização por média móvel.", + "profile_duration_tolerance": "Tolerância usada pela correspondência de perfil para variação na duração total (0,0–0,5). Comportamento padrão: ±25%." + } + }, + "timing_section": { + "name": "Temporização, manutenção e depuração", + "description": "Watchdog, redefinição do progresso, manutenção automática e exposição de entidades de depuração.", + "data": { + "watchdog_interval": "Intervalo do watchdog (segundos)", + "no_update_active_timeout": "Timeout sem atualização (s)", + "progress_reset_delay": "Atraso para redefinição de progresso (segundos)", + "auto_maintenance": "Habilitar manutenção automática", + "expose_debug_entities": "Expor entidades de depuração", + "save_debug_traces": "Salvar rastreamentos de depuração" + }, + "data_description": { + "watchdog_interval": "Segundos entre verificações do watchdog durante a execução. Padrão: 5s. AVISO: Certifique-se de que seja MAIOR que o intervalo de atualização do seu sensor para evitar paradas falsas (ex.: se o sensor atualiza a cada 60s, use 65s+).", + "no_update_active_timeout": "Para sensores que publicam apenas em mudança: force o encerramento de um ciclo ATIVO somente se nenhuma atualização chegar durante esse número de segundos. A conclusão com baixo consumo de energia ainda usa o atraso de desligamento.", + "progress_reset_delay": "Após a conclusão de um ciclo (100%), aguarde alguns segundos de inatividade antes de redefinir o progresso para 0%. Padrão: 300s.", + "auto_maintenance": "Habilita a manutenção automática (reparar amostras, mesclar fragmentos).", + "expose_debug_entities": "Mostra sensores avançados (Confiança, Fase, Ambiguidade) para depuração.", + "save_debug_traces": "Armazena dados detalhados de classificação e rastreamento de energia no histórico (aumenta o uso de armazenamento)." + } + }, + "anti_wrinkle_section": { + "name": "Proteção anti-rugas (secadoras)", + "description": "Ignora rotações do tambor após o ciclo para evitar ciclos fantasma. Apenas secadora / lava e seca.", + "data": { + "anti_wrinkle_enabled": "Escudo anti-amasso (somente secadora)", + "anti_wrinkle_max_power": "Potência máxima do anti-amasso (W)", + "anti_wrinkle_max_duration": "Duração máxima do anti-amasso (s)", + "anti_wrinkle_exit_power": "Potência de saída do anti-amasso (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignora rotações curtas de baixa potência do tambor após o término de um ciclo para evitar ciclos fantasmas (somente secadora/lava-e-seca).", + "anti_wrinkle_max_power": "Rajadas com esta potência ou abaixo são tratadas como rotações anti-amasso em vez de novos ciclos. Aplica-se apenas quando o Escudo Anti-amasso está ativado.", + "anti_wrinkle_max_duration": "Duração máxima da rajada para tratar como rotação anti-amasso. Aplica-se apenas quando o Escudo Anti-amasso está ativado.", + "anti_wrinkle_exit_power": "Se a potência cair abaixo deste nível, sai do modo anti-amasso imediatamente (desligamento real). Aplica-se apenas quando o Escudo Anti-amasso está ativado." + } + }, + "delay_start_section": { + "name": "Detecção de início adiado", + "description": "Trata o modo de espera sustentado de baixa potência como 'Aguardando para iniciar' até o ciclo realmente começar.", + "data": { + "delay_start_detect_enabled": "Ativar detecção de início atrasado", + "delay_confirm_seconds": "Início adiado: tempo de confirmação em espera (s)", + "delay_timeout_hours": "Início atrasado: tempo máximo de espera (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Quando ativado, um breve pico de consumo de baixa energia seguido por energia de espera sustentada é detectado como uma partida atrasada. O sensor de estado mostra 'Aguardando início' durante o atraso. Nenhum tempo ou progresso do programa é monitorado até que o ciclo realmente comece. Requer que start_threshold_w seja definido acima da potência do pico de drenagem.", + "delay_confirm_seconds": "A potência deve permanecer entre Limite de parada (W) e Limite de início (W) por esse número de segundos antes de o WashData entrar em 'Aguardando para iniciar'. Filtra picos breves da navegação pelo menu. Padrão: 60 s.", + "delay_timeout_hours": "Tempo limite de segurança: se a máquina ainda estiver em “Aguardando para iniciar” após tantas horas, WashData será redefinido para Desligado. Padrão: 8h." + } + }, + "external_triggers_section": { + "name": "Gatilhos externos, porta e pausa", + "description": "Sensor externo opcional de fim de ciclo, sensor de porta para detectar pausa/limpo e interruptor de corte de energia ao pausar.", + "data": { + "external_end_trigger_enabled": "Habilitar gatilho externo de fim de ciclo", + "external_end_trigger": "Sensor externo de fim de ciclo", + "external_end_trigger_inverted": "Inverter lógica do gatilho (acionar ao desligar)", + "door_sensor_entity": "Sensor de porta", + "pause_cuts_power": "Corte a energia ao pausar", + "switch_entity": "Entidade de comutação (para pausar corte de energia)", + "notify_unload_delay_minutes": "Atraso na notificação de espera de lavanderia (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Habilita o monitoramento de um sensor binário externo para acionar a conclusão do ciclo.", + "external_end_trigger": "Selecione uma entidade de sensor binário. Quando este sensor for acionado, o ciclo atual terminará com o status 'concluído'.", + "external_end_trigger_inverted": "Por padrão, o gatilho é acionado quando o sensor é LIGADO. Marque esta caixa para acionar quando o sensor for DESLIGADO.", + "door_sensor_entity": "Sensor binário opcional para a porta da máquina (ligado = aberto, desligado = fechado). Quando a porta abre durante um ciclo ativo, WashData confirmará o ciclo como pausado intencionalmente. Após o término do ciclo, se a porta ainda estiver fechada, o estado muda para 'Limpar' até que a porta seja aberta. Nota: fechar a porta NÃO retoma automaticamente um ciclo pausado – a retomada deve ser acionada manualmente por meio do botão Retomar Ciclo ou serviço.", + "pause_cuts_power": "Ao pausar por meio do botão Pause Cycle ou serviço, desligue também a entidade de switch configurada. Só entra em vigor se uma entidade de switch estiver configurada para este dispositivo.", + "switch_entity": "A entidade switch a ser alternada ao pausar ou retomar. Obrigatório quando 'Cortar energia ao pausar' está ativado.", + "notify_unload_delay_minutes": "Envie uma notificação por meio do canal de notificação de término após o término do ciclo e a porta não tiver sido aberta por tantos minutos (requer Sensor de Porta). Defina como 0 para desativar. Padrão: 60 min." + } + }, + "pump_section": { + "name": "Monitor de bomba", + "description": "Somente para o tipo de dispositivo bomba. Dispara um evento se um ciclo da bomba ultrapassar essa duração.", + "data": { + "pump_stuck_duration": "Limite(s) de alerta de bomba presa (somente bomba)" + }, + "data_description": { + "pump_stuck_duration": "Se um ciclo de bomba ainda estiver em execução após esses segundos, um evento ha_washdata_pump_stuck será acionado uma vez. Use isto para detectar um motor emperrado (o funcionamento típico da bomba do reservatório é inferior a 60 s). Padrão: 1800 s (30 min). Apenas tipo de dispositivo de bomba." + } + }, + "device_link_section": { + "name": "Link do dispositivo", + "description": "Opcionalmente, vincule este dispositivo WashData a um dispositivo Home Assistant existente (por exemplo, o plugue inteligente ou o próprio aparelho). O dispositivo WashData é então mostrado como \"Conectado através\" desse dispositivo. Deixe em branco para manter o WashData como um dispositivo independente.", + "data": { + "linked_device": "Dispositivo vinculado" + }, + "data_description": { + "linked_device": "Selecione o dispositivo ao qual conectar o WashData. Isso adiciona uma referência 'Conectado via' no registro do dispositivo; WashData mantém seu próprio cartão de dispositivo e entidades. Limpe a seleção para remover o link." + } + } + } + }, + "diagnostics": { + "title": "Diagnóstico e manutenção", + "description": "Execute ações de manutenção, como mesclar ciclos fragmentados ou migrar dados armazenados.\n\n**Uso de armazenamento**\n\n- Tamanho do arquivo: {file_size_kb} KB\n- Ciclos: {cycle_count}\n- Perfis: {profile_count}\n- Rastreamentos de depuração: {debug_count}", + "menu_options": { + "reprocess_history": "Manutenção: reprocessar e otimizar dados", + "clear_debug_data": "Limpar dados de depuração (liberar espaço)", + "wipe_history": "Limpar TODOS os dados deste dispositivo (irreversível)", + "export_import": "Exportar/Importar JSON com configurações (copiar/colar)", + "menu_back": "← Voltar" + } + }, + "clear_debug_data": { + "title": "Limpar dados de depuração", + "description": "Tem certeza de que deseja excluir todos os rastreamentos de depuração armazenados? Isso liberará espaço, mas removerá informações detalhadas de classificação de ciclos anteriores." + }, + "manage_cycles": { + "title": "Gerenciar ciclos", + "description": "Ciclos recentes:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Rotular automaticamente ciclos anteriores", + "select_cycle_to_label": "Rotular ciclo específico", + "select_cycle_to_delete": "Excluir ciclo", + "interactive_editor": "Editor interativo de mesclar/dividir", + "trim_cycle_select": "Recortar dados do ciclo", + "menu_back": "← Voltar" + } + }, + "manage_cycles_empty": { + "title": "Gerenciar ciclos", + "description": "Nenhum ciclo registrado ainda.", + "menu_options": { + "auto_label_cycles": "Rotular automaticamente ciclos anteriores", + "menu_back": "← Voltar" + } + }, + "manage_profiles": { + "title": "Gerenciar perfis", + "description": "Resumo dos perfis:\n{profile_summary}", + "menu_options": { + "create_profile": "Criar novo perfil", + "edit_profile": "Editar/renomear perfil", + "delete_profile_select": "Excluir perfil", + "profile_stats": "Estatísticas de perfil", + "cleanup_profile": "Limpar histórico - gráfico e exclusão", + "assign_profile_phases_select": "Atribuir intervalos de fase", + "menu_back": "← Voltar" + } + }, + "manage_profiles_empty": { + "title": "Gerenciar perfis", + "description": "Nenhum perfil criado ainda.", + "menu_options": { + "create_profile": "Criar novo perfil", + "menu_back": "← Voltar" + } + }, + "manage_phase_catalog": { + "title": "Gerenciar catálogo de fases", + "description": "Catálogo de fases (padrões para este dispositivo + fases personalizadas):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Criar nova fase", + "phase_catalog_edit_select": "Editar fase", + "phase_catalog_delete": "Excluir fase", + "menu_back": "← Voltar" + } + }, + "phase_catalog_create": { + "title": "Criar fase personalizada", + "description": "Crie um nome e uma descrição de fase personalizados e escolha a qual tipo de dispositivo ela se aplica.", + "data": { + "target_device_type": "Tipo de dispositivo alvo", + "phase_name": "Nome da fase", + "phase_description": "Descrição da fase" + } + }, + "phase_catalog_edit_select": { + "title": "Editar fase personalizada", + "description": "Selecione uma fase personalizada para editar.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "phase_catalog_edit": { + "title": "Editar fase personalizada", + "description": "Editando fase: {phase_name}", + "data": { + "phase_name": "Nome da fase", + "phase_description": "Descrição da fase" + } + }, + "phase_catalog_delete": { + "title": "Excluir fase personalizada", + "description": "Selecione uma fase personalizada para excluir. Quaisquer intervalos atribuídos usando esta fase serão removidos.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "assign_profile_phases": { + "title": "Atribuir intervalos de fase", + "description": "Visualização de fase do perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalos atuais:**\n{current_ranges}\n\nUse as ações abaixo para adicionar, editar, excluir ou salvar intervalos.", + "menu_options": { + "assign_profile_phases_add": "Adicionar intervalo de fase", + "assign_profile_phases_edit_select": "Editar intervalo de fase", + "assign_profile_phases_delete": "Excluir intervalo de fase", + "phase_ranges_clear": "Limpar todos os intervalos", + "assign_profile_phases_auto_detect": "Detectar fases automaticamente", + "phase_ranges_save": "Salvar e voltar", + "menu_back": "← Voltar" + } + }, + "assign_profile_phases_add": { + "title": "Adicionar intervalo de fase", + "description": "Adicione um intervalo de fase ao rascunho atual.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de início", + "end_min": "Minuto de fim" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editar intervalo de fase", + "description": "Selecione um intervalo para editar.", + "data": { + "range_index": "Intervalo de fase" + } + }, + "assign_profile_phases_edit": { + "title": "Editar intervalo de fase", + "description": "Atualize o intervalo de fase selecionado.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de início", + "end_min": "Minuto de fim" + } + }, + "assign_profile_phases_delete": { + "title": "Excluir intervalo de fase", + "description": "Selecione um intervalo para remover do rascunho.", + "data": { + "range_index": "Intervalo de fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectadas automaticamente", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) detectada(s) automaticamente.** Escolha uma ação abaixo.", + "data": { + "action": "Ação" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nomear fases detectadas", + "description": "Perfil: **{profile_name}**\n\n**{detected_count} fase(s) detectada(s):**\n{ranges_summary}\n\nAtribua um nome a cada fase." + }, + "assign_profile_phases_select": { + "title": "Atribuir intervalos de fase", + "description": "Selecione um perfil para configurar os intervalos de fase.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estatísticas de perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Criar novo perfil", + "description": "Crie um novo perfil manualmente ou a partir de um ciclo anterior.\n\nExemplos de nomes de perfil: 'Delicados', 'Pesados', 'Lavagem Rápida'", + "data": { + "profile_name": "Nome do perfil", + "reference_cycle": "Ciclo de referência (opcional)", + "manual_duration": "Duração manual (minutos)" + } + }, + "edit_profile": { + "title": "Editar perfil", + "description": "Selecione um perfil para renomear", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Editar detalhes do perfil", + "description": "Perfil atual: {current_name}", + "data": { + "new_name": "Nome do perfil", + "manual_duration": "Duração manual (minutos)" + } + }, + "delete_profile_select": { + "title": "Excluir perfil", + "description": "Selecione um perfil para excluir", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirmar exclusão do perfil", + "description": "⚠️ Isso excluirá permanentemente o perfil: {profile_name}", + "data": { + "unlabel_cycles": "Remover rótulo dos ciclos que usam este perfil" + } + }, + "auto_label_cycles": { + "title": "Rotular automaticamente ciclos anteriores", + "description": "Foram encontrados {total_count} ciclos no total. Perfis: {profiles}", + "data": { + "confidence_threshold": "Confiança mínima (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Selecione o ciclo para rotular", + "description": "✓ = concluído, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Excluir ciclo", + "description": "⚠️ Isso excluirá permanentemente o ciclo selecionado", + "data": { + "cycle_id": "Ciclo para excluir" + } + }, + "label_cycle": { + "title": "Rotular ciclo", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Novo nome de perfil (se estiver criando)" + } + }, + "post_process": { + "title": "Processar histórico (mesclar/dividir)", + "description": "Limpe o histórico do ciclo mesclando execuções fragmentadas e dividindo ciclos mesclados incorretamente dentro de uma janela de tempo. Insira o número de horas para olhar para trás (use 999999 para todas).", + "data": { + "time_range": "Janela de retroação (horas)", + "gap_seconds": "Limite de mesclagem/divisão (segundos)" + }, + "data_description": { + "time_range": "Número de horas para verificar ciclos fragmentados para mesclar. Use 999999 para processar todos os dados históricos.", + "gap_seconds": "Limite para decidir entre divisão e mesclagem. Lacunas MENORES que isso são mescladas. Lacunas MAIORES que isso são divididas. Reduza este valor para forçar a divisão de um ciclo mesclado incorretamente." + } + }, + "cleanup_profile": { + "title": "Limpar histórico - selecione o perfil", + "description": "Selecione um perfil para visualizar. Isso gerará um gráfico mostrando todos os ciclos anteriores deste perfil para ajudar a identificar valores discrepantes.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Limpar histórico - gráfico e exclusão", + "description": "![Gráfico]({graph_url})\n\n**Visualizando {profile_name}**\n\nO gráfico mostra ciclos individuais como linhas coloridas. Identifique os discrepantes (linhas afastadas do grupo) e combine suas cores na lista abaixo para excluí-los.\n\n**Selecione os ciclos para excluir PERMANENTEMENTE:**", + "data": { + "cycles_to_delete": "Ciclos para excluir (verifique as cores correspondentes)" + } + }, + "interactive_editor": { + "title": "Editor interativo de mesclar/dividir", + "description": "Selecione uma ação manual para executar no histórico do seu ciclo.", + "menu_options": { + "editor_split": "Dividir um ciclo (encontrar lacunas)", + "editor_merge": "Mesclar ciclos (unir fragmentos)", + "editor_delete": "Excluir ciclo(s)", + "menu_back": "← Voltar" + } + }, + "editor_select": { + "title": "Selecionar ciclos", + "description": "Selecione o(s) ciclo(s) a serem processados.\n\n{info_text}", + "data": { + "selected_cycles": "Ciclos" + } + }, + "editor_configure": { + "title": "Configurar e aplicar", + "description": "{preview_md}", + "data": { + "confirm_action": "Ação", + "merged_profile": "Perfil resultante", + "new_profile_name": "Novo nome de perfil", + "confirm_commit": "Sim, confirmo esta ação", + "segment_0_profile": "Perfil do segmento 1", + "segment_1_profile": "Perfil do segmento 2", + "segment_2_profile": "Perfil do segmento 3", + "segment_3_profile": "Perfil do segmento 4", + "segment_4_profile": "Perfil do segmento 5", + "segment_5_profile": "Perfil do segmento 6", + "segment_6_profile": "Perfil do segmento 7", + "segment_7_profile": "Perfil do segmento 8", + "segment_8_profile": "Perfil do segmento 9", + "segment_9_profile": "Perfil do segmento 10" + } + }, + "editor_split_params": { + "title": "Configuração de divisão", + "description": "Ajuste a sensibilidade para dividir este ciclo. Qualquer intervalo ocioso maior que isso causará uma divisão.", + "data": { + "split_mode": "Método de divisão" + } + }, + "editor_split_auto_params": { + "title": "Configuração de divisão automática", + "description": "Ajuste a sensibilidade para dividir este ciclo. Qualquer intervalo ocioso maior do que este causará uma divisão.", + "data": { + "min_gap_seconds": "Limite do intervalo de divisão (segundos)" + } + }, + "editor_split_manual_params": { + "title": "Carimbos de data/hora de divisão manual", + "description": "{preview_md}Janela do ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** em {cycle_date}.\n\nInsira um ou mais carimbos de data/hora de divisão (`HH:MM` ou `HH:MM:SS`), um por linha. O ciclo será cortado em cada carimbo de data/hora — N carimbos de data/hora produzem N+1 segmentos.", + "data": { + "split_timestamps": "Timestamps de divisão" + } + }, + "reprocess_history": { + "title": "Manutenção: reprocessar e otimizar dados", + "description": "Executa limpeza profunda dos dados históricos:\n1. Remove leituras de potência zero (silêncio).\n2. Corrige o tempo/duração do ciclo.\n3. Recalcula todos os modelos estatísticos (envelopes).\n\n**Execute isso para corrigir problemas de dados ou aplicar novos algoritmos.**" + }, + "wipe_history": { + "title": "Limpar histórico (somente para testes)", + "description": "⚠️ Isso excluirá permanentemente TODOS os ciclos e perfis armazenados neste dispositivo. Esta ação não pode ser desfeita!" + }, + "export_import": { + "title": "Exportar/Importar JSON", + "description": "Copie/cole ciclos, perfis e configurações para este dispositivo. Exporte abaixo ou cole JSON para importar.", + "data": { + "mode": "Ação", + "json_payload": "JSON de configuração completa" + }, + "data_description": { + "mode": "Escolha Exportar para copiar dados e configurações atuais ou Importar para colar dados exportados de outro dispositivo WashData.", + "json_payload": "Para exportar: copie este JSON (inclui ciclos, perfis e todas as configurações ajustadas). Para importar: cole o JSON exportado do WashData." + } + }, + "record_cycle": { + "title": "Gravar ciclo", + "description": "Grave manualmente um ciclo para criar um perfil limpo e sem interferências.\n\nStatus: **{status}**\nDuração: {duration}s\nAmostras: {samples}", + "menu_options": { + "record_refresh": "Atualizar status", + "record_stop": "Parar gravação (salvar e processar)", + "record_start": "Iniciar nova gravação", + "record_process": "Processar última gravação (recortar e salvar)", + "record_discard": "Descartar última gravação", + "menu_back": "← Voltar" + } + }, + "record_process": { + "title": "Processar gravação", + "description": "![Gráfico]({graph_url})\n\n**O gráfico mostra a gravação bruta.** Azul = Manter, Vermelho = Corte proposto.\n*O gráfico é estático e não é atualizado com alterações no formulário.*\n\nA gravação foi interrompida. Os cortes são alinhados à taxa de amostragem detectada (~{sampling_rate}s).\n\nDuração bruta: {duration}s\nAmostras: {samples}", + "data": { + "head_trim": "Corte do início (segundos)", + "tail_trim": "Corte do fim (segundos)", + "save_mode": "Destino de salvamento", + "profile_name": "Nome do perfil" + } + }, + "learning_feedbacks": { + "title": "Feedbacks de aprendizagem", + "description": "Feedback pendente: {count}\n\n{pending_table}\n\nSelecione uma solicitação de revisão de ciclo para processar.", + "menu_options": { + "learning_feedbacks_pick": "Revisar um feedback pendente", + "learning_feedbacks_dismiss_all": "Descartar todo o feedback pendente", + "menu_back": "← Voltar" + } + }, + "learning_feedbacks_pick": { + "title": "Selecionar feedback para revisar", + "description": "Selecione uma solicitação de revisão de ciclo para abrir.", + "data": { + "selected_feedback": "Selecionar ciclo para revisar" + } + }, + "learning_feedbacks_empty": { + "title": "Feedbacks de aprendizagem", + "description": "Nenhuma solicitação de feedback pendente foi encontrada.", + "menu_options": { + "menu_back": "← Voltar" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Dispensar todos os feedbacks pendentes", + "description": "Você está prestes a dispensar **{count}** solicitações de feedback pendentes. Os ciclos dispensados permanecem no seu histórico, mas não contribuirão com novo sinal de aprendizagem. Isso não pode ser desfeito.\n\n✅ Marque a caixa abaixo e clique em **Enviar** para dispensar tudo.\n❌ Deixe desmarcado e clique em **Enviar** para cancelar e voltar.", + "data": { + "confirm_dismiss_all": "Confirmar: descartar todas as solicitações de feedback pendentes" + } + }, + "resolve_feedback": { + "title": "Resolver feedback", + "description": "{comparison_img}{candidates_table}\n**Perfil detectado**: {detected_profile} ({confidence_pct}%)\n**Duração estimada**: {est_duration_min} min\n**Duração real**: {act_duration_min} min\n\n__Escolha uma ação abaixo:__", + "data": { + "action": "O que você gostaria de fazer?", + "corrected_profile": "Programa correto (se estiver corrigindo)", + "corrected_duration": "Duração correta em segundos (se estiver corrigindo)" + } + }, + "trim_cycle_select": { + "title": "Recortar ciclo - selecione o ciclo", + "description": "Selecione um ciclo para recortar. ✓ = concluído, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Recortar ciclo", + "description": "Janela atual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Ação" + } + }, + "trim_cycle_start": { + "title": "Definir início do recorte", + "description": "Janela de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInício atual: **{current_wallclock}** (+{current_offset_min} min do início do ciclo)\n\nEscolha um novo horário de início usando o seletor de relógio abaixo.", + "data": { + "trim_start_time": "Novo horário de início", + "trim_start_min": "Novo início (minutos desde o início do ciclo)" + } + }, + "trim_cycle_end": { + "title": "Definir fim do recorte", + "description": "Janela de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFim atual: **{current_wallclock}** (+{current_offset_min} min do início do ciclo)\n\nEscolha um novo horário de término usando o seletor de relógio abaixo.", + "data": { + "trim_end_time": "Novo horário de término", + "trim_end_min": "Novo fim (minutos do início do ciclo)" + } + } + }, + "error": { + "import_failed": "Falha na importação. Cole um JSON de exportação WashData válido.", + "select_exactly_one": "Selecione exatamente um ciclo para esta ação.", + "select_at_least_one": "Selecione pelo menos um ciclo para esta ação.", + "select_at_least_two": "Selecione pelo menos dois ciclos para esta ação.", + "profile_exists": "Já existe um perfil com este nome", + "rename_failed": "Falha ao renomear perfil. O perfil pode não existir ou o novo nome já está em uso.", + "assignment_failed": "Falha ao atribuir perfil ao ciclo", + "duplicate_phase": "Já existe uma fase com este nome.", + "phase_not_found": "A fase selecionada não foi encontrada.", + "invalid_phase_name": "O nome da fase não pode ficar vazio.", + "phase_name_too_long": "O nome da fase é muito longo.", + "invalid_phase_range": "O intervalo de fase é inválido. O fim deve ser maior que o início.", + "invalid_phase_timestamp": "O carimbo de data/hora é inválido. Use o formato AAAA-MM-DD HH:MM ou HH:MM.", + "overlapping_phase_ranges": "Os intervalos de fase se sobrepõem. Ajuste os intervalos e tente novamente.", + "incomplete_phase_row": "Cada linha de fase deve incluir uma fase mais os valores completos de início/fim para o modo selecionado.", + "timestamp_mode_cycle_required": "Selecione um ciclo de origem ao usar o modo de carimbo de data/hora.", + "no_phase_ranges": "Ainda não há intervalos de fase disponíveis para essa ação.", + "feedback_notification_title": "WashData: verificar ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programa**: {program} ({confidence}% de confiança)\n**Horário**: {time}\n\nWashData precisa da sua ajuda para verificar este ciclo detectado.\n\nVá para **Configurações > Dispositivos e serviços > WashData > Configurar > Feedbacks de aprendizagem** para confirmar ou corrigir este resultado.", + "suggestions_ready_notification_title": "WashData: configurações sugeridas prontas ({device})", + "suggestions_ready_notification_message": "O sensor **Configurações sugeridas** agora exibe **{count}** recomendações acionáveis.\n\nPara revisá-las e aplicá-las: **Configurações > Dispositivos e serviços > WashData > Configurar > Configurações avançadas > Aplicar valores sugeridos**.\n\nAs sugestões são opcionais e mostradas para revisão antes de salvar.", + "trim_range_invalid": "O início deve ser antes do fim. Ajuste seus pontos de recorte.", + "trim_failed": "O recorte falhou. O ciclo pode não existir mais ou a janela está vazia.", + "invalid_split_timestamp": "O timestamp é inválido ou está fora da janela do ciclo. Use HH:MM ou HH:MM:SS dentro da janela do ciclo.", + "no_split_segments_found": "Não foi possível produzir segmentos válidos a partir desses timestamps. Verifique se cada timestamp está dentro da janela do ciclo e se os segmentos têm pelo menos 1 minuto de duração.", + "auto_tune_suggestion": "{device_type} {device_title} detectou ciclos fantasmas. Alteração sugerida de min_power: {current_min}W -> {new_min}W (não aplicada automaticamente).", + "auto_tune_title": "Ajuste automático WashData", + "auto_tune_fallback": "{device_type} {device_title} detectou ciclos fantasmas. Alteração sugerida de min_power: {current_min}W -> {new_min}W (não aplicada automaticamente)." + }, + "abort": { + "no_cycles_found": "Nenhum ciclo encontrado", + "no_split_segments_found": "Nenhum segmento divisível foi encontrado no ciclo selecionado.", + "cycle_not_found": "Não foi possível carregar o ciclo selecionado.", + "no_power_data": "Não há dados de rastreamento de energia disponíveis para o ciclo selecionado.", + "no_profiles_found": "Nenhum perfil encontrado. Crie um perfil primeiro.", + "no_profiles_for_matching": "Nenhum perfil disponível para correspondência. Crie perfis primeiro.", + "no_unlabeled_cycles": "Todos os ciclos já estão rotulados", + "no_suggestions": "Ainda não há valores sugeridos disponíveis. Execute alguns ciclos e tente novamente.", + "no_predictions": "Sem previsões", + "no_custom_phases": "Nenhuma fase personalizada encontrada.", + "reprocess_success": "{count} ciclos reprocessados com sucesso.", + "debug_data_cleared": "Dados de depuração de {count} ciclos limpos com sucesso." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Início do ciclo", + "cycle_finish": "Fim do ciclo", + "cycle_live": "Progresso ao vivo" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sem rótulo)", + "create_new_profile": "Criar novo perfil", + "remove_label": "Remover rótulo", + "no_reference_cycle": "(Sem ciclo de referência)", + "all_device_types": "Todos os tipos de dispositivos", + "current_suffix": "(atual)", + "editor_select_info": "Selecione 1 ciclo para dividir ou 2 ou mais ciclos para mesclar.", + "editor_select_info_split": "Selecione 1 ciclo para dividir.", + "editor_select_info_merge": "Selecione 2 ou mais ciclos para mesclar.", + "editor_select_info_delete": "Selecione 1 ou mais ciclos para excluir.", + "no_cycles_recorded": "Nenhum ciclo registrado ainda.", + "profile_summary_line": "- **{name}**: {count} ciclos, {avg}m de média", + "no_profiles_created": "Nenhum perfil criado ainda.", + "phase_preview": "Pré-visualização de fase", + "phase_preview_no_curve": "A curva média do perfil ainda não está disponível. Execute/rotule mais ciclos para este perfil.", + "average_power_curve": "Curva de potência média", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciclos, ~{duration}m de média)", + "profile_option_short_fmt": "{name} ({count} ciclos, ~{duration}m)", + "deleted_cycles_from_profile": "{count} ciclo(s) excluído(s) de {profile}.", + "not_enough_data_graph": "Dados insuficientes para gerar o gráfico.", + "no_profiles_found": "Nenhum perfil encontrado.", + "cycle_info_fmt": "Ciclo: {start}, {duration}m, Atual: {label}", + "top_candidates_header": "### Principais candidatos", + "tbl_profile": "Perfil", + "tbl_confidence": "Confiança", + "tbl_correlation": "Correlação", + "tbl_duration_match": "Correspondência de duração", + "detected_profile": "Perfil detectado", + "estimated_duration": "Duração estimada", + "actual_duration": "Duração real", + "choose_action": "__Escolha uma ação abaixo:__", + "tbl_count": "Contagem", + "tbl_avg": "Média", + "tbl_min": "Mínimo", + "tbl_max": "Máx.", + "tbl_energy_avg": "Energia (média)", + "tbl_energy_total": "Energia (total)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Última execução", + "graph_legend_title": "Legenda do gráfico", + "graph_legend_body": "A faixa azul representa o intervalo de consumo de energia mínimo e máximo observado. A linha mostra a curva de potência média.", + "recording_preview": "Pré-visualização da gravação", + "trim_start": "Início do recorte", + "trim_end": "Fim do recorte", + "split_preview_title": "Pré-visualização da divisão", + "split_preview_found_fmt": "Foram encontrados {count} segmentos.", + "split_preview_confirm_fmt": "Clique em Confirmar para dividir este ciclo em {count} ciclos separados.", + "merge_preview_title": "Pré-visualização da mesclagem", + "merge_preview_joining_fmt": "Unindo {count} ciclos. As lacunas serão preenchidas com leituras de 0W.", + "editor_delete_preview_title": "Prévia de exclusão", + "editor_delete_preview_intro": "Os ciclos selecionados serão excluídos permanentemente:", + "editor_delete_preview_confirm": "Clique em Confirmar para excluir permanentemente esses registros de ciclos.", + "no_power_preview": "*Não há dados de energia disponíveis para pré-visualização.*", + "profile_comparison": "Comparação de perfis", + "actual_cycle_label": "Este ciclo (real)", + "storage_usage_header": "Uso de armazenamento", + "storage_file_size": "Tamanho do arquivo", + "storage_cycles": "Ciclos", + "storage_profiles": "Perfis", + "storage_debug_traces": "Rastreamentos de depuração", + "overview_suffix": "Visão geral", + "phase_preview_for_profile": "Pré-visualização de fase para o perfil", + "current_ranges_header": "Intervalos atuais", + "assign_phases_help": "Use as ações abaixo para adicionar, editar, excluir ou salvar intervalos.", + "profile_summary_header": "Resumo dos perfis", + "recent_cycles_header": "Ciclos recentes", + "trim_cycle_preview_title": "Pré-visualização do recorte do ciclo", + "trim_cycle_preview_no_data": "Não há dados de potência disponíveis para este ciclo.", + "trim_cycle_preview_kept_suffix": "mantido", + "table_program": "Programa", + "table_when": "Quando", + "table_length": "Duração", + "table_match": "Correspondência", + "table_profile": "Perfil", + "table_cycles": "Ciclos", + "table_avg_length": "Duração média", + "table_last_run": "Última execução", + "table_avg_energy": "Energia média", + "table_detected_program": "Programa detectado", + "table_confidence": "Confiança", + "table_reported": "Informado", + "phase_builtin_suffix": "(Embutido)", + "phase_none_available": "Não há fases disponíveis.", + "settings_deprecation_warning": "⚠️ **Tipo de dispositivo obsoleto:** {device_type} está programado para remoção em uma versão futura. O pipeline correspondente do WashData não produz resultados confiáveis ​​para esta classe de dispositivo. Sua integração continua funcionando durante o período de descontinuação; para silenciar este aviso, mude **Tipo de dispositivo** abaixo para um dos tipos suportados (Máquina de lavar, Secadora, Combinação de lavadora e secadora, Máquina de lavar louça, Fritadeira de ar, Máquina de fazer pão ou Bomba) ou para **Outro (Avançado)** se o seu aparelho não corresponder a nenhum dos tipos suportados. **Outros (Avançado)** fornece padrões intencionalmente genéricos que não são ajustados para nenhum dispositivo específico, portanto, você mesmo precisará configurar limites, tempos limite e parâmetros correspondentes; todas as suas configurações existentes são preservadas quando você muda.", + "phase_other_device_types": "Outros tipos de dispositivos:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir um ciclo (encontrar lacunas)", + "merge": "Mesclar ciclos (unir fragmentos)", + "delete": "Excluir ciclo(s)" + } + }, + "split_mode": { + "options": { + "auto": "Detectar automaticamente intervalos ociosos", + "manual": "Timestamps manuais" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Manutenção: reprocessar e otimizar dados", + "clear_debug_data": "Limpar dados de depuração (liberar espaço)", + "wipe_history": "Limpar TODOS os dados deste dispositivo (irreversível)", + "export_import": "Exportar/Importar JSON com configurações (copiar/colar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportar todos os dados", + "import": "Importar/mesclar dados" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Rotular automaticamente ciclos anteriores", + "select_cycle_to_label": "Rotular ciclo específico", + "select_cycle_to_delete": "Excluir ciclo", + "interactive_editor": "Editor interativo de mesclar/dividir", + "trim_cycle": "Recortar dados do ciclo" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Criar novo perfil", + "edit_profile": "Editar/renomear perfil", + "delete_profile": "Excluir perfil", + "profile_stats": "Estatísticas de perfil", + "cleanup_profile": "Limpar histórico - gráfico e exclusão", + "assign_phases": "Atribuir intervalos de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Criar nova fase", + "edit_custom_phase": "Editar fase", + "delete_custom_phase": "Excluir fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Adicionar intervalo de fase", + "edit_range": "Editar intervalo de fase", + "delete_range": "Excluir intervalo de fase", + "clear_ranges": "Limpar todos os intervalos", + "auto_detect_ranges": "Detectar fases automaticamente", + "save_ranges": "Salvar e voltar" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Atualizar status", + "stop_recording": "Parar gravação (salvar e processar)", + "start_recording": "Iniciar nova gravação", + "process_recording": "Processar última gravação (recortar e salvar)", + "discard_recording": "Descartar última gravação" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Criar novo perfil", + "existing_profile": "Adicionar ao perfil existente", + "discard": "Descartar gravação" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - detecção correta", + "correct": "Corrigir - escolher programa/duração", + "ignore": "Ignorar - ciclo falso positivo/ruído", + "delete": "Excluir - remover do histórico" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nomear e aplicar fases", + "cancel": "Voltar sem alterações" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Definir ponto de início", + "set_end": "Definir ponto de fim", + "reset": "Redefinir para duração total", + "apply": "Aplicar recorte", + "cancel": "Cancelar" + } + }, + "device_type": { + "options": { + "washing_machine": "Máquina de lavar", + "dryer": "Secador", + "washer_dryer": "Combinação Lavadora e Secadora", + "dishwasher": "Máquina de lavar louça", + "coffee_machine": "Máquina de café (obsoleta)", + "ev": "Veículo elétrico (obsoleto)", + "air_fryer": "Fritadeira de ar", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "Máquina de fazer pão", + "pump": "Bomba / Bomba de depósito", + "oven": "Forno (obsoleto)", + "other": "Outro (avançado)" + } + } + }, + "services": { + "label_cycle": { + "name": "Rotular ciclo", + "description": "Atribua um perfil existente a um ciclo anterior ou remova o rótulo.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar a ser rotulado." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a ser rotulado." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O nome de um perfil existente (crie perfis no menu Gerenciar Perfis). Deixe em branco para remover o rótulo." + } + } + }, + "create_profile": { + "name": "Criar perfil", + "description": "Crie um novo perfil (autônomo ou baseado em um ciclo de referência).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "Nome do novo perfil (ex.: 'Pesados', 'Delicados')." + }, + "reference_cycle_id": { + "name": "ID do ciclo de referência", + "description": "ID de ciclo opcional para basear este perfil." + } + } + }, + "delete_profile": { + "name": "Excluir perfil", + "description": "Exclua um perfil e, opcionalmente, remova o rótulo dos ciclos que o utilizam.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O perfil a ser excluído." + }, + "unlabel_cycles": { + "name": "Remover rótulo dos ciclos", + "description": "Remove o rótulo do perfil dos ciclos que o utilizam." + } + } + }, + "auto_label_cycles": { + "name": "Rotular automaticamente ciclos anteriores", + "description": "Rotule retroativamente ciclos não rotulados usando correspondência de perfil.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar." + }, + "confidence_threshold": { + "name": "Limite de confiança", + "description": "Confiança mínima de correspondência (0,50–0,95) para aplicar rótulos." + } + } + }, + "export_config": { + "name": "Exportar configuração", + "description": "Exporte os perfis, ciclos e configurações deste dispositivo para um arquivo JSON (por dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar a exportar." + }, + "path": { + "name": "Caminho", + "description": "Caminho de arquivo absoluto opcional para gravação (o padrão é /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importar configuração", + "description": "Importe perfis, ciclos e configurações para este dispositivo a partir de um arquivo de exportação JSON (as configurações podem ser substituídas).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar para importar." + }, + "path": { + "name": "Caminho", + "description": "Caminho absoluto para o arquivo JSON de exportação a ser importado." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar feedback do ciclo", + "description": "Confirme ou corrija um programa detectado automaticamente após um ciclo concluído. Forneça `entry_id` (avançado) ou `device_id` (recomendado).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData (recomendado em vez de entry_id)." + }, + "entry_id": { + "name": "ID de entrada", + "description": "O ID da entrada de configuração do dispositivo (alternativa ao device_id)." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo mostrado na notificação/registros de feedback." + }, + "user_confirmed": { + "name": "Confirmar programa detectado", + "description": "Defina como verdadeiro se o programa detectado estiver correto." + }, + "corrected_profile": { + "name": "Perfil corrigido", + "description": "Se não for confirmado, forneça o nome correto do perfil/programa." + }, + "corrected_duration": { + "name": "Duração corrigida (segundos)", + "description": "Duração corrigida opcional em segundos." + }, + "notes": { + "name": "Observações", + "description": "Observações opcionais sobre este ciclo." + } + } + }, + "record_start": { + "name": "Gravar início do ciclo", + "description": "Inicia a gravação manual de um ciclo limpo (ignora toda a lógica de correspondência).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar a ser gravado." + } + } + }, + "record_stop": { + "name": "Gravar parada do ciclo", + "description": "Interrompe a gravação manual.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo da máquina de lavar para parar a gravação." + } + } + }, + "trim_cycle": { + "name": "Recortar ciclo", + "description": "Recorta os dados de potência de um ciclo anterior para uma janela de tempo específica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a ser recortado." + }, + "trim_start_s": { + "name": "Início do recorte (segundos)", + "description": "Mantém os dados a partir deste deslocamento em segundos. Padrão: 0." + }, + "trim_end_s": { + "name": "Fim do recorte (segundos)", + "description": "Mantém os dados até este deslocamento em segundos. Padrão = duração total." + } + } + }, + "pause_cycle": { + "name": "Ciclo de pausa", + "description": "Pause o ciclo ativo para um dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a ser pausado." + } + } + }, + "resume_cycle": { + "name": "Retomar ciclo", + "description": "Retome um ciclo pausado para um dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a ser retomado." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Em funcionamento" + }, + "match_ambiguity": { + "name": "Ambiguidade de correspondência" + } + }, + "sensor": { + "washer_state": { + "name": "Estado", + "state": { + "off": "Desligado", + "idle": "Ocioso", + "starting": "Iniciando", + "running": "Em funcionamento", + "paused": "Pausado", + "user_paused": "Pausado pelo usuário", + "ending": "Encerrando", + "finished": "Concluído", + "anti_wrinkle": "Anti-amasso", + "delay_wait": "Esperando para começar", + "interrupted": "Interrompido", + "force_stopped": "Parada forçada", + "rinse": "Enxaguar", + "unknown": "Desconhecido", + "clean": "Limpar" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Tempo restante" + }, + "total_duration": { + "name": "Duração total" + }, + "cycle_progress": { + "name": "Progresso" + }, + "current_power": { + "name": "Potência atual" + }, + "elapsed_time": { + "name": "Tempo decorrido" + }, + "debug_info": { + "name": "Informações de depuração" + }, + "match_confidence": { + "name": "Confiança na correspondência" + }, + "top_candidates": { + "name": "Principais candidatos", + "state": { + "none": "Nenhum" + } + }, + "current_phase": { + "name": "Fase atual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Contagem de ciclos de {profile_name}", + "unit_of_measurement": "ciclos" + }, + "suggestions": { + "name": "Configurações sugeridas disponíveis" + }, + "pump_runs_today": { + "name": "Funcionamento da bomba (últimas 24 horas)" + }, + "cycle_count": { + "name": "Contagem de ciclos" + } + }, + "select": { + "program_select": { + "name": "Programa do ciclo", + "state": { + "auto_detect": "Detecção automática" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forçar fim do ciclo" + }, + "pause_cycle": { + "name": "Ciclo de pausa" + }, + "resume_cycle": { + "name": "Retomar ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "É necessário um device_id válido." + }, + "cycle_id_required": { + "message": "É necessário um cycle_id válido." + }, + "profile_name_required": { + "message": "É necessário um profile_name válido." + }, + "device_not_found": { + "message": "Dispositivo não encontrado." + }, + "no_config_entry": { + "message": "Nenhuma entrada de configuração encontrada para o dispositivo." + }, + "integration_not_loaded": { + "message": "Integração não carregada para este dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclo não encontrado ou sem dados de potência." + }, + "trim_failed_empty_window": { + "message": "Recorte falhou - ciclo não encontrado, sem dados de energia ou a janela resultante está vazia." + }, + "trim_invalid_range": { + "message": "trim_end_s deve ser maior que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/pt.json b/custom_components/ha_washdata/translations/pt.json new file mode 100644 index 0000000..9565b20 --- /dev/null +++ b/custom_components/ha_washdata/translations/pt.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configuração WashData", + "description": "Configure a sua máquina de lavar ou outro electrodoméstico.\n\nÉ necessário um sensor de potência.\n\n**De seguida, ser-lhe-á perguntado se deseja criar o seu primeiro perfil.**", + "data": { + "name": "Nome do dispositivo", + "device_type": "Tipo de dispositivo", + "power_sensor": "Sensor de potência", + "min_power": "Limiar mínimo de potência (W)" + }, + "data_description": { + "name": "Um nome descritivo para este dispositivo (por exemplo, 'Máquina de lavar', 'Máquina de lavar loiça').", + "device_type": "Que tipo de electrodoméstico é este? Ajuda a personalizar a detecção e a rotulagem.", + "power_sensor": "A entidade do sensor que reporta o consumo de energia em tempo real (em watts) da sua tomada inteligente.", + "min_power": "Leituras de potência acima deste limiar (em watts) indicam que o electrodoméstico está a funcionar. Comece com 2W para a maioria dos dispositivos." + } + }, + "first_profile": { + "title": "Criar primeiro perfil", + "description": "Um **Ciclo** é uma operação completa do seu electrodoméstico (por exemplo, uma carga de roupa). Um **Perfil** agrupa esses ciclos por tipo (por exemplo, 'Algodão', 'Lavagem Rápida'). Criar um perfil agora ajuda a integração a estimar a duração de imediato.\n\nPode seleccionar manualmente este perfil nos controlos do dispositivo se não for detectado automaticamente.\n\n**Deixe 'Nome do perfil' em branco para ignorar este passo.**", + "data": { + "profile_name": "Nome do perfil (opcional)", + "manual_duration": "Duração estimada (minutos)" + } + } + }, + "error": { + "cannot_connect": "Falha ao ligar", + "invalid_auth": "Autenticação inválida", + "unknown": "Erro inesperado" + }, + "abort": { + "already_configured": "O dispositivo já está configurado" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Rever feedbacks de aprendizagem", + "settings": "Definições", + "notifications": "Notificações", + "manage_cycles": "Gerir ciclos", + "manage_profiles": "Gerir perfis", + "manage_phase_catalog": "Gerir catálogo de fases", + "record_cycle": "Gravar ciclo (manual)", + "diagnostics": "Diagnóstico e manutenção" + } + }, + "apply_suggestions": { + "title": "Copiar valores sugeridos", + "description": "Isto copiará os valores sugeridos actuais para as suas Definições guardadas. Esta é uma acção única e não activa substituições automáticas.\n\nValores sugeridos a copiar:\n{suggested}", + "data": { + "confirm": "Confirmar acção de cópia" + } + }, + "apply_suggestions_confirm": { + "title": "Rever alterações sugeridas", + "description": "WashData encontrou **{pending_count}** valores sugeridos para aplicação.\n\nMudanças:\n{changes}\n\n✅ Marque a caixa abaixo e clique em **Enviar** para aplicar e salvar essas alterações imediatamente.\n❌ Deixe desmarcado e clique em **Enviar** para cancelar e voltar.", + "data": { + "confirm_apply_suggestions": "Aplicar e salvar essas alterações" + } + }, + "settings": { + "title": "Definições", + "description": "{deprecation_warning}Ajuste os limiares de detecção, aprendizagem e comportamento avançado. As predefinições funcionam bem para a maioria das configurações.\n\nDefinições sugeridas disponíveis: {suggestions_count}.\nSe for acima de 0, abra as Definições avançadas e use Aplicar valores sugeridos para rever as recomendações.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos", + "device_type": "Tipo de dispositivo", + "power_sensor": "Entidade do sensor de potência", + "min_power": "Potência mínima (W)", + "off_delay": "Atraso de fim de ciclo (s)", + "notify_actions": "Acções de notificação", + "notify_people": "Atrasar envio até estas pessoas estarem em casa", + "notify_only_when_home": "Atrasar notificações até a pessoa seleccionada estar em casa", + "notify_fire_events": "Acionar eventos de automatização", + "notify_start_services": "Início do Ciclo - Alvos de Notificação", + "notify_finish_services": "Término do Ciclo - Alvos de Notificação", + "notify_live_services": "Progresso ao vivo – metas de notificação", + "notify_before_end_minutes": "Notificação prévia à conclusão (minutos antes do fim)", + "notify_title": "Título da notificação", + "notify_icon": "Ícone de notificação", + "notify_start_message": "Formato da mensagem de início", + "notify_finish_message": "Formato da mensagem de conclusão", + "notify_pre_complete_message": "Formato da mensagem pré-conclusão", + "show_advanced": "Editar definições avançadas (próximo passo)" + }, + "data_description": { + "apply_suggestions": "Assinale esta caixa para copiar os valores sugeridos pelo algoritmo de aprendizagem para o formulário. Reveja antes de guardar.\n\nSugerido (por aprendizagem):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Seleccione o tipo de electrodoméstico (Máquina de lavar, Secadora, Máquina de lavar loiça, Máquina de café, VE). Esta etiqueta é guardada em cada ciclo para melhor organização.", + "power_sensor": "A entidade do sensor que reporta a potência em tempo real (em watts). Pode alterar isto a qualquer momento sem perder dados históricos - útil se substituir uma tomada inteligente.", + "min_power": "Leituras de potência acima deste limiar (em watts) indicam que o electrodoméstico está a funcionar. Comece com 2W para a maioria dos dispositivos.", + "off_delay": "Segundos em que a potência suavizada deve permanecer abaixo do limiar de paragem para marcar a conclusão. Predefinição: 120s.", + "notify_actions": "Sequência de acções opcional do Home Assistant para executar nas notificações (scripts, notify, TTS, etc.). Se definido, as acções são executadas antes do serviço de notificação de fallback.", + "notify_people": "Entidades de pessoas utilizadas para controlo de presença. Quando activado, o envio de notificações é adiado até pelo menos uma pessoa seleccionada estar em casa.", + "notify_only_when_home": "Se activado, adia as notificações até pelo menos uma pessoa seleccionada estar em casa.", + "notify_fire_events": "Se activado, aciona eventos do Home Assistant para início e fim de ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para conduzir automatizações.", + "notify_start_services": "Um ou mais serviços de notificação para alertar quando um ciclo começa. Deixe em branco para pular as notificações de início.", + "notify_finish_services": "Um ou mais serviços de notificação para alertar quando um ciclo termina ou está próximo da conclusão. Deixe em branco para pular as notificações de conclusão.", + "notify_live_services": "Um ou mais serviços de notificação para receber atualizações de progresso ao vivo enquanto o ciclo é executado (somente aplicativo complementar móvel). Deixe em branco para pular as atualizações ao vivo.", + "notify_before_end_minutes": "Minutos antes do fim estimado do ciclo para acionar uma notificação. Defina como 0 para desactivar. Predefinição: 0.", + "notify_title": "Título personalizado para notificações. Suporta o marcador de posição {device}.", + "notify_icon": "Ícone a utilizar nas notificações (ex.: mdi:washing-machine). Deixe em branco para enviar sem ícone.", + "notify_start_message": "Mensagem enviada quando um ciclo começa. Suporta o marcador de posição {device}.", + "notify_finish_message": "Mensagem enviada quando um ciclo termina. Suporta os marcadores de posição {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Texto das atualizações recorrentes do progresso ao vivo enquanto o ciclo é executado. Suporta marcadores de posição {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificações", + "description": "Configure canais de notificação, modelos e actualizações opcionais de progresso em tempo real no telemóvel.", + "data": { + "notify_actions": "Acções de notificação", + "notify_people": "Atrasar envio até estas pessoas estarem em casa", + "notify_only_when_home": "Atrasar notificações até a pessoa seleccionada estar em casa", + "notify_fire_events": "Acionar eventos de automatização", + "notify_start_services": "Início do Ciclo - Alvos de Notificação", + "notify_finish_services": "Término do Ciclo - Alvos de Notificação", + "notify_live_services": "Progresso ao vivo – metas de notificação", + "notify_before_end_minutes": "Notificação prévia à conclusão (minutos antes do fim)", + "notify_live_interval_seconds": "Intervalo de actualização em tempo real (segundos)", + "notify_live_overrun_percent": "Tolerância de excesso de actualizações em tempo real (%)", + "notify_live_chronometer": "Temporizador de contagem regressiva do cronômetro", + "notify_title": "Título da notificação", + "notify_icon": "Ícone de notificação", + "notify_start_message": "Formato da mensagem de início", + "notify_finish_message": "Formato da mensagem de conclusão", + "notify_pre_complete_message": "Formato da mensagem pré-conclusão", + "energy_price_entity": "Preço da Energia - Entidade (opcional)", + "energy_price_static": "Preço da Energia - Valor estático (opcional)", + "go_back": "Voltar sem guardar", + "notify_reminder_message": "Formato de mensagem de lembrete", + "notify_timeout_seconds": "Dispensar automaticamente após (segundos)", + "notify_channel": "Canal de notificação (status/ao vivo/lembrete)", + "notify_finish_channel": "Canal de Notificação Concluído" + }, + "data_description": { + "go_back": "Assinale esta opção e clique em Submeter para descartar quaisquer alterações acima e voltar ao menu principal.", + "notify_actions": "Sequência de acções opcional do Home Assistant para executar nas notificações (scripts, notify, TTS, etc.). Se definido, as acções são executadas antes do serviço de notificação de fallback.", + "notify_people": "Entidades de pessoas utilizadas para controlo de presença. Quando activado, o envio de notificações é adiado até pelo menos uma pessoa seleccionada estar em casa.", + "notify_only_when_home": "Se activado, adia as notificações até pelo menos uma pessoa seleccionada estar em casa.", + "notify_fire_events": "Se activado, aciona eventos do Home Assistant para início e fim de ciclo (ha_washdata_cycle_started e ha_washdata_cycle_ended) para conduzir automatizações.", + "notify_start_services": "Um ou mais serviços de notificação para alertar quando um ciclo começa (por exemplo, notify.mobile_app_my_phone). Deixe em branco para pular as notificações de início.", + "notify_finish_services": "Um ou mais serviços de notificação para alertar quando um ciclo termina ou está próximo da conclusão. Deixe em branco para pular as notificações de conclusão.", + "notify_live_services": "Um ou mais serviços de notificação para receber atualizações de progresso ao vivo enquanto o ciclo é executado (somente aplicativo complementar móvel). Deixe em branco para pular as atualizações ao vivo.", + "notify_before_end_minutes": "Minutos antes do fim estimado do ciclo para acionar uma notificação. Defina como 0 para desactivar. Predefinição: 0.", + "notify_live_interval_seconds": "Com que frequência as actualizações de progresso são enviadas durante o funcionamento. Valores mais baixos são mais reactivos mas consomem mais notificações. É imposto um mínimo de 30 segundos - valores abaixo de 30 s são automaticamente arredondados para 30 s.", + "notify_live_overrun_percent": "Margem de segurança acima da contagem estimada de actualizações para ciclos longos ou que excedam o previsto (por exemplo, 20% permite 1,2× as actualizações estimadas).", + "notify_live_chronometer": "Quando ativado, cada atualização ao vivo inclui uma contagem regressiva do cronômetro até o tempo estimado de término. O cronômetro de notificação diminui automaticamente no dispositivo entre as atualizações (somente aplicativo complementar para Android).", + "notify_title": "Título personalizado para notificações. Suporta o marcador de posição {device}.", + "notify_icon": "Ícone a utilizar nas notificações (ex.: mdi:washing-machine). Deixe em branco para enviar sem ícone.", + "notify_start_message": "Mensagem enviada quando um ciclo começa. Suporta o marcador de posição {device}.", + "notify_finish_message": "Mensagem enviada quando um ciclo termina. Suporta os marcadores de posição {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Selecione uma entidade numérica que contenha o preço atual da eletricidade (por exemplo, sensor.electricity_price, input_number.energy_rate). Quando definido, ativa o espaço reservado {cost}. Tem precedência sobre o valor estático se ambos estiverem configurados.", + "energy_price_static": "Preço fixo da eletricidade por kWh. Quando definido, ativa o espaço reservado {cost}. Ignorado se uma entidade estiver configurada acima. Adicione o símbolo da sua moeda no modelo de mensagem, por exemplo. {cost} €.", + "notify_reminder_message": "O texto do lembrete único enviou o número configurado de minutos antes do término estimado. Diferente das atualizações ao vivo recorrentes acima. Suporta marcadores de posição {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Ignore automaticamente as notificações após esses segundos (encaminhadas como 'tempo limite' do aplicativo complementar). Defina como 0 para mantê-los até serem descartados. Padrão: 0.", + "notify_channel": "Android canal de notificação de aplicativo complementar para notificações de status, progresso ao vivo e lembretes. O som e a importância de um canal são configurados no aplicativo complementar na primeira vez que o nome do canal é usado - WashData define apenas o nome do canal. Deixe em branco para o padrão do aplicativo.", + "notify_finish_channel": "Canal Android separado para o alerta finalizado (também usado pelo lembrete e pelo aviso de espera da lavanderia) para que ele possa ter seu próprio som. Deixe em branco para reutilizar o canal acima.", + "notify_pre_complete_message": "Texto das atualizações recorrentes do progresso ao vivo enquanto o ciclo é executado. Suporta marcadores de posição {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Definições avançadas", + "description": "Ajuste os limiares de detecção, aprendizagem e comportamento avançado. As predefinições funcionam bem para a maioria das configurações.", + "sections": { + "suggestions_section": { + "name": "Definições sugeridas", + "description": "{suggestions_count} sugestões aprendidas disponíveis. Assinale Aplicar valores sugeridos para rever as alterações propostas antes de guardar.", + "data": { + "apply_suggestions": "Aplicar valores sugeridos" + }, + "data_description": { + "apply_suggestions": "Assinale esta caixa para abrir um passo de revisão dos valores sugeridos pelo algoritmo de aprendizagem. Nada é guardado automaticamente.\n\nSugerido (por aprendizagem):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Deteção e limiares de potência", + "description": "Quando o aparelho é considerado LIGADO ou DESLIGADO, limites de energia e temporização para transições de estado.", + "data": { + "start_duration_threshold": "Duração de debounce de início (s)", + "start_energy_threshold": "Porta de energia de início (Wh)", + "completion_min_seconds": "Tempo de execução mínimo de conclusão (segundos)", + "start_threshold_w": "Limiar de início (W)", + "stop_threshold_w": "Limiar de paragem (W)", + "end_energy_threshold": "Porta de energia de fim (Wh)", + "running_dead_zone": "Zona morta após início (segundos)", + "end_repeat_count": "Contagem de repetições de fim", + "min_off_gap": "Intervalo mínimo entre ciclos (s)", + "sampling_interval": "Intervalo de amostragem (s)" + }, + "data_description": { + "start_duration_threshold": "Ignore picos de potência breves mais curtos do que esta duração (segundos). Filtra picos de arranque (ex.: 60W durante 2s) antes do início real do ciclo. Predefinição: 5s.", + "start_energy_threshold": "O ciclo deve acumular pelo menos esta quantidade de energia (Wh) durante a fase de INÍCIO para ser confirmado. Predefinição: 0,005 Wh.", + "completion_min_seconds": "Ciclos mais curtos do que isto serão marcados como 'interrompidos', mesmo que terminem naturalmente. Predefinição: 600s.", + "start_threshold_w": "A potência deve subir ACIMA deste limiar para INICIAR um ciclo. Defina acima da sua potência em standby. Predefinição: min_power + 1 W.", + "stop_threshold_w": "A potência deve cair ABAIXO deste limiar para TERMINAR um ciclo. Defina ligeiramente acima de zero para evitar que ruído provoque fins falsos. Predefinição: 0,6 × min_power.", + "end_energy_threshold": "O ciclo só termina se a energia na última janela off-delay estiver abaixo deste valor (Wh). Evita fins falsos se a potência flutuar perto de zero. Predefinição: 0,05 Wh.", + "running_dead_zone": "Após o início do ciclo, ignore quedas de potência durante este número de segundos para evitar detecção falsa de fim durante a fase inicial de arranque. Defina como 0 para desactivar. Predefinição: 0s.", + "end_repeat_count": "Número de vezes que a condição de fim (potência abaixo do limiar durante off_delay) deve ser satisfeita consecutivamente antes de terminar o ciclo. Valores mais altos evitam fins falsos durante pausas. Predefinição: 1.", + "min_off_gap": "Se um ciclo começar dentro deste número de segundos após o fim do anterior, serão FUNDIDOS num único ciclo.", + "sampling_interval": "Intervalo mínimo de amostragem em segundos. Atualizações mais rápidas que essa taxa serão ignoradas. Padrão: 30s (2s para máquinas de lavar, lavadoras e secadoras e lava-louças)." + } + }, + "matching_section": { + "name": "Correspondência de perfis e aprendizagem", + "description": "Com que agressividade o WashData faz corresponder ciclos em execução a perfis aprendidos e quando pedir feedback.", + "data": { + "profile_match_min_duration_ratio": "Rácio mínimo de duração da correspondência de perfil (0,1-1,0)", + "profile_match_interval": "Intervalo de correspondência de perfil (segundos)", + "profile_match_threshold": "Limiar de correspondência de perfil", + "profile_unmatch_threshold": "Limiar de não-correspondência de perfil", + "auto_label_confidence": "Confiança de rotulagem automática (0-1)", + "learning_confidence": "Confiança do pedido de feedback (0-1)", + "suppress_feedback_notifications": "Suprimir notificações de feedback", + "duration_tolerance": "Tolerância de duração (0-0,5)", + "smoothing_window": "Janela de suavização (amostras)", + "profile_duration_tolerance": "Tolerância de duração da correspondência de perfil (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Rácio mínimo de duração para correspondência de perfil. O ciclo em execução deve ter pelo menos esta fracção da duração do perfil para corresponder. Inferior = correspondência mais cedo. Predefinição: 0,50 (50%).", + "profile_match_interval": "Com que frequência (em segundos) tentar a correspondência de perfil durante um ciclo. Valores mais baixos fornecem detecção mais rápida mas usam mais CPU. Predefinição: 300s (5 minutos).", + "profile_match_threshold": "Pontuação mínima de similaridade DTW (0,0–1,0) necessária para considerar um perfil como correspondência. Superior = correspondência mais rigorosa, menos falsos positivos. Predefinição: 0,4.", + "profile_unmatch_threshold": "Pontuação de similaridade DTW (0,0–1,0) abaixo da qual um perfil previamente correspondido é rejeitado. Deve ser inferior ao limiar de correspondência para evitar oscilações. Predefinição: 0,35.", + "auto_label_confidence": "Rotulear automaticamente os ciclos neste nível de confiança ou acima na conclusão (0,0–1,0).", + "learning_confidence": "Confiança mínima para pedir verificação do utilizador através de um evento (0,0–1,0).", + "suppress_feedback_notifications": "Quando ativado, o WashData ainda rastreará e solicitará feedback internamente, mas não mostrará notificações persistentes solicitando a confirmação dos ciclos. Útil quando os ciclos são sempre detectados corretamente e você acha que os avisos são uma distração.", + "duration_tolerance": "Tolerância para estimativas de tempo restante durante um ciclo (0,0–0,5 corresponde a 0–50%).", + "smoothing_window": "Número de leituras de potência recentes utilizadas para suavização por média móvel.", + "profile_duration_tolerance": "Tolerância utilizada pela correspondência de perfil para variação total de duração (0,0–0,5). Comportamento predefinido: ±25%." + } + }, + "timing_section": { + "name": "Temporização, manutenção e depuração", + "description": "Watchdog, reposição do progresso, manutenção automática e exposição de entidades de depuração.", + "data": { + "watchdog_interval": "Intervalo do watchdog (segundos)", + "no_update_active_timeout": "Tempo limite sem actualizações (s)", + "progress_reset_delay": "Atraso de reinicialização do progresso (segundos)", + "auto_maintenance": "Activar manutenção automática", + "expose_debug_entities": "Expor entidades de depuração", + "save_debug_traces": "Guardar traços de depuração" + }, + "data_description": { + "watchdog_interval": "Segundos entre verificações do watchdog durante o funcionamento. Predefinição: 5s. AVISO: Certifique-se de que é SUPERIOR ao intervalo de actualização do seu sensor para evitar paragens falsas (ex.: se o sensor actualiza a cada 60s, use 65s+).", + "no_update_active_timeout": "Para sensores de publicação por alteração: forçar o fim de um ciclo ACTIVO apenas se não chegarem actualizações durante este número de segundos. A conclusão por baixa potência ainda usa o atraso de desligamento.", + "progress_reset_delay": "Após um ciclo ser concluído (100%), aguarde este número de segundos em inactividade antes de repor o progresso para 0%. Predefinição: 300s.", + "auto_maintenance": "Activar manutenção automática (reparar amostras, fundir fragmentos).", + "expose_debug_entities": "Mostrar sensores avançados (Confiança, Fase, Ambiguidade) para depuração.", + "save_debug_traces": "Guardar dados detalhados de classificação e traço de potência no histórico (aumenta o uso de armazenamento)." + } + }, + "anti_wrinkle_section": { + "name": "Proteção anti-rugas (secadores)", + "description": "Ignora rotações do tambor após o ciclo para evitar ciclos fantasma. Apenas secador / máquina lava e seca.", + "data": { + "anti_wrinkle_enabled": "Escudo anti-vincos (apenas secadora/combinada)", + "anti_wrinkle_max_power": "Potência máxima anti-vincos (W) (apenas secadora/combinada)", + "anti_wrinkle_max_duration": "Duração máxima anti-vincos (s) (apenas secadora/combinada)", + "anti_wrinkle_exit_power": "Potência de saída anti-vincos (W) (apenas secadora/combinada)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorar rotações curtas do tambor de baixa potência após o fim de um ciclo para evitar ciclos fantasma (apenas secadora/combinada).", + "anti_wrinkle_max_power": "Explosões nesta potência ou abaixo são tratadas como rotações anti-vincos em vez de novos ciclos. Aplica-se apenas quando o Escudo anti-vincos está activado.", + "anti_wrinkle_max_duration": "Comprimento máximo da explosão para tratar como rotação anti-vincos. Aplica-se apenas quando o Escudo anti-vincos está activado.", + "anti_wrinkle_exit_power": "Se a potência cair abaixo deste nível, sair do modo anti-vincos imediatamente (desligamento real). Aplica-se apenas quando o Escudo anti-vincos está activado." + } + }, + "delay_start_section": { + "name": "Deteção de início diferido", + "description": "Trata um estado de espera sustentado de baixa potência como 'À espera de iniciar' até o ciclo começar realmente.", + "data": { + "delay_start_detect_enabled": "Ativar detecção de início atrasado", + "delay_confirm_seconds": "Início diferido: tempo de confirmação em espera (s)", + "delay_timeout_hours": "Início atrasado: tempo máximo de espera (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Quando ativado, um breve pico de consumo de baixa energia seguido por energia de espera sustentada é detectado como uma partida atrasada. O sensor de estado mostra 'Aguardando início' durante o atraso. Nenhum tempo ou progresso do programa é monitorado até que o ciclo realmente comece. Requer que start_threshold_w seja definido acima da potência do pico de drenagem.", + "delay_confirm_seconds": "A potência deve permanecer entre o Limiar de paragem (W) e o Limiar de arranque (W) durante este número de segundos antes de o WashData entrar em 'À espera de iniciar'. Filtra picos breves de navegação em menus. Predefinição: 60 s.", + "delay_timeout_hours": "Tempo limite de segurança: se a máquina ainda estiver em “Aguardando para iniciar” após tantas horas, WashData será redefinido para Desligado. Padrão: 8h." + } + }, + "external_triggers_section": { + "name": "Disparadores externos, porta e pausa", + "description": "Sensor externo opcional de fim de ciclo, sensor de porta para deteção de pausa/limpo e interruptor de corte de energia em pausa.", + "data": { + "external_end_trigger_enabled": "Activar gatilho externo de fim de ciclo", + "external_end_trigger": "Sensor externo de fim de ciclo", + "external_end_trigger_inverted": "Inverter lógica do gatilho (acionar quando DESLIGADO)", + "door_sensor_entity": "Sensor de porta", + "pause_cuts_power": "Corte a energia ao pausar", + "switch_entity": "Entidade de comutação (para pausar corte de energia)", + "notify_unload_delay_minutes": "Atraso na notificação de espera de lavanderia (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Activar monitorização de um sensor binário externo para acionar a conclusão do ciclo.", + "external_end_trigger": "Seleccione uma entidade de sensor binário. Quando este sensor accionar, o ciclo actual terminará com o estado 'concluído'.", + "external_end_trigger_inverted": "Por predefinição, o gatilho acciona quando o sensor é LIGADO. Assinale esta caixa para acionar quando o sensor for DESLIGADO.", + "door_sensor_entity": "Sensor binário opcional para a porta da máquina (ligado = aberto, desligado = fechado). Quando a porta abre durante um ciclo ativo, WashData confirmará o ciclo como pausado intencionalmente. Após o término do ciclo, se a porta ainda estiver fechada, o estado muda para 'Limpar' até que a porta seja aberta. Nota: fechar a porta NÃO retoma automaticamente um ciclo pausado – a retomada deve ser acionada manualmente por meio do botão Retomar Ciclo ou serviço.", + "pause_cuts_power": "Ao pausar por meio do botão Pause Cycle ou serviço, desligue também a entidade de switch configurada. Só entra em vigor se uma entidade de switch estiver configurada para este dispositivo.", + "switch_entity": "A entidade switch a ser alternada ao pausar ou retomar. Obrigatório quando 'Cortar energia ao pausar' está ativado.", + "notify_unload_delay_minutes": "Envie uma notificação por meio do canal de notificação de término após o término do ciclo e a porta não tiver sido aberta por tantos minutos (requer Sensor de Porta). Defina como 0 para desativar. Padrão: 60 min." + } + }, + "pump_section": { + "name": "Monitor da bomba", + "description": "Apenas para o tipo de dispositivo bomba. Dispara um evento se um ciclo da bomba ultrapassar esta duração.", + "data": { + "pump_stuck_duration": "Limite(s) de alerta de bomba presa (somente bomba)" + }, + "data_description": { + "pump_stuck_duration": "Se um ciclo de bomba ainda estiver em execução após esses segundos, um evento ha_washdata_pump_stuck será acionado uma vez. Use isto para detectar um motor emperrado (o funcionamento típico da bomba do reservatório é inferior a 60 s). Padrão: 1800 s (30 min). Apenas tipo de dispositivo de bomba." + } + }, + "device_link_section": { + "name": "Link do dispositivo", + "description": "Opcionalmente, vincule este dispositivo WashData a um dispositivo Home Assistant existente (por exemplo, o plugue inteligente ou o próprio aparelho). O dispositivo WashData é então mostrado como \"Conectado através\" desse dispositivo. Deixe em branco para manter o WashData como um dispositivo independente.", + "data": { + "linked_device": "Dispositivo vinculado" + }, + "data_description": { + "linked_device": "Selecione o dispositivo ao qual conectar o WashData. Isso adiciona uma referência 'Conectado via' no registro do dispositivo; WashData mantém seu próprio cartão de dispositivo e entidades. Limpe a seleção para remover o link." + } + } + } + }, + "diagnostics": { + "title": "Diagnóstico e manutenção", + "description": "Execute acções de manutenção como fundir ciclos fragmentados ou migrar dados guardados.\n\n**Uso de armazenamento**\n\n- Tamanho do ficheiro: {file_size_kb} KB\n- Ciclos: {cycle_count}\n- Perfis: {profile_count}\n- Traços de depuração: {debug_count}", + "menu_options": { + "reprocess_history": "Manutenção: reprocessar e optimizar dados", + "clear_debug_data": "Limpar dados de depuração (libertar espaço)", + "wipe_history": "Limpar TODOS os dados deste dispositivo (irreversível)", + "export_import": "Exportar/Importar JSON com definições (copiar/colar)", + "menu_back": "← Voltar" + } + }, + "clear_debug_data": { + "title": "Limpar dados de depuração", + "description": "Tem a certeza de que quer eliminar todos os traços de depuração guardados? Isto libertará espaço mas removerá informação detalhada de classificação de ciclos anteriores." + }, + "manage_cycles": { + "title": "Gerir ciclos", + "description": "Ciclos recentes:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Rotular automaticamente ciclos antigos", + "select_cycle_to_label": "Rotular ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusão/divisão", + "trim_cycle_select": "Cortar dados do ciclo", + "menu_back": "← Voltar" + } + }, + "manage_cycles_empty": { + "title": "Gerir ciclos", + "description": "Ainda não foram registados ciclos.", + "menu_options": { + "auto_label_cycles": "Rotular automaticamente ciclos antigos", + "menu_back": "← Voltar" + } + }, + "manage_profiles": { + "title": "Gerir perfis", + "description": "Resumo do perfil:\n{profile_summary}", + "menu_options": { + "create_profile": "Criar novo perfil", + "edit_profile": "Editar/renomear perfil", + "delete_profile_select": "Eliminar perfil", + "profile_stats": "Estatísticas do perfil", + "cleanup_profile": "Limpar histórico - gráfico e eliminação", + "assign_profile_phases_select": "Atribuir intervalos de fase", + "menu_back": "← Voltar" + } + }, + "manage_profiles_empty": { + "title": "Gerir perfis", + "description": "Ainda não foram criados perfis.", + "menu_options": { + "create_profile": "Criar novo perfil", + "menu_back": "← Voltar" + } + }, + "manage_phase_catalog": { + "title": "Gerir catálogo de fases", + "description": "Catálogo de fases (predefinições para este dispositivo + fases personalizadas):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Criar nova fase", + "phase_catalog_edit_select": "Editar fase", + "phase_catalog_delete": "Eliminar fase", + "menu_back": "← Voltar" + } + }, + "phase_catalog_create": { + "title": "Criar fase personalizada", + "description": "Crie um nome e descrição de fase personalizados e escolha a que tipo de dispositivo se aplica.", + "data": { + "target_device_type": "Tipo de dispositivo alvo", + "phase_name": "Nome da fase", + "phase_description": "Descrição da fase" + } + }, + "phase_catalog_edit_select": { + "title": "Editar fase personalizada", + "description": "Seleccione uma fase personalizada para editar.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "phase_catalog_edit": { + "title": "Editar fase personalizada", + "description": "A editar a fase: {phase_name}", + "data": { + "phase_name": "Nome da fase", + "phase_description": "Descrição da fase" + } + }, + "phase_catalog_delete": { + "title": "Eliminar fase personalizada", + "description": "Seleccione uma fase personalizada para eliminar. Todos os intervalos atribuídos que utilizam esta fase serão removidos.", + "data": { + "phase_name": "Fase personalizada" + } + }, + "assign_profile_phases": { + "title": "Atribuir intervalos de fase", + "description": "Pré-visualização de fase para o perfil: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalos actuais:**\n{current_ranges}\n\nUse as acções abaixo para adicionar, editar, eliminar ou guardar intervalos.", + "menu_options": { + "assign_profile_phases_add": "Adicionar intervalo de fase", + "assign_profile_phases_edit_select": "Editar intervalo de fase", + "assign_profile_phases_delete": "Eliminar intervalo de fase", + "phase_ranges_clear": "Limpar todos os intervalos", + "assign_profile_phases_auto_detect": "Detectar fases automaticamente", + "phase_ranges_save": "Guardar e regressar", + "menu_back": "← Voltar" + } + }, + "assign_profile_phases_add": { + "title": "Adicionar intervalo de fase", + "description": "Adicione um intervalo de fase ao rascunho actual.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de início", + "end_min": "Minuto de fim" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editar intervalo de fase", + "description": "Seleccione um intervalo para editar.", + "data": { + "range_index": "Intervalo de fase" + } + }, + "assign_profile_phases_edit": { + "title": "Editar intervalo de fase", + "description": "Actualize o intervalo de fase seleccionado.", + "data": { + "phase_name": "Fase", + "start_min": "Minuto de início", + "end_min": "Minuto de fim" + } + }, + "assign_profile_phases_delete": { + "title": "Eliminar intervalo de fase", + "description": "Seleccione um intervalo para remover do rascunho.", + "data": { + "range_index": "Intervalo de fase" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fases detectadas automaticamente", + "description": "Perfil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fase(s) detectada(s) automaticamente.** Escolha uma acção abaixo.", + "data": { + "action": "Acção" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Nomear fases detectadas", + "description": "Perfil: **{profile_name}**\n\n**{detected_count} fase(s) detectada(s):**\n{ranges_summary}\n\nAtribua um nome a cada fase." + }, + "assign_profile_phases_select": { + "title": "Atribuir intervalos de fase", + "description": "Seleccione um perfil para configurar intervalos de fase.", + "data": { + "profile": "Perfil" + } + }, + "profile_stats": { + "title": "Estatísticas do perfil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Criar novo perfil", + "description": "Crie um novo perfil manualmente ou a partir de um ciclo anterior.\n\nExemplos de nomes de perfil: 'Delicados', 'Intensivo', 'Lavagem rápida'", + "data": { + "profile_name": "Nome do perfil", + "reference_cycle": "Ciclo de referência (opcional)", + "manual_duration": "Duração manual (minutos)" + } + }, + "edit_profile": { + "title": "Editar perfil", + "description": "Seleccione um perfil para renomear", + "data": { + "profile": "Perfil" + } + }, + "rename_profile": { + "title": "Editar detalhes do perfil", + "description": "Perfil actual: {current_name}", + "data": { + "new_name": "Nome do perfil", + "manual_duration": "Duração manual (minutos)" + } + }, + "delete_profile_select": { + "title": "Eliminar perfil", + "description": "Seleccione um perfil para eliminar", + "data": { + "profile": "Perfil" + } + }, + "delete_profile_confirm": { + "title": "Confirmar eliminação do perfil", + "description": "⚠️ Isto eliminará permanentemente o perfil: {profile_name}", + "data": { + "unlabel_cycles": "Remover rótulo dos ciclos que utilizam este perfil" + } + }, + "auto_label_cycles": { + "title": "Rotular automaticamente ciclos antigos", + "description": "Foram encontrados {total_count} ciclos no total. Perfis: {profiles}", + "data": { + "confidence_threshold": "Confiança mínima (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Seleccionar ciclo para rotular", + "description": "✓ = concluído, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "select_cycle_to_delete": { + "title": "Eliminar ciclo", + "description": "⚠️ Isto eliminará permanentemente o ciclo seleccionado", + "data": { + "cycle_id": "Ciclo a eliminar" + } + }, + "label_cycle": { + "title": "Rotular ciclo", + "description": "{cycle_info}", + "data": { + "profile_name": "Perfil", + "new_profile_name": "Novo nome de perfil (se estiver a criar)" + } + }, + "post_process": { + "title": "Processar histórico (fusão/divisão)", + "description": "Limpe o histórico do ciclo mesclando execuções fragmentadas e dividindo ciclos mesclados incorretamente dentro de uma janela de tempo. Insira o número de horas para olhar para trás (use 999999 para todas).", + "data": { + "time_range": "Janela de retrocesso (horas)", + "gap_seconds": "Intervalo de fusão/divisão (segundos)" + }, + "data_description": { + "time_range": "Número de horas para verificar ciclos fragmentados para mesclar. Use 999999 para processar todos os dados históricos.", + "gap_seconds": "Limiar para decidir divisão vs. fusão. Lacunas MAIS CURTAS do que isto são fundidas. Lacunas MAIS LONGAS são divididas. Reduza para forçar uma divisão num ciclo fundido incorrectamente." + } + }, + "cleanup_profile": { + "title": "Limpar histórico - seleccionar perfil", + "description": "Seleccione um perfil para visualizar. Isto gerará um gráfico com todos os ciclos anteriores deste perfil para ajudar a identificar valores atípicos.", + "data": { + "profile": "Perfil" + } + }, + "cleanup_select": { + "title": "Limpar histórico - gráfico e eliminação", + "description": "![Gráfico]({graph_url})\n\n**A visualizar {profile_name}**\n\nO gráfico mostra ciclos individuais como linhas coloridas. Identifique valores atípicos (linhas distantes do grupo) e corresponda a sua cor na lista abaixo para os eliminar.\n\n**Seleccione ciclos para eliminar PERMANENTEMENTE:**", + "data": { + "cycles_to_delete": "Ciclos a eliminar (verificar cores correspondentes)" + } + }, + "interactive_editor": { + "title": "Editor interactivo de fusão/divisão", + "description": "Seleccione uma acção manual a executar no histórico de ciclos.", + "menu_options": { + "editor_split": "Dividir um ciclo (encontrar lacunas)", + "editor_merge": "Fundir ciclos (juntar fragmentos)", + "editor_delete": "Eliminar ciclo(s)", + "menu_back": "← Voltar" + } + }, + "editor_select": { + "title": "Seleccionar ciclos", + "description": "Seleccione o(s) ciclo(s) a processar.\n\n{info_text}", + "data": { + "selected_cycles": "Ciclos" + } + }, + "editor_configure": { + "title": "Configurar e aplicar", + "description": "{preview_md}", + "data": { + "confirm_action": "Acção", + "merged_profile": "Perfil resultante", + "new_profile_name": "Novo nome de perfil", + "confirm_commit": "Sim, confirmo esta acção", + "segment_0_profile": "Perfil do segmento 1", + "segment_1_profile": "Perfil do segmento 2", + "segment_2_profile": "Perfil do segmento 3", + "segment_3_profile": "Perfil do segmento 4", + "segment_4_profile": "Perfil do segmento 5", + "segment_5_profile": "Perfil do segmento 6", + "segment_6_profile": "Perfil do segmento 7", + "segment_7_profile": "Perfil do segmento 8", + "segment_8_profile": "Perfil do segmento 9", + "segment_9_profile": "Perfil do segmento 10" + } + }, + "editor_split_params": { + "title": "Configuração de divisão", + "description": "Ajuste a sensibilidade para dividir este ciclo. Qualquer lacuna inactiva superior a isto causará uma divisão.", + "data": { + "split_mode": "Método de divisão" + } + }, + "editor_split_auto_params": { + "title": "Configuração de divisão automática", + "description": "Ajuste a sensibilidade para dividir este ciclo. Qualquer intervalo inativo mais longo do que este irá causar uma divisão.", + "data": { + "min_gap_seconds": "Limiar do intervalo de divisão (segundos)" + } + }, + "editor_split_manual_params": { + "title": "Marcas temporais de divisão manual", + "description": "{preview_md}Janela do ciclo: **{cycle_start_wallclock} → {cycle_end_wallclock}** em {cycle_date}.\n\nIntroduza uma ou mais marcas temporais de divisão (`HH:MM` ou `HH:MM:SS`), uma por linha. O ciclo será cortado em cada marca temporal — N marcas temporais produzem N+1 segmentos.", + "data": { + "split_timestamps": "Carimbos temporais de divisão" + } + }, + "reprocess_history": { + "title": "Manutenção: reprocessar e optimizar dados", + "description": "Efectua uma limpeza profunda dos dados históricos:\n1. Remove leituras de potência zero (silêncio).\n2. Corrige o tempo/duração do ciclo.\n3. Recalcula todos os modelos estatísticos (envelopes).\n\n**Execute isto para corrigir problemas de dados ou aplicar novos algoritmos.**" + }, + "wipe_history": { + "title": "Limpar histórico (apenas para testes)", + "description": "⚠️ Isto eliminará permanentemente TODOS os ciclos e perfis guardados neste dispositivo. Isto não pode ser desfeito!" + }, + "export_import": { + "title": "Exportar/Importar JSON", + "description": "Copie/cole ciclos, perfis E definições para este dispositivo. Exporte abaixo ou cole JSON para importar.", + "data": { + "mode": "Acção", + "json_payload": "JSON de configuração completa" + }, + "data_description": { + "mode": "Escolha Exportar para copiar os dados e definições actuais, ou Importar para colar dados exportados de outro dispositivo WashData.", + "json_payload": "Para Exportar: copie este JSON (inclui ciclos, perfis e todas as definições afinadas). Para Importar: cole o JSON exportado do WashData." + } + }, + "record_cycle": { + "title": "Gravar ciclo", + "description": "Grave manualmente um ciclo para criar um perfil limpo sem interferências.\n\nEstado: **{status}**\nDuração: {duration}s\nAmostras: {samples}", + "menu_options": { + "record_refresh": "Actualizar estado", + "record_stop": "Parar gravação (guardar e processar)", + "record_start": "Iniciar nova gravação", + "record_process": "Processar última gravação (cortar e guardar)", + "record_discard": "Descartar última gravação", + "menu_back": "← Voltar" + } + }, + "record_process": { + "title": "Processar gravação", + "description": "![Gráfico]({graph_url})\n\n**O gráfico mostra a gravação bruta.** Azul = Manter, Vermelho = Corte proposto.\n*O gráfico é estático e não actualiza com alterações no formulário.*\n\nGravação parada. Os cortes são alinhados à taxa de amostragem detectada (~{sampling_rate}s).\n\nDuração bruta: {duration}s\nAmostras: {samples}", + "data": { + "head_trim": "Início do corte (segundos)", + "tail_trim": "Fim do corte (segundos)", + "save_mode": "Destino de gravação", + "profile_name": "Nome do perfil" + } + }, + "learning_feedbacks": { + "title": "Feedbacks de aprendizagem", + "description": "Feedback pendente: {count}\n\n{pending_table}\n\nSeleccione um pedido de revisão de ciclo para processar.", + "menu_options": { + "learning_feedbacks_pick": "Rever um feedback pendente", + "learning_feedbacks_dismiss_all": "Ignorar todo o feedback pendente", + "menu_back": "← Voltar" + } + }, + "learning_feedbacks_pick": { + "title": "Selecionar feedback para rever", + "description": "Selecione um pedido de revisão de ciclo para abrir.", + "data": { + "selected_feedback": "Selecionar ciclo para rever" + } + }, + "learning_feedbacks_empty": { + "title": "Feedbacks de aprendizagem", + "description": "Não foram encontrados pedidos de feedback pendentes.", + "menu_options": { + "menu_back": "← Voltar" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Dispensar todos os feedbacks pendentes", + "description": "Está prestes a dispensar **{count}** pedido(s) de feedback pendente(s). Os ciclos dispensados permanecem no seu histórico, mas não irão contribuir com um novo sinal de aprendizagem. Isto não pode ser desfeito.\n\n✅ Marque a caixa abaixo e clique em **Enviar** para dispensar tudo.\n❌ Deixe desmarcado e clique em **Enviar** para cancelar e voltar.", + "data": { + "confirm_dismiss_all": "Confirmar: ignorar todos os pedidos de feedback pendentes" + } + }, + "resolve_feedback": { + "title": "Resolver feedback", + "description": "{comparison_img}{candidates_table}\n**Perfil detectado**: {detected_profile} ({confidence_pct}%)\n**Duração estimada**: {est_duration_min} min\n**Duração real**: {act_duration_min} min\n\n__Escolha uma acção abaixo:__", + "data": { + "action": "O que gostaria de fazer?", + "corrected_profile": "Programa correcto (se estiver a corrigir)", + "corrected_duration": "Duração correcta em segundos (se estiver a corrigir)" + } + }, + "trim_cycle_select": { + "title": "Cortar ciclo - seleccionar ciclo", + "description": "Seleccione um ciclo para cortar. ✓ = concluído, ⚠ = retomado, ✗ = interrompido", + "data": { + "cycle_id": "Ciclo" + } + }, + "trim_cycle": { + "title": "Cortar ciclo", + "description": "Janela actual: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acção" + } + }, + "trim_cycle_start": { + "title": "Definir início do corte", + "description": "Janela de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nInício atual: **{current_wallclock}** (+{current_offset_min} min do início do ciclo)\n\nEscolha um novo horário de início usando o seletor de relógio abaixo.", + "data": { + "trim_start_time": "Novo horário de início", + "trim_start_min": "Novo início (minutos desde o início do ciclo)" + } + }, + "trim_cycle_end": { + "title": "Definir fim do corte", + "description": "Janela de ciclo: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFim atual: **{current_wallclock}** (+{current_offset_min} min do início do ciclo)\n\nEscolha um novo horário de término usando o seletor de relógio abaixo.", + "data": { + "trim_end_time": "Novo horário de término", + "trim_end_min": "Novo fim (minutos do início do ciclo)" + } + } + }, + "error": { + "import_failed": "Importação falhou. Cole um JSON de exportação WashData válido.", + "select_exactly_one": "Seleccione exatamente um ciclo para esta ação.", + "select_at_least_one": "Seleccione pelo menos um ciclo para esta ação.", + "select_at_least_two": "Seleccione pelo menos dois ciclos para esta ação.", + "profile_exists": "Já existe um perfil com este nome", + "rename_failed": "Falha ao renomear o perfil. O perfil pode não existir ou o novo nome já está em uso.", + "assignment_failed": "Falha ao atribuir perfil ao ciclo", + "duplicate_phase": "Já existe uma fase com este nome.", + "phase_not_found": "A fase seleccionada não foi encontrada.", + "invalid_phase_name": "O nome da fase não pode estar vazio.", + "phase_name_too_long": "O nome da fase é demasiado longo.", + "invalid_phase_range": "O intervalo de fase é inválido. O fim deve ser maior que o início.", + "invalid_phase_timestamp": "O carimbo de data/hora é inválido. Use o formato AAAA-MM-DD HH:MM ou HH:MM.", + "overlapping_phase_ranges": "Os intervalos de fase sobrepõem-se. Ajuste os intervalos e tente novamente.", + "incomplete_phase_row": "Cada linha de fase deve incluir uma fase mais valores completos de início/fim para o modo seleccionado.", + "timestamp_mode_cycle_required": "Seleccione um ciclo de origem quando usar o modo de carimbo de data/hora.", + "no_phase_ranges": "Ainda não há intervalos de fase disponíveis para essa acção.", + "feedback_notification_title": "WashData: verificar ciclo ({device})", + "feedback_notification_message": "**Dispositivo**: {device}\n**Programa**: {program} ({confidence}% de confiança)\n**Hora**: {time}\n\nO WashData precisa da sua ajuda para verificar este ciclo detectado.\n\nVá a **Definições > Dispositivos e serviços > WashData > Configurar > Feedbacks de aprendizagem** para confirmar ou corrigir este resultado.", + "suggestions_ready_notification_title": "WashData: definições sugeridas prontas ({device})", + "suggestions_ready_notification_message": "O sensor **Definições sugeridas** reporta agora **{count}** recomendações accionáveis.\n\nPara as rever e aplicar: **Definições > Dispositivos e serviços > WashData > Configurar > Definições avançadas > Aplicar valores sugeridos**.\n\nAs sugestões são opcionais e mostradas para revisão antes de guardar.", + "trim_range_invalid": "O início deve ser antes do fim. Ajuste os seus pontos de corte.", + "trim_failed": "O corte falhou. O ciclo pode já não existir ou a janela está vazia.", + "invalid_split_timestamp": "O carimbo temporal é inválido ou está fora da janela do ciclo. Utilize HH:MM ou HH:MM:SS dentro da janela do ciclo.", + "no_split_segments_found": "Não foi possível produzir segmentos válidos a partir destes carimbos temporais. Certifique-se de que cada carimbo temporal está dentro da janela do ciclo e de que os segmentos têm pelo menos 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} detectou ciclos fantasmas. Alteração sugerida de min_power: {current_min}W -> {new_min}W (não aplicada automaticamente).", + "auto_tune_title": "Ajuste automático WashData", + "auto_tune_fallback": "{device_type} {device_title} detectou ciclos fantasmas. Alteração sugerida de min_power: {current_min}W -> {new_min}W (não aplicada automaticamente)." + }, + "abort": { + "no_cycles_found": "Nenhum ciclo encontrado", + "no_split_segments_found": "Não foram encontrados segmentos divisíveis no ciclo selecionado.", + "cycle_not_found": "Não foi possível carregar o ciclo selecionado.", + "no_power_data": "Não há dados de traço de potência disponíveis para o ciclo seleccionado.", + "no_profiles_found": "Nenhum perfil encontrado. Crie primeiro um perfil.", + "no_profiles_for_matching": "Nenhum perfil disponível para correspondência. Crie primeiro perfis.", + "no_unlabeled_cycles": "Todos os ciclos já estão rotulados", + "no_suggestions": "Ainda não há valores sugeridos disponíveis. Execute alguns ciclos e tente novamente.", + "no_predictions": "Sem previsões", + "no_custom_phases": "Nenhuma fase personalizada encontrada.", + "reprocess_success": "{count} ciclos reprocessados com sucesso.", + "debug_data_cleared": "Dados de depuração de {count} ciclos limpos com sucesso." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Início do ciclo", + "cycle_finish": "Fim do ciclo", + "cycle_live": "Progresso em tempo real" + } + }, + "common_text": { + "options": { + "unlabeled": "(Sem rótulo)", + "create_new_profile": "Criar novo perfil", + "remove_label": "Remover rótulo", + "no_reference_cycle": "(Sem ciclo de referência)", + "all_device_types": "Todos os tipos de dispositivo", + "current_suffix": "(actual)", + "editor_select_info": "Seleccione 1 ciclo para dividir, ou 2+ ciclos para fundir.", + "editor_select_info_split": "Seleccione 1 ciclo para dividir.", + "editor_select_info_merge": "Seleccione 2 ou mais ciclos para unir.", + "editor_select_info_delete": "Seleccione 1 ou mais ciclos para eliminar.", + "no_cycles_recorded": "Ainda não foram registados ciclos.", + "profile_summary_line": "- **{name}**: {count} ciclos, {avg}m méd.", + "no_profiles_created": "Ainda não foram criados perfis.", + "phase_preview": "Pré-visualização da fase", + "phase_preview_no_curve": "A curva média do perfil ainda não está disponível. Execute/rotule mais ciclos para este perfil.", + "average_power_curve": "Curva de potência média", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciclos, ~{duration} min méd.)", + "profile_option_short_fmt": "{name} ({count} ciclos, ~{duration} min)", + "deleted_cycles_from_profile": "Eliminados {count} ciclos de {profile}.", + "not_enough_data_graph": "Dados insuficientes para gerar o gráfico.", + "no_profiles_found": "Nenhum perfil encontrado.", + "cycle_info_fmt": "Ciclo: {start}, {duration} min, Actual: {label}", + "top_candidates_header": "### Principais candidatos", + "tbl_profile": "Perfil", + "tbl_confidence": "Confiança", + "tbl_correlation": "Correlação", + "tbl_duration_match": "Correspondência de duração", + "detected_profile": "Perfil detectado", + "estimated_duration": "Duração estimada", + "actual_duration": "Duração real", + "choose_action": "__Escolha uma acção abaixo:__", + "tbl_count": "Contagem", + "tbl_avg": "Méd.", + "tbl_min": "Mín.", + "tbl_max": "Máx.", + "tbl_energy_avg": "Energia (méd.)", + "tbl_energy_total": "Energia (total)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Última execução", + "graph_legend_title": "Legenda do gráfico", + "graph_legend_body": "A faixa azul representa a gama mínima e máxima de consumo de potência observada. A linha mostra a curva de potência média.", + "recording_preview": "Pré-visualização da gravação", + "trim_start": "Início do corte", + "trim_end": "Fim do corte", + "split_preview_title": "Pré-visualização da divisão", + "split_preview_found_fmt": "Foram encontrados {count} segmentos.", + "split_preview_confirm_fmt": "Clique em Confirmar para dividir este ciclo em {count} ciclos separados.", + "merge_preview_title": "Pré-visualização da fusão", + "merge_preview_joining_fmt": "A fundir {count} ciclos. As lacunas serão preenchidas com leituras de 0W.", + "editor_delete_preview_title": "Pré-visualização de eliminação", + "editor_delete_preview_intro": "Os ciclos selecionados serão eliminados permanentemente:", + "editor_delete_preview_confirm": "Clique em Confirmar para eliminar permanentemente estes registos de ciclos.", + "no_power_preview": "*Não há dados de potência disponíveis para pré-visualização.*", + "profile_comparison": "Comparação de perfis", + "actual_cycle_label": "Este ciclo (real)", + "storage_usage_header": "Uso de armazenamento", + "storage_file_size": "Tamanho do ficheiro", + "storage_cycles": "Ciclos", + "storage_profiles": "Perfis", + "storage_debug_traces": "Traços de depuração", + "overview_suffix": "Visão geral", + "phase_preview_for_profile": "Pré-visualização de fase para o perfil", + "current_ranges_header": "Intervalos actuais", + "assign_phases_help": "Use as acções abaixo para adicionar, editar, eliminar ou guardar intervalos.", + "profile_summary_header": "Resumo do perfil", + "recent_cycles_header": "Ciclos recentes", + "trim_cycle_preview_title": "Pré-visualização do corte do ciclo", + "trim_cycle_preview_no_data": "Não há dados de potência disponíveis para este ciclo.", + "trim_cycle_preview_kept_suffix": "mantido", + "table_program": "Programa", + "table_when": "Quando", + "table_length": "Duração", + "table_match": "Correspondência", + "table_profile": "Perfil", + "table_cycles": "Ciclos", + "table_avg_length": "Duração média", + "table_last_run": "Última execução", + "table_avg_energy": "Energia média", + "table_detected_program": "Programa detetado", + "table_confidence": "Confiança", + "table_reported": "Reportado", + "phase_builtin_suffix": "(Embutido)", + "phase_none_available": "Não há fases disponíveis.", + "settings_deprecation_warning": "⚠️ **Tipo de dispositivo obsoleto:** {device_type} está programado para remoção em uma versão futura. O pipeline correspondente do WashData não produz resultados confiáveis ​​para esta classe de dispositivo. Sua integração continua funcionando durante o período de descontinuação; para silenciar este aviso, mude **Tipo de dispositivo** abaixo para um dos tipos suportados (Máquina de lavar, Secadora, Combinação de lavadora e secadora, Máquina de lavar louça, Fritadeira de ar, Máquina de fazer pão ou Bomba) ou para **Outro (Avançado)** se o seu aparelho não corresponder a nenhum dos tipos suportados. **Outros (Avançado)** fornece padrões intencionalmente genéricos que não são ajustados para nenhum dispositivo específico, portanto, você mesmo precisará configurar limites, tempos limite e parâmetros correspondentes; todas as suas configurações existentes são preservadas quando você muda.", + "phase_other_device_types": "Outros tipos de dispositivos:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dividir um ciclo (encontrar lacunas)", + "merge": "Fundir ciclos (juntar fragmentos)", + "delete": "Eliminar ciclo(s)" + } + }, + "split_mode": { + "options": { + "auto": "Detetar automaticamente intervalos inativos", + "manual": "Carimbos temporais manuais" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Manutenção: reprocessar e optimizar dados", + "clear_debug_data": "Limpar dados de depuração (libertar espaço)", + "wipe_history": "Limpar TODOS os dados deste dispositivo (irreversível)", + "export_import": "Exportar/Importar JSON com definições (copiar/colar)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportar todos os dados", + "import": "Importar/fundir dados" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Rotular automaticamente ciclos antigos", + "select_cycle_to_label": "Rotular ciclo específico", + "select_cycle_to_delete": "Eliminar ciclo", + "interactive_editor": "Editor interactivo de fusão/divisão", + "trim_cycle": "Cortar dados do ciclo" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Criar novo perfil", + "edit_profile": "Editar/renomear perfil", + "delete_profile": "Eliminar perfil", + "profile_stats": "Estatísticas do perfil", + "cleanup_profile": "Limpar histórico - gráfico e eliminação", + "assign_phases": "Atribuir intervalos de fase" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Criar nova fase", + "edit_custom_phase": "Editar fase", + "delete_custom_phase": "Eliminar fase" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Adicionar intervalo de fase", + "edit_range": "Editar intervalo de fase", + "delete_range": "Eliminar intervalo de fase", + "clear_ranges": "Limpar todos os intervalos", + "auto_detect_ranges": "Detectar fases automaticamente", + "save_ranges": "Guardar e regressar" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Actualizar estado", + "stop_recording": "Parar gravação (guardar e processar)", + "start_recording": "Iniciar nova gravação", + "process_recording": "Processar última gravação (cortar e guardar)", + "discard_recording": "Descartar última gravação" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Criar novo perfil", + "existing_profile": "Adicionar a perfil existente", + "discard": "Descartar gravação" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmar - detecção correcta", + "correct": "Corrigir - escolher programa/duração", + "ignore": "Ignorar - falso positivo/ciclo ruidoso", + "delete": "Eliminar - remover do histórico" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Nomear e aplicar fases", + "cancel": "Voltar sem alterações" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Definir ponto de início", + "set_end": "Definir ponto de fim", + "reset": "Repor para duração total", + "apply": "Aplicar corte", + "cancel": "Cancelar" + } + }, + "device_type": { + "options": { + "washing_machine": "Máquina de lavar", + "dryer": "Secador", + "washer_dryer": "Combinação Lavadora e Secadora", + "dishwasher": "Máquina de lavar louça", + "coffee_machine": "Máquina de café (obsoleta)", + "ev": "Veículo elétrico (obsoleto)", + "air_fryer": "Fritadeira de ar", + "heat_pump": "Bomba de calor (obsoleta)", + "bread_maker": "Máquina de fazer pão", + "pump": "Bomba / Bomba de depósito", + "oven": "Forno (obsoleto)", + "other": "Outro (avançado)" + } + } + }, + "services": { + "label_cycle": { + "name": "Rotular ciclo", + "description": "Atribua um perfil existente a um ciclo anterior ou remova o rótulo.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a rotular." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a rotular." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O nome de um perfil existente (crie perfis no menu Gerir perfis). Deixe em branco para remover o rótulo." + } + } + }, + "create_profile": { + "name": "Criar perfil", + "description": "Crie um novo perfil (autónomo ou baseado num ciclo de referência).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "Nome do novo perfil (ex.: 'Intensivo', 'Delicados')." + }, + "reference_cycle_id": { + "name": "ID do ciclo de referência", + "description": "ID de ciclo opcional para basear este perfil." + } + } + }, + "delete_profile": { + "name": "Eliminar perfil", + "description": "Elimine um perfil e opcionalmente remova o rótulo dos ciclos que o utilizam.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "profile_name": { + "name": "Nome do perfil", + "description": "O perfil a eliminar." + }, + "unlabel_cycles": { + "name": "Remover rótulo dos ciclos", + "description": "Remova o rótulo do perfil dos ciclos que utilizam este perfil." + } + } + }, + "auto_label_cycles": { + "name": "Rotular automaticamente ciclos antigos", + "description": "Rotule retroactivamente ciclos sem rótulo usando correspondência de perfil.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "confidence_threshold": { + "name": "Limiar de confiança", + "description": "Confiança mínima de correspondência (0,50-0,95) para aplicar rótulos." + } + } + }, + "export_config": { + "name": "Exportar configuração", + "description": "Exporte os perfis, ciclos e definições deste dispositivo para um ficheiro JSON (por dispositivo).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a exportar." + }, + "path": { + "name": "Caminho", + "description": "Caminho de ficheiro absoluto opcional para escrever (predefinição: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importar configuração", + "description": "Importe perfis, ciclos e definições para este dispositivo a partir de um ficheiro JSON de exportação (as definições podem ser substituídas).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData para onde importar." + }, + "path": { + "name": "Caminho", + "description": "Caminho absoluto para o ficheiro JSON de exportação a importar." + } + } + }, + "submit_cycle_feedback": { + "name": "Enviar feedback do ciclo", + "description": "Confirme ou corrija um programa detectado automaticamente após um ciclo concluído. Forneça `entry_id` (avançado) ou `device_id` (recomendado).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData (recomendado em vez de entry_id)." + }, + "entry_id": { + "name": "ID de entrada", + "description": "O ID de entrada de configuração do dispositivo (alternativa ao device_id)." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo mostrado na notificação/registos de feedback." + }, + "user_confirmed": { + "name": "Confirmar programa detectado", + "description": "Defina como verdadeiro se o programa detectado estiver correcto." + }, + "corrected_profile": { + "name": "Perfil corrigido", + "description": "Se não confirmar, forneça o nome correcto do perfil/programa." + }, + "corrected_duration": { + "name": "Duração corrigida (segundos)", + "description": "Duração corrigida opcional em segundos." + }, + "notes": { + "name": "Notas", + "description": "Notas opcionais sobre este ciclo." + } + } + }, + "record_start": { + "name": "Gravar início do ciclo", + "description": "Comece a gravar manualmente um ciclo limpo (ignora toda a lógica de correspondência).", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a gravar." + } + } + }, + "record_stop": { + "name": "Gravar paragem do ciclo", + "description": "Parar a gravação manual.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData para parar a gravação." + } + } + }, + "trim_cycle": { + "name": "Cortar ciclo", + "description": "Corte os dados de potência de um ciclo anterior para uma janela temporal específica.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData." + }, + "cycle_id": { + "name": "ID do ciclo", + "description": "O ID do ciclo a cortar." + }, + "trim_start_s": { + "name": "Início do corte (segundos)", + "description": "Manter dados a partir deste deslocamento em segundos. Predefinição: 0." + }, + "trim_end_s": { + "name": "Fim do corte (segundos)", + "description": "Manter dados até este deslocamento em segundos. Predefinição = duração total." + } + } + }, + "pause_cycle": { + "name": "Ciclo de pausa", + "description": "Pause o ciclo ativo para um dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a ser pausado." + } + } + }, + "resume_cycle": { + "name": "Retomar ciclo", + "description": "Retome um ciclo pausado para um dispositivo WashData.", + "fields": { + "device_id": { + "name": "Dispositivo", + "description": "O dispositivo WashData a ser retomado." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "A funcionar" + }, + "match_ambiguity": { + "name": "Ambiguidade de correspondência" + } + }, + "sensor": { + "washer_state": { + "name": "Estado", + "state": { + "off": "Desligado", + "idle": "Inactivo", + "starting": "A iniciar", + "running": "A funcionar", + "paused": "Em pausa", + "user_paused": "Em pausa pelo utilizador", + "ending": "A terminar", + "finished": "Concluído", + "anti_wrinkle": "Anti-vincos", + "delay_wait": "Esperando para começar", + "interrupted": "Interrompido", + "force_stopped": "Parado forçosamente", + "rinse": "Enxaguar", + "unknown": "Desconhecido", + "clean": "Limpar" + } + }, + "washer_program": { + "name": "Programa" + }, + "time_remaining": { + "name": "Tempo restante" + }, + "total_duration": { + "name": "Duração total" + }, + "cycle_progress": { + "name": "Progresso" + }, + "current_power": { + "name": "Potência actual" + }, + "elapsed_time": { + "name": "Tempo decorrido" + }, + "debug_info": { + "name": "Informação de depuração" + }, + "match_confidence": { + "name": "Confiança na correspondência" + }, + "top_candidates": { + "name": "Principais candidatos", + "state": { + "none": "Nenhum" + } + }, + "current_phase": { + "name": "Fase actual" + }, + "wash_phase": { + "name": "Fase" + }, + "profile_cycle_count": { + "name": "Perfil {profile_name} - contagem", + "unit_of_measurement": "ciclos" + }, + "suggestions": { + "name": "Definições sugeridas disponíveis" + }, + "pump_runs_today": { + "name": "Funcionamento da bomba (últimas 24 horas)" + }, + "cycle_count": { + "name": "Contagem de ciclos" + } + }, + "select": { + "program_select": { + "name": "Programa do ciclo", + "state": { + "auto_detect": "Detecção automática" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forçar fim do ciclo" + }, + "pause_cycle": { + "name": "Ciclo de pausa" + }, + "resume_cycle": { + "name": "Retomar ciclo" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "É necessário um device_id válido." + }, + "cycle_id_required": { + "message": "É necessário um cycle_id válido." + }, + "profile_name_required": { + "message": "É necessário um profile_name válido." + }, + "device_not_found": { + "message": "Dispositivo não encontrado." + }, + "no_config_entry": { + "message": "Nenhuma entrada de configuração encontrada para o dispositivo." + }, + "integration_not_loaded": { + "message": "Integração não carregada para este dispositivo." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclo não encontrado ou não possui dados de potência." + }, + "trim_failed_empty_window": { + "message": "Corte falhou - ciclo não encontrado, sem dados de potência ou a janela resultante está vazia." + }, + "trim_invalid_range": { + "message": "trim_end_s deve ser maior que trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ro.json b/custom_components/ha_washdata/translations/ro.json new file mode 100644 index 0000000..4c312e8 --- /dev/null +++ b/custom_components/ha_washdata/translations/ro.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Configurare WashData", + "description": "Configurați mașina de spălat sau alt aparat.\n\nEste necesar un senzor de putere.\n\n**În continuare, veți fi întrebat dacă doriți să vă creați primul profil.**", + "data": { + "name": "Numele dispozitivului", + "device_type": "Tip de dispozitiv", + "power_sensor": "Senzor de putere", + "min_power": "Pragul minim de putere (W)" + }, + "data_description": { + "name": "Un nume prietenos pentru acest dispozitiv (de exemplu, „Mașină de spălat”, „Mașină de spălat vase”).", + "device_type": "Ce tip de aparat este acesta? Ajută la personalizarea detectării și etichetării.", + "power_sensor": "Entitatea senzor care raportează consumul de energie în timp real (în wați) de la priza inteligentă.", + "min_power": "Citirile de putere peste acest prag (în wați) indică faptul că aparatul funcționează. Începeți cu 2W pentru majoritatea dispozitivelor." + } + }, + "first_profile": { + "title": "Creați primul profil", + "description": "Un **ciclu** este o funcționare completă a aparatului dvs. (de exemplu, o încărcătură de rufe). Un **Profil** grupează aceste cicluri după tip (de exemplu, „Bumbac”, „Spălare rapidă”). Crearea unui profil acum ajută integrarea să estimeze durata imediat.\n\nPuteți selecta manual acest profil în comenzile dispozitivului dacă nu este detectat automat.\n\n**Lăsați „Numele profilului” necompletat pentru a sări peste acest pas.**", + "data": { + "profile_name": "Nume profil (opțional)", + "manual_duration": "Durata estimată (minute)" + } + } + }, + "error": { + "cannot_connect": "Nu s-a putut conecta", + "invalid_auth": "Autentificare nevalidă", + "unknown": "Eroare neașteptată" + }, + "abort": { + "already_configured": "Dispozitivul este deja configurat" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Examinați feedback-urile de învățare", + "settings": "Setări", + "notifications": "Notificări", + "manage_cycles": "Gestionați ciclurile", + "manage_profiles": "Gestionați profiluri", + "manage_phase_catalog": "Gestionați catalogul de faze", + "record_cycle": "Înregistrare ciclu (manual)", + "diagnostics": "Diagnosticare și întreținere" + } + }, + "apply_suggestions": { + "title": "Copiați valorile sugerate", + "description": "Aceasta va copia valorile actuale sugerate în setările salvate. Aceasta este o acțiune unică și nu activează suprascrierea automată.\n\nValori sugerate de copiat:\n{suggested}", + "data": { + "confirm": "Confirmați acțiunea de copiere" + } + }, + "apply_suggestions_confirm": { + "title": "Examinați modificările sugerate", + "description": "WashData a găsit **{pending_count}** valori sugerate de aplicat.\n\nModificări:\n{changes}\n\n✅ Bifați caseta de mai jos și faceți clic pe **Trimite** pentru a aplica și a salva imediat aceste modificări.\n❌ Lăsați-l nebifat și faceți clic pe **Trimite** pentru a anula și a reveni.", + "data": { + "confirm_apply_suggestions": "Aplicați și salvați aceste modificări" + } + }, + "settings": { + "title": "Setări", + "description": "{deprecation_warning}Reglați pragurile de detectare, învățarea și comportamentul avansat. Valorile implicite funcționează bine pentru majoritatea configurațiilor.\n\nSetări sugerate disponibile: {suggestions_count}.\nDacă este mai mare decât 0, deschideți Setări avansate și folosiți Aplicați valorile sugerate pentru a revizui recomandările.", + "data": { + "apply_suggestions": "Aplicați valorile sugerate", + "device_type": "Tip de dispozitiv", + "power_sensor": "Entitate senzor de putere", + "min_power": "Putere minimă (W)", + "off_delay": "Întârziere de încheiere a ciclului (s)", + "notify_actions": "Acțiuni de notificare", + "notify_people": "Amânați livrarea până când aceste persoane sunt acasă", + "notify_only_when_home": "Întârziați notificările până când persoana selectată este acasă", + "notify_fire_events": "Declanșare evenimente de automatizare", + "notify_start_services": "Cycle Start - Ținte de notificare", + "notify_finish_services": "Terminare ciclu - Ținte de notificare", + "notify_live_services": "Progres live - Ținte de notificare", + "notify_before_end_minutes": "Notificare înainte de finalizare (minute înainte de sfârșit)", + "notify_title": "Titlul notificării", + "notify_icon": "Pictograma de notificare", + "notify_start_message": "Format mesaj de pornire", + "notify_finish_message": "Format mesaj de finalizare", + "notify_pre_complete_message": "Format mesaj pre-finalizare", + "show_advanced": "Editați setările avansate (pasul următor)" + }, + "data_description": { + "apply_suggestions": "Bifați această casetă pentru a copia în formular valorile sugerate de algoritmul de învățare. Examinați înainte de a salva.\n\nSugerat (din învățare):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Selectați tipul de aparat (Mașină de spălat, Uscător, Mașină de spălat vase, Aparat de cafea, VE). Această etichetă este stocată cu fiecare ciclu pentru o mai bună organizare.", + "power_sensor": "Entitatea senzor care raportează puterea în timp real (în wați). Puteți schimba oricând acest lucru fără a pierde datele istorice - util dacă înlocuiți o priză inteligentă.", + "min_power": "Citirile de putere peste acest prag (în wați) indică faptul că aparatul funcționează. Începeți cu 2W pentru majoritatea dispozitivelor.", + "off_delay": "Câte secunde puterea netezită trebuie să rămână sub pragul de oprire pentru a marca finalizarea. Implicit: 120s.", + "notify_actions": "Secvență de acțiuni opțională Home Assistant care se rulează pentru notificări (scripturi, notify, TTS etc.). Dacă este setat, acțiunile rulează înaintea serviciului de notificare alternativă.", + "notify_people": "Entități de persoane utilizate pentru controlul prezenței. Când este activată, livrarea notificărilor este întârziată până când cel puțin o persoană selectată este acasă.", + "notify_only_when_home": "Dacă este activat, amânați notificările până când cel puțin o persoană selectată este acasă.", + "notify_fire_events": "Dacă este activat, declanșează evenimente Home Assistant la startul și sfârșitul ciclului (ha_washdata_cycle_started și ha_washdata_cycle_ended) pentru a conduce automatizările.", + "notify_start_services": "Unul sau mai multe servicii de notificare pentru a alerta când începe un ciclu. Lăsați gol pentru a sări peste notificările de pornire.", + "notify_finish_services": "Unul sau mai multe servicii de notificare pentru a alerta când un ciclu se termină sau se apropie de finalizare. Lăsați gol pentru a sări peste notificările de finalizare.", + "notify_live_services": "Unul sau mai multe servicii de notificare pentru a primi actualizări live despre progres în timp ce ciclul rulează (numai aplicația mobilă însoțitoare). Lăsați gol pentru a omite actualizările live.", + "notify_before_end_minutes": "Cu câte minute înainte de sfârșitul estimat al ciclului pentru a declanșa o notificare. Setați la 0 pentru a dezactiva. Implicit: 0.", + "notify_title": "Titlu personalizat pentru notificări. Acceptă substituentul {device}.", + "notify_icon": "Pictogramă de utilizat pentru notificări (de exemplu, mdi:washing-machine). Lăsați gol pentru a trimite fără pictogramă.", + "notify_start_message": "Mesaj trimis când începe un ciclu. Acceptă substituentul {device}.", + "notify_finish_message": "Mesaj trimis când un ciclu se termină. Acceptă substituenții {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Textul actualizărilor de progres live recurente în timp ce ciclul rulează. Acceptă substituenți {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Notificări", + "description": "Configurați canalele de notificare, șabloanele și actualizările opționale de progres live pe mobil.", + "data": { + "notify_actions": "Acțiuni de notificare", + "notify_people": "Amânați livrarea până când aceste persoane sunt acasă", + "notify_only_when_home": "Întârziați notificările până când persoana selectată este acasă", + "notify_fire_events": "Declanșare evenimente de automatizare", + "notify_start_services": "Cycle Start - Ținte de notificare", + "notify_finish_services": "Terminare ciclu - Ținte de notificare", + "notify_live_services": "Progres live - Ținte de notificare", + "notify_before_end_minutes": "Notificare înainte de finalizare (minute înainte de sfârșit)", + "notify_live_interval_seconds": "Interval de actualizare live (secunde)", + "notify_live_overrun_percent": "Marja de depășire a actualizărilor live (%)", + "notify_live_chronometer": "Cronometru cu numărătoare inversă", + "notify_title": "Titlul notificării", + "notify_icon": "Pictograma de notificare", + "notify_start_message": "Format mesaj de pornire", + "notify_finish_message": "Format mesaj de finalizare", + "notify_pre_complete_message": "Format mesaj pre-finalizare", + "energy_price_entity": "Prețul energiei - Entitate (opțional)", + "energy_price_static": "Prețul energiei - Valoare statică (opțional)", + "go_back": "Reveniți fără salvare", + "notify_reminder_message": "Format mesaj de memento", + "notify_timeout_seconds": "Închidere automată după (secunde)", + "notify_channel": "Canal de notificare (stare/live/memento)", + "notify_finish_channel": "Canal de notificare terminat" + }, + "data_description": { + "go_back": "Bifați aceasta și faceți clic pe Trimitere pentru a renunța la orice modificări de mai sus și a reveni la meniul principal.", + "notify_actions": "Secvență de acțiuni opțională Home Assistant care se rulează pentru notificări (scripturi, notify, TTS etc.). Dacă este setat, acțiunile rulează înaintea serviciului de notificare alternativă.", + "notify_people": "Entități de persoane utilizate pentru controlul prezenței. Când este activată, livrarea notificărilor este întârziată până când cel puțin o persoană selectată este acasă.", + "notify_only_when_home": "Dacă este activat, amânați notificările până când cel puțin o persoană selectată este acasă.", + "notify_fire_events": "Dacă este activat, declanșează evenimente Home Assistant la startul și sfârșitul ciclului (ha_washdata_cycle_started și ha_washdata_cycle_ended) pentru a conduce automatizările.", + "notify_start_services": "Unul sau mai multe servicii de notificare pentru a alerta când începe un ciclu (de exemplu, notify.mobile_app_my_phone). Lăsați gol pentru a sări peste notificările de pornire.", + "notify_finish_services": "Unul sau mai multe servicii de notificare pentru a alerta când un ciclu se termină sau se apropie de finalizare. Lăsați gol pentru a sări peste notificările de finalizare.", + "notify_live_services": "Unul sau mai multe servicii de notificare pentru a primi actualizări live despre progres în timp ce ciclul rulează (numai aplicația mobilă însoțitoare). Lăsați gol pentru a omite actualizările live.", + "notify_before_end_minutes": "Cu câte minute înainte de sfârșitul estimat al ciclului pentru a declanșa o notificare. Setați la 0 pentru a dezactiva. Implicit: 0.", + "notify_live_interval_seconds": "Cât de des sunt trimise actualizările de progres în timpul funcționării. Valorile mai mici sunt mai receptive, dar consumă mai multe notificări. Este impus un minim de 30 de secunde - valorile sub 30s sunt rotunjite automat la 30s.", + "notify_live_overrun_percent": "Marja de siguranță peste numărul estimat de actualizări pentru cicluri lungi/depășite (de exemplu, 20% permite de 1,2 ori actualizările estimate).", + "notify_live_chronometer": "Când este activată, fiecare actualizare live include o numărătoare inversă a cronometrului până la ora estimată de terminare. Cronometrul de notificare se bifează automat pe dispozitiv între actualizări (numai aplicația însoțitoare Android).", + "notify_title": "Titlu personalizat pentru notificări. Acceptă substituentul {device}.", + "notify_icon": "Pictogramă de utilizat pentru notificări (de exemplu, mdi:washing-machine). Lăsați gol pentru a trimite fără pictogramă.", + "notify_start_message": "Mesaj trimis când începe un ciclu. Acceptă substituentul {device}.", + "notify_finish_message": "Mesaj trimis când un ciclu se termină. Acceptă substituenții {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Selectați o entitate numerică care deține prețul curent al energiei electrice (de exemplu, senzor.electricity_price, input_number.energy_rate). Când este setat, activează substituentul {cost}. Are prioritate față de valoarea statică dacă ambele sunt configurate.", + "energy_price_static": "Preț fix al energiei electrice pe kWh. Când este setat, activează substituentul {cost}. Ignorat dacă o entitate este configurată mai sus. Adăugați simbolul valutar în șablonul de mesaj, de ex. {cost} €.", + "notify_reminder_message": "Textul mementoului unic a trimis numărul configurat de minute înainte de sfârșitul estimat. Diferit de actualizările live recurente de mai sus. Acceptă substituenți {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Închideți automat notificările după atâtea secunde (redirecționate ca „timeout” pentru aplicația însoțitoare). Setați la 0 pentru a le păstra până la respingere. Implicit: 0.", + "notify_channel": "Android canal de notificare al aplicației însoțitoare pentru notificări privind starea, progresul live și notificările de memento. Sunetul și importanța unui canal sunt configurate în aplicația însoțitoare prima dată când numele canalului este utilizat - WashData setează doar numele canalului. Lăsați necompletat pentru aplicația implicită.", + "notify_finish_channel": "Separați canalul Android pentru alerta finală (utilizată și de memento și de mesajul de așteptare a rufelor), astfel încât să poată avea propriul sunet. Lăsați gol pentru a reutiliza canalul de mai sus.", + "notify_pre_complete_message": "Textul actualizărilor de progres live recurente în timp ce ciclul rulează. Acceptă substituenți {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Setări avansate", + "description": "Reglați pragurile de detectare, învățarea și comportamentul avansat. Valorile implicite funcționează bine pentru majoritatea configurațiilor.", + "sections": { + "suggestions_section": { + "name": "Setări sugerate", + "description": "{suggestions_count} sugestii învățate disponibile. Bifați Aplicați valorile sugerate pentru a revizui modificările propuse înainte de salvare.", + "data": { + "apply_suggestions": "Aplicați valorile sugerate" + }, + "data_description": { + "apply_suggestions": "Bifați această casetă pentru a deschide un pas de revizuire pentru valorile sugerate din algoritmul de învățare. Nimic nu este salvat automat.\n\nSugerat (din învățare):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detectare și praguri de putere", + "description": "Când aparatul este considerat PORNIT sau OPRIT, praguri energetice și temporizare pentru tranzițiile de stare.", + "data": { + "start_duration_threshold": "Durata debounce la pornire (s)", + "start_energy_threshold": "Poarta de energie la pornire (Wh)", + "completion_min_seconds": "Timp minim de rulare pentru finalizare (secunde)", + "start_threshold_w": "Pragul de pornire (W)", + "stop_threshold_w": "Pragul de oprire (W)", + "end_energy_threshold": "Poarta de energie la final (Wh)", + "running_dead_zone": "Zonă moartă la pornire (secunde)", + "end_repeat_count": "Număr de repetări pentru final", + "min_off_gap": "Intervalul minim între cicluri (s)", + "sampling_interval": "Interval de eșantionare (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorați vârfurile scurte de putere mai scurte decât această durată (secunde). Filtrează vârfurile de pornire (de exemplu, 60 W timp de 2 secunde) înainte de începerea ciclului real. Implicit: 5s.", + "start_energy_threshold": "Ciclul trebuie să acumuleze cel puțin atâta energie (Wh) în timpul fazei de START pentru a fi confirmat. Implicit: 0,005 Wh.", + "completion_min_seconds": "Ciclurile mai scurte decât aceasta vor fi marcate ca „întrerupte”, chiar dacă se termină în mod natural. Implicit: 600s.", + "start_threshold_w": "Puterea trebuie să crească PESTE acest prag pentru a PORNI un ciclu. Setați mai sus decât puterea de așteptare. Implicit: min_power + 1 W.", + "stop_threshold_w": "Puterea trebuie să scadă SUB acest prag pentru a TERMINA un ciclu. Setați chiar peste zero pentru a evita că zgomotul declanșeze finaluri false. Implicit: 0,6 × min_power.", + "end_energy_threshold": "Ciclul se termină numai dacă energia din ultima fereastră de întârziere la oprire este sub această valoare (Wh). Previne finaluri false dacă puterea fluctuează aproape de zero. Implicit: 0,05 Wh.", + "running_dead_zone": "După începerea ciclului, ignorați scăderile de putere timp de atâtea secunde pentru a preveni detectarea falsă a finalului în timpul fazei inițiale de pornire. Setați la 0 pentru a dezactiva. Implicit: 0s.", + "end_repeat_count": "De câte ori condiția de final (putere sub pragul pentru off_delay) trebuie îndeplinită consecutiv înainte de încheierea ciclului. Valorile mai mari previn finaluri false în timpul pauzelor. Implicit: 1.", + "min_off_gap": "Dacă un ciclu începe în atâtea secunde de la încheierea celui precedent, ele vor fi FUZIONATE într-un singur ciclu.", + "sampling_interval": "Interval minim de prelevare în secunde. Actualizările mai rapide decât această rată vor fi ignorate. Implicit: 30 s (2 s pentru mașini de spălat, mașini de spălat cu uscător și mașini de spălat vase)." + } + }, + "matching_section": { + "name": "Potrivire profiluri și învățare", + "description": "Cât de agresiv potrivește WashData ciclurile în desfășurare cu profilurile învățate și când să ceară feedback.", + "data": { + "profile_match_min_duration_ratio": "Rată durată minimă a potrivirii profilului (0,1-1,0)", + "profile_match_interval": "Interval de potrivire a profilului (secunde)", + "profile_match_threshold": "Pragul de potrivire a profilului", + "profile_unmatch_threshold": "Prag de nepotrivire a profilului", + "auto_label_confidence": "Încredere pentru etichetare automată (0-1)", + "learning_confidence": "Încredere pentru solicitarea de feedback (0-1)", + "suppress_feedback_notifications": "Suprimați notificările de feedback", + "duration_tolerance": "Toleranță la durată (0-0,5)", + "smoothing_window": "Fereastră de netezire (eșantioane)", + "profile_duration_tolerance": "Toleranță la durata potrivirii profilului (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Raportul de durată minimă pentru potrivirea profilului. Ciclul în rulare trebuie să aibă cel puțin această fracțiune din durata profilului pentru a se potrivi. Mai mic = potrivire mai devreme. Implicit: 0,50 (50%).", + "profile_match_interval": "Cât de des (în secunde) să se încerce potrivirea profilului în timpul unui ciclu. Valorile mai mici oferă o detectare mai rapidă, dar folosesc mai mult CPU. Implicit: 300s (5 minute).", + "profile_match_threshold": "Scorul minim de similaritate DTW (0,0–1,0) necesar pentru a considera un profil ca potrivit. Mai mare = potrivire mai strictă, mai puține false pozitive. Implicit: 0,4.", + "profile_unmatch_threshold": "Scorul de similaritate DTW (0,0–1,0) sub care un profil potrivit anterior este respins. Trebuie să fie mai mic decât pragul de potrivire pentru a preveni oscilațiile. Implicit: 0,35.", + "auto_label_confidence": "Etichetați automat ciclurile cu această încredere sau mai mare la finalizare (0,0–1,0).", + "learning_confidence": "Încredere minimă pentru a solicita verificarea utilizatorului printr-un eveniment (0,0–1,0).", + "suppress_feedback_notifications": "Când este activat, WashData va urmări în continuare și va solicita feedback intern, dar nu va afișa notificări persistente care vă vor cere să confirmați ciclurile. Este util atunci când ciclurile sunt întotdeauna detectate corect și vi se pare că solicitările vă distrag atenția.", + "duration_tolerance": "Toleranța pentru estimările timpului rămas în timpul unui ciclu (0,0–0,5 corespunde la 0–50%).", + "smoothing_window": "Numărul de citiri recente de putere utilizate pentru netezirea mediei mobile.", + "profile_duration_tolerance": "Toleranța utilizată de potrivirea profilului pentru variația duratei totale (0,0–0,5). Comportament implicit: ±25%." + } + }, + "timing_section": { + "name": "Timp, mentenanță și depanare", + "description": "Watchdog, resetarea progresului, mentenanță automată și expunerea entităților de depanare.", + "data": { + "watchdog_interval": "Intervalul watchdog (secunde)", + "no_update_active_timeout": "Timeout fără actualizare (s)", + "progress_reset_delay": "Întârziere de resetare a progresului (secunde)", + "auto_maintenance": "Activați întreținerea automată", + "expose_debug_entities": "Expuneți entitățile de depanare", + "save_debug_traces": "Salvați urmele de depanare" + }, + "data_description": { + "watchdog_interval": "Secunde între verificările watchdog în timpul funcționării. Implicit: 5s. ATENȚIE: Asigurați-vă că acesta este MAI MARE decât intervalul de actualizare al senzorului pentru a evita opririle false (de exemplu, dacă senzorul se actualizează la fiecare 60 de secunde, utilizați 65s+).", + "no_update_active_timeout": "Pentru senzorii de publicare la modificare: opriți forțat un ciclu ACTIV numai dacă nu sosesc actualizări pentru atâtea secunde. Finalizarea la consum redus folosește în continuare întârzierea la oprire.", + "progress_reset_delay": "După finalizarea unui ciclu (100%), așteptați atâtea secunde de inactivitate înainte de a reseta progresul la 0%. Implicit: 300s.", + "auto_maintenance": "Activați întreținerea automată (reparare eșantioane, îmbinare fragmente).", + "expose_debug_entities": "Afișați senzori avansați (Încredere, Fază, Ambiguitate) pentru depanare.", + "save_debug_traces": "Stocați datele detaliate de clasificare și urmărire a puterii în istoric (crește utilizarea stocării)." + } + }, + "anti_wrinkle_section": { + "name": "Protecție antișifonare (uscătoare)", + "description": "Ignorați rotațiile tamburului după ciclu pentru a preveni ciclurile fantomă. Numai uscător / mașină de spălat cu uscător.", + "data": { + "anti_wrinkle_enabled": "Scut antirid (numai uscător)", + "anti_wrinkle_max_power": "Putere maximă antirid (W)", + "anti_wrinkle_max_duration": "Durata maximă antirid (s)", + "anti_wrinkle_exit_power": "Puterea de ieșire antirid (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorați rotațiile scurte ale tamburului de putere redusă după încheierea unui ciclu pentru a preveni ciclurile fantomă (numai pentru uscător/mașină de spălat cu uscător).", + "anti_wrinkle_max_power": "Exploziile la sau sub această putere sunt tratate ca rotații antirid în loc de noi cicluri. Se aplică numai când Scutul Antirid este activat.", + "anti_wrinkle_max_duration": "Lungimea maximă a exploziei pentru a fi tratată ca rotație antirid. Se aplică numai când Scutul Antirid este activat.", + "anti_wrinkle_exit_power": "Dacă puterea scade sub acest nivel, ieșiți imediat din modul antirid (oprit cu adevărat). Se aplică numai când Scutul Antirid este activat." + } + }, + "delay_start_section": { + "name": "Detectarea pornirii întârziate", + "description": "Tratați așteptarea susținută la putere redusă ca 'Așteaptă să pornească' până când ciclul începe efectiv.", + "data": { + "delay_start_detect_enabled": "Activați Detectarea pornirii întârziate", + "delay_confirm_seconds": "Pornire întârziată: timp de confirmare standby (s)", + "delay_timeout_hours": "Pornire întârziată: timp maxim de așteptare (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Când este activat, un pic scurt de scurgere de putere redusă, urmat de o putere de așteptare susținută, este detectat ca o pornire întârziată. Senzorul de stare afișează „În așteptarea pornirii” în timpul întârzierii. Nu se urmărește timpul sau progresul programului până când ciclul începe efectiv. Necesită ca start_threshold_w să fie setat deasupra puterii vârfului de scurgere.", + "delay_confirm_seconds": "Puterea trebuie să rămână între Prag oprire (W) și Prag pornire (W) pentru atâtea secunde înainte ca WashData să intre în starea 'Așteaptă să pornească'. Filtrează vârfurile scurte din navigarea prin meniu. Implicit: 60 s.", + "delay_timeout_hours": "Timeout de siguranță: dacă mașina este încă în „Așteaptă pornirea” după atâtea ore, WashData se resetează la Off. Implicit: 8 h." + } + }, + "external_triggers_section": { + "name": "Declanșatoare externe, ușă și pauză", + "description": "Senzor extern opțional de final de ciclu, senzor de ușă pentru detectarea pauzei/curat și întrerupător de tăiere a alimentării la pauză.", + "data": { + "external_end_trigger_enabled": "Activați declanșatorul extern de sfârșit de ciclu", + "external_end_trigger": "Senzor extern de sfârșit de ciclu", + "external_end_trigger_inverted": "Inversați logica declanșării (declanșare la dezactivare)", + "door_sensor_entity": "Senzor de ușă", + "pause_cuts_power": "Opriți puterea la pauză", + "switch_entity": "Comutare entitate (pentru întreruperea alimentării în pauză)", + "notify_unload_delay_minutes": "Întârziere de notificare în așteptare pentru spălătorie (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Activați monitorizarea unui senzor binar extern pentru a declanșa finalizarea ciclului.", + "external_end_trigger": "Selectați o entitate senzor binar. Când acest senzor se declanșează, ciclul curent se va încheia cu starea „finalizat”.", + "external_end_trigger_inverted": "În mod implicit, declanșatorul se activează când senzorul este PORNIT. Bifați această casetă pentru a se activa când senzorul se OPRESTE.", + "door_sensor_entity": "Senzor binar opțional pentru ușa mașinii (pornit = deschis, oprit = închis). Când ușa se deschide în timpul unui ciclu activ, WashData va confirma ciclul ca fiind întrerupt intenționat. După încheierea ciclului, dacă ușa este încă închisă, starea se schimbă în „Curățare” până când ușa este deschisă. Notă: închiderea ușii NU reia automat un ciclu întrerupt - reluarea trebuie declanșată manual prin intermediul butonului Reluare ciclu sau serviciu.", + "pause_cuts_power": "Când întrerupeți prin intermediul butonului sau al serviciului Pauză ciclu, dezactivați, de asemenea, entitatea de comutare configurată. Are efect numai dacă o entitate de comutare este configurată pentru acest dispozitiv.", + "switch_entity": "Entitatea comutatoare pentru a comuta atunci când întrerupeți sau reluați. Obligatoriu atunci când „Tăiați puterea la pauză” este activat.", + "notify_unload_delay_minutes": "Trimiteți o notificare prin canalul de notificare de terminare după ce ciclul se termină și ușa nu a fost deschisă timp de atâtea minute (necesită Senzor de ușă). Setați la 0 pentru a dezactiva. Implicit: 60 min." + } + }, + "pump_section": { + "name": "Monitor pompă", + "description": "Numai pentru tipul de dispozitiv pompă. Declanșează un eveniment dacă un ciclu de pompă depășește această durată.", + "data": { + "pump_stuck_duration": "Pragul de alertă de blocare a pompei (doar pompă)" + }, + "data_description": { + "pump_stuck_duration": "Dacă un ciclu de pompă continuă să ruleze după atâtea secunde, un eveniment ha_washdata_pump_stuck este declanșat o dată. Utilizați acest lucru pentru a detecta un motor blocat (funcționarea tipică a pompei de carter este sub 60 s). Implicit: 1800 s (30 min). Numai tipul dispozitivului de pompare." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Opțional, conectați acest dispozitiv WashData la un dispozitiv Home Assistant existent (de exemplu, priza inteligentă sau aparatul în sine). Dispozitivul WashData este apoi afișat ca „Conectat prin” dispozitivul respectiv. Lăsați gol pentru a păstra WashData ca dispozitiv autonom.", + "data": { + "linked_device": "Dispozitiv conectat" + }, + "data_description": { + "linked_device": "Selectați dispozitivul la care să vă conectați WashData. Aceasta adaugă o referință „Conectat prin” în registrul dispozitivului; WashData își păstrează propriul card de dispozitiv și entități. Ștergeți selecția pentru a elimina legătura." + } + } + } + }, + "diagnostics": { + "title": "Diagnosticare și întreținere", + "description": "Rulați acțiuni de întreținere, cum ar fi îmbinarea ciclurilor fragmentate sau migrarea datelor stocate.\n\n**Utilizarea stocării**\n\n- Dimensiunea fișierului: {file_size_kb} KB\n- Cicluri: {cycle_count}\n- Profiluri: {profile_count}\n- Urme de depanare: {debug_count}", + "menu_options": { + "reprocess_history": "Întreținere: reprocesare și optimizare date", + "clear_debug_data": "Ștergeți datele de depanare (eliberați spațiu)", + "wipe_history": "Ștergeți TOATE datele pentru acest dispozitiv (ireversibil)", + "export_import": "Export/Import JSON cu setări (copiere/lipire)", + "menu_back": "← Înapoi" + } + }, + "clear_debug_data": { + "title": "Ștergeți datele de depanare", + "description": "Sigur doriți să ștergeți toate urmele de depanare stocate? Acest lucru va elibera spațiu, dar va elimina informațiile detaliate de clasificare din ciclurile anterioare." + }, + "manage_cycles": { + "title": "Gestionați ciclurile", + "description": "Cicluri recente:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etichetare automată a ciclurilor vechi", + "select_cycle_to_label": "Etichetați un ciclu specific", + "select_cycle_to_delete": "Ștergeți ciclul", + "interactive_editor": "Editor interactiv îmbinare/împărțire", + "trim_cycle_select": "Tăiați datele ciclului", + "menu_back": "← Înapoi" + } + }, + "manage_cycles_empty": { + "title": "Gestionați ciclurile", + "description": "Nu s-au înregistrat încă cicluri.", + "menu_options": { + "auto_label_cycles": "Etichetare automată a ciclurilor vechi", + "menu_back": "← Înapoi" + } + }, + "manage_profiles": { + "title": "Gestionați profiluri", + "description": "Rezumatul profilului:\n{profile_summary}", + "menu_options": { + "create_profile": "Creați un profil nou", + "edit_profile": "Editați/redenumiți profilul", + "delete_profile_select": "Ștergeți profilul", + "profile_stats": "Statistici de profil", + "cleanup_profile": "Curățați istoricul - Grafic și ștergere", + "assign_profile_phases_select": "Atribuiți intervale de fază", + "menu_back": "← Înapoi" + } + }, + "manage_profiles_empty": { + "title": "Gestionați profiluri", + "description": "Nu au fost create profiluri încă.", + "menu_options": { + "create_profile": "Creați un profil nou", + "menu_back": "← Înapoi" + } + }, + "manage_phase_catalog": { + "title": "Gestionați catalogul de faze", + "description": "Catalog de faze (implicit pentru acest dispozitiv + faze personalizate):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Creați o nouă fază", + "phase_catalog_edit_select": "Editați faza", + "phase_catalog_delete": "Ștergeți faza", + "menu_back": "← Înapoi" + } + }, + "phase_catalog_create": { + "title": "Creați o fază personalizată", + "description": "Creați un nume și o descriere personalizate pentru faza și alegeți tipul de dispozitiv căruia i se aplică.", + "data": { + "target_device_type": "Tipul dispozitivului țintă", + "phase_name": "Numele fazei", + "phase_description": "Descrierea fazei" + } + }, + "phase_catalog_edit_select": { + "title": "Editați faza personalizată", + "description": "Selectați o fază personalizată de editat.", + "data": { + "phase_name": "Faza personalizată" + } + }, + "phase_catalog_edit": { + "title": "Editați faza personalizată", + "description": "Editarea fazei: {phase_name}", + "data": { + "phase_name": "Numele fazei", + "phase_description": "Descrierea fazei" + } + }, + "phase_catalog_delete": { + "title": "Ștergeți faza personalizată", + "description": "Selectați o fază personalizată de șters. Orice intervale alocate care utilizează această fază vor fi eliminate.", + "data": { + "phase_name": "Faza personalizată" + } + }, + "assign_profile_phases": { + "title": "Atribuiți intervale de fază", + "description": "Previzualizare fază pentru profil: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalele curente:**\n{current_ranges}\n\nUtilizați acțiunile de mai jos pentru a adăuga, edita, șterge sau salva intervale.", + "menu_options": { + "assign_profile_phases_add": "Adăugați intervalul de fază", + "assign_profile_phases_edit_select": "Editați intervalul de fază", + "assign_profile_phases_delete": "Ștergeți intervalul de fază", + "phase_ranges_clear": "Ștergeți toate intervalele", + "assign_profile_phases_auto_detect": "Detectare automată a fazelor", + "phase_ranges_save": "Salvați și întoarceți-vă", + "menu_back": "← Înapoi" + } + }, + "assign_profile_phases_add": { + "title": "Adăugați intervalul de fază", + "description": "Adăugați un interval de fază la schița curentă.", + "data": { + "phase_name": "Fază", + "start_min": "Minutul de început", + "end_min": "Minutul de sfârșit" + } + }, + "assign_profile_phases_edit_select": { + "title": "Editați intervalul de fază", + "description": "Selectați un interval de editat.", + "data": { + "range_index": "Interval de fază" + } + }, + "assign_profile_phases_edit": { + "title": "Editați intervalul de fază", + "description": "Actualizați intervalul de fază selectat.", + "data": { + "phase_name": "Fază", + "start_min": "Minutul de început", + "end_min": "Minutul de sfârșit" + } + }, + "assign_profile_phases_delete": { + "title": "Ștergeți intervalul de fază", + "description": "Selectați un interval de eliminat din schiță.", + "data": { + "range_index": "Interval de fază" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Faze detectate automat", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fază(e) detectată(e) automat.** Alegeți o acțiune de mai jos.", + "data": { + "action": "Acțiune" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Denumiți fazele detectate", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fază(e) detectată(e):**\n{ranges_summary}\n\nAtribuiți un nume fiecărei faze." + }, + "assign_profile_phases_select": { + "title": "Atribuiți intervale de fază", + "description": "Selectați un profil pentru a configura intervalele de fază.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistici de profil", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Creați un profil nou", + "description": "Creați un profil nou manual sau dintr-un ciclu anterior.\n\nExemple de nume de profil: „Delicate”, „Rezistente”, „Spălare rapidă”", + "data": { + "profile_name": "Nume profil", + "reference_cycle": "Ciclu de referință (opțional)", + "manual_duration": "Durata manuală (minute)" + } + }, + "edit_profile": { + "title": "Editați profilul", + "description": "Selectați un profil pentru redenumire", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Editați detaliile profilului", + "description": "Profil curent: {current_name}", + "data": { + "new_name": "Nume profil", + "manual_duration": "Durata manuală (minute)" + } + }, + "delete_profile_select": { + "title": "Ștergeți profilul", + "description": "Selectați un profil de șters", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Confirmați ștergerea profilului", + "description": "⚠️ Acest lucru va șterge definitiv profilul: {profile_name}", + "data": { + "unlabel_cycles": "Eliminați eticheta din ciclurile care folosesc acest profil" + } + }, + "auto_label_cycles": { + "title": "Etichetare automată a ciclurilor vechi", + "description": "S-au găsit {total_count} cicluri totale. Profiluri: {profiles}", + "data": { + "confidence_threshold": "Încredere minimă (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Selectați ciclul de etichetat", + "description": "✓ = finalizat, ⚠ = reluat, ✗ = întrerupt", + "data": { + "cycle_id": "Ciclu" + } + }, + "select_cycle_to_delete": { + "title": "Ștergeți ciclul", + "description": "⚠️ Acest lucru va șterge definitiv ciclul selectat", + "data": { + "cycle_id": "Ciclu de șters" + } + }, + "label_cycle": { + "title": "Etichetați ciclul", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nume de profil nou (dacă se creează)" + } + }, + "post_process": { + "title": "Procesare istoric (îmbinare/împărțire)", + "description": "Curățați istoricul ciclului prin îmbinarea execuțiilor fragmentate și împărțirea ciclurilor îmbinate incorect într-o fereastră de timp. Introduceți numărul de ore pentru a vă uita înapoi (utilizați 999999 pentru toți).", + "data": { + "time_range": "Fereastra de retrospectivă (ore)", + "gap_seconds": "Limita de îmbinare/împărțire (secunde)" + }, + "data_description": { + "time_range": "Numărul de ore de scanare pentru cicluri fragmentate care urmează să fie îmbinate. Utilizați 999999 pentru a procesa toate datele istorice.", + "gap_seconds": "Prag pentru a decide împărțire vs. îmbinare. Lacunele MAI SCURTE decât aceasta sunt îmbinate. Lacunele MAI LUNGI decât aceasta sunt împărțite. Scădeți aceasta pentru a forța o împărțire pe un ciclu îmbinat incorect." + } + }, + "cleanup_profile": { + "title": "Curățați istoricul - Selectați profilul", + "description": "Selectați un profil de vizualizat. Aceasta va genera un grafic care arată toate ciclurile anterioare pentru acest profil pentru a ajuta la identificarea valorilor aberante.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Curățați istoricul - Grafic și ștergere", + "description": "![Grafic]({graph_url})\n\n**Vizualizarea {profile_name}**\n\nGraficul arată ciclurile individuale sub formă de linii colorate. Identificați valorile aberante (linii departe de grup) și potriviți culoarea lor în lista de mai jos pentru a le șterge.\n\n**Selectați ciclurile pentru a șterge DEFINITIV:**", + "data": { + "cycles_to_delete": "Cicluri de șters (verificați culorile potrivite)" + } + }, + "interactive_editor": { + "title": "Editor interactiv îmbinare/împărțire", + "description": "Selectați o acțiune manuală de efectuat în istoricul ciclului.", + "menu_options": { + "editor_split": "Împărțiți un ciclu (găsiți lacune)", + "editor_merge": "Îmbinați cicluri (alăturați fragmente)", + "editor_delete": "Ștergeți ciclul(e)", + "menu_back": "← Înapoi" + } + }, + "editor_select": { + "title": "Selectați cicluri", + "description": "Selectați ciclul (ciclurile) de procesat.\n\n{info_text}", + "data": { + "selected_cycles": "Cicluri" + } + }, + "editor_configure": { + "title": "Configurați și aplicați", + "description": "{preview_md}", + "data": { + "confirm_action": "Acțiune", + "merged_profile": "Profilul rezultat", + "new_profile_name": "Nume de profil nou", + "confirm_commit": "Da, confirm această acțiune", + "segment_0_profile": "Profilul segmentului 1", + "segment_1_profile": "Profilul segmentului 2", + "segment_2_profile": "Profilul segmentului 3", + "segment_3_profile": "Profilul segmentului 4", + "segment_4_profile": "Profilul segmentului 5", + "segment_5_profile": "Profilul segmentului 6", + "segment_6_profile": "Profilul segmentului 7", + "segment_7_profile": "Profilul segmentului 8", + "segment_8_profile": "Profilul segmentului 9", + "segment_9_profile": "Profilul segmentului 10" + } + }, + "editor_split_params": { + "title": "Configurație de împărțire", + "description": "Reglați sensibilitatea pentru împărțirea acestui ciclu. Orice decalaj inactiv mai lung decât acesta va provoca o împărțire.", + "data": { + "split_mode": "Metodă de împărțire" + } + }, + "editor_split_auto_params": { + "title": "Configurare împărțire automată", + "description": "Ajustați sensibilitatea pentru împărțirea acestui ciclu. Orice pauză de inactivitate mai lungă decât aceasta va provoca o împărțire.", + "data": { + "min_gap_seconds": "Prag pentru intervalul de împărțire (secunde)" + } + }, + "editor_split_manual_params": { + "title": "Marcaje temporale pentru împărțire manuală", + "description": "{preview_md}Fereastra ciclului: **{cycle_start_wallclock} → {cycle_end_wallclock}** pe {cycle_date}.\n\nIntroduceți unul sau mai multe marcaje temporale de împărțire (`HH:MM` sau `HH:MM:SS`), câte unul pe fiecare rând. Ciclul va fi tăiat la fiecare marcaj temporal — N marcaje temporale produc N+1 segmente.", + "data": { + "split_timestamps": "Marcaje temporale de împărțire" + } + }, + "reprocess_history": { + "title": "Întreținere: reprocesare și optimizare date", + "description": "Efectuează curățarea profundă a datelor istorice:\n1. Elimină citirile de putere zero (silențiu).\n2. Corectează sincronizarea/durata ciclului.\n3. Recalculează toate modelele statistice (plicuri).\n\n**Rulați acest lucru pentru a remedia problemele de date sau pentru a aplica algoritmi noi.**" + }, + "wipe_history": { + "title": "Ștergeți istoricul (numai pentru testare)", + "description": "⚠️ Acest lucru va șterge definitiv TOATE ciclurile și profilurile stocate pentru acest dispozitiv. Aceasta nu poate fi anulat!" + }, + "export_import": { + "title": "Export/Import JSON", + "description": "Copiați/lipiți cicluri, profiluri și setări pentru acest dispozitiv. Exportați mai jos sau inserați JSON pentru a importa.", + "data": { + "mode": "Acțiune", + "json_payload": "Configurație completă JSON" + }, + "data_description": { + "mode": "Alegeți Export pentru a copia datele și setările curente sau Import pentru a lipi datele exportate de pe alt dispozitiv WashData.", + "json_payload": "Pentru export: copiați acest JSON (include cicluri, profiluri și toate setările reglate). Pentru import: inserați JSON exportat din WashData." + } + }, + "record_cycle": { + "title": "Înregistrare ciclu", + "description": "Înregistrați manual un ciclu pentru a crea un profil curat, fără interferențe.\n\nStare: **{status}**\nDurată: {duration}s\nEșantioane: {samples}", + "menu_options": { + "record_refresh": "Reîmprospătați starea", + "record_stop": "Opriți înregistrarea (salvare și procesare)", + "record_start": "Începeți o nouă înregistrare", + "record_process": "Procesați ultima înregistrare (tăiere și salvare)", + "record_discard": "Renunțați la ultima înregistrare", + "menu_back": "← Înapoi" + } + }, + "record_process": { + "title": "Procesare înregistrare", + "description": "![Grafic]({graph_url})\n\n**Graficul arată înregistrarea brută.** Albastru = Păstrare, Roșu = Tăiere propusă.\n*Graficul este static și nu se actualizează cu modificările formularului.*\n\nÎnregistrarea s-a oprit. Tăierile sunt aliniate la rata de eșantionare detectată (~{sampling_rate}s).\n\nDurată brută: {duration}s\nEșantioane: {samples}", + "data": { + "head_trim": "Tăiere început (secunde)", + "tail_trim": "Tăiere sfârșit (secunde)", + "save_mode": "Destinație salvare", + "profile_name": "Nume profil" + } + }, + "learning_feedbacks": { + "title": "Feedback-uri de învățare", + "description": "Feedback în așteptare: {count}\n\n{pending_table}\n\nSelectați o solicitare de revizuire a ciclului de procesat.", + "menu_options": { + "learning_feedbacks_pick": "Revizuiți un feedback în așteptare", + "learning_feedbacks_dismiss_all": "Respingeți tot feedbackul în așteptare", + "menu_back": "← Înapoi" + } + }, + "learning_feedbacks_pick": { + "title": "Selectați feedback pentru examinare", + "description": "Selectați o solicitare de examinare a ciclului pentru a o deschide.", + "data": { + "selected_feedback": "Selectați ciclul pentru examinare" + } + }, + "learning_feedbacks_empty": { + "title": "Feedback-uri de învățare", + "description": "Nu s-au găsit solicitări de feedback în așteptare.", + "menu_options": { + "menu_back": "← Înapoi" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Respinge toate feedback-urile în așteptare", + "description": "Sunteți pe cale să respingeți **{count}** solicitări de feedback în așteptare. Ciclurile respinse rămân în istoric, dar nu vor contribui cu un nou semnal de învățare. Acest lucru nu poate fi anulat.\n\n✅ Bifați caseta de mai jos și faceți clic pe **Trimite** pentru a le respinge pe toate.\n❌ Lăsați-o nebifată și faceți clic pe **Trimite** pentru a anula și a reveni.", + "data": { + "confirm_dismiss_all": "Confirmați: respingeți toate solicitările de feedback în așteptare" + } + }, + "resolve_feedback": { + "title": "Rezolvați feedback-ul", + "description": "{comparison_img}{candidates_table}\n**Profil detectat**: {detected_profile} ({confidence_pct}%)\n**Durata estimată**: {est_duration_min} min\n**Durata reală**: {act_duration_min} min\n\n__Alegeți o acțiune de mai jos:__", + "data": { + "action": "Ce doriți să faceți?", + "corrected_profile": "Programul corect (dacă se corectează)", + "corrected_duration": "Durata corectă în secunde (dacă se corectează)" + } + }, + "trim_cycle_select": { + "title": "Tăiere ciclu - Selectați ciclul", + "description": "Selectați un ciclu de tăiat. ✓ = finalizat, ⚠ = reluat, ✗ = întrerupt", + "data": { + "cycle_id": "Ciclu" + } + }, + "trim_cycle": { + "title": "Tăiere ciclu", + "description": "Fereastra curentă: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Acțiune" + } + }, + "trim_cycle_start": { + "title": "Setați începutul tăierii", + "description": "Fereastra de ciclu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nPornire curentă: **{current_wallclock}** (+{current_offset_min} min de la începerea ciclului)\n\nAlegeți o nouă oră de începere folosind selectorul de ceas de mai jos.", + "data": { + "trim_start_time": "Noua ora de incepere", + "trim_start_min": "Pornire nouă (minute de la începerea ciclului)" + } + }, + "trim_cycle_end": { + "title": "Setați sfârșitul tăierii", + "description": "Fereastra de ciclu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nSfârșit curent: **{current_wallclock}** (+{current_offset_min} min de la începutul ciclului)\n\nAlegeți o nouă oră de încheiere folosind selectorul de ceas de mai jos.", + "data": { + "trim_end_time": "Noua oră de sfârșit", + "trim_end_min": "Sfârșit nou (minute de la începerea ciclului)" + } + } + }, + "error": { + "import_failed": "Importul nu a reușit. Lipiți un JSON valid de export WashData.", + "select_exactly_one": "Selectați exact un ciclu pentru această acțiune.", + "select_at_least_one": "Selectați cel puțin un ciclu pentru această acțiune.", + "select_at_least_two": "Selectați cel puțin două cicluri pentru această acțiune.", + "profile_exists": "Un profil cu acest nume există deja", + "rename_failed": "Nu s-a putut redenumi profilul. Este posibil ca profilul să nu existe sau noul nume este deja utilizat.", + "assignment_failed": "Nu s-a putut atribui profilul ciclului", + "duplicate_phase": "O fază cu acest nume există deja.", + "phase_not_found": "Faza selectată nu a fost găsită.", + "invalid_phase_name": "Numele fazei nu poate fi necompletat.", + "phase_name_too_long": "Numele fazei este prea lung.", + "invalid_phase_range": "Intervalul de fază este nevalid. Sfârșitul trebuie să fie mai mare decât începutul.", + "invalid_phase_timestamp": "Marca temporală este nevalidă. Utilizați formatul AAAA-LL-ZZ HH:MM sau HH:MM.", + "overlapping_phase_ranges": "Intervalele de fază se suprapun. Ajustați intervalele și încercați din nou.", + "incomplete_phase_row": "Fiecare rând de fază trebuie să includă o fază plus valori complete de început/sfârșit pentru modul selectat.", + "timestamp_mode_cycle_required": "Vă rugăm să selectați un ciclu sursă când utilizați modul marcaj temporal.", + "no_phase_ranges": "Încă nu sunt disponibile intervale de fază pentru acea acțiune.", + "feedback_notification_title": "WashData: verificați ciclul ({device})", + "feedback_notification_message": "**Dispozitiv**: {device}\n**Program**: {program} ({confidence}% încredere)\n**Ora**: {time}\n\nWashData are nevoie de ajutorul dvs. pentru a verifica acest ciclu detectat.\n\nVă rugăm să accesați **Setări > Dispozitive și servicii > WashData > Configurare > Feedback-uri de învățare** pentru a confirma sau corecta acest rezultat.", + "suggestions_ready_notification_title": "WashData: setări sugerate disponibile ({device})", + "suggestions_ready_notification_message": "Senzorul **Setări sugerate** raportează acum **{count}** recomandări acționabile.\n\nPentru a le revizui și aplica: **Setări > Dispozitive și servicii > WashData > Configurare > Setări avansate > Aplicați valorile sugerate**.\n\nSugestiile sunt opționale și sunt afișate pentru examinare înainte de a salva.", + "trim_range_invalid": "Începutul trebuie să fie înainte de sfârșit. Vă rugăm să vă ajustați punctele de tăiere.", + "trim_failed": "Tăierea a eșuat. Este posibil ca ciclul să nu mai existe sau fereastra este goală.", + "invalid_split_timestamp": "Marcajul temporal este invalid sau în afara ferestrei ciclului. Folosiți HH:MM sau HH:MM:SS în cadrul ferestrei ciclului.", + "no_split_segments_found": "Nu s-au putut produce segmente valide din aceste marcaje temporale. Asigurați-vă că fiecare marcaj temporal este în fereastra ciclului și că segmentele au cel puțin 1 minut.", + "auto_tune_suggestion": "{device_type} {device_title} cicluri fantomă detectate. Modificare sugerată de min_power: {current_min}W -> {new_min}W (nu se aplică automat).", + "auto_tune_title": "Auto-Tune WashData", + "auto_tune_fallback": "{device_type} {device_title} cicluri fantomă detectate. Modificare sugerată de min_power: {current_min}W -> {new_min}W (nu se aplică automat)." + }, + "abort": { + "no_cycles_found": "Nu s-au găsit cicluri", + "no_split_segments_found": "Nu au fost găsite segmente divizabile în ciclul selectat.", + "cycle_not_found": "Ciclul selectat nu a putut fi încărcat.", + "no_power_data": "Nu sunt disponibile date de urmărire a puterii pentru ciclul selectat.", + "no_profiles_found": "Nu s-au găsit profiluri. Creați mai întâi un profil.", + "no_profiles_for_matching": "Nu există profiluri disponibile pentru potrivire. Creați mai întâi profiluri.", + "no_unlabeled_cycles": "Toate ciclurile sunt deja etichetate", + "no_suggestions": "Nu sunt disponibile valori sugerate încă. Rulați câteva cicluri și încercați din nou.", + "no_predictions": "Fără predicții", + "no_custom_phases": "Nu s-au găsit faze personalizate.", + "reprocess_success": "S-au reprocesat cu succes {count} cicluri.", + "debug_data_cleared": "S-au șters datele de depanare din {count} cicluri." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Pornirea ciclului", + "cycle_finish": "Finalizarea ciclului", + "cycle_live": "Progres live" + } + }, + "common_text": { + "options": { + "unlabeled": "(Neetichetat)", + "create_new_profile": "Creați un profil nou", + "remove_label": "Eliminați eticheta", + "no_reference_cycle": "(Fără ciclu de referință)", + "all_device_types": "Toate tipurile de dispozitive", + "current_suffix": "(actual)", + "editor_select_info": "Selectați 1 ciclu pentru a împărți sau 2+ cicluri pentru a îmbina.", + "editor_select_info_split": "Selectați 1 ciclu pentru împărțire.", + "editor_select_info_merge": "Selectați 2 sau mai multe cicluri pentru combinare.", + "editor_select_info_delete": "Selectați 1 sau mai multe cicluri pentru ștergere.", + "no_cycles_recorded": "Nu s-au înregistrat încă cicluri.", + "profile_summary_line": "- **{name}**: {count} cicluri, {avg}m medie", + "no_profiles_created": "Nu au fost create profiluri încă.", + "phase_preview": "Previzualizare fază", + "phase_preview_no_curve": "Curba medie a profilului nu este încă disponibilă. Rulați/etichetați mai multe cicluri pentru acest profil.", + "average_power_curve": "Curba de putere medie", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cicluri, în medie ~{duration}m)", + "profile_option_short_fmt": "{name} ({count} cicluri, ~{duration}m)", + "deleted_cycles_from_profile": "S-au șters {count} cicluri din {profile}.", + "not_enough_data_graph": "Nu sunt suficiente date pentru a genera un grafic.", + "no_profiles_found": "Nu s-au găsit profiluri.", + "cycle_info_fmt": "Ciclu: {start}, {duration}m, curent: {label}", + "top_candidates_header": "### Principalii candidați", + "tbl_profile": "Profil", + "tbl_confidence": "Încredere", + "tbl_correlation": "Corelație", + "tbl_duration_match": "Potrivire durată", + "detected_profile": "Profil detectat", + "estimated_duration": "Durata estimată", + "actual_duration": "Durata reală", + "choose_action": "__Alegeți o acțiune de mai jos:__", + "tbl_count": "Nr.", + "tbl_avg": "Medie", + "tbl_min": "Min.", + "tbl_max": "Max.", + "tbl_energy_avg": "Energie (medie)", + "tbl_energy_total": "Energie (total)", + "tbl_consistency": "Consist.", + "tbl_last_run": "Ultima rulare", + "graph_legend_title": "Legenda graficului", + "graph_legend_body": "Banda albastră reprezintă intervalul minim și maxim de consum observat. Linia arată curba puterii medii.", + "recording_preview": "Previzualizare înregistrare", + "trim_start": "Început tăiere", + "trim_end": "Sfârșit tăiere", + "split_preview_title": "Previzualizare împărțire", + "split_preview_found_fmt": "S-au găsit {count} segmente.", + "split_preview_confirm_fmt": "Faceți clic pe Confirmare pentru a împărți acest ciclu în {count} cicluri separate.", + "merge_preview_title": "Previzualizare îmbinare", + "merge_preview_joining_fmt": "Se îmbină {count} cicluri. Golurile vor fi umplute cu citiri de 0W.", + "editor_delete_preview_title": "Previzualizare ștergere", + "editor_delete_preview_intro": "Ciclurile selectate vor fi șterse definitiv:", + "editor_delete_preview_confirm": "Faceți clic pe Confirmare pentru a șterge definitiv aceste înregistrări de cicluri.", + "no_power_preview": "*Nu sunt disponibile date de putere pentru previzualizare.*", + "profile_comparison": "Comparație de profil", + "actual_cycle_label": "Acest ciclu (real)", + "storage_usage_header": "Utilizarea stocării", + "storage_file_size": "Dimensiunea fișierului", + "storage_cycles": "Cicluri", + "storage_profiles": "Profiluri", + "storage_debug_traces": "Urme de depanare", + "overview_suffix": "Prezentare generală", + "phase_preview_for_profile": "Previzualizare fază pentru profil", + "current_ranges_header": "Intervale curente", + "assign_phases_help": "Utilizați acțiunile de mai jos pentru a adăuga, edita, șterge sau salva intervale.", + "profile_summary_header": "Rezumatul profilului", + "recent_cycles_header": "Cicluri recente", + "trim_cycle_preview_title": "Previzualizare tăiere ciclu", + "trim_cycle_preview_no_data": "Nu sunt disponibile date de putere pentru acest ciclu.", + "trim_cycle_preview_kept_suffix": "păstrat", + "table_program": "Program", + "table_when": "Când", + "table_length": "Durată", + "table_match": "Potrivire", + "table_profile": "Profil", + "table_cycles": "Cicluri", + "table_avg_length": "Durată medie", + "table_last_run": "Ultima rulare", + "table_avg_energy": "Energie medie", + "table_detected_program": "Program detectat", + "table_confidence": "Încredere", + "table_reported": "Raportat", + "phase_builtin_suffix": "(încorporat)", + "phase_none_available": "Nu există faze disponibile.", + "settings_deprecation_warning": "⚠️ **Tip de dispozitiv învechit:** {device_type} este programat pentru eliminare într-o versiune viitoare. Conducta de potrivire a lui WashData nu produce rezultate fiabile pentru această clasă de aparate. Integrarea dvs. continuă să funcționeze pe parcursul perioadei de depreciere; pentru a opri acest avertisment, comutați **Tipul dispozitiv** de mai jos la unul dintre tipurile acceptate (Mașină de spălat, Uscător, Mașină de spălat-Uscător combinată, Mașină de spălat vase, Friteuză cu aer, Mașină de făcut pâine sau Pompă) sau la **Altele (Avansat)** dacă aparatul dvs. nu se potrivește cu niciunul dintre tipurile acceptate. **Altele (Avansate)** furnizează intenționat valori implicite generice care nu sunt reglate pentru niciun dispozitiv specific, așa că va trebui să configurați singur pragurile, timeout-urile și parametrii de potrivire; toate setările existente sunt păstrate atunci când comutați.", + "phase_other_device_types": "Alte tipuri de dispozitive:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Împărțiți un ciclu (găsiți lacune)", + "merge": "Îmbinați cicluri (alăturați fragmente)", + "delete": "Ștergeți ciclul(e)" + } + }, + "split_mode": { + "options": { + "auto": "Detectează automat intervalele inactive", + "manual": "Marcaje temporale manuale" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Întreținere: reprocesare și optimizare date", + "clear_debug_data": "Ștergeți datele de depanare (eliberați spațiu)", + "wipe_history": "Ștergeți TOATE datele pentru acest dispozitiv (ireversibil)", + "export_import": "Export/Import JSON cu setări (copiere/lipire)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportați toate datele", + "import": "Import/îmbinare date" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etichetare automată a ciclurilor vechi", + "select_cycle_to_label": "Etichetați un ciclu specific", + "select_cycle_to_delete": "Ștergeți ciclul", + "interactive_editor": "Editor interactiv îmbinare/împărțire", + "trim_cycle": "Tăiați datele ciclului" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Creați un profil nou", + "edit_profile": "Editați/redenumiți profilul", + "delete_profile": "Ștergeți profilul", + "profile_stats": "Statistici de profil", + "cleanup_profile": "Curățați istoricul - grafic și ștergere", + "assign_phases": "Atribuiți intervale de fază" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Creați o nouă fază", + "edit_custom_phase": "Editați faza", + "delete_custom_phase": "Ștergeți faza" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Adăugați intervalul de fază", + "edit_range": "Editați intervalul de fază", + "delete_range": "Ștergeți intervalul de fază", + "clear_ranges": "Ștergeți toate intervalele", + "auto_detect_ranges": "Detectare automată a fazelor", + "save_ranges": "Salvați și întoarceți-vă" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Reîmprospătați starea", + "stop_recording": "Opriți înregistrarea (salvare și procesare)", + "start_recording": "Începeți o nouă înregistrare", + "process_recording": "Procesați ultima înregistrare (tăiere și salvare)", + "discard_recording": "Renunțați la ultima înregistrare" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Creați un profil nou", + "existing_profile": "Adăugați la profilul existent", + "discard": "Renunțați la înregistrare" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Confirmare - detectare corectă", + "correct": "Corectare - alegeți program/durată", + "ignore": "Ignorare - ciclu fals pozitiv/zgomotos", + "delete": "Ștergere - eliminați din istoric" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Denumiți și aplicați fazele", + "cancel": "Întoarceți-vă fără modificări" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Setați punctul de pornire", + "set_end": "Setați punctul final", + "reset": "Resetați la durata completă", + "apply": "Aplicați tăierea", + "cancel": "Anulați" + } + }, + "device_type": { + "options": { + "washing_machine": "Maşină de spălat", + "dryer": "Uscător", + "washer_dryer": "Combo mașină de spălat cu uscător", + "dishwasher": "Maşină de spălat vase", + "coffee_machine": "Aparat de cafea (învechit)", + "ev": "Vehicul electric (învechit)", + "air_fryer": "Friteuza cu aer", + "heat_pump": "Pompă de căldură (învechită)", + "bread_maker": "Aparat de paine", + "pump": "Pompă / Pompă de colector", + "oven": "Cuptor (depreciat)", + "other": "Altele (avansate)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etichetați ciclul", + "description": "Atribuiți un profil existent unui ciclu anterior sau eliminați eticheta.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat de etichetat." + }, + "cycle_id": { + "name": "ID-ul ciclului", + "description": "ID-ul ciclului de etichetat." + }, + "profile_name": { + "name": "Nume profil", + "description": "Numele unui profil existent (creați profiluri în meniul Gestionare profiluri). Lăsați necompletat pentru a elimina eticheta." + } + } + }, + "create_profile": { + "name": "Creați un profil", + "description": "Creați un profil nou (autonom sau bazat pe un ciclu de referință).", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat." + }, + "profile_name": { + "name": "Nume profil", + "description": "Nume pentru noul profil (de exemplu, „Rezistente”, „Delicate”)." + }, + "reference_cycle_id": { + "name": "ID ciclu de referință", + "description": "ID-ul de ciclu opțional pe care să se bazeze acest profil." + } + } + }, + "delete_profile": { + "name": "Ștergeți profilul", + "description": "Ștergeți un profil și, opțional, eliminați etichetarea ciclurilor care îl folosesc.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat." + }, + "profile_name": { + "name": "Nume profil", + "description": "Profilul de șters." + }, + "unlabel_cycles": { + "name": "Eliminați etichetarea ciclurilor", + "description": "Eliminați eticheta profilului din ciclurile care îl folosesc." + } + } + }, + "auto_label_cycles": { + "name": "Etichetare automată a ciclurilor vechi", + "description": "Etichetați retroactiv ciclurile neetichetate folosind potrivirea profilului.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat." + }, + "confidence_threshold": { + "name": "Pragul de încredere", + "description": "Încrederea minimă de potrivire (0,50-0,95) pentru aplicarea etichetelor." + } + } + }, + "export_config": { + "name": "Exportați configurația", + "description": "Exportați profilurile, ciclurile și setările acestui dispozitiv într-un fișier JSON (per dispozitiv).", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat de exportat." + }, + "path": { + "name": "Cale", + "description": "Calea absolută opțională a fișierului de scris (implicit la /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importați configurația", + "description": "Importați profiluri, cicluri și setări pentru acest dispozitiv dintr-un fișier de export JSON (setările pot fi suprascrise).", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat în care să importați." + }, + "path": { + "name": "Cale", + "description": "Calea absolută către fișierul JSON de export pentru import." + } + } + }, + "submit_cycle_feedback": { + "name": "Trimiteți feedback pentru ciclu", + "description": "Confirmați sau corectați un program detectat automat după un ciclu finalizat. Furnizați fie „entry_id” (avansat), fie „device_id” (recomandat).", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul WashData (recomandat în locul entry_id)." + }, + "entry_id": { + "name": "ID de intrare", + "description": "ID-ul de intrare de configurare pentru dispozitiv (alternativă la device_id)." + }, + "cycle_id": { + "name": "ID-ul ciclului", + "description": "ID-ul ciclului afișat în notificarea/jurnalele de feedback." + }, + "user_confirmed": { + "name": "Confirmați programul detectat", + "description": "Setați ca adevărat dacă programul detectat este corect." + }, + "corrected_profile": { + "name": "Profil corectat", + "description": "Dacă nu este confirmat, furnizați numele corect al profilului/programului." + }, + "corrected_duration": { + "name": "Durata corectată (secunde)", + "description": "Durată opțională corectată în secunde." + }, + "notes": { + "name": "Note", + "description": "Note opționale despre acest ciclu." + } + } + }, + "record_start": { + "name": "Porniți înregistrarea ciclului", + "description": "Începeți înregistrarea manuală a unui ciclu curat (ocolește toată logica de potrivire).", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat de înregistrat." + } + } + }, + "record_stop": { + "name": "Opriți înregistrarea ciclului", + "description": "Opriți înregistrarea manuală.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul mașinii de spălat pentru a opri înregistrarea." + } + } + }, + "trim_cycle": { + "name": "Tăiați ciclul", + "description": "Tăiați datele de putere ale unui ciclu anterior la o anumită fereastră de timp.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul WashData." + }, + "cycle_id": { + "name": "ID-ul ciclului", + "description": "ID-ul ciclului de tăiat." + }, + "trim_start_s": { + "name": "Începutul tăierii (secunde)", + "description": "Păstrați datele de la acest offset în secunde. Implicit 0." + }, + "trim_end_s": { + "name": "Sfârșitul tăierii (secunde)", + "description": "Păstrați datele până la acest offset în secunde. Implicit = durata totală." + } + } + }, + "pause_cycle": { + "name": "Ciclul de pauză", + "description": "Întrerupeți ciclul activ pentru un dispozitiv WashData.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul WashData se întrerupe." + } + } + }, + "resume_cycle": { + "name": "Reluați ciclul", + "description": "Reluați un ciclu întrerupt pentru un dispozitiv WashData.", + "fields": { + "device_id": { + "name": "Dispozitiv", + "description": "Dispozitivul WashData trebuie reluat." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "În funcțiune" + }, + "match_ambiguity": { + "name": "Ambiguitate la potrivire" + } + }, + "sensor": { + "washer_state": { + "name": "Stare", + "state": { + "off": "Oprit", + "idle": "Inactiv", + "starting": "Pornire", + "running": "În funcțiune", + "paused": "Pauză", + "user_paused": "Pus în pauză de utilizator", + "ending": "Se termină", + "finished": "Terminat", + "anti_wrinkle": "Antirid", + "delay_wait": "Așteaptă să Înceapă", + "interrupted": "Întrerupt", + "force_stopped": "Oprit forțat", + "rinse": "Clătire", + "unknown": "Necunoscut", + "clean": "Curat" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Timp rămas" + }, + "total_duration": { + "name": "Durata totală" + }, + "cycle_progress": { + "name": "Progres" + }, + "current_power": { + "name": "Puterea curentă" + }, + "elapsed_time": { + "name": "Timp scurs" + }, + "debug_info": { + "name": "Informații de depanare" + }, + "match_confidence": { + "name": "Încrederea în potrivire" + }, + "top_candidates": { + "name": "Principalii candidați", + "state": { + "none": "Niciunul" + } + }, + "current_phase": { + "name": "Faza curentă" + }, + "wash_phase": { + "name": "Fază" + }, + "profile_cycle_count": { + "name": "Număr de cicluri {profile_name}", + "unit_of_measurement": "cicluri" + }, + "suggestions": { + "name": "Setări sugerate disponibile" + }, + "pump_runs_today": { + "name": "Funcționarea pompei (ultimele 24 de ore)" + }, + "cycle_count": { + "name": "Număr de cicluri" + } + }, + "select": { + "program_select": { + "name": "Programul ciclului", + "state": { + "auto_detect": "Detectare automată" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forțați sfârșitul ciclului" + }, + "pause_cycle": { + "name": "Ciclul de pauză" + }, + "resume_cycle": { + "name": "Reluați ciclul" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Este necesar un device_id valid." + }, + "cycle_id_required": { + "message": "Este necesar un cycle_id valid." + }, + "profile_name_required": { + "message": "Este necesar un nume_profil valid." + }, + "device_not_found": { + "message": "Dispozitivul nu a fost găsit." + }, + "no_config_entry": { + "message": "Nu a fost găsită nicio intrare de configurare pentru dispozitiv." + }, + "integration_not_loaded": { + "message": "Integrarea nu a fost încărcată pentru acest dispozitiv." + }, + "cycle_not_found_or_no_power": { + "message": "Ciclul nu a fost găsit sau nu are date de putere." + }, + "trim_failed_empty_window": { + "message": "Eșec la tăiere - ciclul nu a fost găsit, nu există date de putere sau fereastra rezultată este goală." + }, + "trim_invalid_range": { + "message": "trim_end_s trebuie să fie mai mare decât trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ru.json b/custom_components/ha_washdata/translations/ru.json new file mode 100644 index 0000000..38af937 --- /dev/null +++ b/custom_components/ha_washdata/translations/ru.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Настройка WashData", + "description": "Настройте стиральную машину или другой прибор.\n\nТребуется датчик мощности.\n\n**Далее вас спросят, хотите ли вы создать свой первый профиль.**", + "data": { + "name": "Имя устройства", + "device_type": "Тип устройства", + "power_sensor": "Датчик мощности", + "min_power": "Минимальный порог мощности (Вт)" + }, + "data_description": { + "name": "Понятное имя для этого устройства (например, «Стиральная машина», «Посудомоечная машина»).", + "device_type": "Что это за прибор? Помогает адаптировать обнаружение и маркировку.", + "power_sensor": "Датчик, который в реальном времени сообщает о потребляемой мощности (в ваттах) от вашей умной розетки.", + "min_power": "Показания мощности выше этого порога (в ваттах) указывают на то, что устройство работает. Начните с 2 Вт для большинства устройств." + } + }, + "first_profile": { + "title": "Создать первый профиль", + "description": "**Цикл** - это полная работа вашего прибора (например, загрузка белья). **Профиль** группирует эти циклы по типу (например, «Хлопок», «Быстрая стирка»). Создание профиля сейчас помогает интеграции сразу оценить продолжительность.\n\nВы можете вручную выбрать этот профиль в элементах управления устройством, если он не обнаружен автоматически.\n\n**Оставьте поле «Имя профиля» пустым, чтобы пропустить этот шаг.**", + "data": { + "profile_name": "Имя профиля (необязательно)", + "manual_duration": "Предполагаемая продолжительность (минуты)" + } + } + }, + "error": { + "cannot_connect": "Не удалось подключиться", + "invalid_auth": "Неверная аутентификация", + "unknown": "Неожиданная ошибка" + }, + "abort": { + "already_configured": "Устройство уже настроено" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Просмотреть отзывы об обучении", + "settings": "Настройки", + "notifications": "Уведомления", + "manage_cycles": "Управление циклами", + "manage_profiles": "Управление профилями", + "manage_phase_catalog": "Управление каталогом фаз", + "record_cycle": "Запись цикла (вручную)", + "diagnostics": "Диагностика и обслуживание" + } + }, + "apply_suggestions": { + "title": "Скопировать предлагаемые значения", + "description": "Текущие предложенные значения будут скопированы в сохраненные настройки. Это однократное действие, которое не включает автоматическую перезапись.\n\nРекомендуемые значения для копирования:\n{suggested}", + "data": { + "confirm": "Подтвердить действие копирования" + } + }, + "apply_suggestions_confirm": { + "title": "Просмотрите предлагаемые изменения", + "description": "WashData обнаружила **{pending_count}** рекомендуемые значения для применения.\n\nИзменения:\n{changes}\n\n✅ Установите флажок ниже и нажмите **Отправить**, чтобы немедленно применить и сохранить эти изменения.\n❌ Оставьте флажок снятым и нажмите **Отправить**, чтобы отменить действие и вернуться назад.", + "data": { + "confirm_apply_suggestions": "Примените и сохраните эти изменения" + } + }, + "settings": { + "title": "Настройки", + "description": "{deprecation_warning}Настройте пороги обнаружения, обучение и расширенное поведение. Значения по умолчанию хорошо подходят для большинства конфигураций.\n\nДоступно предложенных настроек: {suggestions_count}.\nЕсли это число больше 0, откройте «Расширенные настройки» и используйте «Применить предложенные значения» для просмотра рекомендаций.", + "data": { + "apply_suggestions": "Применить предложенные значения", + "device_type": "Тип устройства", + "power_sensor": "Объект датчика мощности", + "min_power": "Минимальная мощность (Вт)", + "off_delay": "Задержка окончания цикла (с)", + "notify_actions": "Действия уведомления", + "notify_people": "Отложить доставку, пока эти люди не вернутся домой", + "notify_only_when_home": "Откладывать уведомления, пока выбранный человек не окажется дома", + "notify_fire_events": "Запускать события автоматизации", + "notify_start_services": "Начало цикла - цели уведомления", + "notify_finish_services": "Завершение цикла - цели уведомления", + "notify_live_services": "Текущий прогресс - цели уведомлений", + "notify_before_end_minutes": "Уведомление перед завершением (за сколько минут до конца)", + "notify_title": "Заголовок уведомления", + "notify_icon": "Значок уведомления", + "notify_start_message": "Формат сообщения о запуске", + "notify_finish_message": "Формат сообщения о завершении", + "notify_pre_complete_message": "Формат сообщения перед завершением", + "show_advanced": "Изменить расширенные настройки (следующий шаг)" + }, + "data_description": { + "apply_suggestions": "Установите этот флажок, чтобы скопировать в форму значения, предложенные алгоритмом обучения. Просмотрите перед сохранением.\n\nПредлагается (из обучения):\n- min_power: {suggested_min_power} Вт\n- off_delay: {suggested_off_delay} с\n- watchdog_interval: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} с\n- profile_match_interval: {suggested_profile_match_interval} с\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} Вт\n- stop_threshold_w: {suggested_stop_threshold_w} Вт\n- end_energy_threshold: {suggested_end_energy_threshold} Втч\n- running_dead_zone: {suggested_running_dead_zone} с\n{suggested_reason}", + "device_type": "Выберите тип прибора (стиральная машина, сушильная машина, посудомоечная машина, кофемашина, электромобиль). Этот тег сохраняется при каждом цикле для лучшей организации.", + "power_sensor": "Объект-датчик, сообщающий о мощности в реальном времени (в ваттах). Вы можете изменить это в любое время, не теряя исторических данных - полезно, если вы заменяете умную розетку.", + "min_power": "Показания мощности выше этого порога (в ваттах) указывают на то, что устройство работает. Начните с 2 Вт для большинства устройств.", + "off_delay": "Сглаженная мощность должна оставаться ниже порога остановки в течение этого количества секунд, чтобы отметить завершение. По умолчанию: 120 с.", + "notify_actions": "Дополнительная последовательность действий Home Assistant для уведомлений (скрипты, notify, TTS и т. д.). Если задано, действия выполняются до резервной службы уведомлений.", + "notify_people": "Объекты людей, используемые для ограничения по присутствию. Если включено, доставка уведомлений задерживается до тех пор, пока хотя бы один выбранный человек не окажется дома.", + "notify_only_when_home": "Если включено, откладывать уведомления до тех пор, пока хотя бы один выбранный человек не окажется дома.", + "notify_fire_events": "Если включено, запускает события Home Assistant при начале и завершении цикла (ha_washdata_cycle_started и ha_washdata_cycle_ended) для управления автоматизацией.", + "notify_start_services": "Одна или несколько служб уведомлений для оповещения о начале цикла. Оставьте пустым, чтобы пропустить уведомления о запуске.", + "notify_finish_services": "Одна или несколько служб уведомлений, которые оповещают, когда цикл заканчивается или приближается к завершению. Оставьте пустым, чтобы пропустить уведомления об окончании.", + "notify_live_services": "Одна или несколько служб уведомлений для получения обновлений о ходе выполнения в реальном времени во время выполнения цикла (только мобильное сопутствующее приложение). Оставьте пустым, чтобы пропустить текущие обновления.", + "notify_before_end_minutes": "За сколько минут до предполагаемого окончания цикла срабатывает уведомление. Установите 0, чтобы отключить. По умолчанию: 0.", + "notify_title": "Пользовательский заголовок для уведомлений. Поддерживает заполнитель {device}.", + "notify_icon": "Значок для уведомлений (например, mdi:washing-machine). Оставьте пустым для отправки без значка.", + "notify_start_message": "Сообщение отправляется при запуске цикла. Поддерживает заполнитель {device}.", + "notify_finish_message": "Сообщение отправляется после завершения цикла. Поддерживает заполнители {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Текст повторяющихся обновлений прогресса в реальном времени во время выполнения цикла. Поддерживает заполнители {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Уведомления", + "description": "Настройте каналы уведомлений, шаблоны и дополнительные обновления прогресса на мобильных устройствах в реальном времени.", + "data": { + "notify_actions": "Действия уведомления", + "notify_people": "Отложить доставку, пока эти люди не вернутся домой", + "notify_only_when_home": "Откладывать уведомления, пока выбранный человек не окажется дома", + "notify_fire_events": "Запускать события автоматизации", + "notify_start_services": "Начало цикла - цели уведомления", + "notify_finish_services": "Завершение цикла - цели уведомления", + "notify_live_services": "Текущий прогресс - цели уведомлений", + "notify_before_end_minutes": "Уведомление перед завершением (за сколько минут до конца)", + "notify_live_interval_seconds": "Интервал обновления в реальном времени (секунды)", + "notify_live_overrun_percent": "Допустимое превышение обновлений в реальном времени (%)", + "notify_live_chronometer": "Таймер обратного отсчета хронометра", + "notify_title": "Заголовок уведомления", + "notify_icon": "Значок уведомления", + "notify_start_message": "Формат сообщения о запуске", + "notify_finish_message": "Формат сообщения о завершении", + "notify_pre_complete_message": "Формат сообщения перед завершением", + "energy_price_entity": "Цена энергии - организация (необязательно)", + "energy_price_static": "Цена энергии - статическое значение (необязательно)", + "go_back": "Вернуться без сохранения", + "notify_reminder_message": "Формат сообщения-напоминания", + "notify_timeout_seconds": "Автоматическое закрытие через (секунды)", + "notify_channel": "Канал уведомлений (статус/активный режим/напоминание)", + "notify_finish_channel": "Готовый канал уведомлений" + }, + "data_description": { + "go_back": "Отметьте этот пункт и нажмите Отправить, чтобы отбросить все изменения выше и вернуться в главное меню.", + "notify_actions": "Дополнительная последовательность действий Home Assistant для уведомлений (скрипты, notify, TTS и т. д.). Если задано, действия выполняются до резервной службы уведомлений.", + "notify_people": "Объекты людей, используемые для ограничения по присутствию. Если включено, доставка уведомлений задерживается до тех пор, пока хотя бы один выбранный человек не окажется дома.", + "notify_only_when_home": "Если включено, откладывать уведомления до тех пор, пока хотя бы один выбранный человек не окажется дома.", + "notify_fire_events": "Если включено, запускает события Home Assistant при начале и завершении цикла (ha_washdata_cycle_started и ha_washdata_cycle_ended) для управления автоматизацией.", + "notify_start_services": "Одна или несколько служб уведомлений для оповещения о начале цикла (например, notify.mobile_app_my_phone). Оставьте пустым, чтобы пропустить уведомления о запуске.", + "notify_finish_services": "Одна или несколько служб уведомлений, которые оповещают, когда цикл заканчивается или приближается к завершению. Оставьте пустым, чтобы пропустить уведомления об окончании.", + "notify_live_services": "Одна или несколько служб уведомлений для получения обновлений о ходе выполнения в реальном времени во время выполнения цикла (только мобильное сопутствующее приложение). Оставьте пустым, чтобы пропустить текущие обновления.", + "notify_before_end_minutes": "За сколько минут до предполагаемого окончания цикла срабатывает уведомление. Установите 0, чтобы отключить. По умолчанию: 0.", + "notify_live_interval_seconds": "Как часто обновления прогресса отправляются во время работы. Более низкие значения более отзывчивы, но требуют больше уведомлений. Установлено минимальное значение 30 секунд - значения ниже 30 с автоматически округляются до 30 с.", + "notify_live_overrun_percent": "Запас безопасности сверх расчётного количества обновлений для длительных/превышающих циклов (например, 20% позволяет увеличить расчётное количество обновлений в 1,2 раза).", + "notify_live_chronometer": "Если эта функция включена, каждое оперативное обновление включает обратный отсчет хронометра до предполагаемого времени окончания. Таймер уведомлений автоматически отсчитывает время между обновлениями на устройстве (только в сопутствующем приложении Android).", + "notify_title": "Пользовательский заголовок для уведомлений. Поддерживает заполнитель {device}.", + "notify_icon": "Значок для уведомлений (например, mdi:washing-machine). Оставьте пустым для отправки без значка.", + "notify_start_message": "Сообщение отправляется при запуске цикла. Поддерживает заполнитель {device}.", + "notify_finish_message": "Сообщение отправляется после завершения цикла. Поддерживает заполнители {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Выберите числовой объект, содержащий текущую цену на электроэнергию (например, Sensor.electricity_price, input_number.energy_rate). Если установлено, включает заполнитель {cost}. Имеет приоритет над статическим значением, если оба настроены.", + "energy_price_static": "Фиксированная цена на электроэнергию за кВтч. Если установлено, включает заполнитель {cost}. Игнорируется, если сущность настроена выше. Добавьте символ валюты в шаблон сообщения, например. {cost} €.", + "notify_reminder_message": "Текст разового напоминания отправляется за настроенное количество минут до предполагаемого окончания. В отличие от повторяющихся обновлений в режиме реального времени, приведенных выше. Поддерживает заполнители {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Автоматически закрывать уведомления по истечении этого количества секунд (пересылаемые как «тайм-аут» сопутствующего приложения). Установите значение 0, чтобы сохранять их до закрытия. По умолчанию: 0.", + "notify_channel": "Канал уведомлений о сопутствующем приложении Android для статуса, текущего прогресса и напоминаний. Звук и важность канала настраиваются в сопутствующем приложении при первом использовании имени канала — WashData устанавливает только имя канала. Оставьте пустым для приложения по умолчанию.", + "notify_finish_channel": "Отдельный канал Android для оповещения о завершении (также используется напоминанием и сигналом ожидания стирки), чтобы у него был собственный звук. Оставьте пустым, чтобы повторно использовать канал выше.", + "notify_pre_complete_message": "Текст повторяющихся обновлений прогресса в реальном времени во время выполнения цикла. Поддерживает заполнители {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Расширенные настройки", + "description": "Настройте пороги обнаружения, обучение и расширенное поведение. Значения по умолчанию хорошо подходят для большинства конфигураций.", + "sections": { + "suggestions_section": { + "name": "Предложенные настройки", + "description": "Доступно {suggestions_count} обученных рекомендаций. Отметьте 'Применить предложенные значения', чтобы просмотреть предлагаемые изменения перед сохранением.", + "data": { + "apply_suggestions": "Применить предложенные значения" + }, + "data_description": { + "apply_suggestions": "Установите этот флажок, чтобы открыть шаг проверки предложенных значений алгоритма обучения. Ничего не сохраняется автоматически.\n\nПредлагается (из обучения):\n- min_power: {suggested_min_power} Вт\n- off_delay: {suggested_off_delay} с\n- watchdog_interval: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} с\n- profile_match_interval: {suggested_profile_match_interval} с\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} Вт\n- stop_threshold_w: {suggested_stop_threshold_w} Вт\n- end_energy_threshold: {suggested_end_energy_threshold} Втч\n- running_dead_zone: {suggested_running_dead_zone} с\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Обнаружение и пороги мощности", + "description": "Определяет, когда устройство считается ВКЛЮЧЕННЫМ или ВЫКЛЮЧЕННЫМ, энергетические пороги и время переходов между состояниями.", + "data": { + "start_duration_threshold": "Длительность устранения дребезга запуска (с)", + "start_energy_threshold": "Энергетический порог запуска (Втч)", + "completion_min_seconds": "Минимальное время выполнения для завершения (секунды)", + "start_threshold_w": "Пороговое значение запуска (Вт)", + "stop_threshold_w": "Пороговое значение остановки (Вт)", + "end_energy_threshold": "Энергетический порог завершения (Втч)", + "running_dead_zone": "Мёртвая зона запуска (секунды)", + "end_repeat_count": "Количество повторений условия завершения", + "min_off_gap": "Минимальный разрыв между циклами (с)", + "sampling_interval": "Интервал выборки (с)" + }, + "data_description": { + "start_duration_threshold": "Игнорировать кратковременные скачки мощности короче этой длительности (в секундах). Фильтрует всплески при запуске (например, 60 Вт в течение 2 с) до начала реального цикла. По умолчанию: 5 с.", + "start_energy_threshold": "Для подтверждения цикл должен накопить не менее этого количества энергии (Втч) во время фазы ЗАПУСК. По умолчанию: 0,005 Втч.", + "completion_min_seconds": "Циклы короче этого значения будут помечены как «прерванные», даже если они завершились естественным образом. По умолчанию: 600 с.", + "start_threshold_w": "Мощность должна подняться ВЫШЕ этого порога, чтобы НАЧАТЬ цикл. Установите выше мощности в режиме ожидания. По умолчанию: min_power + 1 Вт.", + "stop_threshold_w": "Мощность должна упасть НИЖЕ этого порога, чтобы ЗАВЕРШИТЬ цикл. Установите чуть выше нуля, чтобы шум не вызывал ложных срабатываний. По умолчанию: 0,6 × min_power.", + "end_energy_threshold": "Цикл заканчивается только в том случае, если энергия в последнем окне задержки выключения ниже этого значения (Втч). Предотвращает ложные срабатывания, если мощность колеблется около нуля. По умолчанию: 0,05 Втч.", + "running_dead_zone": "После начала цикла игнорировать провалы мощности в течение этого количества секунд, чтобы предотвратить ложное обнаружение завершения во время начальной фазы запуска. Установите 0, чтобы отключить. По умолчанию: 0 с.", + "end_repeat_count": "Сколько раз условие завершения (мощность ниже порога для off_delay) должно последовательно выполняться перед завершением цикла. Более высокие значения предотвращают ложные завершения во время пауз. По умолчанию: 1.", + "min_off_gap": "Если цикл начинается в течение указанного количества секунд после окончания предыдущего, они будут ОБЪЕДИНЕНЫ в один цикл.", + "sampling_interval": "Минимальный интервал выборки в секундах. Обновления, превышающие эту скорость, будут игнорироваться. По умолчанию: 30 с (2 с для стиральных, стирально-сушильных и посудомоечных машин)." + } + }, + "matching_section": { + "name": "Сопоставление профилей и обучение", + "description": "Определяет, насколько агрессивно WashData сопоставляет текущие циклы с обученными профилями и когда запрашивать отзыв.", + "data": { + "profile_match_min_duration_ratio": "Минимальный коэффициент продолжительности сопоставления профиля (0,1-1,0)", + "profile_match_interval": "Интервал сопоставления профиля (секунды)", + "profile_match_threshold": "Порог сопоставления профиля", + "profile_unmatch_threshold": "Порог несоответствия профиля", + "auto_label_confidence": "Уверенность для автоматической маркировки (0-1)", + "learning_confidence": "Уверенность для запроса обратной связи (0-1)", + "suppress_feedback_notifications": "Подавить уведомления об отзывах", + "duration_tolerance": "Допуск продолжительности (0-0,5)", + "smoothing_window": "Окно сглаживания (отсчёты)", + "profile_duration_tolerance": "Допуск продолжительности сопоставления профиля (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Минимальный коэффициент продолжительности для сопоставления профиля. Для соответствия продолжительность рабочего цикла должна составлять не менее этой доли продолжительности профиля. Меньше = более раннее совпадение. По умолчанию: 0,50 (50%).", + "profile_match_interval": "Как часто (в секундах) предпринимать попытки сопоставления профилей в течение цикла. Более низкие значения обеспечивают более быстрое обнаружение, но используют больше ресурсов ЦП. По умолчанию: 300 с (5 минут).", + "profile_match_threshold": "Минимальный показатель сходства DTW (0,0–1,0), необходимый для того, чтобы считать профиль совпавшим. Выше = более строгое совпадение, меньше ложных срабатываний. По умолчанию: 0,4.", + "profile_unmatch_threshold": "Показатель сходства DTW (0,0–1,0), ниже которого ранее сопоставленный профиль отклоняется. Должно быть ниже порога совпадения, чтобы предотвратить мерцание. По умолчанию: 0,35.", + "auto_label_confidence": "Автоматически маркировать циклы с этой уверенностью или выше при завершении (0,0–1,0).", + "learning_confidence": "Минимальная уверенность для запроса проверки пользователем через событие (0,0–1,0).", + "suppress_feedback_notifications": "Если эта функция включена, WashData по-прежнему будет отслеживать и запрашивать обратную связь внутри себя, но не будет показывать постоянные уведомления с просьбой подтвердить циклы. Полезно, когда циклы всегда определяются правильно, а подсказки отвлекают.", + "duration_tolerance": "Допуск на оценки оставшегося времени во время цикла (0,0–0,5 соответствует 0–50%).", + "smoothing_window": "Количество последних показаний мощности, используемых для сглаживания скользящего среднего.", + "profile_duration_tolerance": "Допуск, используемый при сопоставлении профилей для отклонения общей продолжительности (0,0–0,5). Поведение по умолчанию: ±25%." + } + }, + "timing_section": { + "name": "Время, обслуживание и отладка", + "description": "Сторожевой таймер, сброс прогресса, автоматическое обслуживание и показ отладочных сущностей.", + "data": { + "watchdog_interval": "Интервал сторожевого таймера (секунды)", + "no_update_active_timeout": "Тайм-аут без обновлений (с)", + "progress_reset_delay": "Задержка сброса прогресса (секунды)", + "auto_maintenance": "Включить автоматическое обслуживание", + "expose_debug_entities": "Показывать объекты отладки", + "save_debug_traces": "Сохранять трассировки отладки" + }, + "data_description": { + "watchdog_interval": "Секунды между проверками сторожевого таймера во время работы. По умолчанию: 5 с. ВНИМАНИЕ: убедитесь, что это значение БОЛЬШЕ интервала обновления вашего датчика, чтобы избежать ложных остановок (например, если датчик обновляется каждые 60 секунд, используйте 65 с и более).", + "no_update_active_timeout": "Для датчиков, публикующих только при изменении: принудительно завершать АКТИВНЫЙ цикл только в том случае, если обновлений нет в течение указанного количества секунд. Завершение при низком потреблении по-прежнему использует задержку выключения.", + "progress_reset_delay": "После завершения цикла (100%) подождать указанное количество секунд простоя перед сбросом прогресса до 0%. По умолчанию: 300 с.", + "auto_maintenance": "Включить автообслуживание (восстановление отсчётов, объединение фрагментов).", + "expose_debug_entities": "Показывать расширенные датчики (уверенность, фаза, неоднозначность) для отладки.", + "save_debug_traces": "Сохранять подробные данные рейтинга и трассировки мощности в истории (увеличивает использование хранилища)." + } + }, + "anti_wrinkle_section": { + "name": "Защита от складок (сушилки)", + "description": "Игнорируйте вращения барабана после завершения цикла, чтобы предотвратить призрачные циклы. Только сушильная / стирально-сушильная машина.", + "data": { + "anti_wrinkle_enabled": "Защита от морщин (только для сушильной машины)", + "anti_wrinkle_max_power": "Максимальная мощность для режима защиты от морщин (Вт)", + "anti_wrinkle_max_duration": "Максимальная длительность режима защиты от морщин (с)", + "anti_wrinkle_exit_power": "Мощность выхода из режима защиты от морщин (Вт)" + }, + "data_description": { + "anti_wrinkle_enabled": "Игнорировать короткие вращения барабана с малой мощностью после окончания цикла, чтобы предотвратить появление ложных циклов (только для сушильной/стирально-сушильной машины).", + "anti_wrinkle_max_power": "Всплески этой мощности или ниже рассматриваются как вращения для защиты от морщин, а не как новые циклы. Применяется только при включённой функции защиты от морщин.", + "anti_wrinkle_max_duration": "Максимальная длина импульса, который может рассматриваться как вращение для защиты от морщин. Применяется только при включённой функции защиты от морщин.", + "anti_wrinkle_exit_power": "Если мощность упадёт ниже этого уровня, немедленно выйти из режима защиты от морщин (настоящее выключение). Применяется только при включённой функции защиты от морщин." + } + }, + "delay_start_section": { + "name": "Обнаружение отложенного старта", + "description": "Считайте устойчивый режим ожидания с низким потреблением состоянием 'Ожидание запуска', пока цикл фактически не начнется.", + "data": { + "delay_start_detect_enabled": "Включить обнаружение отложенного старта", + "delay_confirm_seconds": "Отложенный старт: время подтверждения режима ожидания (s)", + "delay_timeout_hours": "Отложенный старт: максимальное время ожидания (ч)" + }, + "data_description": { + "delay_start_detect_enabled": "Если этот параметр включен, кратковременный всплеск потребления низкой мощности, за которым следует устойчивое питание в режиме ожидания, определяется как отложенный запуск. Во время задержки датчик состояния показывает «Ожидание запуска». Ни время выполнения программы, ни прогресс не отслеживаются до тех пор, пока цикл фактически не начнется. Требуется, чтобы start_threshold_w был установлен выше пиковой мощности стока.", + "delay_confirm_seconds": "Мощность должна оставаться между порогом остановки (W) и порогом запуска (W) в течение этого количества секунд, прежде чем WashData перейдет в состояние 'Ожидание запуска'. Это отфильтровывает короткие пики при навигации по меню. По умолчанию: 60 s.", + "delay_timeout_hours": "Тайм-аут безопасности: если машина все еще находится в состоянии «Ожидание запуска» по истечении этого количества часов, WashData сбрасывается в состояние «Выкл.». По умолчанию: 8 часов." + } + }, + "external_triggers_section": { + "name": "Внешние триггеры, дверь и пауза", + "description": "Необязательный внешний датчик завершения цикла, датчик двери для определения паузы / разгрузки и переключатель отключения питания во время паузы.", + "data": { + "external_end_trigger_enabled": "Включить внешний триггер завершения цикла", + "external_end_trigger": "Внешний датчик окончания цикла", + "external_end_trigger_inverted": "Инвертировать логику триггера (срабатывание при выключении)", + "door_sensor_entity": "Датчик двери", + "pause_cuts_power": "Отключение питания при паузе", + "switch_entity": "Объект переключения (для приостановки отключения электроэнергии)", + "notify_unload_delay_minutes": "Задержка уведомления об ожидании стирки (мин)" + }, + "data_description": { + "external_end_trigger_enabled": "Включить мониторинг внешнего бинарного датчика для инициирования завершения цикла.", + "external_end_trigger": "Выберите объект бинарного датчика. Когда этот датчик сработает, текущий цикл завершится со статусом «завершён».", + "external_end_trigger_inverted": "По умолчанию триггер срабатывает при включении датчика. Установите этот флажок, чтобы срабатывание происходило при выключении датчика.", + "door_sensor_entity": "Дополнительный бинарный датчик для дверцы машины (вкл. = открыто, выкл. = закрыто). Когда дверца откроется во время активного цикла, WashData подтвердит, что цикл был намеренно приостановлен. Если после завершения цикла дверца все еще закрыта, состояние меняется на «Очистка», пока дверца не откроется. Примечание. Закрытие двери НЕ приводит к автоматическому возобновлению приостановленного цикла - возобновление необходимо запускать вручную с помощью кнопки возобновления цикла или службы.", + "pause_cuts_power": "При приостановке с помощью кнопки или службы «Пауза цикла» также отключите настроенный объект переключателя. Вступает в силу только в том случае, если для этого устройства настроен объект коммутатора.", + "switch_entity": "Объект-переключатель для переключения при приостановке или возобновлении. Требуется, если включена функция «Отключение питания при паузе».", + "notify_unload_delay_minutes": "Отправьте уведомление через канал уведомления о завершении после того, как цикл завершится и дверь не будет открыта в течение указанного количества минут (требуется датчик двери). Установите значение 0, чтобы отключить. По умолчанию: 60 мин." + } + }, + "pump_section": { + "name": "Монитор насоса", + "description": "Только для устройств типа насос. Вызывает событие, если цикл насоса длится дольше этого времени.", + "data": { + "pump_stuck_duration": "Порог предупреждения о застревании насоса (s) (только насос)" + }, + "data_description": { + "pump_stuck_duration": "Если по прошествии этого количества секунд цикл помпы все еще продолжается, событие ha_washdata_pump_stuck запускается один раз. Используйте это для обнаружения заклинившего двигателя (обычное время работы водоотливного насоса составляет менее 60 с). По умолчанию: 1800 с (30 мин). Только тип насосного устройства." + } + }, + "device_link_section": { + "name": "Ссылка на устройство", + "description": "При необходимости свяжите это устройство WashData с существующим устройством Home Assistant (например, интеллектуальной розеткой или самим устройством). Устройство WashData затем отображается как «Подключенное через» это устройство. Оставьте пустым, чтобы сохранить WashData как автономное устройство.", + "data": { + "linked_device": "Связанное устройство" + }, + "data_description": { + "linked_device": "Выберите устройство, к которому нужно подключить WashData. Это добавляет ссылку «Подключено через» в реестр устройства; WashData хранит собственную карточку устройства и объекты. Снимите выделение, чтобы удалить ссылку." + } + } + } + }, + "diagnostics": { + "title": "Диагностика и обслуживание", + "description": "Выполняйте действия по обслуживанию, такие как объединение фрагментированных циклов или перенос сохранённых данных.\n\n**Использование хранилища**\n\n- Размер файла: {file_size_kb} КБ\n- Циклы: {cycle_count}\n- Профили: {profile_count}\n- Трассировки отладки: {debug_count}", + "menu_options": { + "reprocess_history": "Обслуживание: повторная обработка и оптимизация данных", + "clear_debug_data": "Очистить данные отладки (освободить место)", + "wipe_history": "Удалить ВСЕ данные на этом устройстве (необратимо)", + "export_import": "Экспорт/импорт JSON с настройками (копировать/вставить)", + "menu_back": "← Назад" + } + }, + "clear_debug_data": { + "title": "Очистить данные отладки", + "description": "Вы уверены, что хотите удалить все сохранённые трассировки отладки? Это освободит место, но удалит подробную информацию о классификации из прошлых циклов." + }, + "manage_cycles": { + "title": "Управление циклами", + "description": "Последние циклы:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Автоматическая маркировка старых циклов", + "select_cycle_to_label": "Маркировка конкретного цикла", + "select_cycle_to_delete": "Удалить цикл", + "interactive_editor": "Интерактивный редактор слияния/разделения", + "trim_cycle_select": "Обрезать данные цикла", + "menu_back": "← Назад" + } + }, + "manage_cycles_empty": { + "title": "Управление циклами", + "description": "Циклы пока не зарегистрированы.", + "menu_options": { + "auto_label_cycles": "Автоматическая маркировка старых циклов", + "menu_back": "← Назад" + } + }, + "manage_profiles": { + "title": "Управление профилями", + "description": "Сводка профилей:\n{profile_summary}", + "menu_options": { + "create_profile": "Создать новый профиль", + "edit_profile": "Редактировать/переименовать профиль", + "delete_profile_select": "Удалить профиль", + "profile_stats": "Статистика профиля", + "cleanup_profile": "Очистка истории - график и удаление", + "assign_profile_phases_select": "Назначить диапазоны фаз", + "menu_back": "← Назад" + } + }, + "manage_profiles_empty": { + "title": "Управление профилями", + "description": "Профили ещё не созданы.", + "menu_options": { + "create_profile": "Создать новый профиль", + "menu_back": "← Назад" + } + }, + "manage_phase_catalog": { + "title": "Управление каталогом фаз", + "description": "Каталог фаз (по умолчанию для этого устройства + пользовательские фазы):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Создать новую фазу", + "phase_catalog_edit_select": "Редактировать фазу", + "phase_catalog_delete": "Удалить фазу", + "menu_back": "← Назад" + } + }, + "phase_catalog_create": { + "title": "Создать пользовательскую фазу", + "description": "Создайте собственное имя и описание фазы и выберите тип устройства, к которому оно применяется.", + "data": { + "target_device_type": "Тип целевого устройства", + "phase_name": "Название фазы", + "phase_description": "Описание фазы" + } + }, + "phase_catalog_edit_select": { + "title": "Изменить пользовательскую фазу", + "description": "Выберите пользовательскую фазу для редактирования.", + "data": { + "phase_name": "Пользовательская фаза" + } + }, + "phase_catalog_edit": { + "title": "Изменить пользовательскую фазу", + "description": "Редактирование фазы: {phase_name}", + "data": { + "phase_name": "Название фазы", + "phase_description": "Описание фазы" + } + }, + "phase_catalog_delete": { + "title": "Удалить пользовательскую фазу", + "description": "Выберите пользовательскую фазу для удаления. Все назначенные диапазоны, использующие эту фазу, будут удалены.", + "data": { + "phase_name": "Пользовательская фаза" + } + }, + "assign_profile_phases": { + "title": "Назначить диапазоны фаз", + "description": "Предварительный просмотр фаз профиля: **{profile_name}**\n\n{timeline_svg}\n\n**Текущие диапазоны:**\n{current_ranges}\n\nИспользуйте действия ниже, чтобы добавлять, редактировать, удалять или сохранять диапазоны.", + "menu_options": { + "assign_profile_phases_add": "Добавить диапазон фаз", + "assign_profile_phases_edit_select": "Редактировать диапазон фаз", + "assign_profile_phases_delete": "Удалить диапазон фаз", + "phase_ranges_clear": "Очистить все диапазоны", + "assign_profile_phases_auto_detect": "Автоматическое определение фаз", + "phase_ranges_save": "Сохранить и вернуться", + "menu_back": "← Назад" + } + }, + "assign_profile_phases_add": { + "title": "Добавить диапазон фаз", + "description": "Добавьте один диапазон фаз в текущий черновик.", + "data": { + "phase_name": "Фаза", + "start_min": "Начальная минута", + "end_min": "Конечная минута" + } + }, + "assign_profile_phases_edit_select": { + "title": "Редактировать диапазон фаз", + "description": "Выберите диапазон для редактирования.", + "data": { + "range_index": "Диапазон фаз" + } + }, + "assign_profile_phases_edit": { + "title": "Редактировать диапазон фаз", + "description": "Обновите выбранный диапазон фаз.", + "data": { + "phase_name": "Фаза", + "start_min": "Начальная минута", + "end_min": "Конечная минута" + } + }, + "assign_profile_phases_delete": { + "title": "Удалить диапазон фаз", + "description": "Выберите диапазон для удаления из черновика.", + "data": { + "range_index": "Диапазон фаз" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Автоматически определённые фазы", + "description": "Профиль: **{profile_name}**\n\n{timeline_svg}\n\n**Автоматически обнаружено фаз: {detected_count}.** Выберите действие ниже.", + "data": { + "action": "Действие" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Назвать обнаруженные фазы", + "description": "Профиль: **{profile_name}**\n\n**Обнаружено {detected_count} фаз:**\n{ranges_summary}\n\nПрисвойте имя каждой фазе." + }, + "assign_profile_phases_select": { + "title": "Назначить диапазоны фаз", + "description": "Выберите профиль для настройки диапазонов фаз.", + "data": { + "profile": "Профиль" + } + }, + "profile_stats": { + "title": "Статистика профиля", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Создать новый профиль", + "description": "Создайте новый профиль вручную или на основе прошлого цикла.\n\nПримеры названий профилей: «Деликатное», «Интенсивное», «Быстрая стирка».", + "data": { + "profile_name": "Имя профиля", + "reference_cycle": "Эталонный цикл (необязательно)", + "manual_duration": "Ручная продолжительность (минуты)" + } + }, + "edit_profile": { + "title": "Редактировать профиль", + "description": "Выберите профиль для переименования", + "data": { + "profile": "Профиль" + } + }, + "rename_profile": { + "title": "Редактировать данные профиля", + "description": "Текущий профиль: {current_name}", + "data": { + "new_name": "Имя профиля", + "manual_duration": "Ручная продолжительность (минуты)" + } + }, + "delete_profile_select": { + "title": "Удалить профиль", + "description": "Выберите профиль для удаления", + "data": { + "profile": "Профиль" + } + }, + "delete_profile_confirm": { + "title": "Подтвердить удаление профиля", + "description": "⚠️ Профиль будет удалён без возможности восстановления: {profile_name}.", + "data": { + "unlabel_cycles": "Удалить метку из циклов, использующих этот профиль" + } + }, + "auto_label_cycles": { + "title": "Автоматическая маркировка старых циклов", + "description": "Всего найдено {total_count} циклов. Профили: {profiles}", + "data": { + "confidence_threshold": "Минимальная уверенность (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Выберите цикл для маркировки", + "description": "✓ = завершено, ⚠ = возобновлено, ✗ = прервано", + "data": { + "cycle_id": "Цикл" + } + }, + "select_cycle_to_delete": { + "title": "Удалить цикл", + "description": "⚠️ Выбранный цикл будет удалён без возможности восстановления.", + "data": { + "cycle_id": "Цикл для удаления" + } + }, + "label_cycle": { + "title": "Отметить цикл", + "description": "{cycle_info}", + "data": { + "profile_name": "Профиль", + "new_profile_name": "Имя нового профиля (если создаётся)" + } + }, + "post_process": { + "title": "Обработка истории (объединить/разделить)", + "description": "Очистите историю циклов, объединив фрагментированные циклы и разделив неправильно объединенные циклы во временном окне. Введите количество часов, на которое нужно оглянуться назад (для всех используйте 999999).", + "data": { + "time_range": "Окно ретроспективного анализа (часы)", + "gap_seconds": "Порог для объединения/разделения (секунды)" + }, + "data_description": { + "time_range": "Количество часов для сканирования фрагментированных циклов для объединения. Используйте 999999 для обработки всех исторических данных.", + "gap_seconds": "Порог для принятия решения о разделении или слиянии. Промежутки КОРОЧЕ этого объединяются. Промежутки ДЛИННЕЕ этого разделяются. Уменьшите это значение, чтобы принудительно разделить неправильно объединённый цикл." + } + }, + "cleanup_profile": { + "title": "Очистка истории - выбор профиля", + "description": "Выберите профиль для визуализации. Это создаст график, показывающий все прошлые циклы для этого профиля, чтобы помочь выявить выбросы.", + "data": { + "profile": "Профиль" + } + }, + "cleanup_select": { + "title": "Очистка истории - график и удаление", + "description": "![График]({graph_url})\n\n**Визуализация {profile_name}**\n\nНа графике отдельные циклы показаны цветными линиями. Определите выбросы (линии, далеко от группы) и найдите соответствие по цвету в списке ниже, чтобы удалить их.\n\n**Выберите циклы для БЕЗВОЗВРАТНОГО удаления:**", + "data": { + "cycles_to_delete": "Циклы для удаления (проверьте соответствие цветов)" + } + }, + "interactive_editor": { + "title": "Интерактивный редактор слияния/разделения", + "description": "Выберите ручное действие для истории цикла.", + "menu_options": { + "editor_split": "Разделить цикл (найти промежутки)", + "editor_merge": "Объединить циклы (сшить фрагменты)", + "editor_delete": "Удалить цикл(ы)", + "menu_back": "← Назад" + } + }, + "editor_select": { + "title": "Выбрать циклы", + "description": "Выберите цикл(ы) для обработки.\n\n{info_text}", + "data": { + "selected_cycles": "Циклы" + } + }, + "editor_configure": { + "title": "Настроить и применить", + "description": "{preview_md}", + "data": { + "confirm_action": "Действие", + "merged_profile": "Результирующий профиль", + "new_profile_name": "Новое имя профиля", + "confirm_commit": "Да, я подтверждаю это действие", + "segment_0_profile": "Профиль сегмента 1", + "segment_1_profile": "Профиль сегмента 2", + "segment_2_profile": "Профиль сегмента 3", + "segment_3_profile": "Профиль сегмента 4", + "segment_4_profile": "Профиль сегмента 5", + "segment_5_profile": "Профиль сегмента 6", + "segment_6_profile": "Профиль сегмента 7", + "segment_7_profile": "Профиль сегмента 8", + "segment_8_profile": "Профиль сегмента 9", + "segment_9_profile": "Профиль сегмента 10" + } + }, + "editor_split_params": { + "title": "Параметры разделения", + "description": "Настройте чувствительность для разделения этого цикла. Любой простой длиннее указанного вызовет разделение.", + "data": { + "split_mode": "Метод разделения" + } + }, + "editor_split_auto_params": { + "title": "Настройка авторазделения", + "description": "Настройте чувствительность разделения этого цикла. Любой интервал простоя дольше этого значения приведёт к разделению.", + "data": { + "min_gap_seconds": "Порог паузы для разделения (секунды)" + } + }, + "editor_split_manual_params": { + "title": "Метки времени для ручного разделения", + "description": "{preview_md}Окно цикла: **{cycle_start_wallclock} → {cycle_end_wallclock}** на {cycle_date}.\n\nВведите одну или несколько меток времени для разделения (`HH:MM` или `HH:MM:SS`), по одной на строку. Цикл будет разрезан по каждой метке времени — N меток времени дают N+1 сегментов.", + "data": { + "split_timestamps": "Временные метки разделения" + } + }, + "reprocess_history": { + "title": "Обслуживание: повторная обработка и оптимизация данных", + "description": "Выполняет глубокую очистку исторических данных:\n1. Обрезает показания при нулевой мощности (тишина).\n2. Исправляет время/продолжительность цикла.\n3. Пересчитывает все статистические модели (огибающие).\n\n**Запустите это, чтобы устранить проблемы с данными или применить новые алгоритмы.**" + }, + "wipe_history": { + "title": "Очистить историю (только для тестирования)", + "description": "⚠️ Это приведёт к безвозвратному удалению ВСЕХ сохранённых циклов и профилей для этого устройства. Отменить это действие невозможно!" + }, + "export_import": { + "title": "Экспорт/импорт JSON", + "description": "Скопируйте/вставьте циклы, профили и настройки для этого устройства. Экспортируйте ниже или вставьте JSON для импорта.", + "data": { + "mode": "Действие", + "json_payload": "Полная конфигурация JSON" + }, + "data_description": { + "mode": "Выберите «Экспорт», чтобы скопировать текущие данные и настройки, или «Импорт», чтобы вставить экспортированные данные с другого устройства WashData.", + "json_payload": "Для экспорта: скопируйте этот JSON (включает циклы, профили и все настроенные параметры). Для импорта: вставьте JSON, экспортированный из WashData." + } + }, + "record_cycle": { + "title": "Запись цикла", + "description": "Запишите цикл вручную, чтобы создать чистый профиль без помех.\n\nСтатус: **{status}**\nПродолжительность: {duration} с\nОтсчёты: {samples}", + "menu_options": { + "record_refresh": "Обновить статус", + "record_stop": "Остановить запись (сохранить и обработать)", + "record_start": "Начать новую запись", + "record_process": "Обработать последнюю запись (обрезать и сохранить)", + "record_discard": "Отменить последнюю запись", + "menu_back": "← Назад" + } + }, + "record_process": { + "title": "Обработка записи", + "description": "![График]({graph_url})\n\n**На графике показана необработанная запись.** Синий = сохранить, красный = предлагаемая обрезка.\n*График статический и не обновляется при изменении формы.*\n\nЗапись остановлена. Точки обрезки выровнены по обнаруженной частоте дискретизации (~{sampling_rate} с).\n\nНеобработанная продолжительность: {duration} с\nОтсчёты: {samples}", + "data": { + "head_trim": "Начало обрезки (секунды)", + "tail_trim": "Конец обрезки (секунды)", + "save_mode": "Место сохранения", + "profile_name": "Имя профиля" + } + }, + "learning_feedbacks": { + "title": "Отзывы об обучении", + "description": "Отзывов в очереди: {count}\n\n{pending_table}\n\nВыберите запрос на проверку цикла для обработки.", + "menu_options": { + "learning_feedbacks_pick": "Проверить ожидающий отзыв", + "learning_feedbacks_dismiss_all": "Отклонить все ожидающие отзывы", + "menu_back": "← Назад" + } + }, + "learning_feedbacks_pick": { + "title": "Выберите отзыв для проверки", + "description": "Выберите запрос на проверку цикла, который нужно открыть.", + "data": { + "selected_feedback": "Выберите цикл для проверки" + } + }, + "learning_feedbacks_empty": { + "title": "Отзывы об обучении", + "description": "Незавершённых запросов на обратную связь не найдено.", + "menu_options": { + "menu_back": "← Назад" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Отклонить все ожидающие отзывы", + "description": "Вы собираетесь отклонить **{count}** ожидающих запросов на отзыв. Отклонённые циклы останутся в вашей истории, но не будут давать новый сигнал обучения. Это действие нельзя отменить.\n\n✅ Установите флажок ниже и нажмите **Отправить**, чтобы отклонить всё.\n❌ Оставьте флажок снятым и нажмите **Отправить**, чтобы отменить действие и вернуться назад.", + "data": { + "confirm_dismiss_all": "Подтвердить: отклонить все ожидающие запросы на отзыв" + } + }, + "resolve_feedback": { + "title": "Обработка отзыва", + "description": "{comparison_img}{candidates_table}\n**Обнаруженный профиль**: {detected_profile} ({confidence_pct}%)\n**Расчётная продолжительность**: {est_duration_min} мин\n**Фактическая продолжительность**: {act_duration_min} мин\n\n__Выберите действие ниже:__", + "data": { + "action": "Что вы хотите сделать?", + "corrected_profile": "Правильная программа (если исправляете)", + "corrected_duration": "Правильная продолжительность в секундах (если исправляете)" + } + }, + "trim_cycle_select": { + "title": "Обрезка цикла - выбор цикла", + "description": "Выберите цикл для обрезки. ✓ = завершено, ⚠ = возобновлено, ✗ = прервано", + "data": { + "cycle_id": "Цикл" + } + }, + "trim_cycle": { + "title": "Обрезка цикла", + "description": "Текущее окно: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Действие" + } + }, + "trim_cycle_start": { + "title": "Установить начало обрезки", + "description": "Окно цикла: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТекущее начало: **{current_wallclock}** (+{current_offset_min} мин от начала цикла)\n\nВыберите новое время начала, используя инструмент выбора часов ниже.", + "data": { + "trim_start_time": "Новое время начала", + "trim_start_min": "Новый старт (минуты от начала цикла)" + } + }, + "trim_cycle_end": { + "title": "Установить конец обрезки", + "description": "Окно цикла: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nТекущее окончание: **{current_wallclock}** (+{current_offset_min} мин от начала цикла)\n\nВыберите новое время окончания, используя инструмент выбора часов ниже.", + "data": { + "trim_end_time": "Новое время окончания", + "trim_end_min": "Новый конец (минуты от начала цикла)" + } + } + }, + "error": { + "import_failed": "Импорт не удался. Вставьте действительный JSON экспорта WashData.", + "select_exactly_one": "Выберите ровно один цикл для этого действия.", + "select_at_least_one": "Выберите хотя бы один цикл для этого действия.", + "select_at_least_two": "Выберите хотя бы два цикла для этого действия.", + "profile_exists": "Профиль с таким именем уже существует", + "rename_failed": "Не удалось переименовать профиль. Возможно, профиль не существует или новое имя уже занято.", + "assignment_failed": "Не удалось назначить профиль циклу.", + "duplicate_phase": "Фаза с таким названием уже существует.", + "phase_not_found": "Выбранная фаза не найдена.", + "invalid_phase_name": "Название фазы не может быть пустым.", + "phase_name_too_long": "Название фазы слишком длинное.", + "invalid_phase_range": "Недопустимый диапазон фаз. Конец должен быть больше начала.", + "invalid_phase_timestamp": "Недопустимая метка времени. Используйте формат ГГГГ-ММ-ДД ЧЧ:ММ или ЧЧ:ММ.", + "overlapping_phase_ranges": "Диапазоны фаз перекрываются. Отрегулируйте диапазоны и повторите попытку.", + "incomplete_phase_row": "Каждая строка фазы должна включать фазу плюс полные значения начала/конца для выбранного режима.", + "timestamp_mode_cycle_required": "Пожалуйста, выберите исходный цикл при использовании режима метки времени.", + "no_phase_ranges": "Для этого действия диапазоны фаз пока недоступны.", + "feedback_notification_title": "WashData: проверка цикла ({device})", + "feedback_notification_message": "**Устройство**: {device}\n**Программа**: {program} (уверенность {confidence}%)\n**Время**: {time}\n\nWashData нуждается в вашей помощи для проверки обнаруженного цикла.\n\nОткройте **Настройки > Устройства и службы > WashData > Настройка > Отзывы об обучении**, чтобы подтвердить или исправить этот результат.", + "suggestions_ready_notification_title": "WashData: предложенные настройки готовы ({device})", + "suggestions_ready_notification_message": "Датчик **Предложенные настройки** теперь сообщает о **{count}** действенных рекомендациях.\n\nЧтобы просмотреть и применить их: **Настройки > Устройства и службы > WashData > Настройка > Расширенные настройки > Применить предложенные значения**.\n\nПредложения необязательны и отображаются для просмотра перед сохранением.", + "trim_range_invalid": "Начало должно быть раньше конца. Пожалуйста, откорректируйте точки обрезки.", + "trim_failed": "Обрезать не удалось. Возможно, цикл больше не существует или окно пусто.", + "invalid_split_timestamp": "Временная метка недействительна или находится вне окна цикла. Используйте HH:MM или HH:MM:SS в пределах окна цикла.", + "no_split_segments_found": "По этим временным меткам не удалось получить корректные сегменты. Убедитесь, что каждая временная метка находится в пределах окна цикла, а длина сегментов составляет не менее 1 минуты.", + "auto_tune_suggestion": "{device_type} {device_title} обнаружил ложные циклы. Рекомендуемое изменение min_power: {current_min}Вт -> {new_min}Вт (не применяется автоматически).", + "auto_tune_title": "Автоматическая настройка WashData", + "auto_tune_fallback": "{device_type} {device_title} обнаружил ложные циклы. Рекомендуемое изменение min_power: {current_min}Вт -> {new_min}Вт (не применяется автоматически)." + }, + "abort": { + "no_cycles_found": "Циклы не найдены", + "no_split_segments_found": "В выбранном цикле не найдено сегментов, которые можно разделить.", + "cycle_not_found": "Не удалось загрузить выбранный цикл.", + "no_power_data": "Для выбранного цикла данные трассировки мощности отсутствуют.", + "no_profiles_found": "Профили не найдены. Сначала создайте профиль.", + "no_profiles_for_matching": "Профили для сопоставления недоступны. Сначала создайте профили.", + "no_unlabeled_cycles": "Все циклы уже отмечены", + "no_suggestions": "Предложенных значений пока нет. Проведите несколько циклов и повторите попытку.", + "no_predictions": "Нет прогнозов", + "no_custom_phases": "Пользовательские фазы не найдены.", + "reprocess_success": "Успешно повторно обработано {count} циклов.", + "debug_data_cleared": "Данные отладки из {count} циклов успешно удалены." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Начало цикла", + "cycle_finish": "Окончание цикла", + "cycle_live": "Прогресс в реальном времени" + } + }, + "common_text": { + "options": { + "unlabeled": "(без маркировки)", + "create_new_profile": "Создать новый профиль", + "remove_label": "Удалить метку", + "no_reference_cycle": "(нет эталонного цикла)", + "all_device_types": "Все типы устройств", + "current_suffix": "(текущий)", + "editor_select_info": "Выберите 1 цикл для разделения или 2+ цикла для объединения.", + "editor_select_info_split": "Выберите 1 цикл для разделения.", + "editor_select_info_merge": "Выберите 2+ цикла для объединения.", + "editor_select_info_delete": "Выберите 1+ циклов для удаления.", + "no_cycles_recorded": "Циклы пока не зарегистрированы.", + "profile_summary_line": "- **{name}**: {count} циклов, в среднем {avg} мин", + "no_profiles_created": "Профили ещё не созданы.", + "phase_preview": "Предварительный просмотр фазы", + "phase_preview_no_curve": "Кривая среднего профиля пока недоступна. Запустите/маркируйте дополнительные циклы для этого профиля.", + "average_power_curve": "Кривая средней мощности", + "unit_min": "мин", + "profile_option_fmt": "{name} ({count} циклов, в среднем ~{duration} мин)", + "profile_option_short_fmt": "{name} ({count} циклов, ~{duration} мин)", + "deleted_cycles_from_profile": "Удалено {count} циклов из {profile}.", + "not_enough_data_graph": "Недостаточно данных для построения графика.", + "no_profiles_found": "Профили не найдены.", + "cycle_info_fmt": "Цикл: {start}, {duration} мин, метка: {label}", + "top_candidates_header": "### Лучшие кандидаты", + "tbl_profile": "Профиль", + "tbl_confidence": "Уверенность", + "tbl_correlation": "Корреляция", + "tbl_duration_match": "Совпадение по длительности", + "detected_profile": "Обнаруженный профиль", + "estimated_duration": "Расчётная продолжительность", + "actual_duration": "Фактическая продолжительность", + "choose_action": "__Выберите действие ниже:__", + "tbl_count": "Кол-во", + "tbl_avg": "Среднее", + "tbl_min": "Мин.", + "tbl_max": "Макс.", + "tbl_energy_avg": "Энергия (средн.)", + "tbl_energy_total": "Энергия (итого)", + "tbl_consistency": "Конс.", + "tbl_last_run": "Последний запуск", + "graph_legend_title": "Легенда графика", + "graph_legend_body": "Синяя полоса представляет наблюдаемый минимальный и максимальный диапазон энергопотребления. Линия показывает кривую средней мощности.", + "recording_preview": "Предварительный просмотр записи", + "trim_start": "Начало обрезки", + "trim_end": "Конец обрезки", + "split_preview_title": "Предварительный просмотр разделения", + "split_preview_found_fmt": "Найдено {count} сегментов.", + "split_preview_confirm_fmt": "Нажмите «Подтвердить», чтобы разделить этот цикл на {count} отдельных цикла.", + "merge_preview_title": "Предварительный просмотр объединения", + "merge_preview_joining_fmt": "Объединение {count} циклов. Пробелы будут заполнены показаниями 0 Вт.", + "editor_delete_preview_title": "Предпросмотр удаления", + "editor_delete_preview_intro": "Выбранные циклы будут удалены безвозвратно:", + "editor_delete_preview_confirm": "Нажмите Подтвердить, чтобы навсегда удалить эти записи циклов.", + "no_power_preview": "*Данные о мощности недоступны для предварительного просмотра.*", + "profile_comparison": "Сравнение профилей", + "actual_cycle_label": "Этот цикл (фактический)", + "storage_usage_header": "Использование хранилища", + "storage_file_size": "Размер файла", + "storage_cycles": "Циклы", + "storage_profiles": "Профили", + "storage_debug_traces": "Трассировки отладки", + "overview_suffix": "Обзор", + "phase_preview_for_profile": "Предварительный просмотр фазы для профиля", + "current_ranges_header": "Текущие диапазоны", + "assign_phases_help": "Используйте действия ниже, чтобы добавлять, редактировать, удалять или сохранять диапазоны.", + "profile_summary_header": "Сводка профилей", + "recent_cycles_header": "Последние циклы", + "trim_cycle_preview_title": "Предварительный просмотр обрезки цикла", + "trim_cycle_preview_no_data": "Для этого цикла данные о мощности отсутствуют.", + "trim_cycle_preview_kept_suffix": "сохраняется", + "table_program": "Программа", + "table_when": "Когда", + "table_length": "Длительность", + "table_match": "Совпадение", + "table_profile": "Профиль", + "table_cycles": "Циклы", + "table_avg_length": "Средн. длительность", + "table_last_run": "Последний запуск", + "table_avg_energy": "Средн. энергия", + "table_detected_program": "Обнаруженная программа", + "table_confidence": "Уверенность", + "table_reported": "Сообщено", + "phase_builtin_suffix": "(Встроенный)", + "phase_none_available": "Нет доступных фаз.", + "settings_deprecation_warning": "⚠️ **Устаревший тип устройства:** {device_type} планируется удалить в будущем выпуске. Конвейер сопоставления WashData не дает надежных результатов для этого класса устройств. Ваша интеграция продолжает работать в течение периода устаревания; Чтобы отключить это предупреждение, переключите **Тип устройства** ниже на один из поддерживаемых типов (стиральная машина, сушильная машина, комбинированная стирально-сушильная машина, посудомоечная машина, аэрофритюрница, хлебопечка или насос) или на **Другое (расширенное)**, если ваше устройство не соответствует ни одному из поддерживаемых типов. **Другое (расширенное)** намеренно предоставляет общие параметры по умолчанию, которые не настроены для какого-либо конкретного устройства, поэтому вам придется самостоятельно настраивать пороговые значения, тайм-ауты и соответствующие параметры; все существующие настройки сохраняются при переключении.", + "phase_other_device_types": "Другие типы устройств:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Разделить цикл (найти промежутки)", + "merge": "Объединить циклы (сшить фрагменты)", + "delete": "Удалить цикл(ы)" + } + }, + "split_mode": { + "options": { + "auto": "Автоматически находить паузы бездействия", + "manual": "Ручные временные метки" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Обслуживание: повторная обработка и оптимизация данных", + "clear_debug_data": "Очистить данные отладки (освободить место)", + "wipe_history": "Удалить ВСЕ данные на этом устройстве (необратимо)", + "export_import": "Экспорт/импорт JSON с настройками (копировать/вставить)" + } + }, + "export_import_mode": { + "options": { + "export": "Экспортировать все данные", + "import": "Импорт/объединение данных" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Автоматическая маркировка старых циклов", + "select_cycle_to_label": "Маркировка конкретного цикла", + "select_cycle_to_delete": "Удалить цикл", + "interactive_editor": "Интерактивный редактор слияния/разделения", + "trim_cycle": "Обрезать данные цикла" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Создать новый профиль", + "edit_profile": "Редактировать/переименовать профиль", + "delete_profile": "Удалить профиль", + "profile_stats": "Статистика профиля", + "cleanup_profile": "Очистка истории: график и удаление", + "assign_phases": "Назначить диапазоны фаз" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Создать новую фазу", + "edit_custom_phase": "Редактировать фазу", + "delete_custom_phase": "Удалить фазу" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Добавить диапазон фаз", + "edit_range": "Редактировать диапазон фаз", + "delete_range": "Удалить диапазон фаз", + "clear_ranges": "Очистить все диапазоны", + "auto_detect_ranges": "Автоматическое определение фаз", + "save_ranges": "Сохранить и вернуться" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Обновить статус", + "stop_recording": "Остановить запись (сохранить и обработать)", + "start_recording": "Начать новую запись", + "process_recording": "Обработать последнюю запись (обрезать и сохранить)", + "discard_recording": "Отменить последнюю запись" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Создать новый профиль", + "existing_profile": "Добавить в существующий профиль", + "discard": "Отменить запись" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Подтвердить - обнаружение правильное", + "correct": "Исправить - выбрать программу/продолжительность", + "ignore": "Игнорировать - цикл ложного срабатывания/шума", + "delete": "Удалить - удалить из истории" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Назвать и применить фазы", + "cancel": "Вернуться без изменений" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Установить начальную точку", + "set_end": "Установить конечную точку", + "reset": "Сбросить до полной продолжительности", + "apply": "Применить обрезку", + "cancel": "Отмена" + } + }, + "device_type": { + "options": { + "washing_machine": "Стиральная машина", + "dryer": "Сушилка", + "washer_dryer": "Комбинированная стиральная машина с сушкой", + "dishwasher": "Посудомоечная машина", + "coffee_machine": "Кофемашина (устарела)", + "ev": "Электромобиль (устарело)", + "air_fryer": "Воздушная фритюрница", + "heat_pump": "Тепловой насос (устарело)", + "bread_maker": "Хлебопечка", + "pump": "Насос / Отстойный насос", + "oven": "Духовка (устарела)", + "other": "Другое (продвинутый уровень)" + } + } + }, + "services": { + "label_cycle": { + "name": "Отметить цикл", + "description": "Назначьте существующий профиль предыдущему циклу или удалите метку.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины для маркировки." + }, + "cycle_id": { + "name": "Идентификатор цикла", + "description": "Идентификатор цикла, который необходимо пометить." + }, + "profile_name": { + "name": "Имя профиля", + "description": "Имя существующего профиля (создайте профили в меню «Управление профилями»). Оставьте пустым, чтобы удалить метку." + } + } + }, + "create_profile": { + "name": "Создать профиль", + "description": "Создайте новый профиль (автономный или на основе эталонного цикла).", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины." + }, + "profile_name": { + "name": "Имя профиля", + "description": "Имя нового профиля (например, «Интенсивный», «Деликатное»)." + }, + "reference_cycle_id": { + "name": "Идентификатор эталонного цикла", + "description": "Необязательный идентификатор цикла, на котором будет основан этот профиль." + } + } + }, + "delete_profile": { + "name": "Удалить профиль", + "description": "Удалите профиль и, при необходимости, снимите маркировку с циклов, использующих его.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины." + }, + "profile_name": { + "name": "Имя профиля", + "description": "Профиль, который нужно удалить." + }, + "unlabel_cycles": { + "name": "Снять маркировку с циклов", + "description": "Удалите метку профиля из циклов, использующих этот профиль." + } + } + }, + "auto_label_cycles": { + "name": "Автоматическая маркировка старых циклов", + "description": "Маркируйте немаркированные циклы задним числом, используя сопоставление профилей.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины." + }, + "confidence_threshold": { + "name": "Порог уверенности", + "description": "Минимальная уверенность в совпадении (0,50–0,95) для применения меток." + } + } + }, + "export_config": { + "name": "Экспортировать конфигурацию", + "description": "Экспортируйте профили, циклы и настройки этого устройства в файл JSON (для каждого устройства).", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины для экспорта." + }, + "path": { + "name": "Путь", + "description": "Необязательный абсолютный путь к файлу для записи (по умолчанию /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Импортировать конфигурацию", + "description": "Импортируйте профили, циклы и настройки для этого устройства из файла экспорта JSON (настройки могут быть перезаписаны).", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины, в которое нужно импортировать." + }, + "path": { + "name": "Путь", + "description": "Абсолютный путь к экспортному файлу JSON для импорта." + } + } + }, + "submit_cycle_feedback": { + "name": "Отправить отзыв о цикле", + "description": "Подтвердите или исправьте автоматически определённую программу после завершения цикла. Укажите либо `entry_id` (дополнительно), либо `device_id` (рекомендуется).", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство WashData (рекомендуется вместо entry_id)." + }, + "entry_id": { + "name": "Идентификатор записи", + "description": "Идентификатор записи конфигурации для устройства (альтернатива device_id)." + }, + "cycle_id": { + "name": "Идентификатор цикла", + "description": "Идентификатор цикла, отображаемый в уведомлении/журналах обратной связи." + }, + "user_confirmed": { + "name": "Подтвердить обнаруженную программу", + "description": "Установите true, если обнаруженная программа верна." + }, + "corrected_profile": { + "name": "Исправленный профиль", + "description": "Если не подтверждено, укажите правильное имя профиля/программы." + }, + "corrected_duration": { + "name": "Исправленная продолжительность (секунды)", + "description": "Необязательная исправленная продолжительность в секундах." + }, + "notes": { + "name": "Примечания", + "description": "Дополнительные примечания об этом цикле." + } + } + }, + "record_start": { + "name": "Начать запись цикла", + "description": "Запустите вручную запись чистого цикла (обходит всю логику сопоставления).", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины для записи." + } + } + }, + "record_stop": { + "name": "Остановить запись цикла", + "description": "Остановите запись вручную.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство стиральной машины для остановки записи." + } + } + }, + "trim_cycle": { + "name": "Обрезать цикл", + "description": "Обрезать данные мощности прошлого цикла до определённого временного окна.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство WashData." + }, + "cycle_id": { + "name": "Идентификатор цикла", + "description": "Идентификатор цикла для обрезки." + }, + "trim_start_s": { + "name": "Начало обрезки (секунды)", + "description": "Сохранять данные начиная с этого смещения в секундах. По умолчанию 0." + }, + "trim_end_s": { + "name": "Конец обрезки (секунды)", + "description": "Сохранять данные до этого смещения в секундах. По умолчанию = полная продолжительность." + } + } + }, + "pause_cycle": { + "name": "Пауза цикла", + "description": "Приостановите активный цикл устройства WashData.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство WashData, которое необходимо приостановить." + } + } + }, + "resume_cycle": { + "name": "Возобновить цикл", + "description": "Возобновите приостановленный цикл для устройства WashData.", + "fields": { + "device_id": { + "name": "Устройство", + "description": "Устройство WashData для возобновления работы." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "В работе" + }, + "match_ambiguity": { + "name": "Неоднозначность сопоставления" + } + }, + "sensor": { + "washer_state": { + "name": "Состояние", + "state": { + "off": "Выключено", + "idle": "Ожидание", + "starting": "Запуск", + "running": "Работает", + "paused": "Пауза", + "user_paused": "Приостановлено пользователем", + "ending": "Завершение", + "finished": "Завершено", + "anti_wrinkle": "Защита от морщин", + "delay_wait": "Ожидание начала", + "interrupted": "Прервано", + "force_stopped": "Принудительная остановка", + "rinse": "Полоскание", + "unknown": "Неизвестно", + "clean": "Чистый" + } + }, + "washer_program": { + "name": "Программа" + }, + "time_remaining": { + "name": "Оставшееся время" + }, + "total_duration": { + "name": "Общая продолжительность" + }, + "cycle_progress": { + "name": "Прогресс" + }, + "current_power": { + "name": "Текущая мощность" + }, + "elapsed_time": { + "name": "Прошедшее время" + }, + "debug_info": { + "name": "Отладочная информация" + }, + "match_confidence": { + "name": "Уверенность в сопоставлении" + }, + "top_candidates": { + "name": "Лучшие кандидаты", + "state": { + "none": "Нет" + } + }, + "current_phase": { + "name": "Текущая фаза" + }, + "wash_phase": { + "name": "Фаза" + }, + "profile_cycle_count": { + "name": "Количество циклов профиля {profile_name}", + "unit_of_measurement": "циклов" + }, + "suggestions": { + "name": "Доступные предложенные настройки" + }, + "pump_runs_today": { + "name": "Работа насоса (последние 24 часа)" + }, + "cycle_count": { + "name": "Количество циклов" + } + }, + "select": { + "program_select": { + "name": "Программа цикла", + "state": { + "auto_detect": "Автоопределение" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Принудительное завершение цикла" + }, + "pause_cycle": { + "name": "Пауза цикла" + }, + "resume_cycle": { + "name": "Возобновить цикл" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Требуется действительный идентификатор устройства." + }, + "cycle_id_required": { + "message": "Требуется действительный Cycl_id." + }, + "profile_name_required": { + "message": "Требуется действительное имя_профиля." + }, + "device_not_found": { + "message": "Устройство не найдено." + }, + "no_config_entry": { + "message": "Для устройства не найдена запись конфигурации." + }, + "integration_not_loaded": { + "message": "Интеграция не загружена для этого устройства." + }, + "cycle_not_found_or_no_power": { + "message": "Цикл не найден или не имеет данных о мощности." + }, + "trim_failed_empty_window": { + "message": "Не удалось выполнить обрезку - цикл не найден, данные о мощности отсутствуют или результирующее окно пусто." + }, + "trim_invalid_range": { + "message": "trim_end_s должно быть больше, чем trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/sk.json b/custom_components/ha_washdata/translations/sk.json new file mode 100644 index 0000000..ce4dabd --- /dev/null +++ b/custom_components/ha_washdata/translations/sk.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Nastavenie WashData", + "description": "Nakonfigurujte si práčku alebo iný spotrebič.\n\nVyžaduje sa snímač napájania.\n\n**Ďalej sa vás spýta, či si chcete vytvoriť svoj prvý profil.**", + "data": { + "name": "Názov zariadenia", + "device_type": "Typ zariadenia", + "power_sensor": "Snímač výkonu", + "min_power": "Minimálny prah výkonu (W)" + }, + "data_description": { + "name": "Priateľský názov pre toto zariadenie (napr. „Práčka“, „Umývačka riadu“).", + "device_type": "O aký typ spotrebiča ide? Pomáha prispôsobiť detekciu a označovanie.", + "power_sensor": "Entita senzora, ktorá hlási spotrebu energie v reálnom čase (vo wattoch) z vašej inteligentnej zásuvky.", + "min_power": "Údaje o príkone nad touto hranicou (vo wattoch) indikujú, že spotrebič beží. Začnite s 2 W pre väčšinu zariadení." + } + }, + "first_profile": { + "title": "Vytvorte prvý profil", + "description": "**Cyklus** je úplný chod vášho spotrebiča (napr. náplň bielizne). **Profil** zoskupuje tieto cykly podľa typu (napr. „Bavlna”, „Rýchle pranie”). Vytvorenie profilu teraz pomôže integrácii okamžite odhadnúť trvanie.\n\nAk sa tento profil nezistí automaticky, môžete ho manuálne vybrať v ovládacích prvkoch zariadenia.\n\n**Ak chcete tento krok preskočiť, ponechajte pole Názov profilu prázdne.**", + "data": { + "profile_name": "Názov profilu (voliteľné)", + "manual_duration": "Odhadované trvanie (minúty)" + } + } + }, + "error": { + "cannot_connect": "Nepodarilo sa pripojiť", + "invalid_auth": "Neplatné overenie", + "unknown": "Neočakávaná chyba" + }, + "abort": { + "already_configured": "Zariadenie je už nakonfigurované" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Pozrite si spätnú väzbu k učeniu", + "settings": "Nastavenia", + "notifications": "Upozornenia", + "manage_cycles": "Spravovať cykly", + "manage_profiles": "Spravovať profily", + "manage_phase_catalog": "Spravovať katalóg fáz", + "record_cycle": "Cyklus nahrávania (manuálne)", + "diagnostics": "Diagnostika a údržba" + } + }, + "apply_suggestions": { + "title": "Skopírovať navrhované hodnoty", + "description": "Týmto sa skopírujú aktuálne navrhované hodnoty do uložených nastavení. Toto je jednorazová akcia a nepovoľuje automatické prepisovanie.\n\nOdporúčané hodnoty na skopírovanie:\n{suggested}", + "data": { + "confirm": "Potvrdiť akciu kopírovania" + } + }, + "apply_suggestions_confirm": { + "title": "Skontrolovať navrhované zmeny", + "description": "WashData nájdené **{pending_count}** navrhované hodnoty, ktoré sa majú použiť.\n\nZmeny:\n{changes}\n\n✅ Začiarknutím políčka nižšie a kliknutím na **Odoslať** tieto zmeny ihneď použijete a uložíte.\n❌ Nechajte nezačiarknuté a kliknutím na **Odoslať** zrušte a vráťte sa späť.", + "data": { + "confirm_apply_suggestions": "Použiť a uložiť tieto zmeny" + } + }, + "settings": { + "title": "Nastavenia", + "description": "{deprecation_warning}Vylaďte prahy detekcie, učenie a pokročilé správanie. Predvolené nastavenia fungujú dobre pre väčšinu konfigurácií.\n\nAktuálne dostupné navrhované nastavenia: {suggestions_count}.\nAk je toto číslo väčšie ako 0, otvorte Rozšírené nastavenia a použite Použiť navrhované hodnoty na prehliadnutie odporúčaní.", + "data": { + "apply_suggestions": "Použiť navrhované hodnoty", + "device_type": "Typ zariadenia", + "power_sensor": "Entita snímača výkonu", + "min_power": "Minimálny výkon (W)", + "off_delay": "Oneskorenie konca cyklu (s)", + "notify_actions": "Akcie upozornení", + "notify_people": "Oneskorené doručenie, kým títo ľudia nebudú doma", + "notify_only_when_home": "Oneskorenie upozornení, kým nebude vybraná osoba doma", + "notify_fire_events": "Spúšťať udalosti automatizácie", + "notify_start_services": "Začiatok cyklu - ciele upozornení", + "notify_finish_services": "Dokončenie cyklu - ciele upozornenia", + "notify_live_services": "Aktuálny pokrok - ciele upozornení", + "notify_before_end_minutes": "Upozornenie pred dokončením (minúty pred koncom)", + "notify_title": "Názov upozornenia", + "notify_icon": "Ikona upozornenia", + "notify_start_message": "Formát správy pri spustení", + "notify_finish_message": "Formát správy po dokončení", + "notify_pre_complete_message": "Formát správy pred dokončením", + "show_advanced": "Upraviť rozšírené nastavenia (ďalší krok)" + }, + "data_description": { + "apply_suggestions": "Začiarknite toto políčko, ak chcete do formulára skopírovať hodnoty navrhnuté učiacim sa algoritmom. Pred uložením skontrolujte.\n\nOdporúčané (z učenia):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Vyberte typ spotrebiča (Práčka, Sušička, Umývačka riadu, Kávovar, EV). Táto značka sa ukladá pri každom cykle pre lepšiu organizáciu.", + "power_sensor": "Entita senzora hlási výkon v reálnom čase (vo wattoch). Toto môžete kedykoľvek zmeniť bez straty historických údajov – užitočné, ak vymeníte inteligentnú zástrčku.", + "min_power": "Údaje o príkone nad touto hranicou (vo wattoch) indikujú, že spotrebič beží. Začnite s 2 W pre väčšinu zariadení.", + "off_delay": "Vyhladený výkon musí zostať pod prahom zastavenia, aby sa označilo dokončenie. Predvolená hodnota: 120 s.", + "notify_actions": "Voliteľná postupnosť akcií Home Assistant, ktorá sa spustí pre upozornenia (skripty, notifikácia, TTS atď.). Ak je nastavené, akcie sa spustia pred záložnou službou upozornení.", + "notify_people": "Osoby entity používané na kontrolu prítomnosti. Keď je táto možnosť povolená, doručenie upozornení sa oneskorí, kým aspoň jedna vybratá osoba nebude doma.", + "notify_only_when_home": "Ak je táto možnosť povolená, upozornenia sa odložia, kým aspoň jedna vybraná osoba nebude doma.", + "notify_fire_events": "Ak je táto možnosť povolená, spúšťajú sa udalosti Home Assistant pre začiatok a koniec cyklu (ha_washdata_cycle_started a ha_washdata_cycle_ended) na riadenie automatizácie.", + "notify_start_services": "Jedna alebo viacero oznamovacích služieb, ktoré upozornia na začiatok cyklu. Ak chcete preskočiť upozornenia na začiatok, nechajte prázdne.", + "notify_finish_services": "Jedna alebo viacero oznamovacích služieb, ktoré vás upozornia, keď sa cyklus skončí alebo sa blíži k dokončeniu. Ak chcete upozornenia na dokončenie preskočiť, nechajte prázdne.", + "notify_live_services": "Jedna alebo viacero oznamovacích služieb, aby dostávali aktuálne aktualizácie priebehu počas cyklu (iba mobilná sprievodná aplikácia). Ak chcete preskočiť aktuálne aktualizácie, nechajte prázdne.", + "notify_before_end_minutes": "Minúty pred odhadovaným koncom cyklu na spustenie upozornenia. Pre deaktiváciu nastavte na 0. Predvolená hodnota: 0.", + "notify_title": "Vlastný názov pre upozornenia. Podporuje zástupný symbol {device}.", + "notify_icon": "Ikona, ktorá sa má použiť na upozornenia (napr. mdi:washing-machine). Ponechajte prázdne pre odoslanie bez ikony.", + "notify_start_message": "Správa odoslaná pri spustení cyklu. Podporuje zástupný symbol {device}.", + "notify_finish_message": "Správa odoslaná po skončení cyklu. Podporuje zástupné symboly {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Text opakujúcich sa živých aktualizácií priebehu cyklu. Podporuje zástupné symboly {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Upozornenia", + "description": "Nakonfigurujte kanály upozornení, šablóny a voliteľné aktualizácie priebežného stavu.", + "data": { + "notify_actions": "Akcie upozornení", + "notify_people": "Oneskorené doručenie, kým títo ľudia nebudú doma", + "notify_only_when_home": "Oneskorenie upozornení, kým nebude vybraná osoba doma", + "notify_fire_events": "Spúšťať udalosti automatizácie", + "notify_start_services": "Začiatok cyklu - ciele upozornení", + "notify_finish_services": "Dokončenie cyklu - ciele upozornenia", + "notify_live_services": "Aktuálny pokrok - ciele upozornení", + "notify_before_end_minutes": "Upozornenie pred dokončením (minúty pred koncom)", + "notify_live_interval_seconds": "Interval živých aktualizácií (sekundy)", + "notify_live_overrun_percent": "Povolenie na prekročenie živej aktualizácie (%)", + "notify_live_chronometer": "Chronometer Časovač odpočítavania", + "notify_title": "Názov upozornenia", + "notify_icon": "Ikona upozornenia", + "notify_start_message": "Formát správy pri spustení", + "notify_finish_message": "Formát správy po dokončení", + "notify_pre_complete_message": "Formát správy pred dokončením", + "energy_price_entity": "Cena energie – subjekt (voliteľné)", + "energy_price_static": "Cena energie – statická hodnota (voliteľné)", + "go_back": "Vrátiť sa bez uloženia", + "notify_reminder_message": "Formát pripomenutia", + "notify_timeout_seconds": "Automaticky zrušiť po (sekundách)", + "notify_channel": "Kanál upozornení (stav/naživo/pripomienka)", + "notify_finish_channel": "Dokončený kanál upozornení" + }, + "data_description": { + "go_back": "Zaškrtnite túto možnosť a kliknutím na Odoslať zahodíte všetky vyššie uvedené zmeny a vrátite sa do hlavnej ponuky.", + "notify_actions": "Voliteľná postupnosť akcií Home Assistant, ktorá sa spustí pre upozornenia (skripty, notifikácia, TTS atď.). Ak je nastavené, akcie sa spustia pred záložnou službou upozornení.", + "notify_people": "Osoby entity používané na kontrolu prítomnosti. Keď je táto možnosť povolená, doručenie upozornení sa oneskorí, kým aspoň jedna vybratá osoba nebude doma.", + "notify_only_when_home": "Ak je táto možnosť povolená, upozornenia sa odložia, kým aspoň jedna vybraná osoba nebude doma.", + "notify_fire_events": "Ak je táto možnosť povolená, spúšťajú sa udalosti Home Assistant pre začiatok a koniec cyklu (ha_washdata_cycle_started a ha_washdata_cycle_ended) na riadenie automatizácie.", + "notify_start_services": "Jedna alebo viacero oznamovacích služieb, ktoré vás upozornia na začiatok cyklu (napr. notify.mobile_app_my_phone). Ak chcete preskočiť upozornenia na začiatok, nechajte prázdne.", + "notify_finish_services": "Jedna alebo viacero oznamovacích služieb, ktoré vás upozornia, keď sa cyklus skončí alebo sa blíži k dokončeniu. Ak chcete upozornenia na dokončenie preskočiť, nechajte prázdne.", + "notify_live_services": "Jedna alebo viacero oznamovacích služieb, aby dostávali aktuálne aktualizácie priebehu počas cyklu (iba mobilná sprievodná aplikácia). Ak chcete preskočiť aktuálne aktualizácie, nechajte prázdne.", + "notify_before_end_minutes": "Minúty pred odhadovaným koncom cyklu na spustenie upozornenia. Pre deaktiváciu nastavte na 0. Predvolená hodnota: 0.", + "notify_live_interval_seconds": "Ako často sa počas behu odosielajú aktualizácie priebehu. Nižšie hodnoty sú citlivejšie, ale spotrebujú viac upozornení. Vynucuje sa minimálne 30 sekúnd - hodnoty pod 30 s sa automaticky zaokrúhľujú na 30 s.", + "notify_live_overrun_percent": "Bezpečnostná rezerva nad odhadovaným počtom aktualizácií pre dlhé/prekračujúce cykly (napríklad 20 % umožňuje 1,2-násobok odhadovanej aktualizácie).", + "notify_live_chronometer": "Ak je táto možnosť povolená, každá aktuálna aktualizácia zahŕňa odpočítavanie chronometra do odhadovaného času dokončenia. Časovač upozornení na zariadení medzi aktualizáciami automaticky odtiká (iba sprievodná aplikácia pre Android).", + "notify_title": "Vlastný názov pre upozornenia. Podporuje zástupný symbol {device}.", + "notify_icon": "Ikona, ktorá sa má použiť na upozornenia (napr. mdi:washing-machine). Ponechajte prázdne pre odoslanie bez ikony.", + "notify_start_message": "Správa odoslaná pri spustení cyklu. Podporuje zástupný symbol {device}.", + "notify_finish_message": "Správa odoslaná po skončení cyklu. Podporuje zástupné symboly {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Vyberte číselnú entitu, ktorá obsahuje aktuálnu cenu elektriny (napr. senzor.cena_elektriny, číslo_vstupu.sadzba_energie). Keď je nastavený, povolí zástupný symbol {cost}. Ak sú obe nakonfigurované, má prednosť pred statickou hodnotou.", + "energy_price_static": "Pevná cena elektriny za kWh. Keď je nastavený, povolí zástupný symbol {cost}. Ignorované, ak je entita nakonfigurovaná vyššie. Do šablóny správy pridajte svoj symbol meny, napr. {cost} €.", + "notify_reminder_message": "Text jednorazovej pripomienky odoslal nakonfigurovaný počet minút pred odhadovaným koncom. Na rozdiel od opakujúcich sa živých aktualizácií vyššie. Podporuje zástupné symboly {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Automaticky zavrieť upozornenia po uplynutí tohto počtu sekúnd (preposlané ako „časový limit“ sprievodnej aplikácie). Nastavte na 0, aby ste ich ponechali, kým sa nezatvoria. Predvolená hodnota: 0.", + "notify_channel": "Android kanál upozornení sprievodnej aplikácie pre stav, aktuálny priebeh a upozornenia na pripomienky. Zvuk a dôležitosť kanála sa konfigurujú v sprievodnej aplikácii pri prvom použití názvu kanála – WashData nastavuje iba názov kanála. Pre predvolenú aplikáciu nechajte prázdne.", + "notify_finish_channel": "Samostatný kanál Android pre dokončené upozornenie (používané aj pripomenutím a čakaním na bielizeň), aby mohlo mať svoj vlastný zvuk. Ak chcete znova použiť kanál vyššie, nechajte prázdne.", + "notify_pre_complete_message": "Text opakujúcich sa živých aktualizácií priebehu cyklu. Podporuje zástupné symboly {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Rozšírené nastavenia", + "description": "Vylaďte prahy detekcie, učenie a pokročilé správanie. Predvolené nastavenia fungujú dobre pre väčšinu konfigurácií.", + "sections": { + "suggestions_section": { + "name": "Navrhované nastavenia", + "description": "K dispozícii je {suggestions_count} naučených odporúčaní. Zaškrtnite 'Použiť navrhované hodnoty', ak si chcete pred uložením prezrieť navrhované zmeny.", + "data": { + "apply_suggestions": "Použiť navrhované hodnoty" + }, + "data_description": { + "apply_suggestions": "Začiarknutím tohto políčka otvoríte krok kontroly navrhovaných hodnôt zo vzdelávacieho algoritmu. Nič sa neukladá automaticky.\n\nOdporúčané (z učenia):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekcia a prahy výkonu", + "description": "Určuje, kedy sa spotrebič považuje za ZAPNUTÝ alebo VYPNUTÝ, energetické brány a časovanie prechodov stavov.", + "data": { + "start_duration_threshold": "Trvanie počiatočného prahu (s)", + "start_energy_threshold": "Energetická brána pri štarte (Wh)", + "completion_min_seconds": "Minimálna doba spustenia na dokončenie (sekundy)", + "start_threshold_w": "Počiatočný prah (W)", + "stop_threshold_w": "Prah zastavenia (W)", + "end_energy_threshold": "Energetická brána pri ukončení (Wh)", + "running_dead_zone": "Mŕtva zóna pri štarte (sekundy)", + "end_repeat_count": "Počet opakovaní podmienky ukončenia", + "min_off_gap": "Minimálna medzera medzi cyklami (s)", + "sampling_interval": "Vzorkovací interval (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorujte krátke skoky napájania kratšie ako toto trvanie (sekundy). Odfiltruje špičky pri spustení (napr. 60 W na 2 s) pred začiatkom skutočného cyklu. Predvolená hodnota: 5 s.", + "start_energy_threshold": "Cyklus musí akumulovať aspoň toľko energie (Wh) počas fázy ŠTART, aby bol potvrdený. Predvolená hodnota: 0,005 Wh.", + "completion_min_seconds": "Cykly kratšie ako toto budú označené ako „prerušené”, aj keď sa skončia prirodzene. Predvolená hodnota: 600 s.", + "start_threshold_w": "Na spustenie cyklu musí výkon stúpnuť NAD túto hranicu. Nastavte vyšší výkon, než je váš pohotovostný režim. Predvolená hodnota: min_power + 1 W.", + "stop_threshold_w": "Na UKONČENIE cyklu musí výkon klesnúť POD túto hranicu. Nastavte tesne nad nulou, aby ste zabránili šumu spúšťajúcemu falošné konce. Predvolená hodnota: 0,6 × min_power.", + "end_energy_threshold": "Cyklus sa skončí iba vtedy, ak je energia v poslednom okne oneskorenia vypnutia pod touto hodnotou (Wh). Zabraňuje falošným koncom, ak výkon kolíše blízko nuly. Predvolená hodnota: 0,05 Wh.", + "running_dead_zone": "Po spustení cyklu ignorujte poklesy výkonu na toľko sekúnd, aby ste zabránili detekcii falošného konca počas počiatočnej fázy roztočenia. Pre deaktiváciu nastavte na 0. Predvolená hodnota: 0 s.", + "end_repeat_count": "Počet, koľkokrát musí byť splnená podmienka ukončenia (výkon pod prahovou hodnotou pre oneskorenie vypnutia) za sebou pred ukončením cyklu. Vyššie hodnoty zabraňujú falošným koncom počas prestávok. Predvolená hodnota: 1.", + "min_off_gap": "Ak cyklus začne v priebehu tohto počtu sekúnd od skončenia predchádzajúceho, zlúčia sa do jedného cyklu.", + "sampling_interval": "Minimálny interval vzorkovania v sekundách. Aktualizácie rýchlejšie ako táto rýchlosť budú ignorované. Predvolené: 30 s (2 s pre práčky, práčky so sušičkou a umývačky riadu)." + } + }, + "matching_section": { + "name": "Párovanie profilov a učenie", + "description": "Určuje, ako agresívne WashData porovnáva bežiace cykly s naučenými profilmi a kedy má požiadať o spätnú väzbu.", + "data": { + "profile_match_min_duration_ratio": "Minimálny pomer trvania pri porovnávaní profilu (0,1–1,0)", + "profile_match_interval": "Interval porovnávania profilu (sekundy)", + "profile_match_threshold": "Prah zhody profilu", + "profile_unmatch_threshold": "Prah nezhody profilu", + "auto_label_confidence": "Spoľahlivosť automatického označenia (0–1)", + "learning_confidence": "Spoľahlivosť žiadosti o spätnú väzbu (0–1)", + "suppress_feedback_notifications": "Potlačiť upozornenia na spätnú väzbu", + "duration_tolerance": "Tolerancia trvania (0–0,5)", + "smoothing_window": "Vyhladzovacie okno (vzorky)", + "profile_duration_tolerance": "Tolerancia trvania pri porovnávaní profilu (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimálny pomer trvania pre porovnávanie profilu. Aby sa cyklus zhodoval, musí byť aspoň tento zlomok trvania profilu. Nižšia = skoršia zhoda. Predvolená hodnota: 0,50 (50 %).", + "profile_match_interval": "Ako často (v sekundách) sa pokúšať o porovnanie profilu počas cyklu. Nižšie hodnoty poskytujú rýchlejšiu detekciu, ale využívajú viac CPU. Predvolené: 300 s (5 minút).", + "profile_match_threshold": "Minimálne skóre podobnosti DTW (0,0–1,0) potrebné na to, aby sa profil považoval za zhodu. Vyššia = prísnejšia zhoda, menej falošných poplachov. Predvolená hodnota: 0,4.", + "profile_unmatch_threshold": "Skóre podobnosti DTW (0,0–1,0), pod ktorým sa zamietne predtým zhodný profil. Mala by byť nižšia ako prah zhody, aby sa zabránilo blikaniu. Predvolená hodnota: 0,35.", + "auto_label_confidence": "Po dokončení automaticky označte cykly s touto spoľahlivosťou alebo nad ňou (0,0–1,0).", + "learning_confidence": "Minimálna spoľahlivosť na vyžiadanie overenia používateľa prostredníctvom udalosti (0,0–1,0).", + "suppress_feedback_notifications": "Keď je funkcia WashData povolená, bude stále sledovať a interne požadovať spätnú väzbu, ale nebude zobrazovať trvalé upozornenia, ktoré vás žiadajú o potvrdenie cyklov. Užitočné, keď sú cykly vždy správne rozpoznané a výzvy vás rušia.", + "duration_tolerance": "Tolerancia pre odhady zostávajúceho času počas cyklu (0,0–0,5 zodpovedá 0–50 %).", + "smoothing_window": "Počet nedávnych meraní výkonu použitých na vyhladenie kĺzavého priemeru.", + "profile_duration_tolerance": "Tolerancia použitá pri porovnávaní profilu pre celkový rozptyl trvania (0,0–0,5). Predvolené správanie: ±25 %." + } + }, + "timing_section": { + "name": "Časovanie, údržba a ladenie", + "description": "Watchdog, reset priebehu, automatická údržba a sprístupnenie ladiacich entít.", + "data": { + "watchdog_interval": "Interval strážneho psa (sekundy)", + "no_update_active_timeout": "Časový limit bez aktualizácie (s)", + "progress_reset_delay": "Oneskorenie resetovania postupu (sekundy)", + "auto_maintenance": "Povoliť automatickú údržbu", + "expose_debug_entities": "Zobraziť entity ladenia", + "save_debug_traces": "Uložiť stopy ladenia" + }, + "data_description": { + "watchdog_interval": "Sekundy medzi kontrolami strážneho psa počas behu. Predvolená hodnota: 5 s. UPOZORNENIE: Uistite sa, že je to VYŠŠIE ako interval aktualizácie vášho snímača, aby ste sa vyhli falošným zastaveniam (napr. ak sa snímač aktualizuje každých 60 s, použite 65 s+).", + "no_update_active_timeout": "Pre senzory zverejnenia pri zmene: vynútené ukončenie AKTÍVNEHO cyklu iba v prípade, že počas tohto množstva sekúnd neprídu žiadne aktualizácie. Dokončenie pri nízkej spotrebe energie stále používa oneskorenie vypnutia.", + "progress_reset_delay": "Po dokončení cyklu (100 %) počkajte toľko sekúnd nečinnosti, kým sa priebeh resetuje na 0 %. Predvolená hodnota: 300 s.", + "auto_maintenance": "Povoliť automatickú údržbu (opraviť vzorky, zlúčiť fragmenty).", + "expose_debug_entities": "Zobraziť pokročilé senzory (spoľahlivosť, fáza, nejednoznačnosť) na ladenie.", + "save_debug_traces": "Ukladanie podrobných údajov o hodnotení a sledovaní výkonu v histórii (zvyšuje využitie úložiska)." + } + }, + "anti_wrinkle_section": { + "name": "Ochrana proti vráskam (sušičky)", + "description": "Ignorujte pohyby bubna po skončení cyklu, aby ste predišli duchovým cyklom. Iba sušička / práčka so sušičkou.", + "data": { + "anti_wrinkle_enabled": "Ochrana proti vráskam (iba sušička)", + "anti_wrinkle_max_power": "Maximálny výkon pri ochrane proti vráskam (W)", + "anti_wrinkle_max_duration": "Maximálne trvanie ochrannej fázy proti vráskam (s)", + "anti_wrinkle_exit_power": "Výstupný výkon pri ochrane proti vráskam (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorujte krátke otáčky bubna s nízkym výkonom po skončení cyklu, aby ste predišli dvojitým cyklom (len sušička/práčka so sušičkou).", + "anti_wrinkle_max_power": "Výbuchy s touto silou alebo pod ňou sa považujú za rotácie pri ochrane proti vráskam namiesto nových cyklov. Platí len vtedy, keď je ochrana proti vráskam aktivovaná.", + "anti_wrinkle_max_duration": "Maximálna dĺžka dávky, ktorá sa bude považovať za rotáciu pri ochrane proti vráskam. Platí len vtedy, keď je ochrana proti vráskam aktivovaná.", + "anti_wrinkle_exit_power": "Ak výkon klesne pod túto úroveň, okamžite ukončite režim ochrany proti vráskam (skutočné vypnutie). Platí len vtedy, keď je ochrana proti vráskam aktivovaná." + } + }, + "delay_start_section": { + "name": "Detekcia oneskoreného štartu", + "description": "Považujte trvalý pohotovostný režim s nízkym odberom za stav 'Čakanie na spustenie', kým sa cyklus skutočne nezačne.", + "data": { + "delay_start_detect_enabled": "Povoliť detekciu oneskoreného spustenia", + "delay_confirm_seconds": "Oneskorený štart: čas potvrdenia pohotovostného režimu (s)", + "delay_timeout_hours": "Odložený štart: Max. čakacia doba (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Keď je aktivovaný, krátky výpadok pri nízkej spotrebe energie, po ktorom nasleduje trvalé napájanie v pohotovostnom režime, sa rozpozná ako oneskorený štart. Senzor stavu zobrazuje počas oneskorenia 'Čaká na spustenie'. Nesleduje sa žiadny čas programu ani priebeh, kým sa cyklus skutočne nezačne. Vyžaduje, aby start_threshold_w bol nastavený nad výkonom vypúšťacej špičky.", + "delay_confirm_seconds": "Výkon musí zostať medzi prahom zastavenia (W) a prahom spustenia (W) počas tohto počtu sekúnd, kým WashData prejde do stavu 'Čakanie na spustenie'. Odfiltruje krátke špičky pri navigácii v menu. Predvolené: 60 s.", + "delay_timeout_hours": "Bezpečnostný časový limit: ak je práčka aj po tomto množstve hodín stále v režime „Čaká na spustenie“, WashData sa resetuje na Off. Predvolené: 8 h." + } + }, + "external_triggers_section": { + "name": "Externé spúšťače, dvere a pauza", + "description": "Voliteľný externý snímač ukončenia cyklu, snímač dverí na detekciu pauzy / vypratania a prepínač vypnutia napájania pri pauze.", + "data": { + "external_end_trigger_enabled": "Povoliť externý spúšťač ukončenia cyklu", + "external_end_trigger": "Externý snímač konca cyklu", + "external_end_trigger_inverted": "Invertovať logiku spúšťača (spúšťač pri vypnutí)", + "door_sensor_entity": "Senzor dverí", + "pause_cuts_power": "Vypnutie napájania pri pozastavení", + "switch_entity": "Prepnúť entitu (pre pozastavenie napájania)", + "notify_unload_delay_minutes": "Oneskorenie čakania na bielizeň (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Povoliť monitorovanie externého binárneho snímača na spustenie dokončenia cyklu.", + "external_end_trigger": "Vyberte entitu binárneho senzora. Keď sa tento snímač spustí, aktuálny cyklus sa skončí so stavom „dokončený“.", + "external_end_trigger_inverted": "Štandardne sa spúšťač aktivuje, keď sa senzor zapne. Začiarknutím tohto políčka sa aktivuje, keď sa senzor namiesto toho vypne.", + "door_sensor_entity": "Voliteľný binárny snímač pre dvierka stroja (zapnuté = otvorené, vypnuté = zatvorené). Keď sa dvierka otvoria počas aktívneho cyklu, WashData potvrdí, že cyklus bol zámerne pozastavený. Po skončení cyklu, ak sú dvierka stále zatvorené, stav sa zmení na „Vyčistiť“, kým sa dvierka neotvoria. Poznámka: zatvorenie dvierok NEOBNOVÍ automaticky pozastavený cyklus – obnovenie sa musí spustiť manuálne pomocou tlačidla alebo služby Obnoviť cyklus.", + "pause_cuts_power": "Pri pozastavení pomocou tlačidla alebo služby Pause Cycle tiež vypnite nakonfigurovanú entitu prepínača. Nadobudne účinnosť iba vtedy, ak je pre toto zariadenie nakonfigurovaná entita prepínača.", + "switch_entity": "Entita prepínania, ktorá sa má prepínať pri pozastavení alebo opätovnom spustení. Vyžaduje sa, keď je aktivovaná možnosť „Cut Power When Pausing“.", + "notify_unload_delay_minutes": "Po skončení cyklu pošlite upozornenie cez kanál upozornenia na ukončenie a dvierka sa toľko minút neotvorili (vyžaduje snímač dverí). Pre deaktiváciu nastavte na 0. Predvolené: 60 min." + } + }, + "pump_section": { + "name": "Monitor čerpadla", + "description": "Len pre typ zariadenia čerpadlo. Vyvolá udalosť, ak cyklus čerpadla beží dlhšie než tento čas.", + "data": { + "pump_stuck_duration": "Prah (y) upozornenia na zaseknutie pumpy (iba pumpa)" + }, + "data_description": { + "pump_stuck_duration": "Ak cyklus pumpy beží aj po tomto mnohých sekundách, udalosť ha_washdata_pump_stuck sa spustí raz. Použite to na zistenie zaseknutého motora (typický chod kalového čerpadla je kratší ako 60 s). Predvolené: 1800 s (30 min). Len typ čerpadla." + } + }, + "device_link_section": { + "name": "Odkaz na zariadenie", + "description": "Voliteľne prepojte toto zariadenie WashData s existujúcim zariadením Home Assistant (napríklad inteligentnou zástrčkou alebo samotným zariadením). Zariadenie WashData sa potom zobrazí ako „Pripojené cez“ toto zariadenie. Nechajte prázdne, aby WashData zostala ako samostatné zariadenie.", + "data": { + "linked_device": "Prepojené zariadenie" + }, + "data_description": { + "linked_device": "Vyberte zariadenie, ku ktorému chcete pripojiť WashData. Tým sa do registra zariadenia pridá odkaz „Pripojené cez“; WashData uchováva svoju vlastnú kartu zariadenia a entity. Ak chcete odkaz odstrániť, zrušte výber." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika a údržba", + "description": "Spustite akcie údržby, ako je zlúčenie fragmentovaných cyklov alebo migrácia uložených údajov.\n\n**Využitie úložiska**\n\n- Veľkosť súboru: {file_size_kb} kB\n- Cykly: {cycle_count}\n- Profily: {profile_count}\n- Stopy ladenia: {debug_count}", + "menu_options": { + "reprocess_history": "Údržba: opätovné spracovanie a optimalizácia údajov", + "clear_debug_data": "Vymazať údaje ladenia (uvoľniť miesto)", + "wipe_history": "Vymazať VŠETKY údaje pre toto zariadenie (nevratné)", + "export_import": "Export/import JSON s nastaveniami (kopírovať/prilepiť)", + "menu_back": "← Späť" + } + }, + "clear_debug_data": { + "title": "Vymazať údaje ladenia", + "description": "Naozaj chcete odstrániť všetky uložené stopy ladenia? Tým sa uvoľní miesto, ale odstránia sa podrobné informácie o hodnotení z minulých cyklov." + }, + "manage_cycles": { + "title": "Spravovať cykly", + "description": "Posledné cykly:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatické označovanie starých cyklov", + "select_cycle_to_label": "Označiť konkrétny cyklus", + "select_cycle_to_delete": "Odstrániť cyklus", + "interactive_editor": "Interaktívny editor zlúčenia/rozdelenia", + "trim_cycle_select": "Orezať dáta cyklu", + "menu_back": "← Späť" + } + }, + "manage_cycles_empty": { + "title": "Spravovať cykly", + "description": "Zatiaľ neboli zaznamenané žiadne cykly.", + "menu_options": { + "auto_label_cycles": "Automatické označovanie starých cyklov", + "menu_back": "← Späť" + } + }, + "manage_profiles": { + "title": "Spravovať profily", + "description": "Zhrnutie profilu:\n{profile_summary}", + "menu_options": { + "create_profile": "Vytvoriť nový profil", + "edit_profile": "Upraviť/premenovať profil", + "delete_profile_select": "Odstrániť profil", + "profile_stats": "Štatistika profilu", + "cleanup_profile": "Vyčistiť históriu – graf a vymazanie", + "assign_profile_phases_select": "Priradiť fázové rozsahy", + "menu_back": "← Späť" + } + }, + "manage_profiles_empty": { + "title": "Spravovať profily", + "description": "Zatiaľ nie sú vytvorené žiadne profily.", + "menu_options": { + "create_profile": "Vytvoriť nový profil", + "menu_back": "← Späť" + } + }, + "manage_phase_catalog": { + "title": "Spravovať katalóg fáz", + "description": "Katalóg fáz (predvolené pre toto zariadenie + vlastné fázy):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Vytvoriť novú fázu", + "phase_catalog_edit_select": "Upraviť fázu", + "phase_catalog_delete": "Odstrániť fázu", + "menu_back": "← Späť" + } + }, + "phase_catalog_create": { + "title": "Vytvoriť vlastnú fázu", + "description": "Vytvorte vlastný názov a popis fázy a vyberte, na ktorý typ zariadenia sa vzťahuje.", + "data": { + "target_device_type": "Cieľový typ zariadenia", + "phase_name": "Názov fázy", + "phase_description": "Popis fázy" + } + }, + "phase_catalog_edit_select": { + "title": "Upraviť vlastnú fázu", + "description": "Vyberte vlastnú fázu, ktorú chcete upraviť.", + "data": { + "phase_name": "Vlastná fáza" + } + }, + "phase_catalog_edit": { + "title": "Upraviť vlastnú fázu", + "description": "Úprava fázy: {phase_name}", + "data": { + "phase_name": "Názov fázy", + "phase_description": "Popis fázy" + } + }, + "phase_catalog_delete": { + "title": "Odstrániť vlastnú fázu", + "description": "Vyberte vlastnú fázu, ktorú chcete odstrániť. Všetky priradené rozsahy pomocou tejto fázy budú odstránené.", + "data": { + "phase_name": "Vlastná fáza" + } + }, + "assign_profile_phases": { + "title": "Priradiť fázové rozsahy", + "description": "Ukážka fázy pre profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuálne rozsahy:**\n{current_ranges}\n\nAk chcete pridať, upraviť, odstrániť alebo uložiť rozsahy, použite akcie uvedené nižšie.", + "menu_options": { + "assign_profile_phases_add": "Pridať fázový rozsah", + "assign_profile_phases_edit_select": "Upraviť fázový rozsah", + "assign_profile_phases_delete": "Odstrániť fázový rozsah", + "phase_ranges_clear": "Vymazať všetky rozsahy", + "assign_profile_phases_auto_detect": "Automatická detekcia fáz", + "phase_ranges_save": "Uložiť a vrátiť sa", + "menu_back": "← Späť" + } + }, + "assign_profile_phases_add": { + "title": "Pridať fázový rozsah", + "description": "Pridajte jeden fázový rozsah k aktuálnemu konceptu.", + "data": { + "phase_name": "Fáza", + "start_min": "Počiatočná minúta", + "end_min": "Koncová minúta" + } + }, + "assign_profile_phases_edit_select": { + "title": "Upraviť fázový rozsah", + "description": "Vyberte rozsah, ktorý chcete upraviť.", + "data": { + "range_index": "Fázový rozsah" + } + }, + "assign_profile_phases_edit": { + "title": "Upraviť fázový rozsah", + "description": "Aktualizujte vybraný fázový rozsah.", + "data": { + "phase_name": "Fáza", + "start_min": "Počiatočná minúta", + "end_min": "Koncová minúta" + } + }, + "assign_profile_phases_delete": { + "title": "Odstrániť fázový rozsah", + "description": "Vyberte rozsah, ktorý chcete odstrániť z konceptu.", + "data": { + "range_index": "Fázový rozsah" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automaticky zistené fázy", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Automaticky rozpoznaných fáz: {detected_count}.** Vyberte akciu nižšie.", + "data": { + "action": "Akcia" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Pomenovať zistené fázy", + "description": "Profil: **{profile_name}**\n\n**Zistilo sa {detected_count} fáz:**\n{ranges_summary}\n\nPriraďte názov každej fáze." + }, + "assign_profile_phases_select": { + "title": "Priradiť fázové rozsahy", + "description": "Vyberte profil na konfiguráciu fázových rozsahov.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Štatistika profilu", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Vytvoriť nový profil", + "description": "Vytvorte nový profil manuálne alebo z minulého cyklu.\n\nPríklady názvov profilu: 'Jemné', 'Silno znečistené', 'Rýchle pranie'", + "data": { + "profile_name": "Názov profilu", + "reference_cycle": "Referenčný cyklus (voliteľné)", + "manual_duration": "Manuálne trvanie (minúty)" + } + }, + "edit_profile": { + "title": "Upraviť profil", + "description": "Vyberte profil, ktorý chcete premenovať", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Upraviť podrobnosti profilu", + "description": "Aktuálny profil: {current_name}", + "data": { + "new_name": "Názov profilu", + "manual_duration": "Manuálne trvanie (minúty)" + } + }, + "delete_profile_select": { + "title": "Odstrániť profil", + "description": "Vyberte profil, ktorý chcete odstrániť", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potvrdiť odstránenie profilu", + "description": "⚠️ Týmto natrvalo odstránite profil: {profile_name}", + "data": { + "unlabel_cycles": "Odstrániť štítok z cyklov používajúcich tento profil" + } + }, + "auto_label_cycles": { + "title": "Automatické označovanie starých cyklov", + "description": "Celkový počet nájdených cyklov: {total_count}. Profily: {profiles}", + "data": { + "confidence_threshold": "Minimálna spoľahlivosť (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Vyberte cyklus na označenie", + "description": "✓ = dokončené, ⚠ = obnovené, ✗ = prerušené", + "data": { + "cycle_id": "Cyklus" + } + }, + "select_cycle_to_delete": { + "title": "Odstrániť cyklus", + "description": "⚠️ Týmto natrvalo vymažete vybraný cyklus", + "data": { + "cycle_id": "Cyklus na vymazanie" + } + }, + "label_cycle": { + "title": "Označiť cyklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nový názov profilu (ak sa vytvára)" + } + }, + "post_process": { + "title": "Spracovanie histórie (zlúčenie/rozdelenie)", + "description": "Vyčistite históriu cyklov zlúčením fragmentovaných cyklov a rozdelením nesprávne zlúčených cyklov v rámci časového okna. Zadajte počet hodín na spätný pohľad (pre všetky použite 999999).", + "data": { + "time_range": "Okno spätného náhľadu (hodiny)", + "gap_seconds": "Medzera pre zlúčenie/rozdelenie (sekundy)" + }, + "data_description": { + "time_range": "Počet hodín na vyhľadávanie fragmentovaných cyklov na zlúčenie. Na spracovanie všetkých historických údajov použite 999999.", + "gap_seconds": "Hranica pre rozhodnutie o rozdelení vs. zlúčení. Medzery KRATŠIE ako toto sú zlúčené. Medzery DLHŠIE ako toto sú rozdelené. Znížte túto hodnotu, ak chcete vynútiť rozdelenie cyklu, ktorý bol zlúčený nesprávne." + } + }, + "cleanup_profile": { + "title": "Vyčistiť históriu – vyberte profil", + "description": "Vyberte profil, ktorý chcete zobraziť. Tým sa vygeneruje graf zobrazujúci všetky minulé cykly pre tento profil, ktorý pomôže identifikovať odľahlé hodnoty.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Vyčistiť históriu – graf a vymazanie", + "description": "![Graf]({graph_url})\n\n**Vizualizácia {profile_name}**\n\nGraf zobrazuje jednotlivé cykly ako farebné čiary. Identifikujte odľahlé hodnoty (čiary vzdialené od skupiny) podľa zodpovedajúcej farby v zozname nižšie a odstráňte ich.\n\n**Vyberte cykly, ktoré chcete TRVALO odstrániť:**", + "data": { + "cycles_to_delete": "Cykly na odstránenie (skontrolujte zodpovedajúce farby)" + } + }, + "interactive_editor": { + "title": "Interaktívny editor zlúčenia/rozdelenia", + "description": "Vyberte manuálnu akciu, ktorá sa má vykonať s históriou cyklu.", + "menu_options": { + "editor_split": "Rozdeliť cyklus (nájsť medzery)", + "editor_merge": "Zlúčiť cykly (spojiť fragmenty)", + "editor_delete": "Odstrániť cykly", + "menu_back": "← Späť" + } + }, + "editor_select": { + "title": "Vybrať cykly", + "description": "Vyberte cykly, ktoré chcete spracovať.\n\n{info_text}", + "data": { + "selected_cycles": "Cykly" + } + }, + "editor_configure": { + "title": "Konfigurovať a použiť", + "description": "{preview_md}", + "data": { + "confirm_action": "Akcia", + "merged_profile": "Výsledný profil", + "new_profile_name": "Nový názov profilu", + "confirm_commit": "Áno, potvrdzujem túto akciu", + "segment_0_profile": "Profil segmentu 1", + "segment_1_profile": "Profil segmentu 2", + "segment_2_profile": "Profil segmentu 3", + "segment_3_profile": "Profil segmentu 4", + "segment_4_profile": "Profil segmentu 5", + "segment_5_profile": "Profil segmentu 6", + "segment_6_profile": "Profil segmentu 7", + "segment_7_profile": "Profil segmentu 8", + "segment_8_profile": "Profil segmentu 9", + "segment_9_profile": "Profil segmentu 10" + } + }, + "editor_split_params": { + "title": "Konfigurácia rozdelenia", + "description": "Nastavte citlivosť pre rozdelenie tohto cyklu. Akákoľvek medzera v nečinnosti dlhšia ako táto spôsobí rozdelenie.", + "data": { + "split_mode": "Metóda rozdelenia" + } + }, + "editor_split_auto_params": { + "title": "Konfigurácia automatického rozdelenia", + "description": "Upravte citlivosť rozdelenia tohto cyklu. Akákoľvek nečinná medzera dlhšia ako táto spôsobí rozdelenie.", + "data": { + "min_gap_seconds": "Prah medzery rozdelenia (sekundy)" + } + }, + "editor_split_manual_params": { + "title": "Ručné časové pečiatky rozdelenia", + "description": "{preview_md}Okno cyklu: **{cycle_start_wallclock} → {cycle_end_wallclock}** dňa {cycle_date}.\n\nZadajte jednu alebo viac časových pečiatok rozdelenia (`HH:MM` alebo `HH:MM:SS`), jednu na riadok. Cyklus bude rozdelený pri každej časovej pečiatke — N časových pečiatok vytvorí N+1 segmentov.", + "data": { + "split_timestamps": "Časové značky rozdelenia" + } + }, + "reprocess_history": { + "title": "Údržba: opätovné spracovanie a optimalizácia údajov", + "description": "Vykonáva hĺbkové čistenie historických údajov:\n1. Orezáva hodnoty s nulovou spotrebou energie (ticho).\n2. Opravuje načasovanie/trvanie cyklu.\n3. Prepočítava všetky štatistické modely (obálky).\n\n**Spustite to, ak chcete vyriešiť problémy s údajmi alebo použiť nové algoritmy.**" + }, + "wipe_history": { + "title": "Vymazať históriu (iba testovanie)", + "description": "⚠️ Týmto natrvalo odstránite VŠETKY uložené cykly a profily pre toto zariadenie. Toto sa nedá vrátiť späť!" + }, + "export_import": { + "title": "Export/Import JSON", + "description": "Skopírujte/prilepte cykly, profily a nastavenia pre toto zariadenie. Exportujte nižšie alebo prilepte JSON na import.", + "data": { + "mode": "Akcia", + "json_payload": "Kompletná konfigurácia JSON" + }, + "data_description": { + "mode": "Zvoľte Export, ak chcete skopírovať aktuálne údaje a nastavenia, alebo Import, ak chcete prilepiť exportované údaje z iného zariadenia WashData.", + "json_payload": "Pre export: skopírujte tento JSON (zahŕňa cykly, profily a všetky doladené nastavenia). Pre import: prilepte JSON exportovaný z WashData." + } + }, + "record_cycle": { + "title": "Nahrávanie cyklu", + "description": "Manuálne zaznamenajte cyklus, aby ste vytvorili čistý profil bez rušenia.\n\nStav: **{status}**\nTrvanie: {duration} s\nVzorky: {samples}", + "menu_options": { + "record_refresh": "Obnoviť stav", + "record_stop": "Zastaviť nahrávanie (uložiť a spracovať)", + "record_start": "Spustiť nové nahrávanie", + "record_process": "Spracovať posledný záznam (orezať a uložiť)", + "record_discard": "Zahodiť posledný záznam", + "menu_back": "← Späť" + } + }, + "record_process": { + "title": "Spracovanie záznamu", + "description": "![Graf]({graph_url})\n\n**Graf ukazuje nespracovaný záznam.** Modrá = Ponechať, Červená = Navrhované orezanie.\n*Graf je statický a pri zmenách formulára sa neaktualizuje.*\n\nNahrávanie bolo zastavené. Orezania sú zarovnané so zistenou vzorkovacou frekvenciou (~{sampling_rate} s).\n\nSurové trvanie: {duration} s\nVzorky: {samples}", + "data": { + "head_trim": "Orezanie začiatku (sekundy)", + "tail_trim": "Orezanie konca (sekundy)", + "save_mode": "Cieľ uloženia", + "profile_name": "Názov profilu" + } + }, + "learning_feedbacks": { + "title": "Spätná väzba na učenie", + "description": "Nevybavená spätná väzba: {count}\n\n{pending_table}\n\nVyberte žiadosť o kontrolu cyklu, ktorú chcete spracovať.", + "menu_options": { + "learning_feedbacks_pick": "Skontrolovať nevybavenú spätnú väzbu", + "learning_feedbacks_dismiss_all": "Zahodiť všetku nevybavenú spätnú väzbu", + "menu_back": "← Späť" + } + }, + "learning_feedbacks_pick": { + "title": "Vyberte spätnú väzbu na kontrolu", + "description": "Vyberte žiadosť o kontrolu cyklu, ktorú chcete otvoriť.", + "data": { + "selected_feedback": "Vyberte cyklus na kontrolu" + } + }, + "learning_feedbacks_empty": { + "title": "Spätná väzba na učenie", + "description": "Nenašli sa žiadne čakajúce žiadosti o spätnú väzbu.", + "menu_options": { + "menu_back": "← Späť" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Zamietnuť všetky čakajúce spätné väzby", + "description": "Chystáte sa zamietnuť **{count}** čakajúcich žiadostí o spätnú väzbu. Zamietnuté cykly zostanú vo vašej histórii, ale nebudú prispievať novým signálom učenia. Túto akciu nie je možné vrátiť späť.\n\n✅ Začiarknite políčko nižšie a kliknutím na **Odoslať** zamietnite všetko.\n❌ Nechajte ho nezačiarknuté a kliknutím na **Odoslať** zrušte akciu a vráťte sa späť.", + "data": { + "confirm_dismiss_all": "Potvrdiť: zahodiť všetky nevybavené žiadosti o spätnú väzbu" + } + }, + "resolve_feedback": { + "title": "Vyriešiť spätnú väzbu", + "description": "{comparison_img}{candidates_table}\n**Zistený profil**: {detected_profile} ({confidence_pct} %)\n**Odhadované trvanie**: {est_duration_min} min\n**Skutočné trvanie**: {act_duration_min} min\n\n__Vyberte akciu nižšie:__", + "data": { + "action": "Čo chcete urobiť?", + "corrected_profile": "Správny program (ak opravujete)", + "corrected_duration": "Správne trvanie v sekundách (ak opravujete)" + } + }, + "trim_cycle_select": { + "title": "Orezanie cyklu - vyberte cyklus", + "description": "Vyberte cyklus, ktorý chcete orezať. ✓ = dokončené, ⚠ = obnovené, ✗ = prerušené", + "data": { + "cycle_id": "Cyklus" + } + }, + "trim_cycle": { + "title": "Orezanie cyklu", + "description": "Aktuálne okno: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akcia" + } + }, + "trim_cycle_start": { + "title": "Nastaviť začiatok orezania", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuálny začiatok: **{current_wallclock}** (+{current_offset_min} min od začiatku cyklu)\n\nVyberte si nový čas začiatku pomocou nástroja na výber hodín nižšie.", + "data": { + "trim_start_time": "Nový čas začiatku", + "trim_start_min": "Nový štart (minúty od začiatku cyklu)" + } + }, + "trim_cycle_end": { + "title": "Nastaviť koniec orezania", + "description": "Okno cyklu: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nAktuálny koniec: **{current_wallclock}** (+{current_offset_min} min od začiatku cyklu)\n\nVyberte si nový čas ukončenia pomocou nástroja na výber hodín nižšie.", + "data": { + "trim_end_time": "Nový čas ukončenia", + "trim_end_min": "Nový koniec (minúty od začiatku cyklu)" + } + } + }, + "error": { + "import_failed": "Import zlyhal. Prilepte platný súbor JSON exportu WashData.", + "select_exactly_one": "Vyberte pre túto akciu presne jeden cyklus.", + "select_at_least_one": "Vyberte pre túto akciu aspoň jeden cyklus.", + "select_at_least_two": "Vyberte pre túto akciu aspoň dva cykly.", + "profile_exists": "Profil s týmto názvom už existuje", + "rename_failed": "Premenovanie profilu zlyhalo. Profil možno neexistuje alebo je nové meno už použité.", + "assignment_failed": "Priradenie profilu k cyklu zlyhalo", + "duplicate_phase": "Fáza s týmto názvom už existuje.", + "phase_not_found": "Vybraná fáza sa nenašla.", + "invalid_phase_name": "Názov fázy nemôže byť prázdny.", + "phase_name_too_long": "Názov fázy je príliš dlhý.", + "invalid_phase_range": "Fázový rozsah je neplatný. Koniec musí byť väčší ako začiatok.", + "invalid_phase_timestamp": "Časová pečiatka je neplatná. Použite formát RRRR-MM-DD HH:MM alebo HH:MM.", + "overlapping_phase_ranges": "Fázové rozsahy sa prekrývajú. Upravte rozsahy a skúste to znova.", + "incomplete_phase_row": "Každý riadok fázy musí obsahovať fázu plus úplné počiatočné/koncové hodnoty pre vybraný režim.", + "timestamp_mode_cycle_required": "Pri použití režimu časovej pečiatky vyberte zdrojový cyklus.", + "no_phase_ranges": "Pre túto akciu zatiaľ nie sú dostupné žiadne fázové rozsahy.", + "feedback_notification_title": "WashData: Overiť cyklus ({device})", + "feedback_notification_message": "**Zariadenie**: {device}\n**Program**: {program} (spoľahlivosť {confidence} %)\n**Čas**: {time}\n\nWashData potrebuje vašu pomoc na overenie tohto zisteného cyklu.\n\nAk chcete potvrdiť alebo opraviť tento výsledok, prejdite do časti **Nastavenia > Zariadenia a služby > WashData > Konfigurovať > Spätná väzba na učenie**.", + "suggestions_ready_notification_title": "WashData: Navrhované nastavenia sú pripravené ({device})", + "suggestions_ready_notification_message": "Senzor **Navrhované nastavenia** teraz hlási **{count}** odporúčaní pripravených na použitie.\n\nAk ich chcete skontrolovať a použiť: **Nastavenia > Zariadenia a služby > WashData > Konfigurovať > Rozšírené nastavenia > Použiť navrhované hodnoty**.\n\nNávrhy sú voliteľné a pred uložením sa zobrazia na kontrolu.", + "trim_range_invalid": "Začiatok musí byť pred koncom. Upravte si body orezania.", + "trim_failed": "Orezanie zlyhalo. Cyklus už nemusí existovať alebo je okno prázdne.", + "invalid_split_timestamp": "Časová značka je neplatná alebo mimo okna cyklu. Použite HH:MM alebo HH:MM:SS v rámci okna cyklu.", + "no_split_segments_found": "Z týchto časových značiek sa nepodarilo vytvoriť žiadne platné segmenty. Uistite sa, že každá časová značka je v okne cyklu a segmenty majú dĺžku aspoň 1 minútu.", + "auto_tune_suggestion": "{device_type} {device_title} zistilo cykly duchov. Navrhovaná zmena minimálneho výkonu: {current_min}W -> {new_min}W (neaplikuje sa automaticky).", + "auto_tune_title": "Automatické ladenie WashData", + "auto_tune_fallback": "{device_type} {device_title} zistilo cykly duchov. Navrhovaná zmena minimálneho výkonu: {current_min}W -> {new_min}W (neaplikuje sa automaticky)." + }, + "abort": { + "no_cycles_found": "Nenašli sa žiadne cykly", + "no_split_segments_found": "Vo vybranom cykle sa nenašli žiadne rozdeliteľné segmenty.", + "cycle_not_found": "Vybraný cyklus sa nepodarilo načítať.", + "no_power_data": "Pre zvolený cyklus nie sú k dispozícii žiadne údaje sledovania výkonu.", + "no_profiles_found": "Nenašli sa žiadne profily. Najprv si vytvorte profil.", + "no_profiles_for_matching": "Nie sú k dispozícii žiadne profily na priradenie. Najprv vytvorte profily.", + "no_unlabeled_cycles": "Všetky cykly sú už označené", + "no_suggestions": "Zatiaľ nie sú k dispozícii žiadne navrhované hodnoty. Spustite niekoľko cyklov a skúste to znova.", + "no_predictions": "Žiadne predpovede", + "no_custom_phases": "Nenašli sa žiadne vlastné fázy.", + "reprocess_success": "Počet úspešne opätovne spracovaných cyklov: {count}.", + "debug_data_cleared": "Údaje ladenia z {count} cyklov boli úspešne vymazané." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Spustenie cyklu", + "cycle_finish": "Dokončenie cyklu", + "cycle_live": "Živý priebeh" + } + }, + "common_text": { + "options": { + "unlabeled": "(Neoznačené)", + "create_new_profile": "Vytvoriť nový profil", + "remove_label": "Odstrániť štítok", + "no_reference_cycle": "(Žiadny referenčný cyklus)", + "all_device_types": "Všetky typy zariadení", + "current_suffix": "(aktuálne)", + "editor_select_info": "Vyberte 1 cyklus na rozdelenie alebo 2+ cykly na zlúčenie.", + "editor_select_info_split": "Vyberte 1 cyklus na rozdelenie.", + "editor_select_info_merge": "Vyberte 2+ cyklov na zlúčenie.", + "editor_select_info_delete": "Vyberte 1+ cyklov na odstránenie.", + "no_cycles_recorded": "Zatiaľ neboli zaznamenané žiadne cykly.", + "profile_summary_line": "- **{name}**: {count} cyklov, priem. {avg} m", + "no_profiles_created": "Zatiaľ nie sú vytvorené žiadne profily.", + "phase_preview": "Ukážka fázy", + "phase_preview_no_curve": "Priemerná profilová krivka zatiaľ nie je k dispozícii. Spustite/označte viac cyklov pre tento profil.", + "average_power_curve": "Priemerná výkonová krivka", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cyklov, priemer ~{duration} m)", + "profile_option_short_fmt": "{name} ({count} cyklov, ~{duration} m)", + "deleted_cycles_from_profile": "Odstránených {count} cyklov z {profile}.", + "not_enough_data_graph": "Nedostatok údajov na vytvorenie grafu.", + "no_profiles_found": "Nenašli sa žiadne profily.", + "cycle_info_fmt": "Cyklus: {start}, {duration} min, štítok: {label}", + "top_candidates_header": "### Najlepší kandidáti", + "tbl_profile": "Profil", + "tbl_confidence": "Spoľahlivosť", + "tbl_correlation": "Korelácia", + "tbl_duration_match": "Zhoda trvania", + "detected_profile": "Zistený profil", + "estimated_duration": "Odhadované trvanie", + "actual_duration": "Skutočné trvanie", + "choose_action": "__Vyberte akciu nižšie:__", + "tbl_count": "Počet", + "tbl_avg": "Priem.", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energia (priem.)", + "tbl_energy_total": "Energia (celkom)", + "tbl_consistency": "Konzist.", + "tbl_last_run": "Posledný chod", + "graph_legend_title": "Legenda grafu", + "graph_legend_body": "Modrý pás predstavuje minimálny a maximálny pozorovaný rozsah odberu energie. Čiara zobrazuje krivku priemerného výkonu.", + "recording_preview": "Ukážka nahrávania", + "trim_start": "Začiatok orezania", + "trim_end": "Koniec orezania", + "split_preview_title": "Ukážka rozdelenia", + "split_preview_found_fmt": "Počet nájdených segmentov: {count}.", + "split_preview_confirm_fmt": "Kliknutím na tlačidlo Potvrdiť rozdelíte tento cyklus do {count} samostatných cyklov.", + "merge_preview_title": "Ukážka zlúčenia", + "merge_preview_joining_fmt": "Spája sa {count} cyklov. Medzery budú vyplnené údajmi 0 W.", + "editor_delete_preview_title": "Náhľad odstránenia", + "editor_delete_preview_intro": "Vybrané cykly budú natrvalo odstránené:", + "editor_delete_preview_confirm": "Kliknutím na Potvrdiť natrvalo odstránite tieto záznamy cyklov.", + "no_power_preview": "*Pre ukážku nie sú k dispozícii žiadne údaje o výkone.*", + "profile_comparison": "Porovnanie profilov", + "actual_cycle_label": "Tento cyklus (skutočný)", + "storage_usage_header": "Využitie úložiska", + "storage_file_size": "Veľkosť súboru", + "storage_cycles": "Cykly", + "storage_profiles": "Profily", + "storage_debug_traces": "Stopy ladenia", + "overview_suffix": "Prehľad", + "phase_preview_for_profile": "Ukážka fázy profilu", + "current_ranges_header": "Aktuálne rozsahy", + "assign_phases_help": "Ak chcete pridať, upraviť, odstrániť alebo uložiť rozsahy, použite akcie uvedené nižšie.", + "profile_summary_header": "Zhrnutie profilu", + "recent_cycles_header": "Nedávne cykly", + "trim_cycle_preview_title": "Ukážka orezania cyklu", + "trim_cycle_preview_no_data": "Pre tento cyklus nie sú k dispozícii žiadne údaje o výkone.", + "trim_cycle_preview_kept_suffix": "zachované", + "table_program": "Program", + "table_when": "Kedy", + "table_length": "Dĺžka", + "table_match": "Zhoda", + "table_profile": "Profil", + "table_cycles": "Cykly", + "table_avg_length": "Priem. dĺžka", + "table_last_run": "Posledný chod", + "table_avg_energy": "Priem. energia", + "table_detected_program": "Zistený program", + "table_confidence": "Spoľahlivosť", + "table_reported": "Nahlásené", + "phase_builtin_suffix": "(Vstavaný)", + "phase_none_available": "Nie sú k dispozícii žiadne fázy.", + "settings_deprecation_warning": "⚠️ **Zastaraný typ zariadenia:** Odstránenie zariadenia {device_type} je naplánované v budúcom vydaní. Zodpovedajúci kanál WashData neposkytuje spoľahlivé výsledky pre túto triedu spotrebičov. Vaša integrácia funguje aj počas obdobia ukončenia podpory; ak chcete toto varovanie stlmiť, prepnite **Typ zariadenia** nižšie na jeden z podporovaných typov (práčka, sušička, kombinovaná práčka so sušičkou, umývačka riadu, fritéza, pekáreň chleba alebo čerpadlo) alebo na **Iné (pokročilé)**, ak váš spotrebič nezodpovedá žiadnemu z podporovaných typov. **Iné (pokročilé)** dodáva zámerne všeobecné predvolené nastavenia, ktoré nie sú vyladené pre žiadne konkrétne zariadenie, takže si budete musieť sami nakonfigurovať prahové hodnoty, časové limity a zodpovedajúce parametre; všetky vaše existujúce nastavenia sa po prepnutí zachovajú.", + "phase_other_device_types": "Ďalšie typy zariadení:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Rozdeliť cyklus (nájsť medzery)", + "merge": "Zlúčiť cykly (spojiť fragmenty)", + "delete": "Odstrániť cykly" + } + }, + "split_mode": { + "options": { + "auto": "Automaticky zistiť medzery nečinnosti", + "manual": "Ručné časové značky" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Údržba: opätovné spracovanie a optimalizácia údajov", + "clear_debug_data": "Vymazať údaje ladenia (uvoľniť miesto)", + "wipe_history": "Vymazať VŠETKY údaje pre toto zariadenie (nevratné)", + "export_import": "Export/import JSON s nastaveniami (kopírovať/prilepiť)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportovať všetky údaje", + "import": "Importovať/zlúčiť údaje" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatické označovanie starých cyklov", + "select_cycle_to_label": "Označiť konkrétny cyklus", + "select_cycle_to_delete": "Odstrániť cyklus", + "interactive_editor": "Interaktívny editor zlúčenia/rozdelenia", + "trim_cycle": "Orezať dáta cyklu" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Vytvoriť nový profil", + "edit_profile": "Upraviť/premenovať profil", + "delete_profile": "Odstrániť profil", + "profile_stats": "Štatistika profilu", + "cleanup_profile": "Vyčistiť históriu – graf a vymazanie", + "assign_phases": "Priradiť fázové rozsahy" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Vytvoriť novú fázu", + "edit_custom_phase": "Upraviť fázu", + "delete_custom_phase": "Odstrániť fázu" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Pridať fázový rozsah", + "edit_range": "Upraviť fázový rozsah", + "delete_range": "Odstrániť fázový rozsah", + "clear_ranges": "Vymazať všetky rozsahy", + "auto_detect_ranges": "Automatická detekcia fáz", + "save_ranges": "Uložiť a vrátiť sa" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Obnoviť stav", + "stop_recording": "Zastaviť nahrávanie (uložiť a spracovať)", + "start_recording": "Spustiť nové nahrávanie", + "process_recording": "Spracovať posledný záznam (orezať a uložiť)", + "discard_recording": "Zahodiť posledný záznam" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Vytvoriť nový profil", + "existing_profile": "Pridať do existujúceho profilu", + "discard": "Zahodiť záznam" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potvrdiť – správna detekcia", + "correct": "Opraviť – vyberte program/trvanie", + "ignore": "Ignorovať – falošne pozitívny/hlučný cyklus", + "delete": "Odstrániť – vymazať z histórie" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Pomenovať a použiť fázy", + "cancel": "Vrátiť sa späť bez zmien" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Nastaviť počiatočný bod", + "set_end": "Nastaviť koncový bod", + "reset": "Obnoviť na plnú dĺžku", + "apply": "Použiť orezanie", + "cancel": "Zrušiť" + } + }, + "device_type": { + "options": { + "washing_machine": "Práčka", + "dryer": "Sušička", + "washer_dryer": "Kombinovaná práčka so sušičkou", + "dishwasher": "Umývačka riadu", + "coffee_machine": "Kávovar (zastaraný)", + "ev": "Elektrické vozidlo (zastarané)", + "air_fryer": "Vzduchová fritéza", + "heat_pump": "Tepelné čerpadlo (zastarané)", + "bread_maker": "Pekáreň na chlieb", + "pump": "Čerpadlo / Kalové čerpadlo", + "oven": "Rúra (zastaraná)", + "other": "Iné (pokročilé)" + } + } + }, + "services": { + "label_cycle": { + "name": "Označiť cyklus", + "description": "Priraďte existujúci profil k minulému cyklu alebo odstráňte štítok.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky na označenie." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu, ktorý sa má označiť." + }, + "profile_name": { + "name": "Názov profilu", + "description": "Názov existujúceho profilu (vytvorte profily v ponuke Spravovať profily). Ak chcete štítok odstrániť, nechajte pole prázdne." + } + } + }, + "create_profile": { + "name": "Vytvoriť profil", + "description": "Vytvorte nový profil (samostatný alebo založený na referenčnom cykle).", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky." + }, + "profile_name": { + "name": "Názov profilu", + "description": "Názov nového profilu (napr. 'Silno znečistené', 'Jemné')." + }, + "reference_cycle_id": { + "name": "ID referenčného cyklu", + "description": "Voliteľné ID cyklu, na ktorom je založený tento profil." + } + } + }, + "delete_profile": { + "name": "Odstrániť profil", + "description": "Odstráňte profil a voliteľne zrušte označenie cyklov pomocou neho.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky." + }, + "profile_name": { + "name": "Názov profilu", + "description": "Profil, ktorý chcete odstrániť." + }, + "unlabel_cycles": { + "name": "Zrušiť označenie cyklov", + "description": "Odstráňte štítok profilu z cyklov používajúcich tento profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatické označovanie starých cyklov", + "description": "Spätne označte neoznačené cykly pomocou porovnávania profilu.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky." + }, + "confidence_threshold": { + "name": "Prah spoľahlivosti", + "description": "Minimálna spoľahlivosť zhody (0,50–0,95) na použitie štítkov." + } + } + }, + "export_config": { + "name": "Exportovať konfiguráciu", + "description": "Exportujte profily, cykly a nastavenia tohto zariadenia do súboru JSON (na server).", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky na export." + }, + "path": { + "name": "Cesta", + "description": "Voliteľná absolútna cesta k súboru na zápis (predvolená je /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importovať konfiguráciu", + "description": "Importujte profily, cykly a nastavenia pre toto zariadenie z exportného súboru JSON (nastavenia môžu byť prepísané).", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky, do ktorého sa má importovať." + }, + "path": { + "name": "Cesta", + "description": "Absolútna cesta k exportovanému súboru JSON na import." + } + } + }, + "submit_cycle_feedback": { + "name": "Odoslať spätnú väzbu cyklu", + "description": "Po dokončení cyklu potvrďte alebo opravte automaticky rozpoznaný program. Zadajte buď `entry_id` (rozšírené) alebo `device_id` (odporúčané).", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie WashData (odporúča sa namiesto entry_id)." + }, + "entry_id": { + "name": "ID vstupu", + "description": "ID konfiguračnej položky pre zariadenie (alternatíva k device_id)." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu zobrazené v upozornení/záznamoch spätnej väzby." + }, + "user_confirmed": { + "name": "Potvrdiť zistený program", + "description": "Nastavte true, ak je zistený program správny." + }, + "corrected_profile": { + "name": "Opravený profil", + "description": "Ak nie je potvrdené, zadajte správny názov profilu/programu." + }, + "corrected_duration": { + "name": "Opravené trvanie (sekundy)", + "description": "Voliteľné opravené trvanie v sekundách." + }, + "notes": { + "name": "Poznámky", + "description": "Nepovinné poznámky o tomto cykle." + } + } + }, + "record_start": { + "name": "Zaznamenať začiatok cyklu", + "description": "Spustite manuálne zaznamenávanie čistého cyklu (obchádza všetku logiku porovnávania).", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky na nahrávanie." + } + } + }, + "record_stop": { + "name": "Zastaviť záznam cyklu", + "description": "Zastavte manuálne nahrávanie.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie práčky na zastavenie nahrávania." + } + } + }, + "trim_cycle": { + "name": "Orezať cyklus", + "description": "Orezať údaje o spotrebe z minulého cyklu na konkrétne časové okno.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie WashData." + }, + "cycle_id": { + "name": "ID cyklu", + "description": "ID cyklu, ktorý sa má orezať." + }, + "trim_start_s": { + "name": "Začiatok orezania (sekundy)", + "description": "Uchovávajte údaje od tohto posunu v sekundách. Predvolená hodnota: 0." + }, + "trim_end_s": { + "name": "Koniec orezania (sekundy)", + "description": "Uchovávajte údaje až do tohto posunu v sekundách. Predvolená hodnota = celé trvanie." + } + } + }, + "pause_cycle": { + "name": "Pozastaviť cyklus", + "description": "Pozastavte aktívny cyklus pre zariadenie WashData.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie WashData pozastavte." + } + } + }, + "resume_cycle": { + "name": "Obnoviť cyklus", + "description": "Obnovte pozastavený cyklus pre zariadenie WashData.", + "fields": { + "device_id": { + "name": "Zariadenie", + "description": "Zariadenie WashData na obnovenie." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "V prevádzke" + }, + "match_ambiguity": { + "name": "Nejednoznačnosť porovnania" + } + }, + "sensor": { + "washer_state": { + "name": "Stav", + "state": { + "off": "Vypnuté", + "idle": "Nečinný", + "starting": "Spúšťa sa", + "running": "V prevádzke", + "paused": "Pozastavené", + "user_paused": "Pozastavené používateľom", + "ending": "Ukončuje sa", + "finished": "Dokončené", + "anti_wrinkle": "Ochrana proti vráskam", + "delay_wait": "Čaká sa na spustenie", + "interrupted": "Prerušený", + "force_stopped": "Nútene zastavené", + "rinse": "Oplachovanie", + "unknown": "Neznámy", + "clean": "Čistý" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Zostávajúci čas" + }, + "total_duration": { + "name": "Celkové trvanie" + }, + "cycle_progress": { + "name": "Priebeh" + }, + "current_power": { + "name": "Aktuálny výkon" + }, + "elapsed_time": { + "name": "Uplynulý čas" + }, + "debug_info": { + "name": "Informácie o ladení" + }, + "match_confidence": { + "name": "Spoľahlivosť porovnania" + }, + "top_candidates": { + "name": "Najlepší kandidáti", + "state": { + "none": "žiadne" + } + }, + "current_phase": { + "name": "Aktuálna fáza" + }, + "wash_phase": { + "name": "Fáza" + }, + "profile_cycle_count": { + "name": "Počet cyklov profilu {profile_name}", + "unit_of_measurement": "cyklov" + }, + "suggestions": { + "name": "Dostupné navrhované nastavenia" + }, + "pump_runs_today": { + "name": "Pumpa beží (posledných 24 h)" + }, + "cycle_count": { + "name": "Počet cyklov" + } + }, + "select": { + "program_select": { + "name": "Program cyklu", + "state": { + "auto_detect": "Automatická detekcia" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Vynútiť koniec cyklu" + }, + "pause_cycle": { + "name": "Pozastaviť cyklus" + }, + "resume_cycle": { + "name": "Obnoviť cyklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Vyžaduje sa platný identifikátor zariadenia." + }, + "cycle_id_required": { + "message": "Vyžaduje sa platný cycle_id." + }, + "profile_name_required": { + "message": "Vyžaduje sa platný názov profilu." + }, + "device_not_found": { + "message": "Zariadenie sa nenašlo." + }, + "no_config_entry": { + "message": "Pre zariadenie sa nenašla žiadna konfiguračná položka." + }, + "integration_not_loaded": { + "message": "Integrácia pre toto zariadenie nie je načítaná." + }, + "cycle_not_found_or_no_power": { + "message": "Cyklus sa nenašiel alebo nemá žiadne údaje o napájaní." + }, + "trim_failed_empty_window": { + "message": "Orezanie zlyhalo – cyklus sa nenašiel, žiadne údaje o napájaní alebo výsledné okno je prázdne." + }, + "trim_invalid_range": { + "message": "trim_end_s musí byť väčšie ako trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/sl.json b/custom_components/ha_washdata/translations/sl.json new file mode 100644 index 0000000..4586a1f --- /dev/null +++ b/custom_components/ha_washdata/translations/sl.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Nastavitev WashData", + "description": "Konfigurirajte svoj pralni stroj ali drugo napravo.\n\nPotreben je senzor moči.\n\n**Nato boste pozvani, ali želite ustvariti svoj prvi profil.**", + "data": { + "name": "Ime naprave", + "device_type": "Vrsta naprave", + "power_sensor": "Senzor moči", + "min_power": "Najmanjši prag moči (W)" + }, + "data_description": { + "name": "Prijazno ime za to napravo (npr. \"Pralni stroj\", \"Pomivalni stroj\").", + "device_type": "Kakšna vrsta naprave je to? Pomaga pri prilagajanju odkrivanja in označevanja.", + "power_sensor": "Entiteta senzorja, ki poroča o porabi energije v realnem času (v vatih) iz vašega pametnega vtiča.", + "min_power": "Odčitki moči nad tem pragom (v vatih) pomenijo, da naprava deluje. Začnite z 2 W za večino naprav." + } + }, + "first_profile": { + "title": "Ustvari prvi profil", + "description": "**Cikel** je celoten zagon vaše naprave (npr. količina perila). **Profil** združuje te cikle glede na vrsto (npr. 'Bombaž', 'Hitro pranje'). Ustvarjanje profila zdaj pomaga integraciji takoj oceniti trajanje.\n\nTa profil lahko ročno izberete v kontrolnikih naprave, če ni samodejno zaznan.\n\n**Pustite »Ime profila« prazno, če želite preskočiti ta korak.**", + "data": { + "profile_name": "Ime profila (neobvezno)", + "manual_duration": "Predvideno trajanje (minute)" + } + } + }, + "error": { + "cannot_connect": "Povezava ni uspela", + "invalid_auth": "Neveljavna avtentikacija", + "unknown": "Nepričakovana napaka" + }, + "abort": { + "already_configured": "Naprava je že konfigurirana" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Preglejte povratne informacije o učenju", + "settings": "Nastavitve", + "notifications": "Obvestila", + "manage_cycles": "Upravljanje ciklov", + "manage_profiles": "Upravljanje profilov", + "manage_phase_catalog": "Upravljanje kataloga faz", + "record_cycle": "Snemanje cikla (ročno)", + "diagnostics": "Diagnostika in vzdrževanje" + } + }, + "apply_suggestions": { + "title": "Kopiraj predlagane vrednosti", + "description": "To bo kopiralo trenutno predlagane vrednosti v vaše shranjene nastavitve. To je enkratno dejanje in ne omogoča samodejnih prepisov.\n\nPredlagane vrednosti za kopiranje:\n{suggested}", + "data": { + "confirm": "Potrdite dejanje kopiranja" + } + }, + "apply_suggestions_confirm": { + "title": "Pregled predlaganih sprememb", + "description": "WashData je našel **{pending_count}** predlagane vrednosti za uporabo.\n\nSpremembe:\n{changes}\n\n✅ Označite spodnje polje in kliknite **Pošlji**, da uporabite in takoj shranite te spremembe.\n❌ Pustite nepotrjeno in kliknite **Pošlji** za preklic in vrnitev.", + "data": { + "confirm_apply_suggestions": "Uporabite in shranite te spremembe" + } + }, + "settings": { + "title": "Nastavitve", + "description": "{deprecation_warning}Prilagodite pragove zaznavanja, učenje in napredno vedenje. Privzete nastavitve delujejo dobro za večino konfiguracij.\n\nTrenutno razpoložljive predlagane nastavitve: {suggestions_count}.\nČe je ta vrednost nad 0, odprite Napredne nastavitve in uporabite Uporabi predlagane vrednosti za pregled priporočil.", + "data": { + "apply_suggestions": "Uporabi predlagane vrednosti", + "device_type": "Vrsta naprave", + "power_sensor": "Entiteta senzorja moči", + "min_power": "Najmanjša moč (W)", + "off_delay": "Zamik konca cikla (s)", + "notify_actions": "Dejanja obveščanja", + "notify_people": "Odložite dostavo, dokler ti ljudje niso doma", + "notify_only_when_home": "Odloži obvestila, dokler izbrana oseba ni doma", + "notify_fire_events": "Sproži dogodke avtomatizacije", + "notify_start_services": "Začetek cikla - Cilji obvestil", + "notify_finish_services": "Konec cikla - Cilji obvestil", + "notify_live_services": "Napredek v živo - cilji obvestil", + "notify_before_end_minutes": "Obvestilo pred zaključkom (minut pred koncem)", + "notify_title": "Naslov obvestila", + "notify_icon": "Ikona obvestila", + "notify_start_message": "Oblika sporočila ob zagonu", + "notify_finish_message": "Oblika sporočila ob zaključku", + "notify_pre_complete_message": "Oblika sporočila pred dokončanjem", + "show_advanced": "Urejanje naprednih nastavitev (naslednji korak)" + }, + "data_description": { + "apply_suggestions": "Označite to polje, če želite v obrazec kopirati vrednosti, ki jih predlaga učni algoritem. Preglejte pred shranjevanjem.\n\nPredlagano (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Izberite vrsto aparata (Pralni stroj, Sušilni stroj, Pomivalni stroj, Kavni avtomat, EV). Ta oznaka se shrani z vsakim ciklom za boljšo organizacijo.", + "power_sensor": "Entiteta senzorja, ki poroča o moči v realnem času (v vatih). To lahko kadar koli spremenite, ne da bi pri tem izgubili zgodovinske podatke – uporabno, če zamenjate pametni vtič.", + "min_power": "Odčitki moči nad tem pragom (v vatih) pomenijo, da naprava deluje. Začnite z 2 W za večino naprav.", + "off_delay": "Sekunde mora izravnana moč ostati pod pragom zaustavitve, da se označi zaključek. Privzeto: 120 s.", + "notify_actions": "Izbirno zaporedje dejanj Home Assistant za zagon ob obvestilih (skripti, obvestilo, TTS itd.). Če je nastavljeno, se dejanja izvajajo pred nadomestno storitvijo obveščanja.", + "notify_people": "Entitete oseb, ki se uporabljajo za preverjanje prisotnosti. Ko je omogočeno, je dostava obvestil zakasnjena, dokler vsaj ena izbrana oseba ni doma.", + "notify_only_when_home": "Če je omogočeno, odloži obvestila, dokler vsaj ena izbrana oseba ni doma.", + "notify_fire_events": "Če je omogočeno, sproži dogodke Home Assistant za začetek in konec cikla (ha_washdata_cycle_started in ha_washdata_cycle_ended), da omogoči avtomatizacijo.", + "notify_start_services": "Ena ali več storitev obveščanja, da vas opozorijo, ko se začne cikel. Pustite prazno, če želite preskočiti obvestila o začetku.", + "notify_finish_services": "Ena ali več storitev obveščanja, da vas opozorijo, ko se cikel konča ali bliža koncu. Pustite prazno, če želite preskočiti obvestila o zaključku.", + "notify_live_services": "Ena ali več storitev obveščanja za prejemanje posodobitev napredka v živo med potekom cikla (samo mobilna spremljevalna aplikacija). Pustite prazno, če želite preskočiti posodobitve v živo.", + "notify_before_end_minutes": "Minute pred predvidenim koncem cikla za sprožitev obvestila. Nastavite na 0, da onemogočite. Privzeto: 0.", + "notify_title": "Naslov po meri za obvestila. Podpira nadomestni znak {device}.", + "notify_icon": "Ikona za uporabo pri obvestilih (npr. mdi:washing-machine). Pustite prazno za pošiljanje brez ikone.", + "notify_start_message": "Sporočilo, poslano ob začetku cikla. Podpira nadomestni znak {device}.", + "notify_finish_message": "Sporočilo, poslano, ko se cikel konča. Podpira nadomestne znake {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Besedilo ponavljajočih se posodobitev napredka v živo med potekom cikla. Podpira nadomestne oznake {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Obvestila", + "description": "Konfigurirajte kanale za obveščanje, predloge in izbirne posodobitve napredka v živo.", + "data": { + "notify_actions": "Dejanja obveščanja", + "notify_people": "Odložite dostavo, dokler ti ljudje niso doma", + "notify_only_when_home": "Odloži obvestila, dokler izbrana oseba ni doma", + "notify_fire_events": "Sproži dogodke avtomatizacije", + "notify_start_services": "Začetek cikla - Cilji obvestil", + "notify_finish_services": "Konec cikla - Cilji obvestil", + "notify_live_services": "Napredek v živo - cilji obvestil", + "notify_before_end_minutes": "Obvestilo pred zaključkom (minut pred koncem)", + "notify_live_interval_seconds": "Interval posodobitve v živo (sekunde)", + "notify_live_overrun_percent": "Dovoljenje za prekoračitev posodobitve v živo (%)", + "notify_live_chronometer": "Odštevalnik kronometra", + "notify_title": "Naslov obvestila", + "notify_icon": "Ikona obvestila", + "notify_start_message": "Oblika sporočila ob zagonu", + "notify_finish_message": "Oblika sporočila ob zaključku", + "notify_pre_complete_message": "Oblika sporočila pred dokončanjem", + "energy_price_entity": "Cena energije – subjekt (neobvezno)", + "energy_price_static": "Cena energije - statična vrednost (neobvezno)", + "go_back": "Vrni se brez shranjevanja", + "notify_reminder_message": "Oblika sporočila opomnika", + "notify_timeout_seconds": "Samodejno opusti po (sekundah)", + "notify_channel": "Kanal za obvestila (stanje/v živo/opomnik)", + "notify_finish_channel": "Končan obvestilni kanal" + }, + "data_description": { + "go_back": "Označite to možnost in kliknite Pošlji, da zavržete vse zgornje spremembe in se vrnete v glavni meni.", + "notify_actions": "Izbirno zaporedje dejanj Home Assistant za zagon ob obvestilih (skripti, obvestilo, TTS itd.). Če je nastavljeno, se dejanja izvajajo pred nadomestno storitvijo obveščanja.", + "notify_people": "Entitete oseb, ki se uporabljajo za preverjanje prisotnosti. Ko je omogočeno, je dostava obvestil zakasnjena, dokler vsaj ena izbrana oseba ni doma.", + "notify_only_when_home": "Če je omogočeno, odloži obvestila, dokler vsaj ena izbrana oseba ni doma.", + "notify_fire_events": "Če je omogočeno, sproži dogodke Home Assistant za začetek in konec cikla (ha_washdata_cycle_started in ha_washdata_cycle_ended), da omogoči avtomatizacijo.", + "notify_start_services": "Ena ali več storitev obveščanja, ki vas opozorijo, ko se začne cikel (npr. notify.mobile_app_my_phone). Pustite prazno, če želite preskočiti obvestila o začetku.", + "notify_finish_services": "Ena ali več storitev obveščanja, da vas opozorijo, ko se cikel konča ali bliža koncu. Pustite prazno, če želite preskočiti obvestila o zaključku.", + "notify_live_services": "Ena ali več storitev obveščanja za prejemanje posodobitev napredka v živo med potekom cikla (samo mobilna spremljevalna aplikacija). Pustite prazno, če želite preskočiti posodobitve v živo.", + "notify_before_end_minutes": "Minute pred predvidenim koncem cikla za sprožitev obvestila. Nastavite na 0, da onemogočite. Privzeto: 0.", + "notify_live_interval_seconds": "Kako pogosto se pošiljajo posodobitve napredka med delovanjem. Nižje vrednosti so bolj odzivne, vendar porabijo več obvestil. Velja najmanj 30 sekund - vrednosti pod 30 s se samodejno zaokrožijo na 30 s.", + "notify_live_overrun_percent": "Varnostna meja nad ocenjenim številom posodobitev za dolge/prekoračljive cikle (na primer 20 % omogoča 1,2-kratno ocenjeno posodobitev).", + "notify_live_chronometer": "Ko je omogočeno, vsaka posodobitev v živo vključuje odštevanje kronometra do predvidenega časa cilja. Časovnik obvestil samodejno odšteka v napravi med posodobitvami (samo spremljevalna aplikacija za Android).", + "notify_title": "Naslov po meri za obvestila. Podpira nadomestni znak {device}.", + "notify_icon": "Ikona za uporabo pri obvestilih (npr. mdi:washing-machine). Pustite prazno za pošiljanje brez ikone.", + "notify_start_message": "Sporočilo, poslano ob začetku cikla. Podpira nadomestni znak {device}.", + "notify_finish_message": "Sporočilo, poslano, ko se cikel konča. Podpira nadomestne znake {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Izberite številsko entiteto, ki vsebuje trenutno ceno električne energije (npr. sensor.electricity_price, input_number.energy_rate). Ko je nastavljeno, omogoči ogrado {cost}. Ima prednost pred statično vrednostjo, če sta konfigurirani obe.", + "energy_price_static": "Fiksna cena električne energije za kWh. Ko je nastavljeno, omogoči ogrado {cost}. Prezrto, če je entiteta konfigurirana zgoraj. V predlogo sporočila dodajte simbol svoje valute, npr. {cost} €.", + "notify_reminder_message": "Besedilo enkratnega opomnika je poslalo nastavljeno število minut pred predvidenim koncem. Za razliko od zgornjih ponavljajočih se posodobitev v živo. Podpira nadomestne oznake {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Samodejno opusti obvestila po toliko sekundah (posredovana kot 'časovna omejitev' spremljevalne aplikacije). Nastavite na 0, da jih obdržite, dokler jih ne zavržete. Privzeto: 0.", + "notify_channel": "Android kanal za obvestila spremljevalne aplikacije za stanje, napredek v živo in obvestila o opomnikih. Zvok in pomembnost kanala se konfigurirata v spremljevalni aplikaciji, ko prvič uporabite ime kanala - WashData nastavi samo ime kanala. Pustite prazno za privzeto aplikacijo.", + "notify_finish_channel": "Ločite Android kanal za končano opozorilo (uporabljata ga tudi opomnik in nagovarjanje, ki čaka na perilo), da bo lahko imelo svoj zvok. Pustite prazno za ponovno uporabo zgornjega kanala.", + "notify_pre_complete_message": "Besedilo ponavljajočih se posodobitev napredka v živo med potekom cikla. Podpira nadomestne oznake {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Napredne nastavitve", + "description": "Prilagodite pragove zaznavanja, učenje in napredno vedenje. Privzete nastavitve delujejo dobro za večino konfiguracij.", + "sections": { + "suggestions_section": { + "name": "Predlagane nastavitve", + "description": "Na voljo je {suggestions_count} naučenih predlogov. Označite 'Uporabi predlagane vrednosti', če želite pred shranjevanjem pregledati predlagane spremembe.", + "data": { + "apply_suggestions": "Uporabi predlagane vrednosti" + }, + "data_description": { + "apply_suggestions": "Označite to polje, če želite odpreti korak pregleda za predlagane vrednosti iz učnega algoritma. Nič se samodejno ne shrani.\n\nPredlagano (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Zaznavanje in pragovi moči", + "description": "Določa, kdaj je naprava obravnavana kot VKLOPLJENA ali IZKLOPLJENA, energijske meje in časovne prehode med stanji.", + "data": { + "start_duration_threshold": "Trajanje začetnega praga (s)", + "start_energy_threshold": "Energijska vrata pri zagonu (Wh)", + "completion_min_seconds": "Minimalni čas delovanja za dokončanje (sekunde)", + "start_threshold_w": "Začetni prag (W)", + "stop_threshold_w": "Prag zaustavitve (W)", + "end_energy_threshold": "Energijska vrata pri koncu (Wh)", + "running_dead_zone": "Mrtva cona pri zagonu (sekunde)", + "end_repeat_count": "Število ponovitev pogoja za konec", + "min_off_gap": "Najmanjši razmik med cikli (s)", + "sampling_interval": "Interval vzorčenja (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorirajte kratke skoke moči, ki so krajši od tega trajanja (v sekundah). Filtrira zagonske konice (npr. 60 W za 2 s), preden se začne pravi cikel. Privzeto: 5 s.", + "start_energy_threshold": "Cikel mora zbrati vsaj toliko energije (Wh) med fazo ZAGONA, da je potrjen. Privzeto: 0,005 Wh.", + "completion_min_seconds": "Cikli, krajši od tega, bodo označeni kot »prekinjeni«, tudi če se končajo naravno. Privzeto: 600 s.", + "start_threshold_w": "Za ZAČETEK cikla se mora moč dvigniti NAD ta prag. Nastavite večjo moč od moči v stanju pripravljenosti. Privzeto: min_power + 1 W.", + "stop_threshold_w": "Za KONEC cikla mora moč pasti POD tem pragom. Nastavite tik nad ničlo, da se izognete hrupu, ki sproži napačne konce. Privzeto: 0,6 × min_power.", + "end_energy_threshold": "Cikel se konča samo, če je energija v zadnjem oknu zamika izklopa pod to vrednostjo (Wh). Preprečuje napačne konce, če moč niha blizu ničle. Privzeto: 0,05 Wh.", + "running_dead_zone": "Po začetku cikla ignorirajte padce moči toliko sekund, da preprečite lažno zaznavo konca med fazo začetnega vrtenja. Nastavite na 0, da onemogočite. Privzeto: 0 s.", + "end_repeat_count": "Kolikokrat mora biti končni pogoj (moč pod pragom za off_delay) zaporedoma izpolnjen pred zaključkom cikla. Višje vrednosti preprečujejo napačne konce med premori. Privzeto: 1.", + "min_off_gap": "Če se cikel začne v toliko sekundah po koncu prejšnjega, bodo ZDRUŽENI v en sam cikel.", + "sampling_interval": "Najmanjši interval vzorčenja v sekundah. Posodobitve, hitrejše od te hitrosti, bodo prezrte. Privzeto: 30 s (2 s za pralne, pralno-sušilne in pomivalne stroje)." + } + }, + "matching_section": { + "name": "Ujemanje profilov in učenje", + "description": "Določa, kako agresivno WashData primerja tekoče cikle z naučenimi profili in kdaj naj zahteva povratne informacije.", + "data": { + "profile_match_min_duration_ratio": "Najmanjše razmerje trajanja ujemanja profila (0,1–1,0)", + "profile_match_interval": "Interval ujemanja profila (sekunde)", + "profile_match_threshold": "Prag ujemanja profila", + "profile_unmatch_threshold": "Prag neujemanja profila", + "auto_label_confidence": "Zaupanje samodejnega označevanja (0–1)", + "learning_confidence": "Zaupanje za zahtevo po povratnih informacijah (0–1)", + "suppress_feedback_notifications": "Zavrni obvestila o povratnih informacijah", + "duration_tolerance": "Toleranca trajanja (0–0,5)", + "smoothing_window": "Okno za glajenje (vzorci)", + "profile_duration_tolerance": "Toleranca trajanja ujemanja profila (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Najmanjše razmerje trajanja za ujemanje profila. Aktivni cikel mora biti vsaj ta delež trajanja profila, da se ujema. Nižje = zgodnejše ujemanje. Privzeto: 0,50 (50 %).", + "profile_match_interval": "Kako pogosto (v sekundah) poskušati ujemati profile med ciklom. Nižje vrednosti zagotavljajo hitrejše zaznavanje, vendar porabijo več procesorja. Privzeto: 300 s (5 minut).", + "profile_match_threshold": "Najmanjša ocena podobnosti DTW (0,0–1,0), ki je potrebna, da se profil obravnava kot ujemanje. Višje = strožje ujemanje, manj lažnih pozitivnih rezultatov. Privzeto: 0,4.", + "profile_unmatch_threshold": "Ocena podobnosti DTW (0,0–1,0), pod katero se predhodno ujemajoči profil zavrne. Biti mora nižji od praga ujemanja, da preprečite utripanje. Privzeto: 0,35.", + "auto_label_confidence": "Samodejno označi cikle na ali nad to stopnjo zaupanja ob zaključku (0,0–1,0).", + "learning_confidence": "Minimalno zaupanje za zahtevo po preverjanju uporabnika prek dogodka (0,0–1,0).", + "suppress_feedback_notifications": "Ko je omogočen, bo WashData še vedno interno sledil in zahteval povratne informacije, vendar ne bo prikazoval trajnih obvestil, ki bi zahtevala potrditev ciklov. Uporabno, kadar so cikli vedno pravilno zaznani in se vam zdijo pozivi moteči.", + "duration_tolerance": "Toleranca za ocene preostalega časa med ciklom (0,0–0,5 ustreza 0–50 %).", + "smoothing_window": "Število nedavnih odčitkov moči, uporabljenih za glajenje drsečega povprečja.", + "profile_duration_tolerance": "Toleranca, uporabljena pri ujemanju profila za skupno varianco trajanja (0,0–0,5). Privzeto vedenje: ±25 %." + } + }, + "timing_section": { + "name": "Časovanje, vzdrževanje in odpravljanje napak", + "description": "Watchdog, ponastavitev napredka, samodejno vzdrževanje in prikaz entitet za odpravljanje napak.", + "data": { + "watchdog_interval": "Interval nadzornika (sekunde)", + "no_update_active_timeout": "Časovna omejitev brez posodobitve (s)", + "progress_reset_delay": "Zakasnitev ponastavitve napredka (sekunde)", + "auto_maintenance": "Omogoči samodejno vzdrževanje", + "expose_debug_entities": "Razkrij entitete za odpravljanje napak", + "save_debug_traces": "Shrani sledi odpravljanja napak" + }, + "data_description": { + "watchdog_interval": "Sekunde med nadzornikovimi pregledi med delovanjem. Privzeto: 5 s. OPOZORILO: Zagotovite, da je ta VIŠJI od intervala posodabljanja vašega senzorja, da se izognete lažnim zaustavitvam (npr. če se senzor posodablja vsakih 60 s, uporabite 65 s+).", + "no_update_active_timeout": "Za senzorje objave ob spremembi: prisilno zaključite AKTIVNI cikel samo, če toliko sekund ne prispe nobena posodobitev. Dokončanje nizke porabe še vedno uporablja zamik izklopa.", + "progress_reset_delay": "Po končanem ciklu (100 %) počakajte toliko sekund nedejavnosti, preden ponastavite napredek na 0 %. Privzeto: 300 s.", + "auto_maintenance": "Omogoči samodejno vzdrževanje (popravi vzorce, spoji fragmente).", + "expose_debug_entities": "Pokaži napredne senzorje (zaupanje, faza, dvoumnost) za odpravljanje napak.", + "save_debug_traces": "Shranite podrobne podatke o razvrstitvi in sledenju moči v zgodovino (poveča uporabo pomnilnika)." + } + }, + "anti_wrinkle_section": { + "name": "Zaščita pred gubami (sušilniki)", + "description": "Prezrite vrtenja bobna po koncu cikla, da preprečite fantomske cikle. Samo za sušilnik / pralno-sušilni stroj.", + "data": { + "anti_wrinkle_enabled": "Zaščita pred gubami (samo za sušilni stroj)", + "anti_wrinkle_max_power": "Največja moč pri zaščiti pred gubami (W)", + "anti_wrinkle_max_duration": "Največje trajanje faze zaščite pred gubami (s)", + "anti_wrinkle_exit_power": "Izhodna moč pri zaščiti pred gubami (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Prezrite kratka vrtenja bobna z nizko močjo po koncu cikla, da preprečite dvojne cikle (samo sušilni/pralno-sušilni stroj).", + "anti_wrinkle_max_power": "Izbruhi pri tej moči ali pod njo se obravnavajo kot vrtenja pri zaščiti pred gubami namesto kot novi cikli. Velja le, če je zaščita pred gubami omogočena.", + "anti_wrinkle_max_duration": "Največja dolžina izbruha, ki se obravnava kot vrtenje pri zaščiti pred gubami. Velja le, če je zaščita pred gubami omogočena.", + "anti_wrinkle_exit_power": "Če moč pade pod to raven, takoj zapustite zaščito pred gubami (resnično izklopi). Velja le, če je zaščita pred gubami omogočena." + } + }, + "delay_start_section": { + "name": "Zaznavanje zakasnjenega začetka", + "description": "Trajno pripravljenost z nizko porabo obravnavajte kot stanje 'Čakanje na začetek', dokler se cikel dejansko ne začne.", + "data": { + "delay_start_detect_enabled": "Omogoči zaznavanje zakasnjenega začetka", + "delay_confirm_seconds": "Zakasnjen začetek: čas potrditve pripravljenosti (s)", + "delay_timeout_hours": "Zakasnitev zagona: Najdaljši čakalni čas (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Ko je omogočeno, je kot zakasnjen zagon zaznan kratek skok nizke porabe energije, ki mu sledi vzdržljivo napajanje v stanju pripravljenosti. Senzor stanja med zakasnitvijo prikazuje 'Čakanje na začetek'. Čas programa ali napredek se ne spremljata, dokler se cikel dejansko ne začne. Zahteva, da mora biti start_threshold_w nastavljen nad močjo odvajanja.", + "delay_confirm_seconds": "Moč mora ostati med pragom ustavitve (W) in pragom zagona (W) toliko sekund, preden WashData preide v stanje 'Čakanje na začetek'. To filtrira kratke konice med premikanjem po menijih. Privzeto: 60 s.", + "delay_timeout_hours": "Varnostna časovna omejitev: če je stroj po toliko urah še vedno v stanju »Čakanje na zagon«, se WashData ponastavi na Off. Privzeto: 8 h." + } + }, + "external_triggers_section": { + "name": "Zunanji sprožilci, vrata in premor", + "description": "Izbirni zunanji senzor za konec cikla, senzor vrat za zaznavanje premora / praznjenja ter stikalo za izklop napajanja med premorom.", + "data": { + "external_end_trigger_enabled": "Omogoči zunanji sprožilec konca cikla", + "external_end_trigger": "Zunanji senzor konca cikla", + "external_end_trigger_inverted": "Obrni logiko sprožilca (sproži ob izklopu)", + "door_sensor_entity": "Senzor za vrata", + "pause_cuts_power": "Prekinite napajanje med premorom", + "switch_entity": "Preklopna entiteta (za premor izklop napajanja)", + "notify_unload_delay_minutes": "Zakasnitev obvestila o čakanju na perilo (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Omogoči nadzor zunanjega binarnega senzorja za sprožitev zaključka cikla.", + "external_end_trigger": "Izberite entiteto binarnega senzorja. Ko se ta senzor sproži, se trenutni cikel konča s statusom »zaključeno«.", + "external_end_trigger_inverted": "Privzeto se sprožilec aktivira, ko se senzor vklopi. Označite to polje, če želite, da se sproži, ko se senzor namesto tega izklopi.", + "door_sensor_entity": "Izbirni binarni senzor za vrata stroja (vklopljeno = odprto, izklopljeno = zaprto). Ko se vrata odprejo med aktivnim ciklom, bo WashData potrdil, da je cikel namerno zaustavljen. Če so vrata po koncu cikla še zaprta, se stanje spremeni v »Čisto«, dokler se vrata ne odprejo. Opomba: zapiranje vrat NE samodejno nadaljuje zaustavljenega cikla - nadaljevanje je treba sprožiti ročno prek gumba Nadaljuj cikel ali storitve.", + "pause_cuts_power": "Pri premoru prek gumba Pause Cycle ali storitve izklopite tudi konfigurirano entiteto stikala. Učinkuje le, če je za to napravo konfigurirana entiteta stikala.", + "switch_entity": "Preklopna entiteta, ki jo želite preklopiti, ko zaustavite ali nadaljujete. Zahtevano, ko je omogočena možnost »Prekini napajanje ob premoru«.", + "notify_unload_delay_minutes": "Pošljite obvestilo prek kanala za obveščanje o zaključku, ko se cikel konča in vrata niso bila odprta toliko minut (potreben je senzor za vrata). Nastavite na 0, da onemogočite. Privzeto: 60 min." + } + }, + "pump_section": { + "name": "Nadzor črpalke", + "description": "Samo za tip naprave črpalka. Sproži dogodek, če cikel črpalke traja dlje od tega časa.", + "data": { + "pump_stuck_duration": "Opozorilni prag (s) zastoja črpalke (samo črpalka)" + }, + "data_description": { + "pump_stuck_duration": "Če se cikel črpalke po toliko sekundah še vedno izvaja, se enkrat sproži dogodek ha_washdata_pump_stuck. Uporabite to za odkrivanje zagozdenega motorja (običajno delovanje črpalke je pod 60 s). Privzeto: 1800 s (30 min). Samo tip črpalke." + } + }, + "device_link_section": { + "name": "Povezava naprave", + "description": "Po želji lahko povežete to napravo WashData z obstoječo napravo Home Assistant (na primer s pametnim vtičem ali samo napravo). Naprava WashData je nato prikazana kot \"Povezana prek\" te naprave. Pustite prazno, da ostane WashData kot samostojna naprava.", + "data": { + "linked_device": "Povezana naprava" + }, + "data_description": { + "linked_device": "Izberite napravo, s katero želite povezati WashData. To doda sklic »Povezano prek« v register naprave; WashData hrani lastno kartico naprave in entitete. Počistite izbiro, da odstranite povezavo." + } + } + } + }, + "diagnostics": { + "title": "Diagnostika in vzdrževanje", + "description": "Izvedite vzdrževalna dejanja, kot je združevanje razdrobljenih ciklov ali selitev shranjenih podatkov.\n\n**Poraba prostora za shranjevanje**\n\n- Velikost datoteke: {file_size_kb} KB\n- Cikli: {cycle_count}\n- Profili: {profile_count}\n- Sledi odpravljanja napak: {debug_count}", + "menu_options": { + "reprocess_history": "Vzdrževanje: ponovna obdelava in optimizacija podatkov", + "clear_debug_data": "Počisti podatke odpravljanja napak (sprosti prostor)", + "wipe_history": "Izbriši VSE podatke za to napravo (nepovratno)", + "export_import": "Izvoz/uvoz JSON z nastavitvami (kopiraj/prilepi)", + "menu_back": "← Nazaj" + } + }, + "clear_debug_data": { + "title": "Počisti podatke odpravljanja napak", + "description": "Ali ste prepričani, da želite izbrisati vse shranjene sledi odpravljanja napak? To bo sprostilo prostor, vendar bo odstranilo podrobne informacije o uvrstitvi iz preteklih ciklov." + }, + "manage_cycles": { + "title": "Upravljanje ciklov", + "description": "Zadnji cikli:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Samodejno označevanje starih ciklov", + "select_cycle_to_label": "Označi določen cikel", + "select_cycle_to_delete": "Izbriši cikel", + "interactive_editor": "Interaktivni urejevalnik za spajanje/razdelitev", + "trim_cycle_select": "Obreži podatke cikla", + "menu_back": "← Nazaj" + } + }, + "manage_cycles_empty": { + "title": "Upravljanje ciklov", + "description": "Ni zabeleženih še nobenih ciklov.", + "menu_options": { + "auto_label_cycles": "Samodejno označevanje starih ciklov", + "menu_back": "← Nazaj" + } + }, + "manage_profiles": { + "title": "Upravljanje profilov", + "description": "Povzetek profila:\n{profile_summary}", + "menu_options": { + "create_profile": "Ustvari nov profil", + "edit_profile": "Uredi/preimenuj profil", + "delete_profile_select": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Čiščenje zgodovine – graf in brisanje", + "assign_profile_phases_select": "Dodeli fazne razpone", + "menu_back": "← Nazaj" + } + }, + "manage_profiles_empty": { + "title": "Upravljanje profilov", + "description": "Ustvarjen še ni noben profil.", + "menu_options": { + "create_profile": "Ustvari nov profil", + "menu_back": "← Nazaj" + } + }, + "manage_phase_catalog": { + "title": "Upravljanje kataloga faz", + "description": "Katalog faz (privzeto za to napravo + faze po meri):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Ustvari novo fazo", + "phase_catalog_edit_select": "Uredi fazo", + "phase_catalog_delete": "Izbriši fazo", + "menu_back": "← Nazaj" + } + }, + "phase_catalog_create": { + "title": "Ustvari fazo po meri", + "description": "Ustvarite ime in opis faze po meri ter izberite, za katero vrsto naprave velja.", + "data": { + "target_device_type": "Vrsta ciljne naprave", + "phase_name": "Ime faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_edit_select": { + "title": "Uredi fazo po meri", + "description": "Izberite fazo po meri za urejanje.", + "data": { + "phase_name": "Faza po meri" + } + }, + "phase_catalog_edit": { + "title": "Uredi fazo po meri", + "description": "Urejanje faze: {phase_name}", + "data": { + "phase_name": "Ime faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_delete": { + "title": "Izbriši fazo po meri", + "description": "Izberite fazo po meri, ki jo želite izbrisati. Vsi dodeljeni obsegi s to fazo bodo odstranjeni.", + "data": { + "phase_name": "Faza po meri" + } + }, + "assign_profile_phases": { + "title": "Dodeli fazne razpone", + "description": "Predogled faze za profil: **{profile_name}**\n\n{timeline_svg}\n\n**Trenutni razponi:**\n{current_ranges}\n\nUporabite spodnja dejanja za dodajanje, urejanje, brisanje ali shranjevanje razponov.", + "menu_options": { + "assign_profile_phases_add": "Dodaj fazni razpon", + "assign_profile_phases_edit_select": "Uredi fazni razpon", + "assign_profile_phases_delete": "Izbriši fazni razpon", + "phase_ranges_clear": "Počisti vse razpone", + "assign_profile_phases_auto_detect": "Samodejno zaznavanje faz", + "phase_ranges_save": "Shrani in vrni", + "menu_back": "← Nazaj" + } + }, + "assign_profile_phases_add": { + "title": "Dodaj fazni razpon", + "description": "Trenutnemu osnutku dodajte en fazni razpon.", + "data": { + "phase_name": "Faza", + "start_min": "Začetna minuta", + "end_min": "Končna minuta" + } + }, + "assign_profile_phases_edit_select": { + "title": "Uredi fazni razpon", + "description": "Izberite razpon za urejanje.", + "data": { + "range_index": "Fazni razpon" + } + }, + "assign_profile_phases_edit": { + "title": "Uredi fazni razpon", + "description": "Posodobite izbrani fazni razpon.", + "data": { + "phase_name": "Faza", + "start_min": "Začetna minuta", + "end_min": "Končna minuta" + } + }, + "assign_profile_phases_delete": { + "title": "Izbriši fazni razpon", + "description": "Izberite razpon, ki ga želite odstraniti iz osnutka.", + "data": { + "range_index": "Fazni razpon" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Samodejno zaznane faze", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Samodejno zaznanih faz: {detected_count}.** Izberite dejanje spodaj.", + "data": { + "action": "Dejanje" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Poimenujte zaznane faze", + "description": "Profil: **{profile_name}**\n\n**Zaznanih {detected_count} faz:**\n{ranges_summary}\n\nVsaki fazi dodelite ime." + }, + "assign_profile_phases_select": { + "title": "Dodeli fazne razpone", + "description": "Izberite profil za konfiguracijo faznih razponov.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistika profila", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Ustvari nov profil", + "description": "Ustvarite nov profil ročno ali iz preteklega cikla.\n\nPrimeri imen profilov: 'Občutljivo', 'Intenzivno pranje', 'Hitro pranje'", + "data": { + "profile_name": "Ime profila", + "reference_cycle": "Referenčni cikel (neobvezno)", + "manual_duration": "Ročno trajanje (minute)" + } + }, + "edit_profile": { + "title": "Uredi profil", + "description": "Izberite profil, ki ga želite preimenovati", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Uredi podrobnosti profila", + "description": "Trenutni profil: {current_name}", + "data": { + "new_name": "Ime profila", + "manual_duration": "Ročno trajanje (minute)" + } + }, + "delete_profile_select": { + "title": "Izbriši profil", + "description": "Izberite profil za brisanje", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potrdite izbris profila", + "description": "⚠️ S tem boste trajno izbrisali profil: {profile_name}", + "data": { + "unlabel_cycles": "Odstranite oznako iz ciklov, ki uporabljajo ta profil" + } + }, + "auto_label_cycles": { + "title": "Samodejno označevanje starih ciklov", + "description": "Skupno najdenih {total_count} ciklov. Profili: {profiles}", + "data": { + "confidence_threshold": "Najmanjše zaupanje (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Izberite cikel za označevanje", + "description": "✓ = končano, ⚠ = nadaljevanje, ✗ = prekinjeno", + "data": { + "cycle_id": "Cikel" + } + }, + "select_cycle_to_delete": { + "title": "Izbriši cikel", + "description": "⚠️ S tem boste trajno izbrisali izbrani cikel", + "data": { + "cycle_id": "Cikel za brisanje" + } + }, + "label_cycle": { + "title": "Označi cikel", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Novo ime profila (če ustvarjate)" + } + }, + "post_process": { + "title": "Obdelava zgodovine (združi/razdeli)", + "description": "Počistite zgodovino ciklov tako, da združite razdrobljene poteke in razdelite nepravilno združene cikle znotraj časovnega okna. Vnesite število ur za ogled nazaj (za vse uporabite 999999).", + "data": { + "time_range": "Okno za pregled nazaj (ure)", + "gap_seconds": "Vrzel za združitev/razdelitev (sekunde)" + }, + "data_description": { + "time_range": "Število ur za iskanje razdrobljenih ciklov za združitev. Za obdelavo vseh zgodovinskih podatkov uporabite 999999.", + "gap_seconds": "Prag za odločitev o razdelitvi ali združitvi. Vrzeli, KRAJŠE od tega, so združene. Vrzeli, DALJŠE od tega, so razdeljene. Znižajte to vrednost, da prisilite razdelitev cikla, ki je bil nepravilno združen." + } + }, + "cleanup_profile": { + "title": "Počisti zgodovino – izberite profil", + "description": "Izberite profil za vizualizacijo. To bo ustvarilo graf, ki prikazuje vse pretekle cikle za ta profil, da bo lažje prepoznati odstopanja.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Čiščenje zgodovine – graf in brisanje", + "description": "![Graf]({graph_url})\n\n**Vizualizacija {profile_name}**\n\nGraf prikazuje posamezne cikle kot barvne črte. Prepoznajte odstopanja (črte, ki so daleč od skupine) in jih poiščite po barvi na spodnjem seznamu, da jih izbrišete.\n\n**Izberite cikle za TRAJNO brisanje:**", + "data": { + "cycles_to_delete": "Cikli za brisanje (preverite ujemajoče se barve)" + } + }, + "interactive_editor": { + "title": "Interaktivni urejevalnik za spajanje/razdelitev", + "description": "Izberite ročno dejanje, ki ga želite izvesti na zgodovini cikla.", + "menu_options": { + "editor_split": "Razdeli cikel (poiščite vrzeli)", + "editor_merge": "Spoji cikle (združi fragmente)", + "editor_delete": "Izbriši cikle", + "menu_back": "← Nazaj" + } + }, + "editor_select": { + "title": "Izberite cikle", + "description": "Izberite cikle za obdelavo.\n\n{info_text}", + "data": { + "selected_cycles": "Cikli" + } + }, + "editor_configure": { + "title": "Konfiguriraj in uporabi", + "description": "{preview_md}", + "data": { + "confirm_action": "Dejanje", + "merged_profile": "Nastali profil", + "new_profile_name": "Novo ime profila", + "confirm_commit": "Da, potrjujem to dejanje", + "segment_0_profile": "Profil segmenta 1", + "segment_1_profile": "Profil segmenta 2", + "segment_2_profile": "Profil segmenta 3", + "segment_3_profile": "Profil segmenta 4", + "segment_4_profile": "Profil segmenta 5", + "segment_5_profile": "Profil segmenta 6", + "segment_6_profile": "Profil segmenta 7", + "segment_7_profile": "Profil segmenta 8", + "segment_8_profile": "Profil segmenta 9", + "segment_9_profile": "Profil segmenta 10" + } + }, + "editor_split_params": { + "title": "Konfiguracija razdelitve", + "description": "Prilagodite občutljivost za razdelitev tega cikla. Vsaka vrzel v nedejavnosti, daljša od te, bo povzročila razdelitev.", + "data": { + "split_mode": "Način razdelitve" + } + }, + "editor_split_auto_params": { + "title": "Konfiguracija samodejne delitve", + "description": "Prilagodite občutljivost delitve tega cikla. Vsak neaktiven razmik, daljši od tega, bo povzročil delitev.", + "data": { + "min_gap_seconds": "Prag razmika delitve (sekunde)" + } + }, + "editor_split_manual_params": { + "title": "Ročni časovni žigi delitve", + "description": "{preview_md}Okno cikla: **{cycle_start_wallclock} → {cycle_end_wallclock}** dne {cycle_date}.\n\nVnesite enega ali več časovnih žigov delitve (`HH:MM` ali `HH:MM:SS`), po enega v vsako vrstico. Cikel bo prerezan pri vsakem časovnem žigu — N časovnih žigov ustvari N+1 segmentov.", + "data": { + "split_timestamps": "Časovni žigi delitve" + } + }, + "reprocess_history": { + "title": "Vzdrževanje: ponovna obdelava in optimizacija podatkov", + "description": "Izvaja globinsko čiščenje zgodovinskih podatkov:\n1. Izloči odčitke brez moči (tišina).\n2. Popravi čas/trajanje cikla.\n3. Preračuna vse statistične modele (ovojnice).\n\n**Zaženite to, da odpravite težave s podatki ali uporabite nove algoritme.**" + }, + "wipe_history": { + "title": "Brisanje zgodovine (samo testiranje)", + "description": "⚠️ S tem boste trajno izbrisali VSE shranjene cikle in profile za to napravo. Tega ni mogoče razveljaviti!" + }, + "export_import": { + "title": "Izvozi/uvozi JSON", + "description": "Kopiraj/prilepi cikle, profile in nastavitve za to napravo. Izvozite spodaj ali prilepite JSON za uvoz.", + "data": { + "mode": "Dejanje", + "json_payload": "Popolna konfiguracija JSON" + }, + "data_description": { + "mode": "Izberite Izvozi, da kopirate trenutne podatke in nastavitve, ali Uvozi, da prilepite izvožene podatke iz druge naprave WashData.", + "json_payload": "Za izvoz: kopirajte ta JSON (vključuje cikle, profile in vse natančno nastavljene nastavitve). Za uvoz: prilepite JSON, izvožen iz WashData." + } + }, + "record_cycle": { + "title": "Snemanje cikla", + "description": "Ročno posnemite cikel, da ustvarite čist profil brez motenj.\n\nStanje: **{status}**\nTrajanje: {duration} s\nVzorci: {samples}", + "menu_options": { + "record_refresh": "Osveži stanje", + "record_stop": "Ustavi snemanje (shrani in obdelaj)", + "record_start": "Začni novo snemanje", + "record_process": "Obdelaj zadnji posnetek (obreži in shrani)", + "record_discard": "Zavrzi zadnji posnetek", + "menu_back": "← Nazaj" + } + }, + "record_process": { + "title": "Obdelava posnetka", + "description": "![Graf]({graph_url})\n\n**Graf prikazuje neobdelan posnetek.** Modra = obdrži, rdeča = predlagano obrezovanje.\n*Graf je statičen in se ne posodablja s spremembami obrazca.*\n\nSnemanje ustavljeno. Obrezovanja so poravnana z zaznano hitrostjo vzorčenja (~{sampling_rate} s).\n\nNeobdelano trajanje: {duration} s\nVzorci: {samples}", + "data": { + "head_trim": "Obrezovanje začetka (sekunde)", + "tail_trim": "Obrezovanje konca (sekunde)", + "save_mode": "Cilj shranjevanja", + "profile_name": "Ime profila" + } + }, + "learning_feedbacks": { + "title": "Povratne informacije o učenju", + "description": "Čakajoče povratne informacije: {count}\n\n{pending_table}\n\nIzberite zahtevo za pregled cikla za obdelavo.", + "menu_options": { + "learning_feedbacks_pick": "Preglej čakajočo povratno informacijo", + "learning_feedbacks_dismiss_all": "Opusti vse čakajoče povratne informacije", + "menu_back": "← Nazaj" + } + }, + "learning_feedbacks_pick": { + "title": "Izberite povratne informacije za pregled", + "description": "Izberite zahtevo za pregled cikla, ki jo želite odpreti.", + "data": { + "selected_feedback": "Izberite cikel za pregled" + } + }, + "learning_feedbacks_empty": { + "title": "Povratne informacije o učenju", + "description": "Ni čakajočih zahtev za povratne informacije.", + "menu_options": { + "menu_back": "← Nazaj" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Zavrzi vse čakajoče povratne informacije", + "description": "Zavreči nameravate **{count}** čakajočih zahtev za povratne informacije. Zavrnjeni cikli ostanejo v vaši zgodovini, vendar ne bodo prispevali novega signala učenja. Tega ni mogoče razveljaviti.\n\n✅ Označite spodnje polje in kliknite **Pošlji**, da zavržete vse.\n❌ Pustite nepotrjeno in kliknite **Pošlji** za preklic in vrnitev.", + "data": { + "confirm_dismiss_all": "Potrdi: opusti vse čakajoče zahteve za povratne informacije" + } + }, + "resolve_feedback": { + "title": "Razreši povratne informacije", + "description": "{comparison_img}{candidates_table}\n**Zaznan profil**: {detected_profile} ({confidence_pct} %)\n**Predvideno trajanje**: {est_duration_min} min\n**Dejansko trajanje**: {act_duration_min} min\n\n__Spodaj izberite dejanje:__", + "data": { + "action": "Kaj želite narediti?", + "corrected_profile": "Pravilen program (če popravljate)", + "corrected_duration": "Pravilno trajanje v sekundah (če popravljate)" + } + }, + "trim_cycle_select": { + "title": "Obrezovanje cikla - izberite cikel", + "description": "Izberite cikel za obrezovanje. ✓ = končano, ⚠ = nadaljevanje, ✗ = prekinjeno", + "data": { + "cycle_id": "Cikel" + } + }, + "trim_cycle": { + "title": "Obrezovanje cikla", + "description": "Trenutno okno: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Dejanje" + } + }, + "trim_cycle_start": { + "title": "Nastavi začetek obrezovanja", + "description": "Krožno okno: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni začetek: **{current_wallclock}** (+{current_offset_min} min od začetka cikla)\n\nS spodnjim izbirnikom ure izberite nov začetni čas.", + "data": { + "trim_start_time": "Nov začetni čas", + "trim_start_min": "Nov začetek (minut od začetka cikla)" + } + }, + "trim_cycle_end": { + "title": "Nastavi konec obrezovanja", + "description": "Krožno okno: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni konec: **{current_wallclock}** (+{current_offset_min} min od začetka cikla)\n\nS spodnjim izbirnikom ure izberite nov končni čas.", + "data": { + "trim_end_time": "Nov končni čas", + "trim_end_min": "Nov konec (minut od začetka cikla)" + } + } + }, + "error": { + "import_failed": "Uvoz ni uspel. Prilepite veljaven JSON za izvoz WashData.", + "select_exactly_one": "Za to dejanje izberite natanko en cikel.", + "select_at_least_one": "Za to dejanje izberite vsaj en cikel.", + "select_at_least_two": "Za to dejanje izberite vsaj dva cikla.", + "profile_exists": "Profil s tem imenom že obstaja", + "rename_failed": "Preimenovanje profila ni uspelo. Profil morda ne obstaja ali pa je novo ime že zasedeno.", + "assignment_failed": "Dodelitev profila ciklu ni uspela", + "duplicate_phase": "Faza s tem imenom že obstaja.", + "phase_not_found": "Izbrane faze ni bilo mogoče najti.", + "invalid_phase_name": "Ime faze ne sme biti prazno.", + "phase_name_too_long": "Ime faze je predolgo.", + "invalid_phase_range": "Fazni razpon je neveljaven. Konec mora biti večji od začetka.", + "invalid_phase_timestamp": "Časovni žig je neveljaven. Uporabite obliko LLLL-MM-DD HH:MM ali HH:MM.", + "overlapping_phase_ranges": "Fazni razponi se prekrivajo. Prilagodite razpone in poskusite znova.", + "incomplete_phase_row": "Vsaka fazna vrstica mora vsebovati fazo in celotne začetne/končne vrednosti za izbrani način.", + "timestamp_mode_cycle_required": "Ko uporabljate način časovnega žiga, izberite izvorni cikel.", + "no_phase_ranges": "Za to dejanje še ni na voljo noben fazni razpon.", + "feedback_notification_title": "WashData: preveritev cikla ({device})", + "feedback_notification_message": "**Naprava**: {device}\n**Program**: {program} ({confidence} % zaupanja)\n**Čas**: {time}\n\nWashData potrebuje vašo pomoč, da preveri ta zaznani cikel.\n\nPojdite na **Nastavitve > Naprave in storitve > WashData > Konfiguracija > Povratne informacije o učenju**, da potrdite ali popravite ta rezultat.", + "suggestions_ready_notification_title": "WashData: predlagane nastavitve pripravljene ({device})", + "suggestions_ready_notification_message": "Senzor **Predlagane nastavitve** zdaj poroča o **{count}** priporočilih za ukrepanje.\n\nČe jih želite pregledati in uporabiti: **Nastavitve > Naprave in storitve > WashData > Konfiguracija > Napredne nastavitve > Uporabi predlagane vrednosti**.\n\nPredlogi niso obvezni in so prikazani v pregledu, preden jih shranite.", + "trim_range_invalid": "Začetek mora biti pred koncem. Prilagodite točke obrezovanja.", + "trim_failed": "Obrezovanje ni uspelo. Cikel morda ne obstaja več ali pa je okno prazno.", + "invalid_split_timestamp": "Časovni žig je neveljaven ali zunaj okna cikla. Uporabite HH:MM ali HH:MM:SS znotraj okna cikla.", + "no_split_segments_found": "Iz teh časovnih žigov ni bilo mogoče ustvariti veljavnih segmentov. Prepričajte se, da je vsak časovni žig znotraj okna cikla in da so segmenti dolgi vsaj 1 minuto.", + "auto_tune_suggestion": "{device_type} {device_title} zaznanih ciklov duhov. Predlagana sprememba najmanjše moči: {current_min}W -> {new_min}W (se ne uporabi samodejno).", + "auto_tune_title": "Samodejna nastavitev WashData", + "auto_tune_fallback": "{device_type} {device_title} zaznanih ciklov duhov. Predlagana sprememba najmanjše moči: {current_min}W -> {new_min}W (se ne uporabi samodejno)." + }, + "abort": { + "no_cycles_found": "Ni najdenih ciklov", + "no_split_segments_found": "V izbranem ciklu ni bilo najdenih nobenih deljivih segmentov.", + "cycle_not_found": "Izbranega cikla ni bilo mogoče naložiti.", + "no_power_data": "Za izbrani cikel ni na voljo nobenih podatkov o sledenju moči.", + "no_profiles_found": "Ni najdenih profilov. Najprej ustvarite profil.", + "no_profiles_for_matching": "Za ujemanje ni na voljo nobenega profila. Najprej ustvarite profile.", + "no_unlabeled_cycles": "Vsi cikli so že označeni", + "no_suggestions": "Predlagane vrednosti še niso na voljo. Zaženite nekaj ciklov in poskusite znova.", + "no_predictions": "Brez napovedi", + "no_custom_phases": "Ni faz po meri.", + "reprocess_success": "Uspešno ponovno obdelanih {count} ciklov.", + "debug_data_cleared": "Podatki odpravljanja napak iz {count} ciklov so bili uspešno izbrisani." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Začetek cikla", + "cycle_finish": "Zaključek cikla", + "cycle_live": "Napredek v živo" + } + }, + "common_text": { + "options": { + "unlabeled": "(neoznačeno)", + "create_new_profile": "Ustvari nov profil", + "remove_label": "Odstrani oznako", + "no_reference_cycle": "(Brez referenčnega cikla)", + "all_device_types": "Vse vrste naprav", + "current_suffix": "(trenutno)", + "editor_select_info": "Izberite 1 cikel za razdelitev ali 2+ cikle za združitev.", + "editor_select_info_split": "Izberite 1 cikel za delitev.", + "editor_select_info_merge": "Izberite 2+ ciklov za združitev.", + "editor_select_info_delete": "Izberite 1+ ciklov za brisanje.", + "no_cycles_recorded": "Ni zabeleženih še nobenih ciklov.", + "profile_summary_line": "- **{name}**: {count} ciklov, povpr. {avg} m", + "no_profiles_created": "Ustvarjen še ni noben profil.", + "phase_preview": "Predogled faze", + "phase_preview_no_curve": "Povprečna krivulja profila še ni na voljo. Zaženite/označite več ciklov za ta profil.", + "average_power_curve": "Krivulja povprečne moči", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciklov, povpr. ~{duration} m)", + "profile_option_short_fmt": "{name} ({count} ciklov, ~{duration} m)", + "deleted_cycles_from_profile": "Izbrisanih {count} ciklov iz {profile}.", + "not_enough_data_graph": "Ni dovolj podatkov za ustvarjanje grafa.", + "no_profiles_found": "Ni najdenih profilov.", + "cycle_info_fmt": "Cikel: {start}, {duration} min, oznaka: {label}", + "top_candidates_header": "### Najboljši kandidati", + "tbl_profile": "Profil", + "tbl_confidence": "Zaupanje", + "tbl_correlation": "Korelacija", + "tbl_duration_match": "Ujemanje trajanja", + "detected_profile": "Zaznan profil", + "estimated_duration": "Predvideno trajanje", + "actual_duration": "Dejansko trajanje", + "choose_action": "__Spodaj izberite dejanje:__", + "tbl_count": "Število", + "tbl_avg": "Povpr.", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energija (povpr.)", + "tbl_energy_total": "Energija (skupaj)", + "tbl_consistency": "Konz.", + "tbl_last_run": "Zadnji zagon", + "graph_legend_title": "Legenda grafa", + "graph_legend_body": "Modri pas predstavlja najnižji in največji opazovani obseg porabe energije. Črta prikazuje krivuljo povprečne moči.", + "recording_preview": "Predogled posnetka", + "trim_start": "Začetek obrezovanja", + "trim_end": "Konec obrezovanja", + "split_preview_title": "Predogled razdelitve", + "split_preview_found_fmt": "Najdenih {count} segmentov.", + "split_preview_confirm_fmt": "Kliknite Potrdi, da ta cikel razdelite na {count} ločenih ciklov.", + "merge_preview_title": "Predogled spajanja", + "merge_preview_joining_fmt": "Spajanje {count} ciklov. Vrzeli bodo zapolnjene z odčitki 0 W.", + "editor_delete_preview_title": "Predogled brisanja", + "editor_delete_preview_intro": "Izbrani cikli bodo trajno izbrisani:", + "editor_delete_preview_confirm": "Kliknite Potrdi, da trajno izbrišete te zapise ciklov.", + "no_power_preview": "*Podatkov o moči ni na voljo za predogled.*", + "profile_comparison": "Primerjava profilov", + "actual_cycle_label": "Ta cikel (dejanski)", + "storage_usage_header": "Poraba prostora za shranjevanje", + "storage_file_size": "Velikost datoteke", + "storage_cycles": "Cikli", + "storage_profiles": "Profili", + "storage_debug_traces": "Sledi odpravljanja napak", + "overview_suffix": "Pregled", + "phase_preview_for_profile": "Predogled faze za profil", + "current_ranges_header": "Trenutni razponi", + "assign_phases_help": "Uporabite spodnja dejanja za dodajanje, urejanje, brisanje ali shranjevanje razponov.", + "profile_summary_header": "Povzetek profila", + "recent_cycles_header": "Nedavni cikli", + "trim_cycle_preview_title": "Predogled obrezovanja cikla", + "trim_cycle_preview_no_data": "Za ta cikel ni podatkov o moči.", + "trim_cycle_preview_kept_suffix": "ohranjeno", + "table_program": "Program", + "table_when": "Kdaj", + "table_length": "Dolžina", + "table_match": "Ujemanje", + "table_profile": "Profil", + "table_cycles": "Cikli", + "table_avg_length": "Povp. dolžina", + "table_last_run": "Zadnji zagon", + "table_avg_energy": "Povp. energija", + "table_detected_program": "Zaznani program", + "table_confidence": "Zaupanje", + "table_reported": "Prijavljeno", + "phase_builtin_suffix": "(vgrajeno)", + "phase_none_available": "Na voljo ni nobenih faz.", + "settings_deprecation_warning": "⚠️ **Zastarela vrsta naprave:** {device_type} je predvidena za odstranitev v prihodnji izdaji. Ujemanje cevovoda WashData ne daje zanesljivih rezultatov za ta razred aparata. Vaša integracija še naprej deluje skozi obdobje opustitve; če želite utišati to opozorilo, preklopite **Vrsto naprave** spodaj na eno od podprtih vrst (pralni stroj, sušilni stroj, kombinirani pralno-sušilni stroj, pomivalni stroj, cvrtnik, pekač kruha ali črpalka) ali na **Drugo (napredno)**, če vaša naprava ne ustreza nobeni od podprtih vrst. **Drugo (napredno)** namenoma posreduje generične privzete nastavitve, ki niso nastavljene za nobeno specifično napravo, zato boste morali sami konfigurirati pragove, časovne omejitve in ustrezne parametre; ob preklopu se ohranijo vse vaše obstoječe nastavitve.", + "phase_other_device_types": "Druge vrste naprav:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Razdeli cikel (poiščite vrzeli)", + "merge": "Spoji cikle (združi fragmente)", + "delete": "Izbriši cikle" + } + }, + "split_mode": { + "options": { + "auto": "Samodejno zaznaj vrzeli neaktivnosti", + "manual": "Ročni časovni žigi" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Vzdrževanje: ponovna obdelava in optimizacija podatkov", + "clear_debug_data": "Počisti podatke odpravljanja napak (sprosti prostor)", + "wipe_history": "Izbriši VSE podatke za to napravo (nepovratno)", + "export_import": "Izvoz/uvoz JSON z nastavitvami (kopiraj/prilepi)" + } + }, + "export_import_mode": { + "options": { + "export": "Izvozi vse podatke", + "import": "Uvozi/združi podatke" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Samodejno označevanje starih ciklov", + "select_cycle_to_label": "Označi določen cikel", + "select_cycle_to_delete": "Izbriši cikel", + "interactive_editor": "Interaktivni urejevalnik za spajanje/razdelitev", + "trim_cycle": "Obreži podatke cikla" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Ustvari nov profil", + "edit_profile": "Uredi/preimenuj profil", + "delete_profile": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Čiščenje zgodovine – graf in brisanje", + "assign_phases": "Dodeli fazne razpone" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Ustvari novo fazo", + "edit_custom_phase": "Uredi fazo", + "delete_custom_phase": "Izbriši fazo" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Dodaj fazni razpon", + "edit_range": "Uredi fazni razpon", + "delete_range": "Izbriši fazni razpon", + "clear_ranges": "Počisti vse razpone", + "auto_detect_ranges": "Samodejno zaznavanje faz", + "save_ranges": "Shrani in vrni" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Osveži stanje", + "stop_recording": "Ustavi snemanje (shrani in obdelaj)", + "start_recording": "Začni novo snemanje", + "process_recording": "Obdelaj zadnji posnetek (obreži in shrani)", + "discard_recording": "Zavrzi zadnji posnetek" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Ustvari nov profil", + "existing_profile": "Dodaj obstoječemu profilu", + "discard": "Zavrzi snemanje" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potrdi – pravilno zaznavanje", + "correct": "Popravi – izberite program/trajanje", + "ignore": "Prezri – lažno pozitiven/šumni cikel", + "delete": "Izbriši – odstrani iz zgodovine" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Poimenuj in uporabi faze", + "cancel": "Nazaj brez sprememb" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Nastavi začetno točko", + "set_end": "Nastavi končno točko", + "reset": "Ponastavi na polno trajanje", + "apply": "Uporabi obrezovanje", + "cancel": "Prekliči" + } + }, + "device_type": { + "options": { + "washing_machine": "pralni stroj", + "dryer": "Sušilnik", + "washer_dryer": "Pralno-sušilni stroj Combo", + "dishwasher": "Pomivalni stroj", + "coffee_machine": "Aparat za kavo (zastarelo)", + "ev": "Električno vozilo (zastarelo)", + "air_fryer": "Zračni cvrtnik", + "heat_pump": "Toplotna črpalka (zastarelo)", + "bread_maker": "pekač kruha", + "pump": "Črpalka / zbiralna črpalka", + "oven": "Pečica (zastarelo)", + "other": "Drugo (napredno)" + } + } + }, + "services": { + "label_cycle": { + "name": "Označi cikel", + "description": "Dodelite obstoječi profil preteklemu ciklu ali odstranite oznako.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja za označevanje." + }, + "cycle_id": { + "name": "ID cikla", + "description": "ID cikla za označevanje." + }, + "profile_name": { + "name": "Ime profila", + "description": "Ime obstoječega profila (profile ustvarite v meniju Upravljanje profilov). Pustite prazno, da odstranite oznako." + } + } + }, + "create_profile": { + "name": "Ustvari profil", + "description": "Ustvarite nov profil (samostojen ali na podlagi referenčnega cikla).", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja." + }, + "profile_name": { + "name": "Ime profila", + "description": "Ime za nov profil (npr. 'Intenzivno pranje', 'Občutljivo')." + }, + "reference_cycle_id": { + "name": "ID referenčnega cikla", + "description": "Izbirni ID cikla, na katerem temelji ta profil." + } + } + }, + "delete_profile": { + "name": "Izbriši profil", + "description": "Izbrišite profil in po želji odstranite oznake ciklov, ki ga uporabljajo.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja." + }, + "profile_name": { + "name": "Ime profila", + "description": "Profil za brisanje." + }, + "unlabel_cycles": { + "name": "Odznači cikle", + "description": "Odstranite oznako profila iz ciklov, ki uporabljajo ta profil." + } + } + }, + "auto_label_cycles": { + "name": "Samodejno označevanje starih ciklov", + "description": "Retroaktivno označite neoznačene cikle z ujemanjem profilov.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja." + }, + "confidence_threshold": { + "name": "Prag zaupanja", + "description": "Najmanjše zaupanje ujemanja (0,50–0,95) za uporabo oznak." + } + } + }, + "export_config": { + "name": "Izvozi konfiguracijo", + "description": "Izvozite profile, cikle in nastavitve te naprave v datoteko JSON (na strežnik).", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja za izvoz." + }, + "path": { + "name": "Pot", + "description": "Izbirna absolutna pot datoteke za pisanje (privzeto je /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Uvozi konfiguracijo", + "description": "Uvozite profile, cikle in nastavitve za to napravo iz izvozne datoteke JSON (nastavitve so lahko prepisane).", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja za uvoz v." + }, + "path": { + "name": "Pot", + "description": "Absolutna pot do izvozne datoteke JSON za uvoz." + } + } + }, + "submit_cycle_feedback": { + "name": "Pošlji povratne informacije o ciklu", + "description": "Po zaključenem ciklu potrdite ali popravite samodejno zaznan program. Navedite `entry_id` (napredno) ali `device_id` (priporočeno).", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava WashData (priporočeno namesto entry_id)." + }, + "entry_id": { + "name": "ID vnosa", + "description": "ID konfiguracijskega vnosa za napravo (alternativa device_id)." + }, + "cycle_id": { + "name": "ID cikla", + "description": "ID cikla, prikazan v povratnem obvestilu/dnevnikih." + }, + "user_confirmed": { + "name": "Potrdite zaznan program", + "description": "Nastavite true, če je zaznan program pravilen." + }, + "corrected_profile": { + "name": "Popravljen profil", + "description": "Če ni potrjen, navedite pravilno ime profila/programa." + }, + "corrected_duration": { + "name": "Popravljeno trajanje (sekunde)", + "description": "Izbirno popravljeno trajanje v sekundah." + }, + "notes": { + "name": "Opombe", + "description": "Neobvezne opombe o tem ciklu." + } + } + }, + "record_start": { + "name": "Začni snemanje cikla", + "description": "Začnite ročno snemati čisti cikel (zaobide vso logiko ujemanja).", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja za snemanje." + } + } + }, + "record_stop": { + "name": "Ustavi snemanje cikla", + "description": "Ustavi ročno snemanje.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava pralnega stroja za zaustavitev snemanja." + } + } + }, + "trim_cycle": { + "name": "Obreži cikel", + "description": "Prirežite podatke o moči preteklega cikla na določeno časovno okno.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava WashData." + }, + "cycle_id": { + "name": "ID cikla", + "description": "ID cikla za obrezovanje." + }, + "trim_start_s": { + "name": "Začetek obrezovanja (sekunde)", + "description": "Shranite podatke od tega odmika v sekundah. Privzeto: 0." + }, + "trim_end_s": { + "name": "Konec obrezovanja (sekunde)", + "description": "Ohranite podatke do tega odmika v sekundah. Privzeto = celotno trajanje." + } + } + }, + "pause_cycle": { + "name": "Premor cikla", + "description": "Zaustavite aktivni cikel za napravo WashData.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava WashData za premor." + } + } + }, + "resume_cycle": { + "name": "Cikel nadaljevanja", + "description": "Nadaljevanje zaustavljenega cikla za napravo WashData.", + "fields": { + "device_id": { + "name": "Naprava", + "description": "Naprava WashData za nadaljevanje." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "V delovanju" + }, + "match_ambiguity": { + "name": "Dvoumnost ujemanja" + } + }, + "sensor": { + "washer_state": { + "name": "Stanje", + "state": { + "off": "Izključeno", + "idle": "Nedejavno", + "starting": "Zaganjanje", + "running": "V delovanju", + "paused": "Zaustavljeno", + "user_paused": "Zaustavljeno s strani uporabnika", + "ending": "Zaključuje se", + "finished": "Končano", + "anti_wrinkle": "Zaščita pred gubami", + "delay_wait": "Čakam na začetek", + "interrupted": "Prekinjeno", + "force_stopped": "Prisilno ustavljeno", + "rinse": "Izpiranje", + "unknown": "Neznano", + "clean": "čisto" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Preostali čas" + }, + "total_duration": { + "name": "Skupno trajanje" + }, + "cycle_progress": { + "name": "Napredek" + }, + "current_power": { + "name": "Trenutna moč" + }, + "elapsed_time": { + "name": "Pretečeni čas" + }, + "debug_info": { + "name": "Informacije o odpravljanju napak" + }, + "match_confidence": { + "name": "Zaupanje ujemanja" + }, + "top_candidates": { + "name": "Najboljši kandidati", + "state": { + "none": "Noben" + } + }, + "current_phase": { + "name": "Trenutna faza" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Število ciklov profila {profile_name}", + "unit_of_measurement": "ciklov" + }, + "suggestions": { + "name": "Razpoložljive predlagane nastavitve" + }, + "pump_runs_today": { + "name": "Črpalka deluje (zadnjih 24 ur)" + }, + "cycle_count": { + "name": "Štetje ciklov" + } + }, + "select": { + "program_select": { + "name": "Program cikla", + "state": { + "auto_detect": "Samodejno zaznaj" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Prisilni konec cikla" + }, + "pause_cycle": { + "name": "Premor cikla" + }, + "resume_cycle": { + "name": "Cikel nadaljevanja" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Potreben je veljaven device_id." + }, + "cycle_id_required": { + "message": "Potreben je veljaven cycle_id." + }, + "profile_name_required": { + "message": "Zahtevano je veljavno profile_name." + }, + "device_not_found": { + "message": "Naprave ni bilo mogoče najti." + }, + "no_config_entry": { + "message": "Za napravo ni bil najden noben konfiguracijski vnos." + }, + "integration_not_loaded": { + "message": "Integracija ni naložena za to napravo." + }, + "cycle_not_found_or_no_power": { + "message": "Cikel ni najden ali nima podatkov o moči." + }, + "trim_failed_empty_window": { + "message": "Obrezovanje ni uspelo - cikel ni najden, ni podatkov o moči ali pa je nastalo okno prazno." + }, + "trim_invalid_range": { + "message": "trim_end_s mora biti večje od trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/sq.json b/custom_components/ha_washdata/translations/sq.json new file mode 100644 index 0000000..d690080 --- /dev/null +++ b/custom_components/ha_washdata/translations/sq.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Konfigurimi WashData", + "description": "Konfiguro makinën larëse ose pajisje tjetër.\n\nKërkohet sensori i fuqisë.\n\n**Më pas, do të pyeteni nëse dëshironi të krijoni profilin tuaj të parë.**", + "data": { + "name": "Emri i pajisjes", + "device_type": "Lloji i pajisjes", + "power_sensor": "Sensori i fuqisë", + "min_power": "Pragu minimal i fuqisë (W)" + }, + "data_description": { + "name": "Një emër miqësor për këtë pajisje (p.sh., \"Lavatriçe\", \"Pjatalarëse\").", + "device_type": "Çfarë lloj pajisjeje është kjo? Ndihmon në përshtatjen e zbulimit dhe etiketimit.", + "power_sensor": "Njësia e sensorit që raporton konsumin e energjisë në kohë reale (në vat) nga spina juaj inteligjente.", + "min_power": "Leximet e fuqisë mbi këtë prag (në vat) tregojnë se pajisja është në punë. Filloni me 2 W për shumicën e pajisjeve." + } + }, + "first_profile": { + "title": "Krijo profilin e parë", + "description": "Një **Cikl** është një funksion i plotë i pajisjes suaj (p.sh., një ngarkesë rrobash). Një **Profil** i grupon këto cikle sipas llojit (p.sh. \"Pambuk\", \"Larje e shpejtë\"). Krijimi i një profili tani ndihmon në vlerësimin e kohëzgjatjes menjëherë.\n\nMund ta zgjidhni manualisht këtë profil në kontrollet e pajisjes nëse nuk zbulohet automatikisht.\n\n**Lëreni 'Emri i profilit' bosh për të kapërcyer këtë hap.**", + "data": { + "profile_name": "Emri i profilit (opsionale)", + "manual_duration": "Kohëzgjatja e parashikuar (minuta)" + } + } + }, + "error": { + "cannot_connect": "Lidhja dështoi", + "invalid_auth": "Vërtetim i pavlefshëm", + "unknown": "Gabim i papritur" + }, + "abort": { + "already_configured": "Pajisja është konfiguruar tashmë" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Rishikoni reagimet e të mësuarit", + "settings": "Cilësimet", + "notifications": "Njoftimet", + "manage_cycles": "Menaxhoni ciklet", + "manage_profiles": "Menaxho profilet", + "manage_phase_catalog": "Menaxho katalogun e fazave", + "record_cycle": "Regjistro ciklin (manual)", + "diagnostics": "Diagnostikimi dhe mirëmbajtja" + } + }, + "apply_suggestions": { + "title": "Kopjo vlerat e sugjeruara", + "description": "Kjo do të kopjojë vlerat aktuale të sugjeruara në cilësimet tuaja të ruajtura. Ky është një veprim një herë dhe nuk mundëson mbishkrime automatike.\n\nVlerat e sugjeruara për të kopjuar:\n{suggested}", + "data": { + "confirm": "Konfirmo veprimin e kopjimit" + } + }, + "apply_suggestions_confirm": { + "title": "Rishiko ndryshimet e sugjeruara", + "description": "WashData u gjet **{pending_count}** vlera(t) e sugjeruara për t'u aplikuar.\n\nNdryshimet:\n{changes}\n\n✅ Kontrolloni kutinë më poshtë dhe klikoni **Dërgo** për të aplikuar dhe ruajtur menjëherë këto ndryshime.\n❌ Lëreni të pazgjedhur dhe klikoni **Dërgo** për ta anuluar dhe për t'u kthyer prapa.", + "data": { + "confirm_apply_suggestions": "Aplikoni dhe ruani këto ndryshime" + } + }, + "settings": { + "title": "Cilësimet", + "description": "{deprecation_warning}Rregulloni pragjet e zbulimit, të mësuarit dhe sjelljen e avancuar. Parazgjedhjet funksionojnë mirë për shumicën e konfigurimeve.\n\nCilësimet e sugjeruara aktualisht të disponueshme: {suggestions_count}.\nNëse kjo është mbi 0, hapni Cilësimet e Avancuara dhe përdorni Apliko Vlerat e Sugjeruara për të rishikuar rekomandimet.", + "data": { + "apply_suggestions": "Apliko vlerat e sugjeruara", + "device_type": "Lloji i pajisjes", + "power_sensor": "Njësia e sensorit të energjisë", + "min_power": "Fuqia minimale (W)", + "off_delay": "Vonesa e përfundimit të ciklit (s)", + "notify_actions": "Veprimet e njoftimit", + "notify_people": "Vono dorëzimin derisa këta njerëz të jenë në shtëpi", + "notify_only_when_home": "Vono njoftimet derisa personi i përzgjedhur të jetë në shtëpi", + "notify_fire_events": "Aktivizo ngjarjet e automatizimit", + "notify_start_services": "Fillimi i ciklit - Objektivat e njoftimit", + "notify_finish_services": "Përfundimi i ciklit - Objektivat e njoftimit", + "notify_live_services": "Progresi i drejtpërdrejtë - Objektivat e njoftimeve", + "notify_before_end_minutes": "Njoftim para përfundimit (minuta para fundit)", + "notify_title": "Titulli i njoftimit", + "notify_icon": "Ikona e njoftimit", + "notify_start_message": "Formati i mesazhit të fillimit", + "notify_finish_message": "Formati i mesazhit të përfundimit", + "notify_pre_complete_message": "Formati i mesazhit para përfundimit", + "show_advanced": "Redakto cilësimet e avancuara (hapi tjetër)" + }, + "data_description": { + "apply_suggestions": "Zgjidhni këtë kuti për të kopjuar vlerat e sugjeruara nga algoritmi i të mësuarit në formë. Rishikoni përpara se të ruani.\n\nSugjeruar (nga të mësuarit):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Zgjidhni llojin e pajisjes (lavatriçe, tharëse, pjatalarëse, makinë kafeje, EV). Ky etiketë ruhet me çdo cikël për organizim më të mirë.", + "power_sensor": "Njësia e sensorit që raporton fuqinë në kohë reale (në vat). Mund ta ndryshoni këtë në çdo kohë pa humbur të dhënat historike - e dobishme nëse zëvendësoni një prizë inteligjente.", + "min_power": "Leximet e fuqisë mbi këtë prag (në vat) tregojnë se pajisja është në punë. Filloni me 2 W për shumicën e pajisjeve.", + "off_delay": "Sekonda fuqia e zbutur duhet të qëndrojë nën pragun e ndalimit për të shënuar përfundimin. Parazgjedhja: 120 s.", + "notify_actions": "Sekuenca opsionale e veprimeve të Home Assistant për t'u ekzekutuar për njoftimet (skriptet, njoftimet, TTS, etj.). Nëse caktohet, veprimet ekzekutohen përpara shërbimit të njoftimit të sërishëm.", + "notify_people": "Njësitë e njerëzve që përdoren për kontrollin e pranisë. Kur aktivizohet, dërgimi i njoftimit vonohet derisa të paktën një person i përzgjedhur të jetë në shtëpi.", + "notify_only_when_home": "Nëse aktivizohet, vononi njoftimet derisa të paktën një person i zgjedhur të jetë në shtëpi.", + "notify_fire_events": "Nëse aktivizohet, aktivizohen ngjarjet e Home Assistant për fillimin dhe përfundimin e ciklit (ha_washdata_cycle_started dhe ha_washdata_cycle_ended) për të drejtuar automatizimet.", + "notify_start_services": "Një ose më shumë shërbime njoftojnë për të sinjalizuar kur fillon një cikël. Lëreni bosh për të kapërcyer njoftimet e fillimit.", + "notify_finish_services": "Një ose më shumë shërbime njoftojnë për të sinjalizuar kur një cikël përfundon ose i afrohet përfundimit. Lëreni bosh për të kapërcyer njoftimet e përfundimit.", + "notify_live_services": "Një ose më shumë shërbime të njoftimit për të marrë përditësime të drejtpërdrejta të progresit gjatë ekzekutimit të ciklit (vetëm aplikacioni shoqërues celular). Lëreni bosh për të kapërcyer përditësimet e drejtpërdrejta.", + "notify_before_end_minutes": "Minuta para përfundimit të parashikuar të ciklit për të aktivizuar një njoftim. Vendoseni në 0 për ta çaktivizuar. Parazgjedhja: 0.", + "notify_title": "Titulli i personalizuar për njoftimet. Mbështet mbajtësin e vendndodhjes {device}.", + "notify_icon": "Ikona për t'u përdorur për njoftime (p.sh. mdi:washing-machine). Lëreni bosh për të dërguar pa ikonë.", + "notify_start_message": "Mesazhi dërgohet kur fillon një cikël. Mbështet mbajtësin e vendndodhjes {device}.", + "notify_finish_message": "Mesazhi dërgohet kur përfundon një cikël. Mbështet {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Teksti i përditësimeve të përsëritura të progresit të drejtpërdrejtë gjatë ekzekutimit të ciklit. Mbështet {device}, {minutes}, {program} vendmbajtësit." + } + }, + "notifications": { + "title": "Njoftimet", + "description": "Konfiguro kanalet e njoftimeve, shabllonet dhe përditësimet opsionale të progresit të drejtpërdrejtë.", + "data": { + "notify_actions": "Veprimet e njoftimit", + "notify_people": "Vono dorëzimin derisa këta njerëz të jenë në shtëpi", + "notify_only_when_home": "Vono njoftimet derisa personi i përzgjedhur të jetë në shtëpi", + "notify_fire_events": "Aktivizo ngjarjet e automatizimit", + "notify_start_services": "Fillimi i ciklit - Objektivat e njoftimit", + "notify_finish_services": "Përfundimi i ciklit - Objektivat e njoftimit", + "notify_live_services": "Progresi i drejtpërdrejtë - Objektivat e njoftimeve", + "notify_before_end_minutes": "Njoftim para përfundimit (minuta para fundit)", + "notify_live_interval_seconds": "Intervali i përditësimit të drejtpërdrejtë (sekonda)", + "notify_live_overrun_percent": "Leja e tejkalimit të përditësimeve të drejtpërdrejta (%)", + "notify_live_chronometer": "Kohëmatësi i numërimit të kronometrit", + "notify_title": "Titulli i njoftimit", + "notify_icon": "Ikona e njoftimit", + "notify_start_message": "Formati i mesazhit të fillimit", + "notify_finish_message": "Formati i mesazhit të përfundimit", + "notify_pre_complete_message": "Formati i mesazhit para përfundimit", + "energy_price_entity": "Çmimi i energjisë - Njësia ekonomike (opsionale)", + "energy_price_static": "Çmimi i energjisë - Vlera statike (opsionale)", + "go_back": "Kthehu pa ruajtur", + "notify_reminder_message": "Formati i mesazhit përkujtues", + "notify_timeout_seconds": "Hiq automatikisht pas (sekonda)", + "notify_channel": "Kanali i njoftimeve (statusi/drejtpërdrejt/kujtues)", + "notify_finish_channel": "Kanali i Njoftimeve i Përfunduar" + }, + "data_description": { + "go_back": "Shënoni këtë dhe klikoni Dërgo për të hedhur poshtë çdo ndryshim më sipër dhe për t'u kthyer te menyja kryesore.", + "notify_actions": "Sekuenca opsionale e veprimeve të Home Assistant për t'u ekzekutuar për njoftimet (skriptet, njoftimet, TTS, etj.). Nëse caktohet, veprimet ekzekutohen përpara shërbimit të njoftimit të sërishëm.", + "notify_people": "Njësitë e njerëzve që përdoren për kontrollin e pranisë. Kur aktivizohet, dërgimi i njoftimit vonohet derisa të paktën një person i përzgjedhur të jetë në shtëpi.", + "notify_only_when_home": "Nëse aktivizohet, vononi njoftimet derisa të paktën një person i zgjedhur të jetë në shtëpi.", + "notify_fire_events": "Nëse aktivizohet, aktivizohen ngjarjet e Home Assistant për fillimin dhe përfundimin e ciklit (ha_washdata_cycle_started dhe ha_washdata_cycle_ended) për të drejtuar automatizimet.", + "notify_start_services": "Një ose më shumë shërbime të njoftimit për të sinjalizuar kur fillon një cikël (p.sh. notify.mobile_app_my_phone). Lëreni bosh për të kapërcyer njoftimet e fillimit.", + "notify_finish_services": "Një ose më shumë shërbime njoftojnë për të sinjalizuar kur një cikël përfundon ose i afrohet përfundimit. Lëreni bosh për të kapërcyer njoftimet e përfundimit.", + "notify_live_services": "Një ose më shumë shërbime të njoftimit për të marrë përditësime të drejtpërdrejta të progresit gjatë ekzekutimit të ciklit (vetëm aplikacioni shoqërues celular). Lëreni bosh për të kapërcyer përditësimet e drejtpërdrejta.", + "notify_before_end_minutes": "Minuta para përfundimit të parashikuar të ciklit për të aktivizuar një njoftim. Vendoseni në 0 për ta çaktivizuar. Parazgjedhja: 0.", + "notify_live_interval_seconds": "Sa shpesh dërgohen përditësimet e progresit gjatë ekzekutimit. Vlerat më të ulëta janë më të përgjegjshme, por konsumojnë më shumë njoftime. Zbatohet një minimum prej 30 sekondash - vlerat nën 30 s rrumbullakohen automatikisht në 30 s.", + "notify_live_overrun_percent": "Marzhi i sigurisë mbi numrin e përllogaritur të përditësimeve për cikle të gjata/tejkaluese (për shembull 20% lejon 1.2 herë përditësimet e vlerësuara).", + "notify_live_chronometer": "Kur aktivizohet, çdo përditësim i drejtpërdrejtë përfshin një numërim kronometër deri në kohën e parashikuar të përfundimit. Kohëmatësi i njoftimeve shënon automatikisht në pajisje ndërmjet përditësimeve (vetëm aplikacioni shoqërues i Android).", + "notify_title": "Titulli i personalizuar për njoftimet. Mbështet mbajtësin e vendndodhjes {device}.", + "notify_icon": "Ikona për t'u përdorur për njoftime (p.sh. mdi:washing-machine). Lëreni bosh për të dërguar pa ikonë.", + "notify_start_message": "Mesazhi dërgohet kur fillon një cikël. Mbështet mbajtësin e vendndodhjes {device}.", + "notify_finish_message": "Mesazhi dërgohet kur përfundon një cikël. Mbështet {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Zgjidh një entitet numerik që mban çmimin aktual të energjisë elektrike (p.sh. sensor.çmimi_energjisë_elektrike, numri_input.norma_energjie). Kur caktohet, aktivizon mbajtësin e vendndodhjes {cost}. Ka përparësi ndaj vlerës statike nëse të dyja janë të konfiguruara.", + "energy_price_static": "Çmimi fiks i energjisë elektrike për kWh. Kur caktohet, aktivizon mbajtësin e vendndodhjes {cost}. Injorohet nëse një entitet është konfiguruar më sipër. Shtoni simbolin tuaj të monedhës në shabllonin e mesazhit, p.sh. {cost} €.", + "notify_reminder_message": "Teksti i kujtesës një herë dërgoi numrin e konfiguruar të minutave përpara përfundimit të parashikuar. Ndryshe nga përditësimet e përsëritura të drejtpërdrejta më sipër. Mbështet {device}, {minutes}, {program} vendmbajtësit.", + "notify_timeout_seconds": "Hiq automatikisht njoftimet pas kaq shumë sekondash (të përcjella si 'përfundimi i kohës' i aplikacionit shoqërues). Vendoseni në 0 për t'i mbajtur ato derisa të hiqen. Parazgjedhja: 0.", + "notify_channel": "Android kanali i njoftimeve të aplikacionit shoqërues për statusin, përparimin e drejtpërdrejtë dhe njoftimet rikujtuese. Tingulli dhe rëndësia e një kanali konfigurohen në aplikacionin shoqërues herën e parë që përdoret emri i kanalit - WashData cakton vetëm emrin e kanalit. Lëreni bosh për parazgjedhjen e aplikacionit.", + "notify_finish_channel": "Ndani kanalin Android për sinjalizimin e përfunduar (përdoret edhe nga rikujtuesi dhe rënkimi i pritjes së rrobave) në mënyrë që të ketë zërin e vet. Lëreni bosh për të ripërdorur kanalin e mësipërm.", + "notify_pre_complete_message": "Teksti i përditësimeve të përsëritura të progresit të drejtpërdrejtë gjatë ekzekutimit të ciklit. Mbështet {device}, {minutes}, {program} vendmbajtësit." + } + }, + "advanced_settings": { + "title": "Cilësimet e avancuara", + "description": "Rregulloni pragjet e zbulimit, të mësuarit dhe sjelljen e avancuar. Vlerat parazgjedhje funksionojnë mirë për shumicën e konfigurimeve.", + "sections": { + "suggestions_section": { + "name": "Cilësimet e sugjeruara", + "description": "{suggestions_count} sugjerime të mësuara janë në dispozicion. Shënoni Apliko vlerat e sugjeruara për të shqyrtuar ndryshimet e propozuara përpara ruajtjes.", + "data": { + "apply_suggestions": "Apliko vlerat e sugjeruara" + }, + "data_description": { + "apply_suggestions": "Zgjidhni këtë kuti për të hapur një hap rishikimi për vlerat e sugjeruara nga algoritmi i të mësuarit. Asgjë nuk ruhet automatikisht.\n\nSugjeruar (nga të mësuarit):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Zbulimi dhe pragjet e fuqisë", + "description": "Kur pajisja konsiderohet E NDEZUR ose E FIKUR, portat e energjisë dhe kohëzimi për kalimet e gjendjes.", + "data": { + "start_duration_threshold": "Kohëzgjatja e pragut të fillimit (s)", + "start_energy_threshold": "Porta e energjisë e fillimit (Wh)", + "completion_min_seconds": "Koha minimale e ekzekutimit për përfundim (sekonda)", + "start_threshold_w": "Pragu i fillimit (W)", + "stop_threshold_w": "Pragu i ndalimit (W)", + "end_energy_threshold": "Porta e energjisë e fundit (Wh)", + "running_dead_zone": "Zona e vdekur e fillimit (sekonda)", + "end_repeat_count": "Numri i përsëritjeve të kushtit të fundit", + "min_off_gap": "Hendeku minimal ndërmjet cikleve (s)", + "sampling_interval": "Intervali i kampionimit (s)" + }, + "data_description": { + "start_duration_threshold": "Injoroni pikat e shkurtra të fuqisë më të shkurtra se kjo kohëzgjatje (sekonda). Filtron majat e nisjes (p.sh., 60 W për 2 s) përpara fillimit të ciklit real. Parazgjedhja: 5 s.", + "start_energy_threshold": "Cikli duhet të grumbullojë të paktën kaq shumë energji (Wh) gjatë fazës START për t'u konfirmuar. Parazgjedhja: 0,005 Wh.", + "completion_min_seconds": "Ciklet më të shkurtra se kjo do të shënohen si 'të ndërprera' edhe nëse përfundojnë natyrshëm. Parazgjedhja: 600 s.", + "start_threshold_w": "Fuqia duhet të rritet MBI këtë prag për të filluar një cikël. Vendoseni më lart se fuqia e gatishmërisë. Parazgjedhja: min_power + 1 W.", + "stop_threshold_w": "Fuqia duhet të bjerë POSHTË këtij pragu për të përfunduar një cikël. Vendoseni pak mbi zero për të shmangur zhurmën që shkakton skajet e rreme. Parazgjedhja: 0,6 × min_power.", + "end_energy_threshold": "Cikli përfundon vetëm nëse energjia në dritaren e fundit Off-Delay është nën këtë vlerë (Wh). Parandalon skajet e rreme nëse fuqia luhatet afër zeros. Parazgjedhja: 0,05 Wh.", + "running_dead_zone": "Pas fillimit të ciklit, injoroni uljen e energjisë për kaq shumë sekonda për të parandaluar zbulimin e rremë të fundit gjatë fazës fillestare të rrotullimit. Vendoseni në 0 për ta çaktivizuar. Parazgjedhja: 0 s.", + "end_repeat_count": "Numri i herëve që kushti i fundit (energjia nën pragun për off_delay) duhet të plotësohet njëpasnjëshëm përpara përfundimit të ciklit. Vlerat më të larta parandalojnë përfundimet e rreme gjatë pauzave. Parazgjedhja: 1.", + "min_off_gap": "Nëse një cikël fillon brenda kaq shumë sekondash nga mbarimi i atij të mëparshëm, ato do të shkrihen në një cikël të vetëm.", + "sampling_interval": "Intervali minimal i marrjes së mostrave në sekonda. Përditësimet më të shpejta se kjo normë do të shpërfillen. Parazgjedhja: 30s (2 për lavatriçe, lavatriçe-tharëse dhe pjatalarëse)." + } + }, + "matching_section": { + "name": "Përputhja e profilit dhe mësimi", + "description": "Sa agresivisht WashData përputh ciklet në ekzekutim me profilet e mësuara dhe kur të kërkohet reagim.", + "data": { + "profile_match_min_duration_ratio": "Raporti minimal i kohëzgjatjes së përputhjes së profilit (0,1–1,0)", + "profile_match_interval": "Intervali i përputhjes së profilit (sekonda)", + "profile_match_threshold": "Pragu i përputhjes së profilit", + "profile_unmatch_threshold": "Pragu i mospërputhjes së profilit", + "auto_label_confidence": "Besueshmëria e etiketimit automatik (0–1)", + "learning_confidence": "Besueshmëria e kërkesës për komente (0–1)", + "suppress_feedback_notifications": "Mbyll njoftimet e komenteve", + "duration_tolerance": "Toleranca e kohëzgjatjes (0–0,5)", + "smoothing_window": "Dritare zbutëse (mostra)", + "profile_duration_tolerance": "Toleranca e kohëzgjatjes së përputhjes së profilit (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Raporti minimal i kohëzgjatjes për përputhjen e profilit. Cikli aktiv duhet të jetë së paku kjo pjesë e kohëzgjatjes së profilit që të përputhet. Më e ulët = përputhje më e hershme. Parazgjedhja: 0,50 (50%).", + "profile_match_interval": "Sa shpesh (në sekonda) të provoni përputhjen e profilit gjatë një cikli. Vlerat më të ulëta ofrojnë zbulim më të shpejtë, por përdorin më shumë CPU. Parazgjedhja: 300 s (5 minuta).", + "profile_match_threshold": "Rezultati minimal i ngjashmërisë DTW (0,0–1,0) kërkohet për të konsideruar një profil si përputhje. Më e lartë = përputhje më e rreptë, më pak pozitive të rreme. Parazgjedhja: 0,4.", + "profile_unmatch_threshold": "Rezultati i ngjashmërisë DTW (0,0–1,0) nën të cilin një profil i përputhur më parë refuzohet. Duhet të jetë më e ulët se pragu i përputhjes për të parandaluar ndryshimet e shpeshta. Parazgjedhja: 0,35.", + "auto_label_confidence": "Etiketoni automatikisht ciklet në ose mbi këtë besueshmëri pas përfundimit (0,0–1,0).", + "learning_confidence": "Besueshmëri minimale për të kërkuar verifikimin e përdoruesit nëpërmjet një ngjarjeje (0,0–1,0).", + "suppress_feedback_notifications": "Kur aktivizohet, WashData do të vazhdojë të gjurmojë dhe të kërkojë reagime nga brenda, por nuk do të shfaqë njoftime të vazhdueshme që ju kërkojnë të konfirmoni ciklet. E dobishme kur ciklet zbulohen gjithmonë në mënyrë korrekte dhe ju i shihni kërkesat që shpërqendrojnë.", + "duration_tolerance": "Toleranca për vlerësimet e kohës së mbetur gjatë një cikli (0,0–0,5 korrespondon me 0–50%).", + "smoothing_window": "Numri i leximeve të fundit të fuqisë të përdorura për zbutjen mesatare lëvizëse.", + "profile_duration_tolerance": "Toleranca e përdorur nga përputhja e profilit për variancën totale të kohëzgjatjes (0,0–0,5). Sjellja e parazgjedhur: ±25%." + } + }, + "timing_section": { + "name": "Koha, mirëmbajtja dhe debugimi", + "description": "Watchdog, rivendosja e progresit, mirëmbajtja automatike dhe ekspozimi i entiteteve të debugimit.", + "data": { + "watchdog_interval": "Intervali i ndjekësit (sekonda)", + "no_update_active_timeout": "Koha e fundit pa përditësime (s)", + "progress_reset_delay": "Vonesa e rivendosjes së progresit (sekonda)", + "auto_maintenance": "Aktivizo mirëmbajtjen automatike", + "expose_debug_entities": "Ekspono njësitë e korrigjimit", + "save_debug_traces": "Ruaj gjurmët e korrigjimit" + }, + "data_description": { + "watchdog_interval": "Sekonda midis kontrolleve të ndjekësit gjatë ekzekutimit. Parazgjedhja: 5 s. PARALAJMËRIM: Sigurohuni që kjo të jetë MË LARTË se intervali i përditësimit të sensorit tuaj për të shmangur ndalesat e rreme (p.sh., nëse sensori përditësohet çdo 60 s, përdorni 65 s+).", + "no_update_active_timeout": "Për sensorët publikim-në-ndryshim: mbyllni me forcë një cikël AKTIV vetëm nëse nuk vijnë përditësime për kaq shumë sekonda. Përfundimi me energji të ulët ende përdor vonesën e çaktivizimit.", + "progress_reset_delay": "Pasi të përfundojë një cikël (100%), prisni kaq shumë sekonda papune përpara se të rivendosni progresin në 0%. Parazgjedhja: 300 s.", + "auto_maintenance": "Aktivizo mirëmbajtjen automatike (riparimi i mostrave, bashkimi i fragmenteve).", + "expose_debug_entities": "Shfaq sensorë të avancuar (besueshmëria, faza, paqartësia) për korrigjimin e gabimeve.", + "save_debug_traces": "Ruani të dhënat e detajuara të renditjes dhe gjurmës së fuqisë në histori (rrit përdorimin e ruajtjes)." + } + }, + "anti_wrinkle_section": { + "name": "Mbrojtja kundër rrudhave (Tharëset)", + "description": "Injoroni rrotullimet e kazanit pas ciklit për të parandaluar ciklet fantazmë. Vetëm për tharëse / lavatriçe-tharëse.", + "data": { + "anti_wrinkle_enabled": "Mbrojtja kundër rrudhave (vetëm tharëse)", + "anti_wrinkle_max_power": "Fuqia maksimale kundër rrudhave (W)", + "anti_wrinkle_max_duration": "Kohëzgjatja maksimale kundër rrudhave (s)", + "anti_wrinkle_exit_power": "Fuqia e daljes kundër rrudhave (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Injoroni rrotullimet e shkurtra të kazanit me fuqi të ulët pas përfundimit të një cikli për të parandaluar ciklet e dyfishta (vetëm tharëse/lavatriçe-tharëse).", + "anti_wrinkle_max_power": "Shpërthimet në ose nën këtë fuqi trajtohen si rrotullime kundër rrudhave në vend të cikleve të reja. Zbatohet vetëm kur është aktivizuar mbrojtja kundër rrudhave.", + "anti_wrinkle_max_duration": "Gjatësia maksimale e shpërthimit për t'u trajtuar si rrotullim kundër rrudhave. Zbatohet vetëm kur është aktivizuar mbrojtja kundër rrudhave.", + "anti_wrinkle_exit_power": "Nëse fuqia bie nën këtë nivel, dilni menjëherë nga mbrojtja kundër rrudhave (fikje e vërtetë). Zbatohet vetëm kur është aktivizuar mbrojtja kundër rrudhave." + } + }, + "delay_start_section": { + "name": "Zbulimi i nisjes së vonuar", + "description": "Trajtojeni pritjen e vazhdueshme me fuqi të ulët si 'Në pritje për të nisur' derisa cikli të fillojë realisht.", + "data": { + "delay_start_detect_enabled": "Aktivizo zbulimin e fillimit të vonuar", + "delay_confirm_seconds": "Nisje e vonuar: Koha e konfirmimit të pritjes (s)", + "delay_timeout_hours": "Fillimi i vonuar: Koha maksimale e pritjes (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kur aktivizohet, zbulohet si një nisje me vonesë, një rritje e shkurtër e shkarkimit me fuqi të ulët e ndjekur nga energjia e qëndrueshme e gatishmërisë. Sensori i gjendjes tregon 'Në pritje për të nisur' gjatë vonesës. Asnjë kohë ose përparim i programit nuk gjurmohet derisa të fillojë realisht cikli. Kërkon që start_threshold_w të vendoset mbi fuqinë e pikës së shkarkimit.", + "delay_confirm_seconds": "Fuqia duhet të qëndrojë midis Pragut të Ndalimit (W) dhe Pragut të Nisjes (W) për kaq sekonda para se WashData të hyjë në gjendjen 'Në pritje për të nisur'. Filtron kulmet e shkurtra gjatë lundrimit në menu. Parazgjedhja: 60 s.", + "delay_timeout_hours": "Kohëzgjatja e sigurisë: nëse pajisja është ende në \"Prit për të nisur\" pas kaq shumë orësh, WashData rivendoset në Off. Parazgjedhja: 8 orë." + } + }, + "external_triggers_section": { + "name": "Shkaktues të jashtëm, dera dhe pauza", + "description": "Sensor opsional i jashtëm për fundin, sensor dere për zbulim të pauzës/pastërtisë dhe ndërprerës për prerjen e energjisë gjatë pauzës.", + "data": { + "external_end_trigger_enabled": "Aktivizo aktivizimin e jashtëm të fundit të ciklit", + "external_end_trigger": "Sensori i jashtëm i fundit të ciklit", + "external_end_trigger_inverted": "Përmbys logjikën e aktivizuesit (aktivizohet kur fiket)", + "door_sensor_entity": "Sensori i derës", + "pause_cuts_power": "Ndërpreni energjinë kur ndaloni", + "switch_entity": "Ndërro entitetin (për ndërprerjen e energjisë në pauzë)", + "notify_unload_delay_minutes": "Vonesa e njoftimit në pritje të rrobave (min.)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktivizo monitorimin e një sensori binar të jashtëm për të shkaktuar përfundimin e ciklit.", + "external_end_trigger": "Zgjidhni një njësi sensor binar. Kur ky sensor aktivizohet, cikli aktual do të përfundojë me statusin \"përfunduar\".", + "external_end_trigger_inverted": "Si parazgjedhje, aktivizuesi ndizet kur sensori ndizet. Zgjidhni këtë kuti për të ndezur kur sensori fiket në vend të kësaj.", + "door_sensor_entity": "Sensori binar opsional për derën e makinës (ndezur = hapur, fikur = mbyllur). Kur dera hapet gjatë një cikli aktiv, WashData do të konfirmojë ciklin si të ndërprerë qëllimisht. Pas përfundimit të ciklit, nëse dera është ende e mbyllur, gjendja ndryshon në \"Pastrim\" derisa të hapet dera. Shënim: mbyllja e derës NUK rifillon automatikisht një cikël të ndërprerë - rifillimi duhet të aktivizohet manualisht nëpërmjet butonit ose shërbimit të Ciklit të Rifillimit.", + "pause_cuts_power": "Kur bëni pauzë nëpërmjet butonit ose shërbimit të Ciklit të Pauzës, fikni gjithashtu entitetin e konfiguruar të ndërprerës. Hyn në fuqi vetëm nëse për këtë pajisje është konfiguruar një ent ndërruesi.", + "switch_entity": "Njësia e ndërrimit për t'u ndërruar kur ndalon ose rifillon. Kërkohet kur aktivizohet \"Prini energjinë kur ndalon\".", + "notify_unload_delay_minutes": "Dërgoni një njoftim nëpërmjet kanalit të njoftimit të përfundimit pasi të përfundojë cikli dhe dera nuk është hapur për kaq shumë minuta (kërkon Sensori i derës). Vendoseni në 0 për ta çaktivizuar. Parazgjedhja: 60 min." + } + }, + "pump_section": { + "name": "Monitori i pompës", + "description": "Vetëm për llojin e pajisjes pompë. Dërgon një ngjarje nëse një cikël pompe zgjat më shumë se kjo kohëzgjatje.", + "data": { + "pump_stuck_duration": "Pragu i alarmit të bllokimit të pompës (vetëm pompa)" + }, + "data_description": { + "pump_stuck_duration": "Nëse një cikël pompe vazhdon të funksionojë pas kaq shumë sekondash, një ngjarje ha_washdata_pump_stuck aktivizohet një herë. Përdoreni këtë për të zbuluar një motor të bllokuar (punimi tipik i pompës së gropës është nën 60 s). Parazgjedhja: 1800 s (30 min). Vetëm lloji i pajisjes së pompës." + } + }, + "device_link_section": { + "name": "Lidhja e pajisjes", + "description": "Lidhni opsionalisht këtë pajisje WashData me një pajisje ekzistuese Home Assistant (për shembull spinën inteligjente ose vetë pajisjen). Pajisja WashData më pas shfaqet si \"Connected via\" nga ajo pajisje. Lëreni bosh për të mbajtur WashData si një pajisje të pavarur.", + "data": { + "linked_device": "Pajisja e lidhur" + }, + "data_description": { + "linked_device": "Zgjidhni pajisjen me të cilën do të lidhni WashData. Kjo shton një referencë 'Connected via' në regjistrin e pajisjes; WashData ruan kartën dhe entitetet e veta të pajisjes. Fshi përzgjedhjen për të hequr lidhjen." + } + } + } + }, + "diagnostics": { + "title": "Diagnostikimi dhe mirëmbajtja", + "description": "Kryeni veprime të mirëmbajtjes si bashkimi i cikleve të fragmentuara ose migrimi i të dhënave të ruajtura.\n\n**Përdorimi i hapësirës ruajtëse**\n\n- Madhësia e skedarit: {file_size_kb} KB\n- Ciklet: {cycle_count}\n- Profilet: {profile_count}\n- Gjurmët e korrigjimit: {debug_count}", + "menu_options": { + "reprocess_history": "Mirëmbajtja: ripërpunoni dhe optimizoni të dhënat", + "clear_debug_data": "Pastro të dhënat e korrigjimit (liro hapësirë)", + "wipe_history": "Fshi TË GJITHA të dhënat për këtë pajisje (të pakthyeshme)", + "export_import": "Eksporto/importo JSON me cilësimet (kopjo/ngjit)", + "menu_back": "← Mbrapa" + } + }, + "clear_debug_data": { + "title": "Pastro të dhënat e korrigjimit", + "description": "Jeni i sigurt që dëshironi të fshini të gjitha gjurmët e ruajtura të korrigjimit? Kjo do të lirojë hapësirë, por do të heqë informacionin e detajuar të renditjes nga ciklet e kaluara." + }, + "manage_cycles": { + "title": "Menaxhoni ciklet", + "description": "Ciklet e fundit:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Etiketimi automatik i cikleve të vjetra", + "select_cycle_to_label": "Etiketoni ciklin specifik", + "select_cycle_to_delete": "Fshi ciklin", + "interactive_editor": "Redaktuesi interaktiv bashkim/ndarje", + "trim_cycle_select": "Prit të dhënat e ciklit", + "menu_back": "← Mbrapa" + } + }, + "manage_cycles_empty": { + "title": "Menaxhoni ciklet", + "description": "Nuk është regjistruar ende asnjë cikël.", + "menu_options": { + "auto_label_cycles": "Etiketimi automatik i cikleve të vjetra", + "menu_back": "← Mbrapa" + } + }, + "manage_profiles": { + "title": "Menaxho profilet", + "description": "Përmbledhje e profilit:\n{profile_summary}", + "menu_options": { + "create_profile": "Krijo një profil të ri", + "edit_profile": "Redakto/riemërto profilin", + "delete_profile_select": "Fshi profilin", + "profile_stats": "Statistikat e profilit", + "cleanup_profile": "Pastroni historikun – grafiku dhe fshirja", + "assign_profile_phases_select": "Cakto intervalet e fazave", + "menu_back": "← Mbrapa" + } + }, + "manage_profiles_empty": { + "title": "Menaxho profilet", + "description": "Nuk janë krijuar ende profile.", + "menu_options": { + "create_profile": "Krijo një profil të ri", + "menu_back": "← Mbrapa" + } + }, + "manage_phase_catalog": { + "title": "Menaxho katalogun e fazave", + "description": "Katalogu i fazave (parazgjedhjet për këtë pajisje + fazat e personalizuara):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Krijo një fazë të re", + "phase_catalog_edit_select": "Modifiko fazën", + "phase_catalog_delete": "Fshi fazën", + "menu_back": "← Mbrapa" + } + }, + "phase_catalog_create": { + "title": "Krijo një fazë të personalizuar", + "description": "Krijoni një emër dhe përshkrim të personalizuar të fazës dhe zgjidhni llojin e pajisjes për të cilën zbatohet.", + "data": { + "target_device_type": "Lloji i pajisjes së synuar", + "phase_name": "Emri i fazës", + "phase_description": "Përshkrimi i fazës" + } + }, + "phase_catalog_edit_select": { + "title": "Redakto fazën e personalizuar", + "description": "Zgjidhni një fazë të personalizuar për të modifikuar.", + "data": { + "phase_name": "Faza e personalizuar" + } + }, + "phase_catalog_edit": { + "title": "Redakto fazën e personalizuar", + "description": "Redaktimi i fazës: {phase_name}", + "data": { + "phase_name": "Emri i fazës", + "phase_description": "Përshkrimi i fazës" + } + }, + "phase_catalog_delete": { + "title": "Fshi fazën e personalizuar", + "description": "Zgjidhni një fazë të personalizuar për ta fshirë. Çdo interval i caktuar duke përdorur këtë fazë do të hiqet.", + "data": { + "phase_name": "Faza e personalizuar" + } + }, + "assign_profile_phases": { + "title": "Cakto intervalet e fazave", + "description": "Pamja paraprake e fazës për profilin: **{profile_name}**\n\n{timeline_svg}\n\n**Intervalet aktuale:**\n{current_ranges}\n\nPërdorni veprimet e mëposhtme për të shtuar, modifikuar, fshirë ose ruajtur intervalet.", + "menu_options": { + "assign_profile_phases_add": "Shto interval fazor", + "assign_profile_phases_edit_select": "Redakto intervalin e fazës", + "assign_profile_phases_delete": "Fshi intervalin e fazës", + "phase_ranges_clear": "Pastro të gjitha intervalet", + "assign_profile_phases_auto_detect": "Zbulo automatikisht fazat", + "phase_ranges_save": "Ruaj dhe kthehu", + "menu_back": "← Mbrapa" + } + }, + "assign_profile_phases_add": { + "title": "Shto interval fazor", + "description": "Shtoni një interval fazor në draftin aktual.", + "data": { + "phase_name": "Faza", + "start_min": "Minuta e fillimit", + "end_min": "Minuta e fundit" + } + }, + "assign_profile_phases_edit_select": { + "title": "Redakto intervalin e fazës", + "description": "Zgjidhni një interval për të modifikuar.", + "data": { + "range_index": "Intervali i fazës" + } + }, + "assign_profile_phases_edit": { + "title": "Redakto intervalin e fazës", + "description": "Përditësoni intervalin e zgjedhur të fazave.", + "data": { + "phase_name": "Faza", + "start_min": "Minuta e fillimit", + "end_min": "Minuta e fundit" + } + }, + "assign_profile_phases_delete": { + "title": "Fshi intervalin e fazës", + "description": "Zgjidhni një interval për të hequr nga drafti.", + "data": { + "range_index": "Intervali i fazës" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Fazat e zbuluara automatikisht", + "description": "Profili: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} faza u zbuluan automatikisht.** Zgjidhni një veprim më poshtë.", + "data": { + "action": "Veprimi" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Emërtoni fazat e zbuluara", + "description": "Profili: **{profile_name}**\n\n**{detected_count} faza të zbuluara:**\n{ranges_summary}\n\nCaktoni një emër për secilën fazë." + }, + "assign_profile_phases_select": { + "title": "Cakto intervalet e fazave", + "description": "Zgjidhni një profil për të konfiguruar intervalet e fazave.", + "data": { + "profile": "Profili" + } + }, + "profile_stats": { + "title": "Statistikat e profilit", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Krijo një profil të ri", + "description": "Krijo një profil të ri me dorë ose nga një cikël i kaluar.\n\nShembuj të emrit të profilit: 'Delikate', 'Ngarkesë e rëndë', 'Larje e shpejtë'", + "data": { + "profile_name": "Emri i profilit", + "reference_cycle": "Cikli i referencës (opsionale)", + "manual_duration": "Kohëzgjatja manuale (minuta)" + } + }, + "edit_profile": { + "title": "Redakto profilin", + "description": "Zgjidhni një profil për ta riemërtuar", + "data": { + "profile": "Profili" + } + }, + "rename_profile": { + "title": "Redakto detajet e profilit", + "description": "Profili aktual: {current_name}", + "data": { + "new_name": "Emri i profilit", + "manual_duration": "Kohëzgjatja manuale (minuta)" + } + }, + "delete_profile_select": { + "title": "Fshi profilin", + "description": "Zgjidhni një profil për të fshirë", + "data": { + "profile": "Profili" + } + }, + "delete_profile_confirm": { + "title": "Konfirmo fshirjen e profilit", + "description": "⚠️ Kjo do të fshijë përgjithmonë profilin: {profile_name}", + "data": { + "unlabel_cycles": "Hiq etiketën nga ciklet që përdorin këtë profil" + } + }, + "auto_label_cycles": { + "title": "Etiketimi automatik i cikleve të vjetra", + "description": "U gjetën {total_count} cikle gjithsej. Profilet: {profiles}", + "data": { + "confidence_threshold": "Besueshmëria minimale (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Zgjidhni ciklin për të etiketuar", + "description": "✓ = përfunduar, ⚠ = rifilluar, ✗ = ndërprerë", + "data": { + "cycle_id": "Cikli" + } + }, + "select_cycle_to_delete": { + "title": "Fshi ciklin", + "description": "⚠️ Kjo do të fshijë përgjithmonë ciklin e zgjedhur", + "data": { + "cycle_id": "Cikli për të fshirë" + } + }, + "label_cycle": { + "title": "Etiketoni ciklin", + "description": "{cycle_info}", + "data": { + "profile_name": "Profili", + "new_profile_name": "Emri i profilit të ri (nëse krijohet)" + } + }, + "post_process": { + "title": "Historiku i procesit (bashkim/ndarje)", + "description": "Pastroni historinë e ciklit duke bashkuar ekzekutimet e fragmentuara dhe duke ndarë ciklet e bashkuara gabimisht brenda një dritareje kohore. Fut numrin e orëve për të parë prapa (përdor 999999 për të gjithë).", + "data": { + "time_range": "Dritarja e rishikimit (orë)", + "gap_seconds": "Hendeku bashkim/ndarje (sekonda)" + }, + "data_description": { + "time_range": "Numri i orëve për të skanuar për cikle të fragmentuara për t'u bashkuar. Përdorni 999999 për të përpunuar të gjitha të dhënat historike.", + "gap_seconds": "Pragu për të vendosur ndarjen kundër bashkimit. Boshllëqet MË SHKURTËR se kjo bashkohen. Boshllëqet MË TË GJATA se kjo ndahen. Uleni këtë për të detyruar një ndarje në një cikël që është bashkuar gabimisht." + } + }, + "cleanup_profile": { + "title": "Pastrimi i historikut – zgjidhni profilin", + "description": "Zgjidhni një profil për të vizualizuar. Kjo do të gjenerojë një grafik që tregon të gjitha ciklet e kaluara për këtë profil për të ndihmuar në identifikimin e vlerave të jashtme.", + "data": { + "profile": "Profili" + } + }, + "cleanup_select": { + "title": "Pastroni historikun – grafiku dhe fshirja", + "description": "![Grafiku]({graph_url})\n\n**Vizualizimi i {profile_name}**\n\nGrafiku tregon ciklet individuale si vija me ngjyra. Identifikoni vlerat e jashtme (vijat larg grupit) dhe gjeni ngjyrën e tyre në listën e mëposhtme për t'i fshirë ato.\n\n**Zgjidhni ciklet për t'i fshirë përgjithmonë:**", + "data": { + "cycles_to_delete": "Ciklet për fshirje (kontrollo ngjyrat që përputhen)" + } + }, + "interactive_editor": { + "title": "Redaktuesi interaktiv bashkim/ndarje", + "description": "Zgjidhni një veprim manual për të kryer në historikun e ciklit.", + "menu_options": { + "editor_split": "Ndaj ciklin (gjej boshllëqet)", + "editor_merge": "Bashko ciklet (bashko fragmentet)", + "editor_delete": "Fshi ciklet", + "menu_back": "← Mbrapa" + } + }, + "editor_select": { + "title": "Zgjidhni ciklet", + "description": "Zgjidhni ciklin(et) që do të përpunoni.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklet" + } + }, + "editor_configure": { + "title": "Konfiguro dhe apliko", + "description": "{preview_md}", + "data": { + "confirm_action": "Veprimi", + "merged_profile": "Profili që rezulton", + "new_profile_name": "Emri i profilit të ri", + "confirm_commit": "Po, e konfirmoj këtë veprim", + "segment_0_profile": "Profili i segmentit 1", + "segment_1_profile": "Profili i segmentit 2", + "segment_2_profile": "Profili i segmentit 3", + "segment_3_profile": "Profili i segmentit 4", + "segment_4_profile": "Profili i segmentit 5", + "segment_5_profile": "Profili i segmentit 6", + "segment_6_profile": "Profili i segmentit 7", + "segment_7_profile": "Profili i segmentit 8", + "segment_8_profile": "Profili i segmentit 9", + "segment_9_profile": "Profili i segmentit 10" + } + }, + "editor_split_params": { + "title": "Konfigurimi i ndarjes", + "description": "Rregulloni ndjeshmërinë për ndarjen e këtij cikli. Çdo boshllëk më i gjatë se kjo do të shkaktojë një ndarje.", + "data": { + "split_mode": "Metoda e ndarjes" + } + }, + "editor_split_auto_params": { + "title": "Konfigurimi i ndarjes automatike", + "description": "Rregulloni ndjeshmërinë për ndarjen e këtij cikli. Çdo boshllëk pasiv më i gjatë se ky do të shkaktojë ndarje.", + "data": { + "min_gap_seconds": "Pragu i boshllëkut të ndarjes (sekonda)" + } + }, + "editor_split_manual_params": { + "title": "Vula kohore të ndarjes manuale", + "description": "{preview_md}Dritarja e ciklit: **{cycle_start_wallclock} → {cycle_end_wallclock}** më {cycle_date}.\n\nFut një ose më shumë vula kohore ndarjeje (`HH:MM` ose `HH:MM:SS`), një për çdo rresht. Cikli do të pritet në çdo vulë kohore — N vula kohore prodhojnë N+1 segmente.", + "data": { + "split_timestamps": "Vulat kohore të ndarjes" + } + }, + "reprocess_history": { + "title": "Mirëmbajtja: ripërpunoni dhe optimizoni të dhënat", + "description": "Kryen pastrim të thellë të të dhënave historike:\n1. Shkurton leximet me fuqi zero (heshtje).\n2. Rregullon kohën/kohëzgjatjen e ciklit.\n3. Rillogarit të gjitha modelet statistikore (zarfet).\n\n**Ekzekutoni këtë për të rregulluar problemet e të dhënave ose për të aplikuar algoritme të reja.**" + }, + "wipe_history": { + "title": "Fshi historikun (vetëm për testim)", + "description": "⚠️ Kjo do të fshijë përgjithmonë TË GJITHA ciklet dhe profilet e ruajtura për këtë pajisje. Kjo nuk mund të zhbëhet!" + }, + "export_import": { + "title": "Eksporto/Importo JSON", + "description": "Kopjo/ngjit ciklet, profilet dhe cilësimet për këtë pajisje. Eksporto më poshtë ose ngjit JSON për të importuar.", + "data": { + "mode": "Veprimi", + "json_payload": "Konfigurimi i plotë JSON" + }, + "data_description": { + "mode": "Zgjidhni Eksporto për të kopjuar të dhënat dhe cilësimet aktuale, ose Importo për të ngjitur të dhënat e eksportuara nga një pajisje tjetër WashData.", + "json_payload": "Për eksport: kopjoni këtë JSON (përfshin ciklet, profilet dhe të gjitha cilësimet e rregulluara mirë). Për import: ngjitni JSON të eksportuar nga WashData." + } + }, + "record_cycle": { + "title": "Regjistrimi i ciklit", + "description": "Regjistroni manualisht një cikël për të krijuar një profil të pastër pa ndërhyrje.\n\nStatusi: **{status}**\nKohëzgjatja: {duration} s\nMostrat: {samples}", + "menu_options": { + "record_refresh": "Rifresko statusin", + "record_stop": "Ndalo regjistrimin (ruaj dhe përpuno)", + "record_start": "Fillo regjistrimin e ri", + "record_process": "Përpuno regjistrimin e fundit (pre dhe ruaj)", + "record_discard": "Hiq regjistrimin e fundit", + "menu_back": "← Mbrapa" + } + }, + "record_process": { + "title": "Procesi i regjistrimit", + "description": "![Grafiku]({graph_url})\n\n**Grafiku tregon regjistrimin e papërpunuar.** Blu = Mbaj, e kuqe = Prerje e propozuar.\n*Grafiku është statik dhe nuk përditësohet me ndryshimet e formës.*\n\nRegjistrimi u ndal. Prerjet janë rreshtuar me shpejtësinë e zbuluar të kampionimit (~{sampling_rate} s).\n\nKohëzgjatja e papërpunuar: {duration} s\nMostrat: {samples}", + "data": { + "head_trim": "Prerja e fillimit (sekonda)", + "tail_trim": "Prerja e fundit (sekonda)", + "save_mode": "Destinacioni i ruajtjes", + "profile_name": "Emri i profilit" + } + }, + "learning_feedbacks": { + "title": "Reagimet e të mësuarit", + "description": "Komente në pritje: {count}\n\n{pending_table}\n\nZgjidhni një kërkesë për rishikim cikli për ta përpunuar.", + "menu_options": { + "learning_feedbacks_pick": "Rishiko një koment në pritje", + "learning_feedbacks_dismiss_all": "Hidh poshtë të gjitha komentet në pritje", + "menu_back": "← Mbrapa" + } + }, + "learning_feedbacks_pick": { + "title": "Zgjidh reagimet për rishikim", + "description": "Zgjidh një kërkesë për rishikim cikli për ta hapur.", + "data": { + "selected_feedback": "Zgjidh ciklin për rishikim" + } + }, + "learning_feedbacks_empty": { + "title": "Reagimet e të mësuarit", + "description": "Nuk u gjet asnjë kërkesë për komente në pritje.", + "menu_options": { + "menu_back": "← Mbrapa" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Hidh poshtë të gjitha reagimet në pritje", + "description": "Ju jeni gati të hidhni poshtë **{count}** kërkesa reagimi në pritje. Ciklet e hedhura poshtë mbeten në historikun tuaj, por nuk do të kontribuojnë me sinjal të ri mësimor. Kjo nuk mund të zhbëhet.\n\n✅ Kontrolloni kutinë më poshtë dhe klikoni **Dërgo** për t'i hedhur poshtë të gjitha.\n❌ Lëreni të pazgjedhur dhe klikoni **Dërgo** për ta anuluar dhe për t'u kthyer prapa.", + "data": { + "confirm_dismiss_all": "Konfirmo: hidh poshtë të gjitha kërkesat e komenteve në pritje" + } + }, + "resolve_feedback": { + "title": "Zgjidhni komentet", + "description": "{comparison_img}{candidates_table}\n**Profili i zbuluar**: {detected_profile} ({confidence_pct}%)\n**Kohëzgjatja e parashikuar**: {est_duration_min} min\n**Kohëzgjatja aktuale**: {act_duration_min} min\n\n__Zgjidhni një veprim më poshtë:__", + "data": { + "action": "Çfarë dëshironi të bëni?", + "corrected_profile": "Programi i saktë (nëse korrigjohet)", + "corrected_duration": "Kohëzgjatja e saktë në sekonda (nëse korrigjohet)" + } + }, + "trim_cycle_select": { + "title": "Prerja e ciklit - zgjidhni ciklin", + "description": "Zgjidhni një cikël për të prerë. ✓ = përfunduar, ⚠ = rifilluar, ✗ = ndërprerë", + "data": { + "cycle_id": "Cikli" + } + }, + "trim_cycle": { + "title": "Prerja e ciklit", + "description": "Dritarja aktuale: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Veprimi" + } + }, + "trim_cycle_start": { + "title": "Cakto fillimin e prerjes", + "description": "Dritarja e ciklit: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFillimi aktual: **{current_wallclock}** (+{current_offset_min} min nga fillimi i ciklit)\n\nZgjidh një orë të re fillimi duke përdorur zgjedhësin e orës më poshtë.", + "data": { + "trim_start_time": "Koha e re e fillimit", + "trim_start_min": "Fillimi i ri (minuta nga fillimi i ciklit)" + } + }, + "trim_cycle_end": { + "title": "Cakto fundin e prerjes", + "description": "Dritarja e ciklit: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nFundi aktual: **{current_wallclock}** (+{current_offset_min} min nga fillimi i ciklit)\n\nZgjidh një kohë të re përfundimi duke përdorur zgjedhësin e orës më poshtë.", + "data": { + "trim_end_time": "Koha e re e përfundimit", + "trim_end_min": "Fundi i ri (minuta nga fillimi i ciklit)" + } + } + }, + "error": { + "import_failed": "Importi dështoi. Ju lutemi ngjisni një JSON të vlefshëm të eksportit të WashData.", + "select_exactly_one": "Zgjidhni saktësisht një cikël për këtë veprim.", + "select_at_least_one": "Zgjidhni të paktën një cikël për këtë veprim.", + "select_at_least_two": "Zgjidhni të paktën dy cikle për këtë veprim.", + "profile_exists": "Një profil me këtë emër ekziston tashmë", + "rename_failed": "Riemërtimi i profilit dështoi. Profili mund të mos ekzistojë ose emri i ri është marrë tashmë.", + "assignment_failed": "Caktimi i profilit për cikël dështoi", + "duplicate_phase": "Një fazë me këtë emër ekziston tashmë.", + "phase_not_found": "Faza e përzgjedhur nuk u gjet.", + "invalid_phase_name": "Emri i fazës nuk mund të jetë bosh.", + "phase_name_too_long": "Emri i fazës është shumë i gjatë.", + "invalid_phase_range": "Intervali i fazës është i pavlefshëm. Fundi duhet të jetë më i madh se fillimi.", + "invalid_phase_timestamp": "Vula kohore është e pavlefshme. Përdor formatin VVVV-MM-DD HH:MM ose HH:MM.", + "overlapping_phase_ranges": "Intervalet e fazave mbivendosen. Rregulloni intervalet dhe provoni përsëri.", + "incomplete_phase_row": "Çdo rresht fazor duhet të përfshijë një fazë plus vlerat e plota të fillimit/mbarimit për modalitetin e zgjedhur.", + "timestamp_mode_cycle_required": "Ju lutemi zgjidhni një cikël burimi kur përdorni modalitetin e vulës kohore.", + "no_phase_ranges": "Nuk disponohet ende asnjë interval fazor për atë veprim.", + "feedback_notification_title": "WashData: verifiko ciklin ({device})", + "feedback_notification_message": "**Pajisja**: {device}\n**Programi**: {program} ({confidence}% besueshmëri)\n**Koha**: {time}\n\nWashData ka nevojë për ndihmën tuaj për të verifikuar këtë cikël të zbuluar.\n\nJu lutemi, shkoni te **Cilësimet > Pajisjet dhe shërbimet > WashData > Konfiguro > Reagimet e të mësuarit** për të konfirmuar ose korrigjuar këtë rezultat.", + "suggestions_ready_notification_title": "WashData: cilësimet e sugjeruara janë gati ({device})", + "suggestions_ready_notification_message": "Sensori **Cilësimet e sugjeruara** raporton tani **{count}** rekomandime të zbatueshme.\n\nPër t'i rishikuar dhe zbatuar ato: **Cilësimet > Pajisjet dhe shërbimet > WashData > Konfiguro > Cilësimet e avancuara > Apliko vlerat e sugjeruara**.\n\nSugjerimet janë opsionale dhe shfaqen për shqyrtim përpara se të ruani.", + "trim_range_invalid": "Fillimi duhet të jetë para fundit. Ju lutemi rregulloni pikat tuaja të prerjes.", + "trim_failed": "Prerja dështoi. Cikli mund të mos ekzistojë më ose dritarja është bosh.", + "invalid_split_timestamp": "Vula kohore është e pavlefshme ose jashtë dritares së ciklit. Përdorni HH:MM ose HH:MM:SS brenda dritares së ciklit.", + "no_split_segments_found": "Nuk u prodhuan segmente të vlefshme nga këto vula kohore. Sigurohuni që çdo vulë kohore të jetë brenda dritares së ciklit dhe segmentet të jenë të paktën 1 minutë të gjata.", + "auto_tune_suggestion": "{device_type} {device_title} zbuloi cikle fantazmë. Ndryshimi i sugjeruar min_fuqi: {current_min}W -> {new_min}W (nuk zbatohet automatikisht).", + "auto_tune_title": "Përshtatja automatike e të dhënave të larjes", + "auto_tune_fallback": "{device_type} {device_title} zbuloi cikle fantazmë. Ndryshimi i sugjeruar min_fuqi: {current_min}W -> {new_min}W (nuk zbatohet automatikisht)." + }, + "abort": { + "no_cycles_found": "Nuk u gjetën cikle", + "no_split_segments_found": "Nuk u gjetën segmente të ndashme në ciklin e zgjedhur.", + "cycle_not_found": "Cikli i zgjedhur nuk mund të ngarkohej.", + "no_power_data": "Nuk ka të dhëna për gjurmët e fuqisë për ciklin e zgjedhur.", + "no_profiles_found": "Nuk u gjet asnjë profil. Krijoni fillimisht një profil.", + "no_profiles_for_matching": "Nuk ka profile të disponueshme për përputhje. Fillimisht krijoni profile.", + "no_unlabeled_cycles": "Të gjitha ciklet tashmë janë etiketuar", + "no_suggestions": "Nuk ka ende vlera të sugjeruara. Kryeni disa cikle dhe provoni përsëri.", + "no_predictions": "Asnjë parashikim", + "no_custom_phases": "Nuk u gjetën faza të personalizuara.", + "reprocess_success": "{count} cikle u ripërpunuan me sukses.", + "debug_data_cleared": "Të dhënat e korrigjimit u pastruan me sukses nga {count} cikle." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Fillimi i ciklit", + "cycle_finish": "Përfundimi i ciklit", + "cycle_live": "Progresi i drejtpërdrejtë" + } + }, + "common_text": { + "options": { + "unlabeled": "(i paetiketuar)", + "create_new_profile": "Krijo një profil të ri", + "remove_label": "Hiq etiketën", + "no_reference_cycle": "(Nuk ka cikël referimi)", + "all_device_types": "Të gjitha llojet e pajisjeve", + "current_suffix": "(aktual)", + "editor_select_info": "Zgjidhni 1 cikël për t'u ndarë ose 2+ cikle për t'u bashkuar.", + "editor_select_info_split": "Zgjidhni 1 cikël për ta ndarë.", + "editor_select_info_merge": "Zgjidhni 2+ cikle për t'i bashkuar.", + "editor_select_info_delete": "Zgjidhni 1+ cikël(e) për t'i fshirë.", + "no_cycles_recorded": "Nuk është regjistruar ende asnjë cikël.", + "profile_summary_line": "- **{name}**: {count} cikle, mesatarisht {avg} m", + "no_profiles_created": "Nuk janë krijuar ende profile.", + "phase_preview": "Pamja paraprake e fazës", + "phase_preview_no_curve": "Kurba e profilit mesatar nuk është ende e disponueshme. Ekzekuto/etiketoni më shumë cikle për këtë profil.", + "average_power_curve": "Kurba e fuqisë mesatare", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cikle, ~{duration} m mesatarisht)", + "profile_option_short_fmt": "{name} ({count} cikle, ~{duration} m)", + "deleted_cycles_from_profile": "U fshinë {count} cikle nga {profile}.", + "not_enough_data_graph": "Nuk ka të dhëna të mjaftueshme për të krijuar grafikun.", + "no_profiles_found": "Nuk u gjet asnjë profil.", + "cycle_info_fmt": "Cikli: {start}, {duration} min, etiketë: {label}", + "top_candidates_header": "### Kandidatët kryesorë", + "tbl_profile": "Profili", + "tbl_confidence": "Besueshmëria", + "tbl_correlation": "Korrelacioni", + "tbl_duration_match": "Përputhja e kohëzgjatjes", + "detected_profile": "Profili i zbuluar", + "estimated_duration": "Kohëzgjatja e parashikuar", + "actual_duration": "Kohëzgjatja aktuale", + "choose_action": "__Zgjidhni një veprim më poshtë:__", + "tbl_count": "Nr.", + "tbl_avg": "Mesatar", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energjia (mesatare)", + "tbl_energy_total": "Energjia (total)", + "tbl_consistency": "Konz.", + "tbl_last_run": "Ekzekutimi i fundit", + "graph_legend_title": "Legjenda e grafikut", + "graph_legend_body": "Shiriti blu përfaqëson diapazonin minimal dhe maksimal të tërheqjes së fuqisë së vëzhguar. Linja tregon kurbën e fuqisë mesatare.", + "recording_preview": "Pamja paraprake e regjistrimit", + "trim_start": "Fillimi i prerjes", + "trim_end": "Fundi i prerjes", + "split_preview_title": "Pamja paraprake e ndarjes", + "split_preview_found_fmt": "U gjetën {count} segmente.", + "split_preview_confirm_fmt": "Kliko Konfirmo për ta ndarë këtë cikël në {count} cikle të veçanta.", + "merge_preview_title": "Pamja paraprake e bashkimit", + "merge_preview_joining_fmt": "Po bashkohet me {count} cikle. Boshllëqet do të mbushen me lexime 0 W.", + "editor_delete_preview_title": "Pamja paraprake e fshirjes", + "editor_delete_preview_intro": "Ciklet e zgjedhura do të fshihen përgjithmonë:", + "editor_delete_preview_confirm": "Klikoni Konfirmo për t'i fshirë përgjithmonë këto regjistrime cikli.", + "no_power_preview": "*Nuk ka të dhëna për energjinë e disponueshme për shikim paraprak.*", + "profile_comparison": "Krahasimi i profilit", + "actual_cycle_label": "Ky cikël (aktual)", + "storage_usage_header": "Përdorimi i hapësirës ruajtëse", + "storage_file_size": "Madhësia e skedarit", + "storage_cycles": "Ciklet", + "storage_profiles": "Profilet", + "storage_debug_traces": "Gjurmët e korrigjimit", + "overview_suffix": "Vështrim i përgjithshëm", + "phase_preview_for_profile": "Pamja paraprake e fazës për profilin", + "current_ranges_header": "Intervalet aktuale", + "assign_phases_help": "Përdorni veprimet e mëposhtme për të shtuar, modifikuar, fshirë ose ruajtur intervalet.", + "profile_summary_header": "Përmbledhje e profilit", + "recent_cycles_header": "Ciklet e fundit", + "trim_cycle_preview_title": "Pamja paraprake e prerjes së ciklit", + "trim_cycle_preview_no_data": "Nuk ka të dhëna për energjinë për këtë cikël.", + "trim_cycle_preview_kept_suffix": "mbajtur", + "table_program": "Programi", + "table_when": "Kur", + "table_length": "Kohëzgjatja", + "table_match": "Përputhja", + "table_profile": "Profili", + "table_cycles": "Ciklet", + "table_avg_length": "Mes. kohëzgjatje", + "table_last_run": "Ekzekutimi i fundit", + "table_avg_energy": "Mes. energji", + "table_detected_program": "Programi i zbuluar", + "table_confidence": "Besueshmëria", + "table_reported": "Raportuar", + "phase_builtin_suffix": "(I integruar)", + "phase_none_available": "Nuk ka faza të disponueshme.", + "settings_deprecation_warning": "⚠️ **Lloji i vjetër i pajisjes:** {device_type} është planifikuar të hiqet në një version të ardhshëm. Tubacioni i përputhjes së WashData nuk prodhon rezultate të besueshme për këtë klasë pajisjesh. Integrimi juaj vazhdon të funksionojë gjatë periudhës së zhvlerësimit; për të heshtur këtë paralajmërim, kaloni **Lloji i pajisjes** më poshtë në një nga llojet e mbështetura (Makineri larëse, Tharëse, Kombinimi Lavastovilje-Tharrëse, Enëlarëse, Frytese me ajër, Prodhues buke ose Pompë) ose në **Të tjera (të avancuara)** nëse pajisja juaj nuk përputhet me asnjë nga llojet e mbështetur. **Të tjera (të avancuara)** dërgon qëllimisht standarde gjenerike që nuk janë të akorduara për ndonjë pajisje specifike, kështu që do t'ju duhet të konfiguroni vetë pragjet, afatet kohore dhe parametrat që përputhen; të gjitha cilësimet tuaja ekzistuese ruhen kur kaloni.", + "phase_other_device_types": "Llojet e tjera të pajisjeve:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Ndaj ciklin (gjej boshllëqet)", + "merge": "Bashko ciklet (bashko fragmentet)", + "delete": "Fshi ciklet" + } + }, + "split_mode": { + "options": { + "auto": "Zbulo automatikisht boshllëqet e pushimit", + "manual": "Vulë(a) kohore manuale" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Mirëmbajtja: ripërpunoni dhe optimizoni të dhënat", + "clear_debug_data": "Pastro të dhënat e korrigjimit (liro hapësirë)", + "wipe_history": "Fshi TË GJITHA të dhënat për këtë pajisje (të pakthyeshme)", + "export_import": "Eksporto/importo JSON me cilësimet (kopjo/ngjit)" + } + }, + "export_import_mode": { + "options": { + "export": "Eksporto të gjitha të dhënat", + "import": "Importo/bashko të dhënat" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Etiketimi automatik i cikleve të vjetra", + "select_cycle_to_label": "Etiketoni ciklin specifik", + "select_cycle_to_delete": "Fshi ciklin", + "interactive_editor": "Redaktuesi interaktiv bashkim/ndarje", + "trim_cycle": "Prit të dhënat e ciklit" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Krijo një profil të ri", + "edit_profile": "Redakto/riemërto profilin", + "delete_profile": "Fshi profilin", + "profile_stats": "Statistikat e profilit", + "cleanup_profile": "Pastroni historikun – grafiku dhe fshirja", + "assign_phases": "Cakto intervalet e fazave" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Krijo një fazë të re", + "edit_custom_phase": "Modifiko fazën", + "delete_custom_phase": "Fshi fazën" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Shto interval fazor", + "edit_range": "Redakto intervalin e fazës", + "delete_range": "Fshi intervalin e fazës", + "clear_ranges": "Pastro të gjitha intervalet", + "auto_detect_ranges": "Zbulo automatikisht fazat", + "save_ranges": "Ruaj dhe kthehu" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Rifresko statusin", + "stop_recording": "Ndalo regjistrimin (ruaj dhe përpuno)", + "start_recording": "Fillo regjistrimin e ri", + "process_recording": "Përpuno regjistrimin e fundit (pre dhe ruaj)", + "discard_recording": "Hiq regjistrimin e fundit" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Krijo një profil të ri", + "existing_profile": "Shto në profilin ekzistues", + "discard": "Hiq regjistrimin" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Konfirmo – zbulimi i saktë", + "correct": "Korrigjo – zgjidhni programin/kohëzgjatjen", + "ignore": "Injoro – cikël i rremë pozitiv/zhurmë", + "delete": "Fshi – hiq nga historiku" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Emërtoni dhe aplikoni fazat", + "cancel": "Kthehu pa ndryshime" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Cakto pikën e fillimit", + "set_end": "Cakto pikën e fundit", + "reset": "Rivendos në kohëzgjatje të plotë", + "apply": "Apliko prerjen", + "cancel": "Anulo" + } + }, + "device_type": { + "options": { + "washing_machine": "Makinë larëse", + "dryer": "Tharëse", + "washer_dryer": "Kombinim larëse-tharëse", + "dishwasher": "Lavastovilje", + "coffee_machine": "Makinë kafeje (e vjetëruar)", + "ev": "Automjeti elektrik (i vjetëruar)", + "air_fryer": "Fryerja e ajrit", + "heat_pump": "Pompë nxehtësie (e vjetëruar)", + "bread_maker": "Bukëbërës", + "pump": "Pompë / Pompë gropë", + "oven": "Furra (e vjetëruar)", + "other": "Të tjera (të avancuara)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiketoni ciklin", + "description": "Caktoni një profil ekzistues në një cikël të kaluar ose hiqni etiketën.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse për të etiketuar." + }, + "cycle_id": { + "name": "ID e ciklit", + "description": "ID-ja e ciklit për të etiketuar." + }, + "profile_name": { + "name": "Emri i profilit", + "description": "Emri i një profili ekzistues (krijoni profile në menunë Menaxho profilet). Lëreni bosh për të hequr etiketën." + } + } + }, + "create_profile": { + "name": "Krijo profilin", + "description": "Krijo një profil të ri (i pavarur ose i bazuar në një cikël referimi).", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse." + }, + "profile_name": { + "name": "Emri i profilit", + "description": "Emri për profilin e ri (p.sh. 'Ngarkesë e rëndë', 'Delikate')." + }, + "reference_cycle_id": { + "name": "ID e ciklit të referencës", + "description": "ID-ja opsionale e ciklit për ta bazuar këtë profil." + } + } + }, + "delete_profile": { + "name": "Fshi profilin", + "description": "Fshini një profil dhe opsionalisht hiqni etiketat nga ciklet që e përdorin atë.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse." + }, + "profile_name": { + "name": "Emri i profilit", + "description": "Profili për të fshirë." + }, + "unlabel_cycles": { + "name": "Hiq etiketën e cikleve", + "description": "Hiqni etiketën e profilit nga ciklet që përdorin këtë profil." + } + } + }, + "auto_label_cycles": { + "name": "Etiketimi automatik i cikleve të vjetra", + "description": "Etiketoni në mënyrë retroaktive ciklet e paetiketuara duke përdorur përputhjen e profilit.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse." + }, + "confidence_threshold": { + "name": "Pragu i besueshmërisë", + "description": "Besueshmëria minimale e përputhjes (0,50–0,95) për të aplikuar etiketat." + } + } + }, + "export_config": { + "name": "Eksporto konfigurimin", + "description": "Eksporto profilet, ciklet dhe cilësimet e kësaj pajisjeje në një skedar JSON (në server).", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse për eksport." + }, + "path": { + "name": "Rruga", + "description": "Shtegu opsional i skedarit absolut për të shkruar (parazgjedhur në /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importo konfigurimin", + "description": "Importoni profilet, ciklet dhe cilësimet për këtë pajisje nga një skedar eksporti JSON (cilësimet mund të mbishkruhen).", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse për t'u importuar." + }, + "path": { + "name": "Rruga", + "description": "Rruga absolute për të importuar skedarin JSON të eksportit." + } + } + }, + "submit_cycle_feedback": { + "name": "Dërgo komentet e ciklit", + "description": "Konfirmoni ose korrigjoni një program të zbuluar automatikisht pas një cikli të përfunduar. Jepni `entry_id` (i avancuar) ose `device_id` (rekomandohet).", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja WashData (rekomandohet në vend të entry_id)." + }, + "entry_id": { + "name": "ID e hyrjes", + "description": "ID-ja e hyrjes së konfigurimit për pajisjen (alternativë e device_id)." + }, + "cycle_id": { + "name": "ID e ciklit", + "description": "ID-ja e ciklit shfaqet në njoftimin/regjistrat e komenteve." + }, + "user_confirmed": { + "name": "Konfirmo programin e zbuluar", + "description": "Cakto true nëse programi i zbuluar është i saktë." + }, + "corrected_profile": { + "name": "Profili i korrigjuar", + "description": "Nëse nuk konfirmohet, jepni emrin e saktë të profilit/programit." + }, + "corrected_duration": { + "name": "Kohëzgjatja e korrigjuar (sekonda)", + "description": "Kohëzgjatja opsionale e korrigjuar në sekonda." + }, + "notes": { + "name": "Shënime", + "description": "Shënime opsionale për këtë cikël." + } + } + }, + "record_start": { + "name": "Fillo regjistrimin e ciklit", + "description": "Filloni të regjistroni manualisht një cikël të pastër (anashkalon të gjithë logjikën e përputhjes).", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse për të regjistruar." + } + } + }, + "record_stop": { + "name": "Ndalo regjistrimin e ciklit", + "description": "Ndalo regjistrimin manual.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja e makinës larëse për të ndaluar regjistrimin." + } + } + }, + "trim_cycle": { + "name": "Prit ciklin", + "description": "Pritini të dhënat e fuqisë së një cikli të kaluar në një dritare kohe specifike.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja WashData." + }, + "cycle_id": { + "name": "ID e ciklit", + "description": "ID-ja e ciklit për t'u prerë." + }, + "trim_start_s": { + "name": "Fillimi i prerjes (sekonda)", + "description": "Mbajini të dhënat nga ky kompensim në sekonda. Parazgjedhja: 0." + }, + "trim_end_s": { + "name": "Fundi i prerjes (sekonda)", + "description": "Mbani të dhënat deri në këtë kompensim në sekonda. Parazgjedhja = kohëzgjatja e plotë." + } + } + }, + "pause_cycle": { + "name": "Cikli i pauzës", + "description": "Ndalo ciklin aktiv për një pajisje WashData.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja WashData në pauzë." + } + } + }, + "resume_cycle": { + "name": "Cikli i rifillimit", + "description": "Rifilloni një cikël të ndërprerë për një pajisje WashData.", + "fields": { + "device_id": { + "name": "Pajisja", + "description": "Pajisja WashData për të rifilluar." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Në punë" + }, + "match_ambiguity": { + "name": "Paqartësia e përputhjes" + } + }, + "sensor": { + "washer_state": { + "name": "Gjendja", + "state": { + "off": "Fikur", + "idle": "Në pritje", + "starting": "Duke filluar", + "running": "Në punë", + "paused": "Në pauzë", + "user_paused": "Ndërprerë nga përdoruesi", + "ending": "Duke përfunduar", + "finished": "Përfunduar", + "anti_wrinkle": "Kundër rrudhave", + "delay_wait": "Në pritje për të filluar", + "interrupted": "E ndërprerë", + "force_stopped": "Ndaluar me forcë", + "rinse": "Shpëlarje", + "unknown": "E panjohur", + "clean": "I pastër" + } + }, + "washer_program": { + "name": "Programi" + }, + "time_remaining": { + "name": "Koha e mbetur" + }, + "total_duration": { + "name": "Kohëzgjatja totale" + }, + "cycle_progress": { + "name": "Progresi" + }, + "current_power": { + "name": "Fuqia aktuale" + }, + "elapsed_time": { + "name": "Koha e kaluar" + }, + "debug_info": { + "name": "Informacioni i korrigjimit" + }, + "match_confidence": { + "name": "Besueshmëria e përputhjes" + }, + "top_candidates": { + "name": "Kandidatët kryesorë", + "state": { + "none": "Asnjë" + } + }, + "current_phase": { + "name": "Faza aktuale" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Numri i cikleve të profilit {profile_name}", + "unit_of_measurement": "cikle" + }, + "suggestions": { + "name": "Cilësimet e sugjeruara të disponueshme" + }, + "pump_runs_today": { + "name": "Punimet e pompës (24 orët e fundit)" + }, + "cycle_count": { + "name": "Numërimi i ciklit" + } + }, + "select": { + "program_select": { + "name": "Programi i ciklit", + "state": { + "auto_detect": "Zbulimi automatik" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Forco përfundimin e ciklit" + }, + "pause_cycle": { + "name": "Cikli i pauzës" + }, + "resume_cycle": { + "name": "Cikli i rifillimit" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Kërkohet një ID e vlefshme e pajisjes." + }, + "cycle_id_required": { + "message": "Kërkohet një cikli_id i vlefshëm." + }, + "profile_name_required": { + "message": "Kërkohet një emër_profili i vlefshëm." + }, + "device_not_found": { + "message": "Pajisja nuk u gjet." + }, + "no_config_entry": { + "message": "Nuk u gjet asnjë hyrje konfigurimi për pajisjen." + }, + "integration_not_loaded": { + "message": "Integrimi nuk është ngarkuar për këtë pajisje." + }, + "cycle_not_found_or_no_power": { + "message": "Cikli nuk u gjet ose nuk ka të dhëna për energjinë." + }, + "trim_failed_empty_window": { + "message": "Prerja dështoi - cikli nuk u gjet, nuk ka të dhëna të energjisë ose dritarja që rezulton është bosh." + }, + "trim_invalid_range": { + "message": "trim_end_s duhet të jetë më i madh se trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/sr-Latn.json b/custom_components/ha_washdata/translations/sr-Latn.json new file mode 100644 index 0000000..608f70f --- /dev/null +++ b/custom_components/ha_washdata/translations/sr-Latn.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData podešavanje", + "description": "Konfiguriš svoju mašinu za pranje veša ili drugi uređaj.\n\nSenzor snage je obavezan.\n\n**Dalje, bićeš upitan da li želiš da napraviš prvi profil.**", + "data": { + "name": "Naziv uređaja", + "device_type": "Tip uređaja", + "power_sensor": "Senzor snage", + "min_power": "Minimalni prag snage (W)" + }, + "data_description": { + "name": "Prijateljski naziv za ovaj uređaj (npr. 'Veš mašina', 'Mašina za suđe').", + "device_type": "Koja je ovo vrsta uređaja? Pomaže u prilagođenom otkrivanju i označavanju.", + "power_sensor": "Senzorski entitet koji prijavljuje potrošnju energije u realnom vremenu (u vatima) iz tvog pametnog priključka.", + "min_power": "Očitavanja snage iznad ovog praga (u vatima) ukazuju da uređaj radi. Počni sa 2W za većinu uređaja." + } + }, + "first_profile": { + "title": "Napravi prvi profil", + "description": "**Ciklus** je kompletan rad tvog uređaja (npr. jedno pranje veša). **Profil** grupiše ove cikluse po tipu (npr. 'Pamuk', 'Brzo pranje'). Kreiranje profila sada pomaže integraciji da odmah proceni trajanje.\n\nMožeš ručno da izabereš ovaj profil u kontrolama uređaja ako nije automatski otkriven.\n\n**Ostavi 'Naziv profila' praznim da preskočiš ovaj korak.**", + "data": { + "profile_name": "Naziv profila (opciono)", + "manual_duration": "Procenjeno trajanje (minuti)" + } + } + }, + "error": { + "cannot_connect": "Povezivanje nije uspelo", + "invalid_auth": "Nevažeća autentifikacija", + "unknown": "Neočekivana greška" + }, + "abort": { + "already_configured": "Uređaj je već konfigurisan" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Pregled povratnih informacija o učenju", + "settings": "Podešavanja", + "notifications": "Obaveštenja", + "manage_cycles": "Upravljaj ciklusima", + "manage_profiles": "Upravljaj profilima", + "manage_phase_catalog": "Upravljaj katalogom faza", + "record_cycle": "Snimi ciklus (ručno)", + "diagnostics": "Dijagnostika i održavanje" + } + }, + "apply_suggestions": { + "title": "Kopiraj predložene vrednosti", + "description": "Ovo će kopirati trenutne predložene vrednosti u tvoja sačuvana podešavanja. Ovo je jednokratna radnja i ne omogućava automatsko prepisivanje.\n\nPredložene vrednosti za kopiranje:\n{suggested}", + "data": { + "confirm": "Potvrdi radnju kopiranja" + } + }, + "apply_suggestions_confirm": { + "title": "Pregljedaj predložene izmene", + "description": "WashData je pronašao **{pending_count}** predložene vrednosti za primenu.\n\nPromene:\n{changes}\n\n✅ Označite polje ispod i kliknite na **Pošalji** da biste odmah primenili i sačuvali ove promene.\n❌ Ostavite to neoznačeno i kliknite na **Pošalji** da biste otkazali i vratili se.", + "data": { + "confirm_apply_suggestions": "Primenite i sačuvajte ove promene" + } + }, + "settings": { + "title": "Podešavanja", + "description": "{deprecation_warning}Podesi pragove detekcije, učenje i napredno ponašanje. Podrazumevane vrednosti dobro funkcionišu za većinu podešavanja.\n\nTrenutno dostupnih predloženih podešavanja: {suggestions_count}.\nAko je ovo iznad 0, otvori Napredna podešavanja i koristi Primeni predložene vrednosti da pregledaš preporuke.", + "data": { + "apply_suggestions": "Primeni predložene vrednosti", + "device_type": "Tip uređaja", + "power_sensor": "Entitet senzora snage", + "min_power": "Minimalna snaga (W)", + "off_delay": "Kašnjenje završetka ciklusa (s)", + "notify_actions": "Radnje obaveštenja", + "notify_people": "Odloži isporuku dok ovi ljudi nisu kod kuće", + "notify_only_when_home": "Odloži obaveštenja dok izabrana osoba nije kod kuće", + "notify_fire_events": "Aktiviraj događaje automatizacije", + "notify_start_services": "Početak ciklusa - Ciljevi obaveštenja", + "notify_finish_services": "Završetak ciklusa - Ciljevi obaveštenja", + "notify_live_services": "Napredak uživo - Ciljevi obaveštenja", + "notify_before_end_minutes": "Obaveštenje pre završetka (minuta pre kraja)", + "notify_title": "Naslov obaveštenja", + "notify_icon": "Ikona obaveštenja", + "notify_start_message": "Format poruke pri pokretanju", + "notify_finish_message": "Format poruke pri završetku", + "notify_pre_complete_message": "Format poruke pre završetka", + "show_advanced": "Uredi napredna podešavanja (sledeći korak)" + }, + "data_description": { + "apply_suggestions": "Označi ovo polje da kopiraš vrednosti koje je predložio algoritam učenja u obrazac. Pregljedaj pre čuvanja.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Izaberi tip uređaja (Veš mašina, Sušilica, Mašina za suđe, Aparat za kafu, EV). Ova oznaka se čuva uz svaki ciklus radi bolje organizacije.", + "power_sensor": "Senzorski entitet koji prijavljuje snagu u realnom vremenu (u vatima). Ovo možeš promeniti u bilo kom trenutku bez gubitka istorijskih podataka - korisno ako zameniš pametni utikač.", + "min_power": "Očitavanja snage iznad ovog praga (u vatima) ukazuju da uređaj radi. Počni sa 2W za većinu uređaja.", + "off_delay": "U sekundama, izglađena snaga mora ostati ispod praga zaustavljanja da bi se označio završetak. Podrazumevano: 120s.", + "notify_actions": "Opciona sekvenca akcija Home Assistanta za pokretanje za obaveštenja (skripte, obaveštenja, TTS, itd.). Ako je podešeno, akcije se pokreću pre rezervnog servisa obaveštenja.", + "notify_people": "Entiteti osoba koji se koriste za praćenje prisustva. Kada je omogućeno, isporuka obaveštenja se odlaže dok bar jedna izabrana osoba nije kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odloži obaveštenja dok bar jedna izabrana osoba nije kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktiviraj Home Assistant događaje za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_end) da pokreneš automatizacije.", + "notify_start_services": "Jedna ili više službi za obaveštavanje da obaveste kada ciklus počne. Ostavite prazno da biste preskočili obaveštenja o početku.", + "notify_finish_services": "Jedna ili više službi za obaveštavanje da obaveste kada se ciklus završi ili se bliži završetku. Ostavite prazno da biste preskočili obaveštenja o završetku.", + "notify_live_services": "Jedna ili više usluga za obaveštavanje da primaju ažuriranja o napretku uživo dok ciklus traje (samo prateća aplikacija za mobilne uređaje). Ostavite prazno da biste preskočili ažuriranja uživo.", + "notify_before_end_minutes": "Nekoliko minuta pre predviđenog kraja ciklusa za pokretanje obaveštenja. Postavi na 0 da onemogućiš. Podrazumevano: 0.", + "notify_title": "Prilagođeni naslov za obaveštenja. Podržava {device} čuvar mesta.", + "notify_icon": "Ikona za korišćenje u obaveštenjima (npr. mdi:washing-machine). Ostavi praznim da pošalješ bez ikone.", + "notify_start_message": "Poruka koja se šalje kada ciklus počne. Podržava {device} čuvar mesta.", + "notify_finish_message": "Poruka koja se šalje kada se ciklus završi. Podržava čuvare mesta {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Tekst ponavljajućih ažuriranja napretka uživo dok ciklus traje. Podržava čuvare mesta {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Obaveštenja", + "description": "Konfiguriši kanale obaveštenja, šablone i opciona ažuriranja napretka na mobilnim uređajima uživo.", + "data": { + "notify_actions": "Radnje obaveštenja", + "notify_people": "Odloži isporuku dok ovi ljudi nisu kod kuće", + "notify_only_when_home": "Odloži obaveštenja dok izabrana osoba nije kod kuće", + "notify_fire_events": "Aktiviraj događaje automatizacije", + "notify_start_services": "Početak ciklusa - Ciljevi obaveštenja", + "notify_finish_services": "Završetak ciklusa - Ciljevi obaveštenja", + "notify_live_services": "Napredak uživo - Ciljevi obaveštenja", + "notify_before_end_minutes": "Obaveštenje pre završetka (minuta pre kraja)", + "notify_live_interval_seconds": "Interval ažuriranja uživo (sekunde)", + "notify_live_overrun_percent": "Dozvola prekoračenja ažuriranja uživo (%)", + "notify_live_chronometer": "Tajmer za odbrojavanje hronometra", + "notify_title": "Naslov obaveštenja", + "notify_icon": "Ikona obaveštenja", + "notify_start_message": "Format poruke pri pokretanju", + "notify_finish_message": "Format poruke pri završetku", + "notify_pre_complete_message": "Format poruke pre završetka", + "energy_price_entity": "Cena energije - entitet (opciono)", + "energy_price_static": "Cena energije - statička vrednost (opciono)", + "go_back": "Vrati se bez čuvanja", + "notify_reminder_message": "Format poruke podsetnika", + "notify_timeout_seconds": "Automatsko odbacivanje posle (sekunde)", + "notify_channel": "Kanal obaveštenja (status/uživo/podsetnik)", + "notify_finish_channel": "Završen kanal za obaveštenja" + }, + "data_description": { + "go_back": "Označi ovo i klikni na Pošalji da odbaciš sve gore navedene izmene i vratiš se u glavni meni.", + "notify_actions": "Opciona sekvenca akcija Home Assistanta za pokretanje za obaveštenja (skripte, obaveštenja, TTS, itd.). Ako je podešeno, akcije se pokreću pre rezervnog servisa obaveštenja.", + "notify_people": "Entiteti osoba koji se koriste za praćenje prisustva. Kada je omogućeno, isporuka obaveštenja se odlaže dok bar jedna izabrana osoba nije kod kuće.", + "notify_only_when_home": "Ako je omogućeno, odloži obaveštenja dok bar jedna izabrana osoba nije kod kuće.", + "notify_fire_events": "Ako je omogućeno, aktiviraj Home Assistant događaje za početak i kraj ciklusa (ha_washdata_cycle_started i ha_washdata_cycle_end) da pokreneš automatizacije.", + "notify_start_services": "Jedna ili više usluga za obaveštavanje koje će vas obavestiti kada ciklus počne (npr. notifi.mobile_app_mi_phone). Ostavite prazno da biste preskočili obaveštenja o početku.", + "notify_finish_services": "Jedna ili više službi za obaveštavanje da obaveste kada se ciklus završi ili se bliži završetku. Ostavite prazno da biste preskočili obaveštenja o završetku.", + "notify_live_services": "Jedna ili više servisa za obaveštavanje da bi dobijali ažuriranja o napretku uživo dok ciklus traje (samo prateća aplikacija za mobilne uređaje). Ostavite prazno da biste preskočili ažuriranja uživo.", + "notify_before_end_minutes": "Nekoliko minuta pre predviđenog kraja ciklusa za pokretanje obaveštenja. Postavi na 0 da onemogućiš. Podrazumevano: 0.", + "notify_live_interval_seconds": "Koliko često se ažuriranja napretka šalju tokom rada. Niže vrednosti bolje reaguju, ali troše više obaveštenja. Primenjuje se minimum od 30 sekundi - vrednosti ispod 30s se automatski zaokružuju na 30s.", + "notify_live_overrun_percent": "Sigurnosna granica iznad procenjenog broja ažuriranja za duge cikluse/cikluse prekoračenja (na primer, 20% dozvoljava 1,2× procenjenih ažuriranja).", + "notify_live_chronometer": "Kada je omogućeno, svako ažuriranje uživo uključuje odbrojavanje hronometra do procenjenog vremena završetka. Tajmer obaveštenja automatski se smanjuje na uređaju između ažuriranja (samo prateća aplikacija za Android).", + "notify_title": "Prilagođeni naslov za obaveštenja. Podržava {device} čuvar mesta.", + "notify_icon": "Ikona za korišćenje u obaveštenjima (npr. mdi:washing-machine). Ostavi praznim da pošalješ bez ikone.", + "notify_start_message": "Poruka koja se šalje kada ciklus počne. Podržava {device} čuvar mesta.", + "notify_finish_message": "Poruka koja se šalje kada se ciklus završi. Podržava čuvare mesta {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Izaberite numerički entitet koji drži trenutnu cenu električne energije (npr. senzor. cena_električne energije, broj_unosa. stopa_energije). Kada je podešeno, omogućava čuvar mesta {cost}. Ima prednost nad statičkom vrednošću ako su obe konfigurisane.", + "energy_price_static": "Fiksna cena električne energije po kVh. Kada je podešeno, omogućava čuvar mesta {cost}. Ignoriše se ako je entitet konfigurisan gore. Dodajte simbol valute u šablon poruke, npr. {cost} €.", + "notify_reminder_message": "Tekst jednokratnog podsetnika poslao je konfigurisani broj minuta pre procenjenog kraja. Za razliku od ponavljajućih ažuriranja uživo iznad. Podržava čuvare mesta {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Automatski odbaci obaveštenja posle ovoliko sekundi (prosleđuje se kao „vremensko ograničenje“ prateće aplikacije). Postavite na 0 da biste ih zadržali dok ih ne odbacite. Podrazumevano: 0.", + "notify_channel": "Android kanal za obaveštenja prateće aplikacije za status, napredak uživo i obaveštenja o podsetnicima. Zvuk i važnost kanala se konfigurišu u pratećoj aplikaciji kada se prvi put koristi naziv kanala - WashData samo postavlja ime kanala. Ostavite prazno za podrazumevanu aplikaciju.", + "notify_finish_channel": "Odvojite Android kanal za završeno upozorenje (koji se takođe koristi za podsetnik i zagovor za pranje veša) kako bi mogao da ima svoj zvuk. Ostavite prazno da ponovo koristite kanal iznad.", + "notify_pre_complete_message": "Tekst ponavljajućih ažuriranja napretka uživo dok ciklus traje. Podržava čuvare mesta {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Napredna podešavanja", + "description": "Podesi pragove detekcije, učenje i napredno ponašanje. Podrazumevane vrednosti dobro funkcionišu za većinu podešavanja.", + "sections": { + "suggestions_section": { + "name": "Predložena podešavanja", + "description": "Dostupno je {suggestions_count} naučenih predloga. Označi 'Primeni predložene vrednosti' da pre čuvanja pregledaš predložene izmene.", + "data": { + "apply_suggestions": "Primeni predložene vrednosti" + }, + "data_description": { + "apply_suggestions": "Označi ovo polje da otvoriš korak pregleda za predložene vrednosti iz algoritma učenja. Ništa se ne čuva automatski.\n\nPredloženo (iz učenja):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detekcija i pragovi snage", + "description": "Određuje kada se uređaj smatra UKLJUČENIM ili ISKLJUČENIM, energetske pragove i vreme prelaza između stanja.", + "data": { + "start_duration_threshold": "Trajanje odbijanja pokretanja (s)", + "start_energy_threshold": "Energetska kapija pokretanja (Wh)", + "completion_min_seconds": "Minimalno vreme završetka (sekunde)", + "start_threshold_w": "Početni prag (W)", + "stop_threshold_w": "Prag zaustavljanja (W)", + "end_energy_threshold": "Energetska kapija završetka (Wh)", + "running_dead_zone": "Mrtva zona (sekunde)", + "end_repeat_count": "Broj ponavljanja završetka", + "min_off_gap": "Minimalni razmak između ciklusa (s)", + "sampling_interval": "Interval uzorkovanja (s)" + }, + "data_description": { + "start_duration_threshold": "Zanemari kratke skokove snage kraće od ovog trajanja (sekunde). Filtrira skokove pri pokretanju (npr. 60W za 2s) pre nego što počne pravi ciklus. Podrazumevano: 5s.", + "start_energy_threshold": "Ciklus mora da akumulira najmanje ovoliko energije (Wh) tokom START faze da bi bio potvrđen. Podrazumevano: 0,005 Wh.", + "completion_min_seconds": "Ciklusi kraći od ovoga biće označeni kao 'prekinuti' čak i ako se završe prirodno. Podrazumevano: 600s.", + "start_threshold_w": "Snaga mora porasti IZNAD ovog praga da bi se pokrenuo ciklus. Postavi više od snage u stanju mirovanja. Podrazumevano: min_power + 1W.", + "stop_threshold_w": "Snaga mora pasti ISPOD ovog praga da bi se ciklus ZAVRŠIO. Postavi malo iznad nule da izbegtneš lažne završetke zbog buke. Podrazumevano: 0,6 × min_power.", + "end_energy_threshold": "Ciklus se završava samo ako je energija u poslednjem prozoru kašnjenja isključenja ispod ove vrednosti (Wh). Sprečava lažne završetke ako snaga fluktuira blizu nule. Podrazumevano: 0,05 Wh.", + "running_dead_zone": "Nakon što ciklus počne, zanemari padove snage ovoliko sekundi da spreči lažno otkrivanje kraja tokom početne faze okretanja. Postavi na 0 da onemogućiš. Podrazumevano: 0s.", + "end_repeat_count": "Koliko puta krajnji uslov (snaga ispod praga za off_delay) mora biti ispunjen uzastopno pre završetka ciklusa. Više vrednosti sprečavaju lažne završetke tokom pauza. Podrazumevano: 1.", + "min_off_gap": "Ako ciklus počne u roku od toliko sekundi od završetka prethodnog, oni će biti SPOJENI u jedan ciklus.", + "sampling_interval": "Minimalni interval uzorkovanja u sekundama. Ažuriranja brža od ove stope će biti zanemarena. Podrazumevano: 30s (2s za mašine za pranje veša, mašine za sušenje veša i mašine za pranje sudova)." + } + }, + "matching_section": { + "name": "Usklađivanje profila i učenje", + "description": "Određuje koliko agresivno WashData poredi aktivne cikluse sa naučenim profilima i kada treba tražiti povratne informacije.", + "data": { + "profile_match_min_duration_ratio": "Minimalni odnos trajanja podudaranja profila (0,1–1,0)", + "profile_match_interval": "Interval podudaranja profila (sekunde)", + "profile_match_threshold": "Prag podudaranja profila", + "profile_unmatch_threshold": "Prag nepodudaranja profila", + "auto_label_confidence": "Pouzdanost automatske oznake (0–1)", + "learning_confidence": "Pouzdanost zahteva za povratne informacije (0–1)", + "suppress_feedback_notifications": "Poništi obaveštenja o povratnim informacijama", + "duration_tolerance": "Tolerancija trajanja (0–0,5)", + "smoothing_window": "Prozor za izglađivanje (uzorci)", + "profile_duration_tolerance": "Tolerancija trajanja podudaranja profila (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minimalni odnos trajanja za podudaranje profila. Tekući ciklus mora biti najmanje ovaj deo trajanja profila da bi se podudario. Niže = ranije podudaranje. Podrazumevano: 0,50 (50%).", + "profile_match_interval": "Koliko često (u sekundama) pokušavati podudaranje profila tokom ciklusa. Niže vrednosti omogućavaju brže otkrivanje, ali koriste više CPU-a. Podrazumevano: 300s (5 minuta).", + "profile_match_threshold": "Minimalni rezultat sličnosti DTW (0,0–1,0) potreban da bi se profil smatrao podudarnim. Više = strože podudaranje, manje lažnih pozitivnih rezultata. Podrazumevano: 0,4.", + "profile_unmatch_threshold": "DTW rezultat sličnosti (0,0–1,0) ispod kojeg se profil koji se prethodno podudario odbacuje. Trebalo bi da bude niže od praga podudaranja da spreči treperenje. Podrazumevano: 0,35.", + "auto_label_confidence": "Automatski označi cikluse na ili iznad ove pouzdanosti po završetku (0,0–1,0).", + "learning_confidence": "Minimalna pouzdanost za zahtev za verifikaciju korisnika putem događaja (0,0–1,0).", + "suppress_feedback_notifications": "Kada je omogućen, WashData će i dalje interno pratiti i zahtevati povratne informacije, ali neće prikazivati stalna obaveštenja koja od vas traže da potvrdite cikluse. Korisno kada se ciklusi uvek ispravno detektuju i ako vam uputstva ometaju.", + "duration_tolerance": "Tolerancija za procene preostalog vremena tokom rada (0,0–0,5 odgovara 0–50%).", + "smoothing_window": "Broj nedavnih očitavanja snage korišćenih za izravnavanje pokretnog proseka.", + "profile_duration_tolerance": "Tolerancija koja se koristi za podudaranje profila za ukupnu varijansu trajanja (0,0–0,5). Podrazumevano ponašanje: ±25%." + } + }, + "timing_section": { + "name": "Vreme, održavanje i otklanjanje grešaka", + "description": "Watchdog, reset napretka, automatsko održavanje i izlaganje entiteta za otklanjanje grešaka.", + "data": { + "watchdog_interval": "Interval nadgledanja (sekunde)", + "no_update_active_timeout": "Vremensko ograničenje bez ažuriranja (s)", + "progress_reset_delay": "Odlaganje resetovanja napretka (sekunde)", + "auto_maintenance": "Omogući automatsko održavanje", + "expose_debug_entities": "Prikaži entitete za otklanjanje grešaka", + "save_debug_traces": "Sačuvaj tragove za otklanjanje grešaka" + }, + "data_description": { + "watchdog_interval": "Sekunde između provera nadgledanja tokom rada. Podrazumevano: 5s. UPOZORENJE: Osiguraj da je ovo VIŠE od intervala ažuriranja tvog senzora da izbegtneš lažna zaustavljanja (npr. ako se senzor ažurira svakih 60 sekundi, koristi 65s+).", + "no_update_active_timeout": "Za senzore koji objavljuju pri promeni: prinudno završi AKTIVNI ciklus samo ako nijedno ažuriranje ne stigne ovoliko sekundi. Završetak sa malom potrošnjom i dalje koristi kašnjenje isključenja.", + "progress_reset_delay": "Nakon što se ciklus završi (100%), sačekaj ovoliko sekundi mirovanja pre nego što resetuješ napredak na 0%. Podrazumevano: 300s.", + "auto_maintenance": "Omogući automatsko održavanje (popravka uzoraka, spajanje fragmenata).", + "expose_debug_entities": "Prikaži napredne senzore (pouzdanost, faza, višeznačnost) za otklanjanje grešaka.", + "save_debug_traces": "Čuvaj detaljne podatke o rangiranju i praćenju napajanja u istoriji (povećava korišćenje prostora za skladištenje)." + } + }, + "anti_wrinkle_section": { + "name": "Zaštita od gužvanja (sušilice)", + "description": "Zanemari okretaje bubnja posle završetka ciklusa da sprečiš fantomske cikluse. Samo sušilica / mašina za pranje i sušenje.", + "data": { + "anti_wrinkle_enabled": "Zaštita od gužvanja (samo sušilica)", + "anti_wrinkle_max_power": "Maksimalna snaga zaštite od gužvanja (W)", + "anti_wrinkle_max_duration": "Maksimalno trajanje zaštite od gužvanja (s)", + "anti_wrinkle_exit_power": "Izlazna snaga zaštite od gužvanja (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Zanemari kratke rotacije bubnja male snage nakon završetka ciklusa da spreči fantomske cikluse (samo sušilica/kombinovana mašina).", + "anti_wrinkle_max_power": "Rafali na ili ispod ove snage tretiraju se kao rotacije zaštite od gužvanja umesto kao novi ciklusi. Primenjuje se samo kada je zaštita od gužvanja omogućena.", + "anti_wrinkle_max_duration": "Maksimalna dužina rafala za tretiranje kao rotacija zaštite od gužvanja. Primenjuje se samo kada je zaštita od gužvanja omogućena.", + "anti_wrinkle_exit_power": "Ako snaga padne ispod ovog nivoa, odmah izađi iz zaštite od gužvanja (tačno isključenje). Primenjuje se samo kada je zaštita od gužvanja omogućena." + } + }, + "delay_start_section": { + "name": "Detekcija odloženog starta", + "description": "Trajan standby sa malom potrošnjom tretiraj kao stanje 'Čekanje na početak' dok ciklus stvarno ne počne.", + "data": { + "delay_start_detect_enabled": "Omogući otkrivanje odloženog starta", + "delay_confirm_seconds": "Odloženi start: vreme potvrde standby režima (s)", + "delay_timeout_hours": "Odložen početak: maksimalno vreme čekanja (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Kada je omogućeno, kratak skok odvoda male snage praćen stalnim napajanjem u stanju pripravnosti detektuje se kao odloženi početak. Senzor stanja pokazuje 'Vaiting to Start' tokom kašnjenja. Ne prati se vreme programa ili napredak sve dok ciklus zapravo ne počne. Zahteva da se start_threshold_v postavi iznad snage odvodnog skoka.", + "delay_confirm_seconds": "Snaga mora ostati između praga zaustavljanja (W) i praga pokretanja (W) ovoliko sekundi pre nego što WashData pređe u stanje 'Čekanje na početak'. Time se filtriraju kratki vrhovi tokom kretanja kroz meni. Podrazumevano: 60 s.", + "delay_timeout_hours": "Bezbednosno isteklo: ako je mašina i dalje u „Čekanju na pokretanje“ posle ovoliko sati, WashData se vraća na Off. Podrazumevano: 8 h." + } + }, + "external_triggers_section": { + "name": "Spoljni okidači, vrata i pauza", + "description": "Opcioni spoljni senzor završetka ciklusa, senzor vrata za detekciju pauze / pražnjenja i prekidač za gašenje napajanja tokom pauze.", + "data": { + "external_end_trigger_enabled": "Omogući spoljni okidač za završetak ciklusa", + "external_end_trigger": "Spoljni senzor kraja ciklusa", + "external_end_trigger_inverted": "Invertovana logika okidača (okidač je isključen)", + "door_sensor_entity": "Door Sensor", + "pause_cuts_power": "Isključite napajanje prilikom pauze", + "switch_entity": "Prebacite entitet (za pauziranje isključenja struje)", + "notify_unload_delay_minutes": "Kašnjenje obaveštenja o čekanju veša (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Omogući praćenje spoljnog binarnog senzora da pokrene završetak ciklusa.", + "external_end_trigger": "Izaberi entitet binarnog senzora. Kada se ovaj senzor aktivira, trenutni ciklus će se završiti sa statusom 'završeno'.", + "external_end_trigger_inverted": "Podrazumevano, okidač se aktivira kada se senzor uključi. Označi ovo polje da se aktivira kada se senzor isključi.", + "door_sensor_entity": "Opcioni binarni senzor za vrata mašine (uključeno = otvoreno, isključeno = zatvoreno). Kada se vrata otvore tokom aktivnog ciklusa, WashData će potvrditi da je ciklus namerno pauziran. Nakon završetka ciklusa, ako su vrata i dalje zatvorena, stanje se menja u 'Clean' dok se vrata ne otvore. Napomena: zatvaranje vrata NE nastavlja automatski pauzirani ciklus - nastavak se mora pokrenuti ručno preko dugmeta Nastavi ciklus ili usluge.", + "pause_cuts_power": "Kada pauzirate preko dugmeta Pause Cicle ili usluge, takođe isključite konfigurisani entitet prekidača. Stupa na snagu samo ako je entitet prekidača konfigurisan za ovaj uređaj.", + "switch_entity": "Entitet prekidača za prebacivanje kada pauzirate ili nastavljate. Obavezno kada je omogućeno „Prekini napajanje pri pauziranju“.", + "notify_unload_delay_minutes": "Pošaljite obaveštenje putem kanala obaveštenja o završetku nakon što se ciklus završi i vrata nisu bila otvorena ovoliko minuta (zahteva senzor vrata). Postavite na 0 da biste onemogućili. Podrazumevano: 60 min." + } + }, + "pump_section": { + "name": "Nadzor pumpe", + "description": "Samo za uređaje tipa pumpa. Pokreće događaj ako ciklus pumpe traje duže od ovog vremena.", + "data": { + "pump_stuck_duration": "Prag (s) upozorenja zastoja pumpe (samo pumpa)" + }, + "data_description": { + "pump_stuck_duration": "Ako ciklus pumpe i dalje radi nakon ovoliko sekundi, događaj ha_vashdata_pump_stuck se pokreće jednom. Koristite ovo za otkrivanje zaglavljenog motora (tipični rad pumpe je ispod 60 s). Podrazumevano: 1800 s (30 min). Samo tip uređaja za pumpu." + } + }, + "device_link_section": { + "name": "Device Link", + "description": "Opciono povežite ovaj WashData uređaj sa postojećim Home Assistant uređajem (na primer, pametnim utikačem ili samim uređajem). WashData uređaj se tada prikazuje kao „Povezan preko“ tog uređaja. Ostavite prazno da biste zadržali WashData kao samostalan uređaj.", + "data": { + "linked_device": "Povezani uređaj" + }, + "data_description": { + "linked_device": "Izaberite uređaj sa kojim ćete povezati WashData. Ovo dodaje referencu „Povezano putem“ u registar uređaja; WashData čuva sopstvenu karticu uređaja i entitete. Obrišite izbor da biste uklonili vezu." + } + } + } + }, + "diagnostics": { + "title": "Dijagnostika i održavanje", + "description": "Pokreni akcije održavanja kao što je spajanje fragmentiranih ciklusa ili migracija sačuvanih podataka.\n\n**Korišćenje prostora za skladištenje**\n\n- Veličina datoteke: {file_size_kb} KB\n- Ciklusi: {cycle_count}\n- Profili: {profile_count}\n- Tragovi za otklanjanje grešaka: {debug_count}", + "menu_options": { + "reprocess_history": "Održavanje: Ponovo obradi i optimizuj podatke", + "clear_debug_data": "Obriši podatke za otklanjanje grešaka (oslobodi prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovrativo)", + "export_import": "Izvezi/uvezi JSON sa podešavanjima (kopiraj/nalepi)", + "menu_back": "← Nazad" + } + }, + "clear_debug_data": { + "title": "Obriši podatke za otklanjanje grešaka", + "description": "Da li si siguran da želiš da izbrišeš sve sačuvane tragove za otklanjanje grešaka? Ovo će osloboditi prostor, ali ukloniti detaljne informacije o rangiranju iz prošlih ciklusa." + }, + "manage_cycles": { + "title": "Upravljaj ciklusima", + "description": "Nedavni ciklusi:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "select_cycle_to_label": "Označi određeni ciklus", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/deljenja", + "trim_cycle_select": "Iseci podatke ciklusa", + "menu_back": "← Nazad" + } + }, + "manage_cycles_empty": { + "title": "Upravljaj ciklusima", + "description": "Još uvek nema zabeleženih ciklusa.", + "menu_options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "menu_back": "← Nazad" + } + }, + "manage_profiles": { + "title": "Upravljaj profilima", + "description": "Rezime profila:\n{profile_summary}", + "menu_options": { + "create_profile": "Napravi novi profil", + "edit_profile": "Uredi/Preimenuj profil", + "delete_profile_select": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Očisti istoriju - grafikon i brisanje", + "assign_profile_phases_select": "Dodeli opsege faza", + "menu_back": "← Nazad" + } + }, + "manage_profiles_empty": { + "title": "Upravljaj profilima", + "description": "Još nema kreiranih profila.", + "menu_options": { + "create_profile": "Napravi novi profil", + "menu_back": "← Nazad" + } + }, + "manage_phase_catalog": { + "title": "Upravljaj katalogom faza", + "description": "Katalog faza (podrazumevano za ovaj uređaj + prilagođene faze):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Napravi novu fazu", + "phase_catalog_edit_select": "Uredi fazu", + "phase_catalog_delete": "Izbriši fazu", + "menu_back": "← Nazad" + } + }, + "phase_catalog_create": { + "title": "Napravi prilagođenu fazu", + "description": "Napravi prilagođeni naziv faze i opis i izaberi na koji tip uređaja se primenjuje.", + "data": { + "target_device_type": "Ciljni tip uređaja", + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_edit_select": { + "title": "Uredi prilagođenu fazu", + "description": "Izaberi prilagođenu fazu za uređivanje.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "phase_catalog_edit": { + "title": "Uredi prilagođenu fazu", + "description": "Faza izmene: {phase_name}", + "data": { + "phase_name": "Naziv faze", + "phase_description": "Opis faze" + } + }, + "phase_catalog_delete": { + "title": "Izbriši prilagođenu fazu", + "description": "Izaberi prilagođenu fazu za brisanje. Svi dodeljeni opsezi koji koriste ovu fazu biće uklonjeni.", + "data": { + "phase_name": "Prilagođena faza" + } + }, + "assign_profile_phases": { + "title": "Dodeli opsege faza", + "description": "Pregled faze za profil: **{profile_name}**\n\n{timeline_svg}\n\n**Trenutni opsezi:**\n{current_ranges}\n\nKoristi akcije u nastavku da dodaš, urediš, izbrišeš ili sačuvaš opsege.", + "menu_options": { + "assign_profile_phases_add": "Dodaj opseg faze", + "assign_profile_phases_edit_select": "Uredi opseg faze", + "assign_profile_phases_delete": "Izbriši opseg faze", + "phase_ranges_clear": "Obriši sve opsege", + "assign_profile_phases_auto_detect": "Automatsko otkrivanje faza", + "phase_ranges_save": "Sačuvaj i vrati", + "menu_back": "← Nazad" + } + }, + "assign_profile_phases_add": { + "title": "Dodaj opseg faze", + "description": "Dodaj jedan opseg faze trenutnom nacrtu.", + "data": { + "phase_name": "Faza", + "start_min": "Početni minut", + "end_min": "Krajnji minut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Uredi opseg faze", + "description": "Izaberi opseg za uređivanje.", + "data": { + "range_index": "Opseg faze" + } + }, + "assign_profile_phases_edit": { + "title": "Uredi opseg faze", + "description": "Ažuriraj izabrani opseg faze.", + "data": { + "phase_name": "Faza", + "start_min": "Početni minut", + "end_min": "Krajnji minut" + } + }, + "assign_profile_phases_delete": { + "title": "Izbriši opseg faze", + "description": "Izaberi opseg koji želiš da ukloniš iz nacrta.", + "data": { + "range_index": "Opseg faze" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatski otkrivene faze", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**Faze ({detected_count}) automatski otkrivene.** Izaberi akciju u nastavku.", + "data": { + "action": "Akcija" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Imenuj otkrivene faze", + "description": "Profil: **{profile_name}**\n\n**Otkrivene faze ({detected_count}):**\n{ranges_summary}\n\nDodeli naziv svakoj fazi." + }, + "assign_profile_phases_select": { + "title": "Dodeli opsege faza", + "description": "Izaberi profil za konfigurisanje opsega faza.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Statistika profila", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Napravi novi profil", + "description": "Napravi novi profil ručno ili iz prethodnog ciklusa.\n\nPrimeri naziva profila: 'Delikatno', 'Teška zaprljanost', 'Brzo pranje'", + "data": { + "profile_name": "Naziv profila", + "reference_cycle": "Referentni ciklus (opciono)", + "manual_duration": "Ručno trajanje (minuti)" + } + }, + "edit_profile": { + "title": "Uredi profil", + "description": "Izaberi profil za preimenovanje", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Uredi detalje profila", + "description": "Trenutni profil: {current_name}", + "data": { + "new_name": "Naziv profila", + "manual_duration": "Ručno trajanje (minuti)" + } + }, + "delete_profile_select": { + "title": "Izbriši profil", + "description": "Izaberi profil koji želiš da izbrišeš", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Potvrdi brisanje profila", + "description": "⚠ Ovo će trajno izbrisati profil: {profile_name}", + "data": { + "unlabel_cycles": "Ukloni oznaku sa ciklusa koji koriste ovaj profil" + } + }, + "auto_label_cycles": { + "title": "Automatski označi stare cikluse", + "description": "Ukupno je pronađeno {total_count} ciklusa. Profili: {profiles}", + "data": { + "confidence_threshold": "Minimalna pouzdanost (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Izaberi ciklus za označavanje", + "description": "✓ = završeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "select_cycle_to_delete": { + "title": "Izbriši ciklus", + "description": "⚠ Ovo će trajno izbrisati izabrani ciklus", + "data": { + "cycle_id": "Izaberi ciklus za brisanje" + } + }, + "label_cycle": { + "title": "Označi ciklus", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Novi naziv profila (ako se kreira)" + } + }, + "post_process": { + "title": "Obrada istorije (spajanje/deljenje)", + "description": "Očistite istoriju ciklusa spajanjem fragmentiranih ciklusa i razdvajanjem pogrešno spojenih ciklusa unutar vremenskog prozora. Unesite broj sati da pogledate unazad (koristite 999999 za sve).", + "data": { + "time_range": "Period pregleda unazad (sati)", + "gap_seconds": "Praznina spajanja/deljenja (sekunde)" + }, + "data_description": { + "time_range": "Broj sati za skeniranje fragmentiranih ciklusa za spajanje. Koristite 999999 za obradu svih istorijskih podataka.", + "gap_seconds": "Prag za odlučivanje o deljenju u odnosu na spajanje. Praznine KRAĆE od ovoga se spajaju. Praznine DUŽE od ovoga se dele. Smanji ovo da prisiliš deljenje ciklusa koji je pogrešno spojen." + } + }, + "cleanup_profile": { + "title": "Očisti istoriju - izaberi profil", + "description": "Izaberi profil za vizualizaciju. Ovo će generisati grafikon koji prikazuje sve prošle cikluse za ovaj profil kako bi se lakše identifikovali otpadni podaci.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Očisti istoriju - grafikon i brisanje", + "description": "![Grafikon]({graph_url})\n\n**Vizualizacija {profile_name}**\n\nGrafikon prikazuje pojedinačne cikluse kao obojene linije. Identifikuj otpadne podatke (linije udaljene od grupe) i poveži njihovu boju sa listom ispod da ih izbrišeš.\n\n**Izaberi cikluse za TRAJNO brisanje:**", + "data": { + "cycles_to_delete": "Ciklusi za brisanje (označi odgovarajuće boje)" + } + }, + "interactive_editor": { + "title": "Interaktivni uređivač spajanja/deljenja", + "description": "Izaberi ručnu akciju koju ćeš izvršiti u istoriji ciklusa.", + "menu_options": { + "editor_split": "Podeli ciklus (pronađi praznine)", + "editor_merge": "Spoji cikluse (poveži fragmente)", + "editor_delete": "Izbriši ciklus(e)", + "menu_back": "← Nazad" + } + }, + "editor_select": { + "title": "Izaberi cikluse", + "description": "Izaberi ciklus(e) za obradu.\n\n{info_text}", + "data": { + "selected_cycles": "Ciklusi" + } + }, + "editor_configure": { + "title": "Konfiguriši i primeni", + "description": "{preview_md}", + "data": { + "confirm_action": "Akcija", + "merged_profile": "Rezultujući profil", + "new_profile_name": "Novi naziv profila", + "confirm_commit": "Da, potvrđujem ovu akciju", + "segment_0_profile": "Segment 1 profil", + "segment_1_profile": "Segment 2 profil", + "segment_2_profile": "Segment 3 profil", + "segment_3_profile": "Segment 4 profil", + "segment_4_profile": "Segment 5 profil", + "segment_5_profile": "Segment 6 profil", + "segment_6_profile": "Segment 7 profil", + "segment_7_profile": "Segment 8 profil", + "segment_8_profile": "Segment 9 profil", + "segment_9_profile": "Segment 10 profil" + } + }, + "editor_split_params": { + "title": "Konfiguracija deljenja", + "description": "Podesi osetljivost za razdvajanje ovog ciklusa. Svaka praznina u praznom hodu duža od ovoga će izazvati deljenje.", + "data": { + "split_mode": "Metoda deljenja" + } + }, + "editor_split_auto_params": { + "title": "Konfiguracija automatske podele", + "description": "Prilagodite osetljivost za podelu ovog ciklusa. Svaki period mirovanja duži od ovog izazvaće podelu.", + "data": { + "min_gap_seconds": "Prag razmaka za podelu (sekunde)" + } + }, + "editor_split_manual_params": { + "title": "Ručne vremenske oznake podele", + "description": "{preview_md}Prozor ciklusa: **{cycle_start_wallclock} → {cycle_end_wallclock}** dana {cycle_date}.\n\nUnesite jednu ili više vremenskih oznaka podele (`HH:MM` ili `HH:MM:SS`), po jednu u svakom redu. Ciklus će biti isečen na svakoj vremenskoj oznaci — N vremenskih oznaka daje N+1 segment.", + "data": { + "split_timestamps": "Vremenske oznake podele" + } + }, + "reprocess_history": { + "title": "Održavanje: Ponovo obradi i optimizuj podatke", + "description": "Vrši dubinsko čišćenje istorijskih podataka:\n1. Skraćuje očitavanja nulte snage (tišina).\n2. Popravlja vreme/trajanje ciklusa.\n3. Preračunava sve statističke modele (koverte).\n\n**Pokreni ovo da rešiš probleme sa podacima ili primeniš nove algoritme.**" + }, + "wipe_history": { + "title": "Obriši istoriju (samo testiranje)", + "description": "⚠ Ovo će trajno izbrisati SVE uskladištene cikluse i profile za ovaj uređaj. Ovo se ne može poništiti!" + }, + "export_import": { + "title": "Izvoz/uvoz JSON", + "description": "Kopiraj/nalepi cikluse, profile I podešavanja za ovaj uređaj. Izvezi ispod ili nalepi JSON da uvzezeš.", + "data": { + "mode": "Akcija", + "json_payload": "Kompletna konfiguracija JSON" + }, + "data_description": { + "mode": "Izaberi Izvezi da kopiraš trenutne podatke i podešavanja ili Uvezi da nalepiš izvezene podatke sa drugog WashData uređaja.", + "json_payload": "Za izvoz: kopiraj ovaj JSON (uključuje cikluse, profile i sva fino podešena podešavanja). Za uvoz: nalepi JSON izvezen iz WashData." + } + }, + "record_cycle": { + "title": "Snimi ciklus", + "description": "Ručno snimi ciklus da napraviš čist profil bez smetnji.\n\nStatus: **{status}**\nTrajanje: {duration} s\nUzorci: {samples}", + "menu_options": { + "record_refresh": "Osveži status", + "record_stop": "Zaustavi snimanje (sačuvaj i obradi)", + "record_start": "Pokreni novo snimanje", + "record_process": "Obradi poslednji snimak (iseci i sačuvaj)", + "record_discard": "Odbaci poslednji snimak", + "menu_back": "← Nazad" + } + }, + "record_process": { + "title": "Obrada snimka", + "description": "![Grafikon]({graph_url})\n\n**Grafikon prikazuje neobrađeni snimak.** Plava = Zadrži, Crvena = Predloženo isecanje.\n*Grafikon je statičan i ne ažurira se promenama uzorka.*\n\nSnimanje je zaustavljeno. Isecanja su usklađena sa otkrivenom stopom uzorkovanja (~{sampling_rate} s).\n\nNeobrađeno trajanje: {duration} s\nUzorci: {samples}", + "data": { + "head_trim": "Skrati početak (sekunde)", + "tail_trim": "Skrati kraj (sekunde)", + "save_mode": "Odredište čuvanja", + "profile_name": "Naziv profila" + } + }, + "learning_feedbacks": { + "title": "Povratne informacije o učenju", + "description": "Povratne informacije na čekanju: {count}\n\n{pending_table}\n\nIzaberi zahtev za pregled ciklusa za obradu.", + "menu_options": { + "learning_feedbacks_pick": "Pregledaj povratnu informaciju na čekanju", + "learning_feedbacks_dismiss_all": "Odbaci sve povratne informacije na čekanju", + "menu_back": "← Nazad" + } + }, + "learning_feedbacks_pick": { + "title": "Izaberite povratne informacije za pregled", + "description": "Izaberite zahtev za pregled ciklusa koji želite da otvorite.", + "data": { + "selected_feedback": "Izaberite ciklus za pregled" + } + }, + "learning_feedbacks_empty": { + "title": "Povratne informacije o učenju", + "description": "Nije pronađen nijedan zahtev za povratne informacije na čekanju.", + "menu_options": { + "menu_back": "← Nazad" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Odbaci sve povratne informacije na čekanju", + "description": "Upravo ćete odbaciti **{count}** zahteva za povratne informacije na čekanju. Odbačeni ciklusi ostaju u vašoj istoriji, ali neće doprineti novom signalu učenja. Ovo se ne može opozvati.\n\n✅ Označite polje ispod i kliknite na **Pošalji** da odbacite sve.\n❌ Ostavite ga neoznačenim i kliknite na **Pošalji** da otkažete i vratite se.", + "data": { + "confirm_dismiss_all": "Potvrdi: odbaci sve zahteve za povratne informacije na čekanju" + } + }, + "resolve_feedback": { + "title": "Reši povratne informacije", + "description": "{comparison_img}{candidates_table}\n**Otkriveni profil**: {detected_profile} ({confidence_pct}%)\n**Procenjeno trajanje**: {est_duration_min} min\n**Stvarno trajanje**: {act_duration_min} min\n\n__Izaberi akciju ispod:__", + "data": { + "action": "Šta bi voleo da uradiš?", + "corrected_profile": "Tačan program (ako se ispravlja)", + "corrected_duration": "Ispravno trajanje u sekundama (ako se ispravlja)" + } + }, + "trim_cycle_select": { + "title": "Iseci ciklus - izaberi ciklus", + "description": "Izaberi ciklus za isecanje. ✓ = završeno, ⚠ = nastavljeno, ✗ = prekinuto", + "data": { + "cycle_id": "Ciklus" + } + }, + "trim_cycle": { + "title": "Iseci ciklus", + "description": "Trenutni prozor: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Akcija" + } + }, + "trim_cycle_start": { + "title": "Postavi početak isecanja", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni početak: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nIzaberite novo vreme početka koristeći birač sata u nastavku.", + "data": { + "trim_start_time": "Novo vreme početka", + "trim_start_min": "Novi početak (minuti od početka ciklusa)" + } + }, + "trim_cycle_end": { + "title": "Postavi kraj isecanja", + "description": "Prozor ciklusa: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nTrenutni kraj: **{current_wallclock}** (+{current_offset_min} min od početka ciklusa)\n\nIzaberite novo vreme završetka koristeći birač sata u nastavku.", + "data": { + "trim_end_time": "Novo vreme završetka", + "trim_end_min": "Novi kraj (minuti od početka ciklusa)" + } + } + }, + "error": { + "import_failed": "Uvoz nije uspeo. Nalepi važeći WashData izvozni JSON.", + "select_exactly_one": "Izaberi tačno jedan ciklus za ovu radnju.", + "select_at_least_one": "Izaberi najmanje jedan ciklus za ovu radnju.", + "select_at_least_two": "Izaberi najmanje dva ciklusa za ovu radnju.", + "profile_exists": "Profil sa ovim nazivom već postoji", + "rename_failed": "Preimenovanje profila nije uspelo. Profil možda ne postoji ili je novi naziv već zauzet.", + "assignment_failed": "Dodeljivanje profila ciklusu nije uspelo", + "duplicate_phase": "Faza sa ovim nazivom već postoji.", + "phase_not_found": "Izabrana faza nije pronađena.", + "invalid_phase_name": "Naziv faze ne može biti prazan.", + "phase_name_too_long": "Naziv faze je predugačak.", + "invalid_phase_range": "Opseg faze je nevažeći. Kraj mora biti veći od početka.", + "invalid_phase_timestamp": "Vremenska oznaka je nevažeća. Koristi format GGGG-MM-DD ČČ:MM ili ČČ:MM.", + "overlapping_phase_ranges": "Opsezi faza se preklapaju. Podesi opsege i pokušaj ponovo.", + "incomplete_phase_row": "Svaki red faze mora da sadrži fazu i kompletne početne/završne vrednosti za izabrani režim.", + "timestamp_mode_cycle_required": "Izaberi izvorni ciklus kada koristiš režim vremenske oznake.", + "no_phase_ranges": "Još uvek nema dostupnih opsega faza za tu akciju.", + "feedback_notification_title": "WashData: Verifikuj ciklus ({device})", + "feedback_notification_message": "**Uređaj**: {device}\n**Program**: {program} ({confidence}% pouzdanosti)\n**Vreme**: {time}\n\nWashData treba tvoju pomoć da potvrdi ovaj otkriveni ciklus.\n\nIdi na **Podešavanja > Uređaji i usluge > WashData > Konfiguriši > Povratne informacije o učenju** da potvrdiš ili ispravišovaj rezultat.", + "suggestions_ready_notification_title": "WashData: Predložena podešavanja su spremna ({device})", + "suggestions_ready_notification_message": "Senzor **Predložena podešavanja** sada prijavljuje **{count}** preporuka koje se mogu primeniti.\n\nDa ih pregledate i primenite: **Podešavanja > Uređaji i usluge > WashData > Konfiguriši > Napredna podešavanja > Primeni predložene vrednosti**.\n\nPredlozi su opcioni i prikazuju se za pregled pre nego što ih sačuvaš.", + "trim_range_invalid": "Početak mora biti pre kraja. Prilagodi tačke isecanja.", + "trim_failed": "Isecanje nije uspelo. Ciklus možda više ne postoji ili je prozor prazan.", + "invalid_split_timestamp": "Vremenska oznaka je nevažeća ili je van prozora ciklusa. Koristi HH:MM ili HH:MM:SS unutar prozora ciklusa.", + "no_split_segments_found": "Iz ovih vremenskih oznaka nije bilo moguće napraviti važeće segmente. Proveri da je svaka vremenska oznaka unutar prozora ciklusa i da segmenti traju najmanje 1 minut.", + "auto_tune_suggestion": "{device_type} {device_title} je otkrio cikluse duhova. Predložena promena min_pover: {current_min}V -> {new_min}V (ne primenjuje se automatski).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} je otkrio cikluse duhova. Predložena promena min_pover: {current_min}V -> {new_min}V (ne primenjuje se automatski)." + }, + "abort": { + "no_cycles_found": "Nisu pronađeni ciklusi", + "no_split_segments_found": "U izabranom ciklusu nisu pronađeni segmenti koji mogu da se podele.", + "cycle_not_found": "Izabrani ciklus nije mogao da se učita.", + "no_power_data": "Nema dostupnih podataka o tragovima napajanja za izabrani ciklus.", + "no_profiles_found": "Nisu pronađeni profili. Prvo napravi profil.", + "no_profiles_for_matching": "Nema dostupnih profila za podudaranje. Prvo napravi profile.", + "no_unlabeled_cycles": "Svi ciklusi su već označeni", + "no_suggestions": "Još uvek nema dostupnih predloženih vrednosti. Pokreni nekoliko ciklusa i pokušaj ponovo.", + "no_predictions": "Nema predviđanja", + "no_custom_phases": "Nisu pronađene prilagođene faze.", + "reprocess_success": "Uspešno ponovo obrađeno {count} ciklusa.", + "debug_data_cleared": "Uspešno su obrisani podaci za otklanjanje grešaka iz {count} ciklusa." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Početak ciklusa", + "cycle_finish": "Završetak ciklusa", + "cycle_live": "Napredak uživo" + } + }, + "common_text": { + "options": { + "unlabeled": "(neoznačeno)", + "create_new_profile": "Napravi novi profil", + "remove_label": "Ukloni oznaku", + "no_reference_cycle": "(Bez referentnog ciklusa)", + "all_device_types": "Svi tipovi uređaja", + "current_suffix": "(trenutni)", + "editor_select_info": "Izaberi 1 ciklus za deljenje ili 2+ ciklusa za spajanje.", + "editor_select_info_split": "Izaberi 1 ciklus za podelu.", + "editor_select_info_merge": "Izaberi 2+ ciklusa za spajanje.", + "editor_select_info_delete": "Izaberi 1+ ciklusa za brisanje.", + "no_cycles_recorded": "Još uvek nema zabeleženih ciklusa.", + "profile_summary_line": "- **{name}**: {count} ciklusa, {avg}min pros.", + "no_profiles_created": "Još nema kreiranih profila.", + "phase_preview": "Pregled faze", + "phase_preview_no_curve": "Kriva prosečnog profila još uvek nije dostupna. Pokreni/označi više ciklusa za ovaj profil.", + "average_power_curve": "Kriva prosečne snage", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} ciklusa, ~{duration}min pros.)", + "profile_option_short_fmt": "{name} ({count} ciklusa, ~{duration}min)", + "deleted_cycles_from_profile": "Izbrisano je {count} ciklusa od {profile}.", + "not_enough_data_graph": "Nema dovoljno podataka za generisanje grafikona.", + "no_profiles_found": "Nisu pronađeni profili.", + "cycle_info_fmt": "Ciklus: {start}, {duration}min, oznaka: {label}", + "top_candidates_header": "### Najbolji kandidati", + "tbl_profile": "Profil", + "tbl_confidence": "Pouzdanost", + "tbl_correlation": "Korelacija", + "tbl_duration_match": "Podudaranje trajanja", + "detected_profile": "Otkriveni profil", + "estimated_duration": "Procenjeno trajanje", + "actual_duration": "Stvarno trajanje", + "choose_action": "__Izaberi akciju ispod:__", + "tbl_count": "Broj", + "tbl_avg": "Pros.", + "tbl_min": "Min", + "tbl_max": "Maks", + "tbl_energy_avg": "Energija (pros.)", + "tbl_energy_total": "Energija (ukupno)", + "tbl_consistency": "Konz.", + "tbl_last_run": "Poslednji zagon", + "graph_legend_title": "Legenda grafikona", + "graph_legend_body": "Plava traka predstavlja posmatrani raspon minimalne i maksimalne potrošnje. Linija prikazuje prosečnu krivu snage.", + "recording_preview": "Pregled snimka", + "trim_start": "Početak isecanja", + "trim_end": "Kraj isecanja", + "split_preview_title": "Pregled deljenja", + "split_preview_found_fmt": "Pronađeno je {count} segmenata.", + "split_preview_confirm_fmt": "Klikni na Potvrdi da podeliš ovaj ciklus na {count} zasebnih ciklusa.", + "merge_preview_title": "Pregled spajanja", + "merge_preview_joining_fmt": "Spajanje {count} ciklusa. Praznine će biti popunjene očitanjima od 0W.", + "editor_delete_preview_title": "Pregled brisanja", + "editor_delete_preview_intro": "Izabrani ciklusi će biti trajno obrisani:", + "editor_delete_preview_confirm": "Klikni na Potvrdi da trajno obrišeš ove zapise ciklusa.", + "no_power_preview": "*Nema dostupnih podataka o snazi za pregled.*", + "profile_comparison": "Poređenje profila", + "actual_cycle_label": "Ovaj ciklus (stvarni)", + "storage_usage_header": "Korišćenje prostora za skladištenje", + "storage_file_size": "Veličina datoteke", + "storage_cycles": "Ciklusi", + "storage_profiles": "Profili", + "storage_debug_traces": "Tragovi za otklanjanje grešaka", + "overview_suffix": "Pregled", + "phase_preview_for_profile": "Pregled faze za profil", + "current_ranges_header": "Trenutni opsezi", + "assign_phases_help": "Koristi akcije u nastavku da dodaš, urediš, izbrišeš ili sačuvaš opsege.", + "profile_summary_header": "Rezime profila", + "recent_cycles_header": "Nedavni ciklusi", + "trim_cycle_preview_title": "Pregled isecanja ciklusa", + "trim_cycle_preview_no_data": "Nema dostupnih podataka o snazi za ovaj ciklus.", + "trim_cycle_preview_kept_suffix": "sačuvano", + "table_program": "Program", + "table_when": "Kada", + "table_length": "Trajanje", + "table_match": "Podudaranje", + "table_profile": "Profil", + "table_cycles": "Ciklusi", + "table_avg_length": "Prosečno trajanje", + "table_last_run": "Poslednji zagon", + "table_avg_energy": "Prosečna energija", + "table_detected_program": "Otkriveni program", + "table_confidence": "Pouzdanost", + "table_reported": "Prijavljeno", + "phase_builtin_suffix": "(ugrađeno)", + "phase_none_available": "Nema dostupnih faza.", + "settings_deprecation_warning": "⚠ **Zastareli tip uređaja:** {device_type} je zakazano za uklanjanje u budućem izdanju. Podudarni cevovod WashData ne daje pouzdane rezultate za ovu klasu uređaja. Vaša integracija nastavlja da funkcioniše tokom perioda zastarelosti; da biste utišali ovo upozorenje, prebacite **Tip uređaja** u nastavku na jedan od podržanih tipova (mašina za pranje veša, mašina za sušenje, kombinovana mašina za pranje i sušenje, mašina za pranje sudova, friteza za vazduh, pekač hleba ili pumpa) ili na **Ostalo (napredno)** ako vaš uređaj ne odgovara nijednom od podržanih tipova. **Ostalo (napredno)** namerno isporučuje generičke podrazumevane vrednosti koje nisu podešene za bilo koji određeni uređaj, tako da ćete morati sami da konfigurišete pragove, vremenska ograničenja i odgovarajuće parametre; sva vaša postojeća podešavanja su sačuvana kada se prebacite.", + "phase_other_device_types": "Drugi tipovi uređaja:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Podeli ciklus (pronađi praznine)", + "merge": "Spoji cikluse (poveži fragmente)", + "delete": "Izbriši ciklus(e)" + } + }, + "split_mode": { + "options": { + "auto": "Automatski otkrij periode mirovanja", + "manual": "Ručne vremenske oznake" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Održavanje: Ponovo obradi i optimizuj podatke", + "clear_debug_data": "Obriši podatke za otklanjanje grešaka (oslobodi prostor)", + "wipe_history": "Obriši SVE podatke za ovaj uređaj (nepovrativo)", + "export_import": "Izvezi/uvezi JSON sa podešavanjima (kopiraj/nalepi)" + } + }, + "export_import_mode": { + "options": { + "export": "Izvezi sve podatke", + "import": "Uvezi/objedini podatke" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatski označi stare cikluse", + "select_cycle_to_label": "Označi određeni ciklus", + "select_cycle_to_delete": "Izbriši ciklus", + "interactive_editor": "Interaktivni uređivač spajanja/deljenja", + "trim_cycle": "Iseci podatke ciklusa" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Napravi novi profil", + "edit_profile": "Uredi/Preimenuj profil", + "delete_profile": "Izbriši profil", + "profile_stats": "Statistika profila", + "cleanup_profile": "Očisti istoriju - grafikon i brisanje", + "assign_phases": "Dodeli opsege faza" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Napravi novu fazu", + "edit_custom_phase": "Uredi fazu", + "delete_custom_phase": "Izbriši fazu" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Dodaj opseg faze", + "edit_range": "Uredi opseg faze", + "delete_range": "Izbriši opseg faze", + "clear_ranges": "Obriši sve opsege", + "auto_detect_ranges": "Automatsko otkrivanje faza", + "save_ranges": "Sačuvaj i vrati" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Osveži status", + "stop_recording": "Zaustavi snimanje (sačuvaj i obradi)", + "start_recording": "Pokreni novo snimanje", + "process_recording": "Obradi poslednji snimak (iseci i sačuvaj)", + "discard_recording": "Odbaci poslednji snimak" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Napravi novi profil", + "existing_profile": "Dodaj u postojeći profil", + "discard": "Odbaci snimanje" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Potvrdi - tačna detekcija", + "correct": "Ispravi - izaberi program/trajanje", + "ignore": "Zanemari - lažno pozitivan/bučni ciklus", + "delete": "Izbriši - ukloni iz istorije" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Imenuj i primeni faze", + "cancel": "Vrati se bez promena" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Postavi početnu tačku", + "set_end": "Postavi krajnju tačku", + "reset": "Resetuj na puno trajanje", + "apply": "Primeni isecanje", + "cancel": "Otkaži" + } + }, + "device_type": { + "options": { + "washing_machine": "Mašina za pranje veša", + "dryer": "Sušač", + "washer_dryer": "Kombinovana mašina za pranje i sušenje", + "dishwasher": "Mašina za pranje sudova", + "coffee_machine": "Aparat za kafu (zastareo)", + "ev": "Električno vozilo (zastarelo)", + "air_fryer": "Air Fryer", + "heat_pump": "Toplotna pumpa (zastarelo)", + "bread_maker": "Bread Maker", + "pump": "Pumpa / Sump Pumpa", + "oven": "Rerna (zastarela)", + "other": "Drugo (napredno)" + } + } + }, + "services": { + "label_cycle": { + "name": "Označi ciklus", + "description": "Dodeli postojeći profil prošlom ciklusu ili ukloni oznaku.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za pranje veša za označavanje." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za označavanje." + }, + "profile_name": { + "name": "Naziv profila", + "description": "Naziv postojećeg profila (napravi profile u meniju Upravljaj profilima). Ostavi praznim da ukloniš oznaku." + } + } + }, + "create_profile": { + "name": "Napravi profil", + "description": "Napravi novi profil (samostalan ili zasnovan na referentnom ciklusu).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine." + }, + "profile_name": { + "name": "Naziv profila", + "description": "Naziv za novi profil (npr. 'Teška zaprljanost', 'Delikatno')." + }, + "reference_cycle_id": { + "name": "ID referentnog ciklusa", + "description": "Opcioni ID ciklusa na kome se zasniva ovaj profil." + } + } + }, + "delete_profile": { + "name": "Izbriši profil", + "description": "Izbriši profil i opciono poništi označavanje ciklusa koji ga koriste.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine." + }, + "profile_name": { + "name": "Naziv profila", + "description": "Profil za brisanje." + }, + "unlabel_cycles": { + "name": "Ukloni oznake ciklusa", + "description": "Ukloni oznaku profila sa ciklusa koji koriste ovaj profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatski označi stare cikluse", + "description": "Retroaktivno označi neoznačene cikluse pomoću podudaranja profila.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine." + }, + "confidence_threshold": { + "name": "Prag pouzdanosti", + "description": "Minimalna pouzdanost podudaranja (0,50–0,95) za primenu oznaka." + } + } + }, + "export_config": { + "name": "Izvezi konfiguraciju", + "description": "Izvezi profile, cikluse i podešavanja ovog uređaja u JSON datoteku (po uređaju).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine za izvoz." + }, + "path": { + "name": "Putanja", + "description": "Opciona apsolutna putanja datoteke za pisanje (podrazumevano je /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Uvezi konfiguraciju", + "description": "Uvezi profile, cikluse i podešavanja za ovaj uređaj iz JSON datoteke za izvoz (podešavanja mogu biti zamenjena).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine za uvoz." + }, + "path": { + "name": "Putanja", + "description": "Apsolutna putanja do JSON datoteke za izvoz za uvoz." + } + } + }, + "submit_cycle_feedback": { + "name": "Pošalji povratne informacije o ciklusu", + "description": "Potvrdi ili ispravi automatski otkriveni program nakon završenog ciklusa. Navedi ili `entry_id` (napredno) ili `device_id` (preporučeno).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "WashData uređaj (preporučuje se umesto entry_id)." + }, + "entry_id": { + "name": "ID unosa", + "description": "ID unosa konfiguracije za uređaj (alternativa device_id)." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa prikazan u obaveštenju o povratnim informacijama / evidenciji." + }, + "user_confirmed": { + "name": "Potvrdi otkriveni program", + "description": "Postavi tačno ako je otkriveni program ispravan." + }, + "corrected_profile": { + "name": "Ispravljeni profil", + "description": "Ako nije potvrđeno, navedi tačan naziv profila/programa." + }, + "corrected_duration": { + "name": "Ispravljeno trajanje (sekunde)", + "description": "Opciono ispravljeno trajanje u sekundama." + }, + "notes": { + "name": "Napomene", + "description": "Opcionalne napomene o ovom ciklusu." + } + } + }, + "record_start": { + "name": "Pokreni snimanje ciklusa", + "description": "Ručno pokreni snimanje čistog ciklusa (zaobilazi svu logiku podudaranja).", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj za snimanje veš mašine." + } + } + }, + "record_stop": { + "name": "Zaustavi snimanje ciklusa", + "description": "Zaustavi ručno snimanje.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj veš mašine za zaustavljanje snimanja." + } + } + }, + "trim_cycle": { + "name": "Iseci ciklus", + "description": "Iseci podatke o snazi iz prošlog ciklusa na određeni vremenski prozor.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "WashData uređaj." + }, + "cycle_id": { + "name": "ID ciklusa", + "description": "ID ciklusa za isecanje." + }, + "trim_start_s": { + "name": "Početak isecanja (sekunde)", + "description": "Čuvaj podatke od ovog pomaka u sekundama. Podrazumevano 0." + }, + "trim_end_s": { + "name": "Kraj isecanja (sekunde)", + "description": "Zadrži podatke do ovog pomaka u sekundama. Podrazumevano = puno trajanje." + } + } + }, + "pause_cycle": { + "name": "Pauziraj ciklus", + "description": "Pauzirajte aktivni ciklus za WashData uređaj.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za pauziranje." + } + } + }, + "resume_cycle": { + "name": "Nastavi ciklus", + "description": "Nastavite pauzirani ciklus za WashData uređaj.", + "fields": { + "device_id": { + "name": "Uređaj", + "description": "Uređaj WashData za nastavak." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "U radu" + }, + "match_ambiguity": { + "name": "Višeznačnost podudaranja" + } + }, + "sensor": { + "washer_state": { + "name": "Stanje", + "state": { + "off": "Isključeno", + "idle": "U mirovanju", + "starting": "Pokreće se", + "running": "U radu", + "paused": "Pauzirano", + "user_paused": "Pauzirano od strane korisnika", + "ending": "Završava se", + "finished": "Završeno", + "anti_wrinkle": "Zaštita od gužvanja", + "delay_wait": "Čeka se početak", + "interrupted": "Prekinuto", + "force_stopped": "Prisilno zaustavljeno", + "rinse": "Ispiranje", + "unknown": "Nepoznato", + "clean": "Čisto" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Preostalo vreme" + }, + "total_duration": { + "name": "Ukupno trajanje" + }, + "cycle_progress": { + "name": "Napredak" + }, + "current_power": { + "name": "Trenutna snaga" + }, + "elapsed_time": { + "name": "Proteklo vreme" + }, + "debug_info": { + "name": "Informacije za otklanjanje grešaka" + }, + "match_confidence": { + "name": "Pouzdanost podudaranja" + }, + "top_candidates": { + "name": "Najbolji kandidati", + "state": { + "none": "Nijedan" + } + }, + "current_phase": { + "name": "Trenutna faza" + }, + "wash_phase": { + "name": "Faza" + }, + "profile_cycle_count": { + "name": "Broj profila {profile_name}", + "unit_of_measurement": "ciklusa" + }, + "suggestions": { + "name": "Dostupna predložena podešavanja" + }, + "pump_runs_today": { + "name": "Pumpa radi (poslednja 24 h)" + }, + "cycle_count": { + "name": "Broj ciklusa" + } + }, + "select": { + "program_select": { + "name": "Program ciklusa", + "state": { + "auto_detect": "Automatska detekcija" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Prisilno završi ciklus" + }, + "pause_cycle": { + "name": "Pauziraj ciklus" + }, + "resume_cycle": { + "name": "Nastavi ciklus" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Neophodan je važeći ID uređaja." + }, + "cycle_id_required": { + "message": "Potreban je važeći id_ciklusa." + }, + "profile_name_required": { + "message": "Potrebno je važeće ime_profila." + }, + "device_not_found": { + "message": "Uređaj nije pronađen." + }, + "no_config_entry": { + "message": "Nije pronađen konfiguracioni unos za uređaj." + }, + "integration_not_loaded": { + "message": "Integracija nije učitana za ovaj uređaj." + }, + "cycle_not_found_or_no_power": { + "message": "Ciklus nije pronađen ili nema podataka o snazi." + }, + "trim_failed_empty_window": { + "message": "Isecanje nije uspelo - ciklus nije pronađen, nema podataka o napajanju ili je prozor kao rezultat prazan." + }, + "trim_invalid_range": { + "message": "trim_end_s mora biti veći od trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/sv.json b/custom_components/ha_washdata/translations/sv.json new file mode 100644 index 0000000..33ae4ed --- /dev/null +++ b/custom_components/ha_washdata/translations/sv.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Setup", + "description": "Konfigurera din tvättmaskin eller annan apparat.\n\nEffektsensor krävs.\n\n**Närnäst kommer du att bli tillfrågad om du vill skapa din första profil.**", + "data": { + "name": "Enhetens namn", + "device_type": "Enhetstyp", + "power_sensor": "Effektsensor", + "min_power": "Minsta effekttröskel (W)" + }, + "data_description": { + "name": "Ett vänligt namn för den här enheten (t.ex. \"Tvättmaskin\", \"Diskmaskin\").", + "device_type": "Vilken typ av apparat är detta? Hjälper till att skräddarsy detektering och märkning.", + "power_sensor": "Sensorenheten som rapporterar strömförbrukning i realtid (i watt) från din smarta kontakt.", + "min_power": "Effektavläsningar över detta tröskelvärde (i watt) indikerar att apparaten är igång. Börja med 2W för de flesta enheter." + } + }, + "first_profile": { + "title": "Skapa första profil", + "description": "En **Cykel** är en komplett körning av din apparat (t.ex. en massa tvätt). En **Profil** grupperar dessa cykler efter typ (t.ex. \"Bomull\", \"Snabbtvätt\"). Att skapa en profil hjälper nu integrationen att uppskatta varaktigheten omedelbart.\n\nDu kan välja den här profilen manuellt i enhetens kontroller om den inte upptäcks automatiskt.\n\n**Lämna \"Profilnamn\" tomt för att hoppa över det här steget.**", + "data": { + "profile_name": "Profilnamn (valfritt)", + "manual_duration": "Beräknad varaktighet (minuter)" + } + } + }, + "error": { + "cannot_connect": "Det gick inte att ansluta", + "invalid_auth": "Ogiltig autentisering", + "unknown": "Oväntat fel" + }, + "abort": { + "already_configured": "Enheten är redan konfigurerad" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Granska inlärningsfeedback", + "settings": "Inställningar", + "notifications": "Aviseringar", + "manage_cycles": "Hantera cykler", + "manage_profiles": "Hantera profiler", + "manage_phase_catalog": "Hantera faskatalog", + "record_cycle": "Spela in cykel (manuell)", + "diagnostics": "Diagnostik & underhåll" + } + }, + "apply_suggestions": { + "title": "Kopiera föreslagna värden", + "description": "Detta kommer att kopiera de nuvarande föreslagna värdena till dina sparade inställningar. Detta är en engångsåtgärd och möjliggör inte automatisk överskrivning.\n\nFöreslagna värden att kopiera:\n{suggested}", + "data": { + "confirm": "Bekräfta kopieringsåtgärd" + } + }, + "apply_suggestions_confirm": { + "title": "Granska föreslagna ändringar", + "description": "WashData hittades **{pending_count}** föreslagna värden att tillämpa.\n\nÄndringar:\n{changes}\n\n✅ Markera rutan nedan och klicka på **Skicka** för att tillämpa och spara dessa ändringar omedelbart.\n❌ Lämna det omarkerat och klicka på **Skicka** för att avbryta och gå tillbaka.", + "data": { + "confirm_apply_suggestions": "Tillämpa och spara dessa ändringar" + } + }, + "settings": { + "title": "Inställningar", + "description": "{deprecation_warning}Justera detektionströsklar, inlärning och avancerat beteende. Standardinställningarna fungerar bra för de flesta inställningar.\n\nTillgängliga föreslagna inställningar: {suggestions_count}.\nOm detta är över 0, öppna Avancerade inställningar och använd Tillämpa föreslagna värden för att granska rekommendationerna.", + "data": { + "apply_suggestions": "Tillämpa föreslagna värden", + "device_type": "Enhetstyp", + "power_sensor": "Effektsensorenhet", + "min_power": "Minsta effekt (W)", + "off_delay": "Cykelslutfördröjning (s)", + "notify_actions": "Aviseringsåtgärder", + "notify_people": "Fördröj leveransen tills dessa personer är hemma", + "notify_only_when_home": "Fördröj aviseringar tills den valda personen är hemma", + "notify_fire_events": "Aktivera automationshändelser", + "notify_start_services": "Cykelstart - Aviseringsmål", + "notify_finish_services": "Cykelavslutning - Notifieringsmål", + "notify_live_services": "Liveframsteg - Aviseringsmål", + "notify_before_end_minutes": "Förhandsavisering (minuter före slut)", + "notify_title": "Aviseringsrubrik", + "notify_icon": "Aviseringsikon", + "notify_start_message": "Meddelandeformat för start", + "notify_finish_message": "Meddelandeformat för slut", + "notify_pre_complete_message": "Meddelandeformat för förhandsavisering", + "show_advanced": "Redigera avancerade inställningar (nästa steg)" + }, + "data_description": { + "apply_suggestions": "Markera den här rutan för att kopiera värden som föreslås av inlärningsalgoritmen till formuläret. Granska innan du sparar.\n\nFöreslagna (från inlärning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Välj typ av apparat (tvättmaskin, torktumlare, diskmaskin, kaffemaskin, EV). Denna tagg lagras med varje cykel för bättre organisation.", + "power_sensor": "Sensorenheten rapporterar effekt i realtid (i watt). Du kan ändra detta när som helst utan att förlora historisk data – användbart om du byter ut en smart kontakt.", + "min_power": "Effektavläsningar över detta tröskelvärde (i watt) indikerar att apparaten är igång. Börja med 2W för de flesta enheter.", + "off_delay": "Sekunder måste den utjämnade effekten hålla sig under stopptröskeln för att markera slutförande. Standard: 120s.", + "notify_actions": "Valfri Home Assistant-åtgärdssekvens att köra för aviseringar (skript, avisering, TTS, etc.). Om inställt körs åtgärder innan reservaviseringstjänst.", + "notify_people": "Personenheter som används för närvarogrind. När det är aktiverat fördröjs aviseringsleveransen tills minst en vald person är hemma.", + "notify_only_when_home": "Om aktiverat, fördröja aviseringar tills minst en vald person är hemma.", + "notify_fire_events": "Om aktiverat, utlöser Home Assistant-händelser för cykelstart och -slut (ha_washdata_cycle_started och ha_washdata_cycle_ended) för att driva automatiseringar.", + "notify_start_services": "En eller flera aviseringstjänster för att varna när en cykel börjar. Lämna tomt för att hoppa över startaviseringar.", + "notify_finish_services": "En eller flera aviseringstjänster för att varna när en cykel avslutas eller närmar sig slutförd. Lämna tomt för att hoppa över slutaviseringar.", + "notify_live_services": "En eller flera aviseringstjänster för att ta emot förloppsuppdateringar i realtid medan cykeln körs (endast mobilappen). Lämna tomt för att hoppa över liveuppdateringar.", + "notify_before_end_minutes": "Minuter före det beräknade slutet av cykeln för att utlösa ett meddelande. Ställ in på 0 för att inaktivera. Standard: 0.", + "notify_title": "Anpassad rubrik för aviseringar. Stöder platshållaren {device}.", + "notify_icon": "Ikon att använda för aviseringar (t.ex. mdi:washing-machine). Lämna tomt för att skicka utan ikon.", + "notify_start_message": "Meddelande som skickas när en cykel startar. Stöder platshållaren {device}.", + "notify_finish_message": "Meddelande som skickas när en cykel är klar. Stöder platshållarna {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Text för de återkommande liveförloppsuppdateringarna medan cykeln körs. Stöder {device}, {minutes}, {program} platshållare." + } + }, + "notifications": { + "title": "Aviseringar", + "description": "Konfigurera aviseringskanaler, mallar och valfria liveuppdateringar för mobila framsteg.", + "data": { + "notify_actions": "Aviseringsåtgärder", + "notify_people": "Fördröj leveransen tills dessa personer är hemma", + "notify_only_when_home": "Fördröj aviseringar tills den valda personen är hemma", + "notify_fire_events": "Aktivera automationshändelser", + "notify_start_services": "Cykelstart - Aviseringsmål", + "notify_finish_services": "Cykelavslutning - Notifieringsmål", + "notify_live_services": "Liveframsteg - Aviseringsmål", + "notify_before_end_minutes": "Förhandsavisering (minuter före slut)", + "notify_live_interval_seconds": "Liveuppdateringsintervall (sekunder)", + "notify_live_overrun_percent": "Tillåten överskridning för liveuppdateringar (%)", + "notify_live_chronometer": "Kronometer nedräkningstimer", + "notify_title": "Aviseringsrubrik", + "notify_icon": "Aviseringsikon", + "notify_start_message": "Meddelandeformat för start", + "notify_finish_message": "Meddelandeformat för slut", + "notify_pre_complete_message": "Meddelandeformat för förhandsavisering", + "energy_price_entity": "Energipris – enhet (valfritt)", + "energy_price_static": "Energipris – statiskt värde (valfritt)", + "go_back": "Gå tillbaka utan att spara", + "notify_reminder_message": "Format för påminnelsemeddelande", + "notify_timeout_seconds": "Stäng automatiskt efter (sekunder)", + "notify_channel": "Aviseringskanal (status/live/påminnelse)", + "notify_finish_channel": "Avslutad aviseringskanal" + }, + "data_description": { + "go_back": "Markera detta och klicka på Skicka för att kasta ändringarna ovan och återgå till huvudmenyn.", + "notify_actions": "Valfri Home Assistant-åtgärdssekvens att köra för aviseringar (skript, avisering, TTS, etc.). Om inställt körs åtgärder innan reservaviseringstjänst.", + "notify_people": "Personenheter som används för närvarogrind. När det är aktiverat fördröjs aviseringsleveransen tills minst en vald person är hemma.", + "notify_only_when_home": "Om aktiverat, fördröja aviseringar tills minst en vald person är hemma.", + "notify_fire_events": "Om aktiverat, utlöser Home Assistant-händelser för cykelstart och -slut (ha_washdata_cycle_started och ha_washdata_cycle_ended) för att driva automatiseringar.", + "notify_start_services": "En eller flera aviseringstjänster för att varna när en cykel börjar (t.ex. notify.mobile_app_my_phone). Lämna tomt för att hoppa över startaviseringar.", + "notify_finish_services": "En eller flera aviseringstjänster för att varna när en cykel avslutas eller närmar sig slutförd. Lämna tomt för att hoppa över slutaviseringar.", + "notify_live_services": "En eller flera aviseringstjänster för att ta emot förloppsuppdateringar i realtid medan cykeln körs (endast mobilappen). Lämna tomt för att hoppa över liveuppdateringar.", + "notify_before_end_minutes": "Minuter före det beräknade slutet av cykeln för att utlösa ett meddelande. Ställ in på 0 för att inaktivera. Standard: 0.", + "notify_live_interval_seconds": "Hur ofta förloppsuppdateringar skickas under körning. Lägre värden är mer lyhörda men förbrukar fler aviseringar. Minst 30 sekunder tillämpas - värden under 30 s avrundas automatiskt uppåt till 30 s.", + "notify_live_overrun_percent": "Säkerhetsmarginal över det uppskattade uppdateringsantalet för långa/överskridande cykler (t.ex. 20 % tillåter 1,2x uppskattade uppdateringar).", + "notify_live_chronometer": "När den är aktiverad inkluderar varje liveuppdatering en kronometernedräkning till den beräknade sluttiden. Aviseringstimern tickar ner automatiskt på enheten mellan uppdateringar (endast medföljande Android-app).", + "notify_title": "Anpassad rubrik för aviseringar. Stöder platshållaren {device}.", + "notify_icon": "Ikon att använda för aviseringar (t.ex. mdi:washing-machine). Lämna tomt för att skicka utan ikon.", + "notify_start_message": "Meddelande som skickas när en cykel startar. Stöder platshållaren {device}.", + "notify_finish_message": "Meddelande som skickas när en cykel är klar. Stöder platshållarna {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Välj en numerisk enhet som har det aktuella elpriset (t.ex. sensor.elpris, input_number.energy_rate). När den är inställd aktiveras platshållaren {cost}. Har företräde framför det statiska värdet om båda är konfigurerade.", + "energy_price_static": "Fast elpris per kWh. När den är inställd aktiveras platshållaren {cost}. Ignoreras om en enhet är konfigurerad ovan. Lägg till din valutasymbol i meddelandemallen, t.ex. {cost} €.", + "notify_reminder_message": "Texten till engångspåminnelsen skickade det konfigurerade antalet minuter före det beräknade slutet. Till skillnad från de återkommande liveuppdateringarna ovan. Stöder {device}, {minutes}, {program} platshållare.", + "notify_timeout_seconds": "Avvisa automatiskt aviseringar efter så här många sekunder (vidarebefordras som \"timeout\") för den kompletterande appen. Ställ in på 0 för att behålla dem tills de tas bort. Standard: 0.", + "notify_channel": "Android aviseringskanal för medföljande appar för status, liveframsteg och påminnelser. Ljudet och betydelsen av en kanal konfigureras i den medföljande appen första gången kanalnamnet används - WashData ställer bara in kanalnamnet. Lämna tomt för appens standard.", + "notify_finish_channel": "Separat Android-kanal för den färdiga aviseringen (används även av påminnelsen och tjatet som väntar på tvätten) så att den kan få sitt eget ljud. Lämna tomt för att återanvända kanalen ovan.", + "notify_pre_complete_message": "Text för de återkommande liveförloppsuppdateringarna medan cykeln körs. Stöder {device}, {minutes}, {program} platshållare." + } + }, + "advanced_settings": { + "title": "Avancerade inställningar", + "description": "Justera detektionströsklar, inlärning och avancerat beteende. Standardinställningarna fungerar bra för de flesta inställningar.", + "sections": { + "suggestions_section": { + "name": "Föreslagna inställningar", + "description": "{suggestions_count} inlärda förslag tillgängliga. Kryssa i Tillämpa föreslagna värden för att granska föreslagna ändringar innan du sparar.", + "data": { + "apply_suggestions": "Tillämpa föreslagna värden" + }, + "data_description": { + "apply_suggestions": "Markera den här rutan för att öppna ett granskningssteg för föreslagna värden från inlärningsalgoritmen. Ingenting sparas automatiskt.\n\nFöreslagna (från inlärning):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Detektering och effekttrösklar", + "description": "När apparaten anses vara PÅ eller AV, energigränser och tidsstyrning för tillståndsövergångar.", + "data": { + "start_duration_threshold": "Startfördröjningslängd (s)", + "start_energy_threshold": "Energiport för start (Wh)", + "completion_min_seconds": "Minsta körtid för slutförande (sekunder)", + "start_threshold_w": "Starttröskel (W)", + "stop_threshold_w": "Stopptröskel (W)", + "end_energy_threshold": "Energiport för slut (Wh)", + "running_dead_zone": "Dödzon under körning (sekunder)", + "end_repeat_count": "Antal upprepningar för avslut", + "min_off_gap": "Minsta mellanrum mellan cykler (s)", + "sampling_interval": "Samplingsintervall (s)" + }, + "data_description": { + "start_duration_threshold": "Ignorera korta kraftspikar kortare än denna varaktighet (sekunder). Filtrerar bort startspikar (t.ex. 60W i 2s) innan den riktiga cykeln startar. Standard: 5s.", + "start_energy_threshold": "Cykeln måste ackumulera minst så mycket energi (Wh) under STARTFASEN för att bekräftas. Standard: 0,005 Wh.", + "completion_min_seconds": "Cykler kortare än detta markeras som \"avbrutna\" även om de avslutas naturligt. Standard: 600s.", + "start_threshold_w": "Effekten måste stiga ÖVER denna tröskel för att STARTA en cykel. Ställ in högre än din standbyeffekt. Standard: min_power + 1 W.", + "stop_threshold_w": "Effekten måste falla UNDER denna tröskel för att AVSLUTA en cykel. Ställ in strax över noll för att undvika brus som utlöser falska avslut. Standard: 0,6 × min_power.", + "end_energy_threshold": "Cykeln avslutas endast om energin i det sista av-fördröjningsfönstret är under detta värde (Wh). Förhindrar falska avslut om effekten fluktuerar nära noll. Standard: 0,05 Wh.", + "running_dead_zone": "Efter cykelstart, ignorera effektsänkningar i så många sekunder för att förhindra falsk slutdetektering under den inledande uppstartsfasen. Ställ in på 0 för att inaktivera. Standard: 0s.", + "end_repeat_count": "Antal gånger avslutsvillkoret (effekt under tröskelvärde i off_delay) måste uppfyllas i följd innan cykeln avslutas. Högre värden förhindrar falska avslut under pauser. Standard: 1.", + "min_off_gap": "Om en cykel startar inom så många sekunder efter att den föregående slutade, kommer de att slås samman till en enda cykel.", + "sampling_interval": "Minsta provtagningsintervall i sekunder. Uppdateringar snabbare än denna takt kommer att ignoreras. Standard: 30s (2s för tvättmaskiner, tvättmaskin-torktumlare och diskmaskiner)." + } + }, + "matching_section": { + "name": "Profilmatchning och inlärning", + "description": "Hur aggressivt WashData matchar pågående cykler mot inlärda profiler och när återkoppling ska begäras.", + "data": { + "profile_match_min_duration_ratio": "Minsta varaktighetsförhållande för profilmatchning (0,1–1,0)", + "profile_match_interval": "Profilmatchningsintervall (sekunder)", + "profile_match_threshold": "Tröskel för profilmatchning", + "profile_unmatch_threshold": "Tröskel för profilavmatchning", + "auto_label_confidence": "Konfidens för automatisk märkning (0–1)", + "learning_confidence": "Konfidens för feedbackbegäran (0–1)", + "suppress_feedback_notifications": "Undertryck feedbackmeddelanden", + "duration_tolerance": "Varaktighetstolerans (0–0,5)", + "smoothing_window": "Utjämningsfönster (prover)", + "profile_duration_tolerance": "Varaktighetstolerans för profilmatchning (0–0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Minsta varaktighetsförhållande för profilmatchning. Den pågående cykeln måste vara minst denna andel av profilens varaktighet för att matcha. Lägre = tidigare matchning. Standard: 0,50 (50%).", + "profile_match_interval": "Hur ofta (i sekunder) profilmatchning ska försökas under en cykel. Lägre värden ger snabbare identifiering men ökar CPU-användningen. Standard: 300s (5 minuter).", + "profile_match_threshold": "Lägsta DTW-likhetspoäng (0,0–1,0) som krävs för att betrakta en profil som en matchning. Högre = striktare matchning, färre falskt positiva. Standard: 0,4.", + "profile_unmatch_threshold": "DTW-likhetspoäng (0,0–1,0) under vilken en tidigare matchad profil avvisas. Bör vara lägre än matchningströskeln för att förhindra flimmer. Standard: 0,35.", + "auto_label_confidence": "Märk automatiskt cykler vid eller över denna konfidens vid slutförande (0,0–1,0).", + "learning_confidence": "Minsta konfidens för att begära användarverifiering via en händelse (0,0–1,0).", + "suppress_feedback_notifications": "När den är aktiverad kommer WashData fortfarande att spåra och begära feedback internt men kommer inte att visa beständiga meddelanden som ber dig att bekräfta cykler. Användbart när cykler alltid upptäcks korrekt och du tycker att uppmaningarna är distraherande.", + "duration_tolerance": "Tolerans för uppskattningar av återstående tid under körning (0,0–0,5 motsvarar 0–50%).", + "smoothing_window": "Antal senaste effektavläsningar som används för utjämning med glidande medelvärde.", + "profile_duration_tolerance": "Tolerans som används av profilmatchning för total varaktighetsvarians (0,0–0,5). Standardbeteende: ±25 %." + } + }, + "timing_section": { + "name": "Timing, underhåll och felsökning", + "description": "Watchdog, återställning av förlopp, automatiskt underhåll och exponering av felsökningsentiteter.", + "data": { + "watchdog_interval": "Watchdog-intervall (sekunder)", + "no_update_active_timeout": "Timeout utan uppdatering (s)", + "progress_reset_delay": "Fördröjning för förloppsåterställning (sekunder)", + "auto_maintenance": "Aktivera automatiskt underhåll", + "expose_debug_entities": "Visa felsökningsenheter", + "save_debug_traces": "Spara felsökningsspår" + }, + "data_description": { + "watchdog_interval": "Sekunder mellan watchdog-kontroller under körning. Standard: 5s. VARNING: Se till att detta är HÖGRE än sensorns uppdateringsintervall för att undvika falska stopp (t.ex. om sensorn uppdateras var 60:e sekund, använd 65s+).", + "no_update_active_timeout": "För sensorer med publicering vid ändring: avsluta endast en AKTIV cykel om inga uppdateringar inkommer under så många sekunder. Slutförande med låg effekt använder fortfarande avstängningsfördröjningen.", + "progress_reset_delay": "Efter att en cykel har slutförts (100 %), vänta så många sekunder med tomgång innan du återställer förloppet till 0 %. Standard: 300s.", + "auto_maintenance": "Aktivera automatiskt underhåll (reparera prover, slå samman fragment).", + "expose_debug_entities": "Visa avancerade sensorer (Konfidens, Fas, Tvetydighet) för felsökning.", + "save_debug_traces": "Lagra detaljerad ranknings- och effektspårdata i historiken (ökar lagringsanvändningen)." + } + }, + "anti_wrinkle_section": { + "name": "Antiskrynkelsskydd (torktumlare)", + "description": "Ignorera trumrotationer efter cykeln för att förhindra spökcykler. Endast torktumlare / kombitvätt.", + "data": { + "anti_wrinkle_enabled": "Antirynkskydd (endast torktumlare/kombinationsmaskin)", + "anti_wrinkle_max_power": "Antirynkskydd: maxeffekt (W) (endast torktumlare/kombinationsmaskin)", + "anti_wrinkle_max_duration": "Antirynkskydd: maxvaraktighet (s) (endast torktumlare/kombinationsmaskin)", + "anti_wrinkle_exit_power": "Antirynkskydd: avslutningseffekt (W) (endast torktumlare/kombinationsmaskin)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ignorera korta trumrotationer med låg effekt efter att en cykel har avslutats för att förhindra spökcykler (endast torktumlare/kombinationsmaskin).", + "anti_wrinkle_max_power": "Pulser vid eller under denna effekt behandlas som antirynkrotationer istället för nya cykler. Gäller endast när Antirynkskydd är aktiverat.", + "anti_wrinkle_max_duration": "Maximal pulslängd att behandla som antirynkrotation. Gäller endast när Antirynkskydd är aktiverat.", + "anti_wrinkle_exit_power": "Om strömmen sjunker under denna nivå, avsluta antirynkläget omedelbart (sant av). Gäller endast när Antirynkskydd är aktiverat." + } + }, + "delay_start_section": { + "name": "Detektering av fördröjd start", + "description": "Behandla ihållande standby med låg effekt som 'Väntar på start' tills cykeln faktiskt börjar.", + "data": { + "delay_start_detect_enabled": "Aktivera detektering av fördröjd start", + "delay_confirm_seconds": "Fördröjd start: bekräftelsetid för standby (s)", + "delay_timeout_hours": "Fördröjd start: Max väntetid (h)" + }, + "data_description": { + "delay_start_detect_enabled": "När den är aktiverad, upptäcks en kort tömningsspets med låg effekt följt av ihållande standby-ström som en fördröjd start. Statussensorn visar 'Väntar på att starta' under fördröjningen. Ingen programtid eller framsteg spåras förrän cykeln faktiskt börjar. Kräver att start_threshold_w ställs in över dräneringsspetseffekten.", + "delay_confirm_seconds": "Effekten måste ligga mellan Stopptröskel (W) och Starttröskel (W) under så många sekunder innan WashData går till 'Väntar på start'. Filtrerar bort korta toppar från menynavigering. Standard: 60 s.", + "delay_timeout_hours": "Säkerhetstidsgräns: om maskinen fortfarande är i 'Väntar på start' efter så många timmar, återställs WashData till Av. Standard: 8 timmar." + } + }, + "external_triggers_section": { + "name": "Externa utlösare, dörr och paus", + "description": "Valfri extern sensor för cykelslut, dörrsensor för paus-/rendetektering och strömbrytare för att bryta ström vid paus.", + "data": { + "external_end_trigger_enabled": "Aktivera extern cykelslututlösare", + "external_end_trigger": "Extern cykelslutsensor", + "external_end_trigger_inverted": "Invertera utlösarlogik (utlösning vid AV)", + "door_sensor_entity": "Dörrsensor", + "pause_cuts_power": "Bryt strömmen vid paus", + "switch_entity": "Switch Entity (för pausströmavbrott)", + "notify_unload_delay_minutes": "Fördröjning av meddelande för tvätt som väntar (min)" + }, + "data_description": { + "external_end_trigger_enabled": "Aktivera övervakning av en extern binär sensor för att utlösa cykelavslutning.", + "external_end_trigger": "Välj en binär sensorenhet. När denna sensor utlöses avslutas den aktuella cykeln med status \"slutförd\".", + "external_end_trigger_inverted": "Som standard utlöses avtryckaren när sensorn slås PÅ. Markera den här rutan för att utlösa när sensorn stängs AV istället.", + "door_sensor_entity": "Tillval binär sensor för maskindörren (på = öppen, av = stängd). När dörren öppnas under en aktiv cykel, kommer WashData att bekräfta att cykeln är avsiktlig pausad. Efter cykelns slut, om dörren fortfarande är stängd, ändras tillståndet till 'Rengör' tills dörren öppnas. Obs: att stänga dörren återupptar INTE en pausad cykel automatiskt – återuppta måste utlösas manuellt via knappen Återuppta cykel eller tjänst.", + "pause_cuts_power": "När du pausar via Pause Cycle-knappen eller tjänsten, stäng också av den konfigurerade switch-entiteten. träder endast i kraft om en switch-enhet är konfigurerad för den här enheten.", + "switch_entity": "Växlingsenheten att växla när du pausar eller återupptar. Krävs när \"Klipp av ström vid paus\" är aktiverat.", + "notify_unload_delay_minutes": "Skicka ett meddelande via aviseringskanalen för mål efter att cykeln är slut och dörren inte har öppnats på så många minuter (kräver Dörrsensor). Ställ in på 0 för att inaktivera. Standard: 60 min." + } + }, + "pump_section": { + "name": "Pumpövervakning", + "description": "Endast för enhetstypen pump. Utlöser en händelse om en pumpcykel kör längre än den här varaktigheten.", + "data": { + "pump_stuck_duration": "Varningströskel för pump fastnat (er) (endast pump)" + }, + "data_description": { + "pump_stuck_duration": "Om en pumpcykel fortfarande körs efter så här många sekunder, utlöses en ha_washdata_pump_stuck-händelse en gång. Använd denna för att upptäcka en motor som har fastnat (typisk drift av sumppumpen är under 60 s). Standard: 1800 s (30 min). Endast pumpenhetstyp." + } + }, + "device_link_section": { + "name": "Enhetslänk", + "description": "Länka eventuellt den här WashData-enheten till en befintlig Home Assistant-enhet (till exempel den smarta kontakten eller själva apparaten). WashData-enheten visas sedan som \"Ansluten via\" den enheten. Lämna tomt för att behålla WashData som en fristående enhet.", + "data": { + "linked_device": "Länkad enhet" + }, + "data_description": { + "linked_device": "Välj enheten att ansluta WashData till. Detta lägger till en \"Ansluten via\"-referens i enhetsregistret; WashData behåller sitt eget enhetskort och entiteter. Rensa markeringen för att ta bort länken." + } + } + } + }, + "diagnostics": { + "title": "Diagnostik & underhåll", + "description": "Kör underhållsåtgärder som att slå samman fragmenterade cykler eller migrera lagrad data.\n\n**Lagringsanvändning**\n\n- Filstorlek: {file_size_kb} KB\n- Cykler: {cycle_count}\n- Profiler: {profile_count}\n- Felsökningsspår: {debug_count}", + "menu_options": { + "reprocess_history": "Underhåll: Bearbeta och optimera data", + "clear_debug_data": "Rensa felsökningsdata (frigör utrymme)", + "wipe_history": "Rensa ALL data för den här enheten (oåterkallelig)", + "export_import": "Exportera/importera JSON med inställningar (kopiera/klistra in)", + "menu_back": "← Tillbaka" + } + }, + "clear_debug_data": { + "title": "Rensa felsökningsdata", + "description": "Är du säker på att du vill ta bort alla lagrade felsökningsspår? Detta frigör utrymme men tar bort detaljerad rankningsinformation från tidigare cykler." + }, + "manage_cycles": { + "title": "Hantera cykler", + "description": "Senaste cyklerna:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Automatisk märkning av gamla cykler", + "select_cycle_to_label": "Märk en specifik cykel", + "select_cycle_to_delete": "Ta bort cykel", + "interactive_editor": "Interaktiv redigerare för sammanslagning/delning", + "trim_cycle_select": "Trimma cykeldata", + "menu_back": "← Tillbaka" + } + }, + "manage_cycles_empty": { + "title": "Hantera cykler", + "description": "Inga cykler registrerade ännu.", + "menu_options": { + "auto_label_cycles": "Automatisk märkning av gamla cykler", + "menu_back": "← Tillbaka" + } + }, + "manage_profiles": { + "title": "Hantera profiler", + "description": "Profilsammanfattning:\n{profile_summary}", + "menu_options": { + "create_profile": "Skapa ny profil", + "edit_profile": "Redigera/byt namn på profil", + "delete_profile_select": "Ta bort profil", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Rensa historik – Graf och radera", + "assign_profile_phases_select": "Tilldela fasintervall", + "menu_back": "← Tillbaka" + } + }, + "manage_profiles_empty": { + "title": "Hantera profiler", + "description": "Inga profiler har skapats ännu.", + "menu_options": { + "create_profile": "Skapa ny profil", + "menu_back": "← Tillbaka" + } + }, + "manage_phase_catalog": { + "title": "Hantera faskatalog", + "description": "Faskatalog (standard för denna enhet + anpassade faser):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Skapa ny fas", + "phase_catalog_edit_select": "Redigera fas", + "phase_catalog_delete": "Ta bort fas", + "menu_back": "← Tillbaka" + } + }, + "phase_catalog_create": { + "title": "Skapa anpassad fas", + "description": "Skapa ett anpassat fasnamn och en beskrivning, och välj vilken enhetstyp det gäller.", + "data": { + "target_device_type": "Målenhetstyp", + "phase_name": "Fasnamn", + "phase_description": "Fasbeskrivning" + } + }, + "phase_catalog_edit_select": { + "title": "Redigera anpassad fas", + "description": "Välj en anpassad fas att redigera.", + "data": { + "phase_name": "Anpassad fas" + } + }, + "phase_catalog_edit": { + "title": "Redigera anpassad fas", + "description": "Redigeringsfas: {phase_name}", + "data": { + "phase_name": "Fasnamn", + "phase_description": "Fasbeskrivning" + } + }, + "phase_catalog_delete": { + "title": "Ta bort anpassad fas", + "description": "Välj en anpassad fas att ta bort. Alla tilldelade intervall som använder denna fas kommer att tas bort.", + "data": { + "phase_name": "Anpassad fas" + } + }, + "assign_profile_phases": { + "title": "Tilldela fasintervall", + "description": "Fasförhandsgranskning för profil: **{profile_name}**\n\n{timeline_svg}\n\n**Aktuella intervall:**\n{current_ranges}\n\nAnvänd åtgärderna nedan för att lägga till, redigera, ta bort eller spara intervall.", + "menu_options": { + "assign_profile_phases_add": "Lägg till fasintervall", + "assign_profile_phases_edit_select": "Redigera fasintervall", + "assign_profile_phases_delete": "Ta bort fasintervall", + "phase_ranges_clear": "Rensa alla intervall", + "assign_profile_phases_auto_detect": "Identifiera faser automatiskt", + "phase_ranges_save": "Spara och återgå", + "menu_back": "← Tillbaka" + } + }, + "assign_profile_phases_add": { + "title": "Lägg till fasintervall", + "description": "Lägg till ett fasintervall till det aktuella utkastet.", + "data": { + "phase_name": "Fas", + "start_min": "Startminut", + "end_min": "Slutminut" + } + }, + "assign_profile_phases_edit_select": { + "title": "Redigera fasintervall", + "description": "Välj ett intervall att redigera.", + "data": { + "range_index": "Fasintervall" + } + }, + "assign_profile_phases_edit": { + "title": "Redigera fasintervall", + "description": "Uppdatera det valda fasintervallet.", + "data": { + "phase_name": "Fas", + "start_min": "Startminut", + "end_min": "Slutminut" + } + }, + "assign_profile_phases_delete": { + "title": "Ta bort fasintervall", + "description": "Välj ett intervall att ta bort från utkastet.", + "data": { + "range_index": "Fasintervall" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Automatiskt identifierade faser", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} fas(er) identifierades automatiskt.** Välj en åtgärd nedan.", + "data": { + "action": "Åtgärd" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Namnge identifierade faser", + "description": "Profil: **{profile_name}**\n\n**{detected_count} fas(er) identifierade:**\n{ranges_summary}\n\nTilldela ett namn till varje fas." + }, + "assign_profile_phases_select": { + "title": "Tilldela fasintervall", + "description": "Välj en profil för att konfigurera fasintervall.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profilstatistik", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Skapa ny profil", + "description": "Skapa en ny profil manuellt eller från en tidigare cykel.\n\nExempel på profilnamn: \"Delikat\", \"Kraftfull\", \"Snabbtvätt\"", + "data": { + "profile_name": "Profilnamn", + "reference_cycle": "Referenscykel (valfritt)", + "manual_duration": "Manuell varaktighet (minuter)" + } + }, + "edit_profile": { + "title": "Redigera profil", + "description": "Välj en profil att byta namn på", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Redigera profilinformation", + "description": "Aktuell profil: {current_name}", + "data": { + "new_name": "Profilnamn", + "manual_duration": "Manuell varaktighet (minuter)" + } + }, + "delete_profile_select": { + "title": "Ta bort profil", + "description": "Välj en profil att ta bort", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Bekräfta borttagning av profil", + "description": "⚠️ Detta kommer att permanent ta bort profilen: {profile_name}", + "data": { + "unlabel_cycles": "Ta bort etiketten från cykler med denna profil" + } + }, + "auto_label_cycles": { + "title": "Automatisk märkning av gamla cykler", + "description": "Hittade {total_count} totala cykler. Profiler: {profiles}", + "data": { + "confidence_threshold": "Minsta konfidens (0,50–0,95)" + } + }, + "select_cycle_to_label": { + "title": "Välj cykel att märka", + "description": "✓ = klar, ⚠ = återupptagen, ✗ = avbruten", + "data": { + "cycle_id": "Cykel" + } + }, + "select_cycle_to_delete": { + "title": "Ta bort cykel", + "description": "⚠️ Detta kommer att permanent ta bort den valda cykeln", + "data": { + "cycle_id": "Cykel att ta bort" + } + }, + "label_cycle": { + "title": "Märk cykel", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Nytt profilnamn (om du skapar)" + } + }, + "post_process": { + "title": "Bearbeta historik (Slå samman/Dela)", + "description": "Rensa upp cykelhistoriken genom att slå samman fragmenterade körningar och dela upp felaktigt sammanslagna cykler inom ett tidsfönster. Ange antal timmar för att se tillbaka (använd 999999 för alla).", + "data": { + "time_range": "Tillbakablicksfönster (timmar)", + "gap_seconds": "Sammanfoga/dela-mellanrum (sekunder)" + }, + "data_description": { + "time_range": "Antal timmar att skanna för att fragmenterade cykler ska sammanfogas. Använd 999999 för att bearbeta alla historiska data.", + "gap_seconds": "Tröskel för att avgöra delning kontra sammanslagning. Luckor KORTARE än detta slås samman. Luckor LÄNGRE än detta delas. Sänk detta för att tvinga en delning på en cykel som sammanfogats felaktigt." + } + }, + "cleanup_profile": { + "title": "Rensa historik – Välj profil", + "description": "Välj en profil att visualisera. Detta genererar ett diagram som visar alla tidigare cykler för denna profil för att hjälpa till att identifiera extremvärden.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Rensa historik – Graf och radera", + "description": "![Graph]({graph_url})\n\n**Visualiserar {profile_name}**\n\nGrafen visar individuella cykler som färgade linjer. Identifiera extremvärden (linjer långt från gruppen) och matcha deras färg i listan nedan för att ta bort dem.\n\n**Välj cykler att radera PERMANENT:**", + "data": { + "cycles_to_delete": "Cykler att ta bort (markera matchande färger)" + } + }, + "interactive_editor": { + "title": "Interaktiv redigerare för sammanslagning/delning", + "description": "Välj en manuell åtgärd att utföra på din cykelhistorik.", + "menu_options": { + "editor_split": "Dela en cykel (hitta luckor)", + "editor_merge": "Slå samman cykler (foga ihop fragment)", + "editor_delete": "Ta bort cykel/cykler", + "menu_back": "← Tillbaka" + } + }, + "editor_select": { + "title": "Välj cykler", + "description": "Välj cykeln/cyklerna som ska bearbetas.\n\n{info_text}", + "data": { + "selected_cycles": "Cykler" + } + }, + "editor_configure": { + "title": "Konfigurera och tillämpa", + "description": "{preview_md}", + "data": { + "confirm_action": "Åtgärd", + "merged_profile": "Resulterande profil", + "new_profile_name": "Nytt profilnamn", + "confirm_commit": "Ja, jag bekräftar denna åtgärd", + "segment_0_profile": "Segment 1-profil", + "segment_1_profile": "Segment 2-profil", + "segment_2_profile": "Segment 3-profil", + "segment_3_profile": "Segment 4-profil", + "segment_4_profile": "Segment 5-profil", + "segment_5_profile": "Segment 6-profil", + "segment_6_profile": "Segment 7-profil", + "segment_7_profile": "Segment 8-profil", + "segment_8_profile": "Segment 9-profil", + "segment_9_profile": "Segment 10-profil" + } + }, + "editor_split_params": { + "title": "Konfiguration för delning", + "description": "Justera känsligheten för att dela denna cykel. Varje tomgångslucka som är längre än detta orsakar en delning.", + "data": { + "split_mode": "Delningsmetod" + } + }, + "editor_split_auto_params": { + "title": "Konfiguration för automatisk delning", + "description": "Justera känsligheten för att dela denna cykel. Alla inaktiva mellanrum som är längre än detta kommer att orsaka en delning.", + "data": { + "min_gap_seconds": "Tröskel för delningsgap (sekunder)" + } + }, + "editor_split_manual_params": { + "title": "Manuella delningstidsstämplar", + "description": "{preview_md}Cykelfönster: **{cycle_start_wallclock} → {cycle_end_wallclock}** den {cycle_date}.\n\nAnge en eller flera delningstidsstämplar (`HH:MM` eller `HH:MM:SS`), en per rad. Cykeln delas vid varje tidsstämpel — N tidsstämplar ger N+1 segment.", + "data": { + "split_timestamps": "Delningstidsstämplar" + } + }, + "reprocess_history": { + "title": "Underhåll: Bearbeta och optimera data", + "description": "Utför djuprengöring av historiska data:\n1. Trimmar nolleffektavläsningar (tystnad).\n2. Korrigerar cykeltid/varaktighet.\n3. Beräknar om alla statistiska modeller (kuvert).\n\n**Kör detta för att åtgärda dataproblem eller tillämpa nya algoritmer.**" + }, + "wipe_history": { + "title": "Rensa historik (endast testning)", + "description": "⚠️ Detta tar permanent bort ALLA lagrade cykler och profiler för den här enheten. Detta kan inte ångras!" + }, + "export_import": { + "title": "Exportera/importera JSON", + "description": "Kopiera/klistra in cykler, profiler OCH inställningar för den här enheten. Exportera nedan eller klistra in JSON för att importera.", + "data": { + "mode": "Åtgärd", + "json_payload": "Komplett konfigurations-JSON" + }, + "data_description": { + "mode": "Välj Exportera för att kopiera aktuell data och inställningar, eller Importera för att klistra in exporterad data från en annan WashData-enhet.", + "json_payload": "För export: kopiera denna JSON (inkluderar cykler, profiler och alla finjusterade inställningar). För import: klistra in JSON exporterad från WashData." + } + }, + "record_cycle": { + "title": "Spela in cykel", + "description": "Spela in en cykel manuellt för att skapa en ren profil utan störningar.\n\nStatus: **{status}**\nVaraktighet: {duration}s\nProver: {samples}", + "menu_options": { + "record_refresh": "Uppdatera status", + "record_stop": "Stoppa inspelning (spara och bearbeta)", + "record_start": "Starta ny inspelning", + "record_process": "Bearbeta senaste inspelning (trimma och spara)", + "record_discard": "Kasta senaste inspelningen", + "menu_back": "← Tillbaka" + } + }, + "record_process": { + "title": "Bearbeta inspelning", + "description": "![Graph]({graph_url})\n\n**Grafen visar råinspelning.** Blå = Behåll, Röd = Föreslagen trim.\n*Diagrammet är statiskt och uppdateras inte med formulärändringar.*\n\nInspelningen stoppades. Trim är justerade till den detekterade samplingsfrekvensen (~{sampling_rate}s).\n\nRå varaktighet: {duration}s\nProver: {samples}", + "data": { + "head_trim": "Trimstart (sekunder)", + "tail_trim": "Trimslut (sekunder)", + "save_mode": "Spara destination", + "profile_name": "Profilnamn" + } + }, + "learning_feedbacks": { + "title": "Inlärningsfeedback", + "description": "Väntande feedback: {count}\n\n{pending_table}\n\nVälj en begäran om cykelgranskning att behandla.", + "menu_options": { + "learning_feedbacks_pick": "Granska väntande feedback", + "learning_feedbacks_dismiss_all": "Avfärda all väntande feedback", + "menu_back": "← Tillbaka" + } + }, + "learning_feedbacks_pick": { + "title": "Välj feedback att granska", + "description": "Välj en begäran om cykelgranskning att öppna.", + "data": { + "selected_feedback": "Välj cykel att granska" + } + }, + "learning_feedbacks_empty": { + "title": "Inlärningsfeedback", + "description": "Inga väntande feedbackförfrågningar hittades.", + "menu_options": { + "menu_back": "← Tillbaka" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Avvisa all väntande feedback", + "description": "Du är på väg att avvisa **{count}** väntande feedbackbegäran. Avvisade cykler finns kvar i historiken men bidrar inte med någon ny lärsignal. Detta kan inte ångras.\n\n✅ Markera rutan nedan och klicka på **Skicka** för att avvisa allt.\n❌ Lämna den omarkerad och klicka på **Skicka** för att avbryta och gå tillbaka.", + "data": { + "confirm_dismiss_all": "Bekräfta: avfärda alla väntande feedbackförfrågningar" + } + }, + "resolve_feedback": { + "title": "Lös feedback", + "description": "{comparison_img}{candidates_table}\n**Identifierad profil**: {detected_profile} ({confidence_pct}%)\n**Uppskattad varaktighet**: {est_duration_min} min\n**Faktisk varaktighet**: {act_duration_min} min\n\n__Välj en åtgärd nedan:__", + "data": { + "action": "Vad vill du göra?", + "corrected_profile": "Korrekt program (om du korrigerar)", + "corrected_duration": "Korrekt varaktighet i sekunder (om du korrigerar)" + } + }, + "trim_cycle_select": { + "title": "Trimma cykel - Välj cykel", + "description": "Välj en cykel att trimma. ✓ = klar, ⚠ = återupptagen, ✗ = avbruten", + "data": { + "cycle_id": "Cykel" + } + }, + "trim_cycle": { + "title": "Trimma cykel", + "description": "Aktuellt fönster: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Åtgärd" + } + }, + "trim_cycle_start": { + "title": "Ange trimstart", + "description": "Cykelfönster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuvarande start: **{current_wallclock}** (+{current_offset_min} min från cykelstart)\n\nVälj en ny starttid med hjälp av klockväljaren nedan.", + "data": { + "trim_start_time": "Ny starttid", + "trim_start_min": "Ny start (minuter från cykelstart)" + } + }, + "trim_cycle_end": { + "title": "Ange trimslut", + "description": "Cykelfönster: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nNuvarande slut: **{current_wallclock}** (+{current_offset_min} min från cykelstart)\n\nVälj en ny sluttid med hjälp av klockväljaren nedan.", + "data": { + "trim_end_time": "Ny sluttid", + "trim_end_min": "Nytt slut (minuter från cykelstart)" + } + } + }, + "error": { + "import_failed": "Importen misslyckades. Vänligen klistra in en giltig WashData-export-JSON.", + "select_exactly_one": "Välj exakt en cykel för den här åtgärden.", + "select_at_least_one": "Välj minst en cykel för den här åtgärden.", + "select_at_least_two": "Välj minst två cykler för den här åtgärden.", + "profile_exists": "En profil med detta namn finns redan", + "rename_failed": "Det gick inte att byta namn på profilen. Profilen kanske inte finns eller det nya namnet är redan taget.", + "assignment_failed": "Det gick inte att tilldela profilen till cykeln", + "duplicate_phase": "En fas med detta namn finns redan.", + "phase_not_found": "Den valda fasen hittades inte.", + "invalid_phase_name": "Fasnamnet får inte vara tomt.", + "phase_name_too_long": "Fasnamnet är för långt.", + "invalid_phase_range": "Fasintervallet är ogiltigt. Slutet måste vara större än början.", + "invalid_phase_timestamp": "Tidsstämpeln är ogiltig. Använd formatet ÅÅÅÅ-MM-DD TT:MM eller TT:MM.", + "overlapping_phase_ranges": "Fasintervall överlappar varandra. Justera intervallen och försök igen.", + "incomplete_phase_row": "Varje fasrad måste innehålla en fas plus kompletta start-/slutvärden för det valda läget.", + "timestamp_mode_cycle_required": "Välj en källcykel när du använder tidsstämpelläget.", + "no_phase_ranges": "Inga fasintervall är tillgängliga för den åtgärden ännu.", + "feedback_notification_title": "WashData: Verifiera cykel ({device})", + "feedback_notification_message": "**Enhet**: {device}\n**Program**: {program} ({confidence}% konfidens)\n**Tid**: {time}\n\nWashData behöver din hjälp för att verifiera denna identifierade cykel.\n\nGå till **Inställningar > Enheter och tjänster > WashData > Konfigurera > Inlärningsfeedback** för att bekräfta eller korrigera detta resultat.", + "suggestions_ready_notification_title": "WashData: Föreslagna inställningar klara ({device})", + "suggestions_ready_notification_message": "Sensorn **Tillgängliga föreslagna inställningar** rapporterar nu **{count}** åtgärdbara rekommendationer.\n\nSå här granskar och tillämpar du dem: **Inställningar > Enheter och tjänster > WashData > Konfigurera > Avancerade inställningar > Tillämpa föreslagna värden**.\n\nFörslag är valfria och visas för granskning innan du sparar.", + "trim_range_invalid": "Starten måste vara före slutet. Justera dina trimpunkter.", + "trim_failed": "Trimmningen misslyckades. Cykeln kanske inte längre finns eller så är fönstret tomt.", + "invalid_split_timestamp": "Tidsstämpeln är ogiltig eller ligger utanför cykelfönstret. Använd HH:MM eller HH:MM:SS inom cykelfönstret.", + "no_split_segments_found": "Inga giltiga segment kunde skapas från dessa tidsstämplar. Kontrollera att varje tidsstämpel ligger inom cykelfönstret och att segmenten är minst 1 minut långa.", + "auto_tune_suggestion": "{device_type} {device_title} upptäckte spökcykler. Föreslagen min_effektändring: {current_min}W -> {new_min}W (tillämpas inte automatiskt).", + "auto_tune_title": "WashData Auto-Tune", + "auto_tune_fallback": "{device_type} {device_title} upptäckte spökcykler. Föreslagen min_effektändring: {current_min}W -> {new_min}W (tillämpas inte automatiskt)." + }, + "abort": { + "no_cycles_found": "Inga cykler hittades", + "no_split_segments_found": "Inga delbara segment hittades i den valda cykeln.", + "cycle_not_found": "Den valda cykeln kunde inte läsas in.", + "no_power_data": "Ingen effektspårdata tillgänglig för den valda cykeln.", + "no_profiles_found": "Inga profiler hittades. Skapa en profil först.", + "no_profiles_for_matching": "Inga profiler tillgängliga för matchning. Skapa profiler först.", + "no_unlabeled_cycles": "Alla cykler är redan märkta", + "no_suggestions": "Inga föreslagna värden tillgängliga ännu. Kör några cykler och försök igen.", + "no_predictions": "Inga förutsägelser", + "no_custom_phases": "Inga anpassade faser hittades.", + "reprocess_success": "Omarbetade {count} cykler.", + "debug_data_cleared": "Felsökningsdata från {count} cykler har rensats." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Cykelstart", + "cycle_finish": "Cykelavslut", + "cycle_live": "Liveförlopp" + } + }, + "common_text": { + "options": { + "unlabeled": "(Omärkt)", + "create_new_profile": "Skapa ny profil", + "remove_label": "Ta bort etikett", + "no_reference_cycle": "(Ingen referenscykel)", + "all_device_types": "Alla enhetstyper", + "current_suffix": "(nuvarande)", + "editor_select_info": "Välj 1 cykel för att dela, eller 2+ cykler för att slå samman.", + "editor_select_info_split": "Välj 1 cykel att dela.", + "editor_select_info_merge": "Välj 2 eller fler cykler att slå samman.", + "editor_select_info_delete": "Välj 1 eller fler cykler att ta bort.", + "no_cycles_recorded": "Inga cykler registrerade ännu.", + "profile_summary_line": "- **{name}**: {count} cykler, {avg}m snitt", + "no_profiles_created": "Inga profiler har skapats ännu.", + "phase_preview": "Fasförhandsgranskning", + "phase_preview_no_curve": "Genomsnittlig profilkurva är inte tillgänglig ännu. Kör/märk fler cykler för denna profil.", + "average_power_curve": "Genomsnittlig effektkurva", + "unit_min": "min", + "profile_option_fmt": "{name} ({count} cykler, ~{duration}m snitt)", + "profile_option_short_fmt": "{name} ({count} cykler, ~{duration}m)", + "deleted_cycles_from_profile": "Raderade {count} cykler från {profile}.", + "not_enough_data_graph": "Inte tillräckligt med data för att generera graf.", + "no_profiles_found": "Inga profiler hittades.", + "cycle_info_fmt": "Cykel: {start}, {duration}m, etikett: {label}", + "top_candidates_header": "### Toppkandidater", + "tbl_profile": "Profil", + "tbl_confidence": "Konfidens", + "tbl_correlation": "Korrelation", + "tbl_duration_match": "Varaktighetsmatchning", + "detected_profile": "Identifierad profil", + "estimated_duration": "Uppskattad varaktighet", + "actual_duration": "Faktisk varaktighet", + "choose_action": "__Välj en åtgärd nedan:__", + "tbl_count": "Antal", + "tbl_avg": "Snitt", + "tbl_min": "Min", + "tbl_max": "Max", + "tbl_energy_avg": "Energi (snitt)", + "tbl_energy_total": "Energi (totalt)", + "tbl_consistency": "Konst.", + "tbl_last_run": "Senaste körning", + "graph_legend_title": "Förklaring till grafen", + "graph_legend_body": "Det blå bandet representerar det minsta och maximala effektförbrukningsintervallet som observerats. Linjen visar medeleffektkurvan.", + "recording_preview": "Förhandsvisning av inspelning", + "trim_start": "Trimstart", + "trim_end": "Trimslut", + "split_preview_title": "Förhandsgranskning av delning", + "split_preview_found_fmt": "Hittade {count} segment.", + "split_preview_confirm_fmt": "Klicka på Bekräfta för att dela upp denna cykel i {count} separata cykler.", + "merge_preview_title": "Förhandsgranskning av sammanslagning", + "merge_preview_joining_fmt": "Slår samman {count} cykler. Luckor fylls med 0W-avläsningar.", + "editor_delete_preview_title": "Förhandsgranskning av borttagning", + "editor_delete_preview_intro": "De valda cyklerna kommer att tas bort permanent:", + "editor_delete_preview_confirm": "Klicka på Bekräfta för att permanent ta bort dessa cykelposter.", + "no_power_preview": "*Ingen effektdata tillgänglig för förhandsgranskning.*", + "profile_comparison": "Profiljämförelse", + "actual_cycle_label": "Denna cykel (faktisk)", + "storage_usage_header": "Lagringsanvändning", + "storage_file_size": "Filstorlek", + "storage_cycles": "Cykler", + "storage_profiles": "Profiler", + "storage_debug_traces": "Felsökningsspår", + "overview_suffix": "Översikt", + "phase_preview_for_profile": "Fasförhandsgranskning för profil", + "current_ranges_header": "Aktuella intervall", + "assign_phases_help": "Använd åtgärderna nedan för att lägga till, redigera, ta bort eller spara intervall.", + "profile_summary_header": "Profilsammanfattning", + "recent_cycles_header": "Senaste cyklerna", + "trim_cycle_preview_title": "Förhandsgranskning av cykeltrimmning", + "trim_cycle_preview_no_data": "Ingen effektdata tillgänglig för denna cykel.", + "trim_cycle_preview_kept_suffix": "behålls", + "table_program": "Program", + "table_when": "När", + "table_length": "Längd", + "table_match": "Matchning", + "table_profile": "Profil", + "table_cycles": "Cykler", + "table_avg_length": "Gen. längd", + "table_last_run": "Senaste körning", + "table_avg_energy": "Gen. energi", + "table_detected_program": "Identifierat program", + "table_confidence": "Konfidens", + "table_reported": "Rapporterad", + "phase_builtin_suffix": "(Inbyggt)", + "phase_none_available": "Inga faser tillgängliga.", + "settings_deprecation_warning": "⚠️ **Föråldrad enhetstyp:** {device_type} är planerad att tas bort i en framtida version. WashDatas matchande pipeline ger inte tillförlitliga resultat för denna apparatklass. Din integration fortsätter att fungera under utfasningsperioden; för att tysta denna varning byter du **Enhetstyp** nedan till en av de stödda typerna (tvättmaskin, torktumlare, tvättmaskin-torktumlare, diskmaskin, luftfritös, brödbakare eller pump), eller till **Annan (avancerat)** om din apparat inte matchar någon av de stödda typerna. **Övrigt (avancerat)** skickar avsiktligt generiska standardinställningar som inte är inställda för någon specifik apparat, så du måste konfigurera trösklar, tidsgränser och matchande parametrar själv; alla dina befintliga inställningar bevaras när du byter.", + "phase_other_device_types": "Andra enhetstyper:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Dela en cykel (hitta luckor)", + "merge": "Slå samman cykler (foga ihop fragment)", + "delete": "Ta bort cykel/cykler" + } + }, + "split_mode": { + "options": { + "auto": "Identifiera automatiskt inaktiva mellanrum", + "manual": "Manuella tidsstämplar" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Underhåll: Bearbeta och optimera data", + "clear_debug_data": "Rensa felsökningsdata (frigör utrymme)", + "wipe_history": "Rensa ALL data för den här enheten (oåterkallelig)", + "export_import": "Exportera/importera JSON med inställningar (kopiera/klistra in)" + } + }, + "export_import_mode": { + "options": { + "export": "Exportera all data", + "import": "Importera/slå samman data" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Automatisk märkning av gamla cykler", + "select_cycle_to_label": "Märk en specifik cykel", + "select_cycle_to_delete": "Ta bort cykel", + "interactive_editor": "Interaktiv redigerare för sammanslagning/delning", + "trim_cycle": "Trimma cykeldata" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Skapa ny profil", + "edit_profile": "Redigera/byt namn på profil", + "delete_profile": "Ta bort profil", + "profile_stats": "Profilstatistik", + "cleanup_profile": "Rensa historik – Graf och radera", + "assign_phases": "Tilldela fasintervall" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Skapa ny fas", + "edit_custom_phase": "Redigera fas", + "delete_custom_phase": "Ta bort fas" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Lägg till fasintervall", + "edit_range": "Redigera fasintervall", + "delete_range": "Ta bort fasintervall", + "clear_ranges": "Rensa alla intervall", + "auto_detect_ranges": "Identifiera faser automatiskt", + "save_ranges": "Spara och återgå" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Uppdatera status", + "stop_recording": "Stoppa inspelning (spara och bearbeta)", + "start_recording": "Starta ny inspelning", + "process_recording": "Bearbeta senaste inspelning (trimma och spara)", + "discard_recording": "Kasta senaste inspelningen" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Skapa ny profil", + "existing_profile": "Lägg till i befintlig profil", + "discard": "Kasta inspelning" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Bekräfta – Korrekt identifiering", + "correct": "Korrigera – Välj program/längd", + "ignore": "Ignorera – Falskt positivt/bullrig cykel", + "delete": "Ta bort – Radera från historiken" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Namnge och tillämpa faser", + "cancel": "Gå tillbaka utan ändringar" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Ange startpunkt", + "set_end": "Ange slutpunkt", + "reset": "Återställ till full varaktighet", + "apply": "Tillämpa trim", + "cancel": "Avbryt" + } + }, + "device_type": { + "options": { + "washing_machine": "Tvättmaskin", + "dryer": "Torktumlare", + "washer_dryer": "Kombination av tvättmaskin och torktumlare", + "dishwasher": "Diskmaskin", + "coffee_machine": "Kaffemaskin (utfasad)", + "ev": "Elfordon (utfasad)", + "air_fryer": "Air Fryer", + "heat_pump": "Värmepump (utfasad)", + "bread_maker": "Brödmaskin", + "pump": "Pump / Sump Pump", + "oven": "Ugn (utfasad)", + "other": "Annat (avancerat)" + } + } + }, + "services": { + "label_cycle": { + "name": "Märk cykel", + "description": "Tilldela en befintlig profil till en tidigare cykel eller ta bort etiketten.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten att märka." + }, + "cycle_id": { + "name": "Cykel-ID", + "description": "ID för cykeln som ska märkas." + }, + "profile_name": { + "name": "Profilnamn", + "description": "Namnet på en befintlig profil (skapa profiler i menyn Hantera profiler). Lämna tomt för att ta bort etiketten." + } + } + }, + "create_profile": { + "name": "Skapa profil", + "description": "Skapa en ny profil (fristående eller baserad på en referenscykel).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten." + }, + "profile_name": { + "name": "Profilnamn", + "description": "Namn på den nya profilen (t.ex. \"Kraftfull\", \"Delikat\")." + }, + "reference_cycle_id": { + "name": "Referenscykel-ID", + "description": "Valfritt cykel-ID att basera denna profil på." + } + } + }, + "delete_profile": { + "name": "Ta bort profil", + "description": "Ta bort en profil och eventuellt avmarkera cykler som använder den.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten." + }, + "profile_name": { + "name": "Profilnamn", + "description": "Profilen som ska tas bort." + }, + "unlabel_cycles": { + "name": "Avmarkera cykler", + "description": "Ta bort profiletikett från cykler som använder denna profil." + } + } + }, + "auto_label_cycles": { + "name": "Automatisk märkning av gamla cykler", + "description": "Märk omärkta cykler retroaktivt med hjälp av profilmatchning.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten." + }, + "confidence_threshold": { + "name": "Konfidenströskel", + "description": "Minsta matchningskonfidens (0,50–0,95) för att applicera etiketter." + } + } + }, + "export_config": { + "name": "Exportera konfiguration", + "description": "Exportera enhetens profiler, cykler och inställningar till en JSON-fil (per enhet).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten att exportera." + }, + "path": { + "name": "Sökväg", + "description": "Valfri absolut filsökväg att skriva till (standard: /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Importera konfiguration", + "description": "Importera profiler, cykler och inställningar för den här enheten från en JSON-exportfil (inställningarna kan skrivas över).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten att importera till." + }, + "path": { + "name": "Sökväg", + "description": "Absolut sökväg till export-JSON-filen som ska importeras." + } + } + }, + "submit_cycle_feedback": { + "name": "Skicka cykelfeedback", + "description": "Bekräfta eller korrigera ett automatiskt identifierat program efter en avslutad cykel. Ange antingen `entry_id` (avancerat) eller `device_id` (rekommenderas).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten (rekommenderas framför entry_id)." + }, + "entry_id": { + "name": "Post-ID", + "description": "Konfigurationspost-ID för enheten (alternativ till device_id)." + }, + "cycle_id": { + "name": "Cykel-ID", + "description": "Cykel-ID som visas i feedbackmeddelandet/loggarna." + }, + "user_confirmed": { + "name": "Bekräfta identifierat program", + "description": "Ange sant om det identifierade programmet är korrekt." + }, + "corrected_profile": { + "name": "Korrigerad profil", + "description": "Om det inte bekräftas, ange korrekt profil-/programnamn." + }, + "corrected_duration": { + "name": "Korrigerad varaktighet (sekunder)", + "description": "Valfri korrigerad varaktighet i sekunder." + }, + "notes": { + "name": "Anteckningar", + "description": "Valfria anteckningar om denna cykel." + } + } + }, + "record_start": { + "name": "Starta cykelinspelning", + "description": "Börja manuellt spela in en ren cykel (kringgår all matchningslogik).", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten att spela in." + } + } + }, + "record_stop": { + "name": "Stoppa cykelinspelning", + "description": "Stoppa manuell inspelning.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten att stoppa inspelning på." + } + } + }, + "trim_cycle": { + "name": "Trimma cykel", + "description": "Trimma effektdata för en tidigare cykel till ett specifikt tidsfönster.", + "fields": { + "device_id": { + "name": "Enhet", + "description": "WashData-enheten." + }, + "cycle_id": { + "name": "Cykel-ID", + "description": "ID för cykeln som ska trimmas." + }, + "trim_start_s": { + "name": "Trimstart (sekunder)", + "description": "Behåll data från denna offset i sekunder. Standard: 0." + }, + "trim_end_s": { + "name": "Trimslut (sekunder)", + "description": "Behåll data upp till denna offset i sekunder. Standard = full varaktighet." + } + } + }, + "pause_cycle": { + "name": "Pauscykel", + "description": "Pausa den aktiva cykeln för en WashData-enhet.", + "fields": { + "device_id": { + "name": "Anordning", + "description": "WashData-enheten att pausa." + } + } + }, + "resume_cycle": { + "name": "Återuppta cykeln", + "description": "Återuppta en pausad cykel för en WashData-enhet.", + "fields": { + "device_id": { + "name": "Anordning", + "description": "WashData-enheten ska återupptas." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Igång" + }, + "match_ambiguity": { + "name": "Matchningstvetydighet" + } + }, + "sensor": { + "washer_state": { + "name": "Tillstånd", + "state": { + "off": "Av", + "idle": "Inaktiv", + "starting": "Startar", + "running": "Igång", + "paused": "Pausad", + "user_paused": "Pausad av användare", + "ending": "Avslutar", + "finished": "Färdig", + "anti_wrinkle": "Antirynkläge", + "delay_wait": "Väntar på att starta", + "interrupted": "Avbruten", + "force_stopped": "Tvångsstoppad", + "rinse": "Sköljning", + "unknown": "Okänd", + "clean": "Rena" + } + }, + "washer_program": { + "name": "Program" + }, + "time_remaining": { + "name": "Återstående tid" + }, + "total_duration": { + "name": "Total varaktighet" + }, + "cycle_progress": { + "name": "Förlopp" + }, + "current_power": { + "name": "Aktuell effekt" + }, + "elapsed_time": { + "name": "Förfluten tid" + }, + "debug_info": { + "name": "Felsökningsinformation" + }, + "match_confidence": { + "name": "Matchningskonfidens" + }, + "top_candidates": { + "name": "Toppkandidater", + "state": { + "none": "Ingen" + } + }, + "current_phase": { + "name": "Aktuell fas" + }, + "wash_phase": { + "name": "Fas" + }, + "profile_cycle_count": { + "name": "Antal cykler för profil {profile_name}", + "unit_of_measurement": "cykler" + }, + "suggestions": { + "name": "Tillgängliga föreslagna inställningar" + }, + "pump_runs_today": { + "name": "Pumpen går (senaste 24 timmarna)" + }, + "cycle_count": { + "name": "Cykelräkning" + } + }, + "select": { + "program_select": { + "name": "Cykelprogram", + "state": { + "auto_detect": "Automatisk identifiering" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Tvinga cykelavslut" + }, + "pause_cycle": { + "name": "Pauscykel" + }, + "resume_cycle": { + "name": "Återuppta cykeln" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Ett giltigt device_id krävs." + }, + "cycle_id_required": { + "message": "Ett giltigt cycle_id krävs." + }, + "profile_name_required": { + "message": "Ett giltigt profile_name krävs." + }, + "device_not_found": { + "message": "Enheten hittades inte." + }, + "no_config_entry": { + "message": "Ingen konfigurationspost hittades för enheten." + }, + "integration_not_loaded": { + "message": "Integrationen har inte laddats för den här enheten." + }, + "cycle_not_found_or_no_power": { + "message": "Cykeln hittades inte eller har inga effektdata." + }, + "trim_failed_empty_window": { + "message": "Trimmningen misslyckades – cykeln hittades inte, inga effektdata eller det resulterande fönstret är tomt." + }, + "trim_invalid_range": { + "message": "trim_end_s måste vara större än trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ta.json b/custom_components/ha_washdata/translations/ta.json new file mode 100644 index 0000000..8d7b7da --- /dev/null +++ b/custom_components/ha_washdata/translations/ta.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData அமைப்பு", + "description": "உங்கள் சலவை இயந்திரம் அல்லது பிற சாதனங்களை உள்ளமைக்கவும்.\n\nபவர் சென்சார் தேவை.\n\n**அடுத்து, உங்கள் முதல் சுயவிவரத்தை உருவாக்க விரும்புகிறீர்களா என்று கேட்கப்படும்.**", + "data": { + "name": "சாதனத்தின் பெயர்", + "device_type": "சாதன வகை", + "power_sensor": "பவர் சென்சார்", + "min_power": "குறைந்தபட்ச ஆற்றல் வரம்பு (W)" + }, + "data_description": { + "name": "இந்தச் சாதனத்திற்கான நட்புப் பெயர் (எ.கா., 'வாஷிங் மெஷின்', 'டிஷ்வாஷர்').", + "device_type": "இது என்ன வகையான சாதனம்? தையல்காரர் கண்டறிதல் மற்றும் லேபிளிங் உதவுகிறது.", + "power_sensor": "உங்கள் ஸ்மார்ட் பிளக்கிலிருந்து நிகழ்நேர மின் நுகர்வு (வாட்களில்) தெரிவிக்கும் சென்சார் நிறுவனம்.", + "min_power": "இந்த வரம்புக்கு மேலே உள்ள சக்தி அளவீடுகள் (வாட்களில்) சாதனம் இயங்குவதைக் குறிக்கிறது. பெரும்பாலான சாதனங்களுக்கு 2W உடன் தொடங்கவும்." + } + }, + "first_profile": { + "title": "முதல் சுயவிவரத்தை உருவாக்கவும்", + "description": "**சைக்கிள்** என்பது உங்கள் சாதனத்தின் முழு ஓட்டமாகும் (எ.கா., சலவை சுமை). ஒரு **சுயவிவரம்** இந்த சுழற்சிகளை வகையின்படி குழுவாக்குகிறது (எ.கா., 'பருத்தி', 'விரைவு கழுவுதல்'). இப்போது சுயவிவரத்தை உருவாக்குவது ஒருங்கிணைப்பு கால அளவை உடனடியாகக் கணக்கிட உதவுகிறது.\n\nசாதனக் கட்டுப்பாடுகள் தானாகக் கண்டறியப்படாவிட்டால், இந்த சுயவிவரத்தை நீங்கள் கைமுறையாகத் தேர்ந்தெடுக்கலாம்.\n\n**இந்தப் படியைத் தவிர்க்க 'சுயவிவரப் பெயரை' காலியாக விடவும்.**", + "data": { + "profile_name": "சுயவிவரப் பெயர் (விரும்பினால்)", + "manual_duration": "மதிப்பிடப்பட்ட காலம் (நிமிடங்கள்)" + } + } + }, + "error": { + "cannot_connect": "இணைக்க முடியவில்லை", + "invalid_auth": "தவறான அங்கீகாரம்", + "unknown": "எதிர்பாராத பிழை" + }, + "abort": { + "already_configured": "சாதனம் ஏற்கனவே உள்ளமைக்கப்பட்டுள்ளது" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "கற்றல் பின்னூட்டங்களை மதிப்பாய்வு செய்யவும்", + "settings": "அமைப்புகள்", + "notifications": "அறிவிப்புகள்", + "manage_cycles": "சுழற்சிகளை நிர்வகிக்கவும்", + "manage_profiles": "சுயவிவரங்களை நிர்வகிக்கவும்", + "manage_phase_catalog": "கட்ட அட்டவணையை நிர்வகிக்கவும்", + "record_cycle": "பதிவு சுழற்சி (கையேடு)", + "diagnostics": "நோய் கண்டறிதல் & பராமரிப்பு" + } + }, + "apply_suggestions": { + "title": "பரிந்துரைக்கப்பட்ட மதிப்புகளை நகலெடுக்கவும்", + "description": "இது உங்கள் சேமித்த அமைப்புகளில் தற்போதைய பரிந்துரைக்கப்பட்ட மதிப்புகளை நகலெடுக்கும். இது ஒருமுறை மட்டுமே செய்யக்கூடிய செயலாகும், மேலும் இது தானாக மேலெழுதப்படுவதை இயக்காது.\n\nநகலெடுக்க பரிந்துரைக்கப்பட்ட மதிப்புகள்:\n{suggested}", + "data": { + "confirm": "நகல் செயலை உறுதிப்படுத்தவும்" + } + }, + "apply_suggestions_confirm": { + "title": "பரிந்துரைக்கப்பட்ட மாற்றங்களை மதிப்பாய்வு செய்யவும்", + "description": "WashData பயன்படுத்துவதற்கு **{pending_count}** பரிந்துரைக்கப்பட்ட மதிப்பு(கள்) கண்டறியப்பட்டது.\n\nமாற்றங்கள்:\n{changes}\n\n✅ கீழே உள்ள பெட்டியை சரிபார்த்து, இந்த மாற்றங்களை உடனடியாக விண்ணப்பிக்கவும் சேமிக்கவும் **சமர்ப்பி** என்பதைக் கிளிக் செய்யவும்.\n❌ அதைத் தேர்வு செய்யாமல் விட்டுவிட்டு, ரத்துசெய்து திரும்பிச் செல்ல **சமர்ப்பி** என்பதைக் கிளிக் செய்யவும்.", + "data": { + "confirm_apply_suggestions": "இந்த மாற்றங்களைப் பயன்படுத்தி சேமிக்கவும்" + } + }, + "settings": { + "title": "அமைப்புகள்", + "description": "{deprecation_warning}டியூன் கண்டறிதல் வரம்புகள், கற்றல் மற்றும் மேம்பட்ட நடத்தை. பெரும்பாலான அமைப்புகளுக்கு இயல்புநிலை நன்றாக வேலை செய்கிறது.\n\nதற்போது கிடைக்கும் பரிந்துரைக்கப்பட்ட அமைப்புகள்: {suggestions_count}.\nஇது 0-ஐ விட அதிகமாக இருந்தால், மேம்பட்ட அமைப்புகளைத் திறந்து பரிந்துரைகளை மதிப்பாய்வு செய்ய 'பரிந்துரைக்கப்பட்ட மதிப்புகளைப் பயன்படுத்தவும்' என்பதைப் பயன்படுத்தவும்.", + "data": { + "apply_suggestions": "பரிந்துரைக்கப்பட்ட மதிப்புகளைப் பயன்படுத்தவும்", + "device_type": "சாதன வகை", + "power_sensor": "பவர் சென்சார் நிறுவனம்", + "min_power": "குறைந்தபட்ச சக்தி (W)", + "off_delay": "சுழற்சி முடிவு தாமதம் (கள்)", + "notify_actions": "அறிவிப்பு நடவடிக்கைகள்", + "notify_people": "இவர்கள் வீட்டில் இருக்கும் வரை டெலிவரி தாமதம்", + "notify_only_when_home": "தேர்ந்தெடுக்கப்பட்ட நபர் வீட்டில் இருக்கும் வரை அறிவிப்புகளை தாமதப்படுத்தவும்", + "notify_fire_events": "ஆட்டோமேஷன் நிகழ்வுகளை இயக்கு", + "notify_start_services": "சுழற்சி தொடக்கம் - அறிவிப்பு இலக்குகள்", + "notify_finish_services": "சுழற்சி முடிவு - அறிவிப்பு இலக்குகள்", + "notify_live_services": "நேரடி முன்னேற்றம் - அறிவிப்பு இலக்குகள்", + "notify_before_end_minutes": "நிறைவுக்கு முந்தைய அறிவிப்பு (முடிவதற்கு சில நிமிடங்களுக்கு முன்)", + "notify_title": "அறிவிப்பு தலைப்பு", + "notify_icon": "அறிவிப்பு ஐகான்", + "notify_start_message": "செய்தி வடிவமைப்பைத் தொடங்கவும்", + "notify_finish_message": "செய்தி வடிவமைப்பை முடிக்கவும்", + "notify_pre_complete_message": "முன் நிறைவு செய்தி வடிவம்", + "show_advanced": "மேம்பட்ட அமைப்புகளைத் திருத்து (அடுத்த படி)" + }, + "data_description": { + "apply_suggestions": "படிவத்தில் கற்றல் அல்காரிதம் பரிந்துரைத்த மதிப்புகளை நகலெடுக்க இந்தப் பெட்டியைத் தேர்வு செய்யவும். சேமிப்பதற்கு முன் மதிப்பாய்வு செய்யவும்.\n\nபரிந்துரைக்கப்படுகிறது (கற்றலில் இருந்து):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "சாதனத்தின் வகையைத் தேர்ந்தெடுக்கவும் (சலவை இயந்திரம், உலர்த்தி, பாத்திரங்கழுவி, காபி இயந்திரம், EV). இந்த குறிச்சொல் சிறந்த அமைப்பிற்காக ஒவ்வொரு சுழற்சியிலும் சேமிக்கப்படுகிறது.", + "power_sensor": "நிகழ்நேர ஆற்றலைப் புகாரளிக்கும் சென்சார் நிறுவனம் (வாட்களில்). வரலாற்றுத் தரவை இழக்காமல் இதை எப்போது வேண்டுமானாலும் மாற்றலாம்- ஸ்மார்ட் பிளக்கை மாற்றினால் பயனுள்ளதாக இருக்கும்.", + "min_power": "இந்த வரம்புக்கு மேலே உள்ள சக்தி அளவீடுகள் (வாட்களில்) சாதனம் இயங்குவதைக் குறிக்கிறது. பெரும்பாலான சாதனங்களுக்கு 2W உடன் தொடங்கவும்.", + "off_delay": "வினாடிகள் நிறைவுற்றதைக் குறிக்க, ஸ்மூத் செய்யப்பட்ட சக்தி நிறுத்த வாசலுக்குக் கீழே இருக்க வேண்டும். இயல்புநிலை: 120வி.", + "notify_actions": "அறிவிப்புகளுக்கு (ஸ்கிரிப்ட்கள், அறிவிப்புகள், TTS போன்றவை) இயக்குவதற்கான விருப்ப வீட்டு உதவியாளர் செயல் வரிசை. அமைக்கப்பட்டால், ஃபால்பேக் அறிவிப்பு சேவைக்கு முன் செயல்கள் இயங்கும்.", + "notify_people": "இருப்புக்கான நுழைவாயிலுக்கு மக்கள் நிறுவனங்கள் பயன்படுத்தப்படுகின்றன. இயக்கப்பட்டால், தேர்ந்தெடுக்கப்பட்ட ஒருவராவது வீட்டில் இருக்கும் வரை அறிவிப்பை வழங்குவது தாமதமாகும்.", + "notify_only_when_home": "இயக்கப்பட்டிருந்தால், தேர்ந்தெடுக்கப்பட்ட ஒருவராவது வீட்டில் இருக்கும் வரை அறிவிப்புகளை தாமதப்படுத்தவும்.", + "notify_fire_events": "இயக்கப்பட்டால், ஆட்டோமேஷனை இயக்க, ஹோம் அசிஸ்டண்ட் நிகழ்வுகள் சுழற்சி தொடக்கம் மற்றும் முடிவடையும் (ha_washdata_cycle_started மற்றும் ha_washdata_cycle_ended).", + "notify_start_services": "சுழற்சி தொடங்கும் போது விழிப்பூட்டுவதற்கு ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்குத் தெரிவிக்கும். தொடக்க அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_finish_services": "சுழற்சி முடிவடையும் போது அல்லது முடிவடையும் போது எச்சரிக்கை செய்ய ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்கு அறிவிக்கும். முடிவின் அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_live_services": "சுழற்சி இயங்கும் போது நேரலை முன்னேற்றப் புதுப்பிப்புகளைப் பெறுவதற்கு ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்குத் தெரிவிக்கவும் (மொபைல் துணை பயன்பாடு மட்டும்). நேரலை அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_before_end_minutes": "அறிவிப்பைத் தூண்டுவதற்கு சுழற்சியின் மதிப்பிடப்பட்ட முடிவிற்கு சில நிமிடங்களுக்கு முன். முடக்க 0 என அமைக்கவும். இயல்புநிலை: 0.", + "notify_title": "அறிவிப்புகளுக்கான தனிப்பயன் தலைப்பு. {device} ஒதுக்கிடத்தை ஆதரிக்கிறது.", + "notify_icon": "அறிவிப்புகளுக்குப் பயன்படுத்த வேண்டிய ஐகான் (எ.கா. mdi:washing-machine). ஐகான் இல்லாமல் அனுப்ப காலியாக விடவும்.", + "notify_start_message": "சுழற்சி தொடங்கும் போது செய்தி அனுப்பப்பட்டது. {device} ஒதுக்கிடத்தை ஆதரிக்கிறது.", + "notify_finish_message": "சுழற்சி முடிந்ததும் செய்தி அனுப்பப்பட்டது. {device}, {duration}, {program}, {energy_kwh}, {cost} ஒதுக்கிடங்களை ஆதரிக்கிறது.", + "notify_pre_complete_message": "சுழற்சி இயங்கும் போது தொடர்ச்சியான நேரடி முன்னேற்றப் புதுப்பிப்புகளின் உரை. {device}, {minutes}, {program} ஒதுக்கிடங்களை ஆதரிக்கிறது." + } + }, + "notifications": { + "title": "அறிவிப்புகள்", + "description": "அறிவிப்பு சேனல்கள், டெம்ப்ளேட்கள் மற்றும் விருப்ப நேரலை மொபைல் முன்னேற்ற புதுப்பிப்புகளை உள்ளமைக்கவும்.", + "data": { + "notify_actions": "அறிவிப்பு நடவடிக்கைகள்", + "notify_people": "இவர்கள் வீட்டில் இருக்கும் வரை டெலிவரி தாமதம்", + "notify_only_when_home": "தேர்ந்தெடுக்கப்பட்ட நபர் வீட்டில் இருக்கும் வரை அறிவிப்புகளை தாமதப்படுத்தவும்", + "notify_fire_events": "ஆட்டோமேஷன் நிகழ்வுகளை இயக்கு", + "notify_start_services": "சுழற்சி தொடக்கம் - அறிவிப்பு இலக்குகள்", + "notify_finish_services": "சுழற்சி முடிவு - அறிவிப்பு இலக்குகள்", + "notify_live_services": "நேரடி முன்னேற்றம் - அறிவிப்பு இலக்குகள்", + "notify_before_end_minutes": "நிறைவுக்கு முந்தைய அறிவிப்பு (முடிவதற்கு சில நிமிடங்களுக்கு முன்)", + "notify_live_interval_seconds": "நேரடி புதுப்பிப்பு இடைவெளி (வினாடிகள்)", + "notify_live_overrun_percent": "நேரடி புதுப்பிப்பு ஓவர்ரன் அலவன்ஸ் (%)", + "notify_live_chronometer": "க்ரோனோமீட்டர் கவுண்டவுன் டைமர்", + "notify_title": "அறிவிப்பு தலைப்பு", + "notify_icon": "அறிவிப்பு ஐகான்", + "notify_start_message": "செய்தி வடிவமைப்பைத் தொடங்கவும்", + "notify_finish_message": "செய்தி வடிவமைப்பை முடிக்கவும்", + "notify_pre_complete_message": "முன் நிறைவு செய்தி வடிவம்", + "energy_price_entity": "ஆற்றல் விலை - நிறுவனம் (விரும்பினால்)", + "energy_price_static": "ஆற்றல் விலை - நிலையான மதிப்பு (விரும்பினால்)", + "go_back": "சேமிக்காமல் திரும்பு", + "notify_reminder_message": "நினைவூட்டல் செய்தி வடிவம்", + "notify_timeout_seconds": "பிறகு (வினாடிகள்) தானாக நிராகரி", + "notify_channel": "அறிவிப்பு சேனல் (நிலை/நேரலை/நினைவூட்டல்)", + "notify_finish_channel": "அறிவிப்பு சேனல் முடிந்தது" + }, + "data_description": { + "go_back": "இதைக் குறியிட்டு Submit என்பதைக் கிளிக் செய்தால், மேலே உள்ள மாற்றங்கள் கைவிடப்பட்டு முதன்மை மெனுவிற்கு திரும்பும்.", + "notify_actions": "அறிவிப்புகளுக்கு (ஸ்கிரிப்ட்கள், அறிவிப்புகள், TTS போன்றவை) இயக்குவதற்கான விருப்ப வீட்டு உதவியாளர் செயல் வரிசை. அமைக்கப்பட்டால், ஃபால்பேக் அறிவிப்பு சேவைக்கு முன் செயல்கள் இயங்கும்.", + "notify_people": "இருப்புக்கான நுழைவாயிலுக்கு மக்கள் நிறுவனங்கள் பயன்படுத்தப்படுகின்றன. இயக்கப்பட்டால், தேர்ந்தெடுக்கப்பட்ட ஒருவராவது வீட்டில் இருக்கும் வரை அறிவிப்பை வழங்குவது தாமதமாகும்.", + "notify_only_when_home": "இயக்கப்பட்டிருந்தால், தேர்ந்தெடுக்கப்பட்ட ஒருவராவது வீட்டில் இருக்கும் வரை அறிவிப்புகளை தாமதப்படுத்தவும்.", + "notify_fire_events": "இயக்கப்பட்டால், ஆட்டோமேஷனை இயக்க, ஹோம் அசிஸ்டண்ட் நிகழ்வுகள் சுழற்சி தொடக்கம் மற்றும் முடிவடையும் (ha_washdata_cycle_started மற்றும் ha_washdata_cycle_ended).", + "notify_start_services": "சுழற்சி தொடங்கும் போது விழிப்பூட்டுவதற்கு ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்கு அறிவிக்கிறது (எ.கா. notify.mobile_app_my_phone). தொடக்க அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_finish_services": "சுழற்சி முடிவடையும் போது அல்லது முடிவடையும் போது எச்சரிக்கை செய்ய ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்கு அறிவிக்கும். முடிவின் அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_live_services": "சுழற்சி இயங்கும் போது நேரடி முன்னேற்றப் புதுப்பிப்புகளைப் பெறுவதற்கு ஒன்று அல்லது அதற்கு மேற்பட்ட சேவைகளுக்குத் தெரிவிக்கவும் (மொபைல் துணை பயன்பாடு மட்டும்). நேரலை அறிவிப்புகளைத் தவிர்க்க காலியாக விடவும்.", + "notify_before_end_minutes": "அறிவிப்பைத் தூண்டுவதற்கு சுழற்சியின் மதிப்பிடப்பட்ட முடிவிற்கு சில நிமிடங்களுக்கு முன். முடக்க 0 என அமைக்கவும். இயல்புநிலை: 0.", + "notify_live_interval_seconds": "இயங்கும் போது எவ்வளவு அடிக்கடி முன்னேற்றப் புதுப்பிப்புகள் அனுப்பப்படுகின்றன. குறைந்த மதிப்புகள் மிகவும் பதிலளிக்கக்கூடியவை ஆனால் அதிக அறிவிப்புகளைப் பயன்படுத்துகின்றன. குறைந்தபட்சம் 30 வினாடிகள் செயல்படுத்தப்படும் - 30 வினாடிகளுக்குக் குறைவான மதிப்புகள் தானாகவே 30 வினாடிகள் வரை வட்டமிடப்படும்.", + "notify_live_overrun_percent": "நீண்ட/அதிகமான சுழற்சிகளுக்கான மதிப்பிடப்பட்ட புதுப்பிப்பு எண்ணிக்கையை விட பாதுகாப்பு வரம்பு (உதாரணமாக 20% 1.2x மதிப்பிடப்பட்ட புதுப்பிப்புகளை அனுமதிக்கிறது).", + "notify_live_chronometer": "இயக்கப்பட்டால், ஒவ்வொரு நேரலைப் புதுப்பித்தலும் மதிப்பிடப்பட்ட முடிக்கும் நேரத்திற்கான காலமானி கவுண்டவுனை உள்ளடக்கும். அறிவிப்பு டைமர் புதுப்பிப்புகளுக்கு இடையில் சாதனத்தில் தானாகவே குறையும் (Android துணை பயன்பாடு மட்டும்).", + "notify_title": "அறிவிப்புகளுக்கான தனிப்பயன் தலைப்பு. {device} ஒதுக்கிடத்தை ஆதரிக்கிறது.", + "notify_icon": "அறிவிப்புகளுக்குப் பயன்படுத்த வேண்டிய ஐகான் (எ.கா. mdi:washing-machine). ஐகான் இல்லாமல் அனுப்ப காலியாக விடவும்.", + "notify_start_message": "சுழற்சி தொடங்கும் போது செய்தி அனுப்பப்பட்டது. {device} ஒதுக்கிடத்தை ஆதரிக்கிறது.", + "notify_finish_message": "சுழற்சி முடிந்ததும் செய்தி அனுப்பப்பட்டது. {device}, {duration}, {program}, {energy_kwh}, {cost} ஒதுக்கிடங்களை ஆதரிக்கிறது.", + "energy_price_entity": "தற்போதைய மின்சார விலையை (எ.கா. sensor.electricity_price, input_number.energy_rate) வைத்திருக்கும் எண் உட்பொருளைத் தேர்ந்தெடுக்கவும். அமைக்கப்பட்டதும், {cost} ஒதுக்கிடத்தை இயக்குகிறது. இரண்டும் கட்டமைக்கப்பட்டிருந்தால் நிலையான மதிப்பை விட முன்னுரிமை பெறுகிறது.", + "energy_price_static": "ஒரு kWhக்கு நிலையான மின்சார விலை. அமைக்கப்பட்டதும், {cost} ஒதுக்கிடத்தை இயக்குகிறது. ஒரு பொருள் மேலே உள்ளமைக்கப்பட்டிருந்தால் புறக்கணிக்கப்படும். செய்தி டெம்ப்ளேட்டில் உங்கள் நாணயச் சின்னத்தைச் சேர்க்கவும், எ.கா. {cost} €.", + "notify_reminder_message": "ஒரு முறை நினைவூட்டலின் உரை, கணக்கிடப்பட்ட முடிவிற்கு முன் உள்ளமைக்கப்பட்ட நிமிடங்களின் எண்ணிக்கையை அனுப்பியது. மேலே உள்ள தொடர் நேரலை புதுப்பிப்புகளிலிருந்து வேறுபட்டது. {device}, {minutes}, {program} ஒதுக்கிடங்களை ஆதரிக்கிறது.", + "notify_timeout_seconds": "இந்த பல வினாடிகளுக்குப் பிறகு தானாகவே அறிவிப்புகளை நிராகரிக்கவும் (தோழர் பயன்பாடான 'நேரம் முடிந்தது' என அனுப்பப்பட்டது). நிராகரிக்கப்படும் வரை அவற்றை வைத்திருக்க 0 என அமைக்கவும். இயல்புநிலை: 0.", + "notify_channel": "நிலை, நேரலை முன்னேற்றம் மற்றும் நினைவூட்டல் அறிவிப்புகளுக்கான Android துணை ஆப்ஸ் அறிவிப்பு சேனல். சேனலின் ஒலியும் முக்கியத்துவமும் சேனலின் பெயரை முதன்முதலில் பயன்படுத்தும்போது துணை பயன்பாட்டில் உள்ளமைக்கப்படும் - WashData சேனல் பெயரை மட்டுமே அமைக்கிறது. பயன்பாட்டின் இயல்புநிலைக்கு காலியாக விடவும்.", + "notify_finish_channel": "முடிக்கப்பட்ட விழிப்பூட்டலுக்காக Android சேனலைப் பிரிக்கவும் (நினைவூட்டல் மற்றும் துணி துவைக்கும் நாக் ஆகியவற்றால் பயன்படுத்தப்படும்) அதன் சொந்த ஒலியைக் கொண்டிருக்கும். மேலே உள்ள சேனலை மீண்டும் பயன்படுத்த, காலியாக விடவும்.", + "notify_pre_complete_message": "சுழற்சி இயங்கும் போது தொடர்ச்சியான நேரடி முன்னேற்றப் புதுப்பிப்புகளின் உரை. {device}, {minutes}, {program} ஒதுக்கிடங்களை ஆதரிக்கிறது." + } + }, + "advanced_settings": { + "title": "மேம்பட்ட அமைப்புகள்", + "description": "டியூன் கண்டறிதல் வரம்புகள், கற்றல் மற்றும் மேம்பட்ட நடத்தை. பெரும்பாலான அமைப்புகளுக்கு இயல்புநிலை நன்றாக வேலை செய்கிறது.", + "sections": { + "suggestions_section": { + "name": "பரிந்துரைக்கப்பட்ட அமைப்புகள்", + "description": "{suggestions_count} கற்றறிந்த பரிந்துரைகள் உள்ளன. சேமிப்பதற்கு முன் முன்மொழியப்பட்ட மாற்றங்களை மதிப்பாய்வு செய்ய 'பரிந்துரைக்கப்பட்ட மதிப்புகளைப் பயன்படுத்தவும்' என்பதைத் தேர்வு செய்யவும்.", + "data": { + "apply_suggestions": "பரிந்துரைக்கப்பட்ட மதிப்புகளைப் பயன்படுத்தவும்" + }, + "data_description": { + "apply_suggestions": "கற்றல் அல்காரிதத்திலிருந்து பரிந்துரைக்கப்பட்ட மதிப்புகளுக்கான மதிப்பாய்வுப் படியைத் திறக்க இந்தப் பெட்டியைத் தேர்வு செய்யவும். எதுவும் தானாகவே சேமிக்கப்படாது.\n\nபரிந்துரைக்கப்படுகிறது (கற்றலில் இருந்து):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "கண்டறிதல் & சக்தி வரம்புகள்", + "description": "சாதனம் எப்போது இயங்குகிறது அல்லது அணைந்துள்ளது எனக் கருதப்படுகிறது, ஆற்றல் வாயில்கள், மற்றும் நிலை மாறுதல்களின் நேரம்.", + "data": { + "start_duration_threshold": "டிபவுன்ஸ் காலத்தைத் தொடங்கு (கள்)", + "start_energy_threshold": "ஸ்டார்ட் எனர்ஜி கேட் (Wh)", + "completion_min_seconds": "நிறைவு குறைந்தபட்ச இயக்க நேரம் (வினாடிகள்)", + "start_threshold_w": "தொடக்க வரம்பு (W)", + "stop_threshold_w": "ஸ்டாப் த்ரெஷோல்ட் (W)", + "end_energy_threshold": "எண்ட் எனர்ஜி கேட் (Wh)", + "running_dead_zone": "ரன்னிங் டெட் சோன் (வினாடிகள்)", + "end_repeat_count": "முடிவு மீண்டும் எண்ணிக்கை", + "min_off_gap": "சுழற்சிகளுக்கு இடையே குறைந்தபட்ச இடைவெளி (கள்)", + "sampling_interval": "மாதிரி இடைவெளி (கள்)" + }, + "data_description": { + "start_duration_threshold": "இந்த கால அளவை விட (வினாடிகள்) குறுகிய பவர் ஸ்பைக்குகளை புறக்கணிக்கவும். உண்மையான சுழற்சி தொடங்கும் முன் பூட் ஸ்பைக்குகளை வடிகட்டுகிறது (எ.கா., 2விக்கு 60W). இயல்புநிலை: 5வி.", + "start_energy_threshold": "உறுதிப்படுத்தப்படுவதற்கு START கட்டத்தில் சுழற்சியானது குறைந்தபட்சம் இவ்வளவு ஆற்றலை (Wh) திரட்ட வேண்டும். இயல்புநிலை: 0.005 Wh.", + "completion_min_seconds": "இதைவிடக் குறைவான சுழற்சிகள் இயற்கையாக முடிந்தாலும் 'குறுக்கீடு' எனக் குறிக்கப்படும். இயல்புநிலை: 600கள்.", + "start_threshold_w": "ஒரு சுழற்சியைத் தொடங்க சக்தி இந்த வரம்புக்கு மேல் உயர வேண்டும். உங்கள் காத்திருப்பு சக்தியை விட அதிகமாக அமைக்கவும். இயல்புநிலை: min_power + 1 W.", + "stop_threshold_w": "ஒரு சுழற்சியை முடிக்க சக்தி இந்த வரம்புக்கு கீழே விழ வேண்டும். தவறான முனைகளைத் தூண்டும் சத்தத்தைத் தவிர்க்க பூஜ்ஜியத்திற்கு சற்று மேலே அமைக்கவும். இயல்புநிலை: 0.6 * min_power.", + "end_energy_threshold": "கடைசி ஆஃப்-டேலே சாளரத்தில் ஆற்றல் இந்த மதிப்பிற்கு (Wh) கீழே இருந்தால் மட்டுமே சுழற்சி முடிவடையும். சக்தி பூஜ்ஜியத்திற்கு அருகில் ஏற்ற இறக்கம் ஏற்பட்டால் தவறான முடிவுகளைத் தடுக்கிறது. இயல்புநிலை: 0.05 Wh.", + "running_dead_zone": "சுழற்சி தொடங்கிய பிறகு, ஆரம்ப ஸ்பின்-அப் கட்டத்தில் தவறான முடிவைக் கண்டறிவதைத் தடுக்க, பல வினாடிகளுக்கு பவர் டிப்ஸைப் புறக்கணிக்கவும். முடக்க 0 என அமைக்கவும். இயல்புநிலை: 0வி.", + "end_repeat_count": "சுழற்சியை முடிக்கும் முன், இறுதி நிலையை (ஆஃப்_டிலேக்கான வரம்பிற்குக் கீழே உள்ள சக்தி) தொடர்ச்சியாகச் சந்திக்க வேண்டிய எண்ணிக்கை. அதிக மதிப்புகள் இடைநிறுத்தங்களின் போது தவறான முடிவுகளைத் தடுக்கின்றன. இயல்புநிலை: 1.", + "min_off_gap": "முந்தையது முடிவடைந்த பல வினாடிகளுக்குள் ஒரு சுழற்சி தொடங்கினால், அவை ஒரே சுழற்சியில் இணைக்கப்படும்.", + "sampling_interval": "வினாடிகளில் குறைந்தபட்ச மாதிரி இடைவெளி. இந்த விகிதத்தை விட வேகமான புதுப்பிப்புகள் புறக்கணிக்கப்படும். இயல்புநிலை: 30கள் (சலவை இயந்திரங்கள், வாஷர்-ட்ரையர்கள் மற்றும் பாத்திரங்களைக் கழுவுபவர்களுக்கு 2வி)." + } + }, + "matching_section": { + "name": "சுயவிவர பொருத்தம் மற்றும் கற்றல்", + "description": "WashData இயங்கிக் கொண்டிருக்கும் சுழற்சிகளை கற்றறிந்த சுயவிவரங்களுடன் எவ்வளவு தீவிரமாக பொருத்துகிறது மற்றும் எப்போது பின்னூட்டம் கேட்கிறது.", + "data": { + "profile_match_min_duration_ratio": "சுயவிவரப் பொருத்தம் குறைந்தபட்ச கால விகிதம் (0.1-1.0)", + "profile_match_interval": "சுயவிவரப் பொருத்த இடைவெளி (வினாடிகள்)", + "profile_match_threshold": "சுயவிவரப் பொருத்த வரம்பு", + "profile_unmatch_threshold": "சுயவிவரம் பொருந்தாத வரம்பு", + "auto_label_confidence": "தன்னியக்க லேபிள் நம்பிக்கை (0-1)", + "learning_confidence": "கருத்துக் கோரிக்கை நம்பிக்கை (0-1)", + "suppress_feedback_notifications": "கருத்து அறிவிப்புகளை அடக்கவும்", + "duration_tolerance": "கால சகிப்புத்தன்மை (0-0.5)", + "smoothing_window": "மென்மையான சாளரம் (மாதிரிகள்)", + "profile_duration_tolerance": "சுயவிவரப் பொருத்த கால சகிப்புத்தன்மை (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "சுயவிவரப் பொருத்தத்திற்கான குறைந்தபட்ச கால விகிதம். இயங்கும் சுழற்சியானது பொருந்த, சுயவிவரக் காலத்தின் இந்தப் பகுதியாவது இருக்க வேண்டும். கீழ் = முந்தைய பொருத்தம். இயல்புநிலை: 0.50 (50%).", + "profile_match_interval": "சுழற்சியின் போது சுயவிவரப் பொருத்தத்தை எவ்வளவு அடிக்கடி (வினாடிகளில்) முயற்சிக்க வேண்டும். குறைந்த மதிப்புகள் வேகமாக கண்டறியும் ஆனால் அதிக CPU ஐப் பயன்படுத்துகின்றன. இயல்புநிலை: 300கள் (5 நிமிடங்கள்).", + "profile_match_threshold": "குறைந்தபட்ச DTW ஒற்றுமை மதிப்பெண் (0.0–1.0) ஒரு சுயவிவரத்தை பொருத்தமாக கருத வேண்டும். உயர் = கடுமையான பொருத்தம், குறைவான தவறான நேர்மறை. இயல்புநிலை: 0.4.", + "profile_unmatch_threshold": "DTW ஒற்றுமை மதிப்பெண் (0.0–1.0) அதற்குக் கீழே முன்பு பொருந்திய சுயவிவரம் நிராகரிக்கப்பட்டது. மினுமினுப்பதைத் தடுக்க போட்டி வரம்பை விட குறைவாக இருக்க வேண்டும். இயல்புநிலை: 0.35.", + "auto_label_confidence": "முடிந்ததும் (0.0–1.0) இந்த நம்பிக்கையில் அல்லது அதற்கு மேல் சுழற்சிகளை தானாகவே லேபிளிடுங்கள்.", + "learning_confidence": "ஒரு நிகழ்வின் மூலம் பயனர் சரிபார்ப்பைக் கோருவதற்கான குறைந்தபட்ச நம்பிக்கை (0.0–1.0).", + "suppress_feedback_notifications": "இயக்கப்பட்டிருக்கும் போது, ​​WashData உள்நாட்டில் தொடர்ந்து கண்காணித்து கருத்துக்களைக் கோரும், ஆனால் சுழற்சிகளை உறுதிப்படுத்துமாறு உங்களிடம் தொடர்ந்து அறிவிப்புகளைக் காட்டாது. சுழற்சிகள் எப்பொழுதும் சரியாகக் கண்டறியப்பட்டு, தூண்டுதல்கள் கவனத்தைத் திசைதிருப்பும் போது பயனுள்ளதாக இருக்கும்.", + "duration_tolerance": "ஒரு ஓட்டத்தின் போது மீதமுள்ள நேர மதிப்பீடுகளுக்கான சகிப்புத்தன்மை (0.0-0.5 0-50% உடன் ஒத்துள்ளது).", + "smoothing_window": "நகரும்-சராசரி ஸ்மூத்திங்கிற்குப் பயன்படுத்தப்படும் சமீபத்திய பவர் ரீடிங்குகளின் எண்ணிக்கை.", + "profile_duration_tolerance": "மொத்த கால மாறுபாட்டிற்கு (0.0–0.5) சுயவிவரப் பொருத்தத்தால் பயன்படுத்தப்படும் சகிப்புத்தன்மை. இயல்புநிலை நடத்தை: ±25%." + } + }, + "timing_section": { + "name": "நேரம், பராமரிப்பு & பிழைத்திருத்தம்", + "description": "வாட்ச்டாக், முன்னேற்ற மீட்டமைப்பு, தானியங்கு பராமரிப்பு, மற்றும் பிழைத்திருத்த உறுப்புகள் காட்டுதல்.", + "data": { + "watchdog_interval": "வாட்ச்டாக் இடைவெளி (வினாடிகள்)", + "no_update_active_timeout": "புதுப்பிப்பு இல்லை (கள்)", + "progress_reset_delay": "முன்னேற்றத்தை மீட்டமைத்தல் தாமதம் (வினாடிகள்)", + "auto_maintenance": "தானியங்கு பராமரிப்பை இயக்கு", + "expose_debug_entities": "பிழைத்திருத்த உறுப்புகளை வெளிப்படுத்தவும்", + "save_debug_traces": "பிழைத்திருத்த தடங்களைச் சேமிக்கவும்" + }, + "data_description": { + "watchdog_interval": "இயங்கும் போது கண்காணிப்புச் சோதனைகளுக்கு இடையில் வினாடிகள். இயல்புநிலை: 5வி. எச்சரிக்கை: தவறான நிறுத்தங்களைத் தவிர்க்க, இது உங்கள் சென்சாரின் புதுப்பிப்பு இடைவெளியை விட அதிகமாக இருப்பதை உறுதிசெய்யவும் (எ.கா., ஒவ்வொரு 60 வினாடிகளிலும் சென்சார் புதுப்பிக்கப்பட்டால், 65வி+ பயன்படுத்தவும்).", + "no_update_active_timeout": "வெளியீட்டின்-மாற்ற உணரிகளுக்கு: இத்தனை வினாடிகளுக்கு எந்த புதுப்பிப்புகளும் வரவில்லை என்றால் மட்டுமே, ஆக்டிவ் சுழற்சியை கட்டாயப்படுத்தி முடிக்கவும். குறைந்த பவர் நிறைவு இன்னும் ஆஃப் தாமதத்தைப் பயன்படுத்துகிறது.", + "progress_reset_delay": "ஒரு சுழற்சி முடிந்ததும் (100%), முன்னேற்றத்தை 0% க்கு மீட்டமைக்கும் முன், செயலற்ற நிலையில் பல வினாடிகள் காத்திருக்கவும். இயல்புநிலை: 300கள்.", + "auto_maintenance": "தானாகப் பராமரிப்பை இயக்கு (மாதிரிகளைச் சரிசெய்தல், துண்டுகளை ஒன்றிணைத்தல்).", + "expose_debug_entities": "பிழைத்திருத்தத்திற்கான மேம்பட்ட உணரிகளைக் (நம்பிக்கை, கட்டம், தெளிவின்மை) காட்டு.", + "save_debug_traces": "வரலாற்றில் விரிவான தரவரிசை மற்றும் பவர் டிரேஸ் தரவைச் சேமிக்கவும் (சேமிப்புப் பயன்பாட்டை அதிகரிக்கிறது)." + } + }, + "anti_wrinkle_section": { + "name": "சுருக்க எதிர்ப்பு கவசம் (உலர்த்தி மட்டும்)", + "description": "பேய் சுழற்சிகளைத் தடுக்க சுழற்சி முடிந்த பின் டிரம் சுழற்சிகளைப் புறக்கணிக்கவும். உலர்த்தி / வாஷர்-டிரையர் மட்டும்.", + "data": { + "anti_wrinkle_enabled": "சுருக்க எதிர்ப்பு கவசம் (உலர்த்தி மட்டும்)", + "anti_wrinkle_max_power": "ஆண்டி ரிங்கிள் மேக்ஸ் பவர் (W)", + "anti_wrinkle_max_duration": "ஆண்டி ரிங்கிள் மேக்ஸ் கால அளவு (கள்)", + "anti_wrinkle_exit_power": "ஆண்டி ரிங்கிள் எக்சிட் பவர் (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "பேய் சுழற்சிகளைத் தடுக்க (டிரையர்/வாஷர்-ட்ரையர் மட்டும்) ஒரு சுழற்சி முடிந்ததும் குறுகிய குறைந்த சக்தி டிரம் சுழற்சிகளைப் புறக்கணிக்கவும்.", + "anti_wrinkle_max_power": "இந்த சக்தியில் அல்லது அதற்குக் கீழே உள்ள வெடிப்புகள் புதிய சுழற்சிகளுக்குப் பதிலாக சுருக்க எதிர்ப்பு சுழற்சிகளாகக் கருதப்படுகின்றன. சுருக்க எதிர்ப்பு கவசம் இயக்கப்பட்டால் மட்டுமே பொருந்தும்.", + "anti_wrinkle_max_duration": "சுருக்க எதிர்ப்பு சுழற்சியாகக் கருதுவதற்கு அதிகபட்ச வெடிப்பு நீளம். சுருக்க எதிர்ப்பு கவசம் இயக்கப்பட்டால் மட்டுமே பொருந்தும்.", + "anti_wrinkle_exit_power": "சக்தி இந்த நிலைக்குக் கீழே குறைந்தால், சுருக்க எதிர்ப்புகளிலிருந்து உடனடியாக வெளியேறவும் (உண்மையானது). சுருக்க எதிர்ப்பு கவசம் இயக்கப்பட்டால் மட்டுமே பொருந்தும்." + } + }, + "delay_start_section": { + "name": "தாமதமான தொடக்கக் கண்டறிதல்", + "description": "சுழற்சி உண்மையில் தொடங்கும் வரை நீடித்த குறைந்த சக்தி காத்திருப்பை 'ஸ்டார்ட் செய்ய காத்திருக்கிறது' எனக் கருதவும்.", + "data": { + "delay_start_detect_enabled": "தாமதமான தொடக்கக் கண்டறிதலை இயக்கு", + "delay_confirm_seconds": "தாமதமான தொடக்கம்: காத்திருப்பு உறுதிப்படுத்தும் நேரம் (வி)", + "delay_timeout_hours": "தாமதமான தொடக்கம்: அதிகபட்ச காத்திருப்பு நேரம் (ம)" + }, + "data_description": { + "delay_start_detect_enabled": "இயக்கப்படும் போது, ​​ஒரு சுருக்கமான குறைந்த-பவர் வடிகால் ஸ்பைக்கைத் தொடர்ந்து நிலையான காத்திருப்பு சக்தி தாமதமான தொடக்கமாகக் கண்டறியப்பட்டது. தாமதத்தின் போது 'ஸ்டார்ட் செய்ய காத்திருக்கிறது' என்பதை ஸ்டேட் சென்சார் காட்டுகிறது. சுழற்சி உண்மையில் தொடங்கும் வரை நிரல் நேரம் அல்லது முன்னேற்றம் கண்காணிக்கப்படாது. வடிகால் ஸ்பைக் சக்திக்கு மேலே அமைக்க start_threshold_w தேவை.", + "delay_confirm_seconds": "WashData 'ஸ்டார்ட் செய்ய காத்திருக்கிறது' நிலைக்கு செல்லும் முன், பல வினாடிகள் சக்தி ஸ்டாப் த்ரெஷோல்ட் (W) மற்றும் ஸ்டார்ட் த்ரெஷோல்ட் (W) இடையில் இருக்க வேண்டும். இது மெனு வழிசெலுத்தலின் குறுகிய உச்சங்களை வடிகட்டுகிறது. இயல்புநிலை: 60 வி.", + "delay_timeout_hours": "பாதுகாப்பு நேரம் முடிந்தது: இந்த பல மணிநேரங்களுக்குப் பிறகும் இயந்திரம் 'தொடக்க காத்திருக்கிறது' எனில், WashData முடக்கத்தில் மீட்டமைக்கப்படும். இயல்புநிலை: 8 மணி." + } + }, + "external_triggers_section": { + "name": "வெளிப்புற தூண்டல்கள், கதவு & இடைநிறுத்தம்", + "description": "விருப்பமான வெளிப்புற முடிவு-தூண்டல் சென்சார், இடைநிறுத்தம்/சுத்தம் கண்டறிய கதவு சென்சார், மற்றும் இடைநிறுத்தும்போது மின்தடுக்கும் ஸ்விட்ச்.", + "data": { + "external_end_trigger_enabled": "வெளிப்புற சுழற்சி முடிவு தூண்டுதலை இயக்கு", + "external_end_trigger": "வெளிப்புற சுழற்சி முடிவு சென்சார்", + "external_end_trigger_inverted": "தூண்டுதல் தர்க்கத்தைத் தலைகீழாக மாற்றவும் (Trigger on OFF)", + "door_sensor_entity": "கதவு சென்சார்", + "pause_cuts_power": "இடைநிறுத்தப்படும் போது பவரை வெட்டுங்கள்", + "switch_entity": "நிறுவனத்தை மாற்றவும் (பவர் கட் இடைநிறுத்தப்படுவதற்கு)", + "notify_unload_delay_minutes": "சலவை காத்திருப்பு அறிவிப்பு தாமதம் (நிமிடம்)" + }, + "data_description": { + "external_end_trigger_enabled": "சுழற்சியை நிறைவு செய்ய வெளிப்புற பைனரி சென்சார் கண்காணிப்பை இயக்கவும்.", + "external_end_trigger": "பைனரி சென்சார் உட்பொருளைத் தேர்ந்தெடுக்கவும். இந்த சென்சார் தூண்டும் போது, ​​தற்போதைய சுழற்சி 'முடிந்தது' என்ற நிலையில் முடிவடையும்.", + "external_end_trigger_inverted": "இயல்பாக, சென்சார் இயக்கப்படும்போது தூண்டுதல் எரிகிறது. அதற்குப் பதிலாக சென்சார் அணைக்கப்படும்போது சுட இந்த பெட்டியை சரிபார்க்கவும்.", + "door_sensor_entity": "இயந்திர கதவுக்கான விருப்ப பைனரி சென்சார் (ஆன் = திறந்த, ஆஃப் = மூடப்பட்டது). செயலில் உள்ள சுழற்சியின் போது கதவு திறக்கும் போது, ​​WashData வேண்டுமென்றே இடைநிறுத்தப்பட்ட சுழற்சியை உறுதி செய்யும். சுழற்சி முடிந்ததும், கதவு மூடியிருந்தால், கதவு திறக்கும் வரை 'சுத்தம்' நிலை மாறும். குறிப்பு: கதவை மூடுவது இடைநிறுத்தப்பட்ட சுழற்சியை தானாக மீண்டும் தொடங்காது - ரெஸ்யூம் சைக்கிள் பட்டன் அல்லது சேவையின் மூலம் கைமுறையாக ரெஸ்யூம் தூண்டப்பட வேண்டும்.", + "pause_cuts_power": "இடைநிறுத்தம் சுழற்சி பொத்தான் அல்லது சேவை வழியாக இடைநிறுத்தப்படும் போது, ​​உள்ளமைக்கப்பட்ட சுவிட்ச் உட்பொருளையும் அணைக்கவும். இந்தச் சாதனத்தில் சுவிட்ச் உட்பொருளை உள்ளமைத்தால் மட்டுமே நடைமுறைக்கு வரும்.", + "switch_entity": "இடைநிறுத்தப்படும்போது அல்லது மீண்டும் தொடங்கும் போது மாறுவதற்கான சுவிட்ச் நிறுவனம். 'இடைநிறுத்தும்போது மின்வெட்டு' இயக்கப்பட்டிருக்கும் போது தேவை.", + "notify_unload_delay_minutes": "சுழற்சி முடிந்த பிறகும், பல நிமிடங்களுக்கு கதவு திறக்கப்படாமல் இருந்த பிறகு, பினிஷ் அறிவிப்பு சேனல் வழியாக அறிவிப்பை அனுப்பவும் (டோர் சென்சார் தேவை). முடக்க 0 என அமைக்கவும். இயல்புநிலை: 60 நிமிடம்." + } + }, + "pump_section": { + "name": "பம்ப் கண்காணிப்பு", + "description": "பம்ப் சாதன வகைக்கு மட்டும். பம்ப் சுழற்சி இந்த நேரத்தை மீறி நீடித்தால் ஒரு நிகழ்வைத் தூண்டும்.", + "data": { + "pump_stuck_duration": "பம்ப் ஸ்டக் அலர்ட் த்ரெஷோல்ட் (கள்) (பம்ப் மட்டும்)" + }, + "data_description": { + "pump_stuck_duration": "இந்த பல வினாடிகளுக்குப் பிறகும் ஒரு பம்ப் சுழற்சி இயங்கினால், ha_washdata_pump_stuck நிகழ்வு ஒரு முறை சுடப்படும். நெரிசலான மோட்டாரைக் கண்டறிய இதைப் பயன்படுத்தவும் (வழக்கமான சம்ப் பம்ப் ரன் 60 வினாடிகளுக்கு கீழ் இருக்கும்). இயல்புநிலை: 1800 வி (30 நிமிடம்). பம்ப் சாதன வகை மட்டுமே." + } + }, + "device_link_section": { + "name": "சாதன இணைப்பு", + "description": "விருப்பமாக இந்த WashData சாதனத்தை ஏற்கனவே உள்ள Home Assistant சாதனத்துடன் இணைக்கவும் (உதாரணமாக ஸ்மார்ட் பிளக் அல்லது சாதனமே). WashData சாதனம் அந்தச் சாதனத்தின் மூலம் \"இணைக்கப்பட்டது\" எனக் காட்டப்படும். WashDataவை ஒரு தனி சாதனமாக வைத்திருக்க, காலியாக விடவும்.", + "data": { + "linked_device": "இணைக்கப்பட்ட சாதனம்" + }, + "data_description": { + "linked_device": "WashData உடன் இணைக்க சாதனத்தைத் தேர்ந்தெடுக்கவும். இது சாதனப் பதிவேட்டில் 'இணைக்கப்பட்ட வழியாக' குறிப்பைச் சேர்க்கிறது; WashData அதன் சொந்த சாதன அட்டை மற்றும் நிறுவனங்களை வைத்திருக்கிறது. இணைப்பை அகற்ற தேர்வை அழிக்கவும்." + } + } + } + }, + "diagnostics": { + "title": "நோய் கண்டறிதல் & பராமரிப்பு", + "description": "துண்டு துண்டான சுழற்சிகளை ஒன்றிணைத்தல் அல்லது சேமிக்கப்பட்ட தரவை நகர்த்துதல் போன்ற பராமரிப்பு நடவடிக்கைகளை இயக்கவும்.\n\n**சேமிப்பு பயன்பாடு**\n\n- கோப்பு அளவு: {file_size_kb} KB\n- சுழற்சிகள்: {cycle_count}\n- சுயவிவரங்கள்: {profile_count}\n- பிழைத்திருத்த தடயங்கள்: {debug_count}", + "menu_options": { + "reprocess_history": "பராமரிப்பு: தரவை மறு செயலாக்கம் & மேம்படுத்துதல்", + "clear_debug_data": "பிழைத்திருத்தத் தரவை அழிக்கவும் (இடத்தைக் காலியாக்கவும்)", + "wipe_history": "இந்தச் சாதனத்திற்கான எல்லா தரவையும் அழிக்கவும் (மாற்ற முடியாதது)", + "export_import": "அமைப்புகளுடன் JSON ஐ ஏற்றுமதி/இறக்குமதி (நகல்/ஒட்டு)", + "menu_back": "← பின்" + } + }, + "clear_debug_data": { + "title": "பிழைத்திருத்தத் தரவை அழிக்கவும்", + "description": "சேமிக்கப்பட்ட அனைத்து பிழைத்திருத்த தடயங்களையும் நிச்சயமாக நீக்க விரும்புகிறீர்களா? இது இடத்தைக் காலியாக்கும் ஆனால் கடந்த காலச் சுழற்சிகளிலிருந்து விரிவான தரவரிசைத் தகவலை அகற்றும்." + }, + "manage_cycles": { + "title": "சுழற்சிகளை நிர்வகிக்கவும்", + "description": "சமீபத்திய சுழற்சிகள்:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "பழைய சைக்கிள்களைத் தானாக லேபிளிடுங்கள்", + "select_cycle_to_label": "லேபிள் குறிப்பிட்ட சுழற்சி", + "select_cycle_to_delete": "சுழற்சியை நீக்கு", + "interactive_editor": "இன்டராக்டிவ் எடிட்டரை ஒன்றிணைத்தல்/பிரித்தல்", + "trim_cycle_select": "சுழற்சி தரவை ஒழுங்கமைக்கவும்", + "menu_back": "← பின்" + } + }, + "manage_cycles_empty": { + "title": "சுழற்சிகளை நிர்வகிக்கவும்", + "description": "இதுவரை எந்த சுழற்சிகளும் பதிவு செய்யப்படவில்லை.", + "menu_options": { + "auto_label_cycles": "பழைய சைக்கிள்களைத் தானாக லேபிளிடுங்கள்", + "menu_back": "← பின்" + } + }, + "manage_profiles": { + "title": "சுயவிவரங்களை நிர்வகிக்கவும்", + "description": "சுயவிவரச் சுருக்கம்:\n{profile_summary}", + "menu_options": { + "create_profile": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "edit_profile": "சுயவிவரத்தைத் திருத்து/மறுபெயரிடு", + "delete_profile_select": "சுயவிவரத்தை நீக்கு", + "profile_stats": "சுயவிவர புள்ளிவிவரங்கள்", + "cleanup_profile": "வரலாற்றை சுத்தம் செய்யவும் - வரைபடம் & நீக்கு", + "assign_profile_phases_select": "கட்ட வரம்புகளை ஒதுக்கவும்", + "menu_back": "← பின்" + } + }, + "manage_profiles_empty": { + "title": "சுயவிவரங்களை நிர்வகிக்கவும்", + "description": "இதுவரை சுயவிவரங்கள் எதுவும் உருவாக்கப்படவில்லை.", + "menu_options": { + "create_profile": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "menu_back": "← பின்" + } + }, + "manage_phase_catalog": { + "title": "கட்ட அட்டவணையை நிர்வகிக்கவும்", + "description": "கட்ட அட்டவணை (இந்தச் சாதனத்திற்கான இயல்புநிலை + தனிப்பயன் கட்டங்கள்):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "புதிய கட்டத்தை உருவாக்கவும்", + "phase_catalog_edit_select": "திருத்த கட்டம்", + "phase_catalog_delete": "கட்டத்தை நீக்கு", + "menu_back": "← பின்" + } + }, + "phase_catalog_create": { + "title": "தனிப்பயன் கட்டத்தை உருவாக்கவும்", + "description": "தனிப்பயன் கட்டப் பெயர் மற்றும் விளக்கத்தை உருவாக்கி, அது எந்த வகையைச் சார்ந்தது என்பதைத் தேர்ந்தெடுக்கவும்.", + "data": { + "target_device_type": "இலக்கு சாதன வகை", + "phase_name": "கட்டப் பெயர்", + "phase_description": "கட்ட விளக்கம்" + } + }, + "phase_catalog_edit_select": { + "title": "தனிப்பயன் கட்டத்தைத் திருத்தவும்", + "description": "திருத்துவதற்கான தனிப்பயன் கட்டத்தைத் தேர்ந்தெடுக்கவும்.", + "data": { + "phase_name": "தனிப்பயன் கட்டம்" + } + }, + "phase_catalog_edit": { + "title": "தனிப்பயன் கட்டத்தைத் திருத்தவும்", + "description": "திருத்தும் கட்டம்: {phase_name}", + "data": { + "phase_name": "கட்டப் பெயர்", + "phase_description": "கட்ட விளக்கம்" + } + }, + "phase_catalog_delete": { + "title": "தனிப்பயன் கட்டத்தை நீக்கு", + "description": "நீக்க தனிப்பயன் கட்டத்தைத் தேர்ந்தெடுக்கவும். இந்தக் கட்டத்தைப் பயன்படுத்தி ஒதுக்கப்பட்ட வரம்புகள் அனைத்தும் அகற்றப்படும்.", + "data": { + "phase_name": "தனிப்பயன் கட்டம்" + } + }, + "assign_profile_phases": { + "title": "கட்ட வரம்புகளை ஒதுக்கவும்", + "description": "சுயவிவரத்திற்கான கட்ட முன்னோட்டம்: **{profile_name}**\n\n{timeline_svg}\n\n**தற்போதைய வரம்புகள்:**\n{current_ranges}\n\nவரம்புகளைச் சேர்க்க, திருத்த, நீக்க அல்லது சேமிக்க கீழே உள்ள செயல்களைப் பயன்படுத்தவும்.", + "menu_options": { + "assign_profile_phases_add": "கட்ட வரம்பைச் சேர்க்கவும்", + "assign_profile_phases_edit_select": "கட்ட வரம்பைத் திருத்தவும்", + "assign_profile_phases_delete": "கட்ட வரம்பை நீக்கு", + "phase_ranges_clear": "அனைத்து வரம்புகளையும் அழி", + "assign_profile_phases_auto_detect": "தானாக கண்டறிதல் கட்டங்கள்", + "phase_ranges_save": "சேமித்து திரும்பவும்", + "menu_back": "← பின்" + } + }, + "assign_profile_phases_add": { + "title": "கட்ட வரம்பைச் சேர்க்கவும்", + "description": "தற்போதைய வரைவில் ஒரு கட்ட வரம்பைச் சேர்க்கவும்.", + "data": { + "phase_name": "கட்டம்", + "start_min": "தொடக்க நிமிடம்", + "end_min": "முடிவு நிமிடம்" + } + }, + "assign_profile_phases_edit_select": { + "title": "கட்ட வரம்பைத் திருத்தவும்", + "description": "திருத்துவதற்கான வரம்பைத் தேர்ந்தெடுக்கவும்.", + "data": { + "range_index": "கட்ட வரம்பு" + } + }, + "assign_profile_phases_edit": { + "title": "கட்ட வரம்பைத் திருத்தவும்", + "description": "தேர்ந்தெடுக்கப்பட்ட கட்ட வரம்பைப் புதுப்பிக்கவும்.", + "data": { + "phase_name": "கட்டம்", + "start_min": "தொடக்க நிமிடம்", + "end_min": "முடிவு நிமிடம்" + } + }, + "assign_profile_phases_delete": { + "title": "கட்ட வரம்பை நீக்கு", + "description": "வரைவில் இருந்து அகற்ற வரம்பைத் தேர்ந்தெடுக்கவும்.", + "data": { + "range_index": "கட்ட வரம்பு" + } + }, + "assign_profile_phases_auto_detect": { + "title": "தானாக கண்டறியப்பட்ட கட்டங்கள்", + "description": "சுயவிவரம்: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} கட்டம்(கள்) தானாக கண்டறியப்பட்டது.** கீழே ஒரு செயலைத் தேர்வு செய்யவும்.", + "data": { + "action": "செயல்" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "பெயர் கண்டறியப்பட்ட கட்டங்கள்", + "description": "சுயவிவரம்: **{profile_name}**\n\n**{detected_count} கட்டம்(கள்) கண்டறியப்பட்டது:**\n{ranges_summary}\n\nஒவ்வொரு கட்டத்திற்கும் ஒரு பெயரை ஒதுக்கவும்." + }, + "assign_profile_phases_select": { + "title": "கட்ட வரம்புகளை ஒதுக்கவும்", + "description": "கட்ட வரம்புகளை உள்ளமைக்க சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்.", + "data": { + "profile": "சுயவிவரம்" + } + }, + "profile_stats": { + "title": "சுயவிவர புள்ளிவிவரங்கள்", + "description": "{stats_table}" + }, + "create_profile": { + "title": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "description": "கைமுறையாக அல்லது கடந்த சுழற்சியில் இருந்து புதிய சுயவிவரத்தை உருவாக்கவும்.\n\nசுயவிவரப் பெயர் எடுத்துக்காட்டுகள்: 'டெலிகேட்ஸ்', 'ஹெவி டியூட்டி', 'குயிக் வாஷ்'", + "data": { + "profile_name": "சுயவிவரப் பெயர்", + "reference_cycle": "குறிப்பு சுழற்சி (விரும்பினால்)", + "manual_duration": "கைமுறை கால அளவு (நிமிடங்கள்)" + } + }, + "edit_profile": { + "title": "சுயவிவரத்தைத் திருத்து", + "description": "மறுபெயரிட சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்", + "data": { + "profile": "சுயவிவரம்" + } + }, + "rename_profile": { + "title": "சுயவிவர விவரங்களைத் திருத்தவும்", + "description": "தற்போதைய சுயவிவரம்: {current_name}", + "data": { + "new_name": "சுயவிவரப் பெயர்", + "manual_duration": "கைமுறை கால அளவு (நிமிடங்கள்)" + } + }, + "delete_profile_select": { + "title": "சுயவிவரத்தை நீக்கு", + "description": "நீக்க ஒரு சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்", + "data": { + "profile": "சுயவிவரம்" + } + }, + "delete_profile_confirm": { + "title": "சுயவிவரத்தை நீக்குவதை உறுதிப்படுத்தவும்", + "description": "⚠️ இது சுயவிவரத்தை நிரந்தரமாக நீக்கும்: {profile_name}", + "data": { + "unlabel_cycles": "இந்த சுயவிவரத்தைப் பயன்படுத்தி சுழற்சிகளிலிருந்து லேபிளை அகற்றவும்" + } + }, + "auto_label_cycles": { + "title": "பழைய சைக்கிள்களைத் தானாக லேபிளிடுங்கள்", + "description": "{total_count} மொத்த சுழற்சிகள் உள்ளன. சுயவிவரங்கள்: {profiles}", + "data": { + "confidence_threshold": "குறைந்தபட்ச நம்பிக்கை (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "லேபிளுக்கு சுழற்சியைத் தேர்ந்தெடுக்கவும்", + "description": "✓ = முடிந்தது, ⚠ = மீண்டும் தொடங்கப்பட்டது, ✗ = குறுக்கிடப்பட்டது", + "data": { + "cycle_id": "சுழற்சி" + } + }, + "select_cycle_to_delete": { + "title": "சுழற்சியை நீக்கு", + "description": "⚠️ இது தேர்ந்தெடுக்கப்பட்ட சுழற்சியை நிரந்தரமாக நீக்கும்", + "data": { + "cycle_id": "நீக்குவதற்கான சுழற்சி" + } + }, + "label_cycle": { + "title": "லேபிள் சுழற்சி", + "description": "{cycle_info}", + "data": { + "profile_name": "சுயவிவரம்", + "new_profile_name": "புதிய சுயவிவரப் பெயர் (உருவாக்கினால்)" + } + }, + "post_process": { + "title": "செயல்முறை வரலாறு (இணைத்தல்/பிரித்தல்)", + "description": "துண்டு துண்டான ரன்களை ஒன்றிணைப்பதன் மூலமும், தவறாக இணைக்கப்பட்ட சுழற்சிகளை நேர சாளரத்திற்குள் பிரிப்பதன் மூலமும் சுழற்சி வரலாற்றை சுத்தம் செய்யவும். திரும்பிப் பார்க்க மணிநேர எண்ணிக்கையை உள்ளிடவும் (அனைவருக்கும் 999999 ஐப் பயன்படுத்தவும்).", + "data": { + "time_range": "லுக்பேக் சாளரம் (மணிநேரம்)", + "gap_seconds": "ஒன்றிணைத்தல்/பிளவு இடைவெளி (வினாடிகள்)" + }, + "data_description": { + "time_range": "துண்டு துண்டான சுழற்சிகளை ஒன்றிணைக்க ஸ்கேன் செய்ய வேண்டிய மணிநேரங்களின் எண்ணிக்கை. அனைத்து வரலாற்றுத் தரவையும் செயலாக்க 999999 ஐப் பயன்படுத்தவும்.", + "gap_seconds": "பிளவு மற்றும் ஒன்றிணைப்பை முடிவு செய்வதற்கான வரம்பு. இதை விட குறுகிய இடைவெளிகள் ஒன்றிணைக்கப்பட்டுள்ளன. இதை விட நீண்ட இடைவெளிகள் பிரிக்கப்படுகின்றன. தவறாக இணைக்கப்பட்ட சுழற்சியில் ஒரு பிரிவை கட்டாயப்படுத்த இதை குறைக்கவும்." + } + }, + "cleanup_profile": { + "title": "வரலாற்றை சுத்தம் செய்யவும் - சுயவிவரத்தைத் தேர்ந்தெடுக்கவும்", + "description": "காட்சிப்படுத்த ஒரு சுயவிவரத்தைத் தேர்ந்தெடுக்கவும். அவுட்லையர்களை அடையாளம் காண உதவும் வகையில், இந்த சுயவிவரத்திற்கான அனைத்து கடந்த சுழற்சிகளையும் காட்டும் வரைபடத்தை இது உருவாக்கும்.", + "data": { + "profile": "சுயவிவரம்" + } + }, + "cleanup_select": { + "title": "வரலாற்றை சுத்தம் செய்யவும் - வரைபடம் & நீக்கு", + "description": "![வரைபடம்]({graph_url})\n\n**காட்சிப்படுத்துதல் {profile_name}**\n\nவரைபடம் தனிப்பட்ட சுழற்சிகளை வண்ணக் கோடுகளாகக் காட்டுகிறது. வெளியே உள்ளவர்களை (குழுவிலிருந்து வெகு தொலைவில் உள்ள கோடுகள்) கண்டறிந்து அவற்றை நீக்க கீழே உள்ள பட்டியலில் அவற்றின் நிறத்தை பொருத்தவும்.\n\n** நிரந்தரமாக நீக்குவதற்கான சுழற்சிகளைத் தேர்ந்தெடுக்கவும்:**", + "data": { + "cycles_to_delete": "நீக்க வேண்டிய சுழற்சிகள் (பொருந்தும் வண்ணங்களைச் சரிபார்க்கவும்)" + } + }, + "interactive_editor": { + "title": "இன்டராக்டிவ் எடிட்டரை ஒன்றிணைத்தல்/பிரித்தல்", + "description": "உங்கள் சுழற்சி வரலாற்றில் செய்ய ஒரு கைமுறை செயலைத் தேர்ந்தெடுக்கவும்.", + "menu_options": { + "editor_split": "ஒரு சுழற்சியைப் பிரித்தல் (இடைவெளிகளைக் கண்டறிதல்)", + "editor_merge": "சுழற்சிகளை ஒன்றிணைக்கவும் (துண்டுகளை இணைக்கவும்)", + "editor_delete": "சுழற்சி(களை) நீக்கு", + "menu_back": "← பின்" + } + }, + "editor_select": { + "title": "சுழற்சிகளைத் தேர்ந்தெடுக்கவும்", + "description": "செயலாக்க சுழற்சியை(களை) தேர்ந்தெடுக்கவும்.\n\n{info_text}", + "data": { + "selected_cycles": "சுழற்சிகள்" + } + }, + "editor_configure": { + "title": "கட்டமைத்து விண்ணப்பிக்கவும்", + "description": "{preview_md}", + "data": { + "confirm_action": "செயல்", + "merged_profile": "முடிவு சுயவிவரம்", + "new_profile_name": "புதிய சுயவிவரப் பெயர்", + "confirm_commit": "ஆம், இந்த செயலை நான் உறுதிப்படுத்துகிறேன்", + "segment_0_profile": "பிரிவு 1 சுயவிவரம்", + "segment_1_profile": "பிரிவு 2 சுயவிவரம்", + "segment_2_profile": "பிரிவு 3 சுயவிவரம்", + "segment_3_profile": "பிரிவு 4 சுயவிவரம்", + "segment_4_profile": "பிரிவு 5 சுயவிவரம்", + "segment_5_profile": "பிரிவு 6 சுயவிவரம்", + "segment_6_profile": "பிரிவு 7 சுயவிவரம்", + "segment_7_profile": "பிரிவு 8 சுயவிவரம்", + "segment_8_profile": "பிரிவு 9 சுயவிவரம்", + "segment_9_profile": "பிரிவு 10 சுயவிவரம்" + } + }, + "editor_split_params": { + "title": "பிளவு கட்டமைப்பு", + "description": "இந்த சுழற்சியை பிரிப்பதற்கான உணர்திறனை சரிசெய்யவும். இதை விட நீண்ட இடைவெளி இருந்தால் அது பிளவை ஏற்படுத்தும்.", + "data": { + "split_mode": "பிளவு முறை" + } + }, + "editor_split_auto_params": { + "title": "தானியங்கு பிரிப்பு அமைப்பு", + "description": "இந்த சுழற்சியைப் பிரிக்கும் உணர்திறனை சரிசெய்யவும். இதைவிட நீளமான எந்த செயலற்ற இடைவெளியும் ஒரு பிரிப்பை ஏற்படுத்தும்.", + "data": { + "min_gap_seconds": "பிரிப்பு இடைவெளி வரம்பு (விநாடிகள்)" + } + }, + "editor_split_manual_params": { + "title": "கைமுறை பிரிப்பு நேரமுத்திரைகள்", + "description": "{preview_md}சுழற்சி சாளரம்: **{cycle_start_wallclock} → {cycle_end_wallclock}** அன்று {cycle_date}.\n\nஒரு அல்லது அதற்கு மேற்பட்ட பிரிப்பு நேரமுத்திரைகளை (`HH:MM` அல்லது `HH:MM:SS`) உள்ளிடவும், ஒவ்வொரு வரியிலும் ஒன்று. ஒவ்வொரு நேரமுத்திரையிலும் சுழற்சி வெட்டப்படும் — N நேரமுத்திரைகள் N+1 பகுதிகளை உருவாக்கும்.", + "data": { + "split_timestamps": "பிரிப்பு நேரமுத்திரைகள்" + } + }, + "reprocess_history": { + "title": "பராமரிப்பு: தரவை மறு செயலாக்கம் & மேம்படுத்துதல்", + "description": "வரலாற்று தரவுகளை ஆழமாக சுத்தம் செய்கிறது:\n1. பூஜ்ஜிய சக்தி அளவீடுகளை (அமைதி) குறைக்கிறது.\n2. சுழற்சி நேரம்/காலத்தை சரிசெய்கிறது.\n3. அனைத்து புள்ளிவிவர மாதிரிகளையும் (உறைகள்) மீண்டும் கணக்கிடுகிறது.\n\n**தரவுச் சிக்கல்களைச் சரிசெய்ய அல்லது புதிய அல்காரிதம்களைப் பயன்படுத்த இதை இயக்கவும்.**" + }, + "wipe_history": { + "title": "வரலாற்றைத் துடைக்கவும் (சோதனைக்கு மட்டும்)", + "description": "⚠️ இது இந்தச் சாதனத்தில் சேமிக்கப்பட்ட அனைத்து சுழற்சிகளையும் சுயவிவரங்களையும் நிரந்தரமாக நீக்கும். இதை செயல்தவிர்க்க முடியாது!" + }, + "export_import": { + "title": "JSON ஏற்றுமதி/இறக்குமதி", + "description": "இந்தச் சாதனத்திற்கான சுழற்சிகள், சுயவிவரங்கள் மற்றும் அமைப்புகளை நகலெடுக்கவும்/ஒட்டவும். கீழே ஏற்றுமதி செய்யவும் அல்லது இறக்குமதி செய்ய JSON ஒட்டவும்.", + "data": { + "mode": "செயல்", + "json_payload": "முழு கட்டமைப்பு JSON" + }, + "data_description": { + "mode": "தற்போதைய தரவு மற்றும் அமைப்புகளை நகலெடுக்க ஏற்றுமதி என்பதைத் தேர்ந்தெடுக்கவும் அல்லது மற்றொரு WashData சாதனத்திலிருந்து ஏற்றுமதி செய்யப்பட்ட தரவை ஒட்டுவதற்கு இறக்குமதி செய்யவும்.", + "json_payload": "ஏற்றுமதிக்கு: இந்த JSON ஐ நகலெடுக்கவும் (சுழற்சிகள், சுயவிவரங்கள் மற்றும் அனைத்து நுணுக்கமான அமைப்புகளும் அடங்கும்). இறக்குமதி செய்ய: WashData இலிருந்து ஏற்றுமதி செய்யப்பட்ட JSON ஐ ஒட்டவும்." + } + }, + "record_cycle": { + "title": "பதிவு சுழற்சி", + "description": "குறுக்கீடு இல்லாமல் சுத்தமான சுயவிவரத்தை உருவாக்க ஒரு சுழற்சியை கைமுறையாக பதிவு செய்யவும்.\n\nநிலை: **{status}**\nகால அளவு: {duration}கள்\nமாதிரிகள்: {samples}", + "menu_options": { + "record_refresh": "நிலையை புதுப்பிக்கவும்", + "record_stop": "பதிவை நிறுத்து (சேமி & செயலாக்கம்)", + "record_start": "புதிய பதிவைத் தொடங்கவும்", + "record_process": "கடைசிப் பதிவைச் செயலாக்கு (டிரிம் & சேவ்)", + "record_discard": "கடைசி பதிவை நிராகரிக்கவும்", + "menu_back": "← பின்" + } + }, + "record_process": { + "title": "செயல்முறை பதிவு", + "description": "![வரைபடம்]({graph_url})\n\n**வரைபடம் மூலப் பதிவைக் காட்டுகிறது.** நீலம் = வைக்கவும், சிவப்பு = முன்மொழியப்பட்ட டிரிம்.\n*வரைபடம் நிலையானது மற்றும் படிவ மாற்றங்களுடன் புதுப்பிக்கப்படாது.*\n\nபதிவு நிறுத்தப்பட்டது. டிரிம்கள் கண்டறியப்பட்ட மாதிரி விகிதத்திற்கு (~{sampling_rate}கள்) சீரமைக்கப்பட்டுள்ளன.\n\nஅசல் காலம்: {duration}வி\nமாதிரிகள்: {samples}", + "data": { + "head_trim": "டிரிம் ஸ்டார்ட் (வினாடிகள்)", + "tail_trim": "டிரிம் எண்ட் (வினாடிகள்)", + "save_mode": "இலக்கைச் சேமிக்கவும்", + "profile_name": "சுயவிவரப் பெயர்" + } + }, + "learning_feedbacks": { + "title": "கற்றல் பின்னூட்டங்கள்", + "description": "நிலுவையில் உள்ள பின்னூட்டங்கள்: {count}\n\n{pending_table}\n\nசெயலாக்க ஒரு சுழற்சி மதிப்பாய்வு கோரிக்கையைத் தேர்ந்தெடுக்கவும்.", + "menu_options": { + "learning_feedbacks_pick": "நிலுவையில் உள்ள ஒரு பின்னூட்டத்தை மதிப்பாய்வு செய்யவும்", + "learning_feedbacks_dismiss_all": "நிலுவையில் உள்ள அனைத்து பின்னூட்டங்களையும் நிராகரிக்கவும்", + "menu_back": "← பின்" + } + }, + "learning_feedbacks_pick": { + "title": "மதிப்பாய்வுக்கான பின்னூட்டத்தைத் தேர்ந்தெடுக்கவும்", + "description": "திறக்க ஒரு சுழற்சி மதிப்பாய்வு கோரிக்கையைத் தேர்ந்தெடுக்கவும்.", + "data": { + "selected_feedback": "மதிப்பாய்வுக்கான சுழற்சியைத் தேர்ந்தெடுக்கவும்" + } + }, + "learning_feedbacks_empty": { + "title": "கற்றல் பின்னூட்டங்கள்", + "description": "நிலுவையில் உள்ள கருத்துக் கோரிக்கைகள் எதுவும் இல்லை.", + "menu_options": { + "menu_back": "← பின்" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "நிலுவையில் உள்ள அனைத்து பின்னூட்டங்களையும் நிராகரிக்கவும்", + "description": "நீங்கள் **{count}** நிலுவையில் உள்ள பின்னூட்டக் கோரிக்கைகளை நிராகரிக்க உள்ளீர்கள். நிராகரிக்கப்பட்ட சுழற்சிகள் உங்கள் வரலாற்றில் இருக்கும், ஆனால் புதிய கற்றல் சிக்னலுக்கு பங்களிக்காது. இதை மாற்ற முடியாது.\n\n✅ அனைத்தையும் நிராகரிக்க கீழே உள்ள பெட்டியைத் தேர்ந்தெடுத்து **சமர்ப்பி** என்பதைக் கிளிக் செய்யவும்.\n❌ அதைத் தேர்வு செய்யாமல் விட்டுவிட்டு, ரத்து செய்து திரும்பிச் செல்ல **சமர்ப்பி** என்பதைக் கிளிக் செய்யவும்.", + "data": { + "confirm_dismiss_all": "உறுதிப்படுத்து: நிலுவையில் உள்ள அனைத்து பின்னூட்ட கோரிக்கைகளையும் நிராகரிக்கவும்" + } + }, + "resolve_feedback": { + "title": "கருத்தைத் தீர்க்கவும்", + "description": "{comparison_img}{candidates_table}\n**கண்டறியப்பட்ட சுயவிவரம்**: {detected_profile} ({confidence_pct}%)\n**மதிப்பிடப்பட்ட காலம்**: {est_duration_min} நிமிடம்\n**உண்மையான காலம்**: {act_duration_min} நிமிடம்\n\n__கீழே ஒரு செயலைத் தேர்ந்தெடுக்கவும்:__", + "data": { + "action": "நீங்கள் என்ன செய்ய விரும்புகிறீர்கள்?", + "corrected_profile": "சரியான திட்டம் (திருத்தினால்)", + "corrected_duration": "விநாடிகளில் சரியான கால அளவு (திருத்தினால்)" + } + }, + "trim_cycle_select": { + "title": "டிரிம் சைக்கிள் - சுழற்சியைத் தேர்ந்தெடுக்கவும்", + "description": "ஒழுங்கமைக்க ஒரு சுழற்சியைத் தேர்ந்தெடுக்கவும். ✓ = முடிந்தது, ⚠ = மீண்டும் தொடங்கப்பட்டது, ✗ = குறுக்கிடப்பட்டது", + "data": { + "cycle_id": "சுழற்சி" + } + }, + "trim_cycle": { + "title": "டிரிம் சைக்கிள்", + "description": "தற்போதைய சாளரம்: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "செயல்" + } + }, + "trim_cycle_start": { + "title": "டிரிம் தொடக்கத்தை அமைக்கவும்", + "description": "சுழற்சி சாளரம்: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nதற்போதைய தொடக்கம்: **{current_wallclock}** (சுழற்சி தொடக்கத்திலிருந்து +{current_offset_min} நிமிடம்)\n\nகீழே உள்ள கடிகாரத் தேர்வியைப் பயன்படுத்தி புதிய தொடக்க நேரத்தைத் தேர்ந்தெடுக்கவும்.", + "data": { + "trim_start_time": "புதிய தொடக்க நேரம்", + "trim_start_min": "புதிய தொடக்கம் (சுழற்சி தொடக்கத்தில் இருந்து நிமிடங்கள்)" + } + }, + "trim_cycle_end": { + "title": "டிரிம் முடிவை அமைக்கவும்", + "description": "சுழற்சி சாளரம்: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nதற்போதைய முடிவு: **{current_wallclock}** (சுழற்சி தொடக்கத்திலிருந்து +{current_offset_min} நிமிடம்)\n\nகீழே உள்ள கடிகாரத் தேர்வியைப் பயன்படுத்தி புதிய முடிவு நேரத்தைத் தேர்ந்தெடுக்கவும்.", + "data": { + "trim_end_time": "புதிய முடிவு நேரம்", + "trim_end_min": "புதிய முடிவு (சுழற்சி தொடங்கி நிமிடங்கள்)" + } + } + }, + "error": { + "import_failed": "இறக்குமதி தோல்வி. சரியான WashData ஏற்றுமதி JSONஐ ஒட்டவும்.", + "select_exactly_one": "இந்தச் செயலுக்கு சரியாக ஒரு சுழற்சியைத் தேர்ந்தெடுக்கவும்.", + "select_at_least_one": "இந்தச் செயலுக்கு குறைந்தது ஒரு சுழற்சியையாவது தேர்ந்தெடுக்கவும்.", + "select_at_least_two": "இந்தச் செயலுக்கு குறைந்தது இரண்டு சுழற்சிகளைத் தேர்ந்தெடுக்கவும்.", + "profile_exists": "இந்தப் பெயரில் ஒரு சுயவிவரம் ஏற்கனவே உள்ளது", + "rename_failed": "சுயவிவரத்தை மறுபெயரிடுவதில் தோல்வி. சுயவிவரம் இல்லாமல் இருக்கலாம் அல்லது புதிய பெயர் ஏற்கனவே எடுக்கப்பட்டிருக்கலாம்.", + "assignment_failed": "சுழற்சிக்கு சுயவிவரத்தை ஒதுக்க முடியவில்லை", + "duplicate_phase": "இந்தப் பெயரில் ஒரு கட்டம் ஏற்கனவே உள்ளது.", + "phase_not_found": "தேர்ந்தெடுக்கப்பட்ட கட்டம் கிடைக்கவில்லை.", + "invalid_phase_name": "கட்டப் பெயர் காலியாக இருக்கக்கூடாது.", + "phase_name_too_long": "கட்டப் பெயர் மிக நீளமாக உள்ளது.", + "invalid_phase_range": "கட்ட வரம்பு தவறானது. தொடக்கத்தை விட முடிவு பெரிதாக இருக்க வேண்டும்.", + "invalid_phase_timestamp": "நேரமுத்திரை தவறானது. YYYY-MM-DD HH:MM அல்லது HH:MM வடிவமைப்பைப் பயன்படுத்தவும்.", + "overlapping_phase_ranges": "கட்ட வரம்புகள் ஒன்றுடன் ஒன்று. வரம்புகளைச் சரிசெய்து மீண்டும் முயலவும்.", + "incomplete_phase_row": "ஒவ்வொரு கட்ட வரிசையும் தேர்ந்தெடுக்கப்பட்ட பயன்முறையில் ஒரு கட்டம் மற்றும் முழுமையான தொடக்க/இறுதி மதிப்புகளைக் கொண்டிருக்க வேண்டும்.", + "timestamp_mode_cycle_required": "நேரமுத்திரை பயன்முறையைப் பயன்படுத்தும் போது, ​​மூலச் சுழற்சியைத் தேர்ந்தெடுக்கவும்.", + "no_phase_ranges": "அந்தச் செயலுக்கான கட்ட வரம்புகள் எதுவும் இன்னும் கிடைக்கவில்லை.", + "feedback_notification_title": "WashData: சுழற்சியைச் சரிபார்க்கவும் ({device})", + "feedback_notification_message": "**சாதனம்**: {device}\n**திட்டம்**: {program} ({confidence}% நம்பிக்கை)\n**நேரம்**: {time}\n\nஇந்த கண்டறியப்பட்ட சுழற்சியைச் சரிபார்க்க WashData க்கு உங்கள் உதவி தேவை.\n\nஇந்த முடிவை உறுதிப்படுத்த அல்லது சரிசெய்ய, தயவுசெய்து **அமைப்புகள் > சாதனங்கள் & சேவைகள் > WashData > உள்ளமைத்தல் > கற்றல் கருத்துகள்** என்பதற்குச் செல்லவும்.", + "suggestions_ready_notification_title": "WashData: பரிந்துரைக்கப்பட்ட அமைப்புகள் தயார் ({device})", + "suggestions_ready_notification_message": "**பரிந்துரைக்கப்பட்ட அமைப்புகள்** சென்சார் இப்போது **{count}** செயல்படக்கூடிய பரிந்துரைகளைப் புகாரளிக்கிறது.\n\nஅவற்றை மதிப்பாய்வு செய்து பயன்படுத்த: **அமைப்புகள் > சாதனங்கள் & சேவைகள் > WashData > உள்ளமைத்தல் > மேம்பட்ட அமைப்புகள் > பரிந்துரைக்கப்பட்ட மதிப்புகளைப் பயன்படுத்து**.\n\nபரிந்துரைகள் விருப்பமானவை மற்றும் நீங்கள் சேமிப்பதற்கு முன் மதிப்பாய்வுக்காக காண்பிக்கப்படும்.", + "trim_range_invalid": "ஆரம்பம் முடிவதற்கு முன் இருக்க வேண்டும். உங்கள் டிரிம் புள்ளிகளை சரிசெய்யவும்.", + "trim_failed": "டிரிம் தோல்வியடைந்தது. சுழற்சி இனி இருக்காது அல்லது சாளரம் காலியாக இருக்கலாம்.", + "invalid_split_timestamp": "நேரமுத்திரை செல்லாது அல்லது சுழற்சி சாளரத்திற்கு வெளியே உள்ளது. சுழற்சி சாளரத்திற்குள் HH:MM அல்லது HH:MM:SS ஐப் பயன்படுத்தவும்.", + "no_split_segments_found": "இந்த நேரமுத்திரைகளில் இருந்து செல்லுபடியாகும் பிரிவுகள் உருவாக்கப்படவில்லை. ஒவ்வொரு நேரமுத்திரையும் சுழற்சி சாளரத்துக்குள் இருப்பதையும் ஒவ்வொரு பிரிவும் குறைந்தது 1 நிமிடம் நீளமுடையதையும் உறுதிசெய்யவும்.", + "auto_tune_suggestion": "{device_type} {device_title} பேய் சுழற்சிகள் கண்டறியப்பட்டன. பரிந்துரைக்கப்பட்ட min_power மாற்றம்: {current_min}W -> {new_min}W (தானாகப் பயன்படுத்தப்படவில்லை).", + "auto_tune_title": "WashData ஆட்டோ-டியூன்", + "auto_tune_fallback": "{device_type} {device_title} பேய் சுழற்சிகள் கண்டறியப்பட்டன. பரிந்துரைக்கப்பட்ட min_power மாற்றம்: {current_min}W -> {new_min}W (தானாகப் பயன்படுத்தப்படவில்லை)." + }, + "abort": { + "no_cycles_found": "சுழற்சிகள் எதுவும் இல்லை", + "no_split_segments_found": "தேர்ந்தெடுக்கப்பட்ட சுழற்சியில் பிரிக்கக்கூடிய பிரிவுகள் எதுவும் கிடைக்கவில்லை.", + "cycle_not_found": "தேர்ந்தெடுக்கப்பட்ட சுழற்சியை ஏற்ற முடியவில்லை.", + "no_power_data": "தேர்ந்தெடுக்கப்பட்ட சுழற்சிக்கான பவர் ட்ரேஸ் தரவு இல்லை.", + "no_profiles_found": "சுயவிவரங்கள் எதுவும் இல்லை. முதலில் ஒரு சுயவிவரத்தை உருவாக்கவும்.", + "no_profiles_for_matching": "பொருத்துவதற்கு சுயவிவரங்கள் எதுவும் இல்லை. முதலில் சுயவிவரங்களை உருவாக்கவும்.", + "no_unlabeled_cycles": "அனைத்து சுழற்சிகளும் ஏற்கனவே பெயரிடப்பட்டுள்ளன", + "no_suggestions": "பரிந்துரைக்கப்பட்ட மதிப்புகள் எதுவும் இன்னும் கிடைக்கவில்லை. சில சுழற்சிகளை இயக்கி மீண்டும் முயற்சிக்கவும்.", + "no_predictions": "கணிப்புகள் இல்லை", + "no_custom_phases": "தனிப்பயன் கட்டங்கள் எதுவும் இல்லை.", + "reprocess_success": "{count} சுழற்சிகள் வெற்றிகரமாக மீண்டும் செயலாக்கப்பட்டன.", + "debug_data_cleared": "{count} சுழற்சிகளில் இருந்து பிழைத்திருத்த தரவு வெற்றிகரமாக அழிக்கப்பட்டது." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "சுழற்சி தொடக்கம்", + "cycle_finish": "சைக்கிள் பினிஷ்", + "cycle_live": "நேரடி முன்னேற்றம்" + } + }, + "common_text": { + "options": { + "unlabeled": "(பெயரிடப்படாத)", + "create_new_profile": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "remove_label": "லேபிளை அகற்று", + "no_reference_cycle": "(குறிப்பு சுழற்சி இல்லை)", + "all_device_types": "அனைத்து சாதன வகைகள்", + "current_suffix": "(தற்போதைய)", + "editor_select_info": "பிரிக்க 1 சுழற்சி அல்லது ஒன்றிணைக்க 2+ சுழற்சிகளைத் தேர்ந்தெடுக்கவும்.", + "editor_select_info_split": "பிரிக்க 1 சுழற்சியைத் தேர்ந்தெடுக்கவும்.", + "editor_select_info_merge": "ஒன்றிணைக்க 2+ சுழற்சிகளைத் தேர்ந்தெடுக்கவும்.", + "editor_select_info_delete": "நீக்க 1+ சுழற்சிகளைத் தேர்ந்தெடுக்கவும்.", + "no_cycles_recorded": "இதுவரை எந்த சுழற்சிகளும் பதிவு செய்யப்படவில்லை.", + "profile_summary_line": "- **{name}**: {count} சுழற்சிகள், {avg}மீ சராசரி", + "no_profiles_created": "இதுவரை சுயவிவரங்கள் எதுவும் உருவாக்கப்படவில்லை.", + "phase_preview": "கட்ட முன்னோட்டம்", + "phase_preview_no_curve": "சராசரி சுயவிவர வளைவு இன்னும் கிடைக்கவில்லை. இந்தச் சுயவிவரத்திற்கு அதிக சுழற்சிகளை இயக்கவும்/லேபிளிடவும்.", + "average_power_curve": "சராசரி சக்தி வளைவு", + "unit_min": "நிமிடம்", + "profile_option_fmt": "{name} ({count} சுழற்சிகள், ~{duration}மீ சராசரி)", + "profile_option_short_fmt": "{name} ({count} சுழற்சிகள், ~{duration}மீ)", + "deleted_cycles_from_profile": "{profile} இலிருந்து {count} சுழற்சிகள் நீக்கப்பட்டன.", + "not_enough_data_graph": "வரைபடத்தை உருவாக்க போதுமான தரவு இல்லை.", + "no_profiles_found": "சுயவிவரங்கள் எதுவும் இல்லை.", + "cycle_info_fmt": "சுழற்சி: {start}, {duration}m, தற்போதைய: {label}", + "top_candidates_header": "### சிறந்த வேட்பாளர்கள்", + "tbl_profile": "சுயவிவரம்", + "tbl_confidence": "நம்பிக்கை", + "tbl_correlation": "தொடர்பு", + "tbl_duration_match": "காலப் பொருத்தம்", + "detected_profile": "கண்டறியப்பட்ட சுயவிவரம்", + "estimated_duration": "மதிப்பிடப்பட்ட காலம்", + "actual_duration": "உண்மையான காலம்", + "choose_action": "__கீழே ஒரு செயலைத் தேர்ந்தெடுக்கவும்:__", + "tbl_count": "எண்ணு", + "tbl_avg": "சராசரி", + "tbl_min": "குறைந்தபட்சம்", + "tbl_max": "அதிகபட்சம்", + "tbl_energy_avg": "ஆற்றல் (சராசரி)", + "tbl_energy_total": "ஆற்றல் (மொத்தம்)", + "tbl_consistency": "கொண்டுள்ளது.", + "tbl_last_run": "கடைசி ஓட்டம்", + "graph_legend_title": "வரைபட லெஜண்ட்", + "graph_legend_body": "நீலப் பட்டையானது குறைந்தபட்சம் மற்றும் அதிகபட்ச மின்னழுத்த வரம்பைக் குறிக்கிறது. வரி சராசரி சக்தி வளைவைக் காட்டுகிறது.", + "recording_preview": "பதிவு முன்னோட்டம்", + "trim_start": "டிரிம் ஸ்டார்ட்", + "trim_end": "டிரிம் எண்ட்", + "split_preview_title": "பிளவு முன்னோட்டம்", + "split_preview_found_fmt": "{count} பிரிவுகள் உள்ளன.", + "split_preview_confirm_fmt": "இந்தச் சுழற்சியை {count} தனித்தனி சுழற்சிகளாகப் பிரிக்க உறுதிப்படுத்து என்பதைக் கிளிக் செய்யவும்.", + "merge_preview_title": "முன்னோட்டத்தை ஒன்றிணைக்கவும்", + "merge_preview_joining_fmt": "{count} சுழற்சிகளில் இணைகிறது. இடைவெளிகள் 0W அளவீடுகளால் நிரப்பப்படும்.", + "editor_delete_preview_title": "நீக்க முன்னோட்டம்", + "editor_delete_preview_intro": "தேர்ந்தெடுக்கப்பட்ட சுழற்சிகள் நிரந்தரமாக நீக்கப்படும்:", + "editor_delete_preview_confirm": "இந்தச் சுழற்சி பதிவுகளை நிரந்தரமாக நீக்க உறுதிப்படுத்து என்பதைக் கிளிக் செய்யவும்.", + "no_power_preview": "*முன்பார்வைக்கு ஆற்றல் தரவு எதுவும் இல்லை.*", + "profile_comparison": "சுயவிவர ஒப்பீடு", + "actual_cycle_label": "இந்த சுழற்சி (உண்மையானது)", + "storage_usage_header": "சேமிப்பக பயன்பாடு", + "storage_file_size": "கோப்பு அளவு", + "storage_cycles": "சுழற்சிகள்", + "storage_profiles": "சுயவிவரங்கள்", + "storage_debug_traces": "பிழைத்திருத்த தடயங்கள்", + "overview_suffix": "கண்ணோட்டம்", + "phase_preview_for_profile": "சுயவிவரத்திற்கான கட்ட முன்னோட்டம்", + "current_ranges_header": "தற்போதைய வரம்புகள்", + "assign_phases_help": "வரம்புகளைச் சேர்க்க, திருத்த, நீக்க அல்லது சேமிக்க கீழே உள்ள செயல்களைப் பயன்படுத்தவும்.", + "profile_summary_header": "சுயவிவர சுருக்கம்", + "recent_cycles_header": "சமீபத்திய சுழற்சிகள்", + "trim_cycle_preview_title": "சுழற்சி டிரிம் முன்னோட்டம்", + "trim_cycle_preview_no_data": "இந்த சுழற்சிக்கான ஆற்றல் தரவு எதுவும் இல்லை.", + "trim_cycle_preview_kept_suffix": "வைத்திருந்தார்", + "table_program": "நிரல்", + "table_when": "எப்போது", + "table_length": "காலம்", + "table_match": "பொருத்தம்", + "table_profile": "சுயவிவரம்", + "table_cycles": "சுழற்சிகள்", + "table_avg_length": "சராசரி காலம்", + "table_last_run": "கடைசி ஓட்டம்", + "table_avg_energy": "சராசரி ஆற்றல்", + "table_detected_program": "கண்டறியப்பட்ட நிரல்", + "table_confidence": "நம்பிக்கை", + "table_reported": "அறிவிக்கப்பட்டது", + "phase_builtin_suffix": "(உள்ளமைக்கப்பட்ட)", + "phase_none_available": "கட்டங்கள் இல்லை.", + "settings_deprecation_warning": "⚠️ **நிறுத்தப்பட்ட சாதன வகை:** {device_type} எதிர்கால வெளியீட்டில் அகற்ற திட்டமிடப்பட்டுள்ளது. WashData இன் பொருந்தும் பைப்லைன் இந்த அப்ளையன்ஸ் வகுப்பிற்கு நம்பகமான முடிவுகளைத் தரவில்லை. உங்கள் ஒருங்கிணைப்பு தேய்மான காலம் முழுவதும் தொடர்ந்து செயல்படுகிறது; இந்த எச்சரிக்கையை அமைதிப்படுத்த, ஆதரிக்கப்படும் வகைகளில் ஒன்றிற்கு (வாஷிங் மெஷின், ட்ரையர், வாஷர்-ட்ரையர் காம்போ, டிஷ்வாஷர், ஏர் பிரையர், பிரட் மேக்கர் அல்லது பம்ப்) கீழே உள்ள **சாதன வகையை** மாற்றவும் அல்லது உங்கள் சாதனம் ஆதரிக்கப்படும் வகைகளுடன் பொருந்தவில்லை என்றால் **மற்ற (மேம்பட்ட)**. ** பிற (மேம்பட்ட)** வேண்டுமென்றே பொதுவான இயல்புநிலைகளை அனுப்புகிறது, அவை எந்தவொரு குறிப்பிட்ட சாதனத்திற்கும் டியூன் செய்யப்படவில்லை, எனவே நீங்கள் வரம்புகள், காலக்கெடு மற்றும் பொருந்தக்கூடிய அளவுருக்களை நீங்களே கட்டமைக்க வேண்டும்; நீங்கள் மாறும்போது ஏற்கனவே உள்ள அனைத்து அமைப்புகளும் பாதுகாக்கப்படும்.", + "phase_other_device_types": "பிற சாதன வகைகள்:" + } + }, + "interactive_editor_action": { + "options": { + "split": "ஒரு சுழற்சியைப் பிரித்தல் (இடைவெளிகளைக் கண்டறிதல்)", + "merge": "சுழற்சிகளை ஒன்றிணைக்கவும் (துண்டுகளை இணைக்கவும்)", + "delete": "சுழற்சி(களை) நீக்கு" + } + }, + "split_mode": { + "options": { + "auto": "செயலற்ற இடைவெளிகளை தானாகக் கண்டறி", + "manual": "கைமுறை நேரமுத்திரை(கள்)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "பராமரிப்பு: தரவை மறு செயலாக்கம் & மேம்படுத்துதல்", + "clear_debug_data": "பிழைத்திருத்தத் தரவை அழிக்கவும் (இடத்தைக் காலியாக்கவும்)", + "wipe_history": "இந்தச் சாதனத்திற்கான எல்லா தரவையும் அழிக்கவும் (மாற்ற முடியாதது)", + "export_import": "அமைப்புகளுடன் JSON ஐ ஏற்றுமதி/இறக்குமதி (நகல்/ஒட்டு)" + } + }, + "export_import_mode": { + "options": { + "export": "எல்லா தரவையும் ஏற்றுமதி செய்யவும்", + "import": "தரவை இறக்குமதி/ஒன்றிணைத்தல்" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "பழைய சைக்கிள்களைத் தானாக லேபிளிடுங்கள்", + "select_cycle_to_label": "லேபிள் குறிப்பிட்ட சுழற்சி", + "select_cycle_to_delete": "சுழற்சியை நீக்கு", + "interactive_editor": "இன்டராக்டிவ் எடிட்டரை ஒன்றிணைத்தல்/பிரித்தல்", + "trim_cycle": "சுழற்சி தரவை ஒழுங்கமைக்கவும்" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "edit_profile": "சுயவிவரத்தைத் திருத்து/மறுபெயரிடு", + "delete_profile": "சுயவிவரத்தை நீக்கு", + "profile_stats": "சுயவிவர புள்ளிவிவரங்கள்", + "cleanup_profile": "வரலாற்றை சுத்தம் செய்யவும் - வரைபடம் & நீக்கு", + "assign_phases": "கட்ட வரம்புகளை ஒதுக்கவும்" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "புதிய கட்டத்தை உருவாக்கவும்", + "edit_custom_phase": "திருத்த கட்டம்", + "delete_custom_phase": "கட்டத்தை நீக்கு" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "கட்ட வரம்பைச் சேர்க்கவும்", + "edit_range": "கட்ட வரம்பைத் திருத்தவும்", + "delete_range": "கட்ட வரம்பை நீக்கு", + "clear_ranges": "அனைத்து வரம்புகளையும் அழி", + "auto_detect_ranges": "தானாக கண்டறிதல் கட்டங்கள்", + "save_ranges": "சேமித்து திரும்பவும்" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "நிலையை புதுப்பிக்கவும்", + "stop_recording": "பதிவை நிறுத்து (சேமி & செயலாக்கம்)", + "start_recording": "புதிய பதிவைத் தொடங்கவும்", + "process_recording": "கடைசிப் பதிவைச் செயலாக்கு (டிரிம் & சேவ்)", + "discard_recording": "கடைசி பதிவை நிராகரிக்கவும்" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "புதிய சுயவிவரத்தை உருவாக்கவும்", + "existing_profile": "ஏற்கனவே உள்ள சுயவிவரத்தில் சேர்க்கவும்", + "discard": "பதிவை நிராகரி" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "உறுதி - சரியான கண்டறிதல்", + "correct": "சரியானது - நிரல்/காலத்தை தேர்வு செய்யவும்", + "ignore": "புறக்கணிப்பு - தவறான நேர்மறை/இரைச்சல் சுழற்சி", + "delete": "நீக்கு - வரலாற்றிலிருந்து நீக்கு" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "கட்டங்களை பெயரிட்டு விண்ணப்பிக்கவும்", + "cancel": "மாற்றங்கள் இல்லாமல் திரும்பிச் செல்லுங்கள்" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "தொடக்கப் புள்ளியை அமைக்கவும்", + "set_end": "இறுதிப் புள்ளியை அமைக்கவும்", + "reset": "முழு காலத்திற்கு மீட்டமைக்கவும்", + "apply": "டிரிம் பயன்படுத்தவும்", + "cancel": "ரத்து செய்" + } + }, + "device_type": { + "options": { + "washing_machine": "சலவை இயந்திரம்", + "dryer": "உலர்த்தி", + "washer_dryer": "வாஷர்-ட்ரையர் சேர்க்கை", + "dishwasher": "பாத்திரங்கழுவி", + "coffee_machine": "காபி இயந்திரம் (நிறுத்தப்பட்டது)", + "ev": "மின்சார வாகனம் (நிறுத்தப்பட்டது)", + "air_fryer": "ஏர் பிரையர்", + "heat_pump": "வெப்ப பம்ப் (நிறுத்தப்பட்டது)", + "bread_maker": "ரொட்டி தயாரிப்பாளர்", + "pump": "பம்ப் / சம்ப் பம்ப்", + "oven": "அடுப்பு (நிறுத்தப்பட்டது)", + "other": "மற்றவை (மேம்பட்ட)" + } + } + }, + "services": { + "label_cycle": { + "name": "லேபிள் சுழற்சி", + "description": "ஏற்கனவே உள்ள சுயவிவரத்தை கடந்த சுழற்சிக்கு ஒதுக்கவும் அல்லது லேபிளை அகற்றவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "லேபிளிட வேண்டிய சலவை இயந்திர சாதனம்." + }, + "cycle_id": { + "name": "சைக்கிள் ஐடி", + "description": "லேபிளிட வேண்டிய சுழற்சியின் ஐடி." + }, + "profile_name": { + "name": "சுயவிவரப் பெயர்", + "description": "ஏற்கனவே உள்ள சுயவிவரத்தின் பெயர் (சுயவிவரங்களை நிர்வகி மெனுவில் சுயவிவரங்களை உருவாக்கவும்). லேபிளை அகற்ற காலியாக விடவும்." + } + } + }, + "create_profile": { + "name": "சுயவிவரத்தை உருவாக்கவும்", + "description": "புதிய சுயவிவரத்தை உருவாக்கவும் (தனிப்பட்ட அல்லது குறிப்பு சுழற்சியின் அடிப்படையில்).", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "சலவை இயந்திர சாதனம்." + }, + "profile_name": { + "name": "சுயவிவரப் பெயர்", + "description": "புதிய சுயவிவரத்திற்கான பெயர் (எ.கா. 'ஹெவி டியூட்டி', 'டெலிகேட்ஸ்')." + }, + "reference_cycle_id": { + "name": "குறிப்பு சுழற்சி ஐடி", + "description": "இந்த சுயவிவரத்தை அடிப்படையாகக் கொள்ள விருப்ப சுழற்சி ஐடி." + } + } + }, + "delete_profile": { + "name": "சுயவிவரத்தை நீக்கு", + "description": "ஒரு சுயவிவரத்தை நீக்கி, அதை பயன்படுத்தி சுழற்சிகளை விருப்பமாக அன்லாப் செய்யவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "சலவை இயந்திர சாதனம்." + }, + "profile_name": { + "name": "சுயவிவரப் பெயர்", + "description": "நீக்க வேண்டிய சுயவிவரம்." + }, + "unlabel_cycles": { + "name": "சுழற்சிகளை லேபிளிடு", + "description": "இந்த சுயவிவரத்தைப் பயன்படுத்தி சுழற்சிகளில் இருந்து சுயவிவர லேபிளை அகற்றவும்." + } + } + }, + "auto_label_cycles": { + "name": "பழைய சைக்கிள்களைத் தானாக லேபிளிடுங்கள்", + "description": "சுயவிவரப் பொருத்தத்தைப் பயன்படுத்தி லேபிளிடப்படாத சுழற்சிகளை முன்னோடியாக லேபிளிடுங்கள்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "சலவை இயந்திர சாதனம்." + }, + "confidence_threshold": { + "name": "நம்பிக்கை வாசல்", + "description": "லேபிள்களைப் பயன்படுத்துவதற்கான குறைந்தபட்ச பொருத்த நம்பிக்கை (0.50-0.95)." + } + } + }, + "export_config": { + "name": "ஏற்றுமதி கட்டமைப்பு", + "description": "இந்தச் சாதனத்தின் சுயவிவரங்கள், சுழற்சிகள் மற்றும் அமைப்புகளை JSON கோப்பிற்கு (ஒரு சாதனத்திற்கு) ஏற்றுமதி செய்யவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "ஏற்றுமதி செய்ய வாஷிங் மெஷின் சாதனம்." + }, + "path": { + "name": "பாதை", + "description": "எழுதுவதற்கான விருப்ப முழுமையான கோப்பு பாதை (/config/ha_washdata_export_{entry}.json க்கு இயல்புநிலை)." + } + } + }, + "import_config": { + "name": "இறக்குமதி கட்டமைப்பு", + "description": "JSON ஏற்றுமதி கோப்பிலிருந்து இந்தச் சாதனத்திற்கான சுயவிவரங்கள், சுழற்சிகள் மற்றும் அமைப்புகளை இறக்குமதி செய்யவும் (அமைப்புகள் மேலெழுதப்படலாம்).", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "இறக்குமதி செய்ய வாஷிங் மெஷின் சாதனம்." + }, + "path": { + "name": "பாதை", + "description": "ஏற்றுமதி JSON கோப்பை இறக்குமதி செய்வதற்கான முழுமையான பாதை." + } + } + }, + "submit_cycle_feedback": { + "name": "சுழற்சிக் கருத்தைச் சமர்ப்பிக்கவும்", + "description": "முடிக்கப்பட்ட சுழற்சிக்குப் பிறகு தானாகக் கண்டறியப்பட்ட நிரலை உறுதிப்படுத்தவும் அல்லது சரிசெய்யவும். `entry_id` (மேம்பட்டது) அல்லது `device_id` (பரிந்துரைக்கப்பட்டது) ஒன்றை வழங்கவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "WashData சாதனம் (entry_id க்குப் பதிலாக பரிந்துரைக்கப்படுகிறது)." + }, + "entry_id": { + "name": "நுழைவு ஐடி", + "description": "சாதனத்திற்கான கட்டமைப்பு நுழைவு ஐடி (device_idக்கு மாற்று)." + }, + "cycle_id": { + "name": "சைக்கிள் ஐடி", + "description": "பின்னூட்ட அறிவிப்பு / பதிவுகளில் சுழற்சி ஐடி காட்டப்பட்டுள்ளது." + }, + "user_confirmed": { + "name": "கண்டறியப்பட்ட திட்டத்தை உறுதிப்படுத்தவும்", + "description": "கண்டறியப்பட்ட நிரல் சரியாக இருந்தால் சரி என்பதை அமைக்கவும்." + }, + "corrected_profile": { + "name": "சரி செய்யப்பட்ட சுயவிவரம்", + "description": "உறுதிப்படுத்தப்படவில்லை என்றால், சரியான சுயவிவரம்/நிரல் பெயரை வழங்கவும்." + }, + "corrected_duration": { + "name": "சரிசெய்யப்பட்ட காலம் (வினாடிகள்)", + "description": "வினாடிகளில் விருப்பமான திருத்தப்பட்ட காலம்." + }, + "notes": { + "name": "குறிப்புகள்", + "description": "இந்த சுழற்சி பற்றிய விருப்ப குறிப்புகள்." + } + } + }, + "record_start": { + "name": "பதிவு சுழற்சி தொடக்கம்", + "description": "சுத்தமான சுழற்சியை கைமுறையாகப் பதிவுசெய்யத் தொடங்குங்கள் (பொருந்தும் அனைத்து தர்க்கங்களையும் புறக்கணிக்கிறது).", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "பதிவு செய்ய வாஷிங் மெஷின் சாதனம்." + } + } + }, + "record_stop": { + "name": "பதிவு சுழற்சி நிறுத்தம்", + "description": "கைமுறையாகப் பதிவு செய்வதை நிறுத்துங்கள்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "பதிவு செய்வதை நிறுத்த வாஷிங் மெஷின் சாதனம்." + } + } + }, + "trim_cycle": { + "name": "டிரிம் சைக்கிள்", + "description": "கடந்த கால சுழற்சியின் ஆற்றல் தரவை ஒரு குறிப்பிட்ட நேர சாளரத்திற்கு ஒழுங்கமைக்கவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "WashData சாதனம்." + }, + "cycle_id": { + "name": "சைக்கிள் ஐடி", + "description": "ஒழுங்கமைக்க வேண்டிய சுழற்சியின் ஐடி." + }, + "trim_start_s": { + "name": "டிரிம் ஸ்டார்ட் (வினாடிகள்)", + "description": "இந்த ஆஃப்செட்டிலிருந்து தரவை நொடிகளில் சேமிக்கவும். இயல்புநிலை 0." + }, + "trim_end_s": { + "name": "டிரிம் எண்ட் (வினாடிகள்)", + "description": "வினாடிகளில் இந்த ஆஃப்செட் வரை தரவை வைத்திருக்கவும். இயல்புநிலை = முழு கால அளவு." + } + } + }, + "pause_cycle": { + "name": "சுழற்சியை இடைநிறுத்தவும்", + "description": "WashData சாதனத்திற்கான செயலில் உள்ள சுழற்சியை இடைநிறுத்தவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "WashData சாதனம் இடைநிறுத்தப்படும்." + } + } + }, + "resume_cycle": { + "name": "சுழற்சியை மீண்டும் தொடங்கவும்", + "description": "WashData சாதனத்திற்கான இடைநிறுத்தப்பட்ட சுழற்சியை மீண்டும் தொடங்கவும்.", + "fields": { + "device_id": { + "name": "சாதனம்", + "description": "WashData சாதனம் மீண்டும் தொடங்கும்." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "ஓடுகிறது" + }, + "match_ambiguity": { + "name": "பொருத்தம் தெளிவின்மை" + } + }, + "sensor": { + "washer_state": { + "name": "மாநிலம்", + "state": { + "off": "ஆஃப்", + "idle": "சும்மா", + "starting": "தொடங்குகிறது", + "running": "ஓடுகிறது", + "paused": "இடைநிறுத்தப்பட்டது", + "user_paused": "பயனரால் இடைநிறுத்தப்பட்டது", + "ending": "முடிவடைகிறது", + "finished": "முடிந்தது", + "anti_wrinkle": "சுருக்க எதிர்ப்பு", + "delay_wait": "தொடங்குவதற்கு காத்திருக்கிறது", + "interrupted": "குறுக்கிடப்பட்டது", + "force_stopped": "படை நிறுத்தப்பட்டது", + "rinse": "துவைக்க", + "unknown": "தெரியவில்லை", + "clean": "சுத்தமான" + } + }, + "washer_program": { + "name": "நிரல்" + }, + "time_remaining": { + "name": "மீதமுள்ள நேரம்" + }, + "total_duration": { + "name": "மொத்த கால அளவு" + }, + "cycle_progress": { + "name": "முன்னேற்றம்" + }, + "current_power": { + "name": "தற்போதைய சக்தி" + }, + "elapsed_time": { + "name": "கழிந்த நேரம்" + }, + "debug_info": { + "name": "பிழைத்திருத்த தகவல்" + }, + "match_confidence": { + "name": "போட்டி நம்பிக்கை" + }, + "top_candidates": { + "name": "சிறந்த வேட்பாளர்கள்", + "state": { + "none": "இல்லை" + } + }, + "current_phase": { + "name": "தற்போதைய கட்டம்" + }, + "wash_phase": { + "name": "கட்டம்" + }, + "profile_cycle_count": { + "name": "சுயவிவரம் {profile_name} எண்ணிக்கை", + "unit_of_measurement": "சுழற்சிகள்" + }, + "suggestions": { + "name": "பரிந்துரைக்கப்பட்ட அமைப்புகள்" + }, + "pump_runs_today": { + "name": "பம்ப் ஓட்டங்கள் (கடந்த 24 மணிநேரம்)" + }, + "cycle_count": { + "name": "சுழற்சி எண்ணிக்கை" + } + }, + "select": { + "program_select": { + "name": "சுழற்சி திட்டம்", + "state": { + "auto_detect": "தானாக கண்டறிதல்" + } + } + }, + "button": { + "force_end_cycle": { + "name": "ஃபோர்ஸ் எண்ட் சைக்கிள்" + }, + "pause_cycle": { + "name": "சுழற்சியை இடைநிறுத்தவும்" + }, + "resume_cycle": { + "name": "சுழற்சியை மீண்டும் தொடங்கவும்" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "சரியான device_id தேவை." + }, + "cycle_id_required": { + "message": "சரியான சுழற்சி_ஐடி தேவை." + }, + "profile_name_required": { + "message": "சரியான profile_பெயர் தேவை." + }, + "device_not_found": { + "message": "சாதனம் கிடைக்கவில்லை." + }, + "no_config_entry": { + "message": "சாதனத்திற்கான கட்டமைப்பு உள்ளீடு எதுவும் இல்லை." + }, + "integration_not_loaded": { + "message": "இந்தச் சாதனத்திற்கான ஒருங்கிணைப்பு ஏற்றப்படவில்லை." + }, + "cycle_not_found_or_no_power": { + "message": "சுழற்சி கண்டறியப்படவில்லை அல்லது ஆற்றல் தரவு இல்லை." + }, + "trim_failed_empty_window": { + "message": "டிரிம் தோல்வியடைந்தது - சுழற்சி கிடைக்கவில்லை, ஆற்றல் தரவு இல்லை அல்லது அதன் விளைவாக சாளரம் காலியாக உள்ளது." + }, + "trim_invalid_range": { + "message": "trim_end_s trim_start_s ஐ விட அதிகமாக இருக்க வேண்டும்." + } + } +} diff --git a/custom_components/ha_washdata/translations/te.json b/custom_components/ha_washdata/translations/te.json new file mode 100644 index 0000000..7e203b6 --- /dev/null +++ b/custom_components/ha_washdata/translations/te.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData సెటప్", + "description": "మీ వాషింగ్ మెషీన్ లేదా ఇతర ఉపకరణాన్ని కాన్ఫిగర్ చేయండి.\n\nపవర్ సెన్సార్ అవసరం.\n\n**తర్వాత, మీరు మీ మొదటి ప్రొఫైల్‌ని సృష్టించాలనుకుంటున్నారా అని అడగబడతారు.**", + "data": { + "name": "పరికరం పేరు", + "device_type": "పరికర రకం", + "power_sensor": "పవర్ సెన్సార్", + "min_power": "కనిష్ట పవర్ థ్రెషోల్డ్ (W)" + }, + "data_description": { + "name": "ఈ పరికరానికి స్నేహపూర్వక పేరు (ఉదా., 'వాషింగ్ మెషిన్', 'డిష్‌వాషర్').", + "device_type": "ఇది ఏ రకమైన ఉపకరణం? టైలర్ డిటెక్షన్ మరియు లేబులింగ్ సహాయం చేస్తుంది.", + "power_sensor": "మీ స్మార్ట్ ప్లగ్ నుండి నిజ-సమయ విద్యుత్ వినియోగాన్ని (వాట్‌లలో) నివేదించే సెన్సార్ ఎంటిటీ.", + "min_power": "ఈ థ్రెషోల్డ్ (వాట్స్‌లో) పైన ఉన్న పవర్ రీడింగ్‌లు ఉపకరణం నడుస్తున్నట్లు సూచిస్తున్నాయి. చాలా పరికరాల కోసం 2Wతో ప్రారంభించండి." + } + }, + "first_profile": { + "title": "మొదటి ప్రొఫైల్‌ను సృష్టించండి", + "description": "**సైకిల్** అనేది మీ ఉపకరణం యొక్క పూర్తి పరుగు (ఉదా., లాండ్రీ లోడ్). ఒక **ప్రొఫైల్** ఈ సైకిల్‌లను రకం ద్వారా సమూహపరుస్తుంది (ఉదా., 'కాటన్', 'క్విక్ వాష్'). ఇప్పుడు ప్రొఫైల్‌ని క్రియేట్ చేయడం వల్ల ఇంటిగ్రేషన్ అంచనా వ్యవధి తక్షణమే సహాయపడుతుంది.\n\nఇది స్వయంచాలకంగా గుర్తించబడకపోతే మీరు పరికర నియంత్రణలలో ఈ ప్రొఫైల్‌ను మాన్యువల్‌గా ఎంచుకోవచ్చు.\n\n**ఈ దశను దాటవేయడానికి 'ప్రొఫైల్ పేరు'ని ఖాళీగా వదిలేయండి.**", + "data": { + "profile_name": "ప్రొఫైల్ పేరు (ఐచ్ఛికం)", + "manual_duration": "అంచనా వ్యవధి (నిమిషాలు)" + } + } + }, + "error": { + "cannot_connect": "కనెక్ట్ చేయడంలో విఫలమైంది", + "invalid_auth": "చెల్లని ప్రమాణీకరణ", + "unknown": "ఊహించని లోపం" + }, + "abort": { + "already_configured": "పరికరం ఇప్పటికే కాన్ఫిగర్ చేయబడింది" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "అభ్యసన అభిప్రాయాలను సమీక్షించండి", + "settings": "సెట్టింగ్‌లు", + "notifications": "నోటిఫికేషన్‌లు", + "manage_cycles": "సైకిళ్లను నిర్వహించండి", + "manage_profiles": "ప్రొఫైల్‌లను నిర్వహించండి", + "manage_phase_catalog": "దశ కేటలాగ్‌ని నిర్వహించండి", + "record_cycle": "రికార్డ్ సైకిల్ (మాన్యువల్)", + "diagnostics": "డయాగ్నోస్టిక్స్ & మెయింటెనెన్స్" + } + }, + "apply_suggestions": { + "title": "సూచించిన విలువలను కాపీ చేయండి", + "description": "ఇది మీ సేవ్ చేసిన సెట్టింగ్‌లలోకి ప్రస్తుత సూచించబడిన విలువలను కాపీ చేస్తుంది. ఇది ఒక-పర్యాయ చర్య మరియు స్వీయ ఓవర్‌రైట్‌లను ప్రారంభించదు.\n\nకాపీ చేయడానికి సూచించబడిన విలువలు:\n{suggested}", + "data": { + "confirm": "కాపీ చర్యను నిర్ధారించండి" + } + }, + "apply_suggestions_confirm": { + "title": "సూచించిన మార్పులను సమీక్షించండి", + "description": "WashData వర్తింపజేయడానికి **{pending_count}** సూచించిన విలువ(లు) కనుగొనబడింది.\n\nమార్పులు:\n{changes}\n\n✅ ఈ మార్పులను వెంటనే వర్తింపజేయడానికి మరియు సేవ్ చేయడానికి దిగువ పెట్టెను తనిఖీ చేసి, **సమర్పించు** క్లిక్ చేయండి.\n❌ దాన్ని ఎంపిక చేయకుండా వదిలేసి, రద్దు చేసి వెనక్కి వెళ్లడానికి **సమర్పించు** క్లిక్ చేయండి.", + "data": { + "confirm_apply_suggestions": "ఈ మార్పులను వర్తింపజేయండి మరియు సేవ్ చేయండి" + } + }, + "settings": { + "title": "సెట్టింగ్‌లు", + "description": "{deprecation_warning}ట్యూన్ డిటెక్షన్ థ్రెషోల్డ్‌లు, లెర్నింగ్ మరియు అధునాతన ప్రవర్తన. చాలా సెటప్‌లకు డిఫాల్ట్‌లు బాగా పని చేస్తాయి.\n\nప్రస్తుతం అందుబాటులో ఉన్న సూచించబడిన సెట్టింగ్‌లు: {suggestions_count}.\nఇది 0 కంటే ఎక్కువగా ఉంటే, అధునాతన సెట్టింగ్‌లను తెరిచి సిఫార్సులను సమీక్షించడానికి 'సూచించబడిన విలువలను వర్తింపజేయండి'ని ఉపయోగించండి.", + "data": { + "apply_suggestions": "సూచించబడిన విలువలను వర్తింపజేయండి", + "device_type": "పరికర రకం", + "power_sensor": "పవర్ సెన్సార్ ఎంటిటీ", + "min_power": "కనిష్ట శక్తి (W)", + "off_delay": "సైకిల్ ముగింపు ఆలస్యం (లు)", + "notify_actions": "నోటిఫికేషన్ చర్యలు", + "notify_people": "ఈ వ్యక్తులు ఇంటికి వచ్చే వరకు డెలివరీ ఆలస్యం", + "notify_only_when_home": "ఎంపిక చేయబడిన వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్‌లను ఆలస్యం చేయండి", + "notify_fire_events": "ఆటోమేషన్ ఈవెంట్‌లను ప్రారంభించు", + "notify_start_services": "సైకిల్ ప్రారంభం - నోటిఫికేషన్ లక్ష్యాలు", + "notify_finish_services": "సైకిల్ ముగింపు - నోటిఫికేషన్ లక్ష్యాలు", + "notify_live_services": "ప్రత్యక్ష పురోగతి - నోటిఫికేషన్ లక్ష్యాలు", + "notify_before_end_minutes": "ప్రీ-కంప్లీషన్ నోటిఫికేషన్ (ముగింపుకు నిమిషాల ముందు)", + "notify_title": "నోటిఫికేషన్ శీర్షిక", + "notify_icon": "నోటిఫికేషన్ చిహ్నం", + "notify_start_message": "సందేశ ఆకృతిని ప్రారంభించండి", + "notify_finish_message": "సందేశ ఆకృతిని ముగించు", + "notify_pre_complete_message": "ప్రీ-కంప్లీషన్ మెసేజ్ ఫార్మాట్", + "show_advanced": "అధునాతన సెట్టింగ్‌లను సవరించండి (తదుపరి దశ)" + }, + "data_description": { + "apply_suggestions": "లెర్నింగ్ అల్గారిథమ్ సూచించిన విలువలను ఫారమ్‌లోకి కాపీ చేయడానికి ఈ పెట్టెను ఎంచుకోండి. సేవ్ చేసే ముందు సమీక్షించండి.\n\nసూచించబడినవి (నేర్చుకోవడం నుండి):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "ఉపకరణం యొక్క రకాన్ని ఎంచుకోండి (వాషింగ్ మెషిన్, డ్రైయర్, డిష్వాషర్, కాఫీ మెషిన్, EV). మెరుగైన సంస్థ కోసం ఈ ట్యాగ్ ప్రతి చక్రంతో నిల్వ చేయబడుతుంది.", + "power_sensor": "సెన్సార్ ఎంటిటీ నిజ-సమయ శక్తిని నివేదిస్తుంది (వాట్స్‌లో). చారిత్రక డేటాను కోల్పోకుండా మీరు దీన్ని ఎప్పుడైనా మార్చవచ్చు-మీరు స్మార్ట్ ప్లగ్‌ని భర్తీ చేస్తే ఉపయోగకరంగా ఉంటుంది.", + "min_power": "ఈ థ్రెషోల్డ్ (వాట్స్‌లో) పైన ఉన్న పవర్ రీడింగ్‌లు ఉపకరణం నడుస్తున్నట్లు సూచిస్తున్నాయి. చాలా పరికరాల కోసం 2Wతో ప్రారంభించండి.", + "off_delay": "సెకన్లు పూర్తయినట్లు గుర్తించడానికి స్మూత్డ్ పవర్ తప్పనిసరిగా స్టాప్ థ్రెషోల్డ్‌కు దిగువన ఉండాలి. డిఫాల్ట్: 120సె.", + "notify_actions": "నోటిఫికేషన్‌ల కోసం అమలు చేయడానికి ఐచ్ఛిక హోమ్ అసిస్టెంట్ యాక్షన్ సీక్వెన్స్ (స్క్రిప్ట్‌లు, నోటిఫై, TTS, మొదలైనవి). సెట్ చేస్తే, ఫాల్‌బ్యాక్ నోటిఫికేషన్ సేవకు ముందు చర్యలు అమలు చేయబడతాయి.", + "notify_people": "ప్రెజెన్స్ గేటింగ్ కోసం ఉపయోగించే వ్యక్తుల ఎంటిటీలు. ఎనేబుల్ చేసినప్పుడు, కనీసం ఒక ఎంచుకున్న వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్ డెలివరీ ఆలస్యం అవుతుంది.", + "notify_only_when_home": "ప్రారంభించబడితే, కనీసం ఎంచుకున్న వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్‌లను ఆలస్యం చేయండి.", + "notify_fire_events": "ప్రారంభించబడితే, ఆటోమేషన్‌లను డ్రైవ్ చేయడానికి సైకిల్ ప్రారంభం మరియు ముగింపు (ha_washdata_cycle_started మరియు ha_washdata_cycle_ended) కోసం హోమ్ అసిస్టెంట్ ఈవెంట్‌లను ప్రారంభించండి.", + "notify_start_services": "చక్రం ప్రారంభమైనప్పుడు అప్రమత్తం చేయడానికి ఒకటి లేదా అంతకంటే ఎక్కువ మంది సేవలకు తెలియజేస్తారు. ప్రారంభ నోటిఫికేషన్‌లను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_finish_services": "ఒకటి లేదా అంతకంటే ఎక్కువ మంది సైకిల్ పూర్తయినప్పుడు లేదా పూర్తవుతున్నప్పుడు అప్రమత్తం చేయడానికి సేవలకు తెలియజేయండి. ముగింపు నోటిఫికేషన్‌లను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_live_services": "సైకిల్ నడుస్తున్నప్పుడు లైవ్ ప్రోగ్రెస్ అప్‌డేట్‌లను స్వీకరించడానికి ఒకటి లేదా అంతకంటే ఎక్కువ మంది సేవలకు తెలియజేస్తారు (మొబైల్ కంపానియన్ యాప్ మాత్రమే). ప్రత్యక్ష ప్రసార నవీకరణలను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_before_end_minutes": "నోటిఫికేషన్‌ను ట్రిగ్గర్ చేయడానికి సైకిల్ అంచనా ముగింపుకు నిమిషాల ముందు. నిలిపివేయడానికి 0కి సెట్ చేయండి. డిఫాల్ట్: 0.", + "notify_title": "నోటిఫికేషన్‌ల కోసం అనుకూల శీర్షిక. {device} ప్లేస్‌హోల్డర్‌కు మద్దతు ఇస్తుంది.", + "notify_icon": "నోటిఫికేషన్‌ల కోసం ఉపయోగించాల్సిన చిహ్నం (ఉదా. mdi:washing-machine). చిహ్నం లేకుండా పంపడానికి ఖాళీగా ఉంచండి.", + "notify_start_message": "చక్రం ప్రారంభమైనప్పుడు సందేశం పంపబడుతుంది. {device} ప్లేస్‌హోల్డర్‌కు మద్దతు ఇస్తుంది.", + "notify_finish_message": "చక్రం ముగిసినప్పుడు సందేశం పంపబడింది. {device}, {duration}, {program}, {energy_kwh}, {cost} ప్లేస్‌హోల్డర్‌లకు మద్దతు ఇస్తుంది.", + "notify_pre_complete_message": "సైకిల్ నడుస్తున్నప్పుడు పునరావృతమయ్యే ప్రత్యక్ష పురోగతి నవీకరణల వచనం. {device}, {minutes}, {program} ప్లేస్‌హోల్డర్‌లకు మద్దతు ఇస్తుంది." + } + }, + "notifications": { + "title": "నోటిఫికేషన్‌లు", + "description": "నోటిఫికేషన్ ఛానెల్‌లు, టెంప్లేట్‌లు మరియు ఐచ్ఛిక ప్రత్యక్ష మొబైల్ ప్రోగ్రెస్ అప్‌డేట్‌లను కాన్ఫిగర్ చేయండి.", + "data": { + "notify_actions": "నోటిఫికేషన్ చర్యలు", + "notify_people": "ఈ వ్యక్తులు ఇంటికి వచ్చే వరకు డెలివరీ ఆలస్యం", + "notify_only_when_home": "ఎంపిక చేయబడిన వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్‌లను ఆలస్యం చేయండి", + "notify_fire_events": "ఆటోమేషన్ ఈవెంట్‌లను ప్రారంభించు", + "notify_start_services": "సైకిల్ ప్రారంభం - నోటిఫికేషన్ లక్ష్యాలు", + "notify_finish_services": "సైకిల్ ముగింపు - నోటిఫికేషన్ లక్ష్యాలు", + "notify_live_services": "ప్రత్యక్ష పురోగతి - నోటిఫికేషన్ లక్ష్యాలు", + "notify_before_end_minutes": "ప్రీ-కంప్లీషన్ నోటిఫికేషన్ (ముగింపుకు నిమిషాల ముందు)", + "notify_live_interval_seconds": "ప్రత్యక్ష నవీకరణ విరామం (సెకన్లు)", + "notify_live_overrun_percent": "లైవ్ అప్‌డేట్ ఓవర్‌రన్ అలవెన్స్ (%)", + "notify_live_chronometer": "క్రోనోమీటర్ కౌంట్‌డౌన్ టైమర్", + "notify_title": "నోటిఫికేషన్ శీర్షిక", + "notify_icon": "నోటిఫికేషన్ చిహ్నం", + "notify_start_message": "సందేశ ఆకృతిని ప్రారంభించండి", + "notify_finish_message": "సందేశ ఆకృతిని ముగించు", + "notify_pre_complete_message": "ప్రీ-కంప్లీషన్ మెసేజ్ ఫార్మాట్", + "energy_price_entity": "శక్తి ధర - ఎంటిటీ (ఐచ్ఛికం)", + "energy_price_static": "శక్తి ధర - స్థిర విలువ (ఐచ్ఛికం)", + "go_back": "సేవ్ చేయకుండా వెనుకకు వెళ్లండి", + "notify_reminder_message": "రిమైండర్ మెసేజ్ ఫార్మాట్", + "notify_timeout_seconds": "తర్వాత (సెకన్లు) స్వయంచాలకంగా తీసివేయండి", + "notify_channel": "నోటిఫికేషన్ ఛానెల్ (స్టేటస్/లైవ్/రిమైండర్)", + "notify_finish_channel": "నోటిఫికేషన్ ఛానెల్ పూర్తయింది" + }, + "data_description": { + "go_back": "దీన్ని టిక్ చేసి Submit క్లిక్ చేస్తే, పై మార్పులన్నీ విస్మరించబడి ప్రధాన మెనూకు తిరిగి వెళ్తారు.", + "notify_actions": "నోటిఫికేషన్‌ల కోసం అమలు చేయడానికి ఐచ్ఛిక హోమ్ అసిస్టెంట్ యాక్షన్ సీక్వెన్స్ (స్క్రిప్ట్‌లు, నోటిఫై, TTS, మొదలైనవి). సెట్ చేస్తే, ఫాల్‌బ్యాక్ నోటిఫికేషన్ సేవకు ముందు చర్యలు అమలు చేయబడతాయి.", + "notify_people": "ప్రెజెన్స్ గేటింగ్ కోసం ఉపయోగించే వ్యక్తుల ఎంటిటీలు. ఎనేబుల్ చేసినప్పుడు, కనీసం ఒక ఎంచుకున్న వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్ డెలివరీ ఆలస్యం అవుతుంది.", + "notify_only_when_home": "ప్రారంభించబడితే, కనీసం ఎంచుకున్న వ్యక్తి ఇంట్లో ఉండే వరకు నోటిఫికేషన్‌లను ఆలస్యం చేయండి.", + "notify_fire_events": "ప్రారంభించబడితే, ఆటోమేషన్‌లను డ్రైవ్ చేయడానికి సైకిల్ ప్రారంభం మరియు ముగింపు (ha_washdata_cycle_started మరియు ha_washdata_cycle_ended) కోసం హోమ్ అసిస్టెంట్ ఈవెంట్‌లను ప్రారంభించండి.", + "notify_start_services": "సైకిల్ ప్రారంభమైనప్పుడు హెచ్చరించడానికి ఒకటి లేదా అంతకంటే ఎక్కువ సేవలకు తెలియజేస్తుంది (ఉదా. notify.mobile_app_my_phone). ప్రారంభ నోటిఫికేషన్‌లను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_finish_services": "ఒకటి లేదా అంతకంటే ఎక్కువ మంది సైకిల్ పూర్తయినప్పుడు లేదా పూర్తవుతున్నప్పుడు అప్రమత్తం చేయడానికి సేవలకు తెలియజేయండి. ముగింపు నోటిఫికేషన్‌లను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_live_services": "సైకిల్ నడుస్తున్నప్పుడు లైవ్ ప్రోగ్రెస్ అప్‌డేట్‌లను స్వీకరించడానికి ఒకటి లేదా అంతకంటే ఎక్కువ మంది సేవలకు తెలియజేస్తారు (మొబైల్ కంపానియన్ యాప్ మాత్రమే). ప్రత్యక్ష ప్రసార నవీకరణలను దాటవేయడానికి ఖాళీగా వదిలివేయండి.", + "notify_before_end_minutes": "నోటిఫికేషన్‌ను ట్రిగ్గర్ చేయడానికి సైకిల్ అంచనా ముగింపుకు నిమిషాల ముందు. నిలిపివేయడానికి 0కి సెట్ చేయండి. డిఫాల్ట్: 0.", + "notify_live_interval_seconds": "రన్ అవుతున్నప్పుడు ప్రోగ్రెస్ అప్‌డేట్‌లు ఎంత తరచుగా పంపబడతాయి. తక్కువ విలువలు మరింత ప్రతిస్పందిస్తాయి కానీ ఎక్కువ నోటిఫికేషన్‌లను వినియోగిస్తాయి. కనిష్టంగా 30 సెకన్లు అమలు చేయబడతాయి - 30 సెకన్ల కంటే తక్కువ విలువలు స్వయంచాలకంగా 30 సెకన్ల వరకు పూర్తి చేయబడతాయి.", + "notify_live_overrun_percent": "సుదీర్ఘమైన/అధికంగా ఉన్న చక్రాల కోసం అంచనా వేసిన అప్‌డేట్ కౌంట్ కంటే ఎక్కువ భద్రతా మార్జిన్ (ఉదాహరణకు 20% 1.2x అంచనా వేసిన అప్‌డేట్‌లను అనుమతిస్తుంది).", + "notify_live_chronometer": "ప్రారంభించబడినప్పుడు, ప్రతి ప్రత్యక్ష నవీకరణ అంచనా ముగింపు సమయానికి క్రోనోమీటర్ కౌంట్‌డౌన్‌ను కలిగి ఉంటుంది. నోటిఫికేషన్ టైమర్ అప్‌డేట్‌ల మధ్య పరికరంలో ఆటోమేటిక్‌గా డౌన్ అవుతుంది (Android కంపానియన్ యాప్ మాత్రమే).", + "notify_title": "నోటిఫికేషన్‌ల కోసం అనుకూల శీర్షిక. {device} ప్లేస్‌హోల్డర్‌కు మద్దతు ఇస్తుంది.", + "notify_icon": "నోటిఫికేషన్‌ల కోసం ఉపయోగించాల్సిన చిహ్నం (ఉదా. mdi:washing-machine). చిహ్నం లేకుండా పంపడానికి ఖాళీగా ఉంచండి.", + "notify_start_message": "చక్రం ప్రారంభమైనప్పుడు సందేశం పంపబడుతుంది. {device} ప్లేస్‌హోల్డర్‌కు మద్దతు ఇస్తుంది.", + "notify_finish_message": "చక్రం ముగిసినప్పుడు సందేశం పంపబడింది. {device}, {duration}, {program}, {energy_kwh}, {cost} ప్లేస్‌హోల్డర్‌లకు మద్దతు ఇస్తుంది.", + "energy_price_entity": "ప్రస్తుత విద్యుత్ ధర (ఉదా. sensor.electricity_price, input_number.energy_rate)ని కలిగి ఉన్న సంఖ్యా ఎంటిటీని ఎంచుకోండి. సెట్ చేసినప్పుడు, {cost} ప్లేస్‌హోల్డర్‌ని ప్రారంభిస్తుంది. రెండూ కాన్ఫిగర్ చేయబడితే స్టాటిక్ విలువ కంటే ప్రాధాన్యతనిస్తుంది.", + "energy_price_static": "kWhకి స్థిర విద్యుత్ ధర. సెట్ చేసినప్పుడు, {cost} ప్లేస్‌హోల్డర్‌ని ప్రారంభిస్తుంది. ఒక ఎంటిటీ పైన కాన్ఫిగర్ చేయబడితే విస్మరించబడుతుంది. సందేశ టెంప్లేట్‌లో మీ కరెన్సీ చిహ్నాన్ని జోడించండి, ఉదా. {cost} €.", + "notify_reminder_message": "వన్-టైమ్ రిమైండర్ యొక్క వచనం అంచనా వేయబడిన ముగింపుకు ముందు కాన్ఫిగర్ చేయబడిన నిమిషాల సంఖ్యను పంపింది. ఎగువ పునరావృతమయ్యే ప్రత్యక్ష ప్రసార నవీకరణల నుండి భిన్నంగా ఉంటుంది. {device}, {minutes}, {program} ప్లేస్‌హోల్డర్‌లకు మద్దతు ఇస్తుంది.", + "notify_timeout_seconds": "చాలా సెకన్ల తర్వాత నోటిఫికేషన్‌లను స్వయంచాలకంగా తీసివేయండి (సహచర యాప్ 'సమయం ముగిసింది'గా ఫార్వార్డ్ చేయబడింది). తీసివేయబడే వరకు వాటిని ఉంచడానికి 0కి సెట్ చేయండి. డిఫాల్ట్: 0.", + "notify_channel": "స్థితి, ప్రత్యక్ష పురోగతి మరియు రిమైండర్ నోటిఫికేషన్‌ల కోసం Android సహచర యాప్ నోటిఫికేషన్ ఛానెల్. ఛానెల్ పేరును మొదటిసారి ఉపయోగించినప్పుడు ఛానెల్ యొక్క ధ్వని మరియు ప్రాముఖ్యత సహచర యాప్‌లో కాన్ఫిగర్ చేయబడతాయి - WashData ఛానెల్ పేరును మాత్రమే సెట్ చేస్తుంది. యాప్ డిఫాల్ట్‌గా ఖాళీగా ఉంచండి.", + "notify_finish_channel": "పూర్తి హెచ్చరిక కోసం Android ఛానెల్‌ని వేరు చేయండి (రిమైండర్ మరియు లాండ్రీ-వెయిటింగ్ నాగ్ కూడా ఉపయోగించబడుతుంది) దాని స్వంత ధ్వనిని కలిగి ఉంటుంది. ఎగువ ఛానెల్‌ని మళ్లీ ఉపయోగించడానికి ఖాళీగా ఉంచండి.", + "notify_pre_complete_message": "సైకిల్ నడుస్తున్నప్పుడు పునరావృతమయ్యే ప్రత్యక్ష పురోగతి నవీకరణల వచనం. {device}, {minutes}, {program} ప్లేస్‌హోల్డర్‌లకు మద్దతు ఇస్తుంది." + } + }, + "advanced_settings": { + "title": "అధునాతన సెట్టింగ్‌లు", + "description": "ట్యూన్ డిటెక్షన్ థ్రెషోల్డ్‌లు, లెర్నింగ్ మరియు అధునాతన ప్రవర్తన. చాలా సెటప్‌లకు డిఫాల్ట్‌లు బాగా పని చేస్తాయి.", + "sections": { + "suggestions_section": { + "name": "సూచించబడిన సెట్టింగ్‌లు", + "description": "{suggestions_count} నేర్చుకున్న సూచనలు అందుబాటులో ఉన్నాయి. సేవ్ చేయడానికి ముందు ప్రతిపాదిత మార్పులను సమీక్షించడానికి 'సూచించబడిన విలువలను వర్తింపజేయండి'ని టిక్ చేయండి.", + "data": { + "apply_suggestions": "సూచించబడిన విలువలను వర్తింపజేయండి" + }, + "data_description": { + "apply_suggestions": "అభ్యాస అల్గారిథమ్ నుండి సూచించబడిన విలువల కోసం సమీక్ష దశను తెరవడానికి ఈ పెట్టెను ఎంచుకోండి. ఏదీ స్వయంచాలకంగా సేవ్ చేయబడదు.\n\nసూచించబడినవి (నేర్చుకోవడం నుండి):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "గుర్తింపు & పవర్ థ్రెషోల్డ్‌లు", + "description": "పరికరం ఎప్పుడు ON లేదా OFFగా పరిగణించబడుతుంది, ఎనర్జీ గేట్లు, మరియు స్థితి మార్పుల సమయ నియంత్రణ.", + "data": { + "start_duration_threshold": "డీబౌన్స్ వ్యవధి (లు) ప్రారంభించండి", + "start_energy_threshold": "ఎనర్జీ గేట్‌ను ప్రారంభించండి (Wh)", + "completion_min_seconds": "పూర్తి కనీస రన్‌టైమ్ (సెకన్లు)", + "start_threshold_w": "ప్రారంభ థ్రెషోల్డ్ (W)", + "stop_threshold_w": "స్టాప్ థ్రెషోల్డ్ (W)", + "end_energy_threshold": "ఎండ్ ఎనర్జీ గేట్ (Wh)", + "running_dead_zone": "రన్నింగ్ డెడ్ జోన్ (సెకన్లు)", + "end_repeat_count": "ఎండ్ రిపీట్ కౌంట్", + "min_off_gap": "చక్రాల (ల) మధ్య కనిష్ట అంతరం", + "sampling_interval": "నమూనా విరామం (లు)" + }, + "data_description": { + "start_duration_threshold": "ఈ వ్యవధి (సెకన్లు) కంటే తక్కువ సంక్షిప్త పవర్ స్పైక్‌లను విస్మరించండి. రియల్ సైకిల్ ప్రారంభమయ్యే ముందు బూట్ స్పైక్‌లను (ఉదా., 2సెకి 60W) ఫిల్టర్ చేస్తుంది. డిఫాల్ట్: 5సె.", + "start_energy_threshold": "నిర్ధారించడానికి START దశలో చక్రం కనీసం ఇంత శక్తిని (Wh) కూడబెట్టుకోవాలి. డిఫాల్ట్: 0.005 Wh.", + "completion_min_seconds": "దీని కంటే చిన్న సైకిల్‌లు సహజంగా పూర్తి చేసినప్పటికీ 'ఇంటరప్టెడ్' అని గుర్తు పెట్టబడతాయి. డిఫాల్ట్: 600లు.", + "start_threshold_w": "చక్రాన్ని ప్రారంభించడానికి శక్తి తప్పనిసరిగా ఈ థ్రెషోల్డ్ పైన పెరగాలి. మీ స్టాండ్‌బై పవర్ కంటే ఎక్కువగా సెట్ చేయండి. డిఫాల్ట్: min_power + 1 W.", + "stop_threshold_w": "చక్రాన్ని ముగించడానికి శక్తి తప్పనిసరిగా ఈ థ్రెషోల్డ్‌కి దిగువన పడిపోతుంది. తప్పుడు చివరలను ప్రేరేపించే శబ్దాన్ని నివారించడానికి సున్నా కంటే కొంచెం పైన సెట్ చేయండి. డిఫాల్ట్: 0.6 * min_power.", + "end_energy_threshold": "చివరి ఆఫ్-డిలే విండోలో శక్తి ఈ విలువ (Wh) కంటే తక్కువగా ఉంటే మాత్రమే చక్రం ముగుస్తుంది. శక్తి సున్నాకి సమీపంలో హెచ్చుతగ్గులకు గురైనట్లయితే తప్పుడు ముగింపులను నివారిస్తుంది. డిఫాల్ట్: 0.05 Wh.", + "running_dead_zone": "సైకిల్ ప్రారంభమైన తర్వాత, ప్రారంభ స్పిన్-అప్ దశలో తప్పుడు ముగింపు గుర్తింపును నిరోధించడానికి చాలా సెకన్ల పాటు పవర్ డిప్‌లను విస్మరించండి. నిలిపివేయడానికి 0కి సెట్ చేయండి. డిఫాల్ట్: 0సె.", + "end_repeat_count": "సైకిల్‌ను ముగించే ముందు ఎండ్ కండిషన్ (ఆఫ్_డిలే కోసం థ్రెషోల్డ్ కంటే తక్కువ పవర్) తప్పనిసరిగా ఎన్నిసార్లు ఉండాలి. అధిక విలువలు పాజ్‌ల సమయంలో తప్పుడు ముగింపులను నివారిస్తాయి. డిఫాల్ట్: 1.", + "min_off_gap": "ఒక చక్రం అంతకు ముందు ముగిసిన చాలా సెకన్లలోపు ప్రారంభమైతే, అవి ఒకే చక్రంలో విలీనం చేయబడతాయి.", + "sampling_interval": "సెకన్లలో కనీస నమూనా విరామం. ఈ రేటు కంటే వేగవంతమైన నవీకరణలు విస్మరించబడతాయి. డిఫాల్ట్: 30సె (వాషింగ్ మెషీన్‌లు, వాషర్-డ్రైయర్‌లు మరియు డిష్‌వాషర్‌ల కోసం 2సె)." + } + }, + "matching_section": { + "name": "ప్రొఫైల్ మ్యాచ్ింగ్ & లెర్నింగ్", + "description": "WashData నడుస్తున్న సైకిళ్లను నేర్చుకున్న ప్రొఫైల్‌లతో ఎంత దూకుడుగా సరిపోల్చుతుంది మరియు ఎప్పుడు ఫీడ్‌బ్యాక్ కోరుతుంది.", + "data": { + "profile_match_min_duration_ratio": "ప్రొఫైల్ మ్యాచ్ కనిష్ట వ్యవధి నిష్పత్తి (0.1-1.0)", + "profile_match_interval": "ప్రొఫైల్ సరిపోలిక విరామం (సెకన్లు)", + "profile_match_threshold": "ప్రొఫైల్ మ్యాచ్ థ్రెషోల్డ్", + "profile_unmatch_threshold": "ప్రొఫైల్ సరిపోలని థ్రెషోల్డ్", + "auto_label_confidence": "స్వీయ లేబుల్ విశ్వాసం (0-1)", + "learning_confidence": "అభిప్రాయ అభ్యర్థన విశ్వాసం (0-1)", + "suppress_feedback_notifications": "ఫీడ్‌బ్యాక్ నోటిఫికేషన్‌లను అణచివేయండి", + "duration_tolerance": "వ్యవధి సహనం (0-0.5)", + "smoothing_window": "స్మూతింగ్ విండో (నమూనాలు)", + "profile_duration_tolerance": "ప్రొఫైల్ మ్యాచ్ వ్యవధి సహనం (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "ప్రొఫైల్ సరిపోలిక కోసం కనీస వ్యవధి నిష్పత్తి. రన్నింగ్ సైకిల్ సరిపోలడానికి ప్రొఫైల్ వ్యవధిలో కనీసం ఈ భాగానికి అయినా ఉండాలి. దిగువ = ముందు సరిపోలిక. డిఫాల్ట్: 0.50 (50%).", + "profile_match_interval": "ఒక సైకిల్ సమయంలో ప్రొఫైల్ మ్యాచింగ్‌ని ఎంత తరచుగా (సెకన్లలో) ప్రయత్నించాలి. తక్కువ విలువలు వేగంగా గుర్తింపును అందిస్తాయి కానీ ఎక్కువ CPUని ఉపయోగిస్తాయి. డిఫాల్ట్: 300సె (5 నిమిషాలు).", + "profile_match_threshold": "ప్రొఫైల్‌ను మ్యాచ్‌గా పరిగణించడానికి కనీస DTW సారూప్యత స్కోర్ (0.0–1.0) అవసరం. అధిక = కఠినమైన సరిపోలిక, తక్కువ తప్పుడు పాజిటివ్‌లు. డిఫాల్ట్: 0.4.", + "profile_unmatch_threshold": "DTW సారూప్యత స్కోర్ (0.0–1.0) క్రింద గతంలో సరిపోలిన ప్రొఫైల్ తిరస్కరించబడింది. మినుకుమినుకుమనకుండా నిరోధించడానికి మ్యాచ్ థ్రెషోల్డ్ కంటే తక్కువగా ఉండాలి. డిఫాల్ట్: 0.35.", + "auto_label_confidence": "పూర్తయిన తర్వాత (0.0–1.0) ఈ విశ్వాసం వద్ద లేదా అంతకంటే ఎక్కువ సైకిల్‌లను స్వయంచాలకంగా లేబుల్ చేయండి.", + "learning_confidence": "ఈవెంట్ ద్వారా వినియోగదారు ధృవీకరణను అభ్యర్థించడానికి కనీస విశ్వాసం (0.0–1.0).", + "suppress_feedback_notifications": "ప్రారంభించబడినప్పుడు, WashData ఇప్పటికీ అంతర్గతంగా ఫీడ్‌బ్యాక్‌ని ట్రాక్ చేస్తుంది మరియు అభ్యర్థిస్తుంది కానీ చక్రాలను నిర్ధారించమని మిమ్మల్ని కోరుతూ నిరంతర నోటిఫికేషన్‌లను చూపదు. సైకిల్‌లు ఎల్లప్పుడూ సరిగ్గా గుర్తించబడినప్పుడు మరియు ప్రాంప్ట్‌లు పరధ్యానంగా అనిపించినప్పుడు ఉపయోగకరంగా ఉంటుంది.", + "duration_tolerance": "పరుగు సమయంలో మిగిలి ఉన్న అంచనాల కోసం సహనం (0.0–0.5 0–50%కి అనుగుణంగా ఉంటుంది).", + "smoothing_window": "కదిలే-సగటు స్మూటింగ్ కోసం ఉపయోగించిన ఇటీవలి పవర్ రీడింగ్‌ల సంఖ్య.", + "profile_duration_tolerance": "మొత్తం వ్యవధి వ్యత్యాసానికి (0.0–0.5) ప్రొఫైల్ సరిపోలిక ద్వారా ఉపయోగించబడే సహనం. డిఫాల్ట్ ప్రవర్తన: ±25%." + } + }, + "timing_section": { + "name": "టైమింగ్, నిర్వహణ & డీబగ్", + "description": "వాచ్‌డాగ్, ప్రోగ్రెస్ రీసెట్, స్వీయ నిర్వహణ, మరియు డీబగ్ ఎంటిటీల ప్రదర్శన.", + "data": { + "watchdog_interval": "వాచ్‌డాగ్ విరామం (సెకన్లు)", + "no_update_active_timeout": "ఏ-నవీకరణ సమయం ముగిసింది (లు)", + "progress_reset_delay": "ప్రోగ్రెస్ రీసెట్ ఆలస్యం (సెకన్లు)", + "auto_maintenance": "స్వీయ నిర్వహణను ప్రారంభించండి", + "expose_debug_entities": "డీబగ్ ఎంటిటీలను బహిర్గతం చేయండి", + "save_debug_traces": "డీబగ్ ట్రేస్‌లను సేవ్ చేయండి" + }, + "data_description": { + "watchdog_interval": "నడుస్తున్నప్పుడు వాచ్‌డాగ్ తనిఖీల మధ్య సెకన్లు. డిఫాల్ట్: 5సె. హెచ్చరిక: తప్పుడు స్టాప్‌లను నివారించడానికి ఇది మీ సెన్సార్ అప్‌డేట్ విరామం కంటే ఎక్కువగా ఉందని నిర్ధారించుకోండి (ఉదా., సెన్సార్ ప్రతి 60 సెకన్లకు అప్‌డేట్ చేయబడితే, 65సె+ ఉపయోగించండి).", + "no_update_active_timeout": "పబ్లిష్-ఆన్-చేంజ్ సెన్సార్‌ల కోసం: ఇన్ని సెకన్ల వరకు ఎటువంటి అప్‌డేట్‌లు రాకుంటే మాత్రమే యాక్టివ్ సైకిల్‌ను బలవంతంగా ముగించండి. తక్కువ-పవర్ పూర్తి చేయడం ఇప్పటికీ ఆఫ్ ఆలస్యాన్ని ఉపయోగిస్తుంది.", + "progress_reset_delay": "చక్రం పూర్తయిన తర్వాత (100%), ప్రోగ్రెస్‌ని 0%కి రీసెట్ చేయడానికి ముందు చాలా సెకన్లపాటు నిష్క్రియంగా వేచి ఉండండి. డిఫాల్ట్: 300సె.", + "auto_maintenance": "స్వీయ-నిర్వహణను ప్రారంభించండి (నమూనాలను మరమ్మతు చేయండి, శకలాలు విలీనం చేయండి).", + "expose_debug_entities": "డీబగ్గింగ్ కోసం అధునాతన సెన్సార్‌లను (విశ్వాసం, దశ, సందిగ్ధత) చూపండి.", + "save_debug_traces": "చరిత్రలో వివరణాత్మక ర్యాంకింగ్ మరియు పవర్ ట్రేస్ డేటాను నిల్వ చేయండి (నిల్వ వినియోగాన్ని పెంచుతుంది)." + } + }, + "anti_wrinkle_section": { + "name": "యాంటీ రింక్ల్ షీల్డ్ (డ్రైయర్ మాత్రమే)", + "description": "దెయ్యం చక్రాలను నివారించడానికి చక్రం ముగిసిన తర్వాత డ్రమ్ భ్రమణాలను విస్మరించండి. డ్రైయర్ / వాషర్-డ్రైయర్‌కు మాత్రమే.", + "data": { + "anti_wrinkle_enabled": "యాంటీ రింక్ల్ షీల్డ్ (డ్రైయర్ మాత్రమే)", + "anti_wrinkle_max_power": "యాంటీ రింకిల్ మాక్స్ పవర్ (W)", + "anti_wrinkle_max_duration": "యాంటీ రింకిల్ గరిష్ట వ్యవధి (లు)", + "anti_wrinkle_exit_power": "యాంటీ రింకిల్ ఎగ్జిట్ పవర్ (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "ఘోస్ట్ సైకిల్స్ (డ్రైయర్/వాషర్-డ్రైయర్ మాత్రమే) నిరోధించడానికి సైకిల్ ముగిసిన తర్వాత తక్కువ-పవర్ డ్రమ్ భ్రమణాలను విస్మరించండి.", + "anti_wrinkle_max_power": "ఈ శక్తి వద్ద లేదా అంతకంటే దిగువన ఉన్న పేలుళ్లు కొత్త చక్రాలకు బదులుగా ముడుతలను వ్యతిరేకించే భ్రమణాలుగా పరిగణించబడతాయి. యాంటీ రింకిల్ షీల్డ్ ప్రారంభించబడినప్పుడు మాత్రమే వర్తిస్తుంది.", + "anti_wrinkle_max_duration": "యాంటీ రింక్ల్ రొటేషన్‌గా పరిగణించడానికి గరిష్ట బర్స్ట్ పొడవు. యాంటీ రింకిల్ షీల్డ్ ప్రారంభించబడినప్పుడు మాత్రమే వర్తిస్తుంది.", + "anti_wrinkle_exit_power": "పవర్ ఈ స్థాయి కంటే తక్కువగా ఉంటే, వెంటనే యాంటీ రింక్ల్ నుండి నిష్క్రమించండి (ట్రూ ఆఫ్). యాంటీ రింకిల్ షీల్డ్ ప్రారంభించబడినప్పుడు మాత్రమే వర్తిస్తుంది." + } + }, + "delay_start_section": { + "name": "ఆలస్యమైన ప్రారంభ గుర్తింపు", + "description": "చక్రం నిజంగా ప్రారంభమయ్యే వరకు నిరంతర తక్కువ-పవర్ స్టాండ్‌బైని 'వెయిటింగ్ టు స్టార్ట్'గా పరిగణించండి.", + "data": { + "delay_start_detect_enabled": "ఆలస్యమైన ప్రారంభ గుర్తింపును ప్రారంభించండి", + "delay_confirm_seconds": "ఆలస్యమైన ప్రారంభం: స్టాండ్‌బై నిర్ధారణ సమయం (సె)", + "delay_timeout_hours": "ఆలస్యంగా ప్రారంభం: గరిష్ట నిరీక్షణ సమయం (గం)" + }, + "data_description": { + "delay_start_detect_enabled": "ప్రారంభించబడినప్పుడు, క్లుప్తమైన తక్కువ-పవర్ డ్రెయిన్ స్పైక్ తర్వాత స్థిరమైన స్టాండ్‌బై పవర్ ఆలస్యంగా ప్రారంభించినట్లు గుర్తించబడుతుంది. రాష్ట్ర సెన్సార్ ఆలస్యం సమయంలో 'వెయిటింగ్ టు స్టార్ట్'ని చూపుతుంది. చక్రం వాస్తవానికి ప్రారంభమయ్యే వరకు ప్రోగ్రామ్ సమయం లేదా పురోగతి ట్రాక్ చేయబడదు. డ్రెయిన్ స్పైక్ పవర్ పైన సెట్ చేయడానికి start_threshold_w అవసరం.", + "delay_confirm_seconds": "WashData 'వెయిటింగ్ టు స్టార్ట్'లోకి వెళ్లే ముందు పవర్ తప్పనిసరిగా స్టాప్ థ్రెషోల్డ్ (W) మరియు స్టార్ట్ థ్రెషోల్డ్ (W) మధ్య ఇన్ని సెకన్లు ఉండాలి. ఇది మెనూ నావిగేషన్‌లో వచ్చే చిన్న పీక్స్‌ను ఫిల్టర్ చేస్తుంది. డిఫాల్ట్: 60 సె.", + "delay_timeout_hours": "సురక్షిత సమయం ముగిసింది: యంత్రం ఇన్ని గంటల తర్వాత కూడా 'ప్రారంభం కోసం వేచి ఉంది'లో ఉంటే, WashData ఆఫ్‌కి రీసెట్ చేయబడుతుంది. డిఫాల్ట్: 8 గం." + } + }, + "external_triggers_section": { + "name": "బాహ్య ట్రిగ్గర్‌లు, తలుపు & పాజ్", + "description": "ఐచ్ఛిక బాహ్య ముగింపు-ట్రిగ్గర్ సెన్సార్, పాజ్/క్లీన్ గుర్తింపుకు తలుపు సెన్సార్, మరియు పాజ్ సమయంలో పవర్ కట్ స్విచ్.", + "data": { + "external_end_trigger_enabled": "బాహ్య సైకిల్ ముగింపు ట్రిగ్గర్‌ని ప్రారంభించండి", + "external_end_trigger": "బాహ్య సైకిల్ ముగింపు సెన్సార్", + "external_end_trigger_inverted": "విలోమం ట్రిగ్గర్ లాజిక్ (ట్రిగ్గర్ ఆన్ ఆఫ్)", + "door_sensor_entity": "డోర్ సెన్సార్", + "pause_cuts_power": "పాజ్ చేస్తున్నప్పుడు పవర్ కట్", + "switch_entity": "స్విచ్ ఎంటిటీ (పాజ్ పవర్ కట్ కోసం)", + "notify_unload_delay_minutes": "లాండ్రీ వెయిటింగ్ నోటిఫికేషన్ ఆలస్యం (నిమి)" + }, + "data_description": { + "external_end_trigger_enabled": "సైకిల్ పూర్తి చేయడాన్ని ట్రిగ్గర్ చేయడానికి బాహ్య బైనరీ సెన్సార్ పర్యవేక్షణను ప్రారంభించండి.", + "external_end_trigger": "బైనరీ సెన్సార్ ఎంటిటీని ఎంచుకోండి. ఈ సెన్సార్ ట్రిగ్గర్ అయినప్పుడు, ప్రస్తుత చక్రం 'పూర్తయింది' అనే స్థితితో ముగుస్తుంది.", + "external_end_trigger_inverted": "డిఫాల్ట్‌గా, సెన్సార్ ఆన్ చేసినప్పుడు ట్రిగ్గర్ మండుతుంది. బదులుగా సెన్సార్ ఆఫ్ అయినప్పుడు కాల్చడానికి ఈ పెట్టెను ఎంచుకోండి.", + "door_sensor_entity": "మెషిన్ డోర్ కోసం ఐచ్ఛిక బైనరీ సెన్సార్ (ఆన్ = ఓపెన్, ఆఫ్ = మూసివేయబడింది). యాక్టివ్ సైకిల్ సమయంలో తలుపు తెరిచినప్పుడు, WashData సైకిల్‌ను ఉద్దేశపూర్వకంగా పాజ్ చేసినట్లు నిర్ధారిస్తుంది. చక్రం ముగిసిన తర్వాత, తలుపు ఇంకా మూసివేయబడితే, తలుపు తెరవబడే వరకు స్థితి 'క్లీన్'కి మారుతుంది. గమనిక: తలుపును మూసివేయడం వలన పాజ్ చేయబడిన సైకిల్ స్వయంచాలకంగా పునఃప్రారంభించబడదు - పునఃప్రారంభం తప్పక రెజ్యూమ్ సైకిల్ బటన్ లేదా సేవ ద్వారా మాన్యువల్‌గా ట్రిగ్గర్ చేయబడాలి.", + "pause_cuts_power": "పాజ్ సైకిల్ బటన్ లేదా సేవ ద్వారా పాజ్ చేస్తున్నప్పుడు, కాన్ఫిగర్ చేయబడిన స్విచ్ ఎంటిటీని కూడా ఆఫ్ చేయండి. ఈ పరికరం కోసం స్విచ్ ఎంటిటీని కాన్ఫిగర్ చేసినట్లయితే మాత్రమే ప్రభావం చూపుతుంది.", + "switch_entity": "పాజ్ చేస్తున్నప్పుడు లేదా పునఃప్రారంభించేటప్పుడు టోగుల్ చేయడానికి స్విచ్ ఎంటిటీ. 'పాజ్ చేస్తున్నప్పుడు పవర్ కట్' ప్రారంభించబడినప్పుడు అవసరం.", + "notify_unload_delay_minutes": "సైకిల్ ముగిసిన తర్వాత మరియు ఇన్ని నిమిషాల వరకు తలుపు తెరవబడన తర్వాత ముగింపు నోటిఫికేషన్ ఛానెల్ ద్వారా నోటిఫికేషన్‌ను పంపండి (డోర్ సెన్సార్ అవసరం). నిలిపివేయడానికి 0కి సెట్ చేయండి. డిఫాల్ట్: 60 నిమి." + } + }, + "pump_section": { + "name": "పంప్ మానిటర్", + "description": "పంప్ పరికర రకానికి మాత్రమే. పంప్ సైకిల్ ఈ వ్యవధిని మించి నడిస్తే ఒక ఈవెంట్‌ను ట్రిగ్గర్ చేస్తుంది.", + "data": { + "pump_stuck_duration": "పంప్ స్టక్ అలర్ట్ థ్రెషోల్డ్ (లు) (పంప్ మాత్రమే)" + }, + "data_description": { + "pump_stuck_duration": "ఇన్ని సెకన్ల తర్వాత కూడా పంప్ సైకిల్ నడుస్తుంటే, ha_washdata_pump_stuck ఈవెంట్ ఒకసారి తొలగించబడుతుంది. జామ్ అయిన మోటారును గుర్తించడానికి దీన్ని ఉపయోగించండి (సాధారణ సంప్ పంప్ రన్ 60 సెకన్లలోపు ఉంటుంది). డిఫాల్ట్: 1800 సె (30 నిమి). పంప్ పరికరం రకం మాత్రమే." + } + }, + "device_link_section": { + "name": "పరికర లింక్", + "description": "ఐచ్ఛికంగా ఈ WashData పరికరాన్ని ఇప్పటికే ఉన్న Home Assistant పరికరానికి లింక్ చేయండి (ఉదాహరణకు స్మార్ట్ ప్లగ్ లేదా ఉపకరణం కూడా). WashData పరికరం ఆ పరికరం \"ద్వారా కనెక్ట్ చేయబడింది\"గా చూపబడుతుంది. వాష్‌డేటాను స్వతంత్ర పరికరంగా ఉంచడానికి ఖాళీగా ఉంచండి.", + "data": { + "linked_device": "లింక్ చేయబడిన పరికరం" + }, + "data_description": { + "linked_device": "WashDataని కనెక్ట్ చేయడానికి పరికరాన్ని ఎంచుకోండి. ఇది పరికర రిజిస్ట్రీలో 'కనెక్ట్ ద్వారా' సూచనను జోడిస్తుంది; WashData దాని స్వంత పరికర కార్డ్ మరియు ఎంటిటీలను ఉంచుతుంది. లింక్‌ను తీసివేయడానికి ఎంపికను క్లియర్ చేయండి." + } + } + } + }, + "diagnostics": { + "title": "డయాగ్నోస్టిక్స్ & మెయింటెనెన్స్", + "description": "విచ్ఛిన్నమైన చక్రాలను విలీనం చేయడం లేదా నిల్వ చేసిన డేటాను తరలించడం వంటి నిర్వహణ చర్యలను అమలు చేయండి.\n\n**నిల్వ వినియోగం**\n\n- ఫైల్ పరిమాణం: {file_size_kb} KB\n- Cycles: {cycle_count}\n- ప్రొఫైల్‌లు: {profile_count}\n- డీబగ్ జాడలు: {debug_count}", + "menu_options": { + "reprocess_history": "నిర్వహణ: రీప్రాసెస్ & డేటాను ఆప్టిమైజ్ చేయండి", + "clear_debug_data": "డీబగ్ డేటాను క్లియర్ చేయండి (స్థలాన్ని ఖాళీ చేయండి)", + "wipe_history": "ఈ పరికరం కోసం మొత్తం డేటాను తుడిచివేయండి (తిరుగులేనిది)", + "export_import": "సెట్టింగ్‌లతో JSONని ఎగుమతి/దిగుమతి చేయండి (కాపీ/పేస్ట్)", + "menu_back": "← వెనుకకు" + } + }, + "clear_debug_data": { + "title": "డీబగ్ డేటాను క్లియర్ చేయండి", + "description": "మీరు నిల్వ చేయబడిన అన్ని డీబగ్ ట్రేస్‌లను ఖచ్చితంగా తొలగించాలనుకుంటున్నారా? ఇది స్థలాన్ని ఖాళీ చేస్తుంది కానీ గత చక్రాల నుండి వివరణాత్మక ర్యాంకింగ్ సమాచారాన్ని తీసివేస్తుంది." + }, + "manage_cycles": { + "title": "సైకిళ్లను నిర్వహించండి", + "description": "ఇటీవలి చక్రాలు:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "పాత సైకిళ్లను ఆటో-లేబుల్ చేయండి", + "select_cycle_to_label": "లేబుల్ నిర్దిష్ట చక్రం", + "select_cycle_to_delete": "సైకిల్‌ను తొలగించండి", + "interactive_editor": "ఇంటరాక్టివ్ ఎడిటర్‌ను విలీనం చేయండి/స్ప్లిట్ చేయండి", + "trim_cycle_select": "సైకిల్ డేటాను ట్రిమ్ చేయండి", + "menu_back": "← వెనుకకు" + } + }, + "manage_cycles_empty": { + "title": "సైకిళ్లను నిర్వహించండి", + "description": "ఇంకా ఏ సైకిల్‌లు రికార్డ్ చేయబడలేదు.", + "menu_options": { + "auto_label_cycles": "పాత సైకిళ్లను ఆటో-లేబుల్ చేయండి", + "menu_back": "← వెనుకకు" + } + }, + "manage_profiles": { + "title": "ప్రొఫైల్‌లను నిర్వహించండి", + "description": "ప్రొఫైల్ సారాంశం:\n{profile_summary}", + "menu_options": { + "create_profile": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "edit_profile": "ప్రొఫైల్‌ని సవరించండి/పేరుమార్చు", + "delete_profile_select": "ప్రొఫైల్‌ను తొలగించండి", + "profile_stats": "ప్రొఫైల్ గణాంకాలు", + "cleanup_profile": "చరిత్రను క్లీన్ అప్ చేయండి - గ్రాఫ్ & డిలీట్", + "assign_profile_phases_select": "దశ పరిధులను కేటాయించండి", + "menu_back": "← వెనుకకు" + } + }, + "manage_profiles_empty": { + "title": "ప్రొఫైల్‌లను నిర్వహించండి", + "description": "ఇంకా ప్రొఫైల్‌లు ఏవీ సృష్టించబడలేదు.", + "menu_options": { + "create_profile": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "menu_back": "← వెనుకకు" + } + }, + "manage_phase_catalog": { + "title": "దశ కేటలాగ్‌ని నిర్వహించండి", + "description": "దశ కేటలాగ్ (ఈ పరికరం కోసం డిఫాల్ట్‌లు + అనుకూల దశలు):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "కొత్త దశను సృష్టించండి", + "phase_catalog_edit_select": "దశను సవరించండి", + "phase_catalog_delete": "దశను తొలగించండి", + "menu_back": "← వెనుకకు" + } + }, + "phase_catalog_create": { + "title": "అనుకూల దశను సృష్టించండి", + "description": "అనుకూల దశ పేరు మరియు వివరణను సృష్టించండి మరియు ఇది ఏ పరికర రకాన్ని వర్తింపజేయాలో ఎంచుకోండి.", + "data": { + "target_device_type": "టార్గెట్ పరికరం రకం", + "phase_name": "దశ పేరు", + "phase_description": "దశ వివరణ" + } + }, + "phase_catalog_edit_select": { + "title": "అనుకూల దశను సవరించండి", + "description": "సవరించడానికి అనుకూల దశను ఎంచుకోండి.", + "data": { + "phase_name": "అనుకూల దశ" + } + }, + "phase_catalog_edit": { + "title": "అనుకూల దశను సవరించండి", + "description": "ఎడిటింగ్ దశ: {phase_name}", + "data": { + "phase_name": "దశ పేరు", + "phase_description": "దశ వివరణ" + } + }, + "phase_catalog_delete": { + "title": "అనుకూల దశను తొలగించండి", + "description": "తొలగించడానికి అనుకూల దశను ఎంచుకోండి. ఈ దశను ఉపయోగించి ఏవైనా కేటాయించబడిన పరిధులు తీసివేయబడతాయి.", + "data": { + "phase_name": "అనుకూల దశ" + } + }, + "assign_profile_phases": { + "title": "దశ పరిధులను కేటాయించండి", + "description": "ప్రొఫైల్ కోసం దశ ప్రివ్యూ: **{profile_name}**\n\n{timeline_svg}\n\n**ప్రస్తుత పరిధులు:**\n{current_ranges}\n\nపరిధులను జోడించడానికి, సవరించడానికి, తొలగించడానికి లేదా సేవ్ చేయడానికి దిగువ చర్యలను ఉపయోగించండి.", + "menu_options": { + "assign_profile_phases_add": "దశ పరిధిని జోడించండి", + "assign_profile_phases_edit_select": "దశ పరిధిని సవరించండి", + "assign_profile_phases_delete": "దశ పరిధిని తొలగించండి", + "phase_ranges_clear": "అన్ని పరిధులను క్లియర్ చేయండి", + "assign_profile_phases_auto_detect": "దశలను స్వయంచాలకంగా గుర్తించండి", + "phase_ranges_save": "సేవ్ మరియు తిరిగి", + "menu_back": "← వెనుకకు" + } + }, + "assign_profile_phases_add": { + "title": "దశ పరిధిని జోడించండి", + "description": "ప్రస్తుత డ్రాఫ్ట్‌కు ఒక దశ పరిధిని జోడించండి.", + "data": { + "phase_name": "దశ", + "start_min": "ప్రారంభ నిమిషం", + "end_min": "ముగింపు నిమిషం" + } + }, + "assign_profile_phases_edit_select": { + "title": "దశ పరిధిని సవరించండి", + "description": "సవరించడానికి పరిధిని ఎంచుకోండి.", + "data": { + "range_index": "దశ పరిధి" + } + }, + "assign_profile_phases_edit": { + "title": "దశ పరిధిని సవరించండి", + "description": "ఎంచుకున్న దశ పరిధిని నవీకరించండి.", + "data": { + "phase_name": "దశ", + "start_min": "ప్రారంభ నిమిషం", + "end_min": "ముగింపు నిమిషం" + } + }, + "assign_profile_phases_delete": { + "title": "దశ పరిధిని తొలగించండి", + "description": "డ్రాఫ్ట్ నుండి తీసివేయడానికి పరిధిని ఎంచుకోండి.", + "data": { + "range_index": "దశ పరిధి" + } + }, + "assign_profile_phases_auto_detect": { + "title": "స్వయంచాలకంగా గుర్తించబడిన దశలు", + "description": "ప్రొఫైల్: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} దశ(లు) స్వయంచాలకంగా గుర్తించబడ్డాయి.** దిగువ చర్యను ఎంచుకోండి.", + "data": { + "action": "చర్య" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "గుర్తించబడిన దశల పేరు", + "description": "ప్రొఫైల్: **{profile_name}**\n\n**{detected_count} దశ(లు) గుర్తించబడ్డాయి:**\n{ranges_summary}\n\nప్రతి దశకు ఒక పేరును కేటాయించండి." + }, + "assign_profile_phases_select": { + "title": "దశ పరిధులను కేటాయించండి", + "description": "దశ పరిధులను కాన్ఫిగర్ చేయడానికి ప్రొఫైల్‌ను ఎంచుకోండి.", + "data": { + "profile": "ప్రొఫైల్" + } + }, + "profile_stats": { + "title": "ప్రొఫైల్ గణాంకాలు", + "description": "{stats_table}" + }, + "create_profile": { + "title": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "description": "మాన్యువల్‌గా లేదా గత చక్రం నుండి కొత్త ప్రొఫైల్‌ను సృష్టించండి.\n\nప్రొఫైల్ పేరు ఉదాహరణలు: 'డెలికేట్స్', 'హెవీ డ్యూటీ', 'క్విక్ వాష్'", + "data": { + "profile_name": "ప్రొఫైల్ పేరు", + "reference_cycle": "రిఫరెన్స్ సైకిల్ (ఐచ్ఛికం)", + "manual_duration": "మాన్యువల్ వ్యవధి (నిమిషాలు)" + } + }, + "edit_profile": { + "title": "ప్రొఫైల్‌ని సవరించండి", + "description": "పేరు మార్చడానికి ప్రొఫైల్‌ను ఎంచుకోండి", + "data": { + "profile": "ప్రొఫైల్" + } + }, + "rename_profile": { + "title": "ప్రొఫైల్ వివరాలను సవరించండి", + "description": "ప్రస్తుత ప్రొఫైల్: {current_name}", + "data": { + "new_name": "ప్రొఫైల్ పేరు", + "manual_duration": "మాన్యువల్ వ్యవధి (నిమిషాలు)" + } + }, + "delete_profile_select": { + "title": "ప్రొఫైల్‌ను తొలగించండి", + "description": "తొలగించడానికి ప్రొఫైల్‌ను ఎంచుకోండి", + "data": { + "profile": "ప్రొఫైల్" + } + }, + "delete_profile_confirm": { + "title": "ప్రొఫైల్ తొలగించడాన్ని నిర్ధారించండి", + "description": "⚠️ ఇది ప్రొఫైల్‌ని శాశ్వతంగా తొలగిస్తుంది: {profile_name}", + "data": { + "unlabel_cycles": "ఈ ప్రొఫైల్‌ని ఉపయోగించి సైకిల్స్ నుండి లేబుల్‌ని తీసివేయండి" + } + }, + "auto_label_cycles": { + "title": "పాత సైకిళ్లను ఆటో-లేబుల్ చేయండి", + "description": "మొత్తం {total_count} చక్రాలు కనుగొనబడ్డాయి. ప్రొఫైల్‌లు: {profiles}", + "data": { + "confidence_threshold": "కనీస విశ్వాసం (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "లేబుల్‌కి సైకిల్‌ని ఎంచుకోండి", + "description": "✓ = పూర్తయింది, ⚠ = పునఃప్రారంభించబడింది, ✗ = అంతరాయం ఏర్పడింది", + "data": { + "cycle_id": "సైకిల్" + } + }, + "select_cycle_to_delete": { + "title": "సైకిల్‌ను తొలగించండి", + "description": "⚠️ ఇది ఎంచుకున్న సైకిల్‌ని శాశ్వతంగా తొలగిస్తుంది", + "data": { + "cycle_id": "తొలగించాల్సిన సైకిల్" + } + }, + "label_cycle": { + "title": "లేబుల్ సైకిల్", + "description": "{cycle_info}", + "data": { + "profile_name": "ప్రొఫైల్", + "new_profile_name": "కొత్త ప్రొఫైల్ పేరు (సృష్టిస్తే)" + } + }, + "post_process": { + "title": "ప్రక్రియ చరిత్ర (విలీనం/విభజన)", + "description": "ఫ్రాగ్మెంటెడ్ రన్‌లను విలీనం చేయడం మరియు టైమ్ విండోలో తప్పుగా విలీనం చేయబడిన సైకిల్‌లను విభజించడం ద్వారా సైకిల్ చరిత్రను క్లీన్ అప్ చేయండి. తిరిగి చూసేందుకు గంటల సంఖ్యను నమోదు చేయండి (అందరికీ 999999 ఉపయోగించండి).", + "data": { + "time_range": "లుక్ బ్యాక్ విండో (గంటలు)", + "gap_seconds": "విలీనం/స్ప్లిట్ గ్యాప్ (సెకన్లు)" + }, + "data_description": { + "time_range": "విలీనమైన చక్రాల కోసం స్కాన్ చేయడానికి గంటల సంఖ్య. మొత్తం చారిత్రక డేటాను ప్రాసెస్ చేయడానికి 999999ని ఉపయోగించండి.", + "gap_seconds": "విభజన vs విలీనం నిర్ణయించడానికి థ్రెషోల్డ్. దీని కంటే తక్కువ ఖాళీలు విలీనం చేయబడ్డాయి. దీని కంటే ఎక్కువ ఖాళీలు విభజించబడ్డాయి. తప్పుగా విలీనం చేయబడిన సైకిల్‌పై స్ప్లిట్‌ని బలవంతంగా తగ్గించడానికి దీన్ని తగ్గించండి." + } + }, + "cleanup_profile": { + "title": "చరిత్రను క్లీన్ అప్ చేయండి - ప్రొఫైల్‌ని ఎంచుకోండి", + "description": "దృశ్యమానం చేయడానికి ప్రొఫైల్‌ను ఎంచుకోండి. ఇది అవుట్‌లయర్‌లను గుర్తించడంలో సహాయపడటానికి ఈ ప్రొఫైల్ కోసం అన్ని గత చక్రాలను చూపే గ్రాఫ్‌ను రూపొందిస్తుంది.", + "data": { + "profile": "ప్రొఫైల్" + } + }, + "cleanup_select": { + "title": "చరిత్రను క్లీన్ అప్ చేయండి - గ్రాఫ్ & డిలీట్", + "description": "![గ్రాఫ్]({graph_url})\n\n**విజువలైజింగ్ {profile_name}**\n\nగ్రాఫ్ వ్యక్తిగత చక్రాలను రంగు రేఖలుగా చూపుతుంది. అవుట్‌లయర్‌లను గుర్తించండి (సమూహానికి దూరంగా ఉన్న లైన్‌లు) మరియు వాటిని తొలగించడానికి దిగువ జాబితాలో వాటి రంగును సరిపోల్చండి.\n\n**శాశ్వతంగా తొలగించడానికి సైకిల్‌లను ఎంచుకోండి:**", + "data": { + "cycles_to_delete": "తొలగించాల్సిన చక్రాలు (సరిపోయే రంగులను తనిఖీ చేయండి)" + } + }, + "interactive_editor": { + "title": "ఇంటరాక్టివ్ ఎడిటర్‌ను విలీనం చేయండి/స్ప్లిట్ చేయండి", + "description": "మీ సైకిల్ చరిత్రలో నిర్వహించడానికి మాన్యువల్ చర్యను ఎంచుకోండి.", + "menu_options": { + "editor_split": "చక్రాన్ని విభజించండి (ఖాళీలను కనుగొనండి)", + "editor_merge": "చక్రాలను విలీనం చేయండి (శకలాలు కలపండి)", + "editor_delete": "సైకిల్(ల)ని తొలగించు", + "menu_back": "← వెనుకకు" + } + }, + "editor_select": { + "title": "చక్రాలను ఎంచుకోండి", + "description": "ప్రాసెస్ చేయడానికి సైకిల్(ల)ని ఎంచుకోండి.\n\n{info_text}", + "data": { + "selected_cycles": "చక్రాలు" + } + }, + "editor_configure": { + "title": "కాన్ఫిగర్ & వర్తించు", + "description": "{preview_md}", + "data": { + "confirm_action": "చర్య", + "merged_profile": "ఫలిత ప్రొఫైల్", + "new_profile_name": "కొత్త ప్రొఫైల్ పేరు", + "confirm_commit": "అవును, నేను ఈ చర్యను ధృవీకరిస్తున్నాను", + "segment_0_profile": "సెగ్మెంట్ 1 ప్రొఫైల్", + "segment_1_profile": "సెగ్మెంట్ 2 ప్రొఫైల్", + "segment_2_profile": "సెగ్మెంట్ 3 ప్రొఫైల్", + "segment_3_profile": "సెగ్మెంట్ 4 ప్రొఫైల్", + "segment_4_profile": "సెగ్మెంట్ 5 ప్రొఫైల్", + "segment_5_profile": "సెగ్మెంట్ 6 ప్రొఫైల్", + "segment_6_profile": "సెగ్మెంట్ 7 ప్రొఫైల్", + "segment_7_profile": "సెగ్మెంట్ 8 ప్రొఫైల్", + "segment_8_profile": "సెగ్మెంట్ 9 ప్రొఫైల్", + "segment_9_profile": "సెగ్మెంట్ 10 ప్రొఫైల్" + } + }, + "editor_split_params": { + "title": "స్ప్లిట్ కాన్ఫిగరేషన్", + "description": "ఈ చక్రాన్ని విభజించడానికి సున్నితత్వాన్ని సర్దుబాటు చేయండి. దీని కంటే ఎక్కువ నిష్క్రియ గ్యాప్ విభజనకు కారణమవుతుంది.", + "data": { + "split_mode": "స్ప్లిట్ విధానం" + } + }, + "editor_split_auto_params": { + "title": "స్వయంచాలక విభజన ఆకృతీకరణ", + "description": "ఈ సైకిల్‌ను విభజించే సున్నితత్వాన్ని సర్దుబాటు చేయండి. దీని కంటే ఎక్కువ కాలం నిర్జీవ విరామం ఉంటే విభజన జరుగుతుంది.", + "data": { + "min_gap_seconds": "విభజన విరామ పరిమితి (సెకన్లు)" + } + }, + "editor_split_manual_params": { + "title": "మాన్యువల్ విభజన టైమ్‌స్టాంప్‌లు", + "description": "{preview_md}సైకిల్ విండో: **{cycle_start_wallclock} → {cycle_end_wallclock}** తేదీ {cycle_date}.\n\nఒకటి లేదా ఎక్కువ విభజన టైమ్‌స్టాంప్‌లను (`HH:MM` లేదా `HH:MM:SS`), ప్రతి పంక్తికి ఒకటి చొప్పున నమోదు చేయండి. ప్రతి టైమ్‌స్టాంప్ వద్ద సైకిల్ కత్తిరించబడుతుంది — N టైమ్‌స్టాంప్‌లు N+1 విభాగాలను సృష్టిస్తాయి.", + "data": { + "split_timestamps": "స్ప్లిట్ టైమ్‌స్టాంప్‌లు" + } + }, + "reprocess_history": { + "title": "నిర్వహణ: రీప్రాసెస్ & డేటాను ఆప్టిమైజ్ చేయండి", + "description": "చారిత్రక డేటా యొక్క లోతైన శుభ్రతను నిర్వహిస్తుంది:\n1. జీరో-పవర్ రీడింగ్‌లను ట్రిమ్ చేస్తుంది (నిశ్శబ్దం).\n2. సైకిల్ టైమింగ్/వ్యవధిని పరిష్కరిస్తుంది.\n3. అన్ని గణాంక నమూనాలను (ఎన్వలప్‌లు) తిరిగి గణిస్తుంది.\n\n**డేటా సమస్యలను పరిష్కరించడానికి లేదా కొత్త అల్గారిథమ్‌లను వర్తింపజేయడానికి దీన్ని అమలు చేయండి.**" + }, + "wipe_history": { + "title": "చరిత్రను తుడిచివేయండి (పరీక్ష మాత్రమే)", + "description": "⚠️ ఇది ఈ పరికరం కోసం నిల్వ చేయబడిన అన్ని సైకిల్‌లు మరియు ప్రొఫైల్‌లను శాశ్వతంగా తొలగిస్తుంది. ఇది రద్దు చేయబడదు!" + }, + "export_import": { + "title": "JSONని ఎగుమతి/దిగుమతి చేయండి", + "description": "ఈ పరికరం కోసం సైకిల్‌లు, ప్రొఫైల్‌లు మరియు సెట్టింగ్‌లను కాపీ/పేస్ట్ చేయండి. దిగుమతి చేయడానికి దిగువన ఎగుమతి చేయండి లేదా JSONని అతికించండి.", + "data": { + "mode": "చర్య", + "json_payload": "పూర్తి కాన్ఫిగరేషన్ JSON" + }, + "data_description": { + "mode": "ప్రస్తుత డేటా మరియు సెట్టింగ్‌లను కాపీ చేయడానికి ఎగుమతి ఎంచుకోండి లేదా మరొక WashData పరికరం నుండి ఎగుమతి చేసిన డేటాను అతికించడానికి దిగుమతి చేయండి.", + "json_payload": "ఎగుమతి కోసం: ఈ JSONని కాపీ చేయండి (సైకిల్‌లు, ప్రొఫైల్‌లు మరియు అన్ని ఫైన్-ట్యూన్ చేసిన సెట్టింగ్‌లు ఉన్నాయి). దిగుమతి కోసం: WashData నుండి ఎగుమతి చేయబడిన JSONని అతికించండి." + } + }, + "record_cycle": { + "title": "రికార్డ్ సైకిల్", + "description": "జోక్యం లేకుండా క్లీన్ ప్రొఫైల్‌ను సృష్టించడానికి మాన్యువల్‌గా సైకిల్‌ను రికార్డ్ చేయండి.\n\nస్థితి: **{status}**\nవ్యవధి: {duration}సె\nనమూనాలు: {samples}", + "menu_options": { + "record_refresh": "స్థితిని రిఫ్రెష్ చేయండి", + "record_stop": "రికార్డింగ్ ఆపివేయి (సేవ్ & ప్రాసెస్)", + "record_start": "కొత్త రికార్డింగ్‌ని ప్రారంభించండి", + "record_process": "చివరి రికార్డింగ్‌ని ప్రాసెస్ చేయండి (ట్రిమ్ & సేవ్)", + "record_discard": "చివరి రికార్డింగ్‌ని విస్మరించండి", + "menu_back": "← వెనుకకు" + } + }, + "record_process": { + "title": "ప్రాసెస్ రికార్డింగ్", + "description": "![గ్రాఫ్]({graph_url})\n\n**గ్రాఫ్ ముడి రికార్డింగ్‌ను చూపుతుంది.** నీలం = ఉంచండి, ఎరుపు = ప్రతిపాదిత ట్రిమ్.\n*గ్రాఫ్ స్థిరంగా ఉంటుంది మరియు ఫారమ్ మార్పులతో నవీకరించబడదు.*\n\nరికార్డింగ్ ఆగిపోయింది. గుర్తించబడిన నమూనా రేటు (~{sampling_rate}లు)కి ట్రిమ్‌లు సమలేఖనం చేయబడ్డాయి.\n\nముడి వ్యవధి: {duration}సె\nనమూనాలు: {samples}", + "data": { + "head_trim": "ట్రిమ్ ప్రారంభం (సెకన్లు)", + "tail_trim": "ట్రిమ్ ఎండ్ (సెకన్లు)", + "save_mode": "గమ్యాన్ని సేవ్ చేయండి", + "profile_name": "ప్రొఫైల్ పేరు" + } + }, + "learning_feedbacks": { + "title": "ఫీడ్‌బ్యాక్‌లను నేర్చుకోవడం", + "description": "పెండింగ్‌లో ఉన్న ఫీడ్‌బ్యాక్‌లు: {count}\n\n{pending_table}\n\nప్రాసెస్ చేయడానికి సైకిల్ సమీక్ష అభ్యర్థనను ఎంచుకోండి.", + "menu_options": { + "learning_feedbacks_pick": "పెండింగ్‌లో ఉన్న ఒక ఫీడ్‌బ్యాక్‌ను సమీక్షించండి", + "learning_feedbacks_dismiss_all": "పెండింగ్‌లో ఉన్న అన్ని ఫీడ్‌బ్యాక్‌లను తీసివేయండి", + "menu_back": "← వెనుకకు" + } + }, + "learning_feedbacks_pick": { + "title": "సమీక్షించడానికి ఫీడ్‌బ్యాక్‌ను ఎంచుకోండి", + "description": "తెరవడానికి ఒక సైకిల్ సమీక్ష అభ్యర్థనను ఎంచుకోండి.", + "data": { + "selected_feedback": "సమీక్షించాల్సిన సైకిల్‌ను ఎంచుకోండి" + } + }, + "learning_feedbacks_empty": { + "title": "ఫీడ్‌బ్యాక్‌లను నేర్చుకోవడం", + "description": "పెండింగ్‌లో ఉన్న ఫీడ్‌బ్యాక్ అభ్యర్థనలు ఏవీ కనుగొనబడలేదు.", + "menu_options": { + "menu_back": "← వెనుకకు" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "పెండింగ్‌లో ఉన్న అన్ని ఫీడ్‌బ్యాక్‌లను విస్మరించండి", + "description": "మీరు **{count}** పెండింగ్‌లో ఉన్న ఫీడ్‌బ్యాక్ అభ్యర్థనలను విస్మరించబోతున్నారు. విస్మరించబడిన సైకిల్‌లు మీ చరిత్రలోనే ఉంటాయి, కానీ కొత్త అభ్యసన సంకేతానికి తోడ్పడవు. దీన్ని వెనక్కి తిప్పలేరు.\n\n✅ అన్నింటినీ విస్మరించడానికి దిగువ పెట్టెను తనిఖీ చేసి **సమర్పించు** క్లిక్ చేయండి.\n❌ దాన్ని ఎంపిక చేయకుండా వదిలేసి, రద్దు చేసి వెనక్కి వెళ్లడానికి **సమర్పించు** క్లిక్ చేయండి.", + "data": { + "confirm_dismiss_all": "నిర్ధారించండి: పెండింగ్‌లో ఉన్న అన్ని ఫీడ్‌బ్యాక్ అభ్యర్థనలను తీసివేయండి" + } + }, + "resolve_feedback": { + "title": "అభిప్రాయాన్ని పరిష్కరించండి", + "description": "{comparison_img}{candidates_table}\n**కనుగొన్న ప్రొఫైల్**: {detected_profile} ({confidence_pct}%)\n**అంచనా వ్యవధి**: {est_duration_min} నిమి\n**వాస్తవ వ్యవధి**: {act_duration_min} నిమి\n\n__క్రింద ఒక చర్యను ఎంచుకోండి:__", + "data": { + "action": "మీరు ఏమి చేయాలనుకుంటున్నారు?", + "corrected_profile": "సరైన ప్రోగ్రామ్ (సరిదిద్దితే)", + "corrected_duration": "సెకన్లలో సరైన వ్యవధి (సవరిస్తే)" + } + }, + "trim_cycle_select": { + "title": "ట్రిమ్ సైకిల్ - సైకిల్ ఎంచుకోండి", + "description": "ట్రిమ్ చేయడానికి సైకిల్‌ను ఎంచుకోండి. ✓ = పూర్తయింది, ⚠ = పునఃప్రారంభించబడింది, ✗ = అంతరాయం ఏర్పడింది", + "data": { + "cycle_id": "సైకిల్" + } + }, + "trim_cycle": { + "title": "ట్రిమ్ సైకిల్", + "description": "ప్రస్తుత విండో: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "చర్య" + } + }, + "trim_cycle_start": { + "title": "ట్రిమ్ ప్రారంభాన్ని సెట్ చేయండి", + "description": "సైకిల్ విండో: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nప్రస్తుత ప్రారంభం: **{current_wallclock}** (సైకిల్ ప్రారంభం నుండి +{current_offset_min} నిమి)\n\nదిగువ క్లాక్ పికర్‌ని ఉపయోగించి కొత్త ప్రారంభ సమయాన్ని ఎంచుకోండి.", + "data": { + "trim_start_time": "కొత్త ప్రారంభ సమయం", + "trim_start_min": "కొత్త ప్రారంభం (సైకిల్ ప్రారంభం నుండి నిమిషాలు)" + } + }, + "trim_cycle_end": { + "title": "ట్రిమ్ ముగింపుని సెట్ చేయండి", + "description": "సైకిల్ విండో: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nప్రస్తుత ముగింపు: **{current_wallclock}** (సైకిల్ ప్రారంభం నుండి +{current_offset_min} నిమి)\n\nదిగువన ఉన్న క్లాక్ పికర్‌ని ఉపయోగించి కొత్త ముగింపు సమయాన్ని ఎంచుకోండి.", + "data": { + "trim_end_time": "కొత్త ముగింపు సమయం", + "trim_end_min": "కొత్త ముగింపు (సైకిల్ ప్రారంభం నుండి నిమిషాలు)" + } + } + }, + "error": { + "import_failed": "దిగుమతి విఫలమైంది. దయచేసి చెల్లుబాటు అయ్యే WashData ఎగుమతి JSONని అతికించండి.", + "select_exactly_one": "ఈ చర్య కోసం ఖచ్చితంగా ఒక సైకిల్‌ను ఎంచుకోండి.", + "select_at_least_one": "ఈ చర్య కోసం కనీసం ఒక సైకిల్‌ను ఎంచుకోండి.", + "select_at_least_two": "ఈ చర్య కోసం కనీసం రెండు సైకిల్‌లను ఎంచుకోండి.", + "profile_exists": "ఈ పేరుతో ప్రొఫైల్ ఇప్పటికే ఉంది", + "rename_failed": "ప్రొఫైల్ పేరు మార్చడంలో విఫలమైంది. ప్రొఫైల్ ఉనికిలో ఉండకపోవచ్చు లేదా కొత్త పేరు ఇప్పటికే తీసుకోబడింది.", + "assignment_failed": "ప్రొఫైల్‌ను సైకిల్‌కి కేటాయించడంలో విఫలమైంది", + "duplicate_phase": "ఈ పేరుతో ఒక దశ ఇప్పటికే ఉంది.", + "phase_not_found": "ఎంచుకున్న దశ కనుగొనబడలేదు.", + "invalid_phase_name": "దశ పేరు ఖాళీగా ఉండకూడదు.", + "phase_name_too_long": "దశ పేరు చాలా పొడవుగా ఉంది.", + "invalid_phase_range": "దశ పరిధి చెల్లదు. ప్రారంభం కంటే ముగింపు ఎక్కువగా ఉండాలి.", + "invalid_phase_timestamp": "టైమ్‌స్టాంప్ చెల్లదు. YYYY-MM-DD HH:MM లేదా HH:MM ఆకృతిని ఉపయోగించండి.", + "overlapping_phase_ranges": "దశ పరిధులు అతివ్యాప్తి చెందుతాయి. పరిధులను సర్దుబాటు చేసి, మళ్లీ ప్రయత్నించండి.", + "incomplete_phase_row": "ఎంచుకున్న మోడ్ కోసం ప్రతి దశ అడ్డు వరుస తప్పనిసరిగా దశతో పాటు పూర్తి ప్రారంభ/ముగింపు విలువలను కలిగి ఉండాలి.", + "timestamp_mode_cycle_required": "టైమ్‌స్టాంప్ మోడ్‌ని ఉపయోగిస్తున్నప్పుడు దయచేసి సోర్స్ సైకిల్‌ను ఎంచుకోండి.", + "no_phase_ranges": "ఆ చర్య కోసం ఇంకా దశల పరిధులు అందుబాటులో లేవు.", + "feedback_notification_title": "WashData: సైకిల్‌ని ధృవీకరించండి ({device})", + "feedback_notification_message": "**పరికరం**: {device}\n**ప్రోగ్రామ్**: {program} ({confidence}% విశ్వాసం)\n**సమయం**: {time}\n\nకనుగొనబడిన ఈ చక్రాన్ని ధృవీకరించడానికి WashDataకి మీ సహాయం కావాలి.\n\nదయచేసి ఈ ఫలితాన్ని నిర్ధారించడానికి లేదా సరిచేయడానికి **సెట్టింగ్‌లు > పరికరాలు & సేవలు > WashData > కాన్ఫిగర్ > లెర్నింగ్ ఫీడ్‌బ్యాక్‌లు**కి వెళ్లండి.", + "suggestions_ready_notification_title": "WashData: సూచించబడిన సెట్టింగ్‌లు సిద్ధంగా ఉన్నాయి ({device})", + "suggestions_ready_notification_message": "**సూచించబడిన సెట్టింగ్‌లు** సెన్సార్ ఇప్పుడు **{count}** చర్య తీసుకోదగిన సిఫార్సులను నివేదిస్తుంది.\n\nవాటిని సమీక్షించడానికి మరియు వర్తింపజేయడానికి: **సెట్టింగ్‌లు > పరికరాలు & సేవలు > WashData > కాన్ఫిగర్ > అధునాతన సెట్టింగ్‌లు > సూచించబడిన విలువలను వర్తింపజేయండి**.\n\nసూచనలు ఐచ్ఛికం మరియు మీరు సేవ్ చేసే ముందు సమీక్ష కోసం చూపబడతాయి.", + "trim_range_invalid": "ప్రారంభం తప్పనిసరిగా ముగింపుకు ముందు ఉండాలి. దయచేసి మీ ట్రిమ్ పాయింట్‌లను సర్దుబాటు చేయండి.", + "trim_failed": "ట్రిమ్ విఫలమైంది. చక్రం ఉనికిలో ఉండకపోవచ్చు లేదా విండో ఖాళీగా ఉంది.", + "invalid_split_timestamp": "టైమ్‌స్టాంప్ చెల్లదు లేదా సైకిల్ విండోకు బయట ఉంది. సైకిల్ విండోలో HH:MM లేదా HH:MM:SS ఉపయోగించండి.", + "no_split_segments_found": "ఈ టైమ్‌స్టాంప్‌లతో చెల్లుబాటు అయ్యే భాగాలు రూపొందించలేకపోయాం. ప్రతి టైమ్‌స్టాంప్ సైకిల్ విండోలో ఉందని మరియు ప్రతి భాగం కనీసం 1 నిమిషం నిడివి కలిగి ఉందని నిర్ధారించండి.", + "auto_tune_suggestion": "{device_type} {device_title} దెయ్యం చక్రాలను గుర్తించింది. సూచించబడిన min_power మార్పు: {current_min}W -> {new_min}W (స్వయంచాలకంగా వర్తించదు).", + "auto_tune_title": "WashData ఆటో-ట్యూన్", + "auto_tune_fallback": "{device_type} {device_title} దెయ్యం చక్రాలను గుర్తించింది. సూచించబడిన min_power మార్పు: {current_min}W -> {new_min}W (స్వయంచాలకంగా వర్తించదు)." + }, + "abort": { + "no_cycles_found": "చక్రాలు ఏవీ కనుగొనబడలేదు", + "no_split_segments_found": "ఎంచుకున్న సైకిల్‌లో విభజించదగిన భాగాలు ఏవీ కనుగొనబడలేదు.", + "cycle_not_found": "ఎంచుకున్న సైకిల్‌ను లోడ్ చేయలేకపోయాం.", + "no_power_data": "ఎంచుకున్న సైకిల్ కోసం పవర్ ట్రేస్ డేటా అందుబాటులో లేదు.", + "no_profiles_found": "ప్రొఫైల్‌లు ఏవీ కనుగొనబడలేదు. ముందుగా ప్రొఫైల్‌ని సృష్టించండి.", + "no_profiles_for_matching": "సరిపోలడానికి ప్రొఫైల్‌లు ఏవీ అందుబాటులో లేవు. ముందుగా ప్రొఫైల్‌లను సృష్టించండి.", + "no_unlabeled_cycles": "అన్ని చక్రాలు ఇప్పటికే లేబుల్ చేయబడ్డాయి", + "no_suggestions": "ఇంకా సూచించబడిన విలువలు ఏవీ అందుబాటులో లేవు. కొన్ని చక్రాలను అమలు చేసి, మళ్లీ ప్రయత్నించండి.", + "no_predictions": "అంచనాలు లేవు", + "no_custom_phases": "అనుకూల దశలు ఏవీ కనుగొనబడలేదు.", + "reprocess_success": "{count} సైకిళ్లు విజయవంతంగా రీప్రాసెస్ చేయబడ్డాయి.", + "debug_data_cleared": "{count} సైకిళ్ల నుండి డీబగ్ డేటా విజయవంతంగా క్లియర్ చేయబడింది." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "సైకిల్ ప్రారంభం", + "cycle_finish": "సైకిల్ ముగింపు", + "cycle_live": "ప్రత్యక్ష పురోగతి" + } + }, + "common_text": { + "options": { + "unlabeled": "(లేబుల్ చేయబడలేదు)", + "create_new_profile": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "remove_label": "లేబుల్‌ని తీసివేయండి", + "no_reference_cycle": "(రిఫరెన్స్ సైకిల్ లేదు)", + "all_device_types": "అన్ని పరికర రకాలు", + "current_suffix": "(ప్రస్తుతం)", + "editor_select_info": "విభజించడానికి 1 సైకిల్‌ని లేదా విలీనం చేయడానికి 2+ సైకిల్‌లను ఎంచుకోండి.", + "editor_select_info_split": "విభజించడానికి 1 సైకిల్‌ను ఎంచుకోండి.", + "editor_select_info_merge": "విలీనం చేయడానికి 2+ సైకిల్‌లను ఎంచుకోండి.", + "editor_select_info_delete": "తొలగించడానికి 1+ సైకిల్(లు)ను ఎంచుకోండి.", + "no_cycles_recorded": "ఇంకా ఏ సైకిల్‌లు రికార్డ్ చేయబడలేదు.", + "profile_summary_line": "- **{name}**: {count} చక్రాలు, {avg}మీ సగటు", + "no_profiles_created": "ఇంకా ప్రొఫైల్‌లు ఏవీ సృష్టించబడలేదు.", + "phase_preview": "దశ ప్రివ్యూ", + "phase_preview_no_curve": "సగటు ప్రొఫైల్ వక్రరేఖ ఇంకా అందుబాటులో లేదు. ఈ ప్రొఫైల్ కోసం మరిన్ని చక్రాలను అమలు చేయండి/లేబుల్ చేయండి.", + "average_power_curve": "సగటు పవర్ కర్వ్", + "unit_min": "నిమి", + "profile_option_fmt": "{name} ({count} చక్రాలు, ~{duration}మీ సగటు)", + "profile_option_short_fmt": "{name} ({count} సైకిళ్లు, ~{duration}మీ)", + "deleted_cycles_from_profile": "{profile} నుండి {count} సైకిల్‌లు తొలగించబడ్డాయి.", + "not_enough_data_graph": "గ్రాఫ్‌ని రూపొందించడానికి తగినంత డేటా లేదు.", + "no_profiles_found": "ప్రొఫైల్‌లు ఏవీ కనుగొనబడలేదు.", + "cycle_info_fmt": "చక్రం: {start}, {duration}మీ, ప్రస్తుత: {label}", + "top_candidates_header": "### అగ్ర అభ్యర్థులు", + "tbl_profile": "ప్రొఫైల్", + "tbl_confidence": "విశ్వాసం", + "tbl_correlation": "సహసంబంధం", + "tbl_duration_match": "వ్యవధి మ్యాచ్", + "detected_profile": "గుర్తించబడిన ప్రొఫైల్", + "estimated_duration": "అంచనా వ్యవధి", + "actual_duration": "వాస్తవ వ్యవధి", + "choose_action": "__క్రింద ఒక చర్యను ఎంచుకోండి:__", + "tbl_count": "లెక్కించు", + "tbl_avg": "సగటు", + "tbl_min": "కనిష్ట", + "tbl_max": "గరిష్టంగా", + "tbl_energy_avg": "శక్తి (సగటు)", + "tbl_energy_total": "శక్తి (మొత్తం)", + "tbl_consistency": "కలిగి ఉంటాయి.", + "tbl_last_run": "చివరి పరుగు", + "graph_legend_title": "గ్రాఫ్ లెజెండ్", + "graph_legend_body": "బ్లూ బ్యాండ్ గమనించిన కనిష్ట మరియు గరిష్ట పవర్ డ్రా పరిధిని సూచిస్తుంది. లైన్ సగటు శక్తి వక్రతను చూపుతుంది.", + "recording_preview": "రికార్డింగ్ ప్రివ్యూ", + "trim_start": "ట్రిమ్ ప్రారంభం", + "trim_end": "ట్రిమ్ ఎండ్", + "split_preview_title": "స్ప్లిట్ ప్రివ్యూ", + "split_preview_found_fmt": "{count} విభాగాలు కనుగొనబడ్డాయి.", + "split_preview_confirm_fmt": "ఈ చక్రాన్ని {count} ప్రత్యేక చక్రాలుగా విభజించడానికి నిర్ధారించు క్లిక్ చేయండి.", + "merge_preview_title": "ప్రివ్యూను విలీనం చేయండి", + "merge_preview_joining_fmt": "{count} సైకిల్స్‌లో చేరుతోంది. ఖాళీలు 0W రీడింగ్‌లతో పూరించబడతాయి.", + "editor_delete_preview_title": "తొలగింపు ప్రివ్యూ", + "editor_delete_preview_intro": "ఎంచుకున్న సైకిల్‌లు శాశ్వతంగా తొలగించబడతాయి:", + "editor_delete_preview_confirm": "ఈ సైకిల్ రికార్డులను శాశ్వతంగా తొలగించడానికి Confirm క్లిక్ చేయండి.", + "no_power_preview": "*ప్రివ్యూ కోసం పవర్ డేటా అందుబాటులో లేదు.*", + "profile_comparison": "ప్రొఫైల్ పోలిక", + "actual_cycle_label": "ఈ చక్రం (అసలు)", + "storage_usage_header": "నిల్వ వినియోగం", + "storage_file_size": "ఫైల్ పరిమాణం", + "storage_cycles": "చక్రాలు", + "storage_profiles": "ప్రొఫైల్స్", + "storage_debug_traces": "డీబగ్ జాడలు", + "overview_suffix": "అవలోకనం", + "phase_preview_for_profile": "ప్రొఫైల్ కోసం దశ ప్రివ్యూ", + "current_ranges_header": "ప్రస్తుత పరిధులు", + "assign_phases_help": "పరిధులను జోడించడానికి, సవరించడానికి, తొలగించడానికి లేదా సేవ్ చేయడానికి దిగువ చర్యలను ఉపయోగించండి.", + "profile_summary_header": "ప్రొఫైల్ సారాంశం", + "recent_cycles_header": "ఇటీవలి చక్రాలు", + "trim_cycle_preview_title": "సైకిల్ ట్రిమ్ ప్రివ్యూ", + "trim_cycle_preview_no_data": "ఈ సైకిల్ కోసం పవర్ డేటా అందుబాటులో లేదు.", + "trim_cycle_preview_kept_suffix": "ఉంచింది", + "table_program": "కార్యక్రమం", + "table_when": "ఎప్పుడు", + "table_length": "వ్యవధి", + "table_match": "మ్యాచ్", + "table_profile": "ప్రొఫైల్", + "table_cycles": "చక్రాలు", + "table_avg_length": "సగటు వ్యవధి", + "table_last_run": "చివరి పరుగు", + "table_avg_energy": "సగటు శక్తి", + "table_detected_program": "గుర్తించబడిన ప్రోగ్రామ్", + "table_confidence": "విశ్వాసం", + "table_reported": "నివేదించబడింది", + "phase_builtin_suffix": "(అంతర్నిర్మిత)", + "phase_none_available": "దశలు అందుబాటులో లేవు.", + "settings_deprecation_warning": "⚠️ **విస్మరించబడిన పరికర రకం:** {device_type} భవిష్యత్తు విడుదలలో తీసివేయడానికి షెడ్యూల్ చేయబడింది. WashData యొక్క మ్యాచింగ్ పైప్‌లైన్ ఈ ఉపకరణ తరగతికి నమ్మదగిన ఫలితాలను అందించదు. మీ ఇంటిగ్రేషన్ డిప్రికేషన్ వ్యవధిలో పని చేస్తూనే ఉంటుంది; ఈ హెచ్చరికను నిశ్శబ్దం చేయడానికి, మద్దతు ఉన్న రకాల్లో ఒకదానికి (వాషింగ్ మెషిన్, డ్రైయర్, వాషర్-డ్రైర్ కాంబో, డిష్‌వాషర్, ఎయిర్ ఫ్రైయర్, బ్రెడ్ మేకర్, లేదా పంప్) దిగువన **పరికర రకాన్ని** మార్చండి లేదా మీ ఉపకరణం మద్దతు ఉన్న ఏ రకంతోనూ సరిపోలకపోతే **ఇతర (అధునాతన)**కి మార్చండి. **ఇతర (అధునాతన)** ఏదైనా నిర్దిష్ట ఉపకరణం కోసం ట్యూన్ చేయని ఉద్దేశపూర్వకంగా జెనరిక్ డిఫాల్ట్‌లను పంపుతుంది, కాబట్టి మీరు థ్రెషోల్డ్‌లు, టైమ్‌అవుట్‌లు మరియు సరిపోలే పారామితులను మీరే కాన్ఫిగర్ చేయాలి; మీరు మారినప్పుడు మీ ప్రస్తుత సెట్టింగ్‌లు అన్నీ భద్రపరచబడతాయి.", + "phase_other_device_types": "ఇతర పరికరాల రకాలు:" + } + }, + "interactive_editor_action": { + "options": { + "split": "చక్రాన్ని విభజించండి (ఖాళీలను కనుగొనండి)", + "merge": "చక్రాలను విలీనం చేయండి (శకలాలు కలపండి)", + "delete": "సైకిల్(ల)ని తొలగించు" + } + }, + "split_mode": { + "options": { + "auto": "నిష్క్రియ గ్యాప్‌లను స్వయంచాలకంగా గుర్తించండి", + "manual": "మాన్యువల్ టైమ్‌స్టాంప్(లు)" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "నిర్వహణ: రీప్రాసెస్ & డేటాను ఆప్టిమైజ్ చేయండి", + "clear_debug_data": "డీబగ్ డేటాను క్లియర్ చేయండి (స్థలాన్ని ఖాళీ చేయండి)", + "wipe_history": "ఈ పరికరం కోసం మొత్తం డేటాను తుడిచివేయండి (తిరుగులేనిది)", + "export_import": "సెట్టింగ్‌లతో JSONని ఎగుమతి/దిగుమతి చేయండి (కాపీ/పేస్ట్)" + } + }, + "export_import_mode": { + "options": { + "export": "మొత్తం డేటాను ఎగుమతి చేయండి", + "import": "డేటాను దిగుమతి/విలీనం చేయండి" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "పాత సైకిళ్లను ఆటో-లేబుల్ చేయండి", + "select_cycle_to_label": "లేబుల్ నిర్దిష్ట చక్రం", + "select_cycle_to_delete": "సైకిల్‌ను తొలగించండి", + "interactive_editor": "ఇంటరాక్టివ్ ఎడిటర్‌ను విలీనం చేయండి/స్ప్లిట్ చేయండి", + "trim_cycle": "సైకిల్ డేటాను ట్రిమ్ చేయండి" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "edit_profile": "ప్రొఫైల్‌ని సవరించండి/పేరుమార్చు", + "delete_profile": "ప్రొఫైల్‌ను తొలగించండి", + "profile_stats": "ప్రొఫైల్ గణాంకాలు", + "cleanup_profile": "చరిత్రను క్లీన్ అప్ చేయండి - గ్రాఫ్ & డిలీట్", + "assign_phases": "దశ పరిధులను కేటాయించండి" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "కొత్త దశను సృష్టించండి", + "edit_custom_phase": "దశను సవరించండి", + "delete_custom_phase": "దశను తొలగించండి" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "దశ పరిధిని జోడించండి", + "edit_range": "దశ పరిధిని సవరించండి", + "delete_range": "దశ పరిధిని తొలగించండి", + "clear_ranges": "అన్ని పరిధులను క్లియర్ చేయండి", + "auto_detect_ranges": "దశలను స్వయంచాలకంగా గుర్తించండి", + "save_ranges": "సేవ్ మరియు తిరిగి" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "స్థితిని రిఫ్రెష్ చేయండి", + "stop_recording": "రికార్డింగ్ ఆపివేయి (సేవ్ & ప్రాసెస్)", + "start_recording": "కొత్త రికార్డింగ్‌ని ప్రారంభించండి", + "process_recording": "చివరి రికార్డింగ్‌ని ప్రాసెస్ చేయండి (ట్రిమ్ & సేవ్)", + "discard_recording": "చివరి రికార్డింగ్‌ని విస్మరించండి" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "కొత్త ప్రొఫైల్‌ని సృష్టించండి", + "existing_profile": "ఇప్పటికే ఉన్న ప్రొఫైల్‌కు జోడించండి", + "discard": "రికార్డింగ్‌ని విస్మరించండి" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "నిర్ధారించండి - సరైన గుర్తింపు", + "correct": "సరైనది - ప్రోగ్రామ్/వ్యవధిని ఎంచుకోండి", + "ignore": "విస్మరించండి - ఫాల్స్ పాజిటివ్/నాయిస్ సైకిల్", + "delete": "తొలగించు - చరిత్ర నుండి తీసివేయి" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "దశలకు పేరు పెట్టండి & దరఖాస్తు చేయండి", + "cancel": "మార్పులు లేకుండా వెనక్కి వెళ్లండి" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "ప్రారంభ స్థానం సెట్ చేయండి", + "set_end": "ముగింపు పాయింట్‌ని సెట్ చేయండి", + "reset": "పూర్తి వ్యవధికి రీసెట్ చేయండి", + "apply": "ట్రిమ్ వర్తించు", + "cancel": "రద్దు చేయి" + } + }, + "device_type": { + "options": { + "washing_machine": "వాషింగ్ మెషిన్", + "dryer": "డ్రైయర్", + "washer_dryer": "వాషర్-డ్రైర్ కాంబో", + "dishwasher": "డిష్వాషర్", + "coffee_machine": "కాఫీ మెషిన్ (విస్మరించబడింది)", + "ev": "ఎలక్ట్రిక్ వాహనం (విస్మరించబడింది)", + "air_fryer": "ఎయిర్ ఫ్రైయర్", + "heat_pump": "హీట్ పంప్ (విస్మరించబడింది)", + "bread_maker": "బ్రెడ్ మేకర్", + "pump": "పంప్ / సంప్ పంప్", + "oven": "ఓవెన్ (విస్మరించబడింది)", + "other": "ఇతర (అధునాతన)" + } + } + }, + "services": { + "label_cycle": { + "name": "లేబుల్ సైకిల్", + "description": "ఇప్పటికే ఉన్న ప్రొఫైల్‌ను గత చక్రానికి కేటాయించండి లేదా లేబుల్‌ను తీసివేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "లేబుల్ చేయడానికి వాషింగ్ మెషీన్ పరికరం." + }, + "cycle_id": { + "name": "సైకిల్ ID", + "description": "లేబుల్ చేయడానికి సైకిల్ యొక్క ID." + }, + "profile_name": { + "name": "ప్రొఫైల్ పేరు", + "description": "ఇప్పటికే ఉన్న ప్రొఫైల్ పేరు (ప్రొఫైళ్లను నిర్వహించండి మెనులో ప్రొఫైల్‌లను సృష్టించండి). లేబుల్‌ని తీసివేయడానికి ఖాళీగా ఉంచండి." + } + } + }, + "create_profile": { + "name": "ప్రొఫైల్ సృష్టించండి", + "description": "కొత్త ప్రొఫైల్‌ను సృష్టించండి (స్వతంత్రంగా లేదా సూచన చక్రం ఆధారంగా).", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "వాషింగ్ మెషీన్ పరికరం." + }, + "profile_name": { + "name": "ప్రొఫైల్ పేరు", + "description": "కొత్త ప్రొఫైల్ పేరు (ఉదా. 'హెవీ డ్యూటీ', 'డెలికేట్స్')." + }, + "reference_cycle_id": { + "name": "సూచన సైకిల్ ID", + "description": "ఈ ప్రొఫైల్‌ను ఆధారం చేసుకోవడానికి ఐచ్ఛిక సైకిల్ ID." + } + } + }, + "delete_profile": { + "name": "ప్రొఫైల్‌ను తొలగించండి", + "description": "ప్రొఫైల్‌ను తొలగించి, దాన్ని ఉపయోగించి సైకిల్‌లను ఐచ్ఛికంగా అన్‌లేబుల్ చేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "వాషింగ్ మెషీన్ పరికరం." + }, + "profile_name": { + "name": "ప్రొఫైల్ పేరు", + "description": "తొలగించాల్సిన ప్రొఫైల్." + }, + "unlabel_cycles": { + "name": "సైకిళ్లను అన్‌లేబుల్ చేయండి", + "description": "ఈ ప్రొఫైల్‌ని ఉపయోగించి సైకిల్స్ నుండి ప్రొఫైల్ లేబుల్‌ని తీసివేయండి." + } + } + }, + "auto_label_cycles": { + "name": "పాత సైకిళ్లను ఆటో-లేబుల్ చేయండి", + "description": "ప్రొఫైల్ మ్యాచింగ్‌ని ఉపయోగించి లేబుల్ చేయని చక్రాలను రెట్రోయాక్టివ్‌గా లేబుల్ చేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "వాషింగ్ మెషీన్ పరికరం." + }, + "confidence_threshold": { + "name": "కాన్ఫిడెన్స్ థ్రెషోల్డ్", + "description": "లేబుల్‌లను వర్తింపజేయడానికి కనీస సరిపోలిక విశ్వాసం (0.50-0.95)." + } + } + }, + "export_config": { + "name": "ఎగుమతి కాన్ఫిగర్", + "description": "ఈ పరికరం యొక్క ప్రొఫైల్‌లు, చక్రాలు మరియు సెట్టింగ్‌లను JSON ఫైల్‌కి (పరికరానికి) ఎగుమతి చేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "ఎగుమతి చేయడానికి వాషింగ్ మెషీన్ పరికరం." + }, + "path": { + "name": "మార్గం", + "description": "వ్రాయడానికి ఐచ్ఛిక సంపూర్ణ ఫైల్ మార్గం (/config/ha_washdata_export_{entry}.jsonకి డిఫాల్ట్)." + } + } + }, + "import_config": { + "name": "కాన్ఫిగరేషన్‌ను దిగుమతి చేయండి", + "description": "JSON ఎగుమతి ఫైల్ నుండి ఈ పరికరం కోసం ప్రొఫైల్‌లు, సైకిళ్లు మరియు సెట్టింగ్‌లను దిగుమతి చేయండి (సెట్టింగ్‌లు ఓవర్‌రైట్ చేయబడవచ్చు).", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "దిగుమతి చేసుకోవడానికి వాషింగ్ మెషీన్ పరికరం." + }, + "path": { + "name": "మార్గం", + "description": "దిగుమతి చేయడానికి ఎగుమతి JSON ఫైల్‌కు సంపూర్ణ మార్గం." + } + } + }, + "submit_cycle_feedback": { + "name": "సైకిల్ అభిప్రాయాన్ని సమర్పించండి", + "description": "పూర్తయిన చక్రం తర్వాత స్వయంచాలకంగా గుర్తించబడిన ప్రోగ్రామ్‌ను నిర్ధారించండి లేదా సరి చేయండి. `entry_id` (అధునాతన) లేదా `device_id` (సిఫార్సు చేయబడింది) అందించండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "WashData పరికరం (entry_idకి బదులుగా సిఫార్సు చేయబడింది)." + }, + "entry_id": { + "name": "ఎంట్రీ ID", + "description": "పరికరం కోసం కాన్ఫిగర్ ఎంట్రీ ఐడి (device_idకి ప్రత్యామ్నాయం)." + }, + "cycle_id": { + "name": "సైకిల్ ID", + "description": "ఫీడ్‌బ్యాక్ నోటిఫికేషన్ / లాగ్‌లలో చూపబడిన సైకిల్ ఐడి." + }, + "user_confirmed": { + "name": "కనుగొనబడిన ప్రోగ్రామ్‌ను నిర్ధారించండి", + "description": "కనుగొనబడిన ప్రోగ్రామ్ సరైనదైతే ఒప్పును సెట్ చేయండి." + }, + "corrected_profile": { + "name": "ప్రొఫైల్ సరిదిద్దబడింది", + "description": "ధృవీకరించబడకపోతే, సరైన ప్రొఫైల్/ప్రోగ్రామ్ పేరును అందించండి." + }, + "corrected_duration": { + "name": "సరిదిద్దబడిన వ్యవధి (సెకన్లు)", + "description": "సెకన్లలో ఐచ్ఛిక సరిదిద్దబడిన వ్యవధి." + }, + "notes": { + "name": "గమనికలు", + "description": "ఈ చక్రం గురించి ఐచ్ఛిక గమనికలు." + } + } + }, + "record_start": { + "name": "రికార్డ్ సైకిల్ ప్రారంభం", + "description": "క్లీన్ సైకిల్‌ను మాన్యువల్‌గా రికార్డ్ చేయడం ప్రారంభించండి (అన్ని సరిపోలే లాజిక్‌లను దాటవేస్తుంది).", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "రికార్డ్ చేయడానికి వాషింగ్ మెషీన్ పరికరం." + } + } + }, + "record_stop": { + "name": "రికార్డ్ సైకిల్ స్టాప్", + "description": "మాన్యువల్ రికార్డింగ్ ఆపివేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "రికార్డింగ్ ఆపడానికి వాషింగ్ మెషీన్ పరికరం." + } + } + }, + "trim_cycle": { + "name": "ట్రిమ్ సైకిల్", + "description": "గత చక్రం యొక్క పవర్ డేటాను నిర్దిష్ట సమయ విండోకు ట్రిమ్ చేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "WashData పరికరం." + }, + "cycle_id": { + "name": "సైకిల్ ID", + "description": "ట్రిమ్ చేయడానికి సైకిల్ యొక్క ID." + }, + "trim_start_s": { + "name": "ట్రిమ్ ప్రారంభం (సెకన్లు)", + "description": "ఈ ఆఫ్‌సెట్ నుండి డేటాను సెకన్లలో ఉంచండి. డిఫాల్ట్ 0." + }, + "trim_end_s": { + "name": "ట్రిమ్ ఎండ్ (సెకన్లు)", + "description": "ఈ ఆఫ్‌సెట్ వరకు డేటాను సెకన్లలో ఉంచండి. డిఫాల్ట్ = పూర్తి వ్యవధి." + } + } + }, + "pause_cycle": { + "name": "పాజ్ సైకిల్", + "description": "WashData పరికరం కోసం క్రియాశీల చక్రాన్ని పాజ్ చేయండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "WashData పరికరం పాజ్ చేయబడుతుంది." + } + } + }, + "resume_cycle": { + "name": "చక్రాన్ని పునఃప్రారంభించండి", + "description": "WashData పరికరం కోసం పాజ్ చేయబడిన సైకిల్‌ను మళ్లీ ప్రారంభించండి.", + "fields": { + "device_id": { + "name": "పరికరం", + "description": "WashData పరికరం పునఃప్రారంభించబడుతుంది." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "నడుస్తోంది" + }, + "match_ambiguity": { + "name": "మ్యాచ్ సందిగ్ధత" + } + }, + "sensor": { + "washer_state": { + "name": "రాష్ట్రం", + "state": { + "off": "ఆఫ్", + "idle": "పనిలేకుండా", + "starting": "ప్రారంభిస్తోంది", + "running": "నడుస్తోంది", + "paused": "పాజ్ చేయబడింది", + "user_paused": "వినియోగదారుచే పాజ్ చేయబడింది", + "ending": "ముగింపు", + "finished": "పూర్తయింది", + "anti_wrinkle": "వ్యతిరేక ముడతలు", + "delay_wait": "ప్రారంభించడానికి వేచి ఉంది", + "interrupted": "అంతరాయం కలిగింది", + "force_stopped": "ఫోర్స్ ఆగిపోయింది", + "rinse": "శుభ్రం చేయు", + "unknown": "తెలియదు", + "clean": "శుభ్రంగా" + } + }, + "washer_program": { + "name": "కార్యక్రమం" + }, + "time_remaining": { + "name": "సమయం మిగిలి ఉంది" + }, + "total_duration": { + "name": "మొత్తం వ్యవధి" + }, + "cycle_progress": { + "name": "పురోగతి" + }, + "current_power": { + "name": "ప్రస్తుత శక్తి" + }, + "elapsed_time": { + "name": "గడిచిన సమయం" + }, + "debug_info": { + "name": "డీబగ్ సమాచారం" + }, + "match_confidence": { + "name": "మ్యాచ్ కాన్ఫిడెన్స్" + }, + "top_candidates": { + "name": "అగ్ర అభ్యర్థులు", + "state": { + "none": "ఏదీ లేదు" + } + }, + "current_phase": { + "name": "ప్రస్తుత దశ" + }, + "wash_phase": { + "name": "దశ" + }, + "profile_cycle_count": { + "name": "ప్రొఫైల్ {profile_name} కౌంట్", + "unit_of_measurement": "చక్రాలు" + }, + "suggestions": { + "name": "సూచించబడిన సెట్టింగ్‌లు" + }, + "pump_runs_today": { + "name": "పంప్ పరుగులు (చివరి 24 గం)" + }, + "cycle_count": { + "name": "సైకిల్ కౌంట్" + } + }, + "select": { + "program_select": { + "name": "సైకిల్ ప్రోగ్రామ్", + "state": { + "auto_detect": "ఆటో-డిటెక్ట్" + } + } + }, + "button": { + "force_end_cycle": { + "name": "ఫోర్స్ ఎండ్ సైకిల్" + }, + "pause_cycle": { + "name": "పాజ్ సైకిల్" + }, + "resume_cycle": { + "name": "చక్రాన్ని పునఃప్రారంభించండి" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "చెల్లుబాటు అయ్యే device_id అవసరం." + }, + "cycle_id_required": { + "message": "చెల్లుబాటు అయ్యే సైకిల్_ఐడి అవసరం." + }, + "profile_name_required": { + "message": "చెల్లుబాటు అయ్యే ప్రొఫైల్_పేరు అవసరం." + }, + "device_not_found": { + "message": "పరికరం కనుగొనబడలేదు." + }, + "no_config_entry": { + "message": "పరికరం కోసం కాన్ఫిగర్ ఎంట్రీ కనుగొనబడలేదు." + }, + "integration_not_loaded": { + "message": "ఈ పరికరం కోసం ఇంటిగ్రేషన్ లోడ్ చేయబడలేదు." + }, + "cycle_not_found_or_no_power": { + "message": "సైకిల్ కనుగొనబడలేదు లేదా పవర్ డేటా లేదు." + }, + "trim_failed_empty_window": { + "message": "ట్రిమ్ విఫలమైంది - సైకిల్ కనుగొనబడలేదు, పవర్ డేటా లేదు లేదా ఫలితంగా విండో ఖాళీగా ఉంది." + }, + "trim_invalid_range": { + "message": "trim_end_s తప్పనిసరిగా trim_start_s కంటే ఎక్కువగా ఉండాలి." + } + } +} diff --git a/custom_components/ha_washdata/translations/th.json b/custom_components/ha_washdata/translations/th.json new file mode 100644 index 0000000..4e26212 --- /dev/null +++ b/custom_components/ha_washdata/translations/th.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "การตั้งค่า WashData", + "description": "กำหนดค่าเครื่องซักผ้าหรืออุปกรณ์อื่นๆ ของคุณ\n\nจำเป็นต้องมีเซ็นเซอร์วัดกำลัง\n\n**ต่อไป ระบบจะถามว่าคุณต้องการสร้างโปรไฟล์แรกหรือไม่**", + "data": { + "name": "ชื่ออุปกรณ์", + "device_type": "ประเภทอุปกรณ์", + "power_sensor": "เซ็นเซอร์วัดกำลัง", + "min_power": "เกณฑ์พลังงานขั้นต่ำ (W)" + }, + "data_description": { + "name": "ชื่อที่จำง่ายสำหรับอุปกรณ์นี้ (เช่น \"เครื่องซักผ้า\" \"เครื่องล้างจาน\")", + "device_type": "นี่คืออุปกรณ์ประเภทใด? ช่วยปรับแต่งการตรวจจับและการติดฉลาก", + "power_sensor": "เอนทิตีเซ็นเซอร์ที่รายงานการใช้พลังงานแบบเรียลไทม์ (เป็นวัตต์) จากปลั๊กอัจฉริยะของคุณ", + "min_power": "การอ่านค่ากำลังไฟฟ้าที่สูงกว่าเกณฑ์นี้ (เป็นวัตต์) แสดงว่าเครื่องกำลังทำงานอยู่ เริ่มต้นด้วย 2W สำหรับอุปกรณ์ส่วนใหญ่" + } + }, + "first_profile": { + "title": "สร้างโปรไฟล์แรก", + "description": "**รอบ** คือการซักรอบเครื่องของคุณอย่างสมบูรณ์ (เช่น การซักผ้า) **โปรไฟล์** จัดกลุ่มโปรแกรมตามประเภท (เช่น 'ผ้าฝ้าย' 'ซักด่วน') ขณะนี้การสร้างโปรไฟล์ช่วยให้ประมาณการระยะเวลาการรวมระบบได้ทันที\n\nคุณสามารถเลือกโปรไฟล์นี้ด้วยตนเองในการควบคุมอุปกรณ์หากไม่ได้ตรวจพบโดยอัตโนมัติ\n\n**เว้น 'ชื่อโปรไฟล์' ว่างไว้เพื่อข้ามขั้นตอนนี้**", + "data": { + "profile_name": "ชื่อโปรไฟล์ (ไม่บังคับ)", + "manual_duration": "ระยะเวลาโดยประมาณ (นาที)" + } + } + }, + "error": { + "cannot_connect": "ไม่สามารถเชื่อมต่อได้", + "invalid_auth": "การรับรองความถูกต้องไม่ถูกต้อง", + "unknown": "ข้อผิดพลาดที่ไม่คาดคิด" + }, + "abort": { + "already_configured": "อุปกรณ์ได้รับการกำหนดค่าแล้ว" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "ทบทวนผลตอบรับการเรียนรู้", + "settings": "การตั้งค่า", + "notifications": "การแจ้งเตือน", + "manage_cycles": "จัดการรอบ", + "manage_profiles": "จัดการโปรไฟล์", + "manage_phase_catalog": "จัดการแคตตาล็อกเฟส", + "record_cycle": "รอบการบันทึก (แบบแมนนวล)", + "diagnostics": "การวินิจฉัยและการบำรุงรักษา" + } + }, + "apply_suggestions": { + "title": "คัดลอกค่าที่แนะนำ", + "description": "การดำเนินการนี้จะคัดลอกค่าที่แนะนำในปัจจุบันไปยังการตั้งค่าที่บันทึกไว้ นี่เป็นการดำเนินการเพียงครั้งเดียวและไม่ได้เปิดใช้งานการเขียนทับอัตโนมัติ\n\nค่าที่แนะนำเพื่อคัดลอก:\n{suggested}", + "data": { + "confirm": "ยืนยันการดำเนินการคัดลอก" + } + }, + "apply_suggestions_confirm": { + "title": "ตรวจสอบการเปลี่ยนแปลงที่แนะนำ", + "description": "พบ WashData **{pending_count}** ค่าที่แนะนำเพื่อใช้\n\nการเปลี่ยนแปลง:\n{changes}\n\n✅ ทำเครื่องหมายในช่องด้านล่างแล้วคลิก **ส่ง** เพื่อนำไปใช้และบันทึกการเปลี่ยนแปลงเหล่านี้ทันที\n❌ ปล่อยว่างไว้โดยไม่เลือกแล้วคลิก **ส่ง** เพื่อยกเลิกและย้อนกลับ", + "data": { + "confirm_apply_suggestions": "ใช้และบันทึกการเปลี่ยนแปลงเหล่านี้" + } + }, + "settings": { + "title": "การตั้งค่า", + "description": "{deprecation_warning}ปรับแต่งเกณฑ์การตรวจจับ การเรียนรู้ และพฤติกรรมขั้นสูง ค่าเริ่มต้นทำงานได้ดีกับการตั้งค่าส่วนใหญ่\n\nการตั้งค่าที่แนะนำที่พร้อมใช้งานในขณะนี้: {suggestions_count}.\nหากค่านี้มากกว่า 0 ให้เปิดการตั้งค่าขั้นสูงและใช้ 'ใช้ค่าที่แนะนำ' เพื่อตรวจสอบคำแนะนำ", + "data": { + "apply_suggestions": "ใช้ค่าที่แนะนำ", + "device_type": "ประเภทอุปกรณ์", + "power_sensor": "เอนทิตีเซ็นเซอร์กำลัง", + "min_power": "กำลังไฟขั้นต่ำ (W)", + "off_delay": "ความล่าช้าในการสิ้นสุดรอบ (s)", + "notify_actions": "การดำเนินการแจ้งเตือน", + "notify_people": "เลื่อนการจัดส่งจนกว่าคนเหล่านี้จะถึงบ้าน", + "notify_only_when_home": "เลื่อนการแจ้งเตือนจนกว่าบุคคลที่เลือกจะถึงบ้าน", + "notify_fire_events": "เปิดใช้งานเหตุการณ์ระบบอัตโนมัติ", + "notify_start_services": "เริ่มรอบ - เป้าหมายการแจ้งเตือน", + "notify_finish_services": "วงจรเสร็จสิ้น - เป้าหมายการแจ้งเตือน", + "notify_live_services": "ความคืบหน้าสด - เป้าหมายการแจ้งเตือน", + "notify_before_end_minutes": "การแจ้งเตือนก่อนเสร็จสิ้น (นาทีก่อนสิ้นสุด)", + "notify_title": "ชื่อการแจ้งเตือน", + "notify_icon": "ไอคอนการแจ้งเตือน", + "notify_start_message": "เริ่มรูปแบบข้อความ", + "notify_finish_message": "เสร็จสิ้นรูปแบบข้อความ", + "notify_pre_complete_message": "รูปแบบข้อความก่อนกรอก", + "show_advanced": "แก้ไขการตั้งค่าขั้นสูง (ขั้นตอนถัดไป)" + }, + "data_description": { + "apply_suggestions": "เลือกช่องนี้เพื่อคัดลอกค่าที่แนะนำโดยอัลกอริทึมการเรียนรู้ลงในแบบฟอร์ม ตรวจสอบก่อนบันทึก\n\nข้อเสนอแนะ (จากการเรียนรู้):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "เลือกประเภทของเครื่องใช้ไฟฟ้า (เครื่องซักผ้า, เครื่องอบผ้า, เครื่องล้างจาน, เครื่องชงกาแฟ, EV) แท็กนี้จะถูกเก็บไว้ในแต่ละรอบเพื่อการจัดระเบียบที่ดีขึ้น", + "power_sensor": "เอนทิตีเซ็นเซอร์รายงานกำลังแบบเรียลไทม์ (เป็นวัตต์) คุณสามารถเปลี่ยนแปลงได้ตลอดเวลาโดยไม่สูญเสียข้อมูลในอดีต ซึ่งมีประโยชน์หากคุณเปลี่ยนปลั๊กอัจฉริยะ", + "min_power": "การอ่านค่ากำลังไฟฟ้าที่สูงกว่าเกณฑ์นี้ (เป็นวัตต์) แสดงว่าเครื่องกำลังทำงานอยู่ เริ่มต้นด้วย 2W สำหรับอุปกรณ์ส่วนใหญ่", + "off_delay": "วินาทีที่กำลังปรับให้เรียบจะต้องอยู่ต่ำกว่าเกณฑ์หยุดเพื่อทำเครื่องหมายว่าเสร็จสมบูรณ์ ค่าเริ่มต้น: 120 วินาที", + "notify_actions": "ลำดับการดำเนินการของ Home Assistant ที่เป็นตัวเลือกสำหรับการแจ้งเตือน (สคริปต์ การแจ้งเตือน TTS ฯลฯ) หากตั้งค่าไว้ การดำเนินการจะทำงานก่อนบริการสำรองจะแจ้ง", + "notify_people": "เอนทิตีบุคคลที่ใช้สำหรับการตรวจสอบสถานะ เมื่อเปิดใช้งาน การส่งการแจ้งเตือนจะล่าช้าจนกว่าบุคคลที่เลือกอย่างน้อยหนึ่งคนจะถึงบ้าน", + "notify_only_when_home": "หากเปิดใช้งาน ให้เลื่อนการแจ้งเตือนจนกว่าบุคคลที่เลือกอย่างน้อยหนึ่งคนจะถึงบ้าน", + "notify_fire_events": "หากเปิดใช้งาน จะส่งสัญญาณเหตุการณ์ Home Assistant สำหรับการเริ่มและสิ้นสุดรอบ (ha_washdata_cycle_started และ ha_washdata_cycle_ended) เพื่อขับเคลื่อนระบบอัตโนมัติ", + "notify_start_services": "บริการแจ้งเตือนอย่างน้อยหนึ่งรายการเพื่อแจ้งเตือนเมื่อรอบเริ่มต้น เว้นว่างไว้เพื่อข้ามการแจ้งเตือนการเริ่มต้น", + "notify_finish_services": "บริการแจ้งเตือนอย่างน้อยหนึ่งบริการเพื่อแจ้งเตือนเมื่อรอบการสิ้นสุดหรือใกล้จะเสร็จสิ้น เว้นว่างไว้เพื่อข้ามการแจ้งเตือนให้เสร็จสิ้น", + "notify_live_services": "แจ้งบริการอย่างน้อยหนึ่งรายการเพื่อรับการอัปเดตความคืบหน้าสดในขณะที่วงจรดำเนินไป (เฉพาะแอปที่แสดงร่วมบนมือถือเท่านั้น) เว้นว่างไว้เพื่อข้ามการอัปเดตสด", + "notify_before_end_minutes": "นาทีก่อนที่จะสิ้นสุดรอบโดยประมาณเพื่อทริกเกอร์การแจ้งเตือน ตั้งค่าเป็น 0 เพื่อปิดใช้งาน ค่าเริ่มต้น: 0", + "notify_title": "ชื่อที่กำหนดเองสำหรับการแจ้งเตือน รองรับตัวยึดตำแหน่ง {device}", + "notify_icon": "ไอคอนที่ใช้สำหรับการแจ้งเตือน (เช่น mdi:washing-machine) เว้นว่างไว้เพื่อส่งโดยไม่มีไอคอน", + "notify_start_message": "ข้อความที่ส่งเมื่อรอบเริ่มต้น รองรับตัวยึดตำแหน่ง {device}", + "notify_finish_message": "ข้อความที่ส่งเมื่อรอบสิ้นสุด รองรับตัวยึดตำแหน่ง {device}, {duration}, {program}, {energy_kwh}, {cost}", + "notify_pre_complete_message": "ข้อความของการอัปเดตความคืบหน้าสดที่เกิดซ้ำในขณะที่วงจรดำเนินไป รองรับตัวยึดตำแหน่ง {device}, {minutes}, {program}" + } + }, + "notifications": { + "title": "การแจ้งเตือน", + "description": "กำหนดค่าช่องทางการแจ้งเตือน เทมเพลต และการอัปเดตความคืบหน้าบนมือถือแบบสดที่เป็นตัวเลือก", + "data": { + "notify_actions": "การดำเนินการแจ้งเตือน", + "notify_people": "เลื่อนการจัดส่งจนกว่าคนเหล่านี้จะถึงบ้าน", + "notify_only_when_home": "เลื่อนการแจ้งเตือนจนกว่าบุคคลที่เลือกจะถึงบ้าน", + "notify_fire_events": "เปิดใช้งานเหตุการณ์ระบบอัตโนมัติ", + "notify_start_services": "เริ่มรอบ - เป้าหมายการแจ้งเตือน", + "notify_finish_services": "วงจรเสร็จสิ้น - เป้าหมายการแจ้งเตือน", + "notify_live_services": "ความคืบหน้าสด - เป้าหมายการแจ้งเตือน", + "notify_before_end_minutes": "การแจ้งเตือนก่อนเสร็จสิ้น (นาทีก่อนสิ้นสุด)", + "notify_live_interval_seconds": "ช่วงเวลาการอัปเดตสด (วินาที)", + "notify_live_overrun_percent": "ค่าเผื่อการโอเวอร์รันการอัปเดตสด (%)", + "notify_live_chronometer": "เครื่องจับเวลาถอยหลังโครโนมิเตอร์", + "notify_title": "ชื่อการแจ้งเตือน", + "notify_icon": "ไอคอนการแจ้งเตือน", + "notify_start_message": "เริ่มรูปแบบข้อความ", + "notify_finish_message": "เสร็จสิ้นรูปแบบข้อความ", + "notify_pre_complete_message": "รูปแบบข้อความก่อนกรอก", + "energy_price_entity": "ราคาพลังงาน - เอนทิตี (ไม่บังคับ)", + "energy_price_static": "ราคาพลังงาน - มูลค่าคงที่ (ไม่บังคับ)", + "go_back": "ย้อนกลับโดยไม่บันทึก", + "notify_reminder_message": "รูปแบบข้อความเตือนความจำ", + "notify_timeout_seconds": "ปิดอัตโนมัติหลังจาก (วินาที)", + "notify_channel": "ช่องทางการแจ้งเตือน (สถานะ/สด/เตือนความจำ)", + "notify_finish_channel": "ช่องทางการแจ้งเรียบร้อยแล้ว" + }, + "data_description": { + "go_back": "ทำเครื่องหมายที่นี่แล้วคลิกส่งเพื่อทิ้งการเปลี่ยนแปลงด้านบนทั้งหมดและกลับไปยังเมนูหลัก", + "notify_actions": "ลำดับการดำเนินการของ Home Assistant ที่เป็นตัวเลือกสำหรับการแจ้งเตือน (สคริปต์ การแจ้งเตือน TTS ฯลฯ) หากตั้งค่าไว้ การดำเนินการจะทำงานก่อนบริการสำรองจะแจ้ง", + "notify_people": "เอนทิตีบุคคลที่ใช้สำหรับการตรวจสอบสถานะ เมื่อเปิดใช้งาน การส่งการแจ้งเตือนจะล่าช้าจนกว่าบุคคลที่เลือกอย่างน้อยหนึ่งคนจะถึงบ้าน", + "notify_only_when_home": "หากเปิดใช้งาน ให้เลื่อนการแจ้งเตือนจนกว่าบุคคลที่เลือกอย่างน้อยหนึ่งคนจะถึงบ้าน", + "notify_fire_events": "หากเปิดใช้งาน จะส่งสัญญาณเหตุการณ์ Home Assistant สำหรับการเริ่มและสิ้นสุดรอบ (ha_washdata_cycle_started และ ha_washdata_cycle_ended) เพื่อขับเคลื่อนระบบอัตโนมัติ", + "notify_start_services": "บริการแจ้งเตือนอย่างน้อยหนึ่งรายการเพื่อแจ้งเตือนเมื่อรอบเริ่มต้น (เช่น notify.mobile_app_my_phone) เว้นว่างไว้เพื่อข้ามการแจ้งเตือนการเริ่มต้น", + "notify_finish_services": "บริการแจ้งเตือนอย่างน้อยหนึ่งบริการเพื่อแจ้งเตือนเมื่อรอบการสิ้นสุดหรือใกล้จะเสร็จสิ้น เว้นว่างไว้เพื่อข้ามการแจ้งเตือนให้เสร็จสิ้น", + "notify_live_services": "แจ้งบริการอย่างน้อยหนึ่งรายการเพื่อรับการอัปเดตความคืบหน้าสดในขณะที่วงจรดำเนินไป (เฉพาะแอปที่แสดงร่วมบนมือถือเท่านั้น) เว้นว่างไว้เพื่อข้ามการอัปเดตสด", + "notify_before_end_minutes": "นาทีก่อนที่จะสิ้นสุดรอบโดยประมาณเพื่อทริกเกอร์การแจ้งเตือน ตั้งค่าเป็น 0 เพื่อปิดใช้งาน ค่าเริ่มต้น: 0", + "notify_live_interval_seconds": "ความถี่ในการส่งการอัปเดตความคืบหน้าขณะทำงาน ค่าที่ต่ำกว่าจะตอบสนองมากกว่าแต่ใช้การแจ้งเตือนมากกว่า บังคับใช้อย่างน้อย 30 วินาที - ค่าที่ต่ำกว่า 30 วินาทีจะถูกปัดเศษเป็น 30 วินาทีโดยอัตโนมัติ", + "notify_live_overrun_percent": "อัตราความปลอดภัยที่สูงกว่าจำนวนการอัปเดตโดยประมาณสำหรับรอบที่ยาวนาน/เกินเวลา (เช่น 20% อนุญาตให้มีการอัปเดตโดยประมาณ 1.2 เท่า)", + "notify_live_chronometer": "เมื่อเปิดใช้งาน การอัปเดตแบบเรียลไทม์แต่ละครั้งจะมีการนับถอยหลังโครโนมิเตอร์จนถึงเวลาสิ้นสุดโดยประมาณ ตัวจับเวลาการแจ้งเตือนจะทำเครื่องหมายบนอุปกรณ์โดยอัตโนมัติระหว่างการอัปเดต (เฉพาะแอปที่ใช้ร่วมกับ Android เท่านั้น)", + "notify_title": "ชื่อที่กำหนดเองสำหรับการแจ้งเตือน รองรับตัวยึดตำแหน่ง {device}", + "notify_icon": "ไอคอนที่ใช้สำหรับการแจ้งเตือน (เช่น mdi:washing-machine) เว้นว่างไว้เพื่อส่งโดยไม่มีไอคอน", + "notify_start_message": "ข้อความที่ส่งเมื่อรอบเริ่มต้น รองรับตัวยึดตำแหน่ง {device}", + "notify_finish_message": "ข้อความที่ส่งเมื่อรอบสิ้นสุด รองรับตัวยึดตำแหน่ง {device}, {duration}, {program}, {energy_kwh}, {cost}", + "energy_price_entity": "เลือกเอนทิตีตัวเลขที่เก็บราคาไฟฟ้าปัจจุบัน (เช่น sensor.electricity_price, input_number.energy_rate) เมื่อตั้งค่าแล้ว ให้เปิดใช้งานตัวยึดตำแหน่ง {cost} มีความสำคัญมากกว่ามูลค่าคงที่หากมีการกำหนดค่าทั้งสองรายการ", + "energy_price_static": "ราคาไฟฟ้าคงที่ต่อกิโลวัตต์ชั่วโมง เมื่อตั้งค่าแล้ว ให้เปิดใช้งานตัวยึดตำแหน่ง {cost} ละเว้นหากมีการกำหนดค่าเอนทิตีด้านบน เพิ่มสัญลักษณ์สกุลเงินของคุณในเทมเพลตข้อความ เช่น {cost} €", + "notify_reminder_message": "ข้อความเตือนความจำแบบครั้งเดียวส่งตามจำนวนนาทีที่กำหนดไว้ก่อนสิ้นสุดโดยประมาณ แตกต่างจากการอัปเดตสดที่เกิดซ้ำด้านบน รองรับตัวยึดตำแหน่ง {device}, {minutes}, {program}", + "notify_timeout_seconds": "ปิดการแจ้งเตือนโดยอัตโนมัติหลังจากผ่านไปหลายวินาทีนี้ (ส่งต่อเป็นแอปที่แสดงร่วม 'หมดเวลา') ตั้งค่าเป็น 0 เพื่อเก็บไว้จนกว่าจะถูกไล่ออก ค่าเริ่มต้น: 0", + "notify_channel": "Android ช่องทางการแจ้งเตือนแอปที่แสดงร่วมสำหรับสถานะ ความคืบหน้าสด และการแจ้งเตือน เสียงและความสำคัญของช่องได้รับการกำหนดค่าในแอปที่แสดงร่วมกันในครั้งแรกที่ใช้ชื่อช่อง - WashData จะตั้งค่าเฉพาะชื่อช่องเท่านั้น เว้นว่างไว้สำหรับค่าเริ่มต้นของแอป", + "notify_finish_channel": "แยกช่อง Android สำหรับการเตือนที่เสร็จสิ้นแล้ว (ใช้โดยตัวเตือนและเสียงจู้จี้รอซักรีดด้วย) เพื่อให้มีเสียงของตัวเองได้ เว้นว่างไว้เพื่อใช้ช่องด้านบนอีกครั้ง", + "notify_pre_complete_message": "ข้อความของการอัปเดตความคืบหน้าสดที่เกิดซ้ำในขณะที่วงจรดำเนินไป รองรับตัวยึดตำแหน่ง {device}, {minutes}, {program}" + } + }, + "advanced_settings": { + "title": "การตั้งค่าขั้นสูง", + "description": "ปรับแต่งเกณฑ์การตรวจจับ การเรียนรู้ และพฤติกรรมขั้นสูง ค่าเริ่มต้นทำงานได้ดีกับการตั้งค่าส่วนใหญ่", + "sections": { + "suggestions_section": { + "name": "การตั้งค่าที่แนะนำ", + "description": "มีคำแนะนำที่เรียนรู้แล้ว {suggestions_count} รายการ ให้ทำเครื่องหมาย 'ใช้ค่าที่แนะนำ' เพื่อตรวจสอบการเปลี่ยนแปลงที่เสนอไว้ก่อนบันทึก", + "data": { + "apply_suggestions": "ใช้ค่าที่แนะนำ" + }, + "data_description": { + "apply_suggestions": "เลือกช่องนี้เพื่อเปิดขั้นตอนการทบทวนค่าที่แนะนำจากอัลกอริทึมการเรียนรู้ ไม่มีสิ่งใดถูกบันทึกโดยอัตโนมัติ\n\nข้อเสนอแนะ (จากการเรียนรู้):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "การตรวจจับและเกณฑ์กำลังไฟ", + "description": "กำหนดว่าเมื่อใดอุปกรณ์จะถือว่าเปิดหรือปิด รวมถึงเกณฑ์พลังงานและจังหวะเวลาของการเปลี่ยนสถานะ", + "data": { + "start_duration_threshold": "เริ่มระยะเวลา Debounce (s)", + "start_energy_threshold": "เริ่ม Energy Gate (Wh)", + "completion_min_seconds": "รันไทม์ขั้นต่ำที่เสร็จสมบูรณ์ (วินาที)", + "start_threshold_w": "เกณฑ์เริ่มต้น (W)", + "stop_threshold_w": "หยุดเกณฑ์ (W)", + "end_energy_threshold": "ประตูพลังงานสิ้นสุด (Wh)", + "running_dead_zone": "วิ่งโซนตาย (วินาที)", + "end_repeat_count": "สิ้นสุดการนับซ้ำ", + "min_off_gap": "ช่องว่างขั้นต่ำระหว่างรอบ (s)", + "sampling_interval": "ช่วงเวลาการสุ่มตัวอย่าง (s)" + }, + "data_description": { + "start_duration_threshold": "เพิกเฉยต่อการเพิ่มพลังสั้นๆ ที่สั้นกว่าระยะเวลานี้ (วินาที) กรองการเพิ่มขึ้นอย่างรวดเร็วของบูต (เช่น 60W เป็นเวลา 2 วินาที) ก่อนเริ่มรอบจริง ค่าเริ่มต้น: 5 วินาที", + "start_energy_threshold": "วงจรจะต้องสะสมพลังงาน (Wh) มากเท่านี้เป็นอย่างน้อยในระหว่างระยะเริ่มต้นจึงจะได้รับการยืนยัน ค่าเริ่มต้น: 0.005 Wh.", + "completion_min_seconds": "รอบที่สั้นกว่านี้จะถูกทำเครื่องหมายว่า 'หยุดชะงัก' แม้ว่าจะเสร็จสิ้นตามธรรมชาติก็ตาม ค่าเริ่มต้น: 600 วินาที", + "start_threshold_w": "กำลังไฟฟ้าต้องสูงกว่าเกณฑ์นี้จึงจะเริ่มต้นวงจรได้ ตั้งค่าให้สูงกว่าพลังงานสแตนด์บายของคุณ ค่าเริ่มต้น: min_power + 1 W.", + "stop_threshold_w": "กำลังไฟฟ้าต้องต่ำกว่าเกณฑ์นี้จึงจะสิ้นสุดรอบได้ ตั้งค่าให้สูงกว่าศูนย์เพื่อหลีกเลี่ยงสัญญาณรบกวนที่ทำให้เกิดการสิ้นสุดที่ผิดพลาด ค่าเริ่มต้น: 0.6 * min_power", + "end_energy_threshold": "รอบการสิ้นสุดจะสิ้นสุดก็ต่อเมื่อพลังงานในหน้าต่าง Off-Delay สุดท้ายต่ำกว่าค่านี้ (Wh) ป้องกันการสิ้นสุดที่ผิดพลาดหากพลังงานผันผวนใกล้ศูนย์ ค่าเริ่มต้น: 0.05 Wh.", + "running_dead_zone": "หลังจากที่วงจรเริ่มต้นขึ้น ให้เพิกเฉยต่อกำลังที่ลดลงเป็นเวลาหลายวินาทีเพื่อป้องกันการตรวจจับปลายที่ผิดพลาดในระหว่างขั้นตอนการปั่นหมาดครั้งแรก ตั้งค่าเป็น 0 เพื่อปิดใช้งาน ค่าเริ่มต้น: 0 วินาที", + "end_repeat_count": "จำนวนครั้งที่ต้องเป็นไปตามเงื่อนไขการสิ้นสุด (กำลังต่ำกว่าเกณฑ์สำหรับ off_delay) ติดต่อกันก่อนที่จะสิ้นสุดรอบ ค่าที่สูงกว่าจะป้องกันการสิ้นสุดที่ผิดพลาดระหว่างการหยุดชั่วคราว ค่าเริ่มต้น: 1.", + "min_off_gap": "หากวงจรเริ่มต้นภายในไม่กี่วินาทีหลังจากรอบก่อนหน้าสิ้นสุดลง วงจรเหล่านั้นจะถูกรวมเข้าเป็นรอบเดียว", + "sampling_interval": "ช่วงเวลาการสุ่มตัวอย่างขั้นต่ำในหน่วยวินาที การอัปเดตที่เร็วกว่าอัตรานี้จะถูกละเว้น ค่าเริ่มต้น: 30 วินาที (2 วินาทีสำหรับเครื่องซักผ้า เครื่องซักผ้า-อบผ้า และเครื่องล้างจาน)" + } + }, + "matching_section": { + "name": "การจับคู่โปรไฟล์และการเรียนรู้", + "description": "กำหนดว่า WashData จะจับคู่รอบที่กำลังทำงานกับโปรไฟล์ที่เรียนรู้ไว้อย่างเข้มงวดเพียงใด และจะขอคำติชมเมื่อใด", + "data": { + "profile_match_min_duration_ratio": "อัตราส่วนระยะเวลาการจับคู่ขั้นต่ำของโปรไฟล์ (0.1-1.0)", + "profile_match_interval": "ช่วงเวลาการจับคู่โปรไฟล์ (วินาที)", + "profile_match_threshold": "เกณฑ์การจับคู่โปรไฟล์", + "profile_unmatch_threshold": "เกณฑ์ไม่ตรงกันของโปรไฟล์", + "auto_label_confidence": "ความเชื่อมั่นป้ายกำกับอัตโนมัติ (0-1)", + "learning_confidence": "ข้อเสนอแนะ ขอความมั่นใจ (0-1)", + "suppress_feedback_notifications": "ระงับการแจ้งเตือนข้อเสนอแนะ", + "duration_tolerance": "ความอดทนต่อระยะเวลา (0-0.5)", + "smoothing_window": "หน้าต่างปรับให้เรียบ (ตัวอย่าง)", + "profile_duration_tolerance": "ความอดทนต่อระยะเวลาการจับคู่โปรไฟล์ (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "อัตราส่วนระยะเวลาขั้นต่ำสำหรับการจับคู่โปรไฟล์ วงจรการทำงานต้องมีระยะเวลาอย่างน้อยเศษส่วนนี้จึงจะตรงกัน ล่าง = การจับคู่ก่อนหน้า ค่าเริ่มต้น: 0.50 (50%)", + "profile_match_interval": "ความถี่ (เป็นวินาที) ที่จะพยายามจับคู่โปรไฟล์ระหว่างรอบ ค่าที่ต่ำกว่าจะทำให้การตรวจจับเร็วขึ้นแต่ใช้ CPU มากกว่า ค่าเริ่มต้น: 300 วินาที (5 นาที)", + "profile_match_threshold": "คะแนนความคล้ายคลึงกัน DTW ขั้นต่ำ (0.0–1.0) ที่จำเป็นในการพิจารณาโปรไฟล์ที่ตรงกัน สูงกว่า = การจับคู่ที่เข้มงวดมากขึ้น ผลบวกลวงน้อยลง ค่าเริ่มต้น: 0.4", + "profile_unmatch_threshold": "คะแนนความคล้ายคลึงกัน DTW (0.0–1.0) ต่ำกว่าซึ่งโปรไฟล์ที่ตรงกันก่อนหน้านี้ถูกปฏิเสธ ควรต่ำกว่าเกณฑ์การจับคู่เพื่อป้องกันการกะพริบ ค่าเริ่มต้น: 0.35", + "auto_label_confidence": "ติดป้ายกำกับรอบโดยอัตโนมัติหรือสูงกว่าความเชื่อมั่นนี้เมื่อเสร็จสิ้น (0.0–1.0)", + "learning_confidence": "ความมั่นใจขั้นต่ำในการขอการยืนยันผู้ใช้ผ่านกิจกรรม (0.0–1.0)", + "suppress_feedback_notifications": "เมื่อเปิดใช้งาน WashData จะยังคงติดตามและขอคำติชมภายใน แต่จะไม่แสดงการแจ้งเตือนอย่างต่อเนื่องเพื่อขอให้คุณยืนยันรอบการทำงาน มีประโยชน์เมื่อตรวจพบรอบอย่างถูกต้องเสมอและคุณพบว่าข้อความแจ้งรบกวนสมาธิ", + "duration_tolerance": "ความคลาดเคลื่อนสำหรับการประมาณเวลาที่เหลืออยู่ระหว่างการวิ่ง (0.0–0.5 สอดคล้องกับ 0–50%)", + "smoothing_window": "จำนวนการอ่านค่ากำลังล่าสุดที่ใช้สำหรับการปรับให้เรียบโดยเฉลี่ยแบบเคลื่อนที่", + "profile_duration_tolerance": "ความคลาดเคลื่อนที่ใช้โดยการจับคู่โปรไฟล์สำหรับความแปรปรวนระยะเวลาทั้งหมด (0.0–0.5) ลักษณะการทำงานเริ่มต้น: ±25%" + } + }, + "timing_section": { + "name": "เวลา การบำรุงรักษา และการดีบัก", + "description": "Watchdog การรีเซ็ตความคืบหน้า การบำรุงรักษาอัตโนมัติ และการแสดงเอนทิตีดีบัก", + "data": { + "watchdog_interval": "ช่วงเวลา Watchdog (วินาที)", + "no_update_active_timeout": "ไม่มีการอัปเดตหมดเวลา (s)", + "progress_reset_delay": "ความล่าช้าในการรีเซ็ตความคืบหน้า (วินาที)", + "auto_maintenance": "เปิดใช้งานการบำรุงรักษาอัตโนมัติ", + "expose_debug_entities": "เปิดเผยเอนทิตีการตรวจแก้จุดบกพร่อง", + "save_debug_traces": "บันทึกการติดตามการแก้ไขข้อบกพร่อง" + }, + "data_description": { + "watchdog_interval": "วินาทีระหว่างการตรวจสอบสุนัขเฝ้าบ้านขณะวิ่ง ค่าเริ่มต้น: 5 วินาที คำเตือน: ตรวจสอบให้แน่ใจว่านี่สูงกว่าช่วงการอัปเดตเซ็นเซอร์ของคุณเพื่อหลีกเลี่ยงการหยุดที่ผิดพลาด (เช่น หากเซ็นเซอร์อัปเดตทุกๆ 60 วินาที ให้ใช้ 65 วินาทีขึ้นไป)", + "no_update_active_timeout": "สำหรับเซ็นเซอร์เผยแพร่เมื่อมีการเปลี่ยนแปลง: บังคับสิ้นสุดรอบการทำงานเท่านั้น หากไม่มีการอัปเดตมาถึงเป็นเวลาหลายวินาทีนี้ การทำเสร็จด้วยพลังงานต่ำยังคงใช้การหน่วงเวลาปิด", + "progress_reset_delay": "หลังจากรอบเสร็จสมบูรณ์ (100%) ให้รอสักครู่ก่อนที่จะรีเซ็ตความคืบหน้าเป็น 0% ค่าเริ่มต้น: 300 วินาที", + "auto_maintenance": "เปิดใช้งานการบำรุงรักษาอัตโนมัติ (ตัวอย่างการซ่อมแซม รวมชิ้นส่วน)", + "expose_debug_entities": "แสดงเซ็นเซอร์ขั้นสูง (Confidence, Phase, Ambiguity) สำหรับการดีบัก", + "save_debug_traces": "จัดเก็บข้อมูลการจัดอันดับและการติดตามพลังงานโดยละเอียดไว้ในประวัติ (เพิ่มการใช้พื้นที่เก็บข้อมูล)" + } + }, + "anti_wrinkle_section": { + "name": "แผงป้องกันรอยยับ (เครื่องอบผ้า)", + "description": "ละเว้นการหมุนถังซักหลังจบรอบเพื่อป้องกันรอบหลอน ใช้ได้กับเครื่องอบผ้า / เครื่องซักอบเท่านั้น", + "data": { + "anti_wrinkle_enabled": "แผ่นป้องกันริ้วรอย (เฉพาะเครื่องอบผ้า)", + "anti_wrinkle_max_power": "พลังต่อต้านริ้วรอยสูงสุด (W)", + "anti_wrinkle_max_duration": "ระยะเวลาสูงสุดในการต่อต้านริ้วรอย (s)", + "anti_wrinkle_exit_power": "กำลังขับทางออกป้องกันริ้วรอย (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "ไม่ต้องสนใจการหมุนถังซักที่ใช้พลังงานต่ำในช่วงสั้นๆ หลังจากสิ้นสุดรอบการซัก เพื่อป้องกันรอบการหยุดทำงาน (เฉพาะเครื่องอบผ้า/เครื่องซักผ้า-เครื่องอบผ้าเท่านั้น)", + "anti_wrinkle_max_power": "การระเบิดที่หรือต่ำกว่ากำลังนี้ถือเป็นการหมุนต่อต้านริ้วรอยแทนรอบใหม่ ใช้ได้เฉพาะเมื่อเปิดใช้งาน Anti-Wrinkle Shield เท่านั้น", + "anti_wrinkle_max_duration": "ความยาวระเบิดสูงสุดเพื่อใช้เป็นการหมุนต่อต้านริ้วรอย ใช้ได้เฉพาะเมื่อเปิดใช้งาน Anti-Wrinkle Shield เท่านั้น", + "anti_wrinkle_exit_power": "หากพลังงานลดลงต่ำกว่าระดับนี้ ให้ออกจากโปรแกรมต่อต้านริ้วรอยทันที (ปิดจริง) ใช้ได้เฉพาะเมื่อเปิดใช้งาน Anti-Wrinkle Shield เท่านั้น" + } + }, + "delay_start_section": { + "name": "การตรวจจับการเริ่มต้นล่าช้า", + "description": "ถือว่าสถานะสแตนด์บายกำลังต่ำที่ต่อเนื่องเป็น 'กำลังรอการเริ่มต้น' จนกว่ารอบจะเริ่มทำงานจริง", + "data": { + "delay_start_detect_enabled": "เปิดใช้งานการตรวจจับการเริ่มต้นล่าช้า", + "delay_confirm_seconds": "การเริ่มต้นล่าช้า: เวลายืนยันโหมดสแตนด์บาย (วินาที)", + "delay_timeout_hours": "การเริ่มต้นล่าช้า: เวลารอสูงสุด (h)" + }, + "data_description": { + "delay_start_detect_enabled": "เมื่อเปิดใช้งาน ตรวจพบการระบายพลังงานต่ำที่พุ่งสูงขึ้นสั้นๆ ตามมาด้วยพลังงานสแตนด์บายอย่างต่อเนื่อง จะถูกตรวจพบว่าเป็นการสตาร์ทล่าช้า เซ็นเซอร์สถานะแสดง 'กำลังรอการเริ่มต้น' ในระหว่างความล่าช้า ไม่มีการติดตามเวลาของโปรแกรมหรือความคืบหน้าจนกว่ารอบจะเริ่มต้นจริง จำเป็นต้องตั้งค่า start_threshold_w เหนือกำลังขัดขวางการเดรน", + "delay_confirm_seconds": "กำลังไฟต้องคงอยู่ระหว่างเกณฑ์หยุด (W) และเกณฑ์เริ่มต้น (W) เป็นเวลานี้ก่อนที่ WashData จะเข้าสู่สถานะ 'กำลังรอการเริ่มต้น' ช่วยกรองยอดพุ่งสั้นๆ ระหว่างการเลื่อนเมนู ค่าเริ่มต้น: 60 วินาที", + "delay_timeout_hours": "หมดเวลาเพื่อความปลอดภัย: หากเครื่องยังคงอยู่ใน 'กำลังรอการเริ่มต้น' หลังจากผ่านไปหลายชั่วโมง WashData จะรีเซ็ตเป็นปิด ค่าเริ่มต้น: 8 ชม." + } + }, + "external_triggers_section": { + "name": "ทริกเกอร์ภายนอก ประตู และการหยุดชั่วคราว", + "description": "เซ็นเซอร์ทริกเกอร์สิ้นสุดภายนอกแบบเลือกได้ เซ็นเซอร์ประตูสำหรับตรวจจับการหยุดชั่วคราว/สถานะสะอาด และสวิตช์ตัดไฟเมื่อหยุดชั่วคราว", + "data": { + "external_end_trigger_enabled": "เปิดใช้งานทริกเกอร์การสิ้นสุดรอบภายนอก", + "external_end_trigger": "เซ็นเซอร์ปลายรอบภายนอก", + "external_end_trigger_inverted": "สลับลอจิกทริกเกอร์ (ทริกเกอร์เปิดปิด)", + "door_sensor_entity": "เซ็นเซอร์ประตู", + "pause_cuts_power": "ตัดไฟเมื่อหยุดชั่วคราว", + "switch_entity": "สวิตช์เอนทิตี (สำหรับการหยุดตัดไฟชั่วคราว)", + "notify_unload_delay_minutes": "การแจ้งเตือนการรอซักรีดล่าช้า (นาที)" + }, + "data_description": { + "external_end_trigger_enabled": "เปิดใช้งานการตรวจสอบเซ็นเซอร์ไบนารีภายนอกเพื่อกระตุ้นให้รอบการทำงานเสร็จสมบูรณ์", + "external_end_trigger": "เลือกเอนทิตีเซ็นเซอร์ไบนารี เมื่อเซ็นเซอร์นี้ทริกเกอร์ วงจรปัจจุบันจะสิ้นสุดด้วยสถานะ 'เสร็จสมบูรณ์'", + "external_end_trigger_inverted": "ตามค่าเริ่มต้น ทริกเกอร์จะเริ่มทำงานเมื่อเซ็นเซอร์เปิด เลือกช่องนี้เพื่อเริ่มทำงานเมื่อเซ็นเซอร์ปิดแทน", + "door_sensor_entity": "เซ็นเซอร์ไบนารี่เสริมสำหรับประตูเครื่องจักร (เปิด = เปิด, ปิด = ปิด) เมื่อประตูเปิดออกระหว่างโปรแกรมที่ใช้งานอยู่ WashData จะยืนยันโปรแกรมดังกล่าวว่าหยุดชั่วคราวโดยเจตนา หลังจากสิ้นสุดรอบ หากประตูยังคงปิดอยู่ สถานะจะเปลี่ยนเป็น 'ทำความสะอาด' จนกว่าประตูจะเปิด หมายเหตุ: การปิดประตูจะไม่กลับมาทำงานต่อโดยอัตโนมัติตามรอบที่หยุดชั่วคราว - การกลับมาทำงานต่อจะต้องถูกกระตุ้นด้วยตนเองผ่านปุ่ม ทำงานต่อรอบการทำงานหรือบริการ", + "pause_cuts_power": "เมื่อหยุดชั่วคราวผ่านปุ่มหยุดรอบชั่วคราวหรือบริการ ให้ปิดเอนทิตีสวิตช์ที่กำหนดค่าไว้ด้วย จะมีผลเฉพาะเมื่อมีการกำหนดค่าเอนทิตีสวิตช์สำหรับอุปกรณ์นี้", + "switch_entity": "เอนทิตีสวิตช์เพื่อสลับเมื่อหยุดชั่วคราวหรือดำเนินการต่อ จำเป็นเมื่อเปิดใช้งาน 'ตัดไฟเมื่อหยุดชั่วคราว'", + "notify_unload_delay_minutes": "ส่งการแจ้งเตือนผ่านช่องทางแจ้งเตือนเมื่อสิ้นสุดรอบและไม่ได้เปิดประตูเป็นเวลาหลายนาที (ต้องใช้เซ็นเซอร์ประตู) ตั้งค่าเป็น 0 เพื่อปิดใช้งาน ค่าเริ่มต้น: 60 นาที" + } + }, + "pump_section": { + "name": "ตัวเฝ้าติดตามปั๊ม", + "description": "ใช้กับประเภทอุปกรณ์ปั๊มเท่านั้น จะส่งเหตุการณ์หากรอบการทำงานของปั๊มเกินระยะเวลานี้", + "data": { + "pump_stuck_duration": "เกณฑ์การแจ้งเตือนปั๊มติด (s) (เฉพาะปั๊ม)" + }, + "data_description": { + "pump_stuck_duration": "หากรอบปั๊มยังคงทำงานอยู่หลังจากผ่านไปหลายวินาทีนี้ เหตุการณ์ ha_washdata_pump_stuck จะทำงานหนึ่งครั้ง ใช้สิ่งนี้เพื่อตรวจจับมอเตอร์ที่ติดขัด (การทำงานของปั๊มบ่อโดยทั่วไปคือต่ำกว่า 60 วินาที) ค่าเริ่มต้น: 1800 วินาที (30 นาที) ประเภทอุปกรณ์ปั๊มเท่านั้น" + } + }, + "device_link_section": { + "name": "ลิงค์อุปกรณ์", + "description": "คุณสามารถเลือกเชื่อมโยงอุปกรณ์ WashData นี้กับอุปกรณ์ Home Assistant ที่มีอยู่ (เช่น ปลั๊กอัจฉริยะหรือตัวอุปกรณ์เอง) จากนั้นอุปกรณ์ WashData จะแสดงเป็น \"เชื่อมต่อผ่าน\" อุปกรณ์นั้น เว้นว่างไว้เพื่อให้ WashData เป็นอุปกรณ์แบบสแตนด์อโลน", + "data": { + "linked_device": "อุปกรณ์ที่เชื่อมโยง" + }, + "data_description": { + "linked_device": "เลือกอุปกรณ์ที่จะเชื่อมต่อ WashData สิ่งนี้จะเพิ่มการอ้างอิง 'เชื่อมต่อผ่าน' ในรีจิสทรีอุปกรณ์ WashData จะเก็บการ์ดอุปกรณ์และเอนทิตีของตัวเอง ล้างการเลือกเพื่อลบลิงก์" + } + } + } + }, + "diagnostics": { + "title": "การวินิจฉัยและการบำรุงรักษา", + "description": "ดำเนินการบำรุงรักษา เช่น การรวมวงจรที่กระจัดกระจายหรือการย้ายข้อมูลที่เก็บไว้\n\n**การใช้พื้นที่เก็บข้อมูล**\n\n- ขนาดไฟล์: {file_size_kb} KB\n- รอบ: {cycle_count}\n- โปรไฟล์: {profile_count}\n- การติดตามการแก้ไขข้อบกพร่อง: {debug_count}", + "menu_options": { + "reprocess_history": "การบำรุงรักษา: ประมวลผลใหม่และเพิ่มประสิทธิภาพข้อมูล", + "clear_debug_data": "ล้างข้อมูลการดีบัก (เพิ่มพื้นที่ว่าง)", + "wipe_history": "ล้างข้อมูลทั้งหมดสำหรับอุปกรณ์นี้ (ไม่สามารถย้อนกลับได้)", + "export_import": "ส่งออก/นำเข้า JSON ด้วยการตั้งค่า (คัดลอก/วาง)", + "menu_back": "← ย้อนกลับ" + } + }, + "clear_debug_data": { + "title": "ล้างข้อมูลการดีบัก", + "description": "คุณแน่ใจหรือไม่ว่าต้องการลบการติดตามการแก้ไขข้อบกพร่องที่เก็บไว้ทั้งหมด การดำเนินการนี้จะทำให้มีพื้นที่ว่างมากขึ้น แต่จะลบข้อมูลการจัดอันดับโดยละเอียดออกจากรอบที่ผ่านมา" + }, + "manage_cycles": { + "title": "จัดการรอบ", + "description": "รอบล่าสุด:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "ติดป้ายกำกับรอบเก่าโดยอัตโนมัติ", + "select_cycle_to_label": "วงจรเฉพาะของฉลาก", + "select_cycle_to_delete": "ลบไซเคิล", + "interactive_editor": "ผสาน / แยกตัวแก้ไขเชิงโต้ตอบ", + "trim_cycle_select": "ข้อมูลรอบการตัดแต่ง", + "menu_back": "← ย้อนกลับ" + } + }, + "manage_cycles_empty": { + "title": "จัดการรอบ", + "description": "ยังไม่มีการบันทึกรอบ", + "menu_options": { + "auto_label_cycles": "ติดป้ายกำกับรอบเก่าโดยอัตโนมัติ", + "menu_back": "← ย้อนกลับ" + } + }, + "manage_profiles": { + "title": "จัดการโปรไฟล์", + "description": "สรุปโปรไฟล์:\n{profile_summary}", + "menu_options": { + "create_profile": "สร้างโปรไฟล์ใหม่", + "edit_profile": "แก้ไข/เปลี่ยนชื่อโปรไฟล์", + "delete_profile_select": "ลบโปรไฟล์", + "profile_stats": "สถิติโปรไฟล์", + "cleanup_profile": "ล้างประวัติ - กราฟ & ลบ", + "assign_profile_phases_select": "กำหนดช่วงเฟส", + "menu_back": "← ย้อนกลับ" + } + }, + "manage_profiles_empty": { + "title": "จัดการโปรไฟล์", + "description": "ยังไม่มีการสร้างโปรไฟล์", + "menu_options": { + "create_profile": "สร้างโปรไฟล์ใหม่", + "menu_back": "← ย้อนกลับ" + } + }, + "manage_phase_catalog": { + "title": "จัดการแคตตาล็อกเฟส", + "description": "แค็ตตาล็อกเฟส (ค่าเริ่มต้นสำหรับอุปกรณ์นี้ + เฟสที่กำหนดเอง):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "สร้างเฟสใหม่", + "phase_catalog_edit_select": "แก้ไขเฟส", + "phase_catalog_delete": "ลบเฟส", + "menu_back": "← ย้อนกลับ" + } + }, + "phase_catalog_create": { + "title": "สร้างเฟสที่กำหนดเอง", + "description": "สร้างชื่อเฟสและคำอธิบายแบบกำหนดเอง และเลือกประเภทอุปกรณ์ที่จะนำไปใช้กับ", + "data": { + "target_device_type": "ประเภทอุปกรณ์เป้าหมาย", + "phase_name": "ชื่อเฟส", + "phase_description": "คำอธิบายเฟส" + } + }, + "phase_catalog_edit_select": { + "title": "แก้ไขเฟสที่กำหนดเอง", + "description": "เลือกเฟสที่กำหนดเองเพื่อแก้ไข", + "data": { + "phase_name": "เฟสที่กำหนดเอง" + } + }, + "phase_catalog_edit": { + "title": "แก้ไขเฟสที่กำหนดเอง", + "description": "ขั้นตอนการแก้ไข: {phase_name}", + "data": { + "phase_name": "ชื่อเฟส", + "phase_description": "คำอธิบายเฟส" + } + }, + "phase_catalog_delete": { + "title": "ลบเฟสแบบกำหนดเอง", + "description": "เลือกเฟสแบบกำหนดเองที่จะลบ ช่วงที่กำหนดโดยใช้ระยะนี้จะถูกลบออก", + "data": { + "phase_name": "เฟสที่กำหนดเอง" + } + }, + "assign_profile_phases": { + "title": "กำหนดช่วงเฟส", + "description": "การแสดงตัวอย่างเฟสสำหรับโปรไฟล์: **{profile_name}**\n\n{timeline_svg}\n\n**ช่วงปัจจุบัน:**\n{current_ranges}\n\nใช้การดำเนินการด้านล่างเพื่อเพิ่ม แก้ไข ลบ หรือบันทึกช่วง", + "menu_options": { + "assign_profile_phases_add": "เพิ่มช่วงเฟส", + "assign_profile_phases_edit_select": "แก้ไขช่วงเฟส", + "assign_profile_phases_delete": "ลบช่วงเฟส", + "phase_ranges_clear": "ล้างทุกช่วง", + "assign_profile_phases_auto_detect": "ตรวจจับเฟสอัตโนมัติ", + "phase_ranges_save": "บันทึกและส่งคืน", + "menu_back": "← ย้อนกลับ" + } + }, + "assign_profile_phases_add": { + "title": "เพิ่มช่วงเฟส", + "description": "เพิ่มช่วงหนึ่งเฟสให้กับร่างปัจจุบัน", + "data": { + "phase_name": "เฟส", + "start_min": "เริ่มต้นนาที", + "end_min": "นาทีสุดท้าย" + } + }, + "assign_profile_phases_edit_select": { + "title": "แก้ไขช่วงเฟส", + "description": "เลือกช่วงที่จะแก้ไข", + "data": { + "range_index": "ช่วงเฟส" + } + }, + "assign_profile_phases_edit": { + "title": "แก้ไขช่วงเฟส", + "description": "อัปเดตช่วงเฟสที่เลือก", + "data": { + "phase_name": "เฟส", + "start_min": "เริ่มต้นนาที", + "end_min": "นาทีสุดท้าย" + } + }, + "assign_profile_phases_delete": { + "title": "ลบช่วงเฟส", + "description": "เลือกช่วงที่จะลบออกจากแบบร่าง", + "data": { + "range_index": "ช่วงเฟส" + } + }, + "assign_profile_phases_auto_detect": { + "title": "เฟสที่ตรวจพบอัตโนมัติ", + "description": "โปรไฟล์: **{profile_name}**\n\n{timeline_svg}\n\n**ตรวจพบเฟส {detected_count} เฟสโดยอัตโนมัติ** เลือกการดำเนินการด้านล่าง", + "data": { + "action": "การกระทำ" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "ชื่อเฟสที่ตรวจพบ", + "description": "โปรไฟล์: **{profile_name}**\n\n**ตรวจพบ {detected_count} เฟส:**\n{ranges_summary}\n\nกำหนดชื่อให้กับแต่ละเฟส" + }, + "assign_profile_phases_select": { + "title": "กำหนดช่วงเฟส", + "description": "เลือกโปรไฟล์เพื่อกำหนดค่าช่วงเฟส", + "data": { + "profile": "โปรไฟล์" + } + }, + "profile_stats": { + "title": "สถิติโปรไฟล์", + "description": "{stats_table}" + }, + "create_profile": { + "title": "สร้างโปรไฟล์ใหม่", + "description": "สร้างโปรไฟล์ใหม่ด้วยตนเองหรือจากรอบที่ผ่านมา\n\nตัวอย่างชื่อโปรไฟล์: 'ผ้าฝ้าย 40°C', 'Eco', 'Quick Wash', 'ผ้าฝ้าย + ซักแห้งตู้'\n\n**สิ่งที่เครื่องจับคู่ใช้:** รูปแบบการใช้พลังงาน ระยะเวลารวมของวงจร และพลังงานทั้งหมด มันไม่ได้อ่านการตั้งค่าอุณหภูมิหรือการหมุนโดยตรง\n\n**โปรไฟล์ที่จะตรวจจับอัตโนมัติได้อย่างน่าเชื่อถือ:** โปรแกรมที่มีระยะเวลาหรือรูปแบบพลังงานที่แตกต่างกันอย่างชัดเจน - เช่น การซักด่วน 20 นาที เทียบกับ รอบผ้าฝ้าย 3 ชั่วโมง หรือรอบการซักอย่างเดียว เทียบกับการซัก+ซักแห้งแบบผสม (ซึ่งโดยทั่วไปจะใช้เวลานานกว่า 2-3×)\n\n**โปรไฟล์ที่อาจจำเป็นต้องเลือกด้วยตนเอง:** ตัวแปรของโปรแกรมเดียวกันที่แตกต่างกันเฉพาะอุณหภูมิหรือความเร็วในการปั่น (เช่น คอตตอน 40°C กับคอตตอน 60°C) มักจะให้พลังงานรูปร่างคล้ายกัน ระบบจะยังคงพยายามแยกแยะความแตกต่างและเรียนรู้จากการแก้ไขของคุณเมื่อเวลาผ่านไป แต่คาดว่าจะยืนยันการจับคู่ด้วยตนเองในตอนแรก\n\n**เคล็ดลับสำหรับเครื่องซักผ้า-เครื่องอบผ้า:** สร้างโปรไฟล์แยกต่างหากสำหรับการซักอย่างเดียวและการซัก+ซักแห้ง - ระยะเวลาและพลังงานที่แตกต่างกันมากเพียงพอสำหรับการตรวจจับอัตโนมัติที่เชื่อถือได้", + "data": { + "profile_name": "ชื่อโปรไฟล์", + "reference_cycle": "วงจรอ้างอิง (ไม่บังคับ)", + "manual_duration": "ระยะเวลาด้วยตนเอง (นาที)" + } + }, + "edit_profile": { + "title": "แก้ไขโปรไฟล์", + "description": "เลือกโปรไฟล์ที่จะเปลี่ยนชื่อ", + "data": { + "profile": "โปรไฟล์" + } + }, + "rename_profile": { + "title": "แก้ไขรายละเอียดโปรไฟล์", + "description": "โปรไฟล์ปัจจุบัน: {current_name}", + "data": { + "new_name": "ชื่อโปรไฟล์", + "manual_duration": "ระยะเวลาด้วยตนเอง (นาที)" + } + }, + "delete_profile_select": { + "title": "ลบโปรไฟล์", + "description": "เลือกโปรไฟล์ที่จะลบ", + "data": { + "profile": "โปรไฟล์" + } + }, + "delete_profile_confirm": { + "title": "ยืนยันการลบโปรไฟล์", + "description": "⚠️ การดำเนินการนี้จะลบโปรไฟล์อย่างถาวร: {profile_name}", + "data": { + "unlabel_cycles": "ลบฉลากออกจากรอบการใช้โปรไฟล์นี้" + } + }, + "auto_label_cycles": { + "title": "ติดป้ายกำกับรอบเก่าโดยอัตโนมัติ", + "description": "พบทั้งหมด {total_count} รอบ โปรไฟล์: {profiles}", + "data": { + "confidence_threshold": "ความเชื่อมั่นขั้นต่ำ (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "เลือกวนไปที่ป้ายกำกับ", + "description": "✓ = เสร็จสิ้น ⚠ = ดำเนินการต่อ ✗ = ถูกขัดจังหวะ", + "data": { + "cycle_id": "วงจร" + } + }, + "select_cycle_to_delete": { + "title": "ลบไซเคิล", + "description": "⚠️การดำเนินการนี้จะลบรอบที่เลือกอย่างถาวร", + "data": { + "cycle_id": "วนรอบเพื่อลบ" + } + }, + "label_cycle": { + "title": "วงจรฉลาก", + "description": "{cycle_info}", + "data": { + "profile_name": "โปรไฟล์", + "new_profile_name": "ชื่อโปรไฟล์ใหม่ (หากกำลังสร้าง)" + } + }, + "post_process": { + "title": "ประวัติกระบวนการ (รวม/แยก)", + "description": "ล้างประวัติรอบการทำงานโดยการรวมการวิ่งที่กระจัดกระจายและแยกรอบการผสานที่ไม่ถูกต้องภายในกรอบเวลา ป้อนจำนวนชั่วโมงเพื่อดูย้อนหลัง (ใช้ทั้งหมด 999999)", + "data": { + "time_range": "กรอบเวลามองย้อนกลับ (ชั่วโมง)", + "gap_seconds": "ผสาน/แยกช่องว่าง (วินาที)" + }, + "data_description": { + "time_range": "จำนวนชั่วโมงในการสแกนหารอบที่กระจัดกระจายเพื่อรวมเข้าด้วยกัน ใช้ 999999 เพื่อประมวลผลข้อมูลประวัติทั้งหมด", + "gap_seconds": "เกณฑ์ในการตัดสินใจแยกและรวม ช่องว่างที่สั้นกว่านี้ถูกรวมเข้าด้วยกัน ช่องว่างที่ยาวกว่านี้จะถูกแยกออก ลดค่านี้ลงเพื่อบังคับให้มีการแยกวงจรที่ผสานไม่ถูกต้อง" + } + }, + "cleanup_profile": { + "title": "ล้างประวัติ - เลือกโปรไฟล์", + "description": "เลือกโปรไฟล์เพื่อแสดงภาพ ซึ่งจะสร้างกราฟที่แสดงรอบที่ผ่านมาทั้งหมดสำหรับโปรไฟล์นี้เพื่อช่วยระบุค่าผิดปกติ", + "data": { + "profile": "โปรไฟล์" + } + }, + "cleanup_select": { + "title": "ล้างประวัติ - กราฟ & ลบ", + "description": "![กราฟ]({graph_url})\n\n**การแสดงภาพ {profile_name}**\n\nกราฟแสดงแต่ละรอบเป็นเส้นสี ระบุค่าผิดปกติ (เส้นที่อยู่ไกลจากกลุ่ม) และจับคู่สีในรายการด้านล่างเพื่อลบออก\n\n**เลือกรอบที่จะลบอย่างถาวร:**", + "data": { + "cycles_to_delete": "รอบที่จะลบ (ตรวจสอบสีที่ตรงกัน)" + } + }, + "interactive_editor": { + "title": "ผสาน / แยกตัวแก้ไขเชิงโต้ตอบ", + "description": "เลือกการดำเนินการโดยเจ้าหน้าที่เพื่อดำเนินการกับประวัติรอบเดือนของคุณ", + "menu_options": { + "editor_split": "แยกวงจร (ค้นหาช่องว่าง)", + "editor_merge": "ผสานวงจร (เข้าร่วมส่วนย่อย)", + "editor_delete": "ลบไซเคิล", + "menu_back": "← ย้อนกลับ" + } + }, + "editor_select": { + "title": "เลือก รอบ", + "description": "เลือกรอบที่จะประมวลผล\n\n{info_text}", + "data": { + "selected_cycles": "รอบ" + } + }, + "editor_configure": { + "title": "กำหนดค่าและนำไปใช้", + "description": "{preview_md}", + "data": { + "confirm_action": "การกระทำ", + "merged_profile": "โปรไฟล์ผลลัพธ์", + "new_profile_name": "ชื่อโปรไฟล์ใหม่", + "confirm_commit": "ใช่ ฉันยืนยันการดำเนินการนี้", + "segment_0_profile": "ส่วนที่ 1 โปรไฟล์", + "segment_1_profile": "ส่วนที่ 2 โปรไฟล์", + "segment_2_profile": "ส่วนที่ 3 โปรไฟล์", + "segment_3_profile": "ส่วนที่ 4 โปรไฟล์", + "segment_4_profile": "ส่วนที่ 5 โปรไฟล์", + "segment_5_profile": "ส่วนที่ 6 โปรไฟล์", + "segment_6_profile": "ส่วนที่ 7 โปรไฟล์", + "segment_7_profile": "ส่วนที่ 8 โปรไฟล์", + "segment_8_profile": "ส่วนที่ 9 โปรไฟล์", + "segment_9_profile": "ส่วนที่ 10 โปรไฟล์" + } + }, + "editor_split_params": { + "title": "การกำหนดค่าแบบแยกส่วน", + "description": "ปรับความไวสำหรับการแยกรอบนี้ ช่องว่างที่ไม่ได้ใช้งานใด ๆ ที่นานกว่านี้จะทำให้เกิดการแตกแยก", + "data": { + "split_mode": "วิธีการแยก" + } + }, + "editor_split_auto_params": { + "title": "การกำหนดค่าการแยกอัตโนมัติ", + "description": "ปรับความไวสำหรับการแยกรอบนี้ ช่องว่างช่วงไม่มีการทำงานที่นานกว่านี้จะทำให้เกิดการแยก", + "data": { + "min_gap_seconds": "เกณฑ์ช่องว่างการแยก (วินาที)" + } + }, + "editor_split_manual_params": { + "title": "การประทับเวลาการแยกด้วยตนเอง", + "description": "{preview_md}หน้าต่างรอบ: **{cycle_start_wallclock} → {cycle_end_wallclock}** ในวันที่ {cycle_date}.\n\nป้อนการประทับเวลาการแยกอย่างน้อยหนึ่งรายการ (`HH:MM` หรือ `HH:MM:SS`) หนึ่งรายการต่อบรรทัด รอบจะถูกตัดที่การประทับเวลาแต่ละรายการ — การประทับเวลา N รายการจะได้ N+1 ช่วง.", + "data": { + "split_timestamps": "เวลาแยกรอบ" + } + }, + "reprocess_history": { + "title": "การบำรุงรักษา: ประมวลผลใหม่และเพิ่มประสิทธิภาพข้อมูล", + "description": "ดำเนินการทำความสะอาดข้อมูลประวัติอย่างล้ำลึก:\n1. ตัดการอ่านค่าพลังงานเป็นศูนย์ (เงียบ)\n2. แก้ไขเวลา/ระยะเวลาของรอบ\n3. คำนวณแบบจำลองทางสถิติทั้งหมดใหม่ (ซองจดหมาย)\n\n**เรียกใช้สิ่งนี้เพื่อแก้ไขปัญหาข้อมูลหรือใช้อัลกอริทึมใหม่**" + }, + "wipe_history": { + "title": "ล้างประวัติ (การทดสอบเท่านั้น)", + "description": "⚠️การดำเนินการนี้จะลบรอบและโปรไฟล์ที่เก็บไว้ทั้งหมดสำหรับอุปกรณ์นี้อย่างถาวร สิ่งนี้ไม่สามารถยกเลิกได้!" + }, + "export_import": { + "title": "ส่งออก/นำเข้า JSON", + "description": "คัดลอก/วางรอบ โปรไฟล์ และการตั้งค่าสำหรับอุปกรณ์นี้ ส่งออกด้านล่างหรือวาง JSON เพื่อนำเข้า", + "data": { + "mode": "การกระทำ", + "json_payload": "กำหนดค่า JSON ให้เสร็จสมบูรณ์" + }, + "data_description": { + "mode": "เลือกส่งออกเพื่อคัดลอกข้อมูลและการตั้งค่าปัจจุบัน หรือนำเข้าเพื่อวางข้อมูลที่ส่งออกจากอุปกรณ์ WashData อื่น", + "json_payload": "สำหรับการส่งออก: คัดลอก JSON นี้ (รวมถึงรอบ โปรไฟล์ และการตั้งค่าที่ปรับแต่งทั้งหมด) สำหรับการนำเข้า: วาง JSON ที่ส่งออกจาก WashData" + } + }, + "record_cycle": { + "title": "บันทึกวงจร", + "description": "บันทึกรอบการทำงานด้วยตนเองเพื่อสร้างโปรไฟล์ที่สะอาดปราศจากการรบกวน\n\nสถานะ: **{status}**\nระยะเวลา: {duration} วิ\nตัวอย่าง: {samples}", + "menu_options": { + "record_refresh": "รีเฟรชสถานะ", + "record_stop": "หยุดการบันทึก (บันทึกและประมวลผล)", + "record_start": "เริ่มการบันทึกใหม่", + "record_process": "ประมวลผลการบันทึกครั้งล่าสุด (ตัดแต่งและบันทึก)", + "record_discard": "ละทิ้งการบันทึกครั้งล่าสุด", + "menu_back": "← ย้อนกลับ" + } + }, + "record_process": { + "title": "การบันทึกกระบวนการ", + "description": "![กราฟ]({graph_url})\n\n**กราฟแสดงการบันทึกแบบ Raw** สีฟ้า = เก็บ สีแดง = เสนอการตัดทอน\n*กราฟเป็นแบบคงที่และไม่อัปเดตตามการเปลี่ยนแปลงแบบฟอร์ม*\n\nหยุดการบันทึกแล้ว การตัดแต่งจะสอดคล้องกับอัตราการสุ่มตัวอย่างที่ตรวจพบ (~{sampling_rate}s)\n\nระยะเวลาดิบ: {duration} วินาที\nตัวอย่าง: {samples}", + "data": { + "head_trim": "ตัดแต่งเริ่มต้น (วินาที)", + "tail_trim": "สิ้นสุดการตัดแต่ง (วินาที)", + "save_mode": "บันทึกปลายทาง", + "profile_name": "ชื่อโปรไฟล์" + } + }, + "learning_feedbacks": { + "title": "ผลตอบรับการเรียนรู้", + "description": "ข้อเสนอแนะที่รอดำเนินการ: {count}\n\n{pending_table}\n\nเลือกคำขอตรวจสอบรอบการทำงานเพื่อดำเนินการ", + "menu_options": { + "learning_feedbacks_pick": "ตรวจสอบข้อเสนอแนะที่รอดำเนินการหนึ่งรายการ", + "learning_feedbacks_dismiss_all": "ละทิ้งข้อเสนอแนะที่รอดำเนินการทั้งหมด", + "menu_back": "← ย้อนกลับ" + } + }, + "learning_feedbacks_pick": { + "title": "เลือกข้อเสนอแนะเพื่อทบทวน", + "description": "เลือกคำขอทบทวนรอบที่จะเปิด", + "data": { + "selected_feedback": "เลือกรอบที่จะทบทวน" + } + }, + "learning_feedbacks_empty": { + "title": "ผลตอบรับการเรียนรู้", + "description": "ไม่พบคำขอความคิดเห็นที่รอดำเนินการ", + "menu_options": { + "menu_back": "← ย้อนกลับ" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "ยกเลิกข้อเสนอแนะที่รอดำเนินการทั้งหมด", + "description": "คุณกำลังจะยกเลิกคำขอข้อเสนอแนะที่รอดำเนินการ **{count}** รายการ รอบที่ถูกยกเลิกจะยังคงอยู่ในประวัติของคุณ แต่จะไม่ส่งสัญญาณการเรียนรู้ใหม่ การดำเนินการนี้ไม่สามารถย้อนกลับได้\n\n✅ ทำเครื่องหมายในช่องด้านล่างแล้วคลิก **ส่ง** เพื่อยกเลิกทั้งหมด\n❌ ปล่อยว่างไว้โดยไม่เลือกแล้วคลิก **ส่ง** เพื่อยกเลิกและย้อนกลับ", + "data": { + "confirm_dismiss_all": "ยืนยัน: ละทิ้งคำขอข้อเสนอแนะที่รอดำเนินการทั้งหมด" + } + }, + "resolve_feedback": { + "title": "แก้ไขคำติชม", + "description": "{comparison_img}{candidates_table}\n**โปรไฟล์ที่ตรวจพบ**: {detected_profile} ({confidence_pct}%)\n**ระยะเวลาโดยประมาณ**: {est_duration_min} นาที\n**ระยะเวลาจริง**: {act_duration_min} นาที\n\n__เลือกการกระทำด้านล่าง:__", + "data": { + "action": "คุณอยากจะทำอะไร?", + "corrected_profile": "โปรแกรมที่ถูกต้อง (ถ้าแก้ไข)", + "corrected_duration": "แก้ไขระยะเวลาเป็นวินาที (หากแก้ไข)" + } + }, + "trim_cycle_select": { + "title": "ทริมวงจร - เลือกวงจร", + "description": "เลือกรอบที่จะตัดแต่ง ✓ = เสร็จสิ้น ⚠ = ดำเนินการต่อ ✗ = ถูกขัดจังหวะ", + "data": { + "cycle_id": "วงจร" + } + }, + "trim_cycle": { + "title": "วงจรตัดแต่ง", + "description": "หน้าต่างปัจจุบัน: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "การกระทำ" + } + }, + "trim_cycle_start": { + "title": "ตั้งค่าเริ่มต้นการตัดแต่ง", + "description": "กรอบเวลาวงจร: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nการเริ่มต้นปัจจุบัน: **{current_wallclock}** (+{current_offset_min} นาทีจากการเริ่มต้นรอบ)\n\nเลือกเวลาเริ่มต้นใหม่โดยใช้ตัวเลือกนาฬิกาด้านล่าง", + "data": { + "trim_start_time": "เวลาเริ่มต้นใหม่", + "trim_start_min": "เริ่มต้นใหม่ (นาทีจากการเริ่มต้นรอบ)" + } + }, + "trim_cycle_end": { + "title": "ตั้งค่าการสิ้นสุดการตัดแต่ง", + "description": "กรอบเวลาวงจร: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nสิ้นสุดปัจจุบัน: **{current_wallclock}** (+{current_offset_min} นาทีจากการเริ่มต้นรอบ)\n\nเลือกเวลาสิ้นสุดใหม่โดยใช้ตัวเลือกนาฬิกาด้านล่าง", + "data": { + "trim_end_time": "เวลาสิ้นสุดใหม่", + "trim_end_min": "สิ้นสุดใหม่ (นาทีจากการเริ่มต้นรอบ)" + } + } + }, + "error": { + "import_failed": "การนำเข้าล้มเหลว โปรดวาง JSON ส่งออก WashData ที่ถูกต้อง", + "select_exactly_one": "เลือกให้ครบ 1 รอบสำหรับการดำเนินการนี้", + "select_at_least_one": "เลือกอย่างน้อย 1 รอบสำหรับการดำเนินการนี้", + "select_at_least_two": "เลือกอย่างน้อย 2 รอบสำหรับการดำเนินการนี้", + "profile_exists": "มีโปรไฟล์ชื่อนี้อยู่แล้ว", + "rename_failed": "ไม่สามารถเปลี่ยนชื่อโปรไฟล์ได้ โปรไฟล์อาจไม่มีอยู่หรือมีผู้ใช้ชื่อใหม่แล้ว", + "assignment_failed": "ไม่สามารถกำหนดโปรไฟล์ให้หมุนเวียนได้", + "duplicate_phase": "มีเฟสที่ใช้ชื่อนี้อยู่แล้ว", + "phase_not_found": "ไม่พบเฟสที่เลือก", + "invalid_phase_name": "ชื่อเฟสต้องไม่เว้นว่าง", + "phase_name_too_long": "ชื่อเฟสยาวเกินไป", + "invalid_phase_range": "ช่วงเฟสไม่ถูกต้อง จุดสิ้นสุดต้องมากกว่าจุดเริ่มต้น", + "invalid_phase_timestamp": "การประทับเวลาไม่ถูกต้อง ใช้รูปแบบ YYYY-MM-DD HH:MM หรือ HH:MM", + "overlapping_phase_ranges": "ช่วงเฟสทับซ้อนกัน ปรับช่วงแล้วลองอีกครั้ง", + "incomplete_phase_row": "แต่ละแถวของเฟสจะต้องมีเฟสบวกกับค่าเริ่มต้น/สิ้นสุดที่สมบูรณ์สำหรับโหมดที่เลือก", + "timestamp_mode_cycle_required": "โปรดเลือกวงจรของแหล่งที่มาเมื่อใช้โหมดการประทับเวลา", + "no_phase_ranges": "ยังไม่มีช่วงเฟสสำหรับการดำเนินการนั้น", + "feedback_notification_title": "WashData: ตรวจสอบรอบการทำงาน ({device})", + "feedback_notification_message": "**อุปกรณ์**: {device}\n**โปรแกรม**: {program} (ความเชื่อมั่น {confidence}%)\n**เวลา**: {time}\n\nWashData ต้องการความช่วยเหลือจากคุณเพื่อตรวจสอบวงจรที่ตรวจพบนี้\n\nโปรดไปที่ **การตั้งค่า > อุปกรณ์และบริการ > WashData > กำหนดค่า > คำติชมการเรียนรู้** เพื่อยืนยันหรือแก้ไขผลลัพธ์นี้", + "suggestions_ready_notification_title": "WashData: การตั้งค่าที่แนะนำพร้อมแล้ว ({device})", + "suggestions_ready_notification_message": "ขณะนี้เซ็นเซอร์ **การตั้งค่าที่แนะนำ** รายงานคำแนะนำที่สามารถดำเนินการได้ **{count}**\n\nหากต้องการตรวจสอบและนำไปใช้: **การตั้งค่า > อุปกรณ์และบริการ > WashData > กำหนดค่า > การตั้งค่าขั้นสูง > ใช้ค่าที่แนะนำ**\n\nคำแนะนำเป็นทางเลือกและแสดงให้ตรวจสอบก่อนบันทึก", + "trim_range_invalid": "เริ่มต้นจะต้องมาก่อนสิ้นสุด โปรดปรับจุดตัดของคุณ", + "trim_failed": "ตัดแต่งล้มเหลว วงจรอาจไม่มีอยู่อีกต่อไปหรือหน้าต่างว่างเปล่า", + "invalid_split_timestamp": "เวลาที่ระบุไม่ถูกต้องหรืออยู่นอกช่วงเวลาของรอบ ใช้รูปแบบ HH:MM หรือ HH:MM:SS ภายในช่วงเวลาของรอบ", + "no_split_segments_found": "ไม่สามารถสร้างส่วนที่ถูกต้องจากเวลาที่ระบุเหล่านี้ได้ ตรวจสอบให้แน่ใจว่าแต่ละเวลาอยู่ในช่วงเวลาของรอบ และแต่ละส่วนยาวอย่างน้อย 1 นาที", + "auto_tune_suggestion": "{device_type} {device_title} ตรวจพบวงจรโกสต์ การเปลี่ยนแปลง min_power ที่แนะนำ: {current_min}W -> {new_min}W (ไม่ได้ใช้โดยอัตโนมัติ)", + "auto_tune_title": "WashData ปรับอัตโนมัติ", + "auto_tune_fallback": "{device_type} {device_title} ตรวจพบวงจรโกสต์ การเปลี่ยนแปลง min_power ที่แนะนำ: {current_min}W -> {new_min}W (ไม่ได้ใช้โดยอัตโนมัติ)" + }, + "abort": { + "no_cycles_found": "ไม่พบรอบ", + "no_split_segments_found": "ไม่พบส่วนที่สามารถแยกได้ในรอบที่เลือก", + "cycle_not_found": "ไม่สามารถโหลดรอบที่เลือกได้", + "no_power_data": "ไม่มีข้อมูลการติดตามพลังงานสำหรับรอบการทำงานที่เลือก", + "no_profiles_found": "ไม่พบโปรไฟล์ สร้างโปรไฟล์ก่อน", + "no_profiles_for_matching": "ไม่มีโปรไฟล์สำหรับการจับคู่ สร้างโปรไฟล์ก่อน", + "no_unlabeled_cycles": "มีป้ายกำกับทุกรอบแล้ว", + "no_suggestions": "ยังไม่มีค่าที่แนะนำ รันสักสองสามรอบแล้วลองอีกครั้ง", + "no_predictions": "ไม่มีการคาดการณ์", + "no_custom_phases": "ไม่พบเฟสที่กำหนดเอง", + "reprocess_success": "ประมวลผลซ้ำสำเร็จแล้ว {count} รอบ", + "debug_data_cleared": "ล้างข้อมูลการแก้ไขข้อบกพร่องจาก {count} รอบเรียบร้อยแล้ว" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "วงจรเริ่มต้น", + "cycle_finish": "วงจรเสร็จสิ้น", + "cycle_live": "ความคืบหน้าสด" + } + }, + "common_text": { + "options": { + "unlabeled": "(ไม่มีป้ายกำกับ)", + "create_new_profile": "สร้างโปรไฟล์ใหม่", + "remove_label": "ลบป้ายกำกับ", + "no_reference_cycle": "(ไม่มีรอบการอ้างอิง)", + "all_device_types": "อุปกรณ์ทุกประเภท", + "current_suffix": "(ปัจจุบัน)", + "editor_select_info": "เลือก 1 รอบที่จะแยก หรือ 2+ รอบเพื่อรวม", + "editor_select_info_split": "เลือก 1 รอบเพื่อแยก", + "editor_select_info_merge": "เลือก 2+ รอบเพื่อรวม", + "editor_select_info_delete": "เลือก 1+ รอบเพื่อลบ", + "no_cycles_recorded": "ยังไม่มีการบันทึกรอบ", + "profile_summary_line": "- **{name}**: {count} รอบ เฉลี่ย {avg}ม", + "no_profiles_created": "ยังไม่มีการสร้างโปรไฟล์", + "phase_preview": "ดูตัวอย่างเฟส", + "phase_preview_no_curve": "เส้นโค้งโปรไฟล์เฉลี่ยยังไม่พร้อมใช้งาน เรียกใช้/ติดป้ายกำกับรอบเพิ่มเติมสำหรับโปรไฟล์นี้", + "average_power_curve": "เส้นโค้งกำลังเฉลี่ย", + "unit_min": "นาที", + "profile_option_fmt": "{name} ({count} รอบ ~{duration}m เฉลี่ย)", + "profile_option_short_fmt": "{name} ({count} รอบ, ~{duration}ม.)", + "deleted_cycles_from_profile": "ลบ {count} รอบจาก {profile}", + "not_enough_data_graph": "ข้อมูลไม่เพียงพอที่จะสร้างกราฟ", + "no_profiles_found": "ไม่พบโปรไฟล์", + "cycle_info_fmt": "วงจร: {start}, {duration}ม. กระแส: {label}", + "top_candidates_header": "### ผู้สมัครอันดับต้นๆ", + "tbl_profile": "โปรไฟล์", + "tbl_confidence": "ความมั่นใจ", + "tbl_correlation": "ความสัมพันธ์", + "tbl_duration_match": "การจับคู่ระยะเวลา", + "detected_profile": "โปรไฟล์ที่ตรวจพบ", + "estimated_duration": "ระยะเวลาโดยประมาณ", + "actual_duration": "ระยะเวลาจริง", + "choose_action": "__เลือกการกระทำด้านล่าง:__", + "tbl_count": "นับ", + "tbl_avg": "เฉลี่ย", + "tbl_min": "นาที", + "tbl_max": "สูงสุด", + "tbl_energy_avg": "พลังงาน (เฉลี่ย)", + "tbl_energy_total": "พลังงาน (รวม)", + "tbl_consistency": "ประกอบด้วย.", + "tbl_last_run": "วิ่งครั้งสุดท้าย", + "graph_legend_title": "ตำนานกราฟ", + "graph_legend_body": "แถบสีน้ำเงินแสดงถึงช่วงการดึงกำลังขั้นต่ำและสูงสุดที่สังเกตได้ เส้นนี้แสดงกราฟกำลังเฉลี่ย", + "recording_preview": "ตัวอย่างการบันทึก", + "trim_start": "ตัดแต่งเริ่มต้น", + "trim_end": "ตัดแต่งปลาย", + "split_preview_title": "ดูตัวอย่างแบบแยกส่วน", + "split_preview_found_fmt": "พบ {count} ส่วน", + "split_preview_confirm_fmt": "คลิกยืนยันเพื่อแบ่งรอบนี้เป็น {count} รอบแยกกัน", + "merge_preview_title": "รวมตัวอย่าง", + "merge_preview_joining_fmt": "เข้าร่วม {count} รอบ ช่องว่างจะเต็มไปด้วยการอ่าน 0W", + "editor_delete_preview_title": "ตัวอย่างก่อนลบ", + "editor_delete_preview_intro": "รอบที่เลือกจะถูกลบอย่างถาวร:", + "editor_delete_preview_confirm": "คลิกยืนยันเพื่อลบระเบียนรอบเหล่านี้อย่างถาวร", + "no_power_preview": "*ไม่มีข้อมูลพลังงานสำหรับการดูตัวอย่าง*", + "profile_comparison": "การเปรียบเทียบโปรไฟล์", + "actual_cycle_label": "รอบนี้ (ตามจริง)", + "storage_usage_header": "การใช้พื้นที่เก็บข้อมูล", + "storage_file_size": "ขนาดไฟล์", + "storage_cycles": "รอบ", + "storage_profiles": "โปรไฟล์", + "storage_debug_traces": "ดีบักการติดตาม", + "overview_suffix": "ภาพรวม", + "phase_preview_for_profile": "ตัวอย่างเฟสสำหรับโปรไฟล์", + "current_ranges_header": "ช่วงปัจจุบัน", + "assign_phases_help": "ใช้การดำเนินการด้านล่างเพื่อเพิ่ม แก้ไข ลบ หรือบันทึกช่วง", + "profile_summary_header": "สรุปโปรไฟล์", + "recent_cycles_header": "รอบล่าสุด", + "trim_cycle_preview_title": "ดูตัวอย่างการตัดวงจร", + "trim_cycle_preview_no_data": "ไม่มีข้อมูลกำลังสำหรับรอบนี้", + "trim_cycle_preview_kept_suffix": "เก็บไว้", + "table_program": "โปรแกรม", + "table_when": "เมื่อใด", + "table_length": "ความยาว", + "table_match": "การจับคู่", + "table_profile": "โปรไฟล์", + "table_cycles": "รอบ", + "table_avg_length": "ความยาวเฉลี่ย", + "table_last_run": "วิ่งครั้งสุดท้าย", + "table_avg_energy": "พลังงานเฉลี่ย", + "table_detected_program": "โปรแกรมที่ตรวจพบ", + "table_confidence": "ความมั่นใจ", + "table_reported": "ที่รายงาน", + "phase_builtin_suffix": "(บิวท์อิน)", + "phase_none_available": "ไม่มีเฟสว่าง.", + "settings_deprecation_warning": "⚠️ **ประเภทอุปกรณ์ที่เลิกใช้งานแล้ว:** {device_type} มีกำหนดการนำออกในรุ่นต่อๆ ไป ไปป์ไลน์ที่ตรงกันของ WashData ไม่ได้ให้ผลลัพธ์ที่เชื่อถือได้สำหรับคลาสอุปกรณ์นี้ การบูรณาการของคุณยังคงดำเนินต่อไปจนถึงช่วงที่เลิกใช้งาน หากต้องการปิดเสียงคำเตือนนี้ ให้เปลี่ยน **ประเภทอุปกรณ์** ด้านล่างเป็นประเภทที่รองรับ (เครื่องซักผ้า เครื่องอบผ้า เครื่องซักผ้า-เครื่องอบผ้า เครื่องล้างจาน เครื่องทอดอากาศ เครื่องทำขนมปัง หรือปั๊ม) หรือเป็น **อื่นๆ (ขั้นสูง)** หากเครื่องใช้ไฟฟ้าของคุณไม่ตรงกับประเภทใดๆ ที่รองรับ **อื่นๆ (ขั้นสูง)** จัดส่งค่าเริ่มต้นทั่วไปโดยเจตนาซึ่งไม่ได้ปรับแต่งสำหรับอุปกรณ์ใดๆ โดยเฉพาะ ดังนั้นคุณจะต้องกำหนดค่าเกณฑ์ ระยะหมดเวลา และพารามิเตอร์ที่ตรงกันด้วยตนเอง การตั้งค่าที่มีอยู่ทั้งหมดของคุณจะถูกเก็บรักษาไว้เมื่อคุณเปลี่ยน", + "phase_other_device_types": "อุปกรณ์ประเภทอื่นๆ:" + } + }, + "interactive_editor_action": { + "options": { + "split": "แยกวงจร (ค้นหาช่องว่าง)", + "merge": "ผสานวงจร (เข้าร่วมส่วนย่อย)", + "delete": "ลบไซเคิล" + } + }, + "split_mode": { + "options": { + "auto": "ตรวจจับช่วงว่างอัตโนมัติ", + "manual": "เวลาที่ระบุเอง" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "การบำรุงรักษา: ประมวลผลใหม่และเพิ่มประสิทธิภาพข้อมูล", + "clear_debug_data": "ล้างข้อมูลการดีบัก (เพิ่มพื้นที่ว่าง)", + "wipe_history": "ล้างข้อมูลทั้งหมดสำหรับอุปกรณ์นี้ (ไม่สามารถย้อนกลับได้)", + "export_import": "ส่งออก/นำเข้า JSON ด้วยการตั้งค่า (คัดลอก/วาง)" + } + }, + "export_import_mode": { + "options": { + "export": "ส่งออกข้อมูลทั้งหมด", + "import": "นำเข้า/รวมข้อมูล" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "ติดป้ายกำกับรอบเก่าโดยอัตโนมัติ", + "select_cycle_to_label": "วงจรเฉพาะของฉลาก", + "select_cycle_to_delete": "ลบไซเคิล", + "interactive_editor": "ผสาน / แยกตัวแก้ไขเชิงโต้ตอบ", + "trim_cycle": "ข้อมูลรอบการตัดแต่ง" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "สร้างโปรไฟล์ใหม่", + "edit_profile": "แก้ไข/เปลี่ยนชื่อโปรไฟล์", + "delete_profile": "ลบโปรไฟล์", + "profile_stats": "สถิติโปรไฟล์", + "cleanup_profile": "ล้างประวัติ - กราฟ & ลบ", + "assign_phases": "กำหนดช่วงเฟส" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "สร้างเฟสใหม่", + "edit_custom_phase": "แก้ไขเฟส", + "delete_custom_phase": "ลบเฟส" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "เพิ่มช่วงเฟส", + "edit_range": "แก้ไขช่วงเฟส", + "delete_range": "ลบช่วงเฟส", + "clear_ranges": "ล้างทุกช่วง", + "auto_detect_ranges": "ตรวจจับเฟสอัตโนมัติ", + "save_ranges": "บันทึกและส่งคืน" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "รีเฟรชสถานะ", + "stop_recording": "หยุดการบันทึก (บันทึกและประมวลผล)", + "start_recording": "เริ่มการบันทึกใหม่", + "process_recording": "ประมวลผลการบันทึกครั้งล่าสุด (ตัดแต่งและบันทึก)", + "discard_recording": "ละทิ้งการบันทึกครั้งล่าสุด" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "สร้างโปรไฟล์ใหม่", + "existing_profile": "เพิ่มไปยังโปรไฟล์ที่มีอยู่", + "discard": "ยกเลิกการบันทึก" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "ยืนยัน - การตรวจจับที่ถูกต้อง", + "correct": "ถูกต้อง - เลือกโปรแกรม/ระยะเวลา", + "ignore": "เพิกเฉย - วงจรบวกลวง/มีเสียงดัง", + "delete": "ลบ - ลบออกจากประวัติ" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "ตั้งชื่อและสมัครเฟส", + "cancel": "กลับไปโดยไม่มีการเปลี่ยนแปลง" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "กำหนดจุดเริ่มต้น", + "set_end": "กำหนดจุดสิ้นสุด", + "reset": "รีเซ็ตเป็นระยะเวลาเต็ม", + "apply": "ใช้ตัดแต่ง", + "cancel": "ยกเลิก" + } + }, + "device_type": { + "options": { + "washing_machine": "เครื่องซักผ้า", + "dryer": "เครื่องอบผ้า", + "washer_dryer": "เครื่องซักผ้า-เครื่องอบผ้า Combo", + "dishwasher": "เครื่องล้างจาน", + "coffee_machine": "เครื่องชงกาแฟ (เลิกใช้แล้ว)", + "ev": "รถยนต์ไฟฟ้า (เลิกใช้แล้ว)", + "air_fryer": "หม้อทอดไร้น้ำมัน", + "heat_pump": "ปั๊มความร้อน (เลิกใช้แล้ว)", + "bread_maker": "เครื่องทำขนมปัง", + "pump": "ปั๊ม/ปั๊มบ่อ", + "oven": "เตาอบ (เลิกใช้แล้ว)", + "other": "อื่นๆ (ขั้นสูง)" + } + } + }, + "services": { + "label_cycle": { + "name": "วงจรฉลาก", + "description": "กำหนดโปรไฟล์ที่มีอยู่ให้กับรอบที่ผ่านมา หรือลบป้ายกำกับออก", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์ติดฉลากเครื่องซักผ้า" + }, + "cycle_id": { + "name": "รหัสไซเคิล", + "description": "ID ของรอบที่จะติดป้ายกำกับ" + }, + "profile_name": { + "name": "ชื่อโปรไฟล์", + "description": "ชื่อของโปรไฟล์ที่มีอยู่ (สร้างโปรไฟล์ในเมนูจัดการโปรไฟล์) เว้นว่างไว้เพื่อนำป้ายกำกับออก" + } + } + }, + "create_profile": { + "name": "สร้างโปรไฟล์", + "description": "สร้างโปรไฟล์ใหม่ (แบบสแตนด์อโลนหรือตามรอบการอ้างอิง)", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้า" + }, + "profile_name": { + "name": "ชื่อโปรไฟล์", + "description": "ชื่อของโปรไฟล์ใหม่ (เช่น 'Heavy Duty', 'Delicates')" + }, + "reference_cycle_id": { + "name": "รหัสไซเคิลอ้างอิง", + "description": "รหัสรอบทางเลือกเพื่อใช้เป็นฐานของโปรไฟล์นี้" + } + } + }, + "delete_profile": { + "name": "ลบโปรไฟล์", + "description": "ลบโปรไฟล์และเลือกยกเลิกการติดป้ายกำกับรอบการใช้งาน", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้า" + }, + "profile_name": { + "name": "ชื่อโปรไฟล์", + "description": "โปรไฟล์ที่จะลบ" + }, + "unlabel_cycles": { + "name": "เลิกติดป้ายกำกับรอบ", + "description": "ลบป้ายกำกับโปรไฟล์ออกจากรอบการใช้โปรไฟล์นี้" + } + } + }, + "auto_label_cycles": { + "name": "ติดป้ายกำกับรอบเก่าโดยอัตโนมัติ", + "description": "ติดป้ายกำกับรอบการทำงานที่ไม่มีป้ายกำกับย้อนหลังโดยใช้การจับคู่โปรไฟล์", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้า" + }, + "confidence_threshold": { + "name": "เกณฑ์ความเชื่อมั่น", + "description": "ความเชื่อมั่นการจับคู่ขั้นต่ำ (0.50-0.95) เพื่อใช้ป้ายกำกับ" + } + } + }, + "export_config": { + "name": "ส่งออกการกำหนดค่า", + "description": "ส่งออกโปรไฟล์ รอบ และการตั้งค่าของอุปกรณ์นี้เป็นไฟล์ JSON (ต่ออุปกรณ์)", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้าเพื่อส่งออก" + }, + "path": { + "name": "เส้นทาง", + "description": "พาธไฟล์สัมบูรณ์ทางเลือกที่จะเขียน (ค่าเริ่มต้นคือ /config/ha_washdata_export_{entry}.json)" + } + } + }, + "import_config": { + "name": "นำเข้าการกำหนดค่า", + "description": "นำเข้าโปรไฟล์ รอบ และการตั้งค่าสำหรับอุปกรณ์นี้จากไฟล์ส่งออก JSON (การตั้งค่าอาจถูกเขียนทับ)", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้าที่จะนำเข้า" + }, + "path": { + "name": "เส้นทาง", + "description": "เส้นทางที่แน่นอนไปยังไฟล์ JSON ส่งออกที่จะนำเข้า" + } + } + }, + "submit_cycle_feedback": { + "name": "ส่งคำติชมรอบ", + "description": "ยืนยันหรือแก้ไขโปรแกรมที่ตรวจพบอัตโนมัติหลังจากรอบการทำงานเสร็จสมบูรณ์ ระบุ `entry_id` (ขั้นสูง) หรือ `device_id` (แนะนำ)", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์ WashData (แนะนำแทน entry_id)" + }, + "entry_id": { + "name": "รหัสรายการ", + "description": "รหัสรายการการกำหนดค่าสำหรับอุปกรณ์ (ทางเลือกอื่นคือ device_id)" + }, + "cycle_id": { + "name": "รหัสไซเคิล", + "description": "รหัสรอบที่แสดงในการแจ้งเตือน / บันทึกข้อเสนอแนะ" + }, + "user_confirmed": { + "name": "ยืนยันโปรแกรมที่ตรวจพบ", + "description": "ตั้งค่าเป็นจริงหากโปรแกรมที่ตรวจพบถูกต้อง" + }, + "corrected_profile": { + "name": "โปรไฟล์ที่แก้ไขแล้ว", + "description": "หากไม่ได้รับการยืนยัน ให้ระบุชื่อโปรไฟล์/โปรแกรมที่ถูกต้อง" + }, + "corrected_duration": { + "name": "ระยะเวลาที่แก้ไข (วินาที)", + "description": "ระยะเวลาแก้ไขเพิ่มเติมในหน่วยวินาที" + }, + "notes": { + "name": "หมายเหตุ", + "description": "หมายเหตุเพิ่มเติมเกี่ยวกับรอบนี้" + } + } + }, + "record_start": { + "name": "บันทึกรอบการเริ่มต้น", + "description": "เริ่มบันทึกวงจรการทำความสะอาดด้วยตนเอง (ข้ามตรรกะที่ตรงกันทั้งหมด)", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้าที่จะบันทึก" + } + } + }, + "record_stop": { + "name": "บันทึกรอบการหยุด", + "description": "หยุดการบันทึกด้วยตนเอง", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์เครื่องซักผ้าหยุดการบันทึก" + } + } + }, + "trim_cycle": { + "name": "วงจรตัดแต่ง", + "description": "ตัดข้อมูลกำลังของรอบที่ผ่านมาเป็นกรอบเวลาที่กำหนด", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์ WashData" + }, + "cycle_id": { + "name": "รหัสไซเคิล", + "description": "ID ของวงรอบที่จะตัดแต่ง" + }, + "trim_start_s": { + "name": "ตัดแต่งเริ่มต้น (วินาที)", + "description": "เก็บข้อมูลจากการชดเชยนี้ในไม่กี่วินาที ค่าเริ่มต้น 0" + }, + "trim_end_s": { + "name": "การตัดขอบ (วินาที)", + "description": "เก็บข้อมูลจนถึงออฟเซ็ตนี้ในไม่กี่วินาที ค่าเริ่มต้น = ระยะเวลาเต็ม" + } + } + }, + "pause_cycle": { + "name": "หยุดวงจรชั่วคราว", + "description": "หยุดวงจรที่ใช้งานชั่วคราวสำหรับอุปกรณ์ WashData", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์ WashData เพื่อหยุดชั่วคราว" + } + } + }, + "resume_cycle": { + "name": "ดำเนินรอบต่อ", + "description": "ดำเนินรอบที่หยุดชั่วคราวต่อสำหรับอุปกรณ์ WashData", + "fields": { + "device_id": { + "name": "อุปกรณ์", + "description": "อุปกรณ์ WashData เพื่อดำเนินการต่อ" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "วิ่ง" + }, + "match_ambiguity": { + "name": "จับคู่ความคลุมเครือ" + } + }, + "sensor": { + "washer_state": { + "name": "สถานะ", + "state": { + "off": "ปิด", + "idle": "ไม่ได้ใช้งาน", + "starting": "กำลังเริ่มต้น", + "running": "วิ่ง", + "paused": "หยุดชั่วคราว", + "user_paused": "หยุดชั่วคราวโดยผู้ใช้", + "ending": "ตอนจบ", + "finished": "ที่เสร็จเรียบร้อย", + "anti_wrinkle": "ต่อต้านริ้วรอย", + "delay_wait": "กำลังรอการเริ่มต้น", + "interrupted": "ขัดจังหวะ", + "force_stopped": "บังคับหยุด", + "rinse": "ล้าง", + "unknown": "ไม่ทราบ", + "clean": "ทำความสะอาด" + } + }, + "washer_program": { + "name": "โปรแกรม" + }, + "time_remaining": { + "name": "เวลาที่เหลืออยู่" + }, + "total_duration": { + "name": "ระยะเวลารวม" + }, + "cycle_progress": { + "name": "ความคืบหน้า" + }, + "current_power": { + "name": "กำลังไฟฟ้าปัจจุบัน" + }, + "elapsed_time": { + "name": "เวลาที่ผ่านไป" + }, + "debug_info": { + "name": "ข้อมูลการแก้ไขข้อบกพร่อง" + }, + "match_confidence": { + "name": "จับคู่ความมั่นใจ" + }, + "top_candidates": { + "name": "ผู้สมัครอันดับต้นๆ", + "state": { + "none": "ไม่มี" + } + }, + "current_phase": { + "name": "เฟสปัจจุบัน" + }, + "wash_phase": { + "name": "เฟส" + }, + "profile_cycle_count": { + "name": "โปรไฟล์ {profile_name} จำนวน", + "unit_of_measurement": "รอบ" + }, + "suggestions": { + "name": "การตั้งค่าที่แนะนำ" + }, + "pump_runs_today": { + "name": "ปั๊มรัน (24 ชั่วโมงล่าสุด)" + }, + "cycle_count": { + "name": "จำนวนรอบ" + } + }, + "select": { + "program_select": { + "name": "โปรแกรมวงจร", + "state": { + "auto_detect": "ตรวจจับอัตโนมัติ" + } + } + }, + "button": { + "force_end_cycle": { + "name": "บังคับให้สิ้นสุดวงจร" + }, + "pause_cycle": { + "name": "หยุดวงจรชั่วคราว" + }, + "resume_cycle": { + "name": "ดำเนินการต่อวงจร" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "จำเป็นต้องมี device_id ที่ถูกต้อง" + }, + "cycle_id_required": { + "message": "จำเป็นต้องมี Cycle_id ที่ถูกต้อง" + }, + "profile_name_required": { + "message": "จำเป็นต้องมีprofile_nameที่ถูกต้อง" + }, + "device_not_found": { + "message": "ไม่พบอุปกรณ์" + }, + "no_config_entry": { + "message": "ไม่พบรายการการกำหนดค่าสำหรับอุปกรณ์" + }, + "integration_not_loaded": { + "message": "ไม่ได้โหลดการบูรณาการสำหรับอุปกรณ์นี้" + }, + "cycle_not_found_or_no_power": { + "message": "ไม่พบรอบการทำงานหรือไม่มีข้อมูลกำลัง" + }, + "trim_failed_empty_window": { + "message": "การตัดขอบล้มเหลว - ไม่พบรอบการทำงาน ไม่มีข้อมูลพลังงาน หรือหน้าต่างผลลัพธ์ว่างเปล่า" + }, + "trim_invalid_range": { + "message": "trim_end_s ต้องมากกว่า trim_start_s" + } + } +} diff --git a/custom_components/ha_washdata/translations/tr.json b/custom_components/ha_washdata/translations/tr.json new file mode 100644 index 0000000..23d5eec --- /dev/null +++ b/custom_components/ha_washdata/translations/tr.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData Kurulumu", + "description": "Çamaşır makinenizi veya diğer cihazınızı yapılandırın.\n\nGüç sensörü gereklidir.\n\n**Ardından, ilk profilinizi oluşturmak isteyip istemediğiniz sorulacaktır.**", + "data": { + "name": "Cihaz Adı", + "device_type": "Cihaz Türü", + "power_sensor": "Güç Sensörü", + "min_power": "Minimum Güç Eşiği (W)" + }, + "data_description": { + "name": "Bu cihaz için kolay bir ad (ör. 'Çamaşır Makinesi', 'Bulaşık Makinesi').", + "device_type": "Bu ne tür bir cihaz? Tespit ve etiketlemenin özel olarak yapılmasına yardımcı olur.", + "power_sensor": "Akıllı prizinizin gerçek zamanlı güç tüketimini (watt cinsinden) bildiren sensör varlığı.", + "min_power": "Bu eşiğin üzerindeki güç değerleri (watt cinsinden) cihazın çalıştığını gösterir. Çoğu cihaz için 2W ile başlayın." + } + }, + "first_profile": { + "title": "İlk Profili Oluştur", + "description": "**Döngü**, cihazınızın tamamen çalıştırılmasıdır (ör. bir miktar çamaşır). **Profil** bu programları türe göre gruplandırır (ör. 'Pamuklu', 'Hızlı Yıkama'). Artık bir profil oluşturmak entegrasyonun süreyi hemen tahmin etmesine yardımcı oluyor.\n\nOtomatik olarak algılanmazsa bu profili cihaz kontrollerinden manuel olarak seçebilirsiniz.\n\n**Bu adımı atlamak için 'Profil Adı'nı boş bırakın.**", + "data": { + "profile_name": "Profil Adı (İsteğe bağlı)", + "manual_duration": "Tahmini Süre (dakika)" + } + } + }, + "error": { + "cannot_connect": "Bağlantı başarısız oldu", + "invalid_auth": "Geçersiz kimlik doğrulama", + "unknown": "Beklenmeyen hata" + }, + "abort": { + "already_configured": "Cihaz zaten yapılandırılmış" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Öğrenme Geri Bildirimlerini İnceleyin", + "settings": "Ayarlar", + "notifications": "Bildirimler", + "manage_cycles": "Döngüleri Yönet", + "manage_profiles": "Profilleri Yönet", + "manage_phase_catalog": "Aşama Kataloğunu Yönet", + "record_cycle": "Kayıt Döngüsü (Manuel)", + "diagnostics": "Teşhis ve Bakım" + } + }, + "apply_suggestions": { + "title": "Önerilen Değerleri Kopyala", + "description": "Bu, mevcut önerilen değerleri kayıtlı Ayarlarınıza kopyalayacaktır. Bu tek seferlik bir işlemdir ve otomatik üzerine yazma özelliğini etkinleştirmez.\n\nKopyalanacak önerilen değerler:\n{suggested}", + "data": { + "confirm": "Kopyalama işlemini onaylayın" + } + }, + "apply_suggestions_confirm": { + "title": "Önerilen Değişiklikleri İnceleyin", + "description": "WashData, **{pending_count}** önerilen değerlerin uygulanmasını buldu.\n\nDeğişiklikler:\n{changes}\n\n✅ Bu değişiklikleri hemen uygulamak ve kaydetmek için aşağıdaki kutuyu işaretleyin ve **Gönder**'i tıklayın.\n❌ İşaretlemeden bırakın ve iptal edip geri dönmek için **Gönder**'i tıklayın.", + "data": { + "confirm_apply_suggestions": "Bu değişiklikleri uygulayın ve kaydedin" + } + }, + "settings": { + "title": "Ayarlar", + "description": "{deprecation_warning}Algılama eşiklerini, öğrenmeyi ve gelişmiş davranışı ayarlayın. Varsayılanlar çoğu kurulum için iyi çalışır.\n\nŞu anda kullanılabilir önerilen ayarlar: {suggestions_count}.\nBu değer 0'dan büyükse, önerileri gözden geçirmek için Gelişmiş Ayarlar'ı açın ve 'Önerilen Değerleri Uygula' seçeneğini kullanın.", + "data": { + "apply_suggestions": "Önerilen Değerleri Uygula", + "device_type": "Cihaz Türü", + "power_sensor": "Güç Sensörü Öğesi", + "min_power": "Minimum Güç (W)", + "off_delay": "Döngü Sonu Gecikmesi (ler)", + "notify_actions": "Bildirim Eylemleri", + "notify_people": "Bu İnsanlar Eve Gelene Kadar Teslimatı Erteleyin", + "notify_only_when_home": "Seçilen Kişi Eve Gelene Kadar Bildirimleri Erteleyin", + "notify_fire_events": "Otomasyon Olaylarını Tetikle", + "notify_start_services": "Döngü Başlatma - Bildirim Hedefleri", + "notify_finish_services": "Döngü Sonu - Bildirim Hedefleri", + "notify_live_services": "Canlı İlerleme - Bildirim Hedefleri", + "notify_before_end_minutes": "Tamamlama Öncesi Bildirim (bitişten dakikalar önce)", + "notify_title": "Bildirim Başlığı", + "notify_icon": "Bildirim Simgesi", + "notify_start_message": "Mesaj Formatını Başlat", + "notify_finish_message": "Mesaj Formatını Bitir", + "notify_pre_complete_message": "Tamamlanma Öncesi Mesaj Formatı", + "show_advanced": "Gelişmiş Ayarları Düzenle (Sonraki Adım)" + }, + "data_description": { + "apply_suggestions": "Öğrenme algoritması tarafından önerilen değerleri forma kopyalamak için bu kutuyu işaretleyin. Kaydetmeden önce inceleyin.\n\nÖnerilen (öğrenmeden):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} sn\n- watchdog_interval: {suggested_watchdog_interval} sn\n- no_update_active_timeout: {suggested_no_update_active_timeout} sn\n- profile_match_interval: {suggested_profile_match_interval} sn\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} sn\n{suggested_reason}", + "device_type": "Cihaz tipini seçin (Çamaşır Makinesi, Kurutucu, Bulaşık Makinesi, Kahve Makinesi, EV). Bu etiket, daha iyi organizasyon için her döngüde saklanır.", + "power_sensor": "Gerçek zamanlı gücü (watt cinsinden) bildiren sensör varlığı. Geçmiş verileri kaybetmeden bunu istediğiniz zaman değiştirebilirsiniz; akıllı fişi değiştirirseniz kullanışlıdır.", + "min_power": "Bu eşiğin üzerindeki güç değerleri (watt cinsinden) cihazın çalıştığını gösterir. Çoğu cihaz için 2W ile başlayın.", + "off_delay": "Tamamlanmayı işaretlemek için yumuşatılmış gücün saniyeler içinde durma eşiğinin altında kalması gerekir. Varsayılan: 120s.", + "notify_actions": "Bildirimler (komut dosyaları, bildirim, TTS vb.) için çalıştırılacak isteğe bağlı Ev Asistanı eylem dizisi. Ayarlanırsa eylemler, geri dönüş bildirim hizmetinden önce çalıştırılır.", + "notify_people": "Varlık geçidi için kullanılan kişi varlıkları. Etkinleştirildiğinde, seçilen en az bir kişi evde olana kadar bildirim teslimi ertelenir.", + "notify_only_when_home": "Etkinleştirilirse, seçilen en az bir kişi evde olana kadar bildirimleri erteleyin.", + "notify_fire_events": "Etkinleştirilirse, otomasyonları desteklemek için döngü başlangıcı ve bitişi (ha_washdata_cycle_started ve ha_washdata_cycle_ended) için Home Assistant olaylarını tetikleyin.", + "notify_start_services": "Bir döngü başladığında uyarı verecek bir veya daha fazla bildirim hizmeti. Başlangıç ​​bildirimlerini atlamak için boş bırakın.", + "notify_finish_services": "Bir döngü tamamlandığında veya tamamlanmaya yaklaştığında uyarı verecek bir veya daha fazla bildirim hizmeti. Bitirme bildirimlerini atlamak için boş bırakın.", + "notify_live_services": "Döngü çalışırken canlı ilerleme güncellemelerini almak için bir veya daha fazla bildirim hizmeti (yalnızca mobil yardımcı uygulama). Canlı güncellemeleri atlamak için boş bırakın.", + "notify_before_end_minutes": "Bir bildirimin tetiklenmesi için döngünün tahmini bitiminden dakikalar önce. Devre dışı bırakmak için 0'a ayarlayın. Varsayılan: 0.", + "notify_title": "Bildirimler için özel başlık. {device} yer tutucusunu destekler.", + "notify_icon": "Bildirimler için kullanılacak simge (ör. mdi:çamaşır makinesi). Simge göndermemek için boş bırakın.", + "notify_start_message": "Bir döngü başladığında gönderilen mesaj. {device} yer tutucusunu destekler.", + "notify_finish_message": "Bir döngü tamamlandığında gönderilen mesaj. {device}, {duration}, {program}, {energy_kwh}, {cost} yer tutucularını destekler.", + "notify_pre_complete_message": "Döngü çalışırken yinelenen canlı ilerlemenin metni güncellenir. {device}, {minutes}, {program} yer tutucularını destekler." + } + }, + "notifications": { + "title": "Bildirimler", + "description": "Bildirim kanallarını, şablonları ve isteğe bağlı canlı mobil ilerleme güncellemelerini yapılandırın.", + "data": { + "notify_actions": "Bildirim Eylemleri", + "notify_people": "Bu İnsanlar Eve Gelene Kadar Teslimatı Erteleyin", + "notify_only_when_home": "Seçilen Kişi Eve Gelene Kadar Bildirimleri Erteleyin", + "notify_fire_events": "Otomasyon Olaylarını Tetikle", + "notify_start_services": "Döngü Başlatma - Bildirim Hedefleri", + "notify_finish_services": "Döngü Sonu - Bildirim Hedefleri", + "notify_live_services": "Canlı İlerleme - Bildirim Hedefleri", + "notify_before_end_minutes": "Tamamlama Öncesi Bildirim (bitişten dakikalar önce)", + "notify_live_interval_seconds": "Canlı Güncelleme Aralığı (saniye)", + "notify_live_overrun_percent": "Canlı Güncelleme Aşım İzinleri (%)", + "notify_live_chronometer": "Kronometre Geri Sayım Sayacı", + "notify_title": "Bildirim Başlığı", + "notify_icon": "Bildirim Simgesi", + "notify_start_message": "Mesaj Formatını Başlat", + "notify_finish_message": "Mesaj Formatını Bitir", + "notify_pre_complete_message": "Tamamlanma Öncesi Mesaj Formatı", + "energy_price_entity": "Enerji Fiyatı – Varlık (isteğe bağlı)", + "energy_price_static": "Enerji Fiyatı - Statik değer (isteğe bağlı)", + "go_back": "Kaydetmeden geri dön", + "notify_reminder_message": "Hatırlatma Mesajı Formatı", + "notify_timeout_seconds": "Şu Süreden Sonra Otomatik Kapat (saniye)", + "notify_channel": "Bildirim Kanalı (durum/canlı/hatırlatma)", + "notify_finish_channel": "Biten Bildirim Kanalı" + }, + "data_description": { + "go_back": "Bunu işaretleyin ve yukarıdaki tüm değişiklikleri atıp ana menüye dönmek için Gönder'e tıklayın.", + "notify_actions": "Bildirimler (komut dosyaları, bildirim, TTS vb.) için çalıştırılacak isteğe bağlı Ev Asistanı eylem dizisi. Ayarlanırsa eylemler, geri dönüş bildirim hizmetinden önce çalıştırılır.", + "notify_people": "Varlık geçidi için kullanılan kişi varlıkları. Etkinleştirildiğinde, seçilen en az bir kişi evde olana kadar bildirim teslimi ertelenir.", + "notify_only_when_home": "Etkinleştirilirse, seçilen en az bir kişi evde olana kadar bildirimleri erteleyin.", + "notify_fire_events": "Etkinleştirilirse, otomasyonları desteklemek için döngü başlangıcı ve bitişi (ha_washdata_cycle_started ve ha_washdata_cycle_ended) için Home Assistant olaylarını tetikleyin.", + "notify_start_services": "Bir döngü başladığında uyarı verecek bir veya daha fazla bildirim hizmeti (ör. notify.mobile_app_my_phone). Başlangıç ​​bildirimlerini atlamak için boş bırakın.", + "notify_finish_services": "Bir döngü tamamlandığında veya tamamlanmaya yaklaştığında uyarı verecek bir veya daha fazla bildirim hizmeti. Bitirme bildirimlerini atlamak için boş bırakın.", + "notify_live_services": "Döngü çalışırken canlı ilerleme güncellemelerini almak için bir veya daha fazla bildirim hizmeti (yalnızca mobil yardımcı uygulama). Canlı güncellemeleri atlamak için boş bırakın.", + "notify_before_end_minutes": "Bir bildirimin tetiklenmesi için döngünün tahmini bitiminden dakikalar önce. Devre dışı bırakmak için 0'a ayarlayın. Varsayılan: 0.", + "notify_live_interval_seconds": "Çalıştırma sırasında ilerleme güncellemelerinin ne sıklıkta gönderildiği. Daha düşük değerler daha duyarlıdır ancak daha fazla bildirim tüketir. Minimum 30 saniye uygulanır; 30 saniyenin altındaki değerler otomatik olarak 30 saniyeye yuvarlanır.", + "notify_live_overrun_percent": "Uzun/fazla çalıştırma döngüleri için tahmini güncelleme sayısının üzerindeki güvenlik marjı (örneğin %20, 1,2 kat tahmini güncellemeye izin verir).", + "notify_live_chronometer": "Etkinleştirildiğinde, her canlı güncelleme tahmini bitiş süresine kadar bir kronometre geri sayımı içerir. Güncellemeler arasında bildirim zamanlayıcısı cihazda otomatik olarak yavaşlar (yalnızca Android tamamlayıcı uygulaması).", + "notify_title": "Bildirimler için özel başlık. {device} yer tutucusunu destekler.", + "notify_icon": "Bildirimler için kullanılacak simge (ör. mdi:çamaşır makinesi). Simge göndermemek için boş bırakın.", + "notify_start_message": "Bir döngü başladığında gönderilen mesaj. {device} yer tutucusunu destekler.", + "notify_finish_message": "Bir döngü tamamlandığında gönderilen mesaj. {device}, {duration}, {program}, {energy_kwh}, {cost} yer tutucularını destekler.", + "energy_price_entity": "Geçerli elektrik fiyatını içeren sayısal bir varlık seçin (ör. sensör.elektrik_fiyat, giriş_numarası.enerji_oranı). Ayarlandığında, {cost} yer tutucusunu etkinleştirir. Her ikisi de yapılandırılmışsa statik değere göre önceliklidir.", + "energy_price_static": "kWh başına sabit elektrik fiyatı. Ayarlandığında, {cost} yer tutucusunu etkinleştirir. Yukarıda bir varlık yapılandırılmışsa yoksayılır. Mesaj şablonuna para birimi simgenizi ekleyin, ör. {cost} €.", + "notify_reminder_message": "Tek seferlik hatırlatma metni, tahmini bitiş süresinden önce yapılandırılmış dakika sayısını gönderdi. Yukarıdaki yinelenen canlı güncellemelerden farklıdır. {device}, {minutes}, {program} yer tutucularını destekler.", + "notify_timeout_seconds": "Bu kadar saniyeden sonra bildirimleri otomatik olarak kapat (tamamlayıcı uygulama 'zaman aşımı' olarak iletilir). Kapatılana kadar bunları tutmak için 0'a ayarlayın. Varsayılan: 0.", + "notify_channel": "Durum, canlı ilerleme ve hatırlatma bildirimleri için Android tamamlayıcı uygulama bildirim kanalı. Bir kanalın sesi ve önemi, kanal adı ilk kez kullanıldığında yardımcı uygulamada yapılandırılır - WashData yalnızca kanal adını ayarlar. Uygulama varsayılanı için boş bırakın.", + "notify_finish_channel": "Bitti uyarısı için ayrı bir Android kanalı (aynı zamanda hatırlatıcı ve çamaşır bekleyen dırdırı tarafından da kullanılır), böylece kendi sesine sahip olabilir. Yukarıdaki kanalı yeniden kullanmak için boş bırakın.", + "notify_pre_complete_message": "Döngü çalışırken yinelenen canlı ilerlemenin metni güncellenir. {device}, {minutes}, {program} yer tutucularını destekler." + } + }, + "advanced_settings": { + "title": "Gelişmiş Ayarlar", + "description": "Algılama eşiklerini, öğrenmeyi ve gelişmiş davranışı ayarlayın. Varsayılanlar çoğu kurulum için iyi çalışır.", + "sections": { + "suggestions_section": { + "name": "Önerilen Ayarlar", + "description": "Kullanıma hazır {suggestions_count} öğrenilmiş öneri var. Kaydetmeden önce önerilen değişiklikleri gözden geçirmek için 'Önerilen Değerleri Uygula' seçeneğini işaretleyin.", + "data": { + "apply_suggestions": "Önerilen Değerleri Uygula" + }, + "data_description": { + "apply_suggestions": "Öğrenme algoritmasından önerilen değerlere yönelik bir inceleme adımı açmak için bu kutuyu işaretleyin. Hiçbir şey otomatik olarak kaydedilmez.\n\nÖnerilen (öğrenmeden):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} sn\n- watchdog_interval: {suggested_watchdog_interval} sn\n- no_update_active_timeout: {suggested_no_update_active_timeout} sn\n- profile_match_interval: {suggested_profile_match_interval} sn\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} sn\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Algılama ve Güç Eşikleri", + "description": "Cihazın ne zaman AÇIK veya KAPALI kabul edildiğini, enerji eşiklerini ve durum geçişlerinin zamanlamasını belirler.", + "data": { + "start_duration_threshold": "Geri Dönme Süresini Başlat", + "start_energy_threshold": "Enerji Kapısını Başlat (Wh)", + "completion_min_seconds": "Tamamlanma Minimum Çalışma Süresi (saniye)", + "start_threshold_w": "Başlangıç ​​Eşiği (W)", + "stop_threshold_w": "Durdurma Eşiği (W)", + "end_energy_threshold": "Son Enerji Kapısı (Wh)", + "running_dead_zone": "Çalışan Ölü Bölge (saniye)", + "end_repeat_count": "Tekrar Sayısını Sonlandır", + "min_off_gap": "Döngüler Arasındaki Minimum Boşluk", + "sampling_interval": "Örnekleme Aralığı (lar)" + }, + "data_description": { + "start_duration_threshold": "Bu süreden (saniye) daha kısa olan kısa güç artışlarını dikkate almayın. Gerçek döngü başlamadan önce önyükleme artışlarını (ör. 2 saniye için 60 W) filtreler. Varsayılan: 5s.", + "start_energy_threshold": "Döngünün onaylanması için START aşamasında en az bu kadar enerji (Wh) biriktirmesi gerekir. Varsayılan: 0,005 Wh.", + "completion_min_seconds": "Bundan daha kısa döngüler, doğal bir şekilde bitseler bile 'kesintiye uğradı' olarak işaretlenecektir. Varsayılan: 600s.", + "start_threshold_w": "Bir döngüyü BAŞLATMAK için gücün bu eşiğin ÜZERİNDE yükselmesi gerekir. Bekleme gücünüzden daha yükseğe ayarlayın. Varsayılan: min_power + 1 W.", + "stop_threshold_w": "Bir döngüyü SONLANDIRMAK için gücün bu eşiğin ALTINA düşmesi gerekir. Gürültünün yanlış uçları tetiklemesini önlemek için sıfırın hemen üstüne ayarlayın. Varsayılan: 0,6 * min_power.", + "end_energy_threshold": "Döngü yalnızca son Düşmede Gecikme penceresindeki enerji bu değerin (Wh) altında olduğunda sona erer. Güç sıfıra yakın dalgalanırsa hatalı uçları önler. Varsayılan: 0,05 Wh.", + "running_dead_zone": "Döngü başladıktan sonra, ilk dönüş aşaması sırasında hatalı uç tespitini önlemek için bu kadar saniye boyunca güç düşüşlerini göz ardı edin. Devre dışı bırakmak için 0'a ayarlayın. Varsayılan: 0s.", + "end_repeat_count": "Döngü sonlandırılmadan önce bitiş koşulunun (kapalı_gecikme için eşiğin altındaki güç) arka arkaya karşılanması gereken sayı. Daha yüksek değerler duraklamalar sırasında hatalı sonları önler. Varsayılan: 1.", + "min_off_gap": "Bir döngü, bir öncekinin bitiminden bu kadar saniye sonra başlarsa, tek bir döngüde BİRLEŞTİRİLECEKTİR.", + "sampling_interval": "Saniye cinsinden minimum örnekleme aralığı. Bu hızdan daha hızlı olan güncellemeler dikkate alınmayacaktır. Varsayılan: 30s (çamaşır makineleri, çamaşır-kurutma makineleri ve bulaşık makineleri için 2s)." + } + }, + "matching_section": { + "name": "Profil Eşleştirme ve Öğrenme", + "description": "WashData'nın çalışan döngüleri öğrenilmiş profillerle ne kadar agresif biçimde eşleştireceğini ve ne zaman geri bildirim isteyeceğini belirler.", + "data": { + "profile_match_min_duration_ratio": "Profil Eşleşmesi Minimum Süre Oranı (0,1-1,0)", + "profile_match_interval": "Profil Eşleşme Aralığı (saniye)", + "profile_match_threshold": "Profil Eşleşme Eşiği", + "profile_unmatch_threshold": "Profil Eşleşmeme Eşiği", + "auto_label_confidence": "Otomatik Etiket Güveni (0-1)", + "learning_confidence": "Geribildirim Talebi Güven (0-1)", + "suppress_feedback_notifications": "Geri Bildirim Bildirimlerini Bastır", + "duration_tolerance": "Süre Toleransı (0-0,5)", + "smoothing_window": "Pencereyi Pürüzsüzleştirme (örnekler)", + "profile_duration_tolerance": "Profil Eşleşme Süresi Toleransı (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Profil eşleştirme için minimum süre oranı. Çalıştırma döngüsünün eşleşmesi için en azından profil süresinin bu kısmı kadar olması gerekir. Daha düşük = daha erken eşleşme. Varsayılan: 0,50 (%50).", + "profile_match_interval": "Bir döngü sırasında profil eşleştirmenin ne sıklıkta (saniye olarak) deneneceği. Daha düşük değerler daha hızlı algılama sağlar ancak daha fazla CPU kullanır. Varsayılan: 300s (5 dakika).", + "profile_match_threshold": "Bir profili eşleşme olarak değerlendirmek için gereken minimum DTW benzerlik puanı (0,0–1,0). Daha yüksek = daha sıkı eşleştirme, daha az yanlış pozitif. Varsayılan: 0,4.", + "profile_unmatch_threshold": "Daha önce eşleşen profilin reddedildiği DTW benzerlik puanı (0,0–1,0). Titremeyi önlemek için eşleşme eşiğinden düşük olmalıdır. Varsayılan: 0,35.", + "auto_label_confidence": "Tamamlandığında bu güven düzeyinde veya üzerinde döngüleri otomatik olarak etiketleyin (0,0–1,0).", + "learning_confidence": "Bir etkinlik aracılığıyla kullanıcı doğrulaması istemek için minimum güven (0,0–1,0).", + "suppress_feedback_notifications": "Etkinleştirildiğinde, WashData yine de dahili olarak takip edecek ve geri bildirim isteyecektir ancak döngüleri onaylamanızı isteyen kalıcı bildirimler göstermeyecektir. Döngüler her zaman doğru şekilde algılandığında ve istemlerin dikkatinizi dağıttığını düşündüğünüzde kullanışlıdır.", + "duration_tolerance": "Bir çalıştırma sırasında kalan süre tahminlerine yönelik tolerans (0,0–0,5, %0–50'ye karşılık gelir).", + "smoothing_window": "Hareketli ortalama yumuşatma için kullanılan son güç okumalarının sayısı.", + "profile_duration_tolerance": "Toplam süre farkı için profil eşleştirme tarafından kullanılan tolerans (0,0–0,5). Varsayılan davranış: ±%25." + } + }, + "timing_section": { + "name": "Zamanlama, Bakım ve Hata Ayıklama", + "description": "Watchdog, ilerleme sıfırlama, otomatik bakım ve hata ayıklama varlıklarının gösterilmesi.", + "data": { + "watchdog_interval": "Watchdog Aralığı (saniye)", + "no_update_active_timeout": "Güncelleme Olmayan Zaman Aşımı (s)", + "progress_reset_delay": "İlerleme Sıfırlama Gecikmesi (saniye)", + "auto_maintenance": "Otomatik Bakımı Etkinleştir", + "expose_debug_entities": "Hata Ayıklama Varlıklarını Açığa Çıkarın", + "save_debug_traces": "Hata Ayıklama İzlerini Kaydet" + }, + "data_description": { + "watchdog_interval": "Çalışırken bekçi köpeği kontrolleri arasındaki saniyeler. Varsayılan: 5s. UYARI: Yanlış duraklamalardan kaçınmak için bunun sensörünüzün güncelleme aralığından DAHA YÜKSEK olduğundan emin olun (örn. sensör her 60 saniyede bir güncelleniyorsa 65 saniye+ kullanın).", + "no_update_active_timeout": "Değişiklik üzerine yayınlama sensörleri için: yalnızca bu kadar saniye boyunca hiçbir güncelleme gelmezse AKTİF bir döngüyü zorla sonlandırın. Düşük güçte tamamlama hala Kapanma Gecikmesini kullanıyor.", + "progress_reset_delay": "Bir döngü tamamlandıktan sonra (%100), ilerlemeyi %0'a sıfırlamadan önce bu kadar saniye boşta bekleyin. Varsayılan: 300s.", + "auto_maintenance": "Otomatik bakımı etkinleştirin (örneklerin onarılması, parçaların birleştirilmesi).", + "expose_debug_entities": "Hata ayıklama için gelişmiş sensörleri (Güven, Faz, Belirsizlik) gösterin.", + "save_debug_traces": "Ayrıntılı sıralama ve güç izleme verilerini geçmişte saklayın (Depolama kullanımını artırır)." + } + }, + "anti_wrinkle_section": { + "name": "Kırışıklık Karşıtı Kalkan (Kurutucular)", + "description": "Hayalet döngüleri önlemek için döngü sonrası tambur dönüşlerini yok sayın. Yalnızca kurutucu / çamaşır-kurutucu için.", + "data": { + "anti_wrinkle_enabled": "Kırışıklık Karşıtı Kalkan (Yalnızca Kurutucu)", + "anti_wrinkle_max_power": "Kırışıklık Karşıtı Maksimum Güç (W)", + "anti_wrinkle_max_duration": "Kırışıklık Karşıtı Maksimum Süre (ler)", + "anti_wrinkle_exit_power": "Kırışıklık Karşıtı Çıkış Gücü (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Hayalet döngüleri önlemek için, bir döngü sona erdikten sonra kısa süreli düşük güçlü tambur dönüşlerini göz ardı edin (yalnızca kurutucu/çamaşır-kurutucu).", + "anti_wrinkle_max_power": "Bu güçteki veya altındaki patlamalar, yeni döngüler yerine kırışık önleyici dönüşler olarak değerlendirilir. Yalnızca Kırışıklık Karşıtı Kalkan etkinleştirildiğinde geçerlidir.", + "anti_wrinkle_max_duration": "Kırışıklık karşıtı rotasyon olarak değerlendirilecek maksimum patlama uzunluğu. Yalnızca Kırışıklık Karşıtı Kalkan etkinleştirildiğinde geçerlidir.", + "anti_wrinkle_exit_power": "Güç bu seviyenin altına düşerse, kırışıklık önleme işleminden hemen çıkın (gerçek kapalı). Yalnızca Kırışıklık Karşıtı Kalkan etkinleştirildiğinde geçerlidir." + } + }, + "delay_start_section": { + "name": "Gecikmeli Başlatma Algılama", + "description": "Sürekli düşük güçlü bekleme durumunu, döngü gerçekten başlayana kadar 'Başlatmayı Bekliyor' durumu olarak değerlendirin.", + "data": { + "delay_start_detect_enabled": "Gecikmeli Başlatma Algılamayı Etkinleştir", + "delay_confirm_seconds": "Gecikmeli Başlatma: Bekleme Onay Süresi (s)", + "delay_timeout_hours": "Gecikmeli Başlatma: Maksimum Bekleme Süresi (saat)" + }, + "data_description": { + "delay_start_detect_enabled": "Etkinleştirildiğinde, kısa bir düşük güç tüketimi artışı ve ardından sürekli bekleme gücü, gecikmeli başlatma olarak algılanır. Durum sensörü gecikme sırasında 'Başlatmayı Bekliyor' mesajını gösteriyor. Döngü gerçekten başlayana kadar hiçbir program süresi veya ilerlemesi takip edilmez. Start_threshold_w'nin drenaj ani gücünün üzerinde ayarlanması gerekir.", + "delay_confirm_seconds": "WashData 'Başlatmayı Bekliyor' durumuna geçmeden önce güç, bu kadar saniye boyunca Durdurma Eşiği (W) ile Başlatma Eşiği (W) arasında kalmalıdır. Bu, menü gezinmesi sırasında oluşan kısa tepe değerlerini filtreler. Varsayılan: 60 s.", + "delay_timeout_hours": "Güvenlik zaman aşımı: Bu kadar saatten sonra makine hala 'Başlatmayı Bekliyor' durumundaysa, WashData Kapalı olarak sıfırlanır. Varsayılan: 8 sa." + } + }, + "external_triggers_section": { + "name": "Harici Tetikleyiciler, Kapı ve Duraklatma", + "description": "İsteğe bağlı harici döngü sonu sensörü, duraklatma / boşaltma algılaması için kapı sensörü ve duraklatma sırasında gücü kesen anahtar.", + "data": { + "external_end_trigger_enabled": "Harici Döngü Sonu Tetikleyicisini Etkinleştir", + "external_end_trigger": "Harici Döngü Sonu Sensörü", + "external_end_trigger_inverted": "Tetikleme Mantığını Tersine Çevir (Tetikleme KAPALI)", + "door_sensor_entity": "Kapı Sensörü", + "pause_cuts_power": "Duraklatıldığında Gücü Kesin", + "switch_entity": "Varlık Değiştir (Güç Kesintisini Duraklatmak için)", + "notify_unload_delay_minutes": "Çamaşır Bekleniyor Bildirim Gecikmesi (dk)" + }, + "data_description": { + "external_end_trigger_enabled": "Döngünün tamamlanmasını tetiklemek için harici bir ikili sensörün izlenmesini etkinleştirin.", + "external_end_trigger": "Bir ikili sensör varlığı seçin. Bu sensör tetiklendiğinde mevcut döngü 'tamamlandı' durumuyla sona erecektir.", + "external_end_trigger_inverted": "Varsayılan olarak tetik, sensör AÇIK konuma getirildiğinde etkinleşir. Bunun yerine sensör KAPALI duruma geldiğinde ateşleme yapmak için bu kutuyu işaretleyin.", + "door_sensor_entity": "Makine kapısı için opsiyonel ikili sensör (açık = açık, kapalı = kapalı). Etkin bir döngü sırasında kapı açıldığında, WashData döngünün kasıtlı olarak duraklatıldığını onaylayacaktır. Döngü sona erdikten sonra kapı hala kapalıysa, kapı açılıncaya kadar durum 'Temiz' olarak değişir. Not: Kapının kapatılması, duraklatılmış bir döngüyü otomatik olarak DEVAM ETMEZ; devam ettirme, Döngüyü Sürdür düğmesi veya hizmet aracılığıyla manuel olarak tetiklenmelidir.", + "pause_cuts_power": "Döngüyü Duraklat düğmesi veya hizmeti aracılığıyla duraklatırken, yapılandırılmış anahtar varlığını da kapatın. Yalnızca bu cihaz için bir anahtar varlığı yapılandırıldığında etkili olur.", + "switch_entity": "Duraklatırken veya devam ettirirken geçiş yapılacak anahtar varlığı. 'Duraklatıldığında Gücü Kes' etkinleştirildiğinde gereklidir.", + "notify_unload_delay_minutes": "Döngü bittikten ve kapı bu kadar dakika boyunca açılmadıktan sonra bitiş bildirimi kanalı aracılığıyla bir bildirim gönderin (Kapı Sensörü gerektirir). Devre dışı bırakmak için 0'a ayarlayın. Varsayılan: 60 dk." + } + }, + "pump_section": { + "name": "Pompa İzleyici", + "description": "Yalnızca pompa cihaz türü içindir. Bir pompa döngüsü bu süreden uzun sürerse olay tetikler.", + "data": { + "pump_stuck_duration": "Pompa Sıkışmış Uyarı Eşiği (ler) (Yalnızca Pompa)" + }, + "data_description": { + "pump_stuck_duration": "Bu kadar saniyeden sonra bir pompa döngüsü hala çalışıyorsa, bir kez ha_washdata_pump_stuck olayı tetiklenir. Sıkışan bir motoru tespit etmek için bunu kullanın (tipik karter pompası çalışması 60 saniyenin altındadır). Varsayılan: 1800 sn (30 dk). Yalnızca pompa cihazı tipi." + } + }, + "device_link_section": { + "name": "Cihaz Bağlantısı", + "description": "İsteğe bağlı olarak bu WashData cihazını mevcut bir Home Assistant cihazına (örneğin akıllı priz veya cihazın kendisi) bağlayın. WashData cihazı daha sonra bu cihaza \"Bağlandı\" olarak gösterilir. WashData'yı bağımsız bir cihaz olarak tutmak için boş bırakın.", + "data": { + "linked_device": "Bağlı Cihaz" + }, + "data_description": { + "linked_device": "WashData'nın bağlanacağı cihazı seçin. Bu, cihaz kaydına 'Şununla bağlanıldı' referansını ekler; WashData kendi cihaz kartını ve varlıklarını saklar. Bağlantıyı kaldırmak için seçimi temizleyin." + } + } + } + }, + "diagnostics": { + "title": "Teşhis ve Bakım", + "description": "Parçalanmış döngüleri birleştirmek veya depolanan verileri taşımak gibi bakım işlemlerini gerçekleştirin.\n\n**Depolama Kullanımı**\n\n- Dosya Boyutu: {file_size_kb} KB\n- Döngüler: {cycle_count}\n- Profiller: {profile_count}\n- Hata Ayıklama İzleri: {debug_count}", + "menu_options": { + "reprocess_history": "Bakım: Verileri Yeniden İşleyin ve Optimize Edin", + "clear_debug_data": "Hata Ayıklama Verilerini Temizle (Yer açın)", + "wipe_history": "Bu cihaza ait TÜM verileri silin (geri döndürülemez)", + "export_import": "JSON'u ayarlarla dışa/içe aktar (kopyala/yapıştır)", + "menu_back": "← Geri" + } + }, + "clear_debug_data": { + "title": "Hata Ayıklama Verilerini Temizle", + "description": "Saklanan tüm hata ayıklama izlerini silmek istediğinizden emin misiniz? Bu, alanı boşaltacak ancak geçmiş döngülerdeki ayrıntılı sıralama bilgilerini kaldıracaktır." + }, + "manage_cycles": { + "title": "Döngüleri Yönet", + "description": "Son döngüler:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Eski Döngüleri Otomatik Etiketle", + "select_cycle_to_label": "Etikete Özel Döngü", + "select_cycle_to_delete": "Döngüyü Sil", + "interactive_editor": "Birleştir/Böl İnteraktif Düzenleyici", + "trim_cycle_select": "Döngü Verilerini Kırp", + "menu_back": "← Geri" + } + }, + "manage_cycles_empty": { + "title": "Döngüleri Yönet", + "description": "Henüz kayıtlı döngü yok.", + "menu_options": { + "auto_label_cycles": "Eski Döngüleri Otomatik Etiketle", + "menu_back": "← Geri" + } + }, + "manage_profiles": { + "title": "Profilleri Yönet", + "description": "Profil Özeti:\n{profile_summary}", + "menu_options": { + "create_profile": "Yeni Profil Oluştur", + "edit_profile": "Profili Düzenle/Yeniden Adlandır", + "delete_profile_select": "Profili Sil", + "profile_stats": "Profil İstatistikleri", + "cleanup_profile": "Geçmişi Temizleme - Grafikleme ve Silme", + "assign_profile_phases_select": "Faz Aralıklarını Atayın", + "menu_back": "← Geri" + } + }, + "manage_profiles_empty": { + "title": "Profilleri Yönet", + "description": "Henüz profil oluşturulmadı.", + "menu_options": { + "create_profile": "Yeni Profil Oluştur", + "menu_back": "← Geri" + } + }, + "manage_phase_catalog": { + "title": "Aşama Kataloğunu Yönet", + "description": "Aşama kataloğu (bu cihaz için varsayılanlar + özel aşamalar):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Yeni Aşama Oluştur", + "phase_catalog_edit_select": "Aşamayı Düzenle", + "phase_catalog_delete": "Aşamayı Sil", + "menu_back": "← Geri" + } + }, + "phase_catalog_create": { + "title": "Özel Aşama Oluştur", + "description": "Özel bir aşama adı ve açıklaması oluşturun ve bunun hangi cihaz türüne uygulanacağını seçin.", + "data": { + "target_device_type": "Hedef Cihaz Türü", + "phase_name": "Aşama Adı", + "phase_description": "Aşama Açıklaması" + } + }, + "phase_catalog_edit_select": { + "title": "Özel Aşamayı Düzenle", + "description": "Düzenlemek için özel bir aşama seçin.", + "data": { + "phase_name": "Özel Aşama" + } + }, + "phase_catalog_edit": { + "title": "Özel Aşamayı Düzenle", + "description": "Düzenleme aşaması: {phase_name}", + "data": { + "phase_name": "Aşama Adı", + "phase_description": "Aşama Açıklaması" + } + }, + "phase_catalog_delete": { + "title": "Özel Aşamayı Sil", + "description": "Silinecek özel bir aşama seçin. Bu aşamayı kullanan atanan aralıklar kaldırılacaktır.", + "data": { + "phase_name": "Özel Aşama" + } + }, + "assign_profile_phases": { + "title": "Faz Aralıklarını Atayın", + "description": "Profil için aşama önizlemesi: **{profile_name}**\n\n{timeline_svg}\n\n**Mevcut aralıklar:**\n{current_ranges}\n\nAralıkları eklemek, düzenlemek, silmek veya kaydetmek için aşağıdaki işlemleri kullanın.", + "menu_options": { + "assign_profile_phases_add": "Faz Aralığı Ekle", + "assign_profile_phases_edit_select": "Faz Aralığını Düzenle", + "assign_profile_phases_delete": "Faz Aralığını Sil", + "phase_ranges_clear": "Tüm Aralıkları Temizle", + "assign_profile_phases_auto_detect": "Aşamaları Otomatik Algıla", + "phase_ranges_save": "Kaydet ve Geri Dön", + "menu_back": "← Geri" + } + }, + "assign_profile_phases_add": { + "title": "Faz Aralığı Ekle", + "description": "Mevcut taslağa bir aşama aralığı ekleyin.", + "data": { + "phase_name": "Faz", + "start_min": "Başlangıç ​​Dakikası", + "end_min": "Bitiş Dakikası" + } + }, + "assign_profile_phases_edit_select": { + "title": "Faz Aralığını Düzenle", + "description": "Düzenlemek için bir aralık seçin.", + "data": { + "range_index": "Faz Aralığı" + } + }, + "assign_profile_phases_edit": { + "title": "Faz Aralığını Düzenle", + "description": "Seçilen faz aralığını güncelleyin.", + "data": { + "phase_name": "Faz", + "start_min": "Başlangıç ​​Dakikası", + "end_min": "Bitiş Dakikası" + } + }, + "assign_profile_phases_delete": { + "title": "Faz Aralığını Sil", + "description": "Taslaktan kaldırılacak bir aralık seçin.", + "data": { + "range_index": "Faz Aralığı" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Otomatik Algılanan Aşamalar", + "description": "Profil: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} aşama otomatik olarak algılandı.** Aşağıdan bir işlem seçin.", + "data": { + "action": "Aksiyon" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Ad Algılanan Aşamalar", + "description": "Profil: **{profile_name}**\n\n**{detected_count} aşama algılandı:**\n{ranges_summary}\n\nHer aşamaya bir ad atayın." + }, + "assign_profile_phases_select": { + "title": "Faz Aralıklarını Atayın", + "description": "Faz aralıklarını yapılandırmak için bir profil seçin.", + "data": { + "profile": "Profil" + } + }, + "profile_stats": { + "title": "Profil İstatistikleri", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Yeni Profil Oluştur", + "description": "Manuel olarak veya geçmiş bir döngüden yeni bir profil oluşturun.\n\nProfil adı örnekleri: 'Hassas', 'Ağır Hizmet', 'Hızlı Yıkama'", + "data": { + "profile_name": "Profil Adı", + "reference_cycle": "Referans Döngüsü (isteğe bağlı)", + "manual_duration": "Manuel Süre (dakika)" + } + }, + "edit_profile": { + "title": "Profili Düzenle", + "description": "Yeniden adlandırmak için bir profil seçin", + "data": { + "profile": "Profil" + } + }, + "rename_profile": { + "title": "Profil Ayrıntılarını Düzenle", + "description": "Mevcut profil: {current_name}", + "data": { + "new_name": "Profil Adı", + "manual_duration": "Manuel Süre (dakika)" + } + }, + "delete_profile_select": { + "title": "Profili Sil", + "description": "Silinecek profili seçin", + "data": { + "profile": "Profil" + } + }, + "delete_profile_confirm": { + "title": "Profili Silmeyi Onayla", + "description": "⚠️ Bu, şu profili kalıcı olarak silecektir: {profile_name}", + "data": { + "unlabel_cycles": "Bu profili kullanarak döngülerden etiketi kaldırın" + } + }, + "auto_label_cycles": { + "title": "Eski Döngüleri Otomatik Etiketle", + "description": "Toplam {total_count} döngü bulundu. Profiller: {profiles}", + "data": { + "confidence_threshold": "Minimum Güven (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Etiketlemek için Döngüyü Seç", + "description": "✓ = tamamlandı, ⚠ = devam ettirildi, ✗ = kesintiye uğradı", + "data": { + "cycle_id": "Döngü" + } + }, + "select_cycle_to_delete": { + "title": "Döngüyü Sil", + "description": "⚠️ Bu, seçilen döngüyü kalıcı olarak silecektir", + "data": { + "cycle_id": "Silinecek Döngü" + } + }, + "label_cycle": { + "title": "Etiket Döngüsü", + "description": "{cycle_info}", + "data": { + "profile_name": "Profil", + "new_profile_name": "Yeni Profil Adı (oluşturuyorsa)" + } + }, + "post_process": { + "title": "İşlem Geçmişi (Birleştir/Böl)", + "description": "Parçalanmış işlemleri birleştirerek ve yanlış birleştirilmiş döngüleri bir zaman penceresi içinde bölerek döngü geçmişini temizleyin. Geriye dönüp bakılacak saat sayısını girin (hepsi için 999999 kullanın).", + "data": { + "time_range": "Yeniden İnceleme Aralığı (saat)", + "gap_seconds": "Boşluğu Birleştir/Böl (saniye)" + }, + "data_description": { + "time_range": "Birleştirilecek parçalanmış döngülerin taranması için gereken saat sayısı. Tüm geçmiş verileri işlemek için 999999'u kullanın.", + "gap_seconds": "Bölünmeye mi yoksa birleştirmeye mi karar verecek eşik. Bundan DAHA KISA boşluklar birleştirilir. Bundan daha uzun boşluklar bölünür. Yanlış birleştirilmiş bir döngüde bölünmeyi zorlamak için bunu düşürün." + } + }, + "cleanup_profile": { + "title": "Geçmişi Temizle - Profil Seç", + "description": "Görselleştirilecek bir profil seçin. Bu, aykırı değerlerin belirlenmesine yardımcı olmak için bu profilin tüm geçmiş döngülerini gösteren bir grafik oluşturacaktır.", + "data": { + "profile": "Profil" + } + }, + "cleanup_select": { + "title": "Geçmişi Temizleme - Grafikleme ve Silme", + "description": "![Grafik]({graph_url})\n\n**Görselleştiriliyor: {profile_name}**\n\nGrafik bireysel döngüleri renkli çizgilerle gösterir. Aykırı değerleri (gruptan uzaktaki çizgiler) belirleyin ve bunları silmek için aşağıdaki listede renklerini eşleştirin.\n\n**KALICI OLARAK silinecek döngüleri seçin:**", + "data": { + "cycles_to_delete": "Silinecek Döngüler (eşleşen renkleri kontrol edin)" + } + }, + "interactive_editor": { + "title": "Birleştir/Böl İnteraktif Düzenleyici", + "description": "Adet geçmişinizde gerçekleştirilecek manuel işlemi seçin.", + "menu_options": { + "editor_split": "Bir Döngüyü Böl (Boşlukları Bul)", + "editor_merge": "Döngüleri Birleştir (Parçaları birleştir)", + "editor_delete": "Döngüleri Sil", + "menu_back": "← Geri" + } + }, + "editor_select": { + "title": "Döngüleri Seçin", + "description": "İşlenecek döngüyü/döngüleri seçin.\n\n{info_text}", + "data": { + "selected_cycles": "Döngüler" + } + }, + "editor_configure": { + "title": "Yapılandır ve Uygula", + "description": "{preview_md}", + "data": { + "confirm_action": "Aksiyon", + "merged_profile": "Ortaya Çıkan Profil", + "new_profile_name": "Yeni Profil Adı", + "confirm_commit": "Evet, bu işlemi onaylıyorum", + "segment_0_profile": "Segment 1 Profili", + "segment_1_profile": "Segment 2 Profili", + "segment_2_profile": "Segment 3 Profili", + "segment_3_profile": "Segment 4 Profili", + "segment_4_profile": "Segment 5 Profili", + "segment_5_profile": "Segment 6 Profili", + "segment_6_profile": "Segment 7 Profili", + "segment_7_profile": "Segment 8 Profili", + "segment_8_profile": "Segment 9 Profili", + "segment_9_profile": "Segment 10 Profili" + } + }, + "editor_split_params": { + "title": "Bölünmüş Yapılandırma", + "description": "Bu döngüyü bölmek için hassasiyeti ayarlayın. Bundan daha uzun herhangi bir rölanti boşluğu bölünmeye neden olacaktır.", + "data": { + "split_mode": "Bölme yöntemi" + } + }, + "editor_split_auto_params": { + "title": "Otomatik Bölme Yapılandırması", + "description": "Bu döngünün bölünmesi için hassasiyeti ayarlayın. Bundan daha uzun herhangi bir boşta kalma aralığı bir bölünmeye neden olur.", + "data": { + "min_gap_seconds": "Bölme Boşluğu Eşiği (saniye)" + } + }, + "editor_split_manual_params": { + "title": "Manuel Bölme Zaman Damgaları", + "description": "{preview_md}Döngü penceresi: **{cycle_start_wallclock} → {cycle_end_wallclock}** tarihinde {cycle_date}.\n\nBir veya daha fazla bölme zaman damgası (`HH:MM` veya `HH:MM:SS`) girin, her satıra bir tane. Döngü her zaman damgasında kesilir — N zaman damgası N+1 segment üretir.", + "data": { + "split_timestamps": "Bölme Zaman Damgaları" + } + }, + "reprocess_history": { + "title": "Bakım: Verileri Yeniden İşleyin ve Optimize Edin", + "description": "Geçmiş verilerin derinlemesine temizliğini gerçekleştirir:\n1. Sıfır güç okumalarını düzeltir (sessizlik).\n2. Döngü zamanlamasını/süresini düzeltir.\n3. Tüm istatistiksel modelleri (zarflar) yeniden hesaplar.\n\n**Veri sorunlarını düzeltmek veya yeni algoritmalar uygulamak için bunu çalıştırın.**" + }, + "wipe_history": { + "title": "Geçmişi Sil (Yalnızca Test)", + "description": "⚠️ Bu, bu cihaz için kayıtlı TÜM döngüleri ve profilleri kalıcı olarak silecektir. Bu geri alınamaz!" + }, + "export_import": { + "title": "JSON'u Dışa/İçe Aktarma", + "description": "Bu cihaz için döngüleri, profilleri VE ayarları kopyalayın/yapıştırın. Aşağıya aktarın veya içe aktarmak için JSON'u yapıştırın.", + "data": { + "mode": "Aksiyon", + "json_payload": "Yapılandırmayı Tamamla JSON" + }, + "data_description": { + "mode": "Geçerli verileri ve ayarları kopyalamak için Dışa Aktar'ı veya başka bir WashData cihazından dışa aktarılan verileri yapıştırmak için İçe Aktar'ı seçin.", + "json_payload": "Dışa Aktarma için: bu JSON'u kopyalayın (döngüleri, profilleri ve tüm ince ayarlı ayarları içerir). İçe Aktarma için: WashData'dan dışa aktarılan JSON'u yapıştırın." + } + }, + "record_cycle": { + "title": "Kayıt Döngüsü", + "description": "Müdahale olmadan temiz bir profil oluşturmak için bir döngüyü manuel olarak kaydedin.\n\nDurum: **{status}**\nSüre: {duration}sn\nÖrnekler: {samples}", + "menu_options": { + "record_refresh": "Durumu Yenile", + "record_stop": "Kaydı Durdur (Kaydet ve İşle)", + "record_start": "Yeni Kaydı Başlat", + "record_process": "Son Kaydı İşle (Kırp ve Kaydet)", + "record_discard": "Son Kaydı Sil", + "menu_back": "← Geri" + } + }, + "record_process": { + "title": "Süreç Kaydı", + "description": "![Grafik]({graph_url})\n\n**Grafik ham kaydı gösterir.** Mavi = Tut, Kırmızı = Önerilen Kırpma.\n*Grafik statiktir ve form değişiklikleriyle güncellenmez.*\n\nKayıt durduruldu. Kırpmalar, tespit edilen örnekleme hızına (~{sampling_rate}s) göre hizalanır.\n\nHam Süre: {duration}s\nÖrnekler: {samples}", + "data": { + "head_trim": "Kesim Başlangıcı (saniye)", + "tail_trim": "Kırpma Sonu (saniye)", + "save_mode": "Hedefi Kaydet", + "profile_name": "Profil Adı" + } + }, + "learning_feedbacks": { + "title": "Öğrenme Geri Bildirimleri", + "description": "Bekleyen geri bildirim: {count}\n\n{pending_table}\n\nİşlenecek bir döngü inceleme isteği seçin.", + "menu_options": { + "learning_feedbacks_pick": "Bekleyen bir geri bildirimi incele", + "learning_feedbacks_dismiss_all": "Bekleyen tüm geri bildirimleri kapat", + "menu_back": "← Geri" + } + }, + "learning_feedbacks_pick": { + "title": "İncelenecek geri bildirimi seçin", + "description": "Açılacak bir döngü inceleme isteği seçin.", + "data": { + "selected_feedback": "İncelenecek Döngüyü Seçin" + } + }, + "learning_feedbacks_empty": { + "title": "Öğrenme Geri Bildirimleri", + "description": "Bekleyen geri bildirim isteği bulunamadı.", + "menu_options": { + "menu_back": "← Geri" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Bekleyen Tüm Geri Bildirimleri Reddet", + "description": "**{count}** bekleyen geri bildirim isteğini reddetmek üzeresiniz. Reddedilen döngüler geçmişinizde kalır ancak yeni öğrenme sinyaline katkıda bulunmaz. Bu işlem geri alınamaz.\n\n✅ Tümünü reddetmek için aşağıdaki kutuyu işaretleyin ve **Gönder**'i tıklayın.\n❌ İşaretlemeden bırakın ve iptal edip geri dönmek için **Gönder**'i tıklayın.", + "data": { + "confirm_dismiss_all": "Onayla: bekleyen tüm geri bildirim isteklerini kapat" + } + }, + "resolve_feedback": { + "title": "Geri Bildirimi Çözümleyin", + "description": "{comparison_img}{candidates_table}\n**Algılanan Profil**: {detected_profile} (%{confidence_pct})\n**Tahmini Süre**: {est_duration_min} dk.\n**Gerçek Süre**: {act_duration_min} dk.\n\n__Aşağıdan bir işlem seçin:__", + "data": { + "action": "Ne yapmak istersin?", + "corrected_profile": "Doğru Program (düzeltiliyorsa)", + "corrected_duration": "Saniye cinsinden Doğru Süre (düzeltiliyorsa)" + } + }, + "trim_cycle_select": { + "title": "Kırpma Döngüsü - Döngüyü Seçin", + "description": "Kırpılacak bir döngü seçin. ✓ = tamamlandı, ⚠ = devam ettirildi, ✗ = kesintiye uğradı", + "data": { + "cycle_id": "Döngü" + } + }, + "trim_cycle": { + "title": "Kırpma Döngüsü", + "description": "Geçerli pencere: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Aksiyon" + } + }, + "trim_cycle_start": { + "title": "Kesim Başlangıcını Ayarla", + "description": "Döngü penceresi: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nMevcut başlangıç: **{current_wallclock}** (döngü başlangıcından +{current_offset_min} dakika sonra)\n\nAşağıdaki saat seçiciyi kullanarak yeni bir başlangıç zamanı seçin.", + "data": { + "trim_start_time": "Yeni başlangıç ​​zamanı", + "trim_start_min": "Yeni başlangıç ​​(döngü başlangıcından dakikalar sonra)" + } + }, + "trim_cycle_end": { + "title": "Kırpma Sonunu Ayarla", + "description": "Döngü penceresi: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nMevcut bitiş: **{current_wallclock}** (döngü başlangıcından +{current_offset_min} dakika sonra)\n\nAşağıdaki saat seçiciyi kullanarak yeni bir bitiş zamanı seçin.", + "data": { + "trim_end_time": "Yeni bitiş zamanı", + "trim_end_min": "Yeni son (döngü başlangıcından dakikalar sonra)" + } + } + }, + "error": { + "import_failed": "İçe aktarma başarısız oldu. Lütfen geçerli bir WashData dışa aktarma JSON'u yapıştırın.", + "select_exactly_one": "Bu işlem için tam olarak bir döngü seçin.", + "select_at_least_one": "Bu işlem için en az bir döngü seçin.", + "select_at_least_two": "Bu işlem için en az iki döngü seçin.", + "profile_exists": "Bu ada sahip bir profil zaten mevcut", + "rename_failed": "Profil yeniden adlandırılamadı. Profil mevcut olmayabilir veya yeni ad zaten alınmış olabilir.", + "assignment_failed": "Profil döngüye atanamadı", + "duplicate_phase": "Bu ada sahip bir aşama zaten mevcut.", + "phase_not_found": "Seçilen aşama bulunamadı.", + "invalid_phase_name": "Aşama adı boş olamaz.", + "phase_name_too_long": "Aşama adı çok uzun.", + "invalid_phase_range": "Faz aralığı geçersiz. Bitiş, başlangıçtan büyük olmalıdır.", + "invalid_phase_timestamp": "Zaman damgası geçersiz. YYYY-AA-GG SS:DD veya SS:DD biçimini kullanın.", + "overlapping_phase_ranges": "Faz aralıkları çakışıyor. Aralıkları ayarlayıp tekrar deneyin.", + "incomplete_phase_row": "Her aşama satırı, seçilen mod için bir aşama artı tam başlangıç/bitiş değerlerini içermelidir.", + "timestamp_mode_cycle_required": "Zaman damgası modunu kullanırken lütfen bir kaynak döngüsü seçin.", + "no_phase_ranges": "Bu eylem için henüz herhangi bir faz aralığı mevcut değil.", + "feedback_notification_title": "WashData: Döngüyü Doğrula ({device})", + "feedback_notification_message": "**Cihaz**: {device}\n**Program**: {program} (%{confidence} güven)\n**Zaman**: {time}\n\nWashData'nın tespit edilen bu döngüyü doğrulamak için yardımınıza ihtiyacı var.\n\nBu sonucu onaylamak veya düzeltmek için lütfen **Ayarlar > Cihazlar ve Hizmetler > WashData > Yapılandır > Öğrenme Geri Bildirimleri** seçeneğine gidin.", + "suggestions_ready_notification_title": "WashData: Önerilen Ayarlar Hazır ({device})", + "suggestions_ready_notification_message": "**Önerilen Ayarlar** sensörü artık **{count}** uygulanabilir öneriler raporluyor.\n\nBunları incelemek ve uygulamak için: **Ayarlar > Cihazlar ve Hizmetler > WashData > Yapılandır > Gelişmiş Ayarlar > Önerilen Değerleri Uygula**.\n\nÖneriler isteğe bağlıdır ve kaydetmeden önce incelenmek üzere gösterilir.", + "trim_range_invalid": "Başlangıç ​​bitişten önce olmalıdır. Lütfen trim noktalarınızı ayarlayın.", + "trim_failed": "Kırpma başarısız oldu. Döngü artık mevcut olmayabilir veya pencere boş olabilir.", + "invalid_split_timestamp": "Zaman damgası geçersiz veya döngü penceresinin dışında. Döngü penceresi içinde HH:MM veya HH:MM:SS kullanın.", + "no_split_segments_found": "Bu zaman damgalarından geçerli segmentler üretilemedi. Her zaman damgasının döngü penceresi içinde olduğundan ve segmentlerin en az 1 dakika uzunluğunda olduğundan emin olun.", + "auto_tune_suggestion": "{device_type} {device_title} hayalet döngüleri algıladı. Önerilen min_güç değişikliği: {current_min}W -> {new_min}W (otomatik olarak uygulanmaz).", + "auto_tune_title": "WashData Otomatik Ayarlama", + "auto_tune_fallback": "{device_type} {device_title} hayalet döngüleri algıladı. Önerilen min_güç değişikliği: {current_min}W -> {new_min}W (otomatik olarak uygulanmaz)." + }, + "abort": { + "no_cycles_found": "Döngü bulunamadı", + "no_split_segments_found": "Seçilen döngüde bölünebilir segment bulunamadı.", + "cycle_not_found": "Seçilen döngü yüklenemedi.", + "no_power_data": "Seçilen döngü için güç izleme verileri mevcut değil.", + "no_profiles_found": "Hiçbir profil bulunamadı. Önce bir profil oluşturun.", + "no_profiles_for_matching": "Eşleştirilecek profil yok. Önce profiller oluşturun.", + "no_unlabeled_cycles": "Tüm döngüler zaten etiketlenmiştir", + "no_suggestions": "Henüz önerilen değer yok. Birkaç döngü çalıştırın ve tekrar deneyin.", + "no_predictions": "Tahmin yok", + "no_custom_phases": "Özel aşama bulunamadı.", + "reprocess_success": "{count} döngü başarıyla yeniden işlendi.", + "debug_data_cleared": "{count} döngüdeki hata ayıklama verileri başarıyla temizlendi." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Döngü Başlatma", + "cycle_finish": "Döngü Sonu", + "cycle_live": "Canlı İlerleme" + } + }, + "common_text": { + "options": { + "unlabeled": "(Etiketlenmemiş)", + "create_new_profile": "Yeni Profil Oluştur", + "remove_label": "Etiketi Kaldır", + "no_reference_cycle": "(Referans döngüsü yok)", + "all_device_types": "Tüm Cihaz Tipleri", + "current_suffix": "(akım)", + "editor_select_info": "Bölmek için 1 döngüyü veya birleştirmek için 2+ döngüyü seçin.", + "editor_select_info_split": "Bölmek için 1 döngü seçin.", + "editor_select_info_merge": "Birleştirmek için 2+ döngü seçin.", + "editor_select_info_delete": "Silmek için 1+ döngü seçin.", + "no_cycles_recorded": "Henüz kayıtlı döngü yok.", + "profile_summary_line": "- **{name}**: {count} döngü, {avg}m ort.", + "no_profiles_created": "Henüz profil oluşturulmadı.", + "phase_preview": "Aşama Önizleme", + "phase_preview_no_curve": "Ortalama profil eğrisi henüz mevcut değil. Bu profil için daha fazla döngü çalıştırın/etiketleyin.", + "average_power_curve": "Ortalama Güç Eğrisi", + "unit_min": "dk.", + "profile_option_fmt": "{name} ({count} döngü, ~{duration}m ort)", + "profile_option_short_fmt": "{name} ({count} döngü, ~{duration}dk)", + "deleted_cycles_from_profile": "{profile} öğesinden {count} döngü silindi.", + "not_enough_data_graph": "Grafik oluşturmak için yeterli veri yok.", + "no_profiles_found": "Hiçbir profil bulunamadı.", + "cycle_info_fmt": "Döngü: {start}, {duration}dk, Akım: {label}", + "top_candidates_header": "### En İyi Adaylar", + "tbl_profile": "Profil", + "tbl_confidence": "Kendinden emin", + "tbl_correlation": "Korelasyon", + "tbl_duration_match": "Süre Eşleşmesi", + "detected_profile": "Algılanan Profil", + "estimated_duration": "Tahmini Süre", + "actual_duration": "Fiili Süre", + "choose_action": "__Aşağıdan bir işlem seçin:__", + "tbl_count": "Saymak", + "tbl_avg": "Ort.", + "tbl_min": "Min.", + "tbl_max": "Maksimum", + "tbl_energy_avg": "Enerji (Ort.)", + "tbl_energy_total": "Enerji (Toplam)", + "tbl_consistency": "Meydana gelmek.", + "tbl_last_run": "Son Çalıştırma", + "graph_legend_title": "Grafik Açıklaması", + "graph_legend_body": "Mavi bant gözlemlenen minimum ve maksimum güç çekiş aralığını temsil eder. Çizgi ortalama güç eğrisini gösterir.", + "recording_preview": "Kayıt Önizlemesi", + "trim_start": "Kırpma Başlangıcı", + "trim_end": "Kırpma Sonu", + "split_preview_title": "Bölünmüş Önizleme", + "split_preview_found_fmt": "{count} segment bulundu.", + "split_preview_confirm_fmt": "Bu döngüyü {count} ayrı döngüye bölmek için Onayla'yı tıklayın.", + "merge_preview_title": "Önizlemeyi Birleştir", + "merge_preview_joining_fmt": "{count} döngüye katılıyoruz. Boşluklar 0W okumalarıyla doldurulacaktır.", + "editor_delete_preview_title": "Silme Önizlemesi", + "editor_delete_preview_intro": "Seçilen döngüler kalıcı olarak silinecek:", + "editor_delete_preview_confirm": "Bu döngü kayıtlarını kalıcı olarak silmek için Onayla'ya tıklayın.", + "no_power_preview": "*Önizleme için güç verisi yok.*", + "profile_comparison": "Profil Karşılaştırması", + "actual_cycle_label": "Bu döngü (gerçek)", + "storage_usage_header": "Depolama Kullanımı", + "storage_file_size": "Dosya Boyutu", + "storage_cycles": "Döngüler", + "storage_profiles": "Profiller", + "storage_debug_traces": "Hata Ayıklama İzleri", + "overview_suffix": "Genel Bakış", + "phase_preview_for_profile": "Profil için aşama önizlemesi", + "current_ranges_header": "Mevcut aralıklar", + "assign_phases_help": "Aralıkları eklemek, düzenlemek, silmek veya kaydetmek için aşağıdaki işlemleri kullanın.", + "profile_summary_header": "Profil Özeti", + "recent_cycles_header": "Son döngüler", + "trim_cycle_preview_title": "Döngü Kırpma Önizlemesi", + "trim_cycle_preview_no_data": "Bu döngü için güç verisi mevcut değil.", + "trim_cycle_preview_kept_suffix": "tutulmuş", + "table_program": "programı", + "table_when": "Ne Zaman", + "table_length": "Süre", + "table_match": "Eşleşme", + "table_profile": "Profil", + "table_cycles": "Döngüler", + "table_avg_length": "Ort. Süre", + "table_last_run": "Son Çalıştırma", + "table_avg_energy": "Ort. Enerji", + "table_detected_program": "Algılanan Program", + "table_confidence": "Kendinden emin", + "table_reported": "Bildirildi", + "phase_builtin_suffix": "(Yerleşik)", + "phase_none_available": "Hiçbir aşama mevcut değil.", + "settings_deprecation_warning": "⚠️ **Kullanımdan kaldırılan cihaz türü:** {device_type}'nin gelecekteki bir sürümde kaldırılması planlanıyor. WashData'nın eşleşen işlem hattı bu cihaz sınıfı için güvenilir sonuçlar üretmiyor. Entegrasyonunuz kullanımdan kaldırma süresi boyunca çalışmaya devam eder; Bu uyarıyı susturmak için aşağıdaki **Cihaz Tipi** seçeneğini desteklenen türlerden birine (Çamaşır Makinesi, Kurutucu, Çamaşır Makinesi-Kurutucu Kombinesi, Bulaşık Makinesi, Hava Fritözü, Ekmek Makinesi veya Pompa) veya cihazınız desteklenen türlerden herhangi biriyle eşleşmiyorsa **Diğer (Gelişmiş)** olarak değiştirin. **Diğer (Gelişmiş)** herhangi bir belirli uygulama için ayarlanmayan genel varsayılanları kasıtlı olarak gönderir; bu nedenle eşikleri, zaman aşımlarını ve eşleşen parametreleri kendiniz yapılandırmanız gerekir; Geçiş yaptığınızda mevcut tüm ayarlarınız korunur.", + "phase_other_device_types": "Diğer cihaz türleri:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Bir Döngüyü Böl (Boşlukları Bul)", + "merge": "Döngüleri Birleştir (Parçaları birleştir)", + "delete": "Döngüleri Sil" + } + }, + "split_mode": { + "options": { + "auto": "Boşta kalma aralıklarını otomatik algıla", + "manual": "Manuel zaman damgaları" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Bakım: Verileri Yeniden İşleyin ve Optimize Edin", + "clear_debug_data": "Hata Ayıklama Verilerini Temizle (Yer açın)", + "wipe_history": "Bu cihaza ait TÜM verileri silin (geri döndürülemez)", + "export_import": "JSON'u ayarlarla dışa/içe aktar (kopyala/yapıştır)" + } + }, + "export_import_mode": { + "options": { + "export": "Tüm Verileri Dışa Aktar", + "import": "Verileri İçe Aktarma/Birleştirme" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Eski Döngüleri Otomatik Etiketle", + "select_cycle_to_label": "Etikete Özel Döngü", + "select_cycle_to_delete": "Döngüyü Sil", + "interactive_editor": "Birleştir/Böl İnteraktif Düzenleyici", + "trim_cycle": "Döngü Verilerini Kırp" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Yeni Profil Oluştur", + "edit_profile": "Profili Düzenle/Yeniden Adlandır", + "delete_profile": "Profili Sil", + "profile_stats": "Profil İstatistikleri", + "cleanup_profile": "Geçmişi Temizleme - Grafikleme ve Silme", + "assign_phases": "Faz Aralıklarını Atayın" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Yeni Aşama Oluştur", + "edit_custom_phase": "Aşamayı Düzenle", + "delete_custom_phase": "Aşamayı Sil" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Faz Aralığı Ekle", + "edit_range": "Faz Aralığını Düzenle", + "delete_range": "Faz Aralığını Sil", + "clear_ranges": "Tüm Aralıkları Temizle", + "auto_detect_ranges": "Aşamaları Otomatik Algıla", + "save_ranges": "Kaydet ve Geri Dön" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Durumu Yenile", + "stop_recording": "Kaydı Durdur (Kaydet ve İşle)", + "start_recording": "Yeni Kaydı Başlat", + "process_recording": "Son Kaydı İşle (Kırp ve Kaydet)", + "discard_recording": "Son Kaydı Sil" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Yeni Profil Oluştur", + "existing_profile": "Mevcut Profile Ekle", + "discard": "Kaydı Sil" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Onayla - Doğru Tespit", + "correct": "Doğru - Program/Süreyi Seçin", + "ignore": "Yoksay - Yanlış Pozitif/Gürültülü Döngü", + "delete": "Sil - Geçmişten Kaldır" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Aşamaları adlandırın ve uygulayın", + "cancel": "Değişiklik yapmadan geri dön" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Başlangıç ​​Noktasını Ayarla", + "set_end": "Bitiş Noktasını Ayarla", + "reset": "Tam Süreye Sıfırla", + "apply": "Kırpmayı Uygula", + "cancel": "İptal et" + } + }, + "device_type": { + "options": { + "washing_machine": "Çamaşır makinesi", + "dryer": "Kurutma makinesi", + "washer_dryer": "Yıkama-Kurutma Makinesi Kombinasyonu", + "dishwasher": "Bulaşık makinesi", + "coffee_machine": "Kahve Makinesi (kullanımdan kaldırıldı)", + "ev": "Elektrikli Araç (kullanımdan kaldırıldı)", + "air_fryer": "Hava Fritözü", + "heat_pump": "Isı Pompası (kullanımdan kaldırıldı)", + "bread_maker": "Ekmek Makinesi", + "pump": "Pompa / Karter Pompası", + "oven": "Fırın (kullanımdan kaldırıldı)", + "other": "Diğer (Gelişmiş)" + } + } + }, + "services": { + "label_cycle": { + "name": "Etiket Döngüsü", + "description": "Mevcut bir profili geçmiş bir döngüye atayın veya etiketi kaldırın.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Etiketlenecek çamaşır makinesi cihazı." + }, + "cycle_id": { + "name": "Döngü Kimliği", + "description": "Etiketlenecek döngünün kimliği." + }, + "profile_name": { + "name": "Profil Adı", + "description": "Mevcut bir profilin adı (Profilleri Yönet menüsünde profiller oluşturun). Etiketi kaldırmak için boş bırakın." + } + } + }, + "create_profile": { + "name": "Profil Oluştur", + "description": "Yeni bir profil oluşturun (bağımsız veya bir referans döngüsüne dayalı).", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Çamaşır makinesi cihazı." + }, + "profile_name": { + "name": "Profil Adı", + "description": "Yeni profilin adı (ör. 'Ağır Hizmet', 'Hassas')." + }, + "reference_cycle_id": { + "name": "Referans Döngü Kimliği", + "description": "Bu profili temel alacak isteğe bağlı döngü kimliği." + } + } + }, + "delete_profile": { + "name": "Profili Sil", + "description": "Bir profili silin ve isteğe bağlı olarak onu kullanarak döngülerin etiketini kaldırın.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Çamaşır makinesi cihazı." + }, + "profile_name": { + "name": "Profil Adı", + "description": "Silinecek profil." + }, + "unlabel_cycles": { + "name": "Döngülerin Etiketini Kaldırma", + "description": "Bu profili kullanarak profil etiketini döngülerden kaldırın." + } + } + }, + "auto_label_cycles": { + "name": "Eski Döngüleri Otomatik Etiketle", + "description": "Profil eşleştirmeyi kullanarak etiketlenmemiş döngüleri geriye dönük olarak etiketleyin.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Çamaşır makinesi cihazı." + }, + "confidence_threshold": { + "name": "Güven Eşiği", + "description": "Etiketleri uygulamak için minimum eşleşme güvenirliği (0,50-0,95)." + } + } + }, + "export_config": { + "name": "Yapılandırmayı Dışa Aktar", + "description": "Bu cihazın profillerini, döngülerini ve ayarlarını bir JSON dosyasına (cihaz başına) aktarın.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Dışa aktarılacak çamaşır makinesi cihazı." + }, + "path": { + "name": "Yol", + "description": "Yazılacak isteğe bağlı mutlak dosya yolu (varsayılan olarak /config/ha_washdata_export_{entry}.json şeklindedir)." + } + } + }, + "import_config": { + "name": "Yapılandırmayı İçe Aktar", + "description": "Bu cihazın profillerini, döngülerini ve ayarlarını bir JSON dışa aktarma dosyasından içe aktarın (ayarların üzerine yazılabilir).", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "İçe aktarılacak çamaşır makinesi cihazı." + }, + "path": { + "name": "Yol", + "description": "İçe aktarılacak dışa aktarma JSON dosyasının mutlak yolu." + } + } + }, + "submit_cycle_feedback": { + "name": "Döngü Geri Bildirimi Gönder", + "description": "Tamamlanan bir döngüden sonra otomatik olarak algılanan bir programı onaylayın veya düzeltin. \"Entry_id\" (gelişmiş) veya \"device_id\" (önerilen) bilgilerini sağlayın.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "WashData cihazı (entry_id yerine önerilir)." + }, + "entry_id": { + "name": "Giriş Kimliği", + "description": "Cihazın yapılandırma girişi kimliği (cihaz_id'ye alternatif)." + }, + "cycle_id": { + "name": "Döngü Kimliği", + "description": "Geri bildirim bildiriminde/günlüklerinde gösterilen döngü kimliği." + }, + "user_confirmed": { + "name": "Algılanan Programı Onayla", + "description": "Algılanan program doğruysa true'yu ayarlayın." + }, + "corrected_profile": { + "name": "Düzeltilmiş Profil", + "description": "Onaylanmazsa doğru profil/program adını girin." + }, + "corrected_duration": { + "name": "Düzeltilmiş Süre (saniye)", + "description": "İsteğe bağlı olarak saniye cinsinden düzeltilmiş süre." + }, + "notes": { + "name": "Notlar", + "description": "Bu döngüyle ilgili isteğe bağlı notlar." + } + } + }, + "record_start": { + "name": "Kayıt Döngüsü Başlatma", + "description": "Temiz bir döngüyü manuel olarak kaydetmeye başlayın (tüm eşleşen mantığı atlar).", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Kaydetmek için çamaşır makinesi cihazı." + } + } + }, + "record_stop": { + "name": "Kayıt Döngüsünü Durdurma", + "description": "Manuel kaydı durdurun.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "Çamaşır makinesi cihazının kaydı durdurması." + } + } + }, + "trim_cycle": { + "name": "Kırpma Döngüsü", + "description": "Geçmiş bir döngünün güç verilerini belirli bir zaman penceresine göre kesin.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "WashData cihazı." + }, + "cycle_id": { + "name": "Döngü Kimliği", + "description": "Kırpılacak döngünün kimliği." + }, + "trim_start_s": { + "name": "Kesim Başlangıcı (saniye)", + "description": "Bu ofsetteki verileri saniyeler içinde saklayın. Varsayılan 0." + }, + "trim_end_s": { + "name": "Kırpma Sonu (saniye)", + "description": "Verileri saniyeler içinde bu ofset seviyesinde tutun. Varsayılan = tam süre." + } + } + }, + "pause_cycle": { + "name": "Döngüyü Duraklat", + "description": "Bir WashData cihazının aktif döngüsünü duraklatın.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "WashData cihazının duraklatılması." + } + } + }, + "resume_cycle": { + "name": "Döngüyü Devam Ettir", + "description": "WashData cihazı için duraklatılmış bir döngüyü devam ettirin.", + "fields": { + "device_id": { + "name": "Cihaz", + "description": "WashData cihazı devam ettirilecek." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Koşma" + }, + "match_ambiguity": { + "name": "Eşleşme Belirsizliği" + } + }, + "sensor": { + "washer_state": { + "name": "Durum", + "state": { + "off": "Kapalı", + "idle": "Boşta", + "starting": "Başlangıç", + "running": "Koşma", + "paused": "Duraklatıldı", + "user_paused": "Kullanıcı tarafından duraklatıldı", + "ending": "Bitiş", + "finished": "Bitti", + "anti_wrinkle": "Kırışıklık Karşıtı", + "delay_wait": "Başlamak için Bekleniyor", + "interrupted": "Kesintiye uğradı", + "force_stopped": "Zorla Durduruldu", + "rinse": "Durulmak", + "unknown": "Bilinmiyor", + "clean": "Temiz" + } + }, + "washer_program": { + "name": "programı" + }, + "time_remaining": { + "name": "Kalan Süre" + }, + "total_duration": { + "name": "Toplam Süre" + }, + "cycle_progress": { + "name": "İlerlemek" + }, + "current_power": { + "name": "Mevcut Güç" + }, + "elapsed_time": { + "name": "Geçen Süre" + }, + "debug_info": { + "name": "Hata Ayıklama Bilgisi" + }, + "match_confidence": { + "name": "Maç Güveni" + }, + "top_candidates": { + "name": "En İyi Adaylar", + "state": { + "none": "Hiçbiri" + } + }, + "current_phase": { + "name": "Mevcut Aşama" + }, + "wash_phase": { + "name": "Faz" + }, + "profile_cycle_count": { + "name": "Profil {profile_name} Sayısı", + "unit_of_measurement": "döngüler" + }, + "suggestions": { + "name": "Önerilen Ayarlar" + }, + "pump_runs_today": { + "name": "Pompa Çalışmaları (Son 24 saat)" + }, + "cycle_count": { + "name": "Döngü Sayısı" + } + }, + "select": { + "program_select": { + "name": "Döngü Programı", + "state": { + "auto_detect": "Otomatik Algılama" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Sonlandırma Döngüsünü Zorla" + }, + "pause_cycle": { + "name": "Döngüyü Duraklat" + }, + "resume_cycle": { + "name": "Döngüyü Devam Ettir" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Geçerli bir cihaz_kimliği gerekli." + }, + "cycle_id_required": { + "message": "Geçerli bircycle_id gereklidir." + }, + "profile_name_required": { + "message": "Geçerli bir profil_adı gerekli." + }, + "device_not_found": { + "message": "Cihaz bulunamadı." + }, + "no_config_entry": { + "message": "Cihaz için yapılandırma girişi bulunamadı." + }, + "integration_not_loaded": { + "message": "Bu cihaz için entegrasyon yüklenmedi." + }, + "cycle_not_found_or_no_power": { + "message": "Döngü bulunamadı veya güç verisi yok." + }, + "trim_failed_empty_window": { + "message": "Kesim başarısız oldu - döngü bulunamadı, güç verisi yok veya ortaya çıkan pencere boş." + }, + "trim_invalid_range": { + "message": "trim_end_s, trim_start_s'den büyük olmalıdır." + } + } +} diff --git a/custom_components/ha_washdata/translations/uk.json b/custom_components/ha_washdata/translations/uk.json new file mode 100644 index 0000000..6579711 --- /dev/null +++ b/custom_components/ha_washdata/translations/uk.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Налаштування WashData", + "description": "Налаштуйте пральну машину чи інший прилад.\n\nПотрібен датчик потужності.\n\n**Далі вас запитають, чи хочете ви створити свій перший профіль.**", + "data": { + "name": "Назва пристрою", + "device_type": "Тип пристрою", + "power_sensor": "Датчик потужності", + "min_power": "Мінімальний поріг потужності (Вт)" + }, + "data_description": { + "name": "Зрозуміла назва цього пристрою (наприклад, «Пральна машина», «Посудомийна машина»).", + "device_type": "Що це за тип приладу? Допомагає адаптувати виявлення та маркування.", + "power_sensor": "Датчик, який повідомляє споживання електроенергії в реальному часі (у ватах) від вашої розумної розетки.", + "min_power": "Показники потужності вище цього порогу (у ватах) вказують на те, що прилад працює. Почніть з 2 Вт для більшості пристроїв." + } + }, + "first_profile": { + "title": "Створити перший профіль", + "description": "**Цикл** – це повний запуск вашого приладу (наприклад, завантаження білизни). **Профіль** групує ці цикли за типом (наприклад, «Бавовна», «Швидке прання»). Створення профілю тепер допомагає негайно оцінити тривалість інтеграції.\n\nВи можете вручну вибрати цей профіль в елементах керування пристроєм, якщо він не визначається автоматично.\n\n**Залиште поле «Назва профілю» порожнім, щоб пропустити цей крок.**", + "data": { + "profile_name": "Ім'я профілю (необов'язково)", + "manual_duration": "Орієнтовна тривалість (хвилини)" + } + } + }, + "error": { + "cannot_connect": "Не вдалося підключитися", + "invalid_auth": "Недійсна автентифікація", + "unknown": "Несподівана помилка" + }, + "abort": { + "already_configured": "Пристрій уже налаштовано" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Відгуки про навчання", + "settings": "Налаштування", + "notifications": "Сповіщення", + "manage_cycles": "Керування циклами", + "manage_profiles": "Керування профілями", + "manage_phase_catalog": "Керування каталогом фаз", + "record_cycle": "Запис циклу (ручний)", + "diagnostics": "Діагностика та технічне обслуговування" + } + }, + "apply_suggestions": { + "title": "Копіювання запропонованих значень", + "description": "Це скопіює поточні запропоновані значення у ваші збережені налаштування. Це одноразова дія, яка не вмикає автоматичне перезаписування.\n\nРекомендовані значення для копіювання:\n{suggested}", + "data": { + "confirm": "Підтвердити копіювання" + } + }, + "apply_suggestions_confirm": { + "title": "Перегляд запропонованих змін", + "description": "WashData знайшло **{pending_count}** запропоновані значення для застосування.\n\nЗміни:\n{changes}\n\n✅ Поставте прапорець нижче та натисніть **Надіслати**, щоб негайно застосувати та зберегти ці зміни.\n❌ Не встановлюйте прапорець і натисніть **Надіслати**, щоб скасувати та повернутися назад.", + "data": { + "confirm_apply_suggestions": "Застосуйте та збережіть ці зміни" + } + }, + "settings": { + "title": "Налаштування", + "description": "{deprecation_warning}Налаштуйте пороги виявлення, навчання та вдосконалену поведінку. Значення за замовчуванням добре працюють для більшості налаштувань.\n\nНаразі доступні рекомендовані налаштування: {suggestions_count}.\nЯкщо це значення більше за 0, відкрийте розширені налаштування та скористайтеся «Застосувати запропоновані значення», щоб переглянути рекомендації.", + "data": { + "apply_suggestions": "Застосувати запропоновані значення", + "device_type": "Тип пристрою", + "power_sensor": "Датчик потужності", + "min_power": "Мінімальна потужність (Вт)", + "off_delay": "Затримка завершення циклу (с)", + "notify_actions": "Дії сповіщень", + "notify_people": "Відкладіть доставку, поки ці люди не повернуться вдома", + "notify_only_when_home": "Відкласти сповіщення, поки вибрана особа не буде вдома", + "notify_fire_events": "Активувати події автоматизації", + "notify_start_services": "Початок циклу - цілі сповіщень", + "notify_finish_services": "Завершення циклу - цілі сповіщень", + "notify_live_services": "Прогрес у реальному часі - цілі сповіщень", + "notify_before_end_minutes": "Сповіщення перед завершенням (за хвилини до завершення)", + "notify_title": "Заголовок сповіщення", + "notify_icon": "Значок сповіщення", + "notify_start_message": "Формат повідомлення про початок", + "notify_finish_message": "Формат повідомлення про завершення", + "notify_pre_complete_message": "Формат повідомлення перед завершенням", + "show_advanced": "Змінити розширені налаштування (наступний крок)" + }, + "data_description": { + "apply_suggestions": "Поставте цей прапорець, щоб скопіювати значення, запропоновані алгоритмом навчання, у форму. Перегляньте перед збереженням.\n\nРекомендовано (з вивчення):\n- min_power: {suggested_min_power} Вт\n- off_delay: {suggested_off_delay} с\n- watchdog_interval: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} с\n- profile_match_interval: {suggested_profile_match_interval} с\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} Вт\n- stop_threshold_w: {suggested_stop_threshold_w} Вт\n- end_energy_threshold: {suggested_end_energy_threshold} Вт·год\n- running_dead_zone: {suggested_running_dead_zone} с\n{suggested_reason}", + "device_type": "Виберіть тип приладу (пральна машина, сушильна машина, посудомийна машина, кавова машина, EV). Цей тег зберігається з кожним циклом для кращої організації.", + "power_sensor": "Датчик, який повідомляє про потужність у реальному часі (у ватах). Ви можете будь-коли змінити це, не втрачаючи історичних даних - це стане в нагоді, якщо ви заміните розумну вилку.", + "min_power": "Показники потужності вище цього порогу (у ватах) вказують на те, що прилад працює. Почніть з 2 Вт для більшості пристроїв.", + "off_delay": "Секунди згладженої потужності повинні залишатися нижче порогового значення зупинки, щоб позначити завершення. За замовчуванням: 120 с.", + "notify_actions": "Додаткова послідовність дій Home Assistant для сповіщень (сценарії, повідомлення, TTS тощо). Якщо встановлено, дії виконуються до служби сповіщення про резервне сповіщення.", + "notify_people": "Сутності людей, які використовуються для перевірки присутності. Якщо ввімкнути, доставка сповіщень затримується, поки принаймні одна вибрана особа не буде вдома.", + "notify_only_when_home": "Якщо ввімкнено, відкладати сповіщення, поки принаймні одна вибрана особа не буде вдома.", + "notify_fire_events": "Якщо ввімкнено, запускати події Home Assistant для початку та кінця циклу (ha_washdata_cycle_started і ha_washdata_cycle_ended) для автоматизації.", + "notify_start_services": "Одна або кілька служб сповіщення про початок циклу. Залиште пустим, щоб пропустити сповіщення про початок.", + "notify_finish_services": "Одна або кілька служб сповіщення, щоб сповістити, коли цикл закінчується або наближається до завершення. Залиште пустим, щоб пропустити сповіщення про завершення.", + "notify_live_services": "Одна або кілька служб сповіщення для отримання поточних оновлень прогресу під час виконання циклу (лише для мобільного допоміжного додатка). Залиште поле пустим, щоб пропустити живі оновлення.", + "notify_before_end_minutes": "За хвилини до передбачуваного завершення циклу, щоб ініціювати сповіщення. Встановіть 0, щоб вимкнути. Типове значення: 0.", + "notify_title": "Спеціальна назва для сповіщень. Підтримує заповнювач {device}.", + "notify_icon": "Значок для сповіщень (наприклад, mdi:washing-machine). Залиште пустим, щоб надсилати без значка.", + "notify_start_message": "Повідомлення надсилається, коли починається цикл. Підтримує заповнювач {device}.", + "notify_finish_message": "Повідомлення надсилається після завершення циклу. Підтримує заповнювачі {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Текст повторюваних оновлень поточного прогресу під час виконання циклу. Підтримує заповнювачі {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Сповіщення", + "description": "Налаштуйте канали сповіщень, шаблони та, за бажанням, поточні оновлення мобільного прогресу.", + "data": { + "notify_actions": "Дії сповіщень", + "notify_people": "Відкладіть доставку, поки ці люди не повернуться вдома", + "notify_only_when_home": "Відкласти сповіщення, поки вибрана особа не буде вдома", + "notify_fire_events": "Активувати події автоматизації", + "notify_start_services": "Початок циклу - цілі сповіщень", + "notify_finish_services": "Завершення циклу - цілі сповіщень", + "notify_live_services": "Прогрес у реальному часі - цілі сповіщень", + "notify_before_end_minutes": "Сповіщення перед завершенням (за хвилини до завершення)", + "notify_live_interval_seconds": "Інтервал оновлення в реальному часі (секунди)", + "notify_live_overrun_percent": "Дозвіл на перевищення поточного оновлення (%)", + "notify_live_chronometer": "Таймер зворотного відліку хронометра", + "notify_title": "Заголовок сповіщення", + "notify_icon": "Значок сповіщення", + "notify_start_message": "Формат повідомлення про початок", + "notify_finish_message": "Формат повідомлення про завершення", + "notify_pre_complete_message": "Формат повідомлення перед завершенням", + "energy_price_entity": "Ціна енергії - Суб’єкт (необов’язково)", + "energy_price_static": "Ціна енергії - статичне значення (необов’язково)", + "go_back": "Повернутися без збереження", + "notify_reminder_message": "Формат повідомлення нагадування", + "notify_timeout_seconds": "Автоматичне відхилення через (секунди)", + "notify_channel": "Канал сповіщень (статус/прямий ефір/нагадування)", + "notify_finish_channel": "Канал сповіщень завершено" + }, + "data_description": { + "go_back": "Позначте це і натисніть Надіслати, щоб відхилити всі зміни вище та повернутися до головного меню.", + "notify_actions": "Додаткова послідовність дій Home Assistant для сповіщень (сценарії, повідомлення, TTS тощо). Якщо встановлено, дії виконуються до служби сповіщення про резервне сповіщення.", + "notify_people": "Сутності людей, які використовуються для перевірки присутності. Якщо ввімкнути, доставка сповіщень затримується, поки принаймні одна вибрана особа не буде вдома.", + "notify_only_when_home": "Якщо ввімкнено, відкладати сповіщення, поки принаймні одна вибрана особа не буде вдома.", + "notify_fire_events": "Якщо ввімкнено, запускати події Home Assistant для початку та кінця циклу (ha_washdata_cycle_started і ha_washdata_cycle_ended) для автоматизації.", + "notify_start_services": "Одна або кілька служб сповіщення, які сповіщають про початок циклу (наприклад, notify.mobile_app_my_phone). Залиште пустим, щоб пропустити сповіщення про початок.", + "notify_finish_services": "Одна або кілька служб сповіщення, щоб сповістити, коли цикл закінчується або наближається до завершення. Залиште пустим, щоб пропустити сповіщення про завершення.", + "notify_live_services": "Одна або кілька служб сповіщення для отримання поточних оновлень прогресу під час виконання циклу (лише для мобільного допоміжного додатка). Залиште поле пустим, щоб пропустити живі оновлення.", + "notify_before_end_minutes": "За хвилини до передбачуваного завершення циклу, щоб ініціювати сповіщення. Встановіть 0, щоб вимкнути. Типове значення: 0.", + "notify_live_interval_seconds": "Як часто під час роботи надсилаються оновлення прогресу. Нижчі значення більш чуйні, але споживають більше сповіщень. Застосовується мінімум 30 секунд - значення нижче 30 секунд автоматично округляються до 30 секунд.", + "notify_live_overrun_percent": "Запас надійності вище приблизної кількості оновлень для довгих/перевершених циклів (наприклад, 20% дозволяє 1,2-кратне передбачуване оновлення).", + "notify_live_chronometer": "Якщо ввімкнено, кожне живе оновлення включає зворотний відлік хронометра до очікуваного часу фінішу. Таймер сповіщень автоматично відраховується на пристрої між оновленнями (лише супутня програма для Android).", + "notify_title": "Спеціальна назва для сповіщень. Підтримує заповнювач {device}.", + "notify_icon": "Значок для сповіщень (наприклад, mdi:washing-machine). Залиште пустим, щоб надсилати без значка.", + "notify_start_message": "Повідомлення надсилається, коли починається цикл. Підтримує заповнювач {device}.", + "notify_finish_message": "Повідомлення надсилається після завершення циклу. Підтримує заповнювачі {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Виберіть числову сутність, яка містить поточну ціну на електроенергію (наприклад, sensor.electricity_price, input_number.energy_rate). Якщо встановлено, вмикає заповнювач {cost}. Має пріоритет над статичним значенням, якщо обидва налаштовані.", + "energy_price_static": "Фіксована ціна електроенергії за кВт/год. Якщо встановлено, вмикає заповнювач {cost}. Ігнорується, якщо об’єкт налаштовано вище. Додайте символ вашої валюти в шаблон повідомлення, напр. {cost} євро.", + "notify_reminder_message": "Текст одноразового нагадування, надісланий за вказану кількість хвилин до передбачуваного закінчення. На відміну від регулярних поточних оновлень вище. Підтримує заповнювачі {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Автоматично відхиляти сповіщення через стільки секунд (пересилаються як час очікування супутньої програми). Установіть значення 0, щоб зберегти їх до звільнення. Типове значення: 0.", + "notify_channel": "Android канал сповіщень супутньої програми для сповіщень про статус, поточний прогрес і сповіщення про нагадування. Звук і важливість каналу налаштовуються в додатку-супутнику під час першого використання назви каналу - WashData встановлює лише назву каналу. Залиште порожнім для програми за умовчанням.", + "notify_finish_channel": "Окремий канал Android для сповіщення про завершення (також використовується нагадуванням і нагадуванням про очікування білизни), щоб воно могло мати власний звук. Залиште пустим, щоб повторно використовувати канал вище.", + "notify_pre_complete_message": "Текст повторюваних оновлень поточного прогресу під час виконання циклу. Підтримує заповнювачі {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Розширені налаштування", + "description": "Налаштуйте пороги виявлення, навчання та вдосконалену поведінку. Значення за замовчуванням добре працюють для більшості конфігурацій.", + "sections": { + "suggestions_section": { + "name": "Пропоновані налаштування", + "description": "Доступно {suggestions_count} вивчених рекомендацій. Позначте 'Застосувати запропоновані значення', щоб переглянути запропоновані зміни перед збереженням.", + "data": { + "apply_suggestions": "Застосувати запропоновані значення" + }, + "data_description": { + "apply_suggestions": "Установіть цей прапорець, щоб відкрити етап перегляду запропонованих значень від алгоритму навчання. Автоматично нічого не зберігається.\n\nРекомендовано (з вивчення):\n- min_power: {suggested_min_power} Вт\n- off_delay: {suggested_off_delay} с\n- watchdog_interval: {suggested_watchdog_interval} с\n- no_update_active_timeout: {suggested_no_update_active_timeout} с\n- profile_match_interval: {suggested_profile_match_interval} с\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} Вт\n- stop_threshold_w: {suggested_stop_threshold_w} Вт\n- end_energy_threshold: {suggested_end_energy_threshold} Вт·год\n- running_dead_zone: {suggested_running_dead_zone} с\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Виявлення та пороги потужності", + "description": "Визначає, коли прилад вважається УВІМКНЕНИМ або ВИМКНЕНИМ, енергетичні пороги та час переходів між станами.", + "data": { + "start_duration_threshold": "Тривалість усунення стрибків (с)", + "start_energy_threshold": "Початковий енергетичний гейт (Вт·год)", + "completion_min_seconds": "Мінімальний час виконання завершення (секунди)", + "start_threshold_w": "Початковий поріг (Вт)", + "stop_threshold_w": "Поріг зупинки (Вт)", + "end_energy_threshold": "Ворота кінцевої енергії (Вт·год)", + "running_dead_zone": "Робоча мертва зона (секунди)", + "end_repeat_count": "Кінець підрахунку повторів", + "min_off_gap": "Мінімальний інтервал між циклами (с)", + "sampling_interval": "Інтервал вибірки (с)" + }, + "data_description": { + "start_duration_threshold": "Ігноруйте короткі стрибки потужності, коротші за цю тривалість (у секундах). Відфільтровує стрибки завантаження (наприклад, 60 Вт протягом 2 с) перед початком справжнього циклу. За замовчуванням: 5 с.", + "start_energy_threshold": "Цикл має накопичити принаймні стільки енергії (Вт·год) під час фази СТАРТ, щоб підтвердити його. За замовчуванням: 0,005 Вт·год.", + "completion_min_seconds": "Цикли, коротші за цей, позначатимуться як «перервані», навіть якщо вони завершаться природним чином. За замовчуванням: 600 с.", + "start_threshold_w": "Потужність має піднятися ВИЩЕ за цей поріг, щоб ЗАПУСТИТИ цикл. Встановіть більше, ніж у режимі очікування. За замовчуванням: min_power + 1 Вт.", + "stop_threshold_w": "Потужність має впасти НИЖЧЕ цього порогу, щоб ЗАВЕРШИТИ цикл. Встановіть трохи вище нуля, щоб уникнути шуму, що викликає помилкові закінчення. Типове значення: 0,6 * min_power.", + "end_energy_threshold": "Цикл завершується, лише якщо енергія в останньому вікні затримки вимкнення нижче цього значення (Вт·год). Запобігає помилковим закінченням, якщо потужність коливається близько нуля. За замовчуванням: 0,05 Вт·год.", + "running_dead_zone": "Після початку циклу ігноруйте падіння потужності протягом цієї кількості секунд, щоб запобігти помилковому виявленню кінця під час початкової фази обертання. Встановіть 0, щоб вимкнути. За замовчуванням: 0 с.", + "end_repeat_count": "Кількість разів, коли умова завершення (потужність нижче порогу для затримки вимкнення) повинна бути виконана послідовно перед завершенням циклу. Вищі значення запобігають помилковим закінченням під час пауз. За замовчуванням: 1.", + "min_off_gap": "Якщо цикл починається протягом такої кількості секунд після закінчення попереднього, вони будуть ОБ’ЄДНАНІ в один цикл.", + "sampling_interval": "Мінімальний інтервал вибірки в секундах. Оновлення швидше, ніж ця швидкість, ігноруватимуться. За замовчуванням: 30 с (2 с для пральних машин, прально-сушильних машин і посудомийних машин)." + } + }, + "matching_section": { + "name": "Зіставлення профілів і навчання", + "description": "Визначає, наскільки агресивно WashData зіставляє поточні цикли з вивченими профілями і коли запитувати відгук.", + "data": { + "profile_match_min_duration_ratio": "Коефіцієнт мінімальної тривалості відповідності профілю (0,1-1,0)", + "profile_match_interval": "Інтервал збігу профілю (секунди)", + "profile_match_threshold": "Поріг відповідності профілю", + "profile_unmatch_threshold": "Поріг невідповідності профілю", + "auto_label_confidence": "Впевненість автоматичної мітки (0-1)", + "learning_confidence": "Достовірність запиту зворотного зв'язку (0-1)", + "suppress_feedback_notifications": "Придушити сповіщення про відгуки", + "duration_tolerance": "Допустима тривалість (0-0,5)", + "smoothing_window": "Вікно згладжування (зразки)", + "profile_duration_tolerance": "Допуск на тривалість відповідності профілю (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Мінімальний коефіцієнт тривалості для відповідності профілю. Щоб збігатися, робочий цикл має становити принаймні цю частину тривалості профілю. Нижче = раннє зіставлення. За замовчуванням: 0,50 (50%).", + "profile_match_interval": "Як часто (у секундах) намагатися зіставити профіль протягом циклу. Нижчі значення забезпечують швидше виявлення, але використовують більше ЦП. За замовчуванням: 300 с (5 хвилин).", + "profile_match_threshold": "Мінімальна оцінка подібності DTW (0,0–1,0), необхідна для розгляду профілю як збігу. Вища = точніша відповідність, менше помилкових спрацьовувань. Типове значення: 0,4.", + "profile_unmatch_threshold": "Оцінка подібності DTW (0,0–1,0), нижче якої попередньо зіставлений профіль відхиляється. Має бути нижчим за поріг збігу, щоб запобігти мерехтінню. Типове значення: 0,35.", + "auto_label_confidence": "Автоматично позначати цикли на рівні або вище цієї впевненості після завершення (0,0–1,0).", + "learning_confidence": "Мінімальна впевненість для запиту підтвердження користувача через подію (0,0–1,0).", + "suppress_feedback_notifications": "Якщо ввімкнено, WashData все одно відстежуватиме та запитуватиме внутрішній відгук, але не відображатиме постійні сповіщення з проханням підтвердити цикли. Корисно, коли цикли завжди виявляються правильно, а підказки відволікають.", + "duration_tolerance": "Допуск для оцінок часу, що залишився під час виконання (0,0–0,5 відповідає 0–50%).", + "smoothing_window": "Кількість останніх вимірювань потужності, використаних для згладжування ковзного середнього.", + "profile_duration_tolerance": "Допуск, який використовується під час зіставлення профілю для загальної дисперсії тривалості (0,0–0,5). Поведінка за замовчуванням: ±25%." + } + }, + "timing_section": { + "name": "Час, обслуговування та налагодження", + "description": "Watchdog, скидання прогресу, автоматичне обслуговування та показ сутностей налагодження.", + "data": { + "watchdog_interval": "Сторожовий інтервал (секунди)", + "no_update_active_timeout": "Час очікування без оновлення (с)", + "progress_reset_delay": "Затримка скидання прогресу (секунди)", + "auto_maintenance": "Увімкнути автоматичне обслуговування", + "expose_debug_entities": "Викрити сутності налагодження", + "save_debug_traces": "Зберегти сліди налагодження" + }, + "data_description": { + "watchdog_interval": "Секунди між контрольними перевірками під час роботи. За замовчуванням: 5 с. ПОПЕРЕДЖЕННЯ: переконайтеся, що цей інтервал ВИЩИЙ за інтервал оновлення вашого датчика, щоб уникнути помилкових зупинок (наприклад, якщо датчик оновлюється кожні 60 с, використовуйте 65 с+).", + "no_update_active_timeout": "Для датчиків публікації після зміни: примусово завершуйте АКТИВНИЙ цикл, лише якщо протягом стільки секунд не надходить оновлення. Завершення з низьким енергоспоживанням усе ще використовує затримку вимкнення.", + "progress_reset_delay": "Після завершення циклу (100%) зачекайте стільки секунд бездіяльності, перш ніж скинути прогрес до 0%. За замовчуванням: 300 с.", + "auto_maintenance": "Увімкнути автоматичне обслуговування (відновлення зразків, об’єднання фрагментів).", + "expose_debug_entities": "Показати розширені датчики (достовірність, фаза, неоднозначність) для налагодження.", + "save_debug_traces": "Зберігайте детальні дані рейтингу та трасування потужності в історії (збільшує використання пам’яті)." + } + }, + "anti_wrinkle_section": { + "name": "Щит проти зморшок (сушарки)", + "description": "Ігноруйте оберти барабана після завершення циклу, щоб запобігти фантомним циклам. Лише сушарка / прально-сушильна машина.", + "data": { + "anti_wrinkle_enabled": "Щит проти зморшок (тільки для сушарки)", + "anti_wrinkle_max_power": "Максимальна потужність проти зморшок (Вт)", + "anti_wrinkle_max_duration": "Максимальна тривалість проти зморшок (с)", + "anti_wrinkle_exit_power": "Вихідна потужність проти зморшок (Вт)" + }, + "data_description": { + "anti_wrinkle_enabled": "Ігноруйте короткі малопотужні оберти барабана після завершення циклу, щоб запобігти повторним циклам (тільки для сушильної/прально-сушильної машин).", + "anti_wrinkle_max_power": "Сплески на рівні або нижче цієї потужності розглядаються як обертання проти зморшок замість нових циклів. Застосовується, лише коли ввімкнено Anti-Wrinkle Shield.", + "anti_wrinkle_max_duration": "Максимальна довжина вибуху для обертання проти зморшок. Застосовується, лише коли ввімкнено Anti-Wrinkle Shield.", + "anti_wrinkle_exit_power": "Якщо потужність падає нижче цього рівня, негайно вимкніть функцію запобігання зморшкам (вимкнено). Застосовується, лише коли ввімкнено Anti-Wrinkle Shield." + } + }, + "delay_start_section": { + "name": "Виявлення відкладеного старту", + "description": "Вважайте тривалий режим очікування з низьким споживанням станом 'Очікування запуску', доки цикл фактично не розпочнеться.", + "data": { + "delay_start_detect_enabled": "Увімкнути виявлення затримки запуску", + "delay_confirm_seconds": "Відкладений старт: час підтвердження режиму очікування (s)", + "delay_timeout_hours": "Відкладений старт: максимальний час очікування (год)" + }, + "data_description": { + "delay_start_detect_enabled": "Якщо ввімкнено, короткий стрибок споживання електроенергії з низьким споживанням електроенергії, а потім тривалий режим очікування, визначається як відкладений старт. Датчик стану показує «Очікування запуску» під час затримки. Тривалість або прогрес програми не відстежуються, поки цикл фактично не розпочнеться. Вимагає, щоб параметр start_threshold_w був встановлений вище стрибкової потужності споживання.", + "delay_confirm_seconds": "Потужність має залишатися між порогом зупинки (W) і порогом запуску (W) протягом цієї кількості секунд, перш ніж WashData перейде в стан 'Очікування запуску'. Це відсіює короткі піки під час навігації меню. Типово: 60 s.", + "delay_timeout_hours": "Безпечний тайм-аут: якщо машина все ще перебуває в режимі «Очікування запуску» через стільки годин, WashData скидається на Вимк. За замовчуванням: 8 год." + } + }, + "external_triggers_section": { + "name": "Зовнішні тригери, дверцята і пауза", + "description": "Необов’язковий зовнішній датчик завершення циклу, датчик дверей для виявлення паузи / розвантаження та перемикач відключення живлення під час паузи.", + "data": { + "external_end_trigger_enabled": "Увімкнути зовнішній тригер завершення циклу", + "external_end_trigger": "Зовнішній датчик кінця циклу", + "external_end_trigger_inverted": "Інвертувати логіку тригера (тригер увімкнено ВИМК.)", + "door_sensor_entity": "Датчик дверей", + "pause_cuts_power": "Вимкніть живлення під час паузи", + "switch_entity": "Switch Entity (для призупинення відключення електроенергії)", + "notify_unload_delay_minutes": "Затримка повідомлення про очікування пральні (хв.)" + }, + "data_description": { + "external_end_trigger_enabled": "Увімкніть моніторинг зовнішнього бінарного датчика, щоб ініціювати завершення циклу.", + "external_end_trigger": "Виберіть бінарний датчик. Коли цей датчик спрацьовує, поточний цикл завершиться зі статусом «завершено».", + "external_end_trigger_inverted": "За замовчуванням тригер спрацьовує, коли датчик вмикається. Установіть цей прапорець, щоб спрацьовувати, коли датчик замість цього вимикається.", + "door_sensor_entity": "Додатковий бінарний датчик для дверцят машини (увімкнено = відкрито, вимкнено = закрито). Коли дверцята відкриваються під час активного циклу, WashData підтвердить, що цикл навмисно призупинено. Після завершення циклу, якщо дверцята все ще закриті, стан змінюється на «Чисто», доки дверцята не відчиняться. Примітка: закриття дверей НЕ автоматично відновлює призупинений цикл - відновлення має бути запущено вручну за допомогою кнопки «Відновити цикл» або служби.", + "pause_cuts_power": "Під час призупинення за допомогою кнопки «Призупинити цикл» або служби також вимкніть налаштовану сутність перемикача. Діє, лише якщо для цього пристрою налаштовано комутатор.", + "switch_entity": "Сутність перемикача, яку потрібно перемикати під час паузи або відновлення. Потрібний, якщо ввімкнено параметр «Вимикати живлення під час паузи».", + "notify_unload_delay_minutes": "Надішліть сповіщення через канал сповіщень про завершення після того, як цикл закінчиться, і двері не відчинятимуться стільки хвилин (потрібен датчик дверей). Встановіть 0, щоб вимкнути. За замовчуванням: 60 хв." + } + }, + "pump_section": { + "name": "Монітор насоса", + "description": "Лише для пристроїв типу насос. Викликає подію, якщо цикл насоса триває довше за цей час.", + "data": { + "pump_stuck_duration": "Порогове значення попередження про зупинку насоса (лише насос)" + }, + "data_description": { + "pump_stuck_duration": "Якщо цикл насоса все ще виконується через стільки секунд, подія ha_washdata_pump_stuck запускається один раз. Використовуйте це для виявлення заклинилого двигуна (звичайний час роботи колодцевого насоса менше 60 с). За замовчуванням: 1800 с (30 хв). Тільки тип насосного пристрою." + } + }, + "device_link_section": { + "name": "Посилання на пристрій", + "description": "Необов’язково зв’яжіть цей пристрій WashData з наявним пристроєм Home Assistant (наприклад, розумною вилкою чи самим приладом). Потім пристрій WashData відображається як «Підключено через» цей пристрій. Залиште поле пустим, щоб WashData залишався автономним пристроєм.", + "data": { + "linked_device": "Підключений пристрій" + }, + "data_description": { + "linked_device": "Виберіть пристрій, до якого потрібно підключити WashData. Це додає посилання «Підключено через» до реєстру пристрою; WashData зберігає власну картку пристрою та сутності. Зніміть вибір, щоб видалити посилання." + } + } + } + }, + "diagnostics": { + "title": "Діагностика та технічне обслуговування", + "description": "Виконуйте дії з обслуговування, як-от об’єднання фрагментованих циклів або перенесення збережених даних.\n\n**Використання сховища**\n\n- Розмір файлу: {file_size_kb} Кб\n- Цикли: {cycle_count}\n- Профілі: {profile_count}\n- Сліди налагодження: {debug_count}", + "menu_options": { + "reprocess_history": "Обслуговування: повторна обробка та оптимізація даних", + "clear_debug_data": "Очистити дані налагодження (звільнити місце)", + "wipe_history": "Очистити ВСІ дані цього пристрою (безповоротно)", + "export_import": "Експорт/імпорт JSON з налаштуваннями (копіювати/вставити)", + "menu_back": "← Назад" + } + }, + "clear_debug_data": { + "title": "Очистити дані налагодження", + "description": "Ви впевнені, що бажаєте видалити всі збережені трасування налагодження? Це звільнить місце, але видалить детальну інформацію про рейтинг із минулих циклів." + }, + "manage_cycles": { + "title": "Керування циклами", + "description": "Останні цикли:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Автоматичне позначення старих циклів", + "select_cycle_to_label": "Позначити конкретний цикл", + "select_cycle_to_delete": "Видалити цикл", + "interactive_editor": "Інтерактивний редактор злиття/розділу", + "trim_cycle_select": "Обрізати дані циклу", + "menu_back": "← Назад" + } + }, + "manage_cycles_empty": { + "title": "Керування циклами", + "description": "Циклів ще не зареєстровано.", + "menu_options": { + "auto_label_cycles": "Автоматичне позначення старих циклів", + "menu_back": "← Назад" + } + }, + "manage_profiles": { + "title": "Керування профілями", + "description": "Підсумок профілю:\n{profile_summary}", + "menu_options": { + "create_profile": "Створити новий профіль", + "edit_profile": "Редагувати/перейменувати профіль", + "delete_profile_select": "Видалити профіль", + "profile_stats": "Статистика профілю", + "cleanup_profile": "Очистити історію - побудувати графік і видалити", + "assign_profile_phases_select": "Призначте діапазони фаз", + "menu_back": "← Назад" + } + }, + "manage_profiles_empty": { + "title": "Керування профілями", + "description": "Профілі ще не створені.", + "menu_options": { + "create_profile": "Створити новий профіль", + "menu_back": "← Назад" + } + }, + "manage_phase_catalog": { + "title": "Керування каталогом фаз", + "description": "Каталог фаз (за умовчанням для цього пристрою + спеціальні фази):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Створити нову фазу", + "phase_catalog_edit_select": "Редагувати фазу", + "phase_catalog_delete": "Видалити фазу", + "menu_back": "← Назад" + } + }, + "phase_catalog_create": { + "title": "Створення спеціальної фази", + "description": "Створіть власну назву та опис фази та виберіть, до якого типу пристрою вона застосовується.", + "data": { + "target_device_type": "Тип цільового пристрою", + "phase_name": "Назва фази", + "phase_description": "Опис фази" + } + }, + "phase_catalog_edit_select": { + "title": "Редагувати спеціальну фазу", + "description": "Виберіть спеціальну фазу для редагування.", + "data": { + "phase_name": "Спеціальна фаза" + } + }, + "phase_catalog_edit": { + "title": "Редагувати спеціальну фазу", + "description": "Етап редагування: {phase_name}", + "data": { + "phase_name": "Назва фази", + "phase_description": "Опис фази" + } + }, + "phase_catalog_delete": { + "title": "Видалити спеціальну фазу", + "description": "Виберіть спеціальну фазу для видалення. Усі призначені діапазони за допомогою цієї фази буде видалено.", + "data": { + "phase_name": "Спеціальна фаза" + } + }, + "assign_profile_phases": { + "title": "Призначте діапазони фаз", + "description": "Попередній перегляд фази для профілю: **{profile_name}**\n\n{timeline_svg}\n\n**Поточні діапазони:**\n{current_ranges}\n\nВикористовуйте наведені нижче дії, щоб додавати, редагувати, видаляти або зберігати діапазони.", + "menu_options": { + "assign_profile_phases_add": "Додайте діапазон фаз", + "assign_profile_phases_edit_select": "Редагувати діапазон фаз", + "assign_profile_phases_delete": "Видалити діапазон фаз", + "phase_ranges_clear": "Очистити всі діапазони", + "assign_profile_phases_auto_detect": "Автоматичне визначення фаз", + "phase_ranges_save": "Зберегти та повернути", + "menu_back": "← Назад" + } + }, + "assign_profile_phases_add": { + "title": "Додайте діапазон фаз", + "description": "Додайте один діапазон фаз до поточної чернетки.", + "data": { + "phase_name": "Фаза", + "start_min": "Початкова хвилина", + "end_min": "Кінцева хвилина" + } + }, + "assign_profile_phases_edit_select": { + "title": "Редагувати діапазон фаз", + "description": "Виберіть діапазон для редагування.", + "data": { + "range_index": "Діапазон фаз" + } + }, + "assign_profile_phases_edit": { + "title": "Редагувати діапазон фаз", + "description": "Оновіть вибраний діапазон фаз.", + "data": { + "phase_name": "Фаза", + "start_min": "Початкова хвилина", + "end_min": "Кінцева хвилина" + } + }, + "assign_profile_phases_delete": { + "title": "Видалити діапазон фаз", + "description": "Виберіть діапазон, який потрібно видалити з чернетки.", + "data": { + "range_index": "Діапазон фаз" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Автоматично визначені фази", + "description": "Профіль: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} фази виявлено автоматично.** Виберіть дію нижче.", + "data": { + "action": "Дія" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Назвіть виявлені фази", + "description": "Профіль: **{profile_name}**\n\n**Виявлено {detected_count} фази:**\n{ranges_summary}\n\nПрисвойте назву кожній фазі." + }, + "assign_profile_phases_select": { + "title": "Призначте діапазони фаз", + "description": "Виберіть профіль, щоб налаштувати діапазони фаз.", + "data": { + "profile": "Профіль" + } + }, + "profile_stats": { + "title": "Статистика профілю", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Створити новий профіль", + "description": "Створіть новий профіль вручну або на основі минулого циклу.\n\nПриклади назв профілів: «Бавовна 40°C», «Еко», «Швидке прання», «Бавовна + Сушіння в шафу».\n\n**Що використовує відповідник:** форма енергоспоживання, загальна тривалість циклу та загальна енергія. Він не зчитує безпосередньо параметри температури або віджимання.\n\n**Профілі, які будуть надійно автоматично визначені:** програми з явно різною тривалістю або моделями потужності – напр. 20-хвилинне швидке прання проти 3-годинного циклу «Бавовна» або цикл лише прання проти комбінованого прання+сушіння (який зазвичай триває у 2–3 рази довше).\n\n**Профілі, для яких може знадобитися ручний вибір:** варіанти однієї програми, що відрізняються лише температурою чи швидкістю віджимання (наприклад, Бавовна 40°C проти Бавовна 60°C), часто дають подібні форми потужності. Система все одно намагатиметься розрізнити їх і вивчати ваші виправлення з часом, але спочатку очікуйте, що збіги підтверджуватимуться вручну.\n\n**Порада щодо прально-сушильної машини:** створіть окремі профілі для режимів лише прання та прання+сушіння – різниця в тривалості й енергоспоживанні достатньо велика для надійного автоматичного визначення.", + "data": { + "profile_name": "Ім'я профілю", + "reference_cycle": "Базовий цикл (необов'язково)", + "manual_duration": "Тривалість вручну (хвилини)" + } + }, + "edit_profile": { + "title": "Редагувати профіль", + "description": "Виберіть профіль для перейменування", + "data": { + "profile": "Профіль" + } + }, + "rename_profile": { + "title": "Редагувати деталі профілю", + "description": "Поточний профіль: {current_name}", + "data": { + "new_name": "Ім'я профілю", + "manual_duration": "Тривалість вручну (хвилини)" + } + }, + "delete_profile_select": { + "title": "Видалити профіль", + "description": "Виберіть профіль для видалення", + "data": { + "profile": "Профіль" + } + }, + "delete_profile_confirm": { + "title": "Підтвердьте видалення профілю", + "description": "⚠️ Це назавжди видалить профіль: {profile_name}", + "data": { + "unlabel_cycles": "Видаліть мітку з циклів за допомогою цього профілю" + } + }, + "auto_label_cycles": { + "title": "Автоматичне позначення старих циклів", + "description": "Загальна кількість циклів: {total_count}. Профілі: {profiles}", + "data": { + "confidence_threshold": "Мінімальна впевненість (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Виберіть цикл для позначення", + "description": "✓ = завершено, ⚠ = відновлено, ✗ = перервано", + "data": { + "cycle_id": "Цикл" + } + }, + "select_cycle_to_delete": { + "title": "Видалити цикл", + "description": "⚠️ Це назавжди видалить вибраний цикл", + "data": { + "cycle_id": "Цикл для видалення" + } + }, + "label_cycle": { + "title": "Позначення циклу", + "description": "{cycle_info}", + "data": { + "profile_name": "Профіль", + "new_profile_name": "Нова назва профілю (якщо створюється)" + } + }, + "post_process": { + "title": "Обробка історії (злиття/розділення)", + "description": "Очистіть історію циклів, об’єднавши фрагментовані цикли та розділивши неправильно об’єднані цикли в межах часового вікна. Введіть кількість годин для перегляду (використовуйте 999999 для всіх).", + "data": { + "time_range": "Вікно огляду (годин)", + "gap_seconds": "Проміжок злиття/розділу (секунди)" + }, + "data_description": { + "time_range": "Кількість годин для сканування фрагментованих циклів для об’єднання. Використовуйте 999999 для обробки всіх історичних даних.", + "gap_seconds": "Порогове значення для прийняття рішення про розділення чи злиття. Прогалини КОРОТШІ, ніж це, об’єднуються. Прогалини ДОВШІ, ніж це, розділені. Знизьте це значення, щоб примусово розділити цикл, який було об’єднано неправильно." + } + }, + "cleanup_profile": { + "title": "Очистити історію - виберіть профіль", + "description": "Виберіть профіль для візуалізації. Це створить графік, який показує всі минулі цикли для цього профілю, щоб допомогти визначити викиди.", + "data": { + "profile": "Профіль" + } + }, + "cleanup_select": { + "title": "Очистити історію - побудувати графік і видалити", + "description": "![Графік]({graph_url})\n\n**Візуалізація {profile_name}**\n\nГрафік показує окремі цикли кольоровими лініями. Визначте викиди (лінії, віддалені від групи) і зіставте їхній колір у списку нижче, щоб видалити їх.\n\n**Виберіть цикли для ПОСТІЙНОГО видалення:**", + "data": { + "cycles_to_delete": "Цикли для видалення (перевірте відповідність кольорів)" + } + }, + "interactive_editor": { + "title": "Інтерактивний редактор злиття/розділу", + "description": "Виберіть дію вручну, яку потрібно виконати з історією циклу.", + "menu_options": { + "editor_split": "Розділити цикл (знайти прогалини)", + "editor_merge": "Цикли злиття (об’єднання фрагментів)", + "editor_delete": "Видалити цикл(и)", + "menu_back": "← Назад" + } + }, + "editor_select": { + "title": "Виберіть цикли", + "description": "Виберіть цикл(и) для обробки.\n\n{info_text}", + "data": { + "selected_cycles": "Цикли" + } + }, + "editor_configure": { + "title": "Налаштувати та застосувати", + "description": "{preview_md}", + "data": { + "confirm_action": "Дія", + "merged_profile": "Результуючий профіль", + "new_profile_name": "Нова назва профілю", + "confirm_commit": "Так, я підтверджую цю дію", + "segment_0_profile": "Сегмент 1 Профіль", + "segment_1_profile": "Сегмент 2 Профіль", + "segment_2_profile": "Сегмент 3 Профіль", + "segment_3_profile": "Сегмент 4 Профіль", + "segment_4_profile": "Сегмент 5 Профіль", + "segment_5_profile": "Сегмент 6 Профіль", + "segment_6_profile": "Сегмент 7 Профіль", + "segment_7_profile": "Сегмент 8 Профіль", + "segment_8_profile": "Сегмент 9 Профіль", + "segment_9_profile": "Сегмент 10 Профіль" + } + }, + "editor_split_params": { + "title": "Конфігурація розділення", + "description": "Відрегулюйте чутливість для розділення цього циклу. Будь-який проміжок простою, довший за цей, призведе до розколу.", + "data": { + "split_mode": "Метод розділення" + } + }, + "editor_split_auto_params": { + "title": "Налаштування автоподілу", + "description": "Налаштуйте чутливість поділу цього циклу. Будь-який інтервал бездіяльності, довший за це значення, призведе до поділу.", + "data": { + "min_gap_seconds": "Поріг паузи для поділу (секунди)" + } + }, + "editor_split_manual_params": { + "title": "Мітки часу ручного поділу", + "description": "{preview_md}Вікно циклу: **{cycle_start_wallclock} → {cycle_end_wallclock}** на {cycle_date}.\n\nВведіть одну або кілька міток часу поділу (`HH:MM` або `HH:MM:SS`), по одній на рядок. Цикл буде розрізано на кожній мітці часу — N міток часу дають N+1 сегментів.", + "data": { + "split_timestamps": "Часові позначки поділу" + } + }, + "reprocess_history": { + "title": "Обслуговування: повторна обробка та оптимізація даних", + "description": "Виконує глибоке очищення історичних даних:\n1. Обрізає показання нульової потужності (тиша).\n2. Виправляє час/тривалість циклу.\n3. Перераховує всі статистичні моделі (конверти).\n\n**Запустіть це, щоб вирішити проблеми з даними або застосувати нові алгоритми.**" + }, + "wipe_history": { + "title": "Очистити історію (лише тестування)", + "description": "⚠️ Це назавжди видалить УСІ збережені цикли та профілі для цього пристрою. Це неможливо скасувати!" + }, + "export_import": { + "title": "Експорт/імпорт JSON", + "description": "Скопіюйте/вставте цикли, профілі та налаштування для цього пристрою. Експортуйте нижче або вставте JSON для імпорту.", + "data": { + "mode": "Дія", + "json_payload": "Повна конфігурація JSON" + }, + "data_description": { + "mode": "Виберіть «Експорт», щоб скопіювати поточні дані та налаштування, або «Імпорт», щоб вставити експортовані дані з іншого пристрою WashData.", + "json_payload": "Для експорту: скопіюйте цей JSON (включає цикли, профілі та всі налаштовані налаштування). Для імпорту: вставте JSON, експортований з WashData." + } + }, + "record_cycle": { + "title": "Запис циклу", + "description": "Запишіть цикл вручну, щоб створити чистий профіль без перешкод.\n\nСтатус: **{status}**\nТривалість: {duration} с\nЗразки: {samples}", + "menu_options": { + "record_refresh": "Оновити статус", + "record_stop": "Зупинити запис (зберегти та обробити)", + "record_start": "Почати новий запис", + "record_process": "Обробити останній запис (обрізати та зберегти)", + "record_discard": "Відкинути останній запис", + "menu_back": "← Назад" + } + }, + "record_process": { + "title": "Обробка запису", + "description": "![Графік]({graph_url})\n\n**На графіку показано необроблений запис.** Синій = зберегти, червоний = запропоноване обрізання.\n*Графік є статичним і не оновлюється зі змінами форми.*\n\nЗапис зупинено. Обрізки вирівнюються відповідно до виявленої частоти дискретизації (~{sampling_rate} с).\n\nНеоброблена тривалість: {duration} с\nЗразки: {samples}", + "data": { + "head_trim": "Початок обрізки (секунди)", + "tail_trim": "Кінець обрізання (секунди)", + "save_mode": "Зберегти пункт призначення", + "profile_name": "Ім'я профілю" + } + }, + "learning_feedbacks": { + "title": "Відгуки про навчання", + "description": "Відгуки в очікуванні: {count}\n\n{pending_table}\n\nВиберіть запит на перегляд циклу для обробки.", + "menu_options": { + "learning_feedbacks_pick": "Переглянути відгук в очікуванні", + "learning_feedbacks_dismiss_all": "Відхилити всі відгуки в очікуванні", + "menu_back": "← Назад" + } + }, + "learning_feedbacks_pick": { + "title": "Виберіть відгук для перегляду", + "description": "Виберіть запит на перегляд циклу, який потрібно відкрити.", + "data": { + "selected_feedback": "Виберіть цикл для перегляду" + } + }, + "learning_feedbacks_empty": { + "title": "Відгуки про навчання", + "description": "Не знайдено запитів на відгуки, що очікують на розгляд.", + "menu_options": { + "menu_back": "← Назад" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Відхилити всі відгуки, що очікують", + "description": "Ви збираєтеся відхилити **{count}** запитів на відгук, що очікують. Відхилені цикли залишаться у вашій історії, але не додадуть нового сигналу навчання. Цю дію не можна скасувати.\n\n✅ Поставте прапорець нижче та натисніть **Надіслати**, щоб відхилити все.\n❌ Не встановлюйте прапорець і натисніть **Надіслати**, щоб скасувати та повернутися назад.", + "data": { + "confirm_dismiss_all": "Підтвердити: відхилити всі запити на відгук в очікуванні" + } + }, + "resolve_feedback": { + "title": "Вирішити відгук", + "description": "{comparison_img}{candidates_table}\n**Виявлений профіль**: {detected_profile} ({confidence_pct}%)\n**Орієнтовна тривалість**: {est_duration_min} хв\n**Фактична тривалість**: {act_duration_min} хв\n\n__Оберіть дію нижче:__", + "data": { + "action": "Що ви хочете зробити?", + "corrected_profile": "Змінити програму (якщо виправляється)", + "corrected_duration": "Змінити тривалість у секундах (якщо виправляється)" + } + }, + "trim_cycle_select": { + "title": "Цикл обрізання - виберіть цикл", + "description": "Виберіть цикл для обрізання. ✓ = завершено, ⚠ = відновлено, ✗ = перервано", + "data": { + "cycle_id": "Цикл" + } + }, + "trim_cycle": { + "title": "Цикл обрізки", + "description": "Поточне вікно: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Дія" + } + }, + "trim_cycle_start": { + "title": "Установіть початок обрізки", + "description": "Вікно циклу: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nПоточний початок: **{current_wallclock}** (+{current_offset_min} хв від початку циклу)\n\nВиберіть новий час початку за допомогою засобу вибору годинника нижче.", + "data": { + "trim_start_time": "Новий час початку", + "trim_start_min": "Новий початок (хвилини від початку циклу)" + } + }, + "trim_cycle_end": { + "title": "Встановити завершення обрізки", + "description": "Вікно циклу: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nПоточний кінець: **{current_wallclock}** (+{current_offset_min} хв від початку циклу)\n\nВиберіть новий час завершення за допомогою засобу вибору годинника нижче.", + "data": { + "trim_end_time": "Новий час закінчення", + "trim_end_min": "Новий кінець (хвилини від початку циклу)" + } + } + }, + "error": { + "import_failed": "Помилка імпорту. Вставте дійсний JSON для експорту WashData.", + "select_exactly_one": "Виберіть рівно один цикл для цієї дії.", + "select_at_least_one": "Виберіть принаймні один цикл для цієї дії.", + "select_at_least_two": "Виберіть принаймні два цикли для цієї дії.", + "profile_exists": "Профіль із такою назвою вже існує", + "rename_failed": "Не вдалося перейменувати профіль. Профіль може не існувати або нове ім'я вже зайнято.", + "assignment_failed": "Не вдалося призначити профіль циклу", + "duplicate_phase": "Фаза з такою назвою вже існує.", + "phase_not_found": "Вибрану фазу не знайдено.", + "invalid_phase_name": "Назва фази не може бути пустою.", + "phase_name_too_long": "Назва фази задовга.", + "invalid_phase_range": "Діапазон фаз недійсний. Кінець має бути більшим за початок.", + "invalid_phase_timestamp": "Мітка часу недійсна. Використовуйте формат РРРР-ММ-ДД ГГ:ХХ або ГГ:ХХ.", + "overlapping_phase_ranges": "Діапазони фаз перекриваються. Налаштуйте діапазони та повторіть спробу.", + "incomplete_phase_row": "Кожен рядок фази має включати фазу плюс повні початкові/кінцеві значення для вибраного режиму.", + "timestamp_mode_cycle_required": "Будь ласка, виберіть вихідний цикл під час використання режиму мітки часу.", + "no_phase_ranges": "Для цієї дії ще немає доступних діапазонів фаз.", + "feedback_notification_title": "WashData: перевірити цикл ({device})", + "feedback_notification_message": "**Пристрій**: {device}\n**Програма**: {program} ({confidence}% впевненості)\n**Час**: {time}\n\nWashData потрібна ваша допомога, щоб перевірити цей виявлений цикл.\n\nБудь ласка, перейдіть до **Налаштування > Пристрої та служби > WashData > Налаштувати > Відгуки про навчання**, щоб підтвердити або виправити цей результат.", + "suggestions_ready_notification_title": "WashData: пропоновані налаштування готові ({device})", + "suggestions_ready_notification_message": "Датчик **Пропонованих налаштувань** тепер повідомляє **{count}** дієві рекомендації.\n\nЩоб переглянути та застосувати їх: **Налаштування > Пристрої та служби > WashData > Налаштувати > Розширені параметри > Застосувати запропоновані значення**.\n\nПропозиції є необов’язковими та відображаються для перегляду перед збереженням.", + "trim_range_invalid": "Початок має бути перед закінченням. Будь ласка, відкоригуйте точки обрізки.", + "trim_failed": "Не вдалося обрізати. Можливо, цикл більше не існує або вікно порожнє.", + "invalid_split_timestamp": "Часова позначка недійсна або виходить за межі вікна циклу. Використовуйте HH:MM або HH:MM:SS у межах вікна циклу.", + "no_split_segments_found": "За цими часовими позначками не вдалося створити жодних дійсних сегментів. Переконайтеся, що кожна часова позначка знаходиться в межах вікна циклу, а сегменти тривають щонайменше 1 хвилину.", + "auto_tune_suggestion": "{device_type} {device_title} виявлено цикли привидів. Пропонована зміна min_power: {current_min}W -> {new_min}W (не застосовується автоматично).", + "auto_tune_title": "Автоналаштування WashData", + "auto_tune_fallback": "{device_type} {device_title} виявлено цикли привидів. Пропонована зміна min_power: {current_min}W -> {new_min}W (не застосовується автоматично)." + }, + "abort": { + "no_cycles_found": "Цикли не знайдено", + "no_split_segments_found": "У вибраному циклі не знайдено сегментів, які можна розділити.", + "cycle_not_found": "Не вдалося завантажити вибраний цикл.", + "no_power_data": "Немає доступних даних трасування потужності для вибраного циклу.", + "no_profiles_found": "Профілі не знайдено. Спочатку створіть профіль.", + "no_profiles_for_matching": "Немає доступних профілів для відповідності. Спочатку створіть профілі.", + "no_unlabeled_cycles": "Усі цикли вже позначені", + "no_suggestions": "Пропонованих значень ще немає. Виконайте кілька циклів і повторіть спробу.", + "no_predictions": "Жодних прогнозів", + "no_custom_phases": "Спеціальні фази не знайдено.", + "reprocess_success": "Успішно повторно оброблено {count} циклів.", + "debug_data_cleared": "Успішно видалено дані налагодження з {count} циклів." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Початок циклу", + "cycle_finish": "Завершення циклу", + "cycle_live": "Живий прогрес" + } + }, + "common_text": { + "options": { + "unlabeled": "(Без мітки)", + "create_new_profile": "Створити новий профіль", + "remove_label": "Видалити мітку", + "no_reference_cycle": "(Без опорного циклу)", + "all_device_types": "Усі типи пристроїв", + "current_suffix": "(поточний)", + "editor_select_info": "Виберіть 1 цикл, щоб розділити, або 2+ цикли, щоб об’єднати.", + "editor_select_info_split": "Виберіть 1 цикл для поділу.", + "editor_select_info_merge": "Виберіть 2+ цикли для об’єднання.", + "editor_select_info_delete": "Виберіть 1+ циклів для видалення.", + "no_cycles_recorded": "Циклів ще не зареєстровано.", + "profile_summary_line": "- **{name}**: {count} циклів, {avg} хв. сер.", + "no_profiles_created": "Профілі ще не створені.", + "phase_preview": "Попередній перегляд фази", + "phase_preview_no_curve": "Середня профільна крива поки що недоступна. Запустити/позначити більше циклів для цього профілю.", + "average_power_curve": "Крива середньої потужності", + "unit_min": "хв", + "profile_option_fmt": "{name} ({count} циклів, ~{duration} м. в середньому)", + "profile_option_short_fmt": "{name} ({count} циклів, ~{duration} хв)", + "deleted_cycles_from_profile": "Видалено {count} циклів із {profile}.", + "not_enough_data_graph": "Недостатньо даних для створення графіка.", + "no_profiles_found": "Профілі не знайдено.", + "cycle_info_fmt": "Цикл: {start}, {duration} хв, поточна: {label}", + "top_candidates_header": "### Кращі кандидати", + "tbl_profile": "Профіль", + "tbl_confidence": "Впевненість", + "tbl_correlation": "Кореляція", + "tbl_duration_match": "Збіг тривалості", + "detected_profile": "Виявлений профіль", + "estimated_duration": "Розрахункова тривалість", + "actual_duration": "Фактична тривалість", + "choose_action": "__Оберіть дію нижче:__", + "tbl_count": "Кількість", + "tbl_avg": "Середнє", + "tbl_min": "Хв", + "tbl_max": "Макс", + "tbl_energy_avg": "Енергія (середнє)", + "tbl_energy_total": "Енергія (загальна)", + "tbl_consistency": "Стабільність", + "tbl_last_run": "Останній запуск", + "graph_legend_title": "Легенда графіка", + "graph_legend_body": "Синя смуга позначає мінімальний і максимальний діапазон споживаної потужності. Лінія показує середню криву потужності.", + "recording_preview": "Попередній перегляд запису", + "trim_start": "Початок обрізки", + "trim_end": "Обрізати кінець", + "split_preview_title": "Розділений попередній перегляд", + "split_preview_found_fmt": "Знайдено {count} сегментів.", + "split_preview_confirm_fmt": "Натисніть Підтвердити, щоб розділити цей цикл на {count} окремих циклів.", + "merge_preview_title": "Попередній перегляд злиття", + "merge_preview_joining_fmt": "Приєднання до {count} циклів. Прогалини будуть заповнені показаннями 0 Вт.", + "editor_delete_preview_title": "Попередній перегляд видалення", + "editor_delete_preview_intro": "Вибрані цикли буде остаточно видалено:", + "editor_delete_preview_confirm": "Натисніть Підтвердити, щоб остаточно видалити ці записи циклів.", + "no_power_preview": "*Немає даних про потужність для попереднього перегляду.*", + "profile_comparison": "Порівняння профілю", + "actual_cycle_label": "Цей цикл (фактично)", + "storage_usage_header": "Використання сховища", + "storage_file_size": "Розмір файлу", + "storage_cycles": "Цикли", + "storage_profiles": "Профілі", + "storage_debug_traces": "Сліди налагодження", + "overview_suffix": "Огляд", + "phase_preview_for_profile": "Попередній перегляд фази для профілю", + "current_ranges_header": "Діапазон струму", + "assign_phases_help": "Використовуйте наведені нижче дії, щоб додавати, редагувати, видаляти або зберігати діапазони.", + "profile_summary_header": "Резюме профілю", + "recent_cycles_header": "Останні цикли", + "trim_cycle_preview_title": "Попередній перегляд циклічного обрізання", + "trim_cycle_preview_no_data": "Немає даних про потужність для цього циклу.", + "trim_cycle_preview_kept_suffix": "збережено", + "table_program": "Програма", + "table_when": "Коли", + "table_length": "Тривалість", + "table_match": "Збіг", + "table_profile": "Профіль", + "table_cycles": "Цикли", + "table_avg_length": "Сер. тривалість", + "table_last_run": "Останній запуск", + "table_avg_energy": "Сер. енергія", + "table_detected_program": "Виявлена програма", + "table_confidence": "Впевненість", + "table_reported": "Повідомлено", + "phase_builtin_suffix": "(вбудований)", + "phase_none_available": "Немає доступних фаз.", + "settings_deprecation_warning": "⚠️ **Застарілий тип пристрою:** {device_type} планується видалити в майбутньому випуску. Конвеєр відповідності WashData не дає надійних результатів для цього класу приладів. Ваша інтеграція продовжує працювати протягом періоду припинення підтримки; щоб вимкнути це попередження, перемкніть **Тип пристрою** нижче на один із підтримуваних типів (пральна машина, сушильна машина, комбінована пральна машина з сушаркою, посудомийна машина, фритюрниця, хлібопічка або насос) або на **Інше (розширений)**, якщо ваш прилад не відповідає жодному з підтримуваних типів. **Інші (розширені)** навмисно надає загальні параметри за замовчуванням, які не налаштовані для жодного конкретного пристрою, тому вам потрібно буде самостійно налаштувати порогові значення, тайм-аути та відповідні параметри; усі наявні налаштування зберігаються під час перемикання.", + "phase_other_device_types": "Інші типи пристроїв:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Розділити цикл (знайти прогалини)", + "merge": "Цикли злиття (об’єднання фрагментів)", + "delete": "Видалити цикл(и)" + } + }, + "split_mode": { + "options": { + "auto": "Автоматично виявляти паузи простою", + "manual": "Ручні часові позначки" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Обслуговування: повторна обробка та оптимізація даних", + "clear_debug_data": "Очистити дані налагодження (звільнити місце)", + "wipe_history": "Очистити ВСІ дані цього пристрою (безповоротно)", + "export_import": "Експорт/імпорт JSON з налаштуваннями (копіювати/вставити)" + } + }, + "export_import_mode": { + "options": { + "export": "Експортувати всі дані", + "import": "Імпорт/об’єднання даних" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Автоматичне позначення старих циклів", + "select_cycle_to_label": "Позначити конкретний цикл", + "select_cycle_to_delete": "Видалити цикл", + "interactive_editor": "Інтерактивний редактор злиття/розділу", + "trim_cycle": "Обрізати дані циклу" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Створити новий профіль", + "edit_profile": "Редагувати/перейменувати профіль", + "delete_profile": "Видалити профіль", + "profile_stats": "Статистика профілю", + "cleanup_profile": "Очистити історію - побудувати графік і видалити", + "assign_phases": "Призначити діапазони фаз" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Створити нову фазу", + "edit_custom_phase": "Редагувати фазу", + "delete_custom_phase": "Видалити фазу" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Додати діапазон фаз", + "edit_range": "Редагувати діапазон фаз", + "delete_range": "Видалити діапазон фаз", + "clear_ranges": "Очистити всі діапазони", + "auto_detect_ranges": "Автоматичне визначення фаз", + "save_ranges": "Зберегти та повернути" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Оновити статус", + "stop_recording": "Зупинити запис (зберегти та обробити)", + "start_recording": "Почати новий запис", + "process_recording": "Обробити останній запис (обрізати та зберегти)", + "discard_recording": "Відкинути останній запис" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Створити новий профіль", + "existing_profile": "Додати до наявного профілю", + "discard": "Скасувати запис" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Підтвердити - правильне виявлення", + "correct": "Правильно - Виберіть програму/тривалість", + "ignore": "Ігнорувати - хибний позитивний/шумовий цикл", + "delete": "Видалити - видалити з історії" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Назвіть і застосуйте фази", + "cancel": "Повернутися без змін" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Встановити початкову точку", + "set_end": "Встановити кінцеву точку", + "reset": "Скинути до повної тривалості", + "apply": "Застосувати Trim", + "cancel": "Скасувати" + } + }, + "device_type": { + "options": { + "washing_machine": "пральна машина", + "dryer": "Сушарка", + "washer_dryer": "Пральна машина Combo", + "dishwasher": "Посудомийна машина", + "coffee_machine": "Кавоварка (застаріла)", + "ev": "Електромобіль (застаріло)", + "air_fryer": "Аерофритюрниця", + "heat_pump": "Тепловий насос (застаріло)", + "bread_maker": "Хлібопічка", + "pump": "Насос / Відстійний насос", + "oven": "Піч (застаріла)", + "other": "Інше (розширений)" + } + } + }, + "services": { + "label_cycle": { + "name": "Позначити цикл", + "description": "Призначте існуючий профіль минулому циклу або видаліть мітку.", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData для маркування." + }, + "cycle_id": { + "name": "Ідентифікатор циклу", + "description": "Ідентифікатор циклу для позначення." + }, + "profile_name": { + "name": "Ім'я профілю", + "description": "Ім’я наявного профілю (створити профілі в меню Керування профілями). Залиште поле порожнім, щоб видалити мітку." + } + } + }, + "create_profile": { + "name": "Створити профіль", + "description": "Створіть новий профіль (окремий або на основі контрольного циклу).", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData." + }, + "profile_name": { + "name": "Ім'я профілю", + "description": "Назва для нового профілю (наприклад, «Heavy Duty», «Delicates»)." + }, + "reference_cycle_id": { + "name": "Ідентифікатор еталонного циклу", + "description": "Додатковий ідентифікатор циклу, на якому базується цей профіль." + } + } + }, + "delete_profile": { + "name": "Видалити профіль", + "description": "Видалити профіль і за бажанням скасувати мітки циклів, використовуючи його.", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData." + }, + "profile_name": { + "name": "Ім'я профілю", + "description": "Профіль для видалення." + }, + "unlabel_cycles": { + "name": "Зняти позначки циклів", + "description": "Видаліть мітку профілю з циклів, які використовують цей профіль." + } + } + }, + "auto_label_cycles": { + "name": "Автоматичне позначення старих циклів", + "description": "Заднім числом позначайте немарковані цикли за допомогою відповідності профілів.", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData." + }, + "confidence_threshold": { + "name": "Поріг впевненості", + "description": "Мінімальна впевненість збігу (0,50-0,95) для застосування міток." + } + } + }, + "export_config": { + "name": "Експорт конфігурації", + "description": "Експортуйте профілі, цикли та налаштування цього пристрою у файл JSON (для кожного пристрою).", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData для експорту." + }, + "path": { + "name": "Шлях", + "description": "Додатковий абсолютний шлях до файлу для запису (за замовчуванням /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Імпорт конфігурації", + "description": "Імпортуйте профілі, цикли та налаштування для цього пристрою з файлу експорту JSON (параметри можуть бути перезаписані).", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData для імпорту." + }, + "path": { + "name": "Шлях", + "description": "Абсолютний шлях до експортованого файлу JSON для імпорту." + } + } + }, + "submit_cycle_feedback": { + "name": "Надіслати відгук про цикл", + "description": "Підтвердьте або виправте автоматично визначену програму після завершення циклу. Укажіть `entry_id` (розширений) або `device_id` (рекомендовано).", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData (рекомендовано замість entry_id)." + }, + "entry_id": { + "name": "ID запису", + "description": "Ідентифікатор запису конфігурації для пристрою (альтернатива device_id)." + }, + "cycle_id": { + "name": "Ідентифікатор циклу", + "description": "Ідентифікатор циклу, показаний у сповіщенні/журналах відгуку." + }, + "user_confirmed": { + "name": "Підтвердити виявлену програму", + "description": "Установіть true, якщо виявлена ​​програма правильна." + }, + "corrected_profile": { + "name": "Виправлений профіль", + "description": "Якщо не підтверджено, укажіть правильну назву профілю/програми." + }, + "corrected_duration": { + "name": "Виправлена ​​тривалість (секунди)", + "description": "Додаткова виправлена ​​тривалість у секундах." + }, + "notes": { + "name": "Примітки", + "description": "Додаткові примітки щодо цього циклу." + } + } + }, + "record_start": { + "name": "Початок циклу запису", + "description": "Почніть запис чистого циклу вручну (обійде всю відповідну логіку).", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData для запису." + } + } + }, + "record_stop": { + "name": "Зупинка циклу запису", + "description": "Зупиніть ручний запис.", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData для зупинки запису." + } + } + }, + "trim_cycle": { + "name": "Цикл обрізки", + "description": "Обріжте дані про потужність минулого циклу до певного часового вікна.", + "fields": { + "device_id": { + "name": "Пристрій", + "description": "Пристрій WashData." + }, + "cycle_id": { + "name": "Ідентифікатор циклу", + "description": "Ідентифікатор циклу, який потрібно обрізати." + }, + "trim_start_s": { + "name": "Початок обрізки (секунди)", + "description": "Зберігайте дані з цього зміщення за лічені секунди. За замовчуванням 0." + }, + "trim_end_s": { + "name": "Кінець обрізання (секунди)", + "description": "Зберігайте дані до цього зміщення за лічені секунди. За замовчуванням = повна тривалість." + } + } + }, + "pause_cycle": { + "name": "Пауза циклу", + "description": "Призупинити активний цикл для пристрою WashData.", + "fields": { + "device_id": { + "name": "пристрій", + "description": "Пристрій WashData для призупинення." + } + } + }, + "resume_cycle": { + "name": "Цикл відновлення", + "description": "Відновити призупинений цикл для пристрою WashData.", + "fields": { + "device_id": { + "name": "пристрій", + "description": "Пристрій WashData для відновлення." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Працює" + }, + "match_ambiguity": { + "name": "Неоднозначність відповідності" + } + }, + "sensor": { + "washer_state": { + "name": "Стан", + "state": { + "off": "Вимкнено", + "idle": "Бездіяльність", + "starting": "Запуск", + "running": "Працює", + "paused": "Призупинено", + "user_paused": "Призупинено користувачем", + "ending": "Закінчення", + "finished": "Готово", + "anti_wrinkle": "Проти зморшок", + "delay_wait": "Очікування початку", + "interrupted": "Перерваний", + "force_stopped": "Примусово зупинено", + "rinse": "Полоскання", + "unknown": "Невідомо", + "clean": "чистий" + } + }, + "washer_program": { + "name": "Програма" + }, + "time_remaining": { + "name": "Час, що залишився" + }, + "total_duration": { + "name": "Загальна тривалість" + }, + "cycle_progress": { + "name": "Прогрес" + }, + "current_power": { + "name": "Поточна потужність" + }, + "elapsed_time": { + "name": "Час, що минув" + }, + "debug_info": { + "name": "Інформація про налагодження" + }, + "match_confidence": { + "name": "Впевненість збігу" + }, + "top_candidates": { + "name": "Кращі кандидати", + "state": { + "none": "Жодного" + } + }, + "current_phase": { + "name": "Поточна фаза" + }, + "wash_phase": { + "name": "Фаза" + }, + "profile_cycle_count": { + "name": "Кількість профілю {profile_name}", + "unit_of_measurement": "циклів" + }, + "suggestions": { + "name": "Доступні пропоновані налаштування" + }, + "pump_runs_today": { + "name": "Насос працює (останні 24 години)" + }, + "cycle_count": { + "name": "Підрахунок циклу" + } + }, + "select": { + "program_select": { + "name": "Програма циклу", + "state": { + "auto_detect": "Автоматичне визначення" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Примусово завершити цикл" + }, + "pause_cycle": { + "name": "Пауза циклу" + }, + "resume_cycle": { + "name": "Цикл відновлення" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Потрібен дійсний device_id." + }, + "cycle_id_required": { + "message": "Потрібен дійсний cycle_id." + }, + "profile_name_required": { + "message": "Потрібне дійсне profile_name." + }, + "device_not_found": { + "message": "Пристрій не знайдено." + }, + "no_config_entry": { + "message": "Для пристрою не знайдено жодного запису конфігурації." + }, + "integration_not_loaded": { + "message": "Інтеграція не завантажена для цього пристрою." + }, + "cycle_not_found_or_no_power": { + "message": "Цикл не знайдено або немає даних про потужність." + }, + "trim_failed_empty_window": { + "message": "Помилка обрізання - цикл не знайдено, немає даних про живлення або кінцеве вікно порожнє." + }, + "trim_invalid_range": { + "message": "trim_end_s має бути більше, ніж trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/ur.json b/custom_components/ha_washdata/translations/ur.json new file mode 100644 index 0000000..3ed78ca --- /dev/null +++ b/custom_components/ha_washdata/translations/ur.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "واش ڈیٹا سیٹ اپ", + "description": "اپنی واشنگ مشین یا دیگر آلات کو ترتیب دیں۔\n\nپاور سینسر کی ضرورت ہے۔\n\n**اس کے بعد، آپ سے پوچھا جائے گا کہ کیا آپ اپنا پہلا پروفائل بنانا چاہتے ہیں۔**", + "data": { + "name": "ڈیوائس کا نام", + "device_type": "ڈیوائس کی قسم", + "power_sensor": "پاور سینسر", + "min_power": "کم از کم پاور تھریشولڈ (W)" + }, + "data_description": { + "name": "اس ڈیوائس کے لیے ایک دوستانہ نام (جیسے، 'واشنگ مشین'، 'ڈش واشر')۔", + "device_type": "یہ کس قسم کا آلہ ہے؟ درزی کا پتہ لگانے اور لیبل لگانے میں مدد کرتا ہے۔", + "power_sensor": "وہ سینسر ادارہ جو آپ کے سمارٹ پلگ سے حقیقی وقت میں بجلی کی کھپت (واٹ میں) کی اطلاع دیتا ہے۔", + "min_power": "اس حد سے اوپر پاور ریڈنگ (واٹ میں) اشارہ کرتی ہے کہ آلات چل رہا ہے۔ زیادہ تر آلات کے لیے 2W کے ساتھ شروع کریں۔" + } + }, + "first_profile": { + "title": "پہلا پروفائل بنائیں", + "description": "ایک **سائیکل** آپ کے آلے کا مکمل دوڑ ہے (مثلاً، لانڈری کا بوجھ)۔ ایک **پروفائل** ان سائیکلوں کو قسم کے لحاظ سے گروپ کرتا ہے (مثلاً 'کاٹن'، 'کوئیک واش')۔ اب پروفائل بنانے سے انضمام کے تخمینے کی مدت میں فوری مدد ملتی ہے۔\n\nاگر خود بخود پتہ نہیں چلتا ہے تو آپ ڈیوائس کنٹرولز میں اس پروفائل کو دستی طور پر منتخب کر سکتے ہیں۔\n\n**اس مرحلہ کو چھوڑنے کے لیے 'پروفائل کا نام' خالی چھوڑ دیں۔**", + "data": { + "profile_name": "پروفائل کا نام (اختیاری)", + "manual_duration": "تخمینی دورانیہ (منٹ)" + } + } + }, + "error": { + "cannot_connect": "رابطہ قائم کرنے میں ناکام", + "invalid_auth": "غلط تصدیق", + "unknown": "غیر متوقع غلطی" + }, + "abort": { + "already_configured": "ڈیوائس پہلے ہی کنفیگر ہے۔" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "سیکھنے کے تاثرات کا جائزہ لیں۔", + "settings": "ترتیبات", + "notifications": "اطلاعات", + "manage_cycles": "سائیکلوں کا نظم کریں۔", + "manage_profiles": "پروفائلز کا نظم کریں۔", + "manage_phase_catalog": "فیز کیٹلاگ کا نظم کریں۔", + "record_cycle": "ریکارڈ سائیکل (دستی)", + "diagnostics": "تشخیص اور دیکھ بھال" + } + }, + "apply_suggestions": { + "title": "تجویز کردہ اقدار کاپی کریں۔", + "description": "یہ موجودہ تجویز کردہ اقدار کو آپ کی محفوظ کردہ ترتیبات میں کاپی کر دے گا۔ یہ ایک بار کی کارروائی ہے اور خودکار طور پر اوور رائٹ کو فعال نہیں کرتی ہے۔\n\nکاپی کرنے کے لیے تجویز کردہ اقدار:\n{suggested}", + "data": { + "confirm": "کاپی ایکشن کی تصدیق کریں۔" + } + }, + "apply_suggestions_confirm": { + "title": "تجویز کردہ تبدیلیوں کا جائزہ لیں۔", + "description": "WashData کو **{pending_count}** لاگو کرنے کے لیے تجویز کردہ قدریں ملیں۔\n\nتبدیلیاں:\n{changes}\n\n✅ نیچے دیے گئے باکس کو چیک کریں اور ان تبدیلیوں کو فوری طور پر لاگو کرنے اور محفوظ کرنے کے لیے **جمع کرائیں** پر کلک کریں۔\n❌ اسے غیر نشان زد رہنے دیں اور منسوخ کرنے اور واپس جانے کے لیے **جمع کرائیں** پر کلک کریں۔", + "data": { + "confirm_apply_suggestions": "ان تبدیلیوں کو لاگو کریں اور محفوظ کریں۔" + } + }, + "settings": { + "title": "ترتیبات", + "description": "{deprecation_warning}پتہ لگانے کی دہلیز، سیکھنے، اور جدید طرز عمل کو ٹیون کریں۔ ڈیفالٹس زیادہ تر سیٹ اپس کے لیے اچھی طرح کام کرتے ہیں۔\n\nفی الحال دستیاب تجویز کردہ ترتیبات: {suggestions_count}.\nاگر یہ 0 سے زیادہ ہو تو اعلی درجے کی ترتیبات کھولیں اور سفارشات کا جائزہ لینے کے لیے 'تجویز کردہ اقدار کا اطلاق کریں۔' استعمال کریں۔", + "data": { + "apply_suggestions": "تجویز کردہ اقدار کا اطلاق کریں۔", + "device_type": "ڈیوائس کی قسم", + "power_sensor": "پاور سینسر ہستی", + "min_power": "کم از کم پاور (W)", + "off_delay": "سائیکل کے اختتام میں تاخیر", + "notify_actions": "اطلاعی کارروائیاں", + "notify_people": "جب تک یہ لوگ گھر نہ ہوں ڈیلیوری میں تاخیر کریں۔", + "notify_only_when_home": "نوٹیفیکیشن میں اس وقت تک تاخیر کریں جب تک کہ منتخب شخص گھر نہ ہو۔", + "notify_fire_events": "آٹومیشن ایونٹس فعال کریں", + "notify_start_services": "سائیکل شروع - اطلاع کے اہداف", + "notify_finish_services": "سائیکل ختم - اطلاع کے اہداف", + "notify_live_services": "لائیو پیش رفت - اطلاع کے اہداف", + "notify_before_end_minutes": "پیشگی تکمیل کی اطلاع (ختم ہونے سے چند منٹ پہلے)", + "notify_title": "اطلاع کا عنوان", + "notify_icon": "اطلاع کا آئیکن", + "notify_start_message": "میسج فارمیٹ شروع کریں۔", + "notify_finish_message": "پیغام کی شکل ختم کریں۔", + "notify_pre_complete_message": "پری تکمیلی پیغام کی شکل", + "show_advanced": "اعلی درجے کی ترتیبات میں ترمیم کریں (اگلا مرحلہ)" + }, + "data_description": { + "apply_suggestions": "سیکھنے کے الگورتھم کے ذریعہ تجویز کردہ اقدار کو فارم میں کاپی کرنے کے لیے اس باکس کو چیک کریں۔ محفوظ کرنے سے پہلے جائزہ لیں۔\n\nتجویز کردہ (سیکھنے سے):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "آلات کی قسم منتخب کریں (واشنگ مشین، ڈرائر، ڈش واشر، کافی مشین، ای وی)۔ یہ ٹیگ بہتر تنظیم کے لیے ہر سائیکل کے ساتھ محفوظ کیا جاتا ہے۔", + "power_sensor": "ریئل ٹائم پاور کی اطلاع دینے والا سینسر ادارہ (واٹ میں)۔ آپ اسے کسی بھی وقت تاریخی ڈیٹا کو کھونے کے بغیر تبدیل کر سکتے ہیں - اگر آپ سمارٹ پلگ تبدیل کرتے ہیں تو مفید ہے۔", + "min_power": "اس حد سے اوپر پاور ریڈنگ (واٹ میں) اشارہ کرتی ہے کہ آلات چل رہا ہے۔ زیادہ تر آلات کے لیے 2W کے ساتھ شروع کریں۔", + "off_delay": "مکمل ہونے کے نشان کے لیے ہموار پاور کو سٹاپ کی حد سے نیچے رہنا چاہیے۔ ڈیفالٹ: 120s۔", + "notify_actions": "اطلاعات (اسکرپٹس، نوٹیفائی، ٹی ٹی ایس، وغیرہ) کے لیے چلانے کے لیے اختیاری ہوم اسسٹنٹ ایکشن سیکوئنس۔ اگر سیٹ ہو تو فال بیک مطلع سروس سے پہلے کارروائیاں چلتی ہیں۔", + "notify_people": "موجودگی گیٹنگ کے لیے استعمال ہونے والے افراد کے ادارے۔ فعال ہونے پر، اطلاع کی ترسیل میں اس وقت تک تاخیر ہوتی ہے جب تک کہ کم از کم ایک منتخب شخص گھر نہ ہو۔", + "notify_only_when_home": "اگر فعال ہو تو، اطلاعات میں اس وقت تک تاخیر کریں جب تک کہ کم از کم ایک منتخب شخص گھر نہ ہو۔", + "notify_fire_events": "فعال ہونے پر، آٹومیشن چلانے کے لیے سائیکل کے آغاز اور اختتام (ha_washdata_cycle_started اور ha_washdata_cycle_ended) کے ہوم اسسٹنٹ ایونٹس کو فائر کریں۔", + "notify_start_services": "ایک سائیکل شروع ہونے پر الرٹ کرنے کے لیے ایک یا زیادہ سروسز کو مطلع کریں۔ شروع کی اطلاعات کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_finish_services": "ایک یا ایک سے زیادہ سروسز کو مطلع کرنے کے لیے جب کوئی سائیکل مکمل ہو جاتا ہے یا مکمل ہونے کے قریب ہوتا ہے۔ ختم ہونے والی اطلاعات کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_live_services": "سائیکل کے چلنے کے دوران لائیو پروگریس اپ ڈیٹس حاصل کرنے کے لیے ایک یا زیادہ سروسز کو مطلع کریں (صرف موبائل ساتھی ایپ)۔ لائیو اپ ڈیٹس کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_before_end_minutes": "ایک اطلاع کو متحرک کرنے کے لیے سائیکل کے متوقع اختتام سے چند منٹ پہلے۔ غیر فعال کرنے کے لیے 0 پر سیٹ کریں۔ ڈیفالٹ: 0۔", + "notify_title": "اطلاعات کے لیے حسب ضرورت عنوان۔ {device} پلیس ہولڈر کو سپورٹ کرتا ہے۔", + "notify_icon": "اطلاعات کے لیے استعمال کرنے کے لیے آئیکن (جیسے mdi:washing-machine)۔ آئیکن کے بغیر بھیجنے کے لیے خالی چھوڑ دیں۔", + "notify_start_message": "ایک سائیکل شروع ہونے پر پیغام بھیجا جاتا ہے۔ {device} پلیس ہولڈر کو سپورٹ کرتا ہے۔", + "notify_finish_message": "ایک سائیکل ختم ہونے پر پیغام بھیجا گیا۔ {device}، {duration}، {program}, {energy_kwh}, {cost} پلیس ہولڈرز کو سپورٹ کرتا ہے۔", + "notify_pre_complete_message": "سائیکل کے چلنے کے دوران اعادی لائیو پروگریس اپ ڈیٹس کا متن۔ {device}، {minutes}، {program} پلیس ہولڈرز کو سپورٹ کرتا ہے۔" + } + }, + "notifications": { + "title": "اطلاعات", + "description": "نوٹیفکیشن چینلز، ٹیمپلیٹس، اور اختیاری لائیو موبائل پروگریس اپ ڈیٹس کو ترتیب دیں۔", + "data": { + "notify_actions": "اطلاعی کارروائیاں", + "notify_people": "جب تک یہ لوگ گھر نہ ہوں ڈیلیوری میں تاخیر کریں۔", + "notify_only_when_home": "نوٹیفیکیشن میں اس وقت تک تاخیر کریں جب تک کہ منتخب شخص گھر نہ ہو۔", + "notify_fire_events": "آٹومیشن ایونٹس فعال کریں", + "notify_start_services": "سائیکل شروع - اطلاع کے اہداف", + "notify_finish_services": "سائیکل ختم - اطلاع کے اہداف", + "notify_live_services": "لائیو پیش رفت - اطلاع کے اہداف", + "notify_before_end_minutes": "پیشگی تکمیل کی اطلاع (ختم ہونے سے چند منٹ پہلے)", + "notify_live_interval_seconds": "لائیو اپ ڈیٹ کا وقفہ (سیکنڈ)", + "notify_live_overrun_percent": "لائیو اپ ڈیٹ اووررن الاؤنس (%)", + "notify_live_chronometer": "کرونومیٹر کاؤنٹ ڈاؤن ٹائمر", + "notify_title": "اطلاع کا عنوان", + "notify_icon": "اطلاع کا آئیکن", + "notify_start_message": "میسج فارمیٹ شروع کریں۔", + "notify_finish_message": "پیغام کی شکل ختم کریں۔", + "notify_pre_complete_message": "پری تکمیلی پیغام کی شکل", + "energy_price_entity": "توانائی کی قیمت - ہستی (اختیاری)", + "energy_price_static": "توانائی کی قیمت - جامد قدر (اختیاری)", + "go_back": "محفوظ کیے بغیر واپس جائیں", + "notify_reminder_message": "یاد دہانی کے پیغام کی شکل", + "notify_timeout_seconds": "(سیکنڈ) کے بعد خودکار طور پر برخاست کریں", + "notify_channel": "اطلاعی چینل (اسٹیٹس/لائیو/ریمائنڈر)", + "notify_finish_channel": "مکمل اطلاعی چینل" + }, + "data_description": { + "go_back": "اسے منتخب کریں اور اوپر کی کسی بھی تبدیلی کو ترک کرکے مرکزی مینو میں واپس جانے کے لیے Submit پر کلک کریں۔", + "notify_actions": "اطلاعات (اسکرپٹس، نوٹیفائی، ٹی ٹی ایس، وغیرہ) کے لیے چلانے کے لیے اختیاری ہوم اسسٹنٹ ایکشن سیکوئنس۔ اگر سیٹ ہو تو فال بیک مطلع سروس سے پہلے کارروائیاں چلتی ہیں۔", + "notify_people": "موجودگی گیٹنگ کے لیے استعمال ہونے والے افراد کے ادارے۔ فعال ہونے پر، اطلاع کی ترسیل میں اس وقت تک تاخیر ہوتی ہے جب تک کہ کم از کم ایک منتخب شخص گھر نہ ہو۔", + "notify_only_when_home": "اگر فعال ہو تو، اطلاعات میں اس وقت تک تاخیر کریں جب تک کہ کم از کم ایک منتخب شخص گھر نہ ہو۔", + "notify_fire_events": "فعال ہونے پر، آٹومیشن چلانے کے لیے سائیکل کے آغاز اور اختتام (ha_washdata_cycle_started اور ha_washdata_cycle_ended) کے ہوم اسسٹنٹ ایونٹس کو فائر کریں۔", + "notify_start_services": "ایک سائیکل شروع ہونے پر الرٹ کرنے کے لیے ایک یا زیادہ مطلع سروسز (جیسے notify.mobile_app_my_phone)۔ شروع کی اطلاعات کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_finish_services": "ایک یا ایک سے زیادہ سروسز کو مطلع کرنے کے لیے جب کوئی سائیکل مکمل ہو جاتا ہے یا مکمل ہونے کے قریب ہوتا ہے۔ ختم ہونے والی اطلاعات کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_live_services": "سائیکل کے چلنے کے دوران لائیو پروگریس اپ ڈیٹس حاصل کرنے کے لیے ایک یا زیادہ سروسز کو مطلع کریں (صرف موبائل ساتھی ایپ)۔ لائیو اپ ڈیٹس کو چھوڑنے کے لیے خالی چھوڑ دیں۔", + "notify_before_end_minutes": "ایک اطلاع کو متحرک کرنے کے لیے سائیکل کے متوقع اختتام سے چند منٹ پہلے۔ غیر فعال کرنے کے لیے 0 پر سیٹ کریں۔ ڈیفالٹ: 0۔", + "notify_live_interval_seconds": "چلتے وقت کتنی بار پروگریس اپ ڈیٹس بھیجے جاتے ہیں۔ نچلی اقدار زیادہ جوابدہ ہیں لیکن زیادہ اطلاعات استعمال کرتی ہیں۔ کم از کم 30 سیکنڈ لاگو کیا جاتا ہے - 30 سیکنڈ سے نیچے کی قدریں خود بخود 30 سیکنڈ تک مکمل ہوجاتی ہیں۔", + "notify_live_overrun_percent": "حفاظتی مارجن لمبے/زیادہ چلنے والے سائیکلوں کے لیے تخمینہ شدہ اپ ڈیٹ کی گنتی سے زیادہ ہے (مثال کے طور پر 20% 1.2x تخمینہ شدہ اپ ڈیٹس کی اجازت دیتا ہے)۔", + "notify_live_chronometer": "فعال ہونے پر، ہر لائیو اپ ڈیٹ میں تخمینہ ختم ہونے کے وقت کے لیے ایک کرونومیٹر کاؤنٹ ڈاؤن شامل ہوتا ہے۔ نوٹیفکیشن ٹائمر اپ ڈیٹس کے درمیان ڈیوائس پر خود بخود ٹک ڈاون ہوجاتا ہے (صرف اینڈرائیڈ ساتھی ایپ)۔", + "notify_title": "اطلاعات کے لیے حسب ضرورت عنوان۔ {device} پلیس ہولڈر کو سپورٹ کرتا ہے۔", + "notify_icon": "اطلاعات کے لیے استعمال کرنے کے لیے آئیکن (جیسے mdi:washing-machine)۔ آئیکن کے بغیر بھیجنے کے لیے خالی چھوڑ دیں۔", + "notify_start_message": "ایک سائیکل شروع ہونے پر پیغام بھیجا جاتا ہے۔ {device} پلیس ہولڈر کو سپورٹ کرتا ہے۔", + "notify_finish_message": "ایک سائیکل ختم ہونے پر پیغام بھیجا گیا۔ {device}، {duration}، {program}, {energy_kwh}, {cost} پلیس ہولڈرز کو سپورٹ کرتا ہے۔", + "energy_price_entity": "ایک عددی ہستی کا انتخاب کریں جس میں بجلی کی موجودہ قیمت ہو (جیسے sensor.electricity_price، input_number.energy_rate)۔ سیٹ ہونے پر، {cost} پلیس ہولڈر کو فعال کرتا ہے۔ جامد قدر پر فوقیت لیتا ہے اگر دونوں کنفیگر ہوں۔", + "energy_price_static": "فی کلو واٹ بجلی کی مقررہ قیمت۔ سیٹ ہونے پر، {cost} پلیس ہولڈر کو فعال کرتا ہے۔ نظر انداز کیا جاتا ہے اگر کوئی ہستی اوپر کنفیگر کی گئی ہو۔ پیغام کے سانچے میں اپنی کرنسی کی علامت شامل کریں، جیسے {cost} €۔", + "notify_reminder_message": "ایک بار کی یاد دہانی کا متن تخمینہ ختم ہونے سے پہلے ترتیب شدہ تعداد کو بھیج دیا گیا۔ اوپر بار بار چلنے والی لائیو اپ ڈیٹس سے الگ۔ {device}، {minutes}، {program} پلیس ہولڈرز کو سپورٹ کرتا ہے۔", + "notify_timeout_seconds": "اس کئی سیکنڈ کے بعد خودکار طور پر اطلاعات کو برخاست کر دیں (ساتھی ایپ 'ٹائم آؤٹ' کے بطور فارورڈ کیا گیا)۔ انہیں برخاست ہونے تک رکھنے کے لیے 0 پر سیٹ کریں۔ ڈیفالٹ: 0۔", + "notify_channel": "اسٹیٹس، لائیو پیشرفت، اور یاد دہانی کی اطلاعات کے لیے Android ساتھی ایپ نوٹیفکیشن چینل۔ چینل کا نام پہلی بار استعمال کرنے پر ساتھی ایپ میں چینل کی آواز اور اہمیت کو ترتیب دیا جاتا ہے - واش ڈیٹا صرف چینل کا نام سیٹ کرتا ہے۔ ایپ ڈیفالٹ کے لیے خالی چھوڑ دیں۔", + "notify_finish_channel": "تیار شدہ الرٹ کے لیے علیحدہ Android چینل (جسے یاد دہانی اور لانڈری کے انتظار میں استعمال کیا جاتا ہے) تاکہ اس کی اپنی آواز ہو۔ اوپر والے چینل کو دوبارہ استعمال کرنے کے لیے خالی چھوڑ دیں۔", + "notify_pre_complete_message": "سائیکل کے چلنے کے دوران اعادی لائیو پروگریس اپ ڈیٹس کا متن۔ {device}، {minutes}، {program} پلیس ہولڈرز کو سپورٹ کرتا ہے۔" + } + }, + "advanced_settings": { + "title": "اعلی درجے کی ترتیبات", + "description": "پتہ لگانے کی دہلیز، سیکھنے، اور جدید طرز عمل کو ٹیون کریں۔ ڈیفالٹس زیادہ تر سیٹ اپس کے لیے اچھی طرح کام کرتے ہیں۔", + "sections": { + "suggestions_section": { + "name": "تجویز کردہ ترتیبات", + "description": "{suggestions_count} سیکھی گئی تجاویز دستیاب ہیں۔ محفوظ کرنے سے پہلے مجوزہ تبدیلیوں کا جائزہ لینے کے لیے 'تجویز کردہ اقدار کا اطلاق کریں' کو منتخب کریں۔", + "data": { + "apply_suggestions": "تجویز کردہ اقدار کا اطلاق کریں۔" + }, + "data_description": { + "apply_suggestions": "سیکھنے کے الگورتھم سے تجویز کردہ اقدار کے لیے جائزہ لینے کا مرحلہ کھولنے کے لیے اس باکس کو چیک کریں۔ کچھ بھی خود بخود محفوظ نہیں ہوتا ہے۔\n\nتجویز کردہ (سیکھنے سے):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "پتہ لگانے اور پاور کی دہلیز", + "description": "آلہ کب آن یا آف سمجھا جائے، انرجی گیٹس، اور حالت کی تبدیلیوں کا وقت۔", + "data": { + "start_duration_threshold": "ڈیباؤنس کا دورانیہ شروع کریں", + "start_energy_threshold": "اسٹارٹ انرجی گیٹ (Wh)", + "completion_min_seconds": "تکمیل کا کم از کم رن ٹائم (سیکنڈ)", + "start_threshold_w": "شروع کی حد (W)", + "stop_threshold_w": "اسٹاپ تھریشولڈ (W)", + "end_energy_threshold": "اینڈ انرجی گیٹ (Wh)", + "running_dead_zone": "رننگ ڈیڈ زون (سیکنڈ)", + "end_repeat_count": "دوبارہ گنتی ختم کریں۔", + "min_off_gap": "سائیکلوں کے درمیان کم سے کم فرق", + "sampling_interval": "نمونے لینے کا وقفہ (s)" + }, + "data_description": { + "start_duration_threshold": "اس دورانیے (سیکنڈ) سے کم پاور سپائکس کو نظر انداز کریں۔ اصلی سائیکل شروع ہونے سے پہلے بوٹ اسپائکس (جیسے 2s کے لیے 60W) کو فلٹر کریں۔ ڈیفالٹ: 5s۔", + "start_energy_threshold": "سائیکل کو START مرحلے کے دوران کم از کم اتنی توانائی (Wh) جمع کرنی چاہیے تاکہ تصدیق کی جائے۔ ڈیفالٹ: 0.005 Wh.", + "completion_min_seconds": "اس سے چھوٹے سائیکلوں کو 'رکاوٹ' کے طور پر نشان زد کیا جائے گا چاہے وہ قدرتی طور پر ختم ہوں۔ ڈیفالٹ: 600s۔", + "start_threshold_w": "ایک سائیکل شروع کرنے کے لیے طاقت کو اس حد سے اوپر اٹھنا چاہیے۔ اپنی اسٹینڈ بائی پاور سے اونچا سیٹ کریں۔ ڈیفالٹ: min_power + 1 W", + "stop_threshold_w": "ایک سائیکل کو ختم کرنے کے لیے طاقت کو اس حد سے نیچے آنا چاہیے۔ جھوٹے سروں کو متحرک کرنے والے شور سے بچنے کے لیے صفر سے بالکل اوپر سیٹ کریں۔ ڈیفالٹ: 0.6 * min_power۔", + "end_energy_threshold": "سائیکل صرف اس صورت میں ختم ہوتا ہے جب آخری آف ڈیلے ونڈو میں توانائی اس قدر (Wh) سے کم ہو۔ اگر پاور صفر کے قریب اتار چڑھاؤ آتی ہے تو جھوٹے سروں کو روکتا ہے۔ ڈیفالٹ: 0.05 Wh.", + "running_dead_zone": "سائیکل شروع ہونے کے بعد، ابتدائی اسپن اپ مرحلے کے دوران غلط اختتام کی نشاندہی کو روکنے کے لیے اس کئی سیکنڈ تک پاور ڈپس کو نظر انداز کریں۔ غیر فعال کرنے کے لیے 0 پر سیٹ کریں۔ ڈیفالٹ: 0s", + "end_repeat_count": "سائیکل ختم کرنے سے پہلے اختتامی حالت کی تعداد (آف_ڈیلے کے لیے حد سے نیچے کی طاقت) کو لگاتار پورا کیا جانا چاہیے۔ اعلی اقدار توقف کے دوران غلط سروں کو روکتی ہیں۔ طے شدہ: 1۔", + "min_off_gap": "اگر کوئی سائیکل پچھلے ایک ختم ہونے کے اتنے سیکنڈ کے اندر شروع ہوتا ہے، تو وہ ایک ہی چکر میں ضم ہو جائیں گے۔", + "sampling_interval": "سیکنڈ میں نمونے لینے کا کم از کم وقفہ۔ اس شرح سے زیادہ تیز اپ ڈیٹس کو نظر انداز کر دیا جائے گا۔ پہلے سے طے شدہ: 30s (2s واشنگ مشینوں، واشر ڈرائر، اور ڈش واشرز کے لیے)۔" + } + }, + "matching_section": { + "name": "پروفائل میچنگ اور سیکھنا", + "description": "WashData چلتے ہوئے سائیکلوں کو سیکھی ہوئی پروفائلز کے ساتھ کتنی شدت سے ملاتا ہے اور کب تاثرات طلب کرتا ہے۔", + "data": { + "profile_match_min_duration_ratio": "پروفائل میچ کم از کم دورانیہ کا تناسب (0.1-1.0)", + "profile_match_interval": "پروفائل میچ کا وقفہ (سیکنڈ)", + "profile_match_threshold": "پروفائل میچ کی حد", + "profile_unmatch_threshold": "پروفائل غیر مماثل حد", + "auto_label_confidence": "خودکار لیبل اعتماد (0-1)", + "learning_confidence": "تاثرات کی درخواست اعتماد (0-1)", + "suppress_feedback_notifications": "تاثرات کی اطلاعات کو دبا دیں۔", + "duration_tolerance": "دورانیہ رواداری (0-0.5)", + "smoothing_window": "ہموار کھڑکی (نمونے)", + "profile_duration_tolerance": "پروفائل میچ دورانیہ رواداری (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "پروفائل میچنگ کے لیے کم از کم دورانیہ کا تناسب۔ رننگ سائیکل کو مماثل ہونے کے لیے پروفائل کی مدت کا کم از کم اتنا حصہ ہونا چاہیے۔ نچلا = پہلے کا ملاپ۔ ڈیفالٹ: 0.50 (50%)۔", + "profile_match_interval": "ایک سائیکل کے دوران کتنی بار (سیکنڈوں میں) پروفائل میچنگ کی کوشش کرنی ہے۔ نچلی اقدار تیزی سے پتہ لگاتی ہیں لیکن زیادہ CPU استعمال کرتی ہیں۔ پہلے سے طے شدہ: 300s (5 منٹ)۔", + "profile_match_threshold": "پروفائل کو میچ کے طور پر سمجھنے کے لیے کم از کم DTW مماثلت کا اسکور (0.0–1.0) درکار ہے۔ اعلی = سخت مماثلت، کم غلط مثبت۔ ڈیفالٹ: 0.4۔", + "profile_unmatch_threshold": "DTW مماثلت کا اسکور (0.0–1.0) جس کے نیچے پہلے سے مماثل پروفائل کو مسترد کر دیا گیا ہے۔ ٹمٹماہٹ کو روکنے کے لیے میچ کی حد سے کم ہونا چاہیے۔ ڈیفالٹ: 0.35۔", + "auto_label_confidence": "مکمل ہونے پر اس اعتماد پر یا اس سے اوپر سائیکلوں کو خودکار طور پر لیبل کریں (0.0–1.0)۔", + "learning_confidence": "کسی ایونٹ (0.0–1.0) کے ذریعے صارف کی تصدیق کی درخواست کرنے کے لیے کم از کم اعتماد۔", + "suppress_feedback_notifications": "فعال ہونے پر، WashData اب بھی اندرونی طور پر تاثرات کو ٹریک کرے گا اور درخواست کرے گا لیکن آپ سے سائیکل کی تصدیق کرنے کے لیے مسلسل اطلاعات نہیں دکھائے گا۔ مفید ہے جب سائیکلوں کا ہمیشہ صحیح پتہ لگایا جاتا ہے اور آپ کو اشارے پریشان کن معلوم ہوتے ہیں۔", + "duration_tolerance": "ایک دوڑ کے دوران وقت کے بقایا تخمینوں کے لیے رواداری (0.0–0.5 0–50% کے مساوی ہے)۔", + "smoothing_window": "موونگ-ایوریج اسموتھنگ کے لیے استعمال ہونے والی حالیہ پاور ریڈنگز کی تعداد۔", + "profile_duration_tolerance": "کل دورانیے کے تغیر (0.0–0.5) کے لیے پروفائل مماثلت کے ذریعے رواداری کا استعمال کیا جاتا ہے۔ پہلے سے طے شدہ سلوک: ±25%۔" + } + }, + "timing_section": { + "name": "ٹائمنگ، مینٹیننس اور ڈیبگ", + "description": "واچ ڈاگ، پیش رفت ری سیٹ، خودکار مینٹیننس، اور ڈیبگ اینٹیٹیز کی نمائش۔", + "data": { + "watchdog_interval": "واچ ڈاگ وقفہ (سیکنڈ)", + "no_update_active_timeout": "کوئی اپ ڈیٹ ٹائم آؤٹ (s)", + "progress_reset_delay": "پیش رفت دوبارہ ترتیب دینے میں تاخیر (سیکنڈ)", + "auto_maintenance": "آٹو مینٹیننس کو فعال کریں۔", + "expose_debug_entities": "ڈیبگ اداروں کو بے نقاب کریں۔", + "save_debug_traces": "ڈیبگ ٹریس کو محفوظ کریں۔" + }, + "data_description": { + "watchdog_interval": "دوڑتے وقت واچ ڈاگ چیک کے درمیان سیکنڈ۔ ڈیفالٹ: 5s۔ انتباہ: یقینی بنائیں کہ یہ آپ کے سینسر کے اپ ڈیٹ وقفہ سے زیادہ ہے تاکہ غلط اسٹاپس سے بچا جا سکے (مثال کے طور پر، اگر سینسر ہر 60s میں اپ ڈیٹ کرتا ہے تو 65s+ استعمال کریں)۔", + "no_update_active_timeout": "پبلش آن چینج سینسرز کے لیے: صرف ایک فعال سائیکل کو زبردستی ختم کریں اگر اتنے سیکنڈ تک کوئی اپ ڈیٹس نہ پہنچیں۔ کم طاقت کی تکمیل اب بھی آف تاخیر کا استعمال کرتی ہے۔", + "progress_reset_delay": "ایک سائیکل مکمل ہونے کے بعد (100%)، پیش رفت کو 0% پر ری سیٹ کرنے سے پہلے کئی سیکنڈ تک بیکار کا انتظار کریں۔ ڈیفالٹ: 300s۔", + "auto_maintenance": "خودکار دیکھ بھال کو فعال کریں (مرمت کے نمونے، ٹکڑوں کو ضم کریں)۔", + "expose_debug_entities": "ڈیبگنگ کے لیے اعلی درجے کے سینسر (اعتماد، مرحلہ، ابہام) دکھائیں۔", + "save_debug_traces": "تاریخ میں تفصیلی درجہ بندی اور پاور ٹریس ڈیٹا کو اسٹور کریں (اسٹوریج کے استعمال کو بڑھاتا ہے)۔" + } + }, + "anti_wrinkle_section": { + "name": "اینٹی رنکل شیلڈ (ڈرائر)", + "description": "بھوتیہ سائیکلوں سے بچنے کے لیے سائیکل ختم ہونے کے بعد ڈرم کی گردشوں کو نظر انداز کریں۔ صرف ڈرائر / واشر-ڈرائر کے لیے۔", + "data": { + "anti_wrinkle_enabled": "اینٹی رنکل شیلڈ (صرف ڈرائر)", + "anti_wrinkle_max_power": "اینٹی رنکل میکس پاور (W)", + "anti_wrinkle_max_duration": "اینٹی رنکل زیادہ سے زیادہ دورانیہ (s)", + "anti_wrinkle_exit_power": "اینٹی رنکل ایگزٹ پاور (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "گھوسٹ سائیکل (صرف ڈرائر/واشر ڈرائر) کو روکنے کے لیے سائیکل ختم ہونے کے بعد کم طاقت والے ڈرم کی گردش کو نظر انداز کریں۔", + "anti_wrinkle_max_power": "اس طاقت پر یا اس سے نیچے پھٹنے کو نئے چکروں کی بجائے شکن مخالف گردش سمجھا جاتا ہے۔ صرف اس وقت لاگو ہوتا ہے جب اینٹی رنکل شیلڈ فعال ہو۔", + "anti_wrinkle_max_duration": "اینٹی شیکن گردش کے طور پر علاج کرنے کے لئے زیادہ سے زیادہ پھٹ کی لمبائی۔ صرف اس وقت لاگو ہوتا ہے جب اینٹی رنکل شیلڈ فعال ہو۔", + "anti_wrinkle_exit_power": "اگر پاور اس سطح سے نیچے گرتی ہے، تو فوری طور پر اینٹی شیکن سے باہر نکلیں (ٹرو آف)۔ صرف اس وقت لاگو ہوتا ہے جب اینٹی رنکل شیلڈ فعال ہو۔" + } + }, + "delay_start_section": { + "name": "تاخیر سے آغاز کا پتہ لگانا", + "description": "مسلسل کم طاقت والے اسٹینڈ بائی کو 'شروع کا انتظار' سمجھیں جب تک کہ سائیکل واقعی شروع نہ ہو جائے۔", + "data": { + "delay_start_detect_enabled": "تاخیر سے آغاز کا پتہ لگانے کو فعال کریں۔", + "delay_confirm_seconds": "تاخیر سے آغاز: اسٹینڈ بائی تصدیقی وقت (s)", + "delay_timeout_hours": "تاخیر سے آغاز: زیادہ سے زیادہ انتظار کا وقت (h)" + }, + "data_description": { + "delay_start_detect_enabled": "فعال ہونے پر، ایک مختصر کم پاور ڈرین اسپائک جس کے بعد مستقل اسٹینڈ بائی پاور کا پتہ چلتا ہے کہ تاخیر سے آغاز ہوتا ہے۔ ریاستی سینسر تاخیر کے دوران 'شروع کا انتظار' دکھاتا ہے۔ کسی پروگرام کے وقت یا پیشرفت کا پتہ نہیں لگایا جاتا ہے جب تک کہ سائیکل اصل میں شروع نہ ہو۔ start_threshold_w کو ڈرین اسپائک پاور کے اوپر سیٹ کرنے کی ضرورت ہے۔", + "delay_confirm_seconds": "WashData کے 'شروع کا انتظار' میں داخل ہونے سے پہلے پاور کو اتنے سیکنڈ تک اسٹاپ تھریشولڈ (W) اور اسٹارٹ تھریشولڈ (W) کے درمیان رہنا چاہیے۔ یہ مینو نیویگیشن کی مختصر چوٹیوں کو فلٹر کرتا ہے۔ ڈیفالٹ: 60s۔", + "delay_timeout_hours": "سیفٹی ٹائم آؤٹ: اگر مشین اتنے گھنٹوں کے بعد بھی 'شروع ہونے کے انتظار میں' ہے، تو واش ڈیٹا دوبارہ آف پر سیٹ ہو جاتا ہے۔ پہلے سے طے شدہ: 8 گھنٹے۔" + } + }, + "external_triggers_section": { + "name": "بیرونی ٹرگرز، دروازہ اور وقفہ", + "description": "اختیاری بیرونی اختتامی ٹرگر سینسر، وقفہ/صاف حالت کا پتہ لگانے کے لیے دروازے کا سینسر، اور وقفے پر پاور کٹ سوئچ۔", + "data": { + "external_end_trigger_enabled": "ایکسٹرنل سائیکل اینڈ ٹرگر کو فعال کریں۔", + "external_end_trigger": "بیرونی سائیکل اینڈ سینسر", + "external_end_trigger_inverted": "الٹا ٹرگر لاجک (ٹرگر آن آف)", + "door_sensor_entity": "دروازے کا سینسر", + "pause_cuts_power": "موقوف کرتے وقت پاور کاٹ دیں۔", + "switch_entity": "سوئچ ہستی (پاور کٹ کو روکنے کے لیے)", + "notify_unload_delay_minutes": "لانڈری کے انتظار کی اطلاع میں تاخیر (منٹ)" + }, + "data_description": { + "external_end_trigger_enabled": "سائیکل کی تکمیل کو متحرک کرنے کے لیے بیرونی بائنری سینسر کی نگرانی کو فعال کریں۔", + "external_end_trigger": "ایک بائنری سینسر ہستی کا انتخاب کریں۔ جب یہ سینسر ٹرگر ہوتا ہے، تو موجودہ دور 'مکمل' کی حیثیت کے ساتھ ختم ہو جائے گا۔", + "external_end_trigger_inverted": "پہلے سے طے شدہ طور پر، جب سینسر آن ہوتا ہے تو ٹرگر فائر ہوتا ہے۔ اس کے بجائے سینسر کے آف ہونے پر فائر کرنے کے لیے اس باکس کو چیک کریں۔", + "door_sensor_entity": "مشین کے دروازے کے لیے اختیاری بائنری سینسر (آن = اوپن، آف = بند)۔ جب ایک فعال سائیکل کے دوران دروازہ کھلتا ہے، تو WashData سائیکل کی تصدیق کرے گا جیسا کہ جان بوجھ کر موقوف کیا گیا ہے۔ سائیکل ختم ہونے کے بعد، اگر دروازہ ابھی بھی بند ہے، تو حالت اس وقت تک 'کلین' میں بدل جاتی ہے جب تک کہ دروازہ کھلا نہ ہو۔ نوٹ: دروازہ بند کرنے سے رکے ہوئے سائیکل کو خودکار طور پر دوبارہ شروع نہیں کیا جاتا ہے - دوبارہ شروع کرنے کو دستی طور پر ریزیومے سائیکل بٹن یا سروس کے ذریعے متحرک ہونا چاہیے۔", + "pause_cuts_power": "پاز سائیکل بٹن یا سروس کے ذریعے توقف کرتے وقت، کنفیگر شدہ سوئچ ہستی کو بھی بند کر دیں۔ صرف اس صورت میں اثر انداز ہوتا ہے جب اس آلہ کے لیے ایک سوئچ ہستی کو کنفیگر کیا گیا ہو۔", + "switch_entity": "موقوف یا دوبارہ شروع کرنے پر ٹوگل کرنے کے لیے سوئچ ہستی۔ جب 'روکتے وقت پاور کاٹنا' فعال ہو تو درکار ہے۔", + "notify_unload_delay_minutes": "سائیکل ختم ہونے کے بعد فنش نوٹیفکیشن چینل کے ذریعے ایک اطلاع بھیجیں اور اس کئی منٹ تک دروازہ نہیں کھولا گیا (دروازے کے سینسر کی ضرورت ہے)۔ غیر فعال کرنے کے لیے 0 پر سیٹ کریں۔ ڈیفالٹ: 60 منٹ۔" + } + }, + "pump_section": { + "name": "پمپ مانیٹر", + "description": "صرف پمپ ڈیوائس کی قسم۔ اگر پمپ سائیکل اس دورانیے سے زیادہ چلے تو ایک ایونٹ فائر کرتا ہے۔", + "data": { + "pump_stuck_duration": "پمپ اسٹک الرٹ تھریشولڈ (صرف پمپ)" + }, + "data_description": { + "pump_stuck_duration": "اگر اتنے سیکنڈز کے بعد بھی پمپ سائیکل چل رہا ہے تو ایک ha_washdata_pump_stuck ایونٹ ایک بار فائر کیا جاتا ہے۔ اسے جام شدہ موٹر کا پتہ لگانے کے لیے استعمال کریں (عام سمپ پمپ رن 60 سیکنڈ سے کم ہے)۔ ڈیفالٹ: 1800 سیکنڈ (30 منٹ)۔ صرف پمپ ڈیوائس کی قسم۔" + } + }, + "device_link_section": { + "name": "ڈیوائس کا لنک", + "description": "اختیاری طور پر اس واش ڈیٹا ڈیوائس کو موجودہ Home Assistant ڈیوائس سے لنک کریں (مثال کے طور پر اسمارٹ پلگ یا خود آلات)۔ واش ڈیٹا ڈیوائس کو اس ڈیوائس کے ذریعے \"کنیکٹڈ\" کے طور پر دکھایا جاتا ہے۔ واش ڈیٹا کو اسٹینڈ ایلون ڈیوائس کے طور پر رکھنے کے لیے خالی چھوڑ دیں۔", + "data": { + "linked_device": "منسلک ڈیوائس" + }, + "data_description": { + "linked_device": "واش ڈیٹا کو جوڑنے کے لیے آلہ منتخب کریں۔ یہ ڈیوائس رجسٹری میں ایک 'کنیکٹڈ بذریعہ' حوالہ شامل کرتا ہے۔ واش ڈیٹا اپنا آلہ کارڈ اور اداروں کو رکھتا ہے۔ لنک کو ہٹانے کے لیے انتخاب کو صاف کریں۔" + } + } + } + }, + "diagnostics": { + "title": "تشخیص اور دیکھ بھال", + "description": "دیکھ بھال کی کارروائیاں چلائیں جیسے بکھرے ہوئے چکروں کو ضم کرنا یا ذخیرہ شدہ ڈیٹا کو منتقل کرنا۔\n\n**اسٹوریج کا استعمال**\n\n- فائل کا سائز: {file_size_kb} KB\n- سائیکل: {cycle_count}\n- پروفائلز: {profile_count}\n- ڈیبگ ٹریس: {debug_count}", + "menu_options": { + "reprocess_history": "دیکھ بھال: دوبارہ عمل اور ڈیٹا کو بہتر بنائیں", + "clear_debug_data": "ڈیبگ ڈیٹا صاف کریں (جگہ خالی کریں)", + "wipe_history": "اس آلہ کا تمام ڈیٹا صاف کریں (ناقابل واپسی)", + "export_import": "ترتیبات کے ساتھ JSON برآمد/درآمد کریں (کاپی/پیسٹ)", + "menu_back": "← واپس" + } + }, + "clear_debug_data": { + "title": "ڈیبگ ڈیٹا صاف کریں۔", + "description": "کیا آپ واقعی تمام ذخیرہ شدہ ڈیبگ نشانات کو حذف کرنا چاہتے ہیں؟ یہ جگہ خالی کر دے گا لیکن پچھلے چکروں سے درجہ بندی کی تفصیلی معلومات کو ہٹا دے گا۔" + }, + "manage_cycles": { + "title": "سائیکلوں کا نظم کریں۔", + "description": "حالیہ چکر:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "پرانے سائیکلوں کو آٹو لیبل کریں۔", + "select_cycle_to_label": "لیبل مخصوص سائیکل", + "select_cycle_to_delete": "سائیکل کو حذف کریں۔", + "interactive_editor": "انٹرایکٹو ایڈیٹر کو ضم/تقسیم کریں۔", + "trim_cycle_select": "ٹرم سائیکل ڈیٹا", + "menu_back": "← واپس" + } + }, + "manage_cycles_empty": { + "title": "سائیکلوں کا نظم کریں۔", + "description": "ابھی تک کوئی سائیکل ریکارڈ نہیں ہوا۔", + "menu_options": { + "auto_label_cycles": "پرانے سائیکلوں کو آٹو لیبل کریں۔", + "menu_back": "← واپس" + } + }, + "manage_profiles": { + "title": "پروفائلز کا نظم کریں۔", + "description": "پروفائل کا خلاصہ:\n{profile_summary}", + "menu_options": { + "create_profile": "نیا پروفائل بنائیں", + "edit_profile": "پروفائل میں ترمیم/نام تبدیل کریں۔", + "delete_profile_select": "پروفائل حذف کریں۔", + "profile_stats": "پروفائل کے اعدادوشمار", + "cleanup_profile": "کلین اپ ہسٹری - گراف اور ڈیلیٹ کریں۔", + "assign_profile_phases_select": "فیز رینجز تفویض کریں۔", + "menu_back": "← واپس" + } + }, + "manage_profiles_empty": { + "title": "پروفائلز کا نظم کریں۔", + "description": "ابھی تک کوئی پروفائل نہیں بنایا گیا ہے۔", + "menu_options": { + "create_profile": "نیا پروفائل بنائیں", + "menu_back": "← واپس" + } + }, + "manage_phase_catalog": { + "title": "فیز کیٹلاگ کا نظم کریں۔", + "description": "فیز کیٹلاگ (اس ڈیوائس کے لیے ڈیفالٹ + حسب ضرورت مراحل):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "نیا مرحلہ بنائیں", + "phase_catalog_edit_select": "فیز میں ترمیم کریں۔", + "phase_catalog_delete": "مرحلہ حذف کریں۔", + "menu_back": "← واپس" + } + }, + "phase_catalog_create": { + "title": "اپنی مرضی کا مرحلہ بنائیں", + "description": "ایک حسب ضرورت فیز کا نام اور تفصیل بنائیں، اور منتخب کریں کہ یہ کس ڈیوائس کی قسم پر لاگو ہوتا ہے۔", + "data": { + "target_device_type": "ٹارگٹ ڈیوائس کی قسم", + "phase_name": "فیز کا نام", + "phase_description": "مرحلے کی تفصیل" + } + }, + "phase_catalog_edit_select": { + "title": "حسب ضرورت مرحلے میں ترمیم کریں۔", + "description": "ترمیم کرنے کے لیے ایک حسب ضرورت مرحلہ منتخب کریں۔", + "data": { + "phase_name": "حسب ضرورت مرحلہ" + } + }, + "phase_catalog_edit": { + "title": "حسب ضرورت مرحلے میں ترمیم کریں۔", + "description": "ترمیم کا مرحلہ: {phase_name}", + "data": { + "phase_name": "فیز کا نام", + "phase_description": "مرحلے کی تفصیل" + } + }, + "phase_catalog_delete": { + "title": "اپنی مرضی کے مرحلے کو حذف کریں۔", + "description": "حذف کرنے کے لیے حسب ضرورت مرحلہ منتخب کریں۔ اس مرحلے کا استعمال کرتے ہوئے کسی بھی تفویض کردہ رینجز کو ہٹا دیا جائے گا۔", + "data": { + "phase_name": "حسب ضرورت مرحلہ" + } + }, + "assign_profile_phases": { + "title": "فیز رینجز تفویض کریں۔", + "description": "پروفائل کے لیے فیز پیش نظارہ: **{profile_name}**\n\n{timeline_svg}\n\n**موجودہ حدود:**\n{current_ranges}\n\nرینجز کو شامل کرنے، ترمیم کرنے، حذف کرنے یا محفوظ کرنے کے لیے نیچے دی گئی کارروائیوں کا استعمال کریں۔", + "menu_options": { + "assign_profile_phases_add": "فیز رینج شامل کریں۔", + "assign_profile_phases_edit_select": "فیز رینج میں ترمیم کریں۔", + "assign_profile_phases_delete": "فیز رینج کو حذف کریں۔", + "phase_ranges_clear": "تمام حدود کو صاف کریں۔", + "assign_profile_phases_auto_detect": "مراحل کا خود بخود پتہ لگائیں۔", + "phase_ranges_save": "محفوظ کریں اور واپس کریں۔", + "menu_back": "← واپس" + } + }, + "assign_profile_phases_add": { + "title": "فیز رینج شامل کریں۔", + "description": "موجودہ مسودے میں ایک فیز رینج شامل کریں۔", + "data": { + "phase_name": "مرحلہ", + "start_min": "شروع منٹ", + "end_min": "اختتامی منٹ" + } + }, + "assign_profile_phases_edit_select": { + "title": "فیز رینج میں ترمیم کریں۔", + "description": "ترمیم کرنے کے لیے ایک رینج منتخب کریں۔", + "data": { + "range_index": "فیز رینج" + } + }, + "assign_profile_phases_edit": { + "title": "فیز رینج میں ترمیم کریں۔", + "description": "منتخب شدہ فیز رینج کو اپ ڈیٹ کریں۔", + "data": { + "phase_name": "مرحلہ", + "start_min": "شروع منٹ", + "end_min": "اختتامی منٹ" + } + }, + "assign_profile_phases_delete": { + "title": "فیز رینج کو حذف کریں۔", + "description": "ڈرافٹ سے ہٹانے کے لیے ایک رینج منتخب کریں۔", + "data": { + "range_index": "فیز رینج" + } + }, + "assign_profile_phases_auto_detect": { + "title": "خود بخود پتہ چلنے والے مراحل", + "description": "پروفائل: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} مرحلے (مرحلوں) کا خود بخود پتہ چلا۔** نیچے ایک کارروائی کا انتخاب کریں۔", + "data": { + "action": "ایکشن" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "نام معلوم شدہ مراحل", + "description": "پروفائل: **{profile_name}**\n\n**{detected_count} مرحلے کا پتہ چلا:**\n{ranges_summary}\n\nہر مرحلے کو ایک نام تفویض کریں۔" + }, + "assign_profile_phases_select": { + "title": "فیز رینجز تفویض کریں۔", + "description": "فیز رینجز کنفیگر کرنے کے لیے پروفائل منتخب کریں۔", + "data": { + "profile": "پروفائل" + } + }, + "profile_stats": { + "title": "پروفائل کے اعدادوشمار", + "description": "{stats_table}" + }, + "create_profile": { + "title": "نیا پروفائل بنائیں", + "description": "دستی طور پر یا ماضی کے دور سے نیا پروفائل بنائیں۔\n\nپروفائل نام کی مثالیں: 'نازک'، 'ہیوی ڈیوٹی'، 'کوئیک واش'", + "data": { + "profile_name": "پروفائل کا نام", + "reference_cycle": "حوالہ سائیکل (اختیاری)", + "manual_duration": "دستی دورانیہ (منٹ)" + } + }, + "edit_profile": { + "title": "پروفائل میں ترمیم کریں۔", + "description": "نام تبدیل کرنے کے لیے پروفائل منتخب کریں۔", + "data": { + "profile": "پروفائل" + } + }, + "rename_profile": { + "title": "پروفائل کی تفصیلات میں ترمیم کریں۔", + "description": "موجودہ پروفائل: {current_name}", + "data": { + "new_name": "پروفائل کا نام", + "manual_duration": "دستی دورانیہ (منٹ)" + } + }, + "delete_profile_select": { + "title": "پروفائل حذف کریں۔", + "description": "حذف کرنے کے لیے ایک پروفائل منتخب کریں۔", + "data": { + "profile": "پروفائل" + } + }, + "delete_profile_confirm": { + "title": "پروفائل کو حذف کرنے کی تصدیق کریں۔", + "description": "⚠️ یہ پروفائل کو مستقل طور پر حذف کر دے گا: {profile_name}", + "data": { + "unlabel_cycles": "اس پروفائل کا استعمال کرتے ہوئے سائیکلوں سے لیبل کو ہٹا دیں۔" + } + }, + "auto_label_cycles": { + "title": "پرانے سائیکلوں کو آٹو لیبل کریں۔", + "description": "کل {total_count} چکر ملے۔ پروفائلز: {profiles}", + "data": { + "confidence_threshold": "کم از کم اعتماد (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "لیبل کے لیے سائیکل کو منتخب کریں۔", + "description": "✓ = مکمل، ⚠ = دوبارہ شروع، ✗ = رکاوٹ", + "data": { + "cycle_id": "سائیکل" + } + }, + "select_cycle_to_delete": { + "title": "سائیکل کو حذف کریں۔", + "description": "⚠️ یہ منتخب شدہ سائیکل کو مستقل طور پر حذف کر دے گا۔", + "data": { + "cycle_id": "حذف کرنے کے لیے سائیکل" + } + }, + "label_cycle": { + "title": "لیبل سائیکل", + "description": "{cycle_info}", + "data": { + "profile_name": "پروفائل", + "new_profile_name": "نیا پروفائل نام (اگر بنا رہے ہو)" + } + }, + "post_process": { + "title": "عمل کی سرگزشت (ضم/تقسیم)", + "description": "بکھرے ہوئے رنز کو ضم کرکے اور ٹائم ونڈو کے اندر غلط طریقے سے ضم شدہ سائیکلوں کو تقسیم کرکے سائیکل کی تاریخ کو صاف کریں۔ پیچھے دیکھنے کے لیے گھنٹوں کی تعداد درج کریں (سب کے لیے 999999 استعمال کریں)۔", + "data": { + "time_range": "لُک بیک ونڈو (گھنٹے)", + "gap_seconds": "ضم/تقسیم فرق (سیکنڈ)" + }, + "data_description": { + "time_range": "ضم ہونے کے لیے بکھرے ہوئے چکروں کو اسکین کرنے کے لیے گھنٹوں کی تعداد۔ تمام تاریخی ڈیٹا پر کارروائی کرنے کے لیے 999999 استعمال کریں۔", + "gap_seconds": "تقسیم بمقابلہ انضمام کا فیصلہ کرنے کی حد۔ اس سے چھوٹے فرق کو ضم کر دیا گیا ہے۔ اس سے زیادہ طویل وقفے تقسیم ہیں۔ غلط طریقے سے ضم ہونے والے سائیکل پر تقسیم کرنے کے لیے اسے نیچے کریں۔" + } + }, + "cleanup_profile": { + "title": "کلین اپ ہسٹری - پروفائل منتخب کریں۔", + "description": "تصور کرنے کے لیے ایک پروفائل منتخب کریں۔ یہ ایک گراف تیار کرے گا جس میں اس پروفائل کے تمام ماضی کے چکر دکھائے جائیں گے تاکہ باہر والوں کی شناخت میں مدد مل سکے۔", + "data": { + "profile": "پروفائل" + } + }, + "cleanup_select": { + "title": "کلین اپ ہسٹری - گراف اور ڈیلیٹ کریں۔", + "description": "![گراف]({graph_url})\n\n**دیکھنا {profile_name}**\n\nگراف انفرادی سائیکلوں کو رنگین لائنوں کے طور پر دکھاتا ہے۔ آؤٹ لیرز کی شناخت کریں (گروپ سے دور لائنیں) اور انہیں حذف کرنے کے لیے نیچے دی گئی فہرست میں ان کے رنگ سے مماثل ہوں۔\n\n**مستقل طور پر حذف کرنے کے لیے سائیکل منتخب کریں:**", + "data": { + "cycles_to_delete": "حذف کرنے کے لیے سائیکل (مماثل رنگ چیک کریں)" + } + }, + "interactive_editor": { + "title": "انٹرایکٹو ایڈیٹر کو ضم/تقسیم کریں۔", + "description": "اپنی سائیکل کی تاریخ پر انجام دینے کے لیے ایک دستی کارروائی کا انتخاب کریں۔", + "menu_options": { + "editor_split": "ایک سائیکل کو تقسیم کریں (خالی تلاش کریں)", + "editor_merge": "سائیکلوں کو ضم کریں (ٹکڑوں میں شامل ہوں)", + "editor_delete": "سائیکل کو حذف کریں", + "menu_back": "← واپس" + } + }, + "editor_select": { + "title": "سائیکل منتخب کریں۔", + "description": "عمل کرنے کے لیے سائیکل (سائیکلز) کو منتخب کریں۔\n\n{info_text}", + "data": { + "selected_cycles": "سائیکل" + } + }, + "editor_configure": { + "title": "ترتیب دیں اور لاگو کریں۔", + "description": "{preview_md}", + "data": { + "confirm_action": "ایکشن", + "merged_profile": "نتیجہ خیز پروفائل", + "new_profile_name": "نیا پروفائل نام", + "confirm_commit": "ہاں، میں اس کارروائی کی تصدیق کرتا ہوں۔", + "segment_0_profile": "سیگمنٹ 1 پروفائل", + "segment_1_profile": "سیگمنٹ 2 پروفائل", + "segment_2_profile": "سیگمنٹ 3 پروفائل", + "segment_3_profile": "سیگمنٹ 4 پروفائل", + "segment_4_profile": "سیگمنٹ 5 پروفائل", + "segment_5_profile": "سیگمنٹ 6 پروفائل", + "segment_6_profile": "سیگمنٹ 7 پروفائل", + "segment_7_profile": "سیگمنٹ 8 پروفائل", + "segment_8_profile": "سیگمنٹ 9 پروفائل", + "segment_9_profile": "سیگمنٹ 10 پروفائل" + } + }, + "editor_split_params": { + "title": "تقسیم کنفیگریشن", + "description": "اس سائیکل کو تقسیم کرنے کے لیے حساسیت کو ایڈجسٹ کریں۔ اس سے لمبا کوئی بھی بیکار وقفہ تقسیم کا سبب بنے گا۔", + "data": { + "split_mode": "تقسیم کا طریقہ" + } + }, + "editor_split_auto_params": { + "title": "خودکار تقسیم کی ترتیب", + "description": "اس سائیکل کو تقسیم کرنے کی حساسیت کو ایڈجسٹ کریں۔ اس سے زیادہ طویل کوئی بھی غیر فعال وقفہ تقسیم کا سبب بنے گا۔", + "data": { + "min_gap_seconds": "تقسیم وقفہ حد (سیکنڈ)" + } + }, + "editor_split_manual_params": { + "title": "دستی تقسیم کے ٹائم اسٹیمپ", + "description": "{preview_md}سائیکل ونڈو: **{cycle_start_wallclock} → {cycle_end_wallclock}** بروز {cycle_date}۔\n\nایک یا ایک سے زیادہ تقسیم ٹائم اسٹیمپ (`HH:MM` یا `HH:MM:SS`) درج کریں، ہر سطر میں ایک۔ ہر ٹائم اسٹیمپ پر سائیکل کاٹا جائے گا — N ٹائم اسٹیمپ N+1 حصے بناتے ہیں۔", + "data": { + "split_timestamps": "تقسیم کے ٹائم اسٹیمپس" + } + }, + "reprocess_history": { + "title": "دیکھ بھال: دوبارہ عمل اور ڈیٹا کو بہتر بنائیں", + "description": "تاریخی ڈیٹا کی گہری صفائی کرتا ہے:\n1. زیرو پاور ریڈنگ کو تراشتا ہے (خاموشی)۔\n2. سائیکل کا وقت/دورانیہ درست کرتا ہے۔\n3. تمام شماریاتی ماڈلز (لفافے) کی دوبارہ گنتی کرتا ہے۔\n\n**ڈیٹا کے مسائل کو ٹھیک کرنے یا نئے الگورتھم لاگو کرنے کے لیے اسے چلائیں۔**" + }, + "wipe_history": { + "title": "وائپ ہسٹری (صرف ٹیسٹنگ)", + "description": "⚠️ یہ اس آلے کے تمام ذخیرہ شدہ سائیکل اور پروفائلز کو مستقل طور پر حذف کر دے گا۔ اسے کالعدم نہیں کیا جا سکتا!" + }, + "export_import": { + "title": "JSON برآمد/درآمد کریں۔", + "description": "اس ڈیوائس کے لیے سائیکل، پروفائلز، اور سیٹنگز کو کاپی/پیسٹ کریں۔ نیچے برآمد کریں یا درآمد کرنے کے لیے JSON چسپاں کریں۔", + "data": { + "mode": "ایکشن", + "json_payload": "مکمل کنفیگریشن JSON" + }, + "data_description": { + "mode": "موجودہ ڈیٹا اور سیٹنگز کو کاپی کرنے کے لیے ایکسپورٹ کا انتخاب کریں، یا کسی دوسرے واش ڈیٹا ڈیوائس سے ایکسپورٹ شدہ ڈیٹا کو پیسٹ کرنے کے لیے امپورٹ کریں۔", + "json_payload": "ایکسپورٹ کے لیے: اس JSON کو کاپی کریں (بشمول سائیکل، پروفائلز، اور تمام عمدہ ترتیبات)۔ درآمد کے لیے: WashData سے برآمد کردہ JSON پیسٹ کریں۔" + } + }, + "record_cycle": { + "title": "ریکارڈ سائیکل", + "description": "بغیر مداخلت کے صاف پروفائل بنانے کے لیے ایک سائیکل کو دستی طور پر ریکارڈ کریں۔\n\nحیثیت: **{status}**\nدورانیہ: {duration}s\nنمونے: {samples}", + "menu_options": { + "record_refresh": "ریفریش اسٹیٹس", + "record_stop": "ریکارڈنگ بند کریں (محفوظ کریں اور عمل کریں)", + "record_start": "نئی ریکارڈنگ شروع کریں۔", + "record_process": "آخری ریکارڈنگ پر عمل کریں (تراشیں اور محفوظ کریں)", + "record_discard": "آخری ریکارڈنگ کو مسترد کریں۔", + "menu_back": "← واپس" + } + }, + "record_process": { + "title": "عمل کی ریکارڈنگ", + "description": "![گراف]({graph_url})\n\n**گراف خام ریکارڈنگ دکھاتا ہے۔** نیلا = رکھیں، سرخ = مجوزہ تراشیں۔\n*گراف جامد ہے اور فارم کی تبدیلیوں کے ساتھ اپ ڈیٹ نہیں ہوتا ہے۔*\n\nریکارڈنگ رک گئی۔ تراشے ہوئے نمونے لینے کی شرح (~{sampling_rate}s) کے ساتھ منسلک ہیں۔\n\nخام دورانیہ: {duration}s\nنمونے: {samples}", + "data": { + "head_trim": "ٹرم اسٹارٹ (سیکنڈ)", + "tail_trim": "ٹرم اینڈ (سیکنڈ)", + "save_mode": "منزل کو محفوظ کریں۔", + "profile_name": "پروفائل کا نام" + } + }, + "learning_feedbacks": { + "title": "سیکھنے کے تاثرات", + "description": "زیر التواء تاثرات: {count}\n\n{pending_table}\n\nپروسیسنگ کے لیے سائیکل جائزے کی درخواست منتخب کریں۔", + "menu_options": { + "learning_feedbacks_pick": "زیر التواء تاثرات کا جائزہ لیں", + "learning_feedbacks_dismiss_all": "تمام زیر التواء تاثرات مسترد کریں", + "menu_back": "← واپس" + } + }, + "learning_feedbacks_pick": { + "title": "جائزے کے لیے تاثرات منتخب کریں", + "description": "کھولنے کے لیے سائیکل جائزہ درخواست منتخب کریں۔", + "data": { + "selected_feedback": "جائزے کے لیے سائیکل منتخب کریں" + } + }, + "learning_feedbacks_empty": { + "title": "سیکھنے کے تاثرات", + "description": "کوئی زیر التواء رائے کی درخواستیں نہیں ملی۔", + "menu_options": { + "menu_back": "← واپس" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "تمام زیر التوا تاثرات مسترد کریں", + "description": "آپ **{count}** زیر التوا تاثراتی درخواستیں مسترد کرنے والے ہیں۔ مسترد شدہ سائیکل آپ کی ہسٹری میں رہیں گے لیکن نئے سیکھنے کے سگنل میں حصہ نہیں ڈالیں گے۔ اسے واپس نہیں لیا جا سکتا۔\n\n✅ سب کو مسترد کرنے کے لیے نیچے دیے گئے باکس کو چیک کریں اور **جمع کرائیں** پر کلک کریں۔\n❌ اسے غیر نشان زد رہنے دیں اور منسوخ کرنے اور واپس جانے کے لیے **جمع کرائیں** پر کلک کریں۔", + "data": { + "confirm_dismiss_all": "تصدیق کریں: تمام زیر التواء تاثراتی درخواستیں مسترد کریں" + } + }, + "resolve_feedback": { + "title": "تاثرات حل کریں۔", + "description": "{comparison_img}{candidates_table}\n** پروفائل کا پتہ چلا**: {detected_profile} ({confidence_pct}%)\n**تخمینی مدت**: {est_duration_min} منٹ\n**اصل دورانیہ**: {act_duration_min} منٹ\n\n__ذیل میں ایک عمل کا انتخاب کریں:__", + "data": { + "action": "آپ کیا کرنا چاہیں گے؟", + "corrected_profile": "درست پروگرام (اگر درست ہو)", + "corrected_duration": "سیکنڈ میں درست دورانیہ (اگر درست ہو)" + } + }, + "trim_cycle_select": { + "title": "ٹرم سائیکل - سائیکل کو منتخب کریں۔", + "description": "تراشنے کے لیے ایک سائیکل منتخب کریں۔ ✓ = مکمل، ⚠ = دوبارہ شروع، ✗ = رکاوٹ", + "data": { + "cycle_id": "سائیکل" + } + }, + "trim_cycle": { + "title": "ٹرم سائیکل", + "description": "موجودہ ونڈو: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "ایکشن" + } + }, + "trim_cycle_start": { + "title": "ٹرم اسٹارٹ سیٹ کریں۔", + "description": "سائیکل ونڈو: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nموجودہ آغاز: **{current_wallclock}** (سائیکل شروع ہونے سے +{current_offset_min} منٹ)\n\nنیچے دیے گئے گھڑی چنندہ کا استعمال کرتے ہوئے ایک نیا وقت شروع کریں۔", + "data": { + "trim_start_time": "نئے آغاز کا وقت", + "trim_start_min": "نیا آغاز (سائیکل شروع ہونے سے منٹ)" + } + }, + "trim_cycle_end": { + "title": "ٹرم اینڈ سیٹ کریں۔", + "description": "سائیکل ونڈو: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nموجودہ اختتام: **{current_wallclock}** (سائیکل کے آغاز سے +{current_offset_min} منٹ)\n\nذیل میں گھڑی چنندہ کا استعمال کرتے ہوئے ایک نیا اختتامی وقت منتخب کریں۔", + "data": { + "trim_end_time": "نیا اختتامی وقت", + "trim_end_min": "نیا اختتام (سائیکل شروع ہونے سے منٹ)" + } + } + }, + "error": { + "import_failed": "درآمد ناکام ہو گیا۔ براہ کرم ایک درست واش ڈیٹا ایکسپورٹ JSON پیسٹ کریں۔", + "select_exactly_one": "اس کارروائی کے لیے بالکل ایک سائیکل منتخب کریں۔", + "select_at_least_one": "اس کارروائی کے لیے کم از کم ایک سائیکل منتخب کریں۔", + "select_at_least_two": "اس کارروائی کے لیے کم از کم دو سائیکل منتخب کریں۔", + "profile_exists": "اس نام کا پروفائل پہلے سے موجود ہے۔", + "rename_failed": "پروفائل کا نام تبدیل کرنے میں ناکام۔ ہو سکتا ہے پروفائل موجود نہ ہو یا نیا نام پہلے ہی لے لیا گیا ہو۔", + "assignment_failed": "سائیکل کو پروفائل تفویض کرنے میں ناکام", + "duplicate_phase": "اس نام کا ایک مرحلہ پہلے سے موجود ہے۔", + "phase_not_found": "منتخب مرحلہ نہیں ملا۔", + "invalid_phase_name": "فیز کا نام خالی نہیں ہو سکتا۔", + "phase_name_too_long": "فیز کا نام بہت لمبا ہے۔", + "invalid_phase_range": "فیز رینج غلط ہے۔ اختتام شروع سے بڑا ہونا چاہیے۔", + "invalid_phase_timestamp": "ٹائم اسٹیمپ غلط ہے۔ YYYY-MM-DD HH:MM یا HH:MM فارمیٹ استعمال کریں۔", + "overlapping_phase_ranges": "فیز رینج اوورلیپ۔ رینجز کو ایڈجسٹ کریں اور دوبارہ کوشش کریں۔", + "incomplete_phase_row": "ہر مرحلے کی قطار میں منتخب موڈ کے لیے فیز کے علاوہ مکمل آغاز/اختتام کی قدریں شامل ہونی چاہئیں۔", + "timestamp_mode_cycle_required": "ٹائم اسٹیمپ موڈ استعمال کرتے وقت براہ کرم سورس سائیکل منتخب کریں۔", + "no_phase_ranges": "اس کارروائی کے لیے ابھی تک کوئی فیز رینج دستیاب نہیں ہے۔", + "feedback_notification_title": "واش ڈیٹا: سائیکل کی تصدیق کریں ({device})", + "feedback_notification_message": "**آلہ**: {device}\n**پروگرام**: {program} ({confidence}% اعتماد)\n**وقت**: {time}\n\nاس دریافت شدہ سائیکل کی توثیق کرنے کے لیے WashData کو آپ کی مدد کی ضرورت ہے۔\n\nاس نتیجے کی تصدیق یا درستگی کے لیے براہ کرم **سیٹنگز> ڈیوائسز اور سروسز> واش ڈیٹا> کنفیگر> لرننگ فیڈ بیکس** پر جائیں۔", + "suggestions_ready_notification_title": "واش ڈیٹا: تجویز کردہ ترتیبات تیار ({device})", + "suggestions_ready_notification_message": "**تجویز کردہ ترتیبات** سینسر اب **{count}** قابل عمل تجاویز کی اطلاع دیتا ہے۔\n\nان کا جائزہ لینے اور لاگو کرنے کے لیے: ** سیٹنگز > ڈیوائسز اور سروسز > واش ڈیٹا > کنفیگر > ایڈوانسڈ سیٹنگز > اپلائی تجویز کردہ ویلیوز**۔\n\nتجاویز اختیاری ہیں اور محفوظ کرنے سے پہلے نظرثانی کے لیے دکھائی جاتی ہیں۔", + "trim_range_invalid": "آغاز ختم ہونے سے پہلے ہونا چاہیے۔ براہ کرم اپنے ٹرم پوائنٹس کو ایڈجسٹ کریں۔", + "trim_failed": "تراشنا ناکام ہو گیا۔ ہو سکتا ہے کہ سائیکل اب موجود نہ ہو یا ونڈو خالی ہو۔", + "invalid_split_timestamp": "ٹائم اسٹیمپ غلط ہے یا سائیکل ونڈو سے باہر ہے۔ سائیکل ونڈو کے اندر HH:MM یا HH:MM:SS استعمال کریں۔", + "no_split_segments_found": "ان ٹائم اسٹیمپس سے کوئی درست سیگمنٹس نہیں بن سکے۔ یقینی بنائیں کہ ہر ٹائم اسٹیمپ سائیکل ونڈو کے اندر ہے اور ہر سیگمنٹ کم از کم 1 منٹ لمبا ہے۔", + "auto_tune_suggestion": "{device_type} {device_title} بھوت چکروں کا پتہ چلا۔ تجویز کردہ min_power تبدیلی: {current_min}W -> {new_min}W (خودکار طور پر لاگو نہیں ہوا)۔", + "auto_tune_title": "واش ڈیٹا آٹو ٹیون", + "auto_tune_fallback": "{device_type} {device_title} بھوت چکروں کا پتہ چلا۔ تجویز کردہ min_power تبدیلی: {current_min}W -> {new_min}W (خودکار طور پر لاگو نہیں ہوا)۔" + }, + "abort": { + "no_cycles_found": "کوئی سائیکل نہیں ملا", + "no_split_segments_found": "منتخب کردہ سائیکل میں تقسیم کیے جا سکنے والے کوئی سیگمنٹس نہیں ملے۔", + "cycle_not_found": "منتخب کردہ سائیکل لوڈ نہیں کیا جا سکا۔", + "no_power_data": "منتخب شدہ سائیکل کے لیے کوئی پاور ٹریس ڈیٹا دستیاب نہیں ہے۔", + "no_profiles_found": "کوئی پروفائلز نہیں ملے۔ پہلے پروفائل بنائیں۔", + "no_profiles_for_matching": "مماثلت کے لیے کوئی پروفائل دستیاب نہیں ہے۔ پہلے پروفائلز بنائیں۔", + "no_unlabeled_cycles": "تمام سائیکلوں پر پہلے ہی لیبل لگا ہوا ہے۔", + "no_suggestions": "ابھی تک کوئی تجویز کردہ قدر دستیاب نہیں ہے۔ چند سائیکل چلائیں اور دوبارہ کوشش کریں۔", + "no_predictions": "کوئی پیشین گوئیاں نہیں۔", + "no_custom_phases": "کوئی حسب ضرورت مراحل نہیں ملے۔", + "reprocess_success": "کامیابی کے ساتھ {count} سائیکلوں پر دوبارہ عمل کیا گیا۔", + "debug_data_cleared": "{count} سائیکلوں سے ڈیبگ ڈیٹا کو کامیابی کے ساتھ صاف کر دیا گیا۔" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "سائیکل اسٹارٹ", + "cycle_finish": "سائیکل ختم", + "cycle_live": "لائیو پروگریس" + } + }, + "common_text": { + "options": { + "unlabeled": "(غیر لیبل لگا ہوا)", + "create_new_profile": "نیا پروفائل بنائیں", + "remove_label": "لیبل کو ہٹا دیں۔", + "no_reference_cycle": "(کوئی حوالہ سائیکل نہیں)", + "all_device_types": "ڈیوائس کی تمام اقسام", + "current_suffix": "(موجودہ)", + "editor_select_info": "تقسیم کرنے کے لیے 1 سائیکل، یا ضم کرنے کے لیے 2+ سائیکل منتخب کریں۔", + "editor_select_info_split": "تقسیم کرنے کے لیے 1 سائیکل منتخب کریں۔", + "editor_select_info_merge": "ضم کرنے کے لیے 2+ سائیکل منتخب کریں۔", + "editor_select_info_delete": "حذف کرنے کے لیے 1+ سائیکل منتخب کریں۔", + "no_cycles_recorded": "ابھی تک کوئی سائیکل ریکارڈ نہیں ہوا۔", + "profile_summary_line": "- **{name}**: {count} سائیکل، {avg}m اوسط", + "no_profiles_created": "ابھی تک کوئی پروفائل نہیں بنایا گیا ہے۔", + "phase_preview": "فیز کا پیش نظارہ", + "phase_preview_no_curve": "پروفائل کا اوسط وکر ابھی دستیاب نہیں ہے۔ اس پروفائل کے لیے مزید سائیکل چلائیں/لیبل لگائیں۔", + "average_power_curve": "اوسط پاور وکر", + "unit_min": "منٹ", + "profile_option_fmt": "{name} ({count} سائیکل، ~{duration}m اوسط)", + "profile_option_short_fmt": "{name} ({count} سائیکل، ~{duration}m)", + "deleted_cycles_from_profile": "{profile} سے {count} سائیکلوں کو حذف کر دیا گیا۔", + "not_enough_data_graph": "گراف بنانے کے لیے کافی ڈیٹا نہیں ہے۔", + "no_profiles_found": "کوئی پروفائلز نہیں ملے۔", + "cycle_info_fmt": "سائیکل: {start}، {duration}m، موجودہ: {label}", + "top_candidates_header": "### سرفہرست امیدوار", + "tbl_profile": "پروفائل", + "tbl_confidence": "اعتماد", + "tbl_correlation": "ارتباط", + "tbl_duration_match": "دورانیہ میچ", + "detected_profile": "پروفائل کا پتہ چلا", + "estimated_duration": "متوقع دورانیہ", + "actual_duration": "اصل دورانیہ", + "choose_action": "__ذیل میں ایک عمل کا انتخاب کریں:__", + "tbl_count": "شمار", + "tbl_avg": "اوسط", + "tbl_min": "کم از کم", + "tbl_max": "زیادہ سے زیادہ", + "tbl_energy_avg": "توانائی (اوسط)", + "tbl_energy_total": "توانائی (کل)", + "tbl_consistency": "پر مشتمل", + "tbl_last_run": "آخری رن", + "graph_legend_title": "گراف لیجنڈ", + "graph_legend_body": "نیلا بینڈ مشاہدہ کی گئی کم سے کم اور زیادہ سے زیادہ پاور ڈرا رینج کی نمائندگی کرتا ہے۔ لائن اوسط پاور وکر کو ظاہر کرتی ہے۔", + "recording_preview": "ریکارڈنگ کا پیش نظارہ", + "trim_start": "ٹرم اسٹارٹ", + "trim_end": "ٹرم اینڈ", + "split_preview_title": "تقسیم کا پیش نظارہ", + "split_preview_found_fmt": "{count} سیگمنٹس ملے۔", + "split_preview_confirm_fmt": "اس سائیکل کو {count} الگ الگ سائیکلوں میں تقسیم کرنے کے لیے تصدیق پر کلک کریں۔", + "merge_preview_title": "پیش نظارہ کو ضم کریں۔", + "merge_preview_joining_fmt": "{count} سائیکلوں میں شامل ہونا۔ خلا کو 0W ریڈنگ سے پُر کیا جائے گا۔", + "editor_delete_preview_title": "حذف کا پیش نظارہ", + "editor_delete_preview_intro": "منتخب کردہ سائیکل مستقل طور پر حذف کر دیے جائیں گے:", + "editor_delete_preview_confirm": "ان سائیکل ریکارڈز کو مستقل طور پر حذف کرنے کے لیے Confirm پر کلک کریں۔", + "no_power_preview": "*پیش نظارہ کے لیے کوئی پاور ڈیٹا دستیاب نہیں ہے۔*", + "profile_comparison": "پروفائل کا موازنہ", + "actual_cycle_label": "یہ سائیکل (حقیقی)", + "storage_usage_header": "اسٹوریج کا استعمال", + "storage_file_size": "فائل کا سائز", + "storage_cycles": "سائیکل", + "storage_profiles": "پروفائلز", + "storage_debug_traces": "ڈیبگ ٹریسز", + "overview_suffix": "جائزہ", + "phase_preview_for_profile": "پروفائل کے لیے فیز کا پیش نظارہ", + "current_ranges_header": "موجودہ حدود", + "assign_phases_help": "رینجز کو شامل کرنے، ترمیم کرنے، حذف کرنے یا محفوظ کرنے کے لیے نیچے دی گئی کارروائیوں کا استعمال کریں۔", + "profile_summary_header": "پروفائل کا خلاصہ", + "recent_cycles_header": "حالیہ چکر", + "trim_cycle_preview_title": "سائیکل ٹرم کا پیش نظارہ", + "trim_cycle_preview_no_data": "اس سائیکل کے لیے کوئی پاور ڈیٹا دستیاب نہیں ہے۔", + "trim_cycle_preview_kept_suffix": "رکھا", + "table_program": "پروگرام", + "table_when": "کب", + "table_length": "دورانیہ", + "table_match": "مماثلت", + "table_profile": "پروفائل", + "table_cycles": "سائیکل", + "table_avg_length": "اوسط دورانیہ", + "table_last_run": "آخری رن", + "table_avg_energy": "اوسط توانائی", + "table_detected_program": "پتہ لگا ہوا پروگرام", + "table_confidence": "اعتماد", + "table_reported": "رپورٹ کیا گیا", + "phase_builtin_suffix": "(بلٹ ان)", + "phase_none_available": "کوئی فیز دستیاب نہیں۔", + "settings_deprecation_warning": "⚠️ **فرسودہ ڈیوائس کی قسم:** {device_type} کو مستقبل کی ریلیز میں ہٹانے کے لیے شیڈول کیا گیا ہے۔ واش ڈیٹا کی مماثل پائپ لائن اس آلات کی کلاس کے لیے قابل اعتماد نتائج پیدا نہیں کرتی ہے۔ آپ کا انضمام فرسودگی کی مدت کے دوران کام کرتا رہتا ہے۔ اس انتباہ کو خاموش کرنے کے لیے، **ڈیوائس کی قسم** کو ذیل میں معاون اقسام میں سے کسی ایک (واشنگ مشین، ڈرائر، واشر ڈرائر کومبو، ڈش واشر، ایئر فرائر، بریڈ میکر، یا پمپ)، یا **دیگر (ایڈوانسڈ)** پر سوئچ کریں اگر آپ کا آلہ کسی بھی معاون قسم سے مماثل نہیں ہے۔ **دیگر (ایڈوانسڈ)** جان بوجھ کر جنرک ڈیفالٹس بھیجتے ہیں جو کسی مخصوص آلات کے لیے نہیں بنائے جاتے ہیں، اس لیے آپ کو حد، ٹائم آؤٹ، اور مماثل پیرامیٹرز خود ترتیب دینے کی ضرورت ہوگی۔ جب آپ سوئچ کرتے ہیں تو آپ کی تمام موجودہ ترتیبات محفوظ رہتی ہیں۔", + "phase_other_device_types": "ڈیوائس کی دیگر اقسام:" + } + }, + "interactive_editor_action": { + "options": { + "split": "ایک سائیکل کو تقسیم کریں (خالی تلاش کریں)", + "merge": "سائیکلوں کو ضم کریں (ٹکڑوں میں شامل ہوں)", + "delete": "سائیکل کو حذف کریں" + } + }, + "split_mode": { + "options": { + "auto": "خودکار طور پر بیکار خلا تلاش کریں", + "manual": "دستی ٹائم اسٹیمپ" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "دیکھ بھال: دوبارہ عمل اور ڈیٹا کو بہتر بنائیں", + "clear_debug_data": "ڈیبگ ڈیٹا صاف کریں (جگہ خالی کریں)", + "wipe_history": "اس آلہ کا تمام ڈیٹا صاف کریں (ناقابل واپسی)", + "export_import": "ترتیبات کے ساتھ JSON برآمد/درآمد کریں (کاپی/پیسٹ)" + } + }, + "export_import_mode": { + "options": { + "export": "تمام ڈیٹا ایکسپورٹ کریں۔", + "import": "ڈیٹا کو درآمد/ضم کریں۔" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "پرانے سائیکلوں کو آٹو لیبل کریں۔", + "select_cycle_to_label": "لیبل مخصوص سائیکل", + "select_cycle_to_delete": "سائیکل کو حذف کریں۔", + "interactive_editor": "انٹرایکٹو ایڈیٹر کو ضم/تقسیم کریں۔", + "trim_cycle": "ٹرم سائیکل ڈیٹا" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "نیا پروفائل بنائیں", + "edit_profile": "پروفائل میں ترمیم/نام تبدیل کریں۔", + "delete_profile": "پروفائل حذف کریں۔", + "profile_stats": "پروفائل کے اعدادوشمار", + "cleanup_profile": "کلین اپ ہسٹری - گراف اور ڈیلیٹ کریں۔", + "assign_phases": "فیز رینجز تفویض کریں۔" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "نیا مرحلہ بنائیں", + "edit_custom_phase": "فیز میں ترمیم کریں۔", + "delete_custom_phase": "مرحلہ حذف کریں۔" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "فیز رینج شامل کریں۔", + "edit_range": "فیز رینج میں ترمیم کریں۔", + "delete_range": "فیز رینج کو حذف کریں۔", + "clear_ranges": "تمام حدود کو صاف کریں۔", + "auto_detect_ranges": "مراحل کا خود بخود پتہ لگائیں۔", + "save_ranges": "محفوظ کریں اور واپس کریں۔" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "ریفریش اسٹیٹس", + "stop_recording": "ریکارڈنگ بند کریں (محفوظ کریں اور عمل کریں)", + "start_recording": "نئی ریکارڈنگ شروع کریں۔", + "process_recording": "آخری ریکارڈنگ پر عمل کریں (تراشیں اور محفوظ کریں)", + "discard_recording": "آخری ریکارڈنگ کو مسترد کریں۔" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "نیا پروفائل بنائیں", + "existing_profile": "موجودہ پروفائل میں شامل کریں۔", + "discard": "ریکارڈنگ کو مسترد کریں۔" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "تصدیق کریں - درست پتہ لگانا", + "correct": "درست - پروگرام/دورانیہ کا انتخاب کریں۔", + "ignore": "نظر انداز کریں - غلط مثبت/شور کا چکر", + "delete": "حذف کریں - تاریخ سے ہٹا دیں۔" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "نام کریں اور مراحل کا اطلاق کریں۔", + "cancel": "بغیر کسی تبدیلی کے واپس جائیں۔" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "سٹارٹ پوائنٹ سیٹ کریں۔", + "set_end": "اختتامی نقطہ مقرر کریں۔", + "reset": "مکمل دورانیہ پر ری سیٹ کریں۔", + "apply": "ٹرم لگائیں۔", + "cancel": "منسوخ کریں۔" + } + }, + "device_type": { + "options": { + "washing_machine": "واشنگ مشین", + "dryer": "ڈرائر", + "washer_dryer": "واشر ڈرائر کومبو", + "dishwasher": "ڈش واشر", + "coffee_machine": "کافی مشین (فرسودہ)", + "ev": "الیکٹرک وہیکل (فرسودہ)", + "air_fryer": "ایئر فریئر", + "heat_pump": "ہیٹ پمپ (فرسودہ)", + "bread_maker": "روٹی بنانے والا", + "pump": "پمپ / سمپ پمپ", + "oven": "تندور (فرسودہ)", + "other": "دیگر (جدید)" + } + } + }, + "services": { + "label_cycle": { + "name": "لیبل سائیکل", + "description": "ایک موجودہ پروفائل کو ماضی کے دور میں تفویض کریں، یا لیبل کو ہٹا دیں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "لیبل لگانے کے لیے واشنگ مشین ڈیوائس۔" + }, + "cycle_id": { + "name": "سائیکل ID", + "description": "لیبل لگانے کے لیے سائیکل کی ID۔" + }, + "profile_name": { + "name": "پروفائل کا نام", + "description": "موجودہ پروفائل کا نام (منیج پروفائلز مینو میں پروفائلز بنائیں)۔ لیبل ہٹانے کے لیے خالی چھوڑ دیں۔" + } + } + }, + "create_profile": { + "name": "پروفائل بنائیں", + "description": "ایک نیا پروفائل بنائیں (اسٹینڈ لون یا ریفرنس سائیکل پر مبنی)۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واشنگ مشین کا آلہ۔" + }, + "profile_name": { + "name": "پروفائل کا نام", + "description": "نئے پروفائل کے لیے نام (مثلاً 'ہیوی ڈیوٹی'، 'ڈیلیکیٹس')۔" + }, + "reference_cycle_id": { + "name": "حوالہ سائیکل ID", + "description": "اس پروفائل کی بنیاد رکھنے کے لیے اختیاری سائیکل ID۔" + } + } + }, + "delete_profile": { + "name": "پروفائل حذف کریں۔", + "description": "پروفائل کو حذف کریں اور اختیاری طور پر اس کا استعمال کرتے ہوئے سائیکلوں کو غیر لیبل کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واشنگ مشین کا آلہ۔" + }, + "profile_name": { + "name": "پروفائل کا نام", + "description": "حذف کرنے والا پروفائل۔" + }, + "unlabel_cycles": { + "name": "سائیکلوں کو غیر لیبل کریں۔", + "description": "اس پروفائل کا استعمال کرتے ہوئے سائیکلوں سے پروفائل لیبل کو ہٹا دیں۔" + } + } + }, + "auto_label_cycles": { + "name": "پرانے سائیکلوں کو آٹو لیبل کریں۔", + "description": "پروفائل مماثلت کا استعمال کرتے ہوئے سابقہ ​​طور پر بغیر لیبل والے سائیکلوں کو لیبل کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واشنگ مشین کا آلہ۔" + }, + "confidence_threshold": { + "name": "اعتماد کی حد", + "description": "لیبل لگانے کے لیے کم از کم میچ کا اعتماد (0.50-0.95)۔" + } + } + }, + "export_config": { + "name": "ترتیب برآمد کریں۔", + "description": "اس ڈیوائس کے پروفائلز، سائیکلز اور سیٹنگز کو JSON فائل (فی ڈیوائس) میں ایکسپورٹ کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "برآمد کرنے کے لیے واشنگ مشین ڈیوائس۔" + }, + "path": { + "name": "راستہ", + "description": "لکھنے کے لیے اختیاری مطلق فائل کا راستہ (/config/ha_washdata_export_{entry}.json کے لیے پہلے سے طے شدہ)۔" + } + } + }, + "import_config": { + "name": "ترتیب درآمد کریں۔", + "description": "JSON ایکسپورٹ فائل سے اس ڈیوائس کے لیے پروفائلز، سائیکل اور سیٹنگز درآمد کریں (ترتیبات اوور رائٹ ہو سکتی ہیں)۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واشنگ مشین کا آلہ جس میں درآمد کرنا ہے۔" + }, + "path": { + "name": "راستہ", + "description": "درآمد کرنے کے لیے برآمد JSON فائل کا مطلق راستہ۔" + } + } + }, + "submit_cycle_feedback": { + "name": "سائیکل فیڈ بیک جمع کروائیں۔", + "description": "مکمل سائیکل کے بعد خود بخود پتہ چلنے والے پروگرام کی تصدیق یا درست کریں۔ یا تو `entry_id` (جدید) یا `device_id` (تجویز کردہ) فراہم کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واش ڈیٹا ڈیوائس (entry_id کے بجائے تجویز کردہ)۔" + }, + "entry_id": { + "name": "اندراج کی شناخت", + "description": "ڈیوائس کے لیے کنفیگریشن انٹری آئی ڈی (device_id کا متبادل)۔" + }, + "cycle_id": { + "name": "سائیکل ID", + "description": "فیڈ بیک نوٹیفکیشن / لاگز میں دکھائی گئی سائیکل آئی ڈی۔" + }, + "user_confirmed": { + "name": "دریافت شدہ پروگرام کی تصدیق کریں۔", + "description": "درست سیٹ کریں اگر پتہ چلا پروگرام درست ہے۔" + }, + "corrected_profile": { + "name": "درست شدہ پروفائل", + "description": "اگر تصدیق نہ ہو تو درست پروفائل/پروگرام کا نام فراہم کریں۔" + }, + "corrected_duration": { + "name": "درست دورانیہ (سیکنڈ)", + "description": "سیکنڈوں میں اختیاری درست شدہ دورانیہ۔" + }, + "notes": { + "name": "نوٹس", + "description": "اس سائیکل کے بارے میں اختیاری نوٹ۔" + } + } + }, + "record_start": { + "name": "ریکارڈ سائیکل اسٹارٹ", + "description": "کلین سائیکل کو دستی طور پر ریکارڈ کرنا شروع کریں (تمام مماثل منطق کو نظرانداز کرتا ہے)۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "ریکارڈ کرنے کے لیے واشنگ مشین کا آلہ۔" + } + } + }, + "record_stop": { + "name": "سائیکل سٹاپ کو ریکارڈ کریں۔", + "description": "دستی ریکارڈنگ بند کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "ریکارڈنگ کو روکنے کے لیے واشنگ مشین کا آلہ۔" + } + } + }, + "trim_cycle": { + "name": "ٹرم سائیکل", + "description": "ماضی کے سائیکل کے پاور ڈیٹا کو مخصوص ٹائم ونڈو میں ٹرم کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واش ڈیٹا ڈیوائس۔" + }, + "cycle_id": { + "name": "سائیکل ID", + "description": "ٹرم کرنے کے لیے سائیکل کی ID۔" + }, + "trim_start_s": { + "name": "ٹرم اسٹارٹ (سیکنڈ)", + "description": "اس آفسیٹ سے ڈیٹا کو سیکنڈوں میں رکھیں۔ ڈیفالٹ 0۔" + }, + "trim_end_s": { + "name": "ٹرم اینڈ (سیکنڈ)", + "description": "ڈیٹا کو سیکنڈوں میں اس آفسیٹ تک رکھیں۔ ڈیفالٹ = پوری مدت۔" + } + } + }, + "pause_cycle": { + "name": "سائیکل موقوف کریں۔", + "description": "واش ڈیٹا ڈیوائس کے لیے ایکٹو سائیکل کو روک دیں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "موقوف کرنے کے لیے واش ڈیٹا ڈیوائس۔" + } + } + }, + "resume_cycle": { + "name": "سائیکل دوبارہ شروع کریں۔", + "description": "WashData ڈیوائس کے لیے موقوف شدہ سائیکل دوبارہ شروع کریں۔", + "fields": { + "device_id": { + "name": "ڈیوائس", + "description": "واش ڈیٹا ڈیوائس دوبارہ شروع کرنا ہے۔" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "چل رہا ہے۔" + }, + "match_ambiguity": { + "name": "میچ ابہام" + } + }, + "sensor": { + "washer_state": { + "name": "ریاست", + "state": { + "off": "آف", + "idle": "بیکار", + "starting": "شروع ہو رہا ہے۔", + "running": "چل رہا ہے۔", + "paused": "روک دیا گیا", + "user_paused": "صارف کے ذریعے روکا گیا", + "ending": "ختم ہونے والا", + "finished": "ختم", + "anti_wrinkle": "اینٹی شیکن", + "delay_wait": "شروع ہونے کا انتظار ہے۔", + "interrupted": "خلل ڈالا۔", + "force_stopped": "زبردستی روکا گیا۔", + "rinse": "کلی کریں۔", + "unknown": "نامعلوم", + "clean": "صاف" + } + }, + "washer_program": { + "name": "پروگرام" + }, + "time_remaining": { + "name": "باقی وقت" + }, + "total_duration": { + "name": "کل دورانیہ" + }, + "cycle_progress": { + "name": "پیش رفت" + }, + "current_power": { + "name": "کرنٹ پاور" + }, + "elapsed_time": { + "name": "گزرا ہوا وقت" + }, + "debug_info": { + "name": "ڈیبگ کی معلومات" + }, + "match_confidence": { + "name": "اعتماد کا میچ" + }, + "top_candidates": { + "name": "سرفہرست امیدوار", + "state": { + "none": "کوئی نہیں۔" + } + }, + "current_phase": { + "name": "موجودہ مرحلہ" + }, + "wash_phase": { + "name": "مرحلہ" + }, + "profile_cycle_count": { + "name": "پروفائل {profile_name} شمار", + "unit_of_measurement": "سائیکل" + }, + "suggestions": { + "name": "تجویز کردہ ترتیبات" + }, + "pump_runs_today": { + "name": "پمپ رن (آخری 24 گھنٹے)" + }, + "cycle_count": { + "name": "سائیکل شمار" + } + }, + "select": { + "program_select": { + "name": "سائیکل پروگرام", + "state": { + "auto_detect": "خودکار پتہ لگانا" + } + } + }, + "button": { + "force_end_cycle": { + "name": "زبردستی اختتامی سائیکل" + }, + "pause_cycle": { + "name": "سائیکل موقوف کریں۔" + }, + "resume_cycle": { + "name": "سائیکل دوبارہ شروع کریں۔" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "ایک درست device_id درکار ہے۔" + }, + "cycle_id_required": { + "message": "ایک درست cycle_id درکار ہے۔" + }, + "profile_name_required": { + "message": "ایک درست پروفائل_نام درکار ہے۔" + }, + "device_not_found": { + "message": "آلہ نہیں ملا۔" + }, + "no_config_entry": { + "message": "ڈیوائس کے لیے کوئی کنفیگریشن اندراج نہیں ملا۔" + }, + "integration_not_loaded": { + "message": "انٹیگریشن اس ڈیوائس کے لیے لوڈ نہیں ہے۔" + }, + "cycle_not_found_or_no_power": { + "message": "سائیکل نہیں ملا یا کوئی پاور ڈیٹا نہیں ہے۔" + }, + "trim_failed_empty_window": { + "message": "ٹرم ناکام ہو گئی - سائیکل نہیں ملا، کوئی پاور ڈیٹا نہیں، یا نتیجے میں آنے والی ونڈو خالی ہے۔" + }, + "trim_invalid_range": { + "message": "trim_end_s trim_start_s سے بڑا ہونا چاہیے۔" + } + } +} diff --git a/custom_components/ha_washdata/translations/vi.json b/custom_components/ha_washdata/translations/vi.json new file mode 100644 index 0000000..f1b8a0b --- /dev/null +++ b/custom_components/ha_washdata/translations/vi.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "Cài đặt WashData", + "description": "Cấu hình máy giặt hoặc thiết bị khác của bạn.\n\nCần có cảm biến nguồn.\n\n**Tiếp theo, bạn sẽ được hỏi xem bạn có muốn tạo hồ sơ đầu tiên của mình không.**", + "data": { + "name": "Tên thiết bị", + "device_type": "Loại thiết bị", + "power_sensor": "Cảm biến nguồn", + "min_power": "Ngưỡng công suất tối thiểu (W)" + }, + "data_description": { + "name": "Tên thân thiện cho thiết bị này (ví dụ: 'Máy giặt', 'Máy rửa chén').", + "device_type": "Đây là loại thiết bị gì? Giúp phát hiện và ghi nhãn phù hợp.", + "power_sensor": "Thực thể cảm biến báo cáo mức tiêu thụ điện năng theo thời gian thực (tính bằng watt) từ phích cắm thông minh của bạn.", + "min_power": "Chỉ số công suất trên ngưỡng này (tính bằng watt) cho biết thiết bị đang chạy. Bắt đầu với 2W cho hầu hết các thiết bị." + } + }, + "first_profile": { + "title": "Tạo hồ sơ đầu tiên", + "description": "**Chu kỳ** là một lần chạy hoàn chỉnh của thiết bị của bạn (ví dụ: một mẻ đồ giặt). **Hồ sơ** nhóm các chu trình này theo loại (ví dụ: 'Cotton', 'Giặt nhanh'). Việc tạo hồ sơ hiện giúp ước tính thời lượng tích hợp ngay lập tức.\n\nBạn có thể chọn cấu hình này theo cách thủ công trong phần điều khiển thiết bị nếu nó không được tự động phát hiện.\n\n**Để trống 'Tên hồ sơ' để bỏ qua bước này.**", + "data": { + "profile_name": "Tên hồ sơ (Tùy chọn)", + "manual_duration": "Thời lượng ước tính (phút)" + } + } + }, + "error": { + "cannot_connect": "Không thể kết nối", + "invalid_auth": "Xác thực không hợp lệ", + "unknown": "Lỗi không mong muốn" + }, + "abort": { + "already_configured": "Thiết bị đã được định cấu hình" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "Xem lại phản hồi học tập", + "settings": "Cài đặt", + "notifications": "Thông báo", + "manage_cycles": "Quản lý chu kỳ", + "manage_profiles": "Quản lý hồ sơ", + "manage_phase_catalog": "Quản lý danh mục giai đoạn", + "record_cycle": "Chu kỳ ghi (Thủ công)", + "diagnostics": "Chẩn đoán & Bảo trì" + } + }, + "apply_suggestions": { + "title": "Sao chép các giá trị được đề xuất", + "description": "Thao tác này sẽ sao chép các giá trị được đề xuất hiện tại vào Cài đặt đã lưu của bạn. Đây là hành động một lần và không cho phép ghi đè tự động.\n\nCác giá trị được đề xuất để sao chép:\n{suggested}", + "data": { + "confirm": "Xác nhận hành động sao chép" + } + }, + "apply_suggestions_confirm": { + "title": "Xem lại các thay đổi được đề xuất", + "description": "WashData đã tìm thấy **{pending_count}** giá trị được đề xuất để áp dụng.\n\nThay đổi:\n{changes}\n\n✅ Đánh dấu vào ô bên dưới và nhấp vào **Gửi** để áp dụng và lưu những thay đổi này ngay lập tức.\n❌ Bỏ chọn và nhấp vào **Gửi** để hủy và quay lại.", + "data": { + "confirm_apply_suggestions": "Áp dụng và lưu những thay đổi này" + } + }, + "settings": { + "title": "Cài đặt", + "description": "{deprecation_warning}Điều chỉnh các ngưỡng phát hiện, học tập và hành vi nâng cao. Mặc định hoạt động tốt cho hầu hết các thiết lập.\n\nCác cài đặt được đề xuất hiện có sẵn: {suggestions_count}.\nNếu giá trị này lớn hơn 0, hãy mở cài đặt nâng cao và dùng 'Áp dụng các giá trị được đề xuất' để xem lại các đề xuất.", + "data": { + "apply_suggestions": "Áp dụng các giá trị được đề xuất", + "device_type": "Loại thiết bị", + "power_sensor": "Thực thể cảm biến nguồn", + "min_power": "Công suất tối thiểu (W)", + "off_delay": "Độ trễ kết thúc chu kỳ (s)", + "notify_actions": "Hành động thông báo", + "notify_people": "Trì hoãn giao hàng cho đến khi những người này về nhà", + "notify_only_when_home": "Trì hoãn thông báo cho đến khi người được chọn ở nhà", + "notify_fire_events": "Kích hoạt sự kiện tự động hóa", + "notify_start_services": "Bắt đầu chu kỳ - Mục tiêu thông báo", + "notify_finish_services": "Kết thúc chu kỳ - Mục tiêu thông báo", + "notify_live_services": "Tiến trình trực tiếp - Mục tiêu thông báo", + "notify_before_end_minutes": "Thông báo trước khi hoàn thành (phút trước khi kết thúc)", + "notify_title": "Tiêu đề thông báo", + "notify_icon": "Biểu tượng thông báo", + "notify_start_message": "Bắt đầu định dạng tin nhắn", + "notify_finish_message": "Hoàn tất định dạng tin nhắn", + "notify_pre_complete_message": "Định dạng tin nhắn trước khi hoàn thành", + "show_advanced": "Chỉnh sửa cài đặt nâng cao (Bước tiếp theo)" + }, + "data_description": { + "apply_suggestions": "Chọn hộp này để sao chép các giá trị do thuật toán học đề xuất vào biểu mẫu. Xem lại trước khi lưu.\n\nĐề xuất (từ học tập):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "Chọn loại thiết bị (Máy giặt, Máy sấy, Máy rửa chén, Máy pha cà phê, EV). Thẻ này được lưu trữ theo từng chu kỳ để tổ chức tốt hơn.", + "power_sensor": "Thực thể cảm biến báo cáo công suất theo thời gian thực (tính bằng watt). Bạn có thể thay đổi điều này bất kỳ lúc nào mà không làm mất dữ liệu lịch sử-hữu ích nếu bạn thay thế phích cắm thông minh.", + "min_power": "Chỉ số công suất trên ngưỡng này (tính bằng watt) cho biết thiết bị đang chạy. Bắt đầu với 2W cho hầu hết các thiết bị.", + "off_delay": "Số giây công suất được làm mịn phải ở dưới ngưỡng dừng để đánh dấu mức hoàn thành. Mặc định: 120s.", + "notify_actions": "Chuỗi hành động Home Assistant tùy chọn để chạy thông báo (tập lệnh, thông báo, TTS, v.v.). Nếu được đặt, các hành động sẽ chạy trước dịch vụ thông báo dự phòng.", + "notify_people": "Các thực thể con người được sử dụng để kiểm soát sự hiện diện. Khi được bật, việc gửi thông báo sẽ bị trì hoãn cho đến khi có ít nhất một người được chọn ở nhà.", + "notify_only_when_home": "Nếu được bật, hãy trì hoãn thông báo cho đến khi có ít nhất một người được chọn ở nhà.", + "notify_fire_events": "Nếu được bật, hãy kích hoạt các sự kiện Trợ lý gia đình để bắt đầu và kết thúc chu kỳ (ha_washdata_cycle_started và ha_washdata_cycle_ended) để thúc đẩy quá trình tự động hóa.", + "notify_start_services": "Một hoặc nhiều dịch vụ thông báo để cảnh báo khi một chu kỳ bắt đầu. Để trống để bỏ qua thông báo bắt đầu.", + "notify_finish_services": "Một hoặc nhiều dịch vụ thông báo để cảnh báo khi một chu trình kết thúc hoặc gần hoàn thành. Để trống để bỏ qua thông báo kết thúc.", + "notify_live_services": "Một hoặc nhiều dịch vụ thông báo để nhận cập nhật tiến độ trực tiếp trong khi chu kỳ chạy (chỉ ứng dụng đồng hành trên thiết bị di động). Để trống để bỏ qua cập nhật trực tiếp.", + "notify_before_end_minutes": "Vài phút trước khi kết thúc chu kỳ ước tính để kích hoạt thông báo. Đặt thành 0 để tắt. Mặc định: 0.", + "notify_title": "Tiêu đề tùy chỉnh cho thông báo. Hỗ trợ trình giữ chỗ {device}.", + "notify_icon": "Biểu tượng dùng cho thông báo (ví dụ: mdi:washing-machine). Để trống để gửi không có biểu tượng.", + "notify_start_message": "Tin nhắn được gửi khi một chu kỳ bắt đầu. Hỗ trợ trình giữ chỗ {device}.", + "notify_finish_message": "Tin nhắn được gửi khi một chu kỳ kết thúc. Hỗ trợ các phần giữ chỗ {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "notify_pre_complete_message": "Văn bản cập nhật tiến trình trực tiếp định kỳ trong khi chu kỳ chạy. Hỗ trợ phần giữ chỗ {device}, {minutes}, {program}." + } + }, + "notifications": { + "title": "Thông báo", + "description": "Định cấu hình các kênh thông báo, mẫu và cập nhật tiến trình di động trực tiếp tùy chọn.", + "data": { + "notify_actions": "Hành động thông báo", + "notify_people": "Trì hoãn giao hàng cho đến khi những người này về nhà", + "notify_only_when_home": "Trì hoãn thông báo cho đến khi người được chọn ở nhà", + "notify_fire_events": "Kích hoạt sự kiện tự động hóa", + "notify_start_services": "Bắt đầu chu kỳ - Mục tiêu thông báo", + "notify_finish_services": "Kết thúc chu kỳ - Mục tiêu thông báo", + "notify_live_services": "Tiến trình trực tiếp - Mục tiêu thông báo", + "notify_before_end_minutes": "Thông báo trước khi hoàn thành (phút trước khi kết thúc)", + "notify_live_interval_seconds": "Khoảng thời gian cập nhật trực tiếp (giây)", + "notify_live_overrun_percent": "Phụ cấp tràn cập nhật trực tiếp (%)", + "notify_live_chronometer": "Đồng hồ đếm ngược Chronometer", + "notify_title": "Tiêu đề thông báo", + "notify_icon": "Biểu tượng thông báo", + "notify_start_message": "Bắt đầu định dạng tin nhắn", + "notify_finish_message": "Hoàn tất định dạng tin nhắn", + "notify_pre_complete_message": "Định dạng tin nhắn trước khi hoàn thành", + "energy_price_entity": "Giá năng lượng - Thực thể (tùy chọn)", + "energy_price_static": "Giá năng lượng - Giá trị tĩnh (tùy chọn)", + "go_back": "Quay lại mà không lưu", + "notify_reminder_message": "Định dạng tin nhắn nhắc nhở", + "notify_timeout_seconds": "Tự động loại bỏ sau (giây)", + "notify_channel": "Kênh thông báo (trạng thái/trực tiếp/nhắc nhở)", + "notify_finish_channel": "Kênh thông báo đã hoàn thành" + }, + "data_description": { + "go_back": "Chọn mục này rồi nhấp Gửi để bỏ mọi thay đổi ở trên và quay lại menu chính.", + "notify_actions": "Chuỗi hành động Home Assistant tùy chọn để chạy thông báo (tập lệnh, thông báo, TTS, v.v.). Nếu được đặt, các hành động sẽ chạy trước dịch vụ thông báo dự phòng.", + "notify_people": "Các thực thể con người được sử dụng để kiểm soát sự hiện diện. Khi được bật, việc gửi thông báo sẽ bị trì hoãn cho đến khi có ít nhất một người được chọn ở nhà.", + "notify_only_when_home": "Nếu được bật, hãy trì hoãn thông báo cho đến khi có ít nhất một người được chọn ở nhà.", + "notify_fire_events": "Nếu được bật, hãy kích hoạt các sự kiện Trợ lý gia đình để bắt đầu và kết thúc chu kỳ (ha_washdata_cycle_started và ha_washdata_cycle_ended) để thúc đẩy quá trình tự động hóa.", + "notify_start_services": "Một hoặc nhiều dịch vụ thông báo để cảnh báo khi một chu kỳ bắt đầu (ví dụ: notification.mobile_app_my_phone). Để trống để bỏ qua thông báo bắt đầu.", + "notify_finish_services": "Một hoặc nhiều dịch vụ thông báo để cảnh báo khi một chu trình kết thúc hoặc gần hoàn thành. Để trống để bỏ qua thông báo kết thúc.", + "notify_live_services": "Một hoặc nhiều dịch vụ thông báo để nhận cập nhật tiến độ trực tiếp trong khi chu kỳ chạy (chỉ ứng dụng đồng hành trên thiết bị di động). Để trống để bỏ qua cập nhật trực tiếp.", + "notify_before_end_minutes": "Vài phút trước khi kết thúc chu kỳ ước tính để kích hoạt thông báo. Đặt thành 0 để tắt. Mặc định: 0.", + "notify_live_interval_seconds": "Tần suất cập nhật tiến độ được gửi trong khi chạy. Giá trị thấp hơn phản hồi nhanh hơn nhưng tiêu thụ nhiều thông báo hơn. Tối thiểu 30 giây được thực thi - các giá trị dưới 30 giây sẽ tự động được làm tròn thành 30 giây.", + "notify_live_overrun_percent": "Biên độ an toàn cao hơn số lượng cập nhật ước tính cho các chu kỳ dài/chạy quá mức (ví dụ: 20% cho phép cập nhật ước tính gấp 1,2 lần).", + "notify_live_chronometer": "Khi được bật, mỗi bản cập nhật trực tiếp sẽ bao gồm đồng hồ đếm ngược thời gian cho đến thời gian kết thúc ước tính. Đồng hồ thông báo tự động đếm ngược trên thiết bị giữa các lần cập nhật (chỉ ứng dụng đồng hành với Android).", + "notify_title": "Tiêu đề tùy chỉnh cho thông báo. Hỗ trợ trình giữ chỗ {device}.", + "notify_icon": "Biểu tượng dùng cho thông báo (ví dụ: mdi:washing-machine). Để trống để gửi không có biểu tượng.", + "notify_start_message": "Tin nhắn được gửi khi một chu kỳ bắt đầu. Hỗ trợ trình giữ chỗ {device}.", + "notify_finish_message": "Tin nhắn được gửi khi một chu kỳ kết thúc. Hỗ trợ các phần giữ chỗ {device}, {duration}, {program}, {energy_kwh}, {cost}.", + "energy_price_entity": "Chọn một thực thể số chứa giá điện hiện tại (ví dụ: cảm biến.điện_price, input_number.energy_rate). Khi được đặt, hãy bật trình giữ chỗ {cost}. Được ưu tiên hơn giá trị tĩnh nếu cả hai đều được cấu hình.", + "energy_price_static": "Giá điện cố định trên mỗi kWh. Khi được đặt, hãy bật trình giữ chỗ {cost}. Bị bỏ qua nếu một thực thể được định cấu hình ở trên. Thêm ký hiệu tiền tệ của bạn vào mẫu tin nhắn, ví dụ: {cost} €.", + "notify_reminder_message": "Văn bản của lời nhắc một lần đã gửi số phút đã định cấu hình trước khi kết thúc dự kiến. Khác biệt với các cập nhật trực tiếp định kỳ ở trên. Hỗ trợ các phần giữ chỗ {device}, {minutes}, {program}.", + "notify_timeout_seconds": "Tự động loại bỏ thông báo sau nhiều giây này (được chuyển tiếp dưới dạng 'hết thời gian chờ' của ứng dụng đồng hành). Đặt thành 0 để giữ chúng cho đến khi bị loại bỏ. Mặc định: 0.", + "notify_channel": "Android kênh thông báo ứng dụng đồng hành để biết trạng thái, tiến trình trực tiếp và thông báo lời nhắc. Âm thanh và tầm quan trọng của kênh được định cấu hình trong ứng dụng đồng hành khi sử dụng tên kênh lần đầu tiên - WashData chỉ đặt tên kênh. Để trống cho mặc định của ứng dụng.", + "notify_finish_channel": "Tách kênh Android cho cảnh báo kết thúc (cũng được sử dụng bởi lời nhắc và lời cằn nhằn khi chờ giặt) để kênh có thể có âm thanh riêng. Để trống để sử dụng lại kênh ở trên.", + "notify_pre_complete_message": "Văn bản cập nhật tiến trình trực tiếp định kỳ trong khi chu kỳ chạy. Hỗ trợ phần giữ chỗ {device}, {minutes}, {program}." + } + }, + "advanced_settings": { + "title": "Cài đặt nâng cao", + "description": "Điều chỉnh các ngưỡng phát hiện, học tập và hành vi nâng cao. Mặc định hoạt động tốt cho hầu hết các thiết lập.", + "sections": { + "suggestions_section": { + "name": "Cài đặt đề xuất", + "description": "Có {suggestions_count} đề xuất đã học được. Hãy chọn 'Áp dụng các giá trị được đề xuất' để xem lại các thay đổi đề xuất trước khi lưu.", + "data": { + "apply_suggestions": "Áp dụng các giá trị được đề xuất" + }, + "data_description": { + "apply_suggestions": "Chọn hộp này để mở bước xem xét các giá trị được đề xuất từ ​​thuật toán học. Không có gì được lưu tự động.\n\nĐề xuất (từ học tập):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "Phát hiện và ngưỡng công suất", + "description": "Khi nào thiết bị được coi là BẬT hoặc TẮT, các ngưỡng năng lượng và thời điểm chuyển trạng thái.", + "data": { + "start_duration_threshold": "Bắt đầu thời lượng gỡ lỗi (các)", + "start_energy_threshold": "Cổng năng lượng khởi động (Wh)", + "completion_min_seconds": "Thời gian chạy tối thiểu hoàn thành (giây)", + "start_threshold_w": "Ngưỡng bắt đầu (W)", + "stop_threshold_w": "Ngưỡng dừng (W)", + "end_energy_threshold": "Cổng năng lượng cuối (Wh)", + "running_dead_zone": "Chạy vùng chết (giây)", + "end_repeat_count": "Số lần lặp lại kết thúc", + "min_off_gap": "Khoảng cách tối thiểu giữa các chu kỳ", + "sampling_interval": "Khoảng thời gian lấy mẫu (s)" + }, + "data_description": { + "start_duration_threshold": "Bỏ qua các đợt tăng sức mạnh ngắn hơn khoảng thời gian này (giây). Lọc ra các xung khởi động (ví dụ: 60W trong 2 giây) trước khi chu kỳ thực bắt đầu. Mặc định: 5s.", + "start_energy_threshold": "Chu trình phải tích lũy ít nhất mức năng lượng (Wh) này trong giai đoạn BẮT ĐẦU để được xác nhận. Mặc định: 0,005 Wh.", + "completion_min_seconds": "Các chu kỳ ngắn hơn thời gian này sẽ được đánh dấu là 'bị gián đoạn' ngay cả khi chúng kết thúc một cách tự nhiên. Mặc định: 600s.", + "start_threshold_w": "Công suất phải tăng TRÊN ngưỡng này để BẮT ĐẦU một chu kỳ. Đặt cao hơn nguồn điện dự phòng của bạn. Mặc định: min_power + 1 W.", + "stop_threshold_w": "Công suất phải giảm xuống DƯỚI ngưỡng này để KẾT THÚC một chu kỳ. Đặt ngay trên 0 để tránh tiếng ồn gây ra các đầu sai. Mặc định: 0,6 * min_power.", + "end_energy_threshold": "Chu kỳ chỉ kết thúc nếu năng lượng trong cửa sổ Tắt trễ cuối cùng thấp hơn giá trị này (Wh). Ngăn chặn các đầu sai nếu nguồn điện dao động gần bằng 0. Mặc định: 0,05 Wh.", + "running_dead_zone": "Sau khi chu kỳ bắt đầu, hãy bỏ qua tình trạng mất điện trong nhiều giây này để ngăn chặn việc phát hiện sai đầu cuối trong giai đoạn khởi động ban đầu. Đặt thành 0 để tắt. Mặc định: 0s.", + "end_repeat_count": "Số lần điều kiện kết thúc (công suất dưới ngưỡng cho off_delay) phải được đáp ứng liên tiếp trước khi kết thúc chu kỳ. Giá trị cao hơn ngăn chặn kết thúc sai trong quá trình tạm dừng. Mặc định: 1.", + "min_off_gap": "Nếu một chu kỳ bắt đầu trong vòng nhiều giây này kể từ khi kết thúc chu kỳ trước đó, chúng sẽ được HỢP NHẤT thành một chu kỳ duy nhất.", + "sampling_interval": "Khoảng thời gian lấy mẫu tối thiểu tính bằng giây. Cập nhật nhanh hơn tốc độ này sẽ bị bỏ qua. Mặc định: 30 giây (2 giây đối với máy giặt, máy giặt-sấy và máy rửa chén)." + } + }, + "matching_section": { + "name": "Khớp hồ sơ và học tập", + "description": "WashData sẽ khớp các chu kỳ đang chạy với các hồ sơ đã học một cách quyết liệt đến mức nào và khi nào cần hỏi phản hồi.", + "data": { + "profile_match_min_duration_ratio": "Tỷ lệ thời lượng tối thiểu phù hợp với hồ sơ (0,1-1,0)", + "profile_match_interval": "Khoảng thời gian khớp hồ sơ (giây)", + "profile_match_threshold": "Ngưỡng đối sánh hồ sơ", + "profile_unmatch_threshold": "Ngưỡng không khớp hồ sơ", + "auto_label_confidence": "Độ tin cậy của nhãn tự động (0-1)", + "learning_confidence": "Độ tin cậy của yêu cầu phản hồi (0-1)", + "suppress_feedback_notifications": "Ngăn chặn thông báo phản hồi", + "duration_tolerance": "Dung sai thời lượng (0-0,5)", + "smoothing_window": "Làm mịn cửa sổ (mẫu)", + "profile_duration_tolerance": "Dung sai thời lượng khớp hồ sơ (0-0,5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "Tỷ lệ thời lượng tối thiểu để khớp hồ sơ. Chu kỳ chạy ít nhất phải bằng khoảng thời gian này của hồ sơ để khớp. Thấp hơn = khớp sớm hơn. Mặc định: 0,50 (50%).", + "profile_match_interval": "Tần suất (tính bằng giây) để thử khớp hồ sơ trong một chu kỳ. Giá trị thấp hơn giúp phát hiện nhanh hơn nhưng sử dụng nhiều CPU hơn. Mặc định: 300s (5 phút).", + "profile_match_threshold": "Cần có điểm tương tự DTW tối thiểu (0,0–1,0) để coi một hồ sơ là phù hợp. Cao hơn = kết hợp chặt chẽ hơn, ít kết quả sai hơn. Mặc định: 0,4.", + "profile_unmatch_threshold": "Điểm tương tự DTW (0,0–1,0) mà dưới mức đó hồ sơ phù hợp trước đó sẽ bị từ chối. Nên thấp hơn ngưỡng phù hợp để tránh hiện tượng nhấp nháy. Mặc định: 0,35.", + "auto_label_confidence": "Tự động gắn nhãn chu kỳ ở mức hoặc cao hơn độ tin cậy này khi hoàn thành (0,0–1,0).", + "learning_confidence": "Độ tin cậy tối thiểu để yêu cầu xác minh người dùng thông qua một sự kiện (0,0–1,0).", + "suppress_feedback_notifications": "Khi được bật, WashData sẽ vẫn theo dõi và yêu cầu phản hồi nội bộ nhưng sẽ không hiển thị thông báo liên tục yêu cầu bạn xác nhận chu kỳ. Hữu ích khi các chu kỳ luôn được phát hiện chính xác và bạn thấy các lời nhắc khiến bạn mất tập trung.", + "duration_tolerance": "Dung sai cho ước tính thời gian còn lại trong quá trình chạy (0,0–0,5 tương ứng với 0–50%).", + "smoothing_window": "Số chỉ số công suất gần đây được sử dụng để làm mịn đường trung bình động.", + "profile_duration_tolerance": "Dung sai được sử dụng bằng cách so khớp hồ sơ cho tổng phương sai thời lượng (0,0–0,5). Hành vi mặc định: ±25%." + } + }, + "timing_section": { + "name": "Thời gian, bảo trì và gỡ lỗi", + "description": "Watchdog, đặt lại tiến trình, tự động bảo trì và hiển thị thực thể gỡ lỗi.", + "data": { + "watchdog_interval": "Khoảng thời gian giám sát (giây)", + "no_update_active_timeout": "Hết thời gian chờ không cập nhật", + "progress_reset_delay": "Độ trễ thiết lập lại tiến trình (giây)", + "auto_maintenance": "Kích hoạt tính năng tự động bảo trì", + "expose_debug_entities": "Hiển thị các thực thể gỡ lỗi", + "save_debug_traces": "Lưu dấu vết gỡ lỗi" + }, + "data_description": { + "watchdog_interval": "Giây giữa các lần kiểm tra của cơ quan giám sát trong khi chạy. Mặc định: 5s. CẢNH BÁO: Đảm bảo giá trị này CAO hơn khoảng thời gian cập nhật cảm biến của bạn để tránh dừng nhầm (ví dụ: nếu cảm biến cập nhật sau mỗi 60 giây, hãy sử dụng 65 giây+).", + "no_update_active_timeout": "Đối với cảm biến xuất bản khi thay đổi: chỉ buộc kết thúc chu trình HOẠT ĐỘNG nếu không có bản cập nhật nào đến trong nhiều giây này. Hoàn thành ở mức năng lượng thấp vẫn sử dụng Độ trễ tắt.", + "progress_reset_delay": "Sau khi một chu kỳ hoàn thành (100%), hãy đợi nhiều giây không hoạt động này trước khi đặt lại tiến trình về 0%. Mặc định: 300s.", + "auto_maintenance": "Cho phép tự động bảo trì (sửa chữa mẫu, hợp nhất các đoạn).", + "expose_debug_entities": "Hiển thị các cảm biến nâng cao (Độ tin cậy, Pha, Sự mơ hồ) để gỡ lỗi.", + "save_debug_traces": "Lưu trữ xếp hạng chi tiết và dữ liệu theo dõi sức mạnh trong lịch sử (Tăng mức sử dụng bộ nhớ)." + } + }, + "anti_wrinkle_section": { + "name": "Tấm chắn chống nhăn (Máy sấy)", + "description": "Bỏ qua các lần quay lồng sau khi kết thúc chu kỳ để tránh chu kỳ ma. Chỉ dành cho máy sấy / máy giặt-sấy.", + "data": { + "anti_wrinkle_enabled": "Tấm chắn chống nhăn (Chỉ dành cho máy sấy)", + "anti_wrinkle_max_power": "Công suất chống nhăn tối đa (W)", + "anti_wrinkle_max_duration": "Thời lượng chống nhăn tối đa (s)", + "anti_wrinkle_exit_power": "Công suất thoát nếp nhăn (W)" + }, + "data_description": { + "anti_wrinkle_enabled": "Bỏ qua các vòng quay trống công suất thấp trong thời gian ngắn sau khi chu trình kết thúc để tránh chu kỳ ma (chỉ máy sấy/máy giặt-máy sấy).", + "anti_wrinkle_max_power": "Các lần tăng tốc bằng hoặc thấp hơn công suất này được coi là các vòng quay chống nếp nhăn thay vì các chu kỳ mới. Chỉ áp dụng khi Tấm chắn chống nhăn được bật.", + "anti_wrinkle_max_duration": "Chiều dài bùng nổ tối đa được coi là vòng xoay chống nhăn. Chỉ áp dụng khi Tấm chắn chống nhăn được bật.", + "anti_wrinkle_exit_power": "Nếu điện năng giảm xuống dưới mức này, hãy thoát khỏi tính năng chống nhăn ngay lập tức (tắt đúng). Chỉ áp dụng khi Tấm chắn chống nhăn được bật." + } + }, + "delay_start_section": { + "name": "Phát hiện khởi động trễ", + "description": "Coi trạng thái chờ công suất thấp kéo dài là 'Đang chờ bắt đầu' cho đến khi chu kỳ thực sự bắt đầu.", + "data": { + "delay_start_detect_enabled": "Bật tính năng phát hiện khởi động bị trì hoãn", + "delay_confirm_seconds": "Khởi động trễ: Thời gian xác nhận chờ (giây)", + "delay_timeout_hours": "Bắt đầu bị trì hoãn: Thời gian chờ tối đa (h)" + }, + "data_description": { + "delay_start_detect_enabled": "Khi được bật, một đợt tiêu hao năng lượng thấp trong thời gian ngắn, sau đó là nguồn điện dự phòng duy trì được phát hiện là khởi động chậm. Cảm biến trạng thái hiển thị 'Đang chờ bắt đầu' trong thời gian trễ. Không có thời gian hoặc tiến trình của chương trình nào được theo dõi cho đến khi chu kỳ thực sự bắt đầu. Yêu cầu phải đặt start_threshold_w trên mức công suất tăng đột biến.", + "delay_confirm_seconds": "Công suất phải nằm giữa Ngưỡng dừng (W) và Ngưỡng bắt đầu (W) trong khoảng thời gian này trước khi WashData chuyển sang trạng thái 'Đang chờ bắt đầu'. Giúp lọc các đỉnh ngắn khi điều hướng menu. Mặc định: 60 giây.", + "delay_timeout_hours": "Hết thời gian chờ an toàn: nếu máy vẫn ở trạng thái 'Đang chờ khởi động' sau nhiều giờ như vậy, WashData sẽ đặt lại thành Tắt. Mặc định: 8 giờ." + } + }, + "external_triggers_section": { + "name": "Kích hoạt bên ngoài, cửa và tạm dừng", + "description": "Cảm biến kích hoạt kết thúc bên ngoài tùy chọn, cảm biến cửa để phát hiện tạm dừng/sạch, và công tắc cắt điện khi tạm dừng.", + "data": { + "external_end_trigger_enabled": "Kích hoạt trình kích hoạt kết thúc chu kỳ bên ngoài", + "external_end_trigger": "Cảm biến kết thúc chu kỳ bên ngoài", + "external_end_trigger_inverted": "Đảo ngược logic kích hoạt (Kích hoạt khi TẮT)", + "door_sensor_entity": "Cảm biến cửa", + "pause_cuts_power": "Cắt điện khi tạm dừng", + "switch_entity": "Chuyển đổi thực thể (để tạm dừng cắt điện)", + "notify_unload_delay_minutes": "Độ trễ thông báo chờ giặt (phút)" + }, + "data_description": { + "external_end_trigger_enabled": "Cho phép giám sát cảm biến nhị phân bên ngoài để kích hoạt hoàn thành chu trình.", + "external_end_trigger": "Chọn thực thể cảm biến nhị phân. Khi cảm biến này kích hoạt, chu kỳ hiện tại sẽ kết thúc với trạng thái “đã hoàn thành”.", + "external_end_trigger_inverted": "Theo mặc định, trình kích hoạt sẽ kích hoạt khi cảm biến BẬT. Thay vào đó, hãy chọn hộp này để kích hoạt khi cảm biến TẮT.", + "door_sensor_entity": "Cảm biến nhị phân tùy chọn cho cửa máy (bật = mở, tắt = đóng). Khi cửa mở trong một chu trình đang hoạt động, WashData sẽ xác nhận chu trình đó được tạm dừng một cách có chủ ý. Sau khi chu trình kết thúc, nếu cửa vẫn đóng, trạng thái sẽ chuyển sang 'Sạch' cho đến khi cửa được mở. Lưu ý: việc đóng cửa KHÔNG tự động tiếp tục lại chu trình đã tạm dừng - việc tiếp tục phải được kích hoạt thủ công thông qua nút hoặc dịch vụ Tiếp tục chu trình.", + "pause_cuts_power": "Khi tạm dừng thông qua nút hoặc dịch vụ Tạm dừng Chu kỳ, hãy tắt cả thực thể chuyển đổi đã định cấu hình. Chỉ có hiệu lực nếu thực thể chuyển đổi được định cấu hình cho thiết bị này.", + "switch_entity": "Thực thể chuyển đổi để chuyển đổi khi tạm dừng hoặc tiếp tục. Bắt buộc khi bật 'Cắt nguồn khi tạm dừng'.", + "notify_unload_delay_minutes": "Gửi thông báo qua kênh thông báo kết thúc sau khi chu trình kết thúc và cửa chưa được mở trong nhiều phút này (yêu cầu Cảm biến cửa). Đặt thành 0 để tắt. Mặc định: 60 phút." + } + }, + "pump_section": { + "name": "Giám sát bơm", + "description": "Chỉ dành cho loại thiết bị bơm. Sẽ phát một sự kiện nếu chu kỳ bơm vượt quá thời lượng này.", + "data": { + "pump_stuck_duration": "(Các) Ngưỡng cảnh báo bị kẹt máy bơm (Chỉ máy bơm)" + }, + "data_description": { + "pump_stuck_duration": "Nếu chu trình bơm vẫn chạy sau nhiều giây này, sự kiện ha_washdata_pump_stuck sẽ được kích hoạt một lần. Sử dụng tính năng này để phát hiện động cơ bị kẹt (thời gian chạy máy bơm bể phốt thông thường là dưới 60 giây). Mặc định: 1800 giây (30 phút). Chỉ loại thiết bị bơm." + } + }, + "device_link_section": { + "name": "Liên kết thiết bị", + "description": "Tùy chọn liên kết thiết bị WashData này với thiết bị Home Assistant hiện có (ví dụ: phích cắm thông minh hoặc chính thiết bị đó). Sau đó, thiết bị WashData được hiển thị là \"Đã kết nối qua\" thiết bị đó. Để trống để giữ WashData làm thiết bị độc lập.", + "data": { + "linked_device": "Thiết bị được liên kết" + }, + "data_description": { + "linked_device": "Chọn thiết bị để kết nối WashData. Điều này thêm tham chiếu 'Đã kết nối qua' trong sổ đăng ký thiết bị; WashData giữ thẻ thiết bị và các thực thể của riêng mình. Xóa lựa chọn để loại bỏ liên kết." + } + } + } + }, + "diagnostics": { + "title": "Chẩn đoán & Bảo trì", + "description": "Chạy các hành động bảo trì như hợp nhất các chu trình bị phân mảnh hoặc di chuyển dữ liệu được lưu trữ.\n\n**Sử dụng bộ nhớ**\n\n- Kích thước Tệp: {file_size_kb} KB\n- Chu kỳ: {cycle_count}\n- Hồ sơ: {profile_count}\n- Dấu vết gỡ lỗi: {debug_count}", + "menu_options": { + "reprocess_history": "Bảo trì: Tái xử lý & Tối ưu hóa dữ liệu", + "clear_debug_data": "Xóa dữ liệu gỡ lỗi (Giải phóng dung lượng)", + "wipe_history": "Xóa TẤT CẢ dữ liệu cho thiết bị này (không thể đảo ngược)", + "export_import": "Xuất/Nhập JSON kèm theo cài đặt (sao chép/dán)", + "menu_back": "← Quay lại" + } + }, + "clear_debug_data": { + "title": "Xóa dữ liệu gỡ lỗi", + "description": "Bạn có chắc chắn muốn xóa tất cả dấu vết gỡ lỗi được lưu trữ không? Điều này sẽ giải phóng dung lượng nhưng xóa thông tin xếp hạng chi tiết khỏi các chu kỳ trước." + }, + "manage_cycles": { + "title": "Quản lý chu kỳ", + "description": "Chu kỳ gần đây:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "Tự động dán nhãn chu kỳ cũ", + "select_cycle_to_label": "Chu kỳ cụ thể của nhãn", + "select_cycle_to_delete": "Xóa chu kỳ", + "interactive_editor": "Trình soạn thảo tương tác hợp nhất/tách", + "trim_cycle_select": "Dữ liệu chu kỳ cắt", + "menu_back": "← Quay lại" + } + }, + "manage_cycles_empty": { + "title": "Quản lý chu kỳ", + "description": "Chưa có chu kỳ nào được ghi lại.", + "menu_options": { + "auto_label_cycles": "Tự động dán nhãn chu kỳ cũ", + "menu_back": "← Quay lại" + } + }, + "manage_profiles": { + "title": "Quản lý hồ sơ", + "description": "Tóm tắt hồ sơ:\n{profile_summary}", + "menu_options": { + "create_profile": "Tạo hồ sơ mới", + "edit_profile": "Chỉnh sửa/Đổi tên hồ sơ", + "delete_profile_select": "Xóa hồ sơ", + "profile_stats": "Thống kê hồ sơ", + "cleanup_profile": "Dọn dẹp lịch sử - Biểu đồ & Xóa", + "assign_profile_phases_select": "Chỉ định phạm vi pha", + "menu_back": "← Quay lại" + } + }, + "manage_profiles_empty": { + "title": "Quản lý hồ sơ", + "description": "Chưa có hồ sơ nào được tạo.", + "menu_options": { + "create_profile": "Tạo hồ sơ mới", + "menu_back": "← Quay lại" + } + }, + "manage_phase_catalog": { + "title": "Quản lý danh mục giai đoạn", + "description": "Danh mục pha (mặc định cho thiết bị này + các pha tùy chỉnh):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "Tạo giai đoạn mới", + "phase_catalog_edit_select": "Chỉnh sửa giai đoạn", + "phase_catalog_delete": "Xóa giai đoạn", + "menu_back": "← Quay lại" + } + }, + "phase_catalog_create": { + "title": "Tạo giai đoạn tùy chỉnh", + "description": "Tạo tên và mô tả pha tùy chỉnh, đồng thời chọn loại thiết bị áp dụng.", + "data": { + "target_device_type": "Loại thiết bị mục tiêu", + "phase_name": "Tên giai đoạn", + "phase_description": "Giai đoạn Mô tả" + } + }, + "phase_catalog_edit_select": { + "title": "Chỉnh sửa giai đoạn tùy chỉnh", + "description": "Chọn một giai đoạn tùy chỉnh để chỉnh sửa.", + "data": { + "phase_name": "Giai đoạn tùy chỉnh" + } + }, + "phase_catalog_edit": { + "title": "Chỉnh sửa giai đoạn tùy chỉnh", + "description": "Giai đoạn chỉnh sửa: {phase_name}", + "data": { + "phase_name": "Tên giai đoạn", + "phase_description": "Giai đoạn Mô tả" + } + }, + "phase_catalog_delete": { + "title": "Xóa giai đoạn tùy chỉnh", + "description": "Chọn một giai đoạn tùy chỉnh để xóa. Mọi phạm vi được chỉ định sử dụng giai đoạn này sẽ bị xóa.", + "data": { + "phase_name": "Giai đoạn tùy chỉnh" + } + }, + "assign_profile_phases": { + "title": "Chỉ định phạm vi pha", + "description": "Xem trước giai đoạn cho hồ sơ: **{profile_name}**\n\n{timeline_svg}\n\n**Phạm vi hiện tại:**\n{current_ranges}\n\nSử dụng các thao tác bên dưới để thêm, chỉnh sửa, xóa hoặc lưu phạm vi.", + "menu_options": { + "assign_profile_phases_add": "Thêm phạm vi pha", + "assign_profile_phases_edit_select": "Chỉnh sửa phạm vi pha", + "assign_profile_phases_delete": "Xóa phạm vi pha", + "phase_ranges_clear": "Xóa tất cả các phạm vi", + "assign_profile_phases_auto_detect": "Tự động phát hiện các pha", + "phase_ranges_save": "Lưu và trả lại", + "menu_back": "← Quay lại" + } + }, + "assign_profile_phases_add": { + "title": "Thêm phạm vi pha", + "description": "Thêm một phạm vi pha vào dự thảo hiện tại.", + "data": { + "phase_name": "Giai đoạn", + "start_min": "Phút bắt đầu", + "end_min": "Phút cuối" + } + }, + "assign_profile_phases_edit_select": { + "title": "Chỉnh sửa phạm vi pha", + "description": "Chọn một phạm vi để chỉnh sửa.", + "data": { + "range_index": "Phạm vi pha" + } + }, + "assign_profile_phases_edit": { + "title": "Chỉnh sửa phạm vi pha", + "description": "Cập nhật phạm vi pha đã chọn.", + "data": { + "phase_name": "Giai đoạn", + "start_min": "Phút bắt đầu", + "end_min": "Phút cuối" + } + }, + "assign_profile_phases_delete": { + "title": "Xóa phạm vi pha", + "description": "Chọn một phạm vi để loại bỏ khỏi bản nháp.", + "data": { + "range_index": "Phạm vi pha" + } + }, + "assign_profile_phases_auto_detect": { + "title": "Các pha được phát hiện tự động", + "description": "Hồ sơ: **{profile_name}**\n\n{timeline_svg}\n\n**{detected_count} (các) pha được phát hiện tự động.** Chọn một hành động bên dưới.", + "data": { + "action": "Hoạt động" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "Tên các giai đoạn được phát hiện", + "description": "Hồ sơ: **{profile_name}**\n\n**{detected_count} pha được phát hiện:**\n{ranges_summary}\n\nGán tên cho từng giai đoạn." + }, + "assign_profile_phases_select": { + "title": "Chỉ định phạm vi pha", + "description": "Chọn một cấu hình để cấu hình phạm vi pha.", + "data": { + "profile": "Hồ sơ" + } + }, + "profile_stats": { + "title": "Thống kê hồ sơ", + "description": "{stats_table}" + }, + "create_profile": { + "title": "Tạo hồ sơ mới", + "description": "Tạo hồ sơ mới theo cách thủ công hoặc từ chu kỳ trước.\n\nVí dụ về tên hồ sơ: 'Đồ tinh tế', 'Nhiệm vụ nặng nề', 'Giặt nhanh'", + "data": { + "profile_name": "Tên hồ sơ", + "reference_cycle": "Chu kỳ tham chiếu (tùy chọn)", + "manual_duration": "Thời lượng thủ công (phút)" + } + }, + "edit_profile": { + "title": "Chỉnh sửa hồ sơ", + "description": "Chọn hồ sơ để đổi tên", + "data": { + "profile": "Hồ sơ" + } + }, + "rename_profile": { + "title": "Chỉnh sửa chi tiết hồ sơ", + "description": "Hồ sơ hiện tại: {current_name}", + "data": { + "new_name": "Tên hồ sơ", + "manual_duration": "Thời lượng thủ công (phút)" + } + }, + "delete_profile_select": { + "title": "Xóa hồ sơ", + "description": "Chọn hồ sơ để xóa", + "data": { + "profile": "Hồ sơ" + } + }, + "delete_profile_confirm": { + "title": "Xác nhận Xóa hồ sơ", + "description": "⚠️ Thao tác này sẽ xóa vĩnh viễn hồ sơ: {profile_name}", + "data": { + "unlabel_cycles": "Xóa nhãn khỏi chu kỳ bằng hồ sơ này" + } + }, + "auto_label_cycles": { + "title": "Tự động dán nhãn chu kỳ cũ", + "description": "Đã tìm thấy tổng cộng {total_count} chu kỳ. Hồ sơ: {profiles}", + "data": { + "confidence_threshold": "Độ tin cậy tối thiểu (0,50-0,95)" + } + }, + "select_cycle_to_label": { + "title": "Chọn chu trình để dán nhãn", + "description": "✓ = hoàn thành, ⚠ = tiếp tục, ✗ = bị gián đoạn", + "data": { + "cycle_id": "ID chu kỳ" + } + }, + "select_cycle_to_delete": { + "title": "Xóa chu kỳ", + "description": "⚠️ Thao tác này sẽ xóa vĩnh viễn chu trình đã chọn", + "data": { + "cycle_id": "Chu kỳ để xóa" + } + }, + "label_cycle": { + "title": "Chu kỳ nhãn", + "description": "{cycle_info}", + "data": { + "profile_name": "Hồ sơ", + "new_profile_name": "Tên hồ sơ mới (nếu tạo)" + } + }, + "post_process": { + "title": "Lịch sử quy trình (Hợp nhất/Tách)", + "description": "Dọn dẹp lịch sử chu trình bằng cách hợp nhất các lần chạy bị phân mảnh và tách các chu kỳ được hợp nhất không chính xác trong một khoảng thời gian. Nhập số giờ để nhìn lại (dùng 999999 cho tất cả).", + "data": { + "time_range": "Khoảng thời gian xem lại (giờ)", + "gap_seconds": "Khoảng cách Hợp nhất/Tách (giây)" + }, + "data_description": { + "time_range": "Số giờ quét các chu kỳ phân mảnh để hợp nhất. Sử dụng 999999 để xử lý tất cả dữ liệu lịch sử.", + "gap_seconds": "Ngưỡng để quyết định chia tách và hợp nhất. Khoảng trống NGẮN HƠN hơn mức này sẽ được hợp nhất. Khoảng trống dài hơn khoảng trống này sẽ được chia ra. Hạ thấp mức này để buộc phân tách trên một chu kỳ được hợp nhất không chính xác." + } + }, + "cleanup_profile": { + "title": "Dọn dẹp lịch sử - Chọn hồ sơ", + "description": "Chọn một hồ sơ để trực quan hóa. Điều này sẽ tạo ra một biểu đồ hiển thị tất cả các chu kỳ trước đây cho cấu hình này để giúp xác định các ngoại lệ.", + "data": { + "profile": "Hồ sơ" + } + }, + "cleanup_select": { + "title": "Dọn dẹp lịch sử - Biểu đồ & Xóa", + "description": "![Biểu đồ]({graph_url})\n\n**Đang hiển thị {profile_name}**\n\nBiểu đồ hiển thị các chu kỳ riêng lẻ dưới dạng các đường màu. Xác định các ngoại lệ (các dòng ở xa nhóm) và khớp màu của chúng trong danh sách bên dưới để xóa chúng.\n\n**Chọn chu kỳ để xóa VĨNH VIỄN:**", + "data": { + "cycles_to_delete": "Chu kỳ cần xóa (kiểm tra màu phù hợp)" + } + }, + "interactive_editor": { + "title": "Trình soạn thảo tương tác hợp nhất/tách", + "description": "Chọn một hành động thủ công để thực hiện trên lịch sử chu kỳ của bạn.", + "menu_options": { + "editor_split": "Tách một chu kỳ (Tìm khoảng trống)", + "editor_merge": "Hợp nhất các chu kỳ (Nối các đoạn)", + "editor_delete": "Xóa (các) chu kỳ", + "menu_back": "← Quay lại" + } + }, + "editor_select": { + "title": "Chọn chu kỳ", + "description": "Chọn (các) chu trình để xử lý.\n\n{info_text}", + "data": { + "selected_cycles": "Chu kỳ" + } + }, + "editor_configure": { + "title": "Cấu hình & Áp dụng", + "description": "{preview_md}", + "data": { + "confirm_action": "Hoạt động", + "merged_profile": "Hồ sơ kết quả", + "new_profile_name": "Tên hồ sơ mới", + "confirm_commit": "Có, tôi xác nhận hành động này", + "segment_0_profile": "Hồ sơ đoạn 1", + "segment_1_profile": "Hồ sơ đoạn 2", + "segment_2_profile": "Hồ sơ đoạn 3", + "segment_3_profile": "Hồ sơ đoạn 4", + "segment_4_profile": "Hồ sơ đoạn 5", + "segment_5_profile": "Hồ sơ đoạn 6", + "segment_6_profile": "Hồ sơ đoạn 7", + "segment_7_profile": "Hồ sơ đoạn 8", + "segment_8_profile": "Hồ sơ đoạn 9", + "segment_9_profile": "Hồ sơ đoạn 10" + } + }, + "editor_split_params": { + "title": "Cấu hình phân chia", + "description": "Điều chỉnh độ nhạy để tách chu kỳ này. Bất kỳ khoảng trống nhàn rỗi nào dài hơn khoảng cách này sẽ gây ra sự phân chia.", + "data": { + "split_mode": "Phương pháp tách" + } + }, + "editor_split_auto_params": { + "title": "Cấu hình tách tự động", + "description": "Điều chỉnh độ nhạy để tách chu kỳ này. Bất kỳ khoảng nghỉ nào dài hơn mức này sẽ gây ra việc tách.", + "data": { + "min_gap_seconds": "Ngưỡng khoảng tách (giây)" + } + }, + "editor_split_manual_params": { + "title": "Dấu thời gian tách thủ công", + "description": "{preview_md}Cửa sổ chu kỳ: **{cycle_start_wallclock} → {cycle_end_wallclock}** vào {cycle_date}.\n\nNhập một hoặc nhiều dấu thời gian tách (`HH:MM` hoặc `HH:MM:SS`), mỗi dòng một dấu. Chu kỳ sẽ bị cắt tại mỗi dấu thời gian — N dấu thời gian tạo ra N+1 phân đoạn.", + "data": { + "split_timestamps": "Dấu thời gian tách" + } + }, + "reprocess_history": { + "title": "Bảo trì: Tái xử lý & Tối ưu hóa dữ liệu", + "description": "Thực hiện làm sạch sâu dữ liệu lịch sử:\n1. Cắt bớt số đọc công suất bằng 0 (im lặng).\n2. Sửa thời gian/thời lượng chu kỳ.\n3. Tính toán lại tất cả các mô hình thống kê (phong bì).\n\n**Chạy phần mềm này để khắc phục sự cố dữ liệu hoặc áp dụng thuật toán mới.**" + }, + "wipe_history": { + "title": "Xóa lịch sử (Chỉ thử nghiệm)", + "description": "⚠️ Thao tác này sẽ xóa vĩnh viễn TẤT CẢ chu kỳ và hồ sơ được lưu trữ cho thiết bị này. Điều này không thể được hoàn tác!" + }, + "export_import": { + "title": "Xuất/Nhập JSON", + "description": "Sao chép/dán chu trình, cấu hình VÀ cài đặt cho thiết bị này. Xuất bên dưới hoặc dán JSON để nhập.", + "data": { + "mode": "Hoạt động", + "json_payload": "JSON cấu hình hoàn chỉnh" + }, + "data_description": { + "mode": "Chọn Xuất để sao chép dữ liệu và cài đặt hiện tại hoặc Nhập để dán dữ liệu đã xuất từ ​​một thiết bị WashData khác.", + "json_payload": "Để xuất: sao chép JSON này (bao gồm các chu trình, cấu hình và tất cả các cài đặt được tinh chỉnh). Đối với Nhập: dán JSON được xuất từ ​​WashData." + } + }, + "record_cycle": { + "title": "Chu kỳ ghi", + "description": "Ghi lại chu trình theo cách thủ công để tạo hồ sơ rõ ràng mà không bị can thiệp.\n\nTrạng thái: **{status}**\nThời lượng: {duration} giây\nMẫu: {samples}", + "menu_options": { + "record_refresh": "Trạng thái làm mới", + "record_stop": "Dừng ghi (Lưu & Xử lý)", + "record_start": "Bắt đầu ghi âm mới", + "record_process": "Xử lý bản ghi cuối cùng (Cắt & Lưu)", + "record_discard": "Hủy bản ghi cuối cùng", + "menu_back": "← Quay lại" + } + }, + "record_process": { + "title": "Ghi lại quá trình", + "description": "![Biểu đồ]({graph_url})\n\n**Biểu đồ hiển thị bản ghi thô.** Xanh = Giữ, Đỏ = Cắt đề xuất.\n*Biểu đồ tĩnh và không cập nhật khi có thay đổi về biểu mẫu.*\n\nĐã dừng ghi. Các phần cắt được căn chỉnh theo tốc độ lấy mẫu được phát hiện (~{sampling_rate} giây).\n\nThời lượng thô: {duration} giây\nMẫu: {samples}", + "data": { + "head_trim": "Cắt bắt đầu (giây)", + "tail_trim": "Cắt bớt kết thúc (giây)", + "save_mode": "Lưu điểm đến", + "profile_name": "Tên hồ sơ" + } + }, + "learning_feedbacks": { + "title": "Phản hồi học tập", + "description": "Phản hồi đang chờ xử lý: {count}\n\n{pending_table}\n\nChọn một yêu cầu xem xét chu kỳ để xử lý.", + "menu_options": { + "learning_feedbacks_pick": "Xem lại một phản hồi đang chờ xử lý", + "learning_feedbacks_dismiss_all": "Bỏ qua toàn bộ phản hồi đang chờ xử lý", + "menu_back": "← Quay lại" + } + }, + "learning_feedbacks_pick": { + "title": "Chọn phản hồi để xem lại", + "description": "Chọn một yêu cầu xem xét chu kỳ để mở.", + "data": { + "selected_feedback": "Chọn chu kỳ để xem lại" + } + }, + "learning_feedbacks_empty": { + "title": "Phản hồi học tập", + "description": "Không tìm thấy yêu cầu phản hồi đang chờ xử lý.", + "menu_options": { + "menu_back": "← Quay lại" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "Bỏ qua tất cả phản hồi đang chờ", + "description": "Bạn sắp bỏ qua **{count}** yêu cầu phản hồi đang chờ. Các chu kỳ bị bỏ qua vẫn nằm trong lịch sử của bạn nhưng sẽ không đóng góp tín hiệu học tập mới. Không thể hoàn tác việc này.\n\n✅ Đánh dấu vào ô bên dưới và nhấp vào **Gửi** để bỏ qua tất cả.\n❌ Bỏ chọn và nhấp vào **Gửi** để hủy và quay lại.", + "data": { + "confirm_dismiss_all": "Xác nhận: bỏ qua toàn bộ yêu cầu phản hồi đang chờ xử lý" + } + }, + "resolve_feedback": { + "title": "Giải quyết phản hồi", + "description": "{comparison_img}{candidates_table}\n**Hồ sơ được phát hiện**: {detected_profile} ({confidence_pct}%)\n**Thời lượng ước tính**: {est_duration_min} phút\n**Thời lượng thực tế**: {act_duration_min} phút\n\n__Chọn một hành động dưới đây:__", + "data": { + "action": "Bạn muốn làm gì?", + "corrected_profile": "Đúng chương trình (nếu sửa)", + "corrected_duration": "Thời lượng chính xác tính bằng giây (nếu sửa)" + } + }, + "trim_cycle_select": { + "title": "Chu trình cắt - Chọn chu trình", + "description": "Chọn một chu kỳ để cắt. ✓ = hoàn thành, ⚠ = tiếp tục, ✗ = bị gián đoạn", + "data": { + "cycle_id": "ID chu kỳ" + } + }, + "trim_cycle": { + "title": "Chu kỳ cắt", + "description": "Cửa sổ hiện tại: {trim_summary}\n\n{trim_svg}", + "data": { + "action": "Hoạt động" + } + }, + "trim_cycle_start": { + "title": "Đặt bắt đầu cắt", + "description": "Cửa sổ chu kỳ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nThời gian bắt đầu hiện tại: **{current_wallclock}** (+{current_offset_min} phút kể từ khi bắt đầu chu kỳ)\n\nChọn thời gian bắt đầu mới bằng cách sử dụng bộ chọn đồng hồ bên dưới.", + "data": { + "trim_start_time": "Thời gian bắt đầu mới", + "trim_start_min": "Khởi đầu mới (phút kể từ khi bắt đầu chu kỳ)" + } + }, + "trim_cycle_end": { + "title": "Đặt phần cuối", + "description": "Cửa sổ chu kỳ: {cycle_start_wallclock} → {cycle_end_wallclock}\n\nKết thúc hiện tại: **{current_wallclock}** (+{current_offset_min} phút kể từ khi bắt đầu chu kỳ)\n\nChọn thời gian kết thúc mới bằng cách sử dụng công cụ chọn đồng hồ bên dưới.", + "data": { + "trim_end_time": "Thời gian kết thúc mới", + "trim_end_min": "Kết thúc mới (phút kể từ khi bắt đầu chu kỳ)" + } + } + }, + "error": { + "import_failed": "Nhập không thành công. Vui lòng dán JSON xuất WashData hợp lệ.", + "select_exactly_one": "Chọn đúng một chu kỳ cho thao tác này.", + "select_at_least_one": "Chọn ít nhất một chu kỳ cho thao tác này.", + "select_at_least_two": "Chọn ít nhất hai chu kỳ cho thao tác này.", + "profile_exists": "Hồ sơ có tên này đã tồn tại", + "rename_failed": "Không đổi tên được hồ sơ. Hồ sơ có thể không tồn tại hoặc tên mới đã được sử dụng.", + "assignment_failed": "Không thể chỉ định hồ sơ cho chu kỳ", + "duplicate_phase": "Một giai đoạn có tên này đã tồn tại.", + "phase_not_found": "Giai đoạn đã chọn không được tìm thấy.", + "invalid_phase_name": "Tên giai đoạn không được để trống.", + "phase_name_too_long": "Tên giai đoạn quá dài.", + "invalid_phase_range": "Phạm vi pha không hợp lệ. Kết thúc phải lớn hơn bắt đầu.", + "invalid_phase_timestamp": "Dấu thời gian không hợp lệ. Sử dụng định dạng YYYY-MM-DD HH:MM hoặc HH:MM.", + "overlapping_phase_ranges": "Phạm vi pha chồng chéo. Điều chỉnh phạm vi và thử lại.", + "incomplete_phase_row": "Mỗi hàng pha phải bao gồm một pha cộng với các giá trị bắt đầu/kết thúc hoàn chỉnh cho chế độ đã chọn.", + "timestamp_mode_cycle_required": "Vui lòng chọn chu kỳ nguồn khi sử dụng chế độ dấu thời gian.", + "no_phase_ranges": "Chưa có phạm vi pha nào cho hành động đó.", + "feedback_notification_title": "WashData: Chu trình xác minh ({device})", + "feedback_notification_message": "**Thiết bị**: {device}\n**Chương trình**: {program} (độ tin cậy {confidence}%)\n**Thời gian**: {time}\n\nWashData cần sự trợ giúp của bạn để xác minh chu trình được phát hiện này.\n\nVui lòng truy cập **Cài đặt > Thiết bị & Dịch vụ > WashData > Định cấu hình > Phản hồi tìm hiểu** để xác nhận hoặc sửa kết quả này.", + "suggestions_ready_notification_title": "WashData: Sẵn sàng cài đặt được đề xuất ({device})", + "suggestions_ready_notification_message": "Cảm biến **Cài đặt được đề xuất** hiện báo cáo **{count}** đề xuất có thể thực hiện được.\n\nĐể xem xét và áp dụng chúng: **Cài đặt > Thiết bị & Dịch vụ > WashData > Định cấu hình > Cài đặt nâng cao > Áp dụng các giá trị được đề xuất**.\n\nĐề xuất là tùy chọn và được hiển thị để xem xét trước khi bạn lưu.", + "trim_range_invalid": "Bắt đầu phải trước khi kết thúc. Hãy điều chỉnh điểm cắt của bạn.", + "trim_failed": "Cắt không thành công. Chu trình có thể không còn tồn tại hoặc cửa sổ trống.", + "invalid_split_timestamp": "Dấu thời gian không hợp lệ hoặc nằm ngoài khung thời gian của chu kỳ. Hãy dùng HH:MM hoặc HH:MM:SS trong phạm vi của chu kỳ.", + "no_split_segments_found": "Không thể tạo đoạn hợp lệ từ các dấu thời gian này. Hãy đảm bảo mỗi dấu thời gian nằm trong khung thời gian của chu kỳ và mỗi đoạn dài ít nhất 1 phút.", + "auto_tune_suggestion": "{device_type} {device_title} đã phát hiện chu kỳ ma. Thay đổi công suất tối thiểu được đề xuất: {current_min}W -> {new_min}W (không được áp dụng tự động).", + "auto_tune_title": "Tự động điều chỉnh WashData", + "auto_tune_fallback": "{device_type} {device_title} đã phát hiện chu kỳ ma. Thay đổi công suất tối thiểu được đề xuất: {current_min}W -> {new_min}W (không được áp dụng tự động)." + }, + "abort": { + "no_cycles_found": "Không tìm thấy chu kỳ nào", + "no_split_segments_found": "Không tìm thấy đoạn nào có thể tách trong chu kỳ đã chọn.", + "cycle_not_found": "Không thể tải chu kỳ đã chọn.", + "no_power_data": "Không có dữ liệu theo dõi nguồn điện cho chu kỳ đã chọn.", + "no_profiles_found": "Không tìm thấy hồ sơ nào. Tạo một hồ sơ đầu tiên.", + "no_profiles_for_matching": "Không có hồ sơ nào có sẵn để kết hợp. Tạo hồ sơ đầu tiên.", + "no_unlabeled_cycles": "Tất cả các chu kỳ đã được dán nhãn", + "no_suggestions": "Chưa có giá trị đề xuất nào. Chạy một vài chu kỳ và thử lại.", + "no_predictions": "Không có dự đoán", + "no_custom_phases": "Không tìm thấy giai đoạn tùy chỉnh nào.", + "reprocess_success": "Đã xử lý lại thành công {count} chu kỳ.", + "debug_data_cleared": "Đã xóa thành công dữ liệu gỡ lỗi khỏi chu kỳ {count}." + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "Bắt đầu chu kỳ", + "cycle_finish": "Kết thúc chu kỳ", + "cycle_live": "Tiến trình trực tiếp" + } + }, + "common_text": { + "options": { + "unlabeled": "(Chưa gắn nhãn)", + "create_new_profile": "Tạo hồ sơ mới", + "remove_label": "Xóa nhãn", + "no_reference_cycle": "(Không có chu kỳ tham chiếu)", + "all_device_types": "Tất cả các loại thiết bị", + "current_suffix": "(hiện hành)", + "editor_select_info": "Chọn 1 chu kỳ để tách hoặc hơn 2 chu kỳ để hợp nhất.", + "editor_select_info_split": "Chọn 1 chu kỳ để tách.", + "editor_select_info_merge": "Chọn từ 2 chu kỳ trở lên để gộp.", + "editor_select_info_delete": "Chọn từ 1 chu kỳ trở lên để xóa.", + "no_cycles_recorded": "Chưa có chu kỳ nào được ghi lại.", + "profile_summary_line": "- **{name}**: {count} chu kỳ, trung bình là {avg} tháng", + "no_profiles_created": "Chưa có hồ sơ nào được tạo.", + "phase_preview": "Xem trước giai đoạn", + "phase_preview_no_curve": "Đường cong hồ sơ trung bình chưa có sẵn. Chạy/gắn nhãn nhiều chu kỳ hơn cho hồ sơ này.", + "average_power_curve": "Đường cong công suất trung bình", + "unit_min": "phút", + "profile_option_fmt": "{name} ({count} chu kỳ, trung bình ~{duration}m)", + "profile_option_short_fmt": "{name} ({count} chu kỳ, ~{duration}m)", + "deleted_cycles_from_profile": "Đã xóa {count} chu kỳ khỏi {profile}.", + "not_enough_data_graph": "Không đủ dữ liệu để tạo biểu đồ.", + "no_profiles_found": "Không tìm thấy hồ sơ nào.", + "cycle_info_fmt": "Chu kỳ: {start}, {duration}m, Hiện tại: {label}", + "top_candidates_header": "### Ứng viên hàng đầu", + "tbl_profile": "Hồ sơ", + "tbl_confidence": "Sự tự tin", + "tbl_correlation": "Tương quan", + "tbl_duration_match": "Thời lượng khớp", + "detected_profile": "Hồ sơ được phát hiện", + "estimated_duration": "Thời lượng ước tính", + "actual_duration": "Thời lượng thực tế", + "choose_action": "__Chọn một hành động dưới đây:__", + "tbl_count": "Đếm", + "tbl_avg": "Trung bình", + "tbl_min": "tối thiểu", + "tbl_max": "Tối đa", + "tbl_energy_avg": "Năng lượng (Trung bình)", + "tbl_energy_total": "Năng lượng (Tổng cộng)", + "tbl_consistency": "Bao gồm.", + "tbl_last_run": "Lần chạy cuối cùng", + "graph_legend_title": "Chú giải biểu đồ", + "graph_legend_body": "Dải màu xanh biểu thị phạm vi tiêu thụ điện năng tối thiểu và tối đa được quan sát. Đường này hiển thị đường cong công suất trung bình.", + "recording_preview": "Xem trước ghi âm", + "trim_start": "Cắt bắt đầu", + "trim_end": "Cắt bớt phần cuối", + "split_preview_title": "Xem trước tách", + "split_preview_found_fmt": "Đã tìm thấy {count} đoạn.", + "split_preview_confirm_fmt": "Nhấp vào Xác nhận để chia chu kỳ này thành {count} chu kỳ riêng biệt.", + "merge_preview_title": "Hợp nhất bản xem trước", + "merge_preview_joining_fmt": "Tham gia {count} chu kỳ. Các khoảng trống sẽ được lấp đầy bằng số đọc 0W.", + "editor_delete_preview_title": "Xem trước khi xóa", + "editor_delete_preview_intro": "Các chu kỳ đã chọn sẽ bị xóa vĩnh viễn:", + "editor_delete_preview_confirm": "Nhấp Xác nhận để xóa vĩnh viễn các bản ghi chu kỳ này.", + "no_power_preview": "*Không có dữ liệu nguồn để xem trước.*", + "profile_comparison": "So sánh hồ sơ", + "actual_cycle_label": "Chu kỳ này (thực tế)", + "storage_usage_header": "Sử dụng bộ nhớ", + "storage_file_size": "Kích thước tệp", + "storage_cycles": "Chu kỳ", + "storage_profiles": "Hồ sơ", + "storage_debug_traces": "Dấu vết gỡ lỗi", + "overview_suffix": "Tổng quan", + "phase_preview_for_profile": "Xem trước giai đoạn cho hồ sơ", + "current_ranges_header": "phạm vi hiện tại", + "assign_phases_help": "Sử dụng các thao tác bên dưới để thêm, chỉnh sửa, xóa hoặc lưu phạm vi.", + "profile_summary_header": "Tóm tắt hồ sơ", + "recent_cycles_header": "Chu kỳ gần đây", + "trim_cycle_preview_title": "Xem trước chu kỳ cắt", + "trim_cycle_preview_no_data": "Không có dữ liệu nguồn cho chu kỳ này.", + "trim_cycle_preview_kept_suffix": "giữ", + "table_program": "Chương trình", + "table_when": "Khi nào", + "table_length": "Thời lượng", + "table_match": "Khớp", + "table_profile": "Hồ sơ", + "table_cycles": "Chu kỳ", + "table_avg_length": "Thời lượng TB", + "table_last_run": "Lần chạy cuối cùng", + "table_avg_energy": "Năng lượng TB", + "table_detected_program": "Chương trình phát hiện", + "table_confidence": "Sự tự tin", + "table_reported": "Đã báo cáo", + "phase_builtin_suffix": "(Tích hợp)", + "phase_none_available": "Không có giai đoạn nào có sẵn.", + "settings_deprecation_warning": "⚠️ **Loại thiết bị không được dùng nữa:** {device_type} được lên lịch xóa trong bản phát hành trong tương lai. Quy trình phù hợp của WashData không tạo ra kết quả đáng tin cậy cho loại thiết bị này. Tích hợp của bạn tiếp tục hoạt động trong suốt thời gian ngừng sử dụng; để tắt tiếng cảnh báo này, hãy chuyển **Loại thiết bị** bên dưới sang một trong các loại được hỗ trợ (Máy giặt, Máy sấy, Combo máy giặt-máy sấy, Máy rửa chén, Nồi chiên không khí, Máy làm bánh mì hoặc Máy bơm) hoặc sang **Khác (Nâng cao)** nếu thiết bị của bạn không phù hợp với bất kỳ loại nào được hỗ trợ. **Khác (Nâng cao)** cung cấp các giá trị mặc định chung có chủ ý không được điều chỉnh cho bất kỳ thiết bị cụ thể nào, do đó, bạn sẽ cần phải tự mình định cấu hình ngưỡng, thời gian chờ và các thông số phù hợp; tất cả cài đặt hiện có của bạn sẽ được giữ nguyên khi bạn chuyển đổi.", + "phase_other_device_types": "Các loại thiết bị khác:" + } + }, + "interactive_editor_action": { + "options": { + "split": "Tách một chu kỳ (Tìm khoảng trống)", + "merge": "Hợp nhất các chu kỳ (Nối các đoạn)", + "delete": "Xóa (các) chu kỳ" + } + }, + "split_mode": { + "options": { + "auto": "Tự động phát hiện khoảng nghỉ", + "manual": "Dấu thời gian thủ công" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "Bảo trì: Tái xử lý & Tối ưu hóa dữ liệu", + "clear_debug_data": "Xóa dữ liệu gỡ lỗi (Giải phóng dung lượng)", + "wipe_history": "Xóa TẤT CẢ dữ liệu cho thiết bị này (không thể đảo ngược)", + "export_import": "Xuất/Nhập JSON kèm theo cài đặt (sao chép/dán)" + } + }, + "export_import_mode": { + "options": { + "export": "Xuất tất cả dữ liệu", + "import": "Nhập/Hợp nhất dữ liệu" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "Tự động dán nhãn chu kỳ cũ", + "select_cycle_to_label": "Chu kỳ cụ thể của nhãn", + "select_cycle_to_delete": "Xóa chu kỳ", + "interactive_editor": "Trình soạn thảo tương tác hợp nhất/tách", + "trim_cycle": "Dữ liệu chu kỳ cắt" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "Tạo hồ sơ mới", + "edit_profile": "Chỉnh sửa/Đổi tên hồ sơ", + "delete_profile": "Xóa hồ sơ", + "profile_stats": "Thống kê hồ sơ", + "cleanup_profile": "Dọn dẹp lịch sử - Biểu đồ & Xóa", + "assign_phases": "Chỉ định phạm vi pha" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "Tạo giai đoạn mới", + "edit_custom_phase": "Chỉnh sửa giai đoạn", + "delete_custom_phase": "Xóa giai đoạn" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "Thêm phạm vi pha", + "edit_range": "Chỉnh sửa phạm vi pha", + "delete_range": "Xóa phạm vi pha", + "clear_ranges": "Xóa tất cả các phạm vi", + "auto_detect_ranges": "Tự động phát hiện các pha", + "save_ranges": "Lưu và trả lại" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "Trạng thái làm mới", + "stop_recording": "Dừng ghi (Lưu & Xử lý)", + "start_recording": "Bắt đầu ghi âm mới", + "process_recording": "Xử lý bản ghi cuối cùng (Cắt & Lưu)", + "discard_recording": "Hủy bản ghi cuối cùng" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "Tạo hồ sơ mới", + "existing_profile": "Thêm vào hồ sơ hiện có", + "discard": "Hủy ghi âm" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "Xác nhận - Phát hiện chính xác", + "correct": "Đúng - Chọn Chương trình/Thời lượng", + "ignore": "Bỏ qua - Chu kỳ dương tính giả/ồn ào", + "delete": "Xóa - Xóa khỏi Lịch sử" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "Đặt tên và áp dụng các giai đoạn", + "cancel": "Quay lại mà không thay đổi" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "Đặt điểm bắt đầu", + "set_end": "Đặt điểm cuối", + "reset": "Đặt lại về thời lượng đầy đủ", + "apply": "Áp dụng Cắt", + "cancel": "Hủy bỏ" + } + }, + "device_type": { + "options": { + "washing_machine": "Máy giặt", + "dryer": "Máy sấy", + "washer_dryer": "Combo Máy Giặt-Sấy", + "dishwasher": "Máy rửa chén", + "coffee_machine": "Máy pha cà phê (không dùng nữa)", + "ev": "Xe điện (không dùng nữa)", + "air_fryer": "Nồi chiên không khí", + "heat_pump": "Bơm nhiệt (không dùng nữa)", + "bread_maker": "Máy làm bánh mì", + "pump": "Máy bơm / Máy bơm bể phốt", + "oven": "Lò nướng (không dùng nữa)", + "other": "Khác (Nâng cao)" + } + } + }, + "services": { + "label_cycle": { + "name": "Chu kỳ nhãn", + "description": "Gán cấu hình hiện có cho chu kỳ trước hoặc xóa nhãn.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt cần dán nhãn." + }, + "cycle_id": { + "name": "ID chu kỳ", + "description": "ID của chu trình cần dán nhãn." + }, + "profile_name": { + "name": "Tên hồ sơ", + "description": "Tên của hồ sơ hiện có (tạo hồ sơ trong menu Quản lý hồ sơ). Để trống để loại bỏ nhãn." + } + } + }, + "create_profile": { + "name": "Tạo hồ sơ", + "description": "Tạo một hồ sơ mới (độc lập hoặc dựa trên chu trình tham chiếu).", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt." + }, + "profile_name": { + "name": "Tên hồ sơ", + "description": "Tên cho hồ sơ mới (ví dụ: 'Nhiệm vụ nặng nề', 'Món ngon')." + }, + "reference_cycle_id": { + "name": "ID chu kỳ tham chiếu", + "description": "ID chu kỳ tùy chọn để làm cơ sở cho hồ sơ này." + } + } + }, + "delete_profile": { + "name": "Xóa hồ sơ", + "description": "Xóa một hồ sơ và tùy ý hủy nhãn các chu kỳ sử dụng nó.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt." + }, + "profile_name": { + "name": "Tên hồ sơ", + "description": "Hồ sơ cần xóa." + }, + "unlabel_cycles": { + "name": "Bỏ nhãn chu kỳ", + "description": "Xóa nhãn hồ sơ khỏi các chu kỳ sử dụng hồ sơ này." + } + } + }, + "auto_label_cycles": { + "name": "Tự động dán nhãn chu kỳ cũ", + "description": "Dán nhãn trước đây cho các chu kỳ không được gắn nhãn bằng cách sử dụng kết hợp hồ sơ.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt." + }, + "confidence_threshold": { + "name": "Ngưỡng tin cậy", + "description": "Độ tin cậy đối sánh tối thiểu (0,50-0,95) để áp dụng nhãn." + } + } + }, + "export_config": { + "name": "Xuất cấu hình", + "description": "Xuất cấu hình, chu kỳ và cài đặt của thiết bị này sang tệp JSON (mỗi thiết bị).", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt xuất khẩu." + }, + "path": { + "name": "Con đường", + "description": "Đường dẫn tệp tuyệt đối tùy chọn để ghi (mặc định là /config/ha_washdata_export_{entry}.json)." + } + } + }, + "import_config": { + "name": "Nhập cấu hình", + "description": "Nhập cấu hình, chu trình và cài đặt cho thiết bị này từ tệp xuất JSON (cài đặt có thể bị ghi đè).", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt cần nhập khẩu." + }, + "path": { + "name": "Con đường", + "description": "Đường dẫn tuyệt đối đến tệp JSON xuất cần nhập." + } + } + }, + "submit_cycle_feedback": { + "name": "Gửi phản hồi chu kỳ", + "description": "Xác nhận hoặc sửa chương trình được phát hiện tự động sau khi hoàn thành một chu trình. Cung cấp `entry_id` (nâng cao) hoặc `device_id` (được khuyến nghị).", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị WashData (được khuyến nghị thay vì entry_id)." + }, + "entry_id": { + "name": "ID mục nhập", + "description": "Id mục nhập cấu hình cho thiết bị (thay thế cho device_id)." + }, + "cycle_id": { + "name": "ID chu kỳ", + "description": "Id chu kỳ được hiển thị trong thông báo/nhật ký phản hồi." + }, + "user_confirmed": { + "name": "Xác nhận chương trình đã phát hiện", + "description": "Đặt đúng nếu chương trình được phát hiện là chính xác." + }, + "corrected_profile": { + "name": "Hồ sơ đã sửa", + "description": "Nếu không được xác nhận, hãy cung cấp tên hồ sơ/chương trình chính xác." + }, + "corrected_duration": { + "name": "Thời lượng đã sửa (giây)", + "description": "Thời lượng hiệu chỉnh tùy chọn tính bằng giây." + }, + "notes": { + "name": "Ghi chú", + "description": "Ghi chú tùy chọn về chu kỳ này." + } + } + }, + "record_start": { + "name": "Bắt đầu chu kỳ ghi", + "description": "Bắt đầu ghi thủ công một chu trình sạch (bỏ qua tất cả logic khớp).", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt để ghi lại." + } + } + }, + "record_stop": { + "name": "Ghi lại chu kỳ dừng", + "description": "Dừng ghi thủ công.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị máy giặt dừng ghi." + } + } + }, + "trim_cycle": { + "name": "Chu kỳ cắt", + "description": "Cắt bớt dữ liệu nguồn của chu kỳ trước thành một khoảng thời gian cụ thể.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị WashData." + }, + "cycle_id": { + "name": "ID chu kỳ", + "description": "ID của chu trình cần cắt." + }, + "trim_start_s": { + "name": "Cắt bắt đầu (giây)", + "description": "Giữ dữ liệu từ phần bù này trong vài giây. Mặc định 0." + }, + "trim_end_s": { + "name": "Cắt bớt kết thúc (giây)", + "description": "Giữ dữ liệu ở mức bù này trong vài giây. Mặc định = thời lượng đầy đủ." + } + } + }, + "pause_cycle": { + "name": "Chu kỳ tạm dừng", + "description": "Tạm dừng chu trình hoạt động cho thiết bị WashData.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị WashData sẽ tạm dừng." + } + } + }, + "resume_cycle": { + "name": "Tiếp tục chu kỳ", + "description": "Tiếp tục chu trình đã tạm dừng cho thiết bị WashData.", + "fields": { + "device_id": { + "name": "Thiết bị", + "description": "Thiết bị WashData sẽ tiếp tục." + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "Đang chạy" + }, + "match_ambiguity": { + "name": "Sự mơ hồ về trận đấu" + } + }, + "sensor": { + "washer_state": { + "name": "Tình trạng", + "state": { + "off": "Tắt", + "idle": "Nhàn rỗi", + "starting": "Bắt đầu", + "running": "Đang chạy", + "paused": "Đã tạm dừng", + "user_paused": "Tạm dừng bởi người dùng", + "ending": "Kết thúc", + "finished": "Hoàn thành", + "anti_wrinkle": "Chống nhăn", + "delay_wait": "Đang chờ bắt đầu", + "interrupted": "Bị gián đoạn", + "force_stopped": "Buộc dừng", + "rinse": "Rửa sạch", + "unknown": "Không xác định", + "clean": "Lau dọn" + } + }, + "washer_program": { + "name": "Chương trình" + }, + "time_remaining": { + "name": "Thời gian còn lại" + }, + "total_duration": { + "name": "Tổng thời lượng" + }, + "cycle_progress": { + "name": "Tiến triển" + }, + "current_power": { + "name": "Nguồn điện hiện tại" + }, + "elapsed_time": { + "name": "Thời gian đã trôi qua" + }, + "debug_info": { + "name": "Thông tin gỡ lỗi" + }, + "match_confidence": { + "name": "Sự tự tin trong trận đấu" + }, + "top_candidates": { + "name": "Ứng viên hàng đầu", + "state": { + "none": "Không có" + } + }, + "current_phase": { + "name": "Giai đoạn hiện tại" + }, + "wash_phase": { + "name": "Giai đoạn" + }, + "profile_cycle_count": { + "name": "Hồ sơ {profile_name} Đếm", + "unit_of_measurement": "cycles" + }, + "suggestions": { + "name": "Cài đặt được đề xuất" + }, + "pump_runs_today": { + "name": "Máy bơm chạy (24 giờ qua)" + }, + "cycle_count": { + "name": "Số chu kỳ" + } + }, + "select": { + "program_select": { + "name": "Chương trình chu trình", + "state": { + "auto_detect": "Tự động phát hiện" + } + } + }, + "button": { + "force_end_cycle": { + "name": "Buộc kết thúc chu kỳ" + }, + "pause_cycle": { + "name": "Chu kỳ tạm dừng" + }, + "resume_cycle": { + "name": "Tiếp tục chu kỳ" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "Cần có device_id hợp lệ." + }, + "cycle_id_required": { + "message": "Cần có Cycle_id hợp lệ." + }, + "profile_name_required": { + "message": "Cần có profile_name hợp lệ." + }, + "device_not_found": { + "message": "Không tìm thấy thiết bị." + }, + "no_config_entry": { + "message": "Không tìm thấy mục cấu hình cho thiết bị." + }, + "integration_not_loaded": { + "message": "Tích hợp không được tải cho thiết bị này." + }, + "cycle_not_found_or_no_power": { + "message": "Không tìm thấy chu trình hoặc không có dữ liệu nguồn." + }, + "trim_failed_empty_window": { + "message": "Cắt không thành công - không tìm thấy chu kỳ, không có dữ liệu nguồn hoặc cửa sổ kết quả trống." + }, + "trim_invalid_range": { + "message": "trim_end_s phải lớn hơn trim_start_s." + } + } +} diff --git a/custom_components/ha_washdata/translations/zh-Hans.json b/custom_components/ha_washdata/translations/zh-Hans.json new file mode 100644 index 0000000..3348e20 --- /dev/null +++ b/custom_components/ha_washdata/translations/zh-Hans.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData 设置", + "description": "配置您的洗衣机或其他设备。\n\n需要功率传感器。\n\n**接下来,系统会询问您是否要创建您的第一个个人资料。**", + "data": { + "name": "设备名称", + "device_type": "设备类型", + "power_sensor": "功率传感器", + "min_power": "最小功率阈值(W)" + }, + "data_description": { + "name": "该设备的友好名称(例如“洗衣机”、“洗碗机”)。", + "device_type": "这是什么类型的设备?帮助定制检测和标记。", + "power_sensor": "报告智能插头实时功耗(以瓦为单位)的传感器实体。", + "min_power": "功率读数高于此阈值(以瓦为单位)表明设备正在运行。大多数设备从 2W 开始。" + } + }, + "first_profile": { + "title": "创建第一个配置文件", + "description": "**周期**是您的设备的完整运行(例如,一堆衣物)。 **配置文件**按类型(例如“棉质”、“快洗”)对这些周期进行分组。现在创建配置文件可以帮助集成立即估计持续时间。\n\n如果未自动检测到此配置文件,您可以在设备控件中手动选择它。\n\n**将“配置文件名称”留空以跳过此步骤。**", + "data": { + "profile_name": "配置文件名称(可选)", + "manual_duration": "预计持续时间(分钟)" + } + } + }, + "error": { + "cannot_connect": "连接失败", + "invalid_auth": "验证无效", + "unknown": "意外错误" + }, + "abort": { + "already_configured": "设备已配置" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "查看学习反馈", + "settings": "设置", + "notifications": "通知", + "manage_cycles": "管理周期", + "manage_profiles": "管理个人资料", + "manage_phase_catalog": "管理阶段目录", + "record_cycle": "记录周期(手动)", + "diagnostics": "诊断与维护" + } + }, + "apply_suggestions": { + "title": "复制建议值", + "description": "这会将当前建议值复制到您保存的设置中。这是一次性操作,不会启用自动覆盖。\n\n建议复制的值:\n{suggested}", + "data": { + "confirm": "确认复制操作" + } + }, + "apply_suggestions_confirm": { + "title": "查看建议的更改", + "description": "WashData 发现 **{pending_count}** 要应用的建议值。\n\n变化:\n{changes}\n\n✅ 选中下面的框并单击 **提交** 以立即应用并保存这些更改。\n❌ 不勾选并点击**提交**取消并返回。", + "data": { + "confirm_apply_suggestions": "应用并保存这些更改" + } + }, + "settings": { + "title": "设置", + "description": "{deprecation_warning}调整检测阈值、学习和高级行为。默认值适用于大多数设置。\n\n当前可用的建议设置:{suggestions_count}。\n如果该值大于 0,请打开高级设置并使用“应用建议值”查看建议。", + "data": { + "apply_suggestions": "应用建议值", + "device_type": "设备类型", + "power_sensor": "功率传感器实体", + "min_power": "最小功率(瓦)", + "off_delay": "周期结束延迟(秒)", + "notify_actions": "通知操作", + "notify_people": "延迟送货,直到这些人回家", + "notify_only_when_home": "延迟通知,直到选定的人在家", + "notify_fire_events": "触发自动化事件", + "notify_start_services": "周期开始 - 通知目标", + "notify_finish_services": "周期完成 - 通知目标", + "notify_live_services": "实时进展 - 通知目标", + "notify_before_end_minutes": "预完成通知(结束前几分钟)", + "notify_title": "通知标题", + "notify_icon": "通知图标", + "notify_start_message": "启动消息格式", + "notify_finish_message": "完成消息格式", + "notify_pre_complete_message": "预完成消息格式", + "show_advanced": "编辑高级设置(下一步)" + }, + "data_description": { + "apply_suggestions": "选中此框可将学习算法建议的值复制到表单中。保存前检查。\n\n建议(来自学习):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "选择设备类型(洗衣机、烘干机、洗碗机、咖啡机、电动汽车)。该标签随每个周期一起存储,以便更好地组织。", + "power_sensor": "传感器实体报告实时功率(以瓦为单位)。您可以随时更改此设置,而不会丢失历史数据 - 如果您更换智能插头,这很有用。", + "min_power": "功率读数高于此阈值(以瓦为单位)表明设备正在运行。大多数设备从 2W 开始。", + "off_delay": "平滑功率必须保持低于停止阈值的秒数才能标记完成。默认值:120 秒。", + "notify_actions": "用于运行通知的可选 Home Assistant 操作序列(脚本、通知、TTS 等)。如果设置,操作将在回退通知服务之前运行。", + "notify_people": "用于存在门控的人员实体。启用后,通知发送会延迟,直到至少一名选定的人在家。", + "notify_only_when_home": "如果启用,则延迟通知,直到至少一个选定的人在家。", + "notify_fire_events": "如果启用,则触发循环开始和结束的 Home Assistant 事件(ha_washdata_cycle_started 和 ha_washdata_cycle_ended)以驱动自动化。", + "notify_start_services": "一个或多个通知服务在周期开始时发出警报。留空以跳过开始通知。", + "notify_finish_services": "一个或多个通知服务在周期完成或接近完成时发出警报。留空以跳过完成通知。", + "notify_live_services": "一项或多项通知服务在周期运行时接收实时进度更新(仅限移动配套应用程序)。留空以跳过实时更新。", + "notify_before_end_minutes": "触发通知的预计周期结束前的分钟数。设置为 0 以禁用。默认值:0。", + "notify_title": "通知的自定义标题。支持 {device} 占位符。", + "notify_icon": "用于通知的图标(例如 mdi:洗衣机)。留空以不发送图标。", + "notify_start_message": "循环开始时发送的消息。支持 {device} 占位符。", + "notify_finish_message": "循环结束时发送的消息。支持 {device}、{duration}、{program}, {energy_kwh}, {cost} 占位符。", + "notify_pre_complete_message": "循环运行时,循环实时进度的文本会更新。支持 {device}、{minutes}、{program} 占位符。" + } + }, + "notifications": { + "title": "通知", + "description": "配置通知渠道、模板和可选的实时移动进度更新。", + "data": { + "notify_actions": "通知操作", + "notify_people": "延迟送货,直到这些人回家", + "notify_only_when_home": "延迟通知,直到选定的人在家", + "notify_fire_events": "触发自动化事件", + "notify_start_services": "周期开始 - 通知目标", + "notify_finish_services": "周期完成 - 通知目标", + "notify_live_services": "实时进展 - 通知目标", + "notify_before_end_minutes": "预完成通知(结束前几分钟)", + "notify_live_interval_seconds": "实时更新间隔(秒)", + "notify_live_overrun_percent": "实时更新超限容限 (%)", + "notify_live_chronometer": "天文台倒计时器", + "notify_title": "通知标题", + "notify_icon": "通知图标", + "notify_start_message": "启动消息格式", + "notify_finish_message": "完成消息格式", + "notify_pre_complete_message": "预完成消息格式", + "energy_price_entity": "能源价格 - 实体(可选)", + "energy_price_static": "能源价格 - 静态值(可选)", + "go_back": "不保存并返回", + "notify_reminder_message": "提醒消息格式", + "notify_timeout_seconds": "自动关闭后(秒)", + "notify_channel": "通知通道(状态/实时/提醒)", + "notify_finish_channel": "完成通知通道" + }, + "data_description": { + "go_back": "勾选此项并点击提交,以放弃上述任何更改并返回主菜单。", + "notify_actions": "用于运行通知的可选 Home Assistant 操作序列(脚本、通知、TTS 等)。如果设置,操作将在回退通知服务之前运行。", + "notify_people": "用于存在门控的人员实体。启用后,通知发送会延迟,直到至少一名选定的人在家。", + "notify_only_when_home": "如果启用,则延迟通知,直到至少一个选定的人在家。", + "notify_fire_events": "如果启用,则触发循环开始和结束的 Home Assistant 事件(ha_washdata_cycle_started 和 ha_washdata_cycle_ended)以驱动自动化。", + "notify_start_services": "一个或多个通知服务在周期开始时发出警报(例如notify.mobile_app_my_phone)。留空以跳过开始通知。", + "notify_finish_services": "一个或多个通知服务在周期完成或接近完成时发出警报。留空以跳过完成通知。", + "notify_live_services": "一项或多项通知服务在周期运行时接收实时进度更新(仅限移动配套应用程序)。留空以跳过实时更新。", + "notify_before_end_minutes": "触发通知的预计周期结束前的分钟数。设置为 0 以禁用。默认值:0。", + "notify_live_interval_seconds": "运行时发送进度更新的频率。值越低,响应速度越快,但会消耗更多通知。强制执行至少 30 秒 - 低于 30 秒的值将自动四舍五入为 30 秒。", + "notify_live_overrun_percent": "安全裕度高于长/超出周期的估计更新计数(例如 20% 允许 1.2 倍估计更新)。", + "notify_live_chronometer": "启用后,每次实时更新都会包含预计完成时间的计时器倒计时。更新之间,通知计时器会在设备上自动计时(仅限 Android 配套应用程序)。", + "notify_title": "通知的自定义标题。支持 {device} 占位符。", + "notify_icon": "用于通知的图标(例如 mdi:洗衣机)。留空以不发送图标。", + "notify_start_message": "循环开始时发送的消息。支持 {device} 占位符。", + "notify_finish_message": "循环结束时发送的消息。支持 {device}、{duration}、{program}, {energy_kwh}, {cost} 占位符。", + "energy_price_entity": "选择保存当前电价的数字实体(例如sensor.electricity_price、input_number.energy_rate)。设置后,启用 {cost} 占位符。如果两者都配置了,则优先于静态值。", + "energy_price_static": "每千瓦时固定电价。设置后,启用 {cost} 占位符。如果上面配置了实体,则忽略。在消息模板中添加您的货币符号,例如{cost} 欧元。", + "notify_reminder_message": "一次性提醒文本在预计结束前发送配置的分钟数。与上面的定期实时更新不同。支持 {device}、{minutes}、{program} 占位符。", + "notify_timeout_seconds": "在这么多秒后自动关闭通知(作为配套应用程序“超时”转发)。设置为 0 以保留它们直到被解雇。默认值:0。", + "notify_channel": "Android 配套应用通知渠道,用于显示状态、实时进度和提醒通知。频道的声音和重要性是在第一次使用频道名称时在配套应用程序中配置的 - WashData 仅设置频道名称。对于应用程序默认值,将其留空。", + "notify_finish_channel": "单独的 Android 通道用于已完成的警报(也由提醒和洗衣等待小车使用),以便它可以有自己的声音。留空以重复使用上面的通道。", + "notify_pre_complete_message": "循环运行时,循环实时进度的文本会更新。支持 {device}、{minutes}、{program} 占位符。" + } + }, + "advanced_settings": { + "title": "高级设置", + "description": "调整检测阈值、学习和高级行为。默认值适用于大多数设置。", + "sections": { + "suggestions_section": { + "name": "建议设置", + "description": "{suggestions_count} 条已学习的建议可用。勾选应用建议值以在保存前查看建议的更改。", + "data": { + "apply_suggestions": "应用建议值" + }, + "data_description": { + "apply_suggestions": "选中此框可打开学习算法建议值的检查步骤。没有任何内容会自动保存。\n\n建议(来自学习):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "检测与功率阈值", + "description": "设备在何时被视为开启或关闭、能量门限以及状态转换的时序。", + "data": { + "start_duration_threshold": "开始去抖持续时间(秒)", + "start_energy_threshold": "启动能量门(Wh)", + "completion_min_seconds": "完成最短运行时间(秒)", + "start_threshold_w": "启动阈值(W)", + "stop_threshold_w": "停止阈值(W)", + "end_energy_threshold": "最终能量门(Wh)", + "running_dead_zone": "运行死区(秒)", + "end_repeat_count": "结束重复计数", + "min_off_gap": "周期之间的最小间隙(秒)", + "sampling_interval": "采样间隔(秒)" + }, + "data_description": { + "start_duration_threshold": "忽略短于此持续时间(秒)的短暂功率峰值。在实际周期开始之前过滤掉启动尖峰(例如 60W 持续 2 秒)。默认值:5 秒。", + "start_energy_threshold": "循环必须在启动阶段积累至少这么多的能量 (Wh) 才能得到确认。默认值:0.005 瓦时。", + "completion_min_seconds": "比这短的周期即使自然结束也会被标记为“中断”。默认值:600 秒。", + "start_threshold_w": "功率必须上升到该阈值以上才能开始一个周期。设置高于待机功率。默认值:min_power + 1 W。", + "stop_threshold_w": "功率必须低于该阈值才能结束一个周期。设置略高于零以避免噪音触发错误结束。默认值:0.6 * min_power。", + "end_energy_threshold": "仅当最后一个关闭延迟窗口中的能量低于该值 (Wh) 时,循环才会结束。如果功率波动接近于零,可防止错误结束。默认值:0.05 瓦时。", + "running_dead_zone": "周期开始后,忽略如此多秒的功率骤降,以防止初始旋转阶段期间的错误结束检测。设置为 0 以禁用。默认值:0 秒。", + "end_repeat_count": "在结束周期之前必须连续满足结束条件(功率低于 off_delay 阈值)的次数。较高的值可防止暂停期间出现错误结束。默认值:1。", + "min_off_gap": "如果一个周期在前一个周期结束后的这么多秒内开始,它们将被合并为一个周期。", + "sampling_interval": "最小采样间隔(以秒为单位)。超过此速率的更新将被忽略。默认值:30 秒(洗衣机、洗衣干衣机和洗碗机为 2 秒)。" + } + }, + "matching_section": { + "name": "配置文件匹配与学习", + "description": "WashData 以多积极的方式将运行中的周期与已学习的配置文件进行匹配,以及何时请求反馈。", + "data": { + "profile_match_min_duration_ratio": "配置文件匹配最短持续时间比率(0.1-1.0)", + "profile_match_interval": "配置文件匹配间隔(秒)", + "profile_match_threshold": "配置文件匹配阈值", + "profile_unmatch_threshold": "配置文件不匹配阈值", + "auto_label_confidence": "自动标记置信度 (0-1)", + "learning_confidence": "反馈请求置信度 (0-1)", + "suppress_feedback_notifications": "抑制反馈通知", + "duration_tolerance": "持续时间容差(0-0.5)", + "smoothing_window": "平滑窗口(样本)", + "profile_duration_tolerance": "配置文件匹配持续时间容差 (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "配置文件匹配的最小持续时间比率。运行周期必须至少是配置文件持续时间的这一部分才能匹配。较低 = 较早匹配。默认值:0.50 (50%)。", + "profile_match_interval": "在一个周期内尝试配置文件匹配的频率(以秒为单位)。较低的值可提供更快的检测速度,但会使用更多的 CPU。默认值:300 秒(5 分钟)。", + "profile_match_threshold": "将配置文件视为匹配所需的最低 DTW 相似度分数 (0.0–1.0)。越高=匹配越严格,误报越少。默认值:0.4。", + "profile_unmatch_threshold": "DTW 相似性得分 (0.0–1.0),低于该得分的先前匹配的配置文件将被拒绝。应低于匹配阈值以防止闪烁。默认值:0.35。", + "auto_label_confidence": "完成时自动标记等于或高于此置信度的循环 (0.0–1.0)。", + "learning_confidence": "通过事件请求用户验证的最低置信度 (0.0–1.0)。", + "suppress_feedback_notifications": "启用后,WashData 仍将在内部跟踪和请求反馈,但不会显示要求您确认周期的持续通知。当循环总是被正确检测到并且您发现提示分散注意力时很有用。", + "duration_tolerance": "运行期间剩余时间估计的容差(0.0–0.5 对应于 0–50%)。", + "smoothing_window": "用于移动平均平滑的最近功率读数的数量。", + "profile_duration_tolerance": "配置文件匹配所使用的总持续时间方差的容差 (0.0–0.5)。默认行为:±25%。" + } + }, + "timing_section": { + "name": "时序、维护与调试", + "description": "看门狗、进度重置、自动维护以及调试实体的公开。", + "data": { + "watchdog_interval": "看门狗间隔(秒)", + "no_update_active_timeout": "无更新超时(秒)", + "progress_reset_delay": "进度重置延迟(秒)", + "auto_maintenance": "启用自动维护", + "expose_debug_entities": "公开调试实体", + "save_debug_traces": "保存调试跟踪" + }, + "data_description": { + "watchdog_interval": "运行时看门狗检查之间的秒数。默认值:5 秒。警告:确保该值高于传感器的更新间隔,以避免错误停止(例如,如果传感器每 60 秒更新一次,则使用 65 秒以上)。", + "no_update_active_timeout": "对于更改发布传感器:只有在这么多秒内没有更新到达时才强制结束 ACTIVE 周期。低功耗完成仍然使用关闭延迟。", + "progress_reset_delay": "一个周期完成 (100%) 后,等待这么多秒的空闲时间,然后再将进度重置为 0%。 Default: 300s.", + "auto_maintenance": "启用自动维护(修复样本、合并片段)。", + "expose_debug_entities": "显示高级传感器(置信度、相位、模糊度)以进行调试。", + "save_debug_traces": "在历史记录中存储详细的排名和电量跟踪数据(增加存储使用量)。" + } + }, + "anti_wrinkle_section": { + "name": "防皱保护(烘干机)", + "description": "忽略周期结束后的滚筒转动,以防止幽灵周期。仅适用于烘干机 / 洗烘一体机。", + "data": { + "anti_wrinkle_enabled": "抗皱护罩(仅限烘干机)", + "anti_wrinkle_max_power": "抗皱最大功率(W)", + "anti_wrinkle_max_duration": "抗皱最大持续时间(秒)", + "anti_wrinkle_exit_power": "抗皱退出功率(W)" + }, + "data_description": { + "anti_wrinkle_enabled": "循环结束后忽略短暂的低功率滚筒旋转,以防止幽灵循环(仅限烘干机/洗衣烘干机)。", + "anti_wrinkle_max_power": "等于或低于该功率的爆发被视为抗皱旋转而不是新的循环。仅在启用抗皱护罩时适用。", + "anti_wrinkle_max_duration": "视为抗皱旋转的最大爆破长度。仅在启用抗皱护罩时适用。", + "anti_wrinkle_exit_power": "如果功率低于此水平,请立即退出抗皱(真正关闭)。仅在启用抗皱护罩时适用。" + } + }, + "delay_start_section": { + "name": "延迟启动检测", + "description": "在周期真正开始之前,将持续的低功耗待机视为“等待启动”。", + "data": { + "delay_start_detect_enabled": "启用延迟启动检测", + "delay_confirm_seconds": "延迟启动:待机确认时间(秒)", + "delay_timeout_hours": "延迟启动:最长等待时间 (h)" + }, + "data_description": { + "delay_start_detect_enabled": "启用后,会检测到短暂的低功耗峰值,然后持续待机功耗,作为延迟启动。延迟期间状态传感器显示“等待启动”。在周期实际开始之前,不会跟踪计划时间或进度。要求将 start_threshold_w 设置为高于漏极尖峰功率。", + "delay_confirm_seconds": "在 WashData 进入“等待启动”之前,功率必须在停止阈值 (W) 和启动阈值 (W) 之间保持这么多秒。可过滤短暂的菜单导航峰值。默认值:60 秒。", + "delay_timeout_hours": "安全超时:如果经过这么多小时机器仍处于“等待启动”状态,WashData 将重置为“关闭”。默认值:8 小时。" + } + }, + "external_triggers_section": { + "name": "外部触发、门控与暂停", + "description": "可选的外部结束触发传感器、用于暂停/洁净检测的门传感器,以及暂停断电开关。", + "data": { + "external_end_trigger_enabled": "启用外部循环结束触发器", + "external_end_trigger": "外部循环结束传感器", + "external_end_trigger_inverted": "反转触发逻辑(触发关闭)", + "door_sensor_entity": "门磁", + "pause_cuts_power": "暂停时切断电源", + "switch_entity": "开关实体(用于暂停断电)", + "notify_unload_delay_minutes": "洗衣等待通知延迟(分钟)" + }, + "data_description": { + "external_end_trigger_enabled": "启用对外部二进制传感器的监控以触发循环完成。", + "external_end_trigger": "选择二进制传感器实体。当该传感器触发时,当前周期将以“已完成”状态结束。", + "external_end_trigger_inverted": "默认情况下,当传感器打开时触发器会触发。选中此框以在传感器关闭时触发。", + "door_sensor_entity": "用于机器门的可选二进制传感器(开=打开,关=关闭)。当门在活动周期期间打开时,WashData 将确认该周期是有意暂停的。循环结束后,如果门仍然关闭,则状态更改为“清洁”,直到门打开。注意:关上门不会自动恢复暂停的循环 - 必须通过“恢复循环”按钮或服务手动触发恢复。", + "pause_cuts_power": "通过“暂停循环”按钮或服务暂停时,还要关闭配置的开关实体。仅当为该设备配置了交换实体时才生效。", + "switch_entity": "暂停或恢复时要切换的开关实体。启用“暂停时切断电源”时需要。", + "notify_unload_delay_minutes": "循环结束且门在这么多分钟内未打开后,通过完成通知通道发送通知(需要门传感器)。设置为 0 以禁用。默认值:60 分钟" + } + }, + "pump_section": { + "name": "泵监控", + "description": "仅适用于泵设备类型。如果泵周期运行超过此时长,则触发事件。", + "data": { + "pump_stuck_duration": "泵卡住警报阈值(仅限泵)" + }, + "data_description": { + "pump_stuck_duration": "如果泵循环在这么多秒后仍在运行,则会触发一次 ha_washdata_pump_stuck 事件。用它来检测堵塞的电机(典型的污水泵运行时间低于 60 秒)。默认值:1800 秒(30 分钟)。仅限泵设备类型。" + } + }, + "device_link_section": { + "name": "设备链接", + "description": "(可选)将此 WashData 设备链接到现有的 Home Assistant 设备(例如智能插头或设备本身)。然后,WashData 设备将显示为“通过该设备连接”。留空可将 WashData 保留为独立设备。", + "data": { + "linked_device": "链接设备" + }, + "data_description": { + "linked_device": "选择要连接 WashData 的设备。这会在设备注册表中添加“连接方式”引用; WashData保留自己的设备卡和实体。清除选择以删除链接。" + } + } + } + }, + "diagnostics": { + "title": "诊断与维护", + "description": "运行维护操作,例如合并碎片周期或迁移存储的数据。\n\n**存储使用情况**\n\n- 文件大小:{file_size_kb} KB\n- 周期:{cycle_count}\n- 个人资料:{profile_count}\n- 调试跟踪:{debug_count}", + "menu_options": { + "reprocess_history": "维护:重新处理和优化数据", + "clear_debug_data": "清除调试数据(释放空间)", + "wipe_history": "擦除该设备的所有数据(不可逆)", + "export_import": "使用设置导出/导入 JSON(复制/粘贴)", + "menu_back": "← 返回" + } + }, + "clear_debug_data": { + "title": "清除调试数据", + "description": "您确定要删除所有存储的调试跟踪吗?这将释放空间,但会删除过去周期中的详细排名信息。" + }, + "manage_cycles": { + "title": "管理周期", + "description": "最近的周期:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "自动标记旧自行车", + "select_cycle_to_label": "标签特定周期", + "select_cycle_to_delete": "删除循环", + "interactive_editor": "合并/拆分交互式编辑器", + "trim_cycle_select": "修剪周期数据", + "menu_back": "← 返回" + } + }, + "manage_cycles_empty": { + "title": "管理周期", + "description": "尚未记录任何周期。", + "menu_options": { + "auto_label_cycles": "自动标记旧自行车", + "menu_back": "← 返回" + } + }, + "manage_profiles": { + "title": "管理个人资料", + "description": "简介摘要:\n{profile_summary}", + "menu_options": { + "create_profile": "创建新的个人资料", + "edit_profile": "编辑/重命名配置文件", + "delete_profile_select": "删除个人资料", + "profile_stats": "个人资料统计", + "cleanup_profile": "清理历史记录 - 图表和删除", + "assign_profile_phases_select": "分配相位范围", + "menu_back": "← 返回" + } + }, + "manage_profiles_empty": { + "title": "管理个人资料", + "description": "尚未创建配置文件。", + "menu_options": { + "create_profile": "创建新的个人资料", + "menu_back": "← 返回" + } + }, + "manage_phase_catalog": { + "title": "管理阶段目录", + "description": "阶段目录(该设备的默认值+自定义阶段):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "创建新阶段", + "phase_catalog_edit_select": "编辑阶段", + "phase_catalog_delete": "删除阶段", + "menu_back": "← 返回" + } + }, + "phase_catalog_create": { + "title": "创建自定义阶段", + "description": "创建自定义阶段名称和描述,然后选择其适用的设备类型。", + "data": { + "target_device_type": "目标设备类型", + "phase_name": "阶段名称", + "phase_description": "阶段描述" + } + }, + "phase_catalog_edit_select": { + "title": "编辑自定义阶段", + "description": "选择要编辑的自定义阶段。", + "data": { + "phase_name": "定制阶段" + } + }, + "phase_catalog_edit": { + "title": "编辑自定义阶段", + "description": "编辑阶段:{phase_name}", + "data": { + "phase_name": "阶段名称", + "phase_description": "阶段描述" + } + }, + "phase_catalog_delete": { + "title": "删除自定义阶段", + "description": "选择要删除的自定义阶段。使用此阶段的任何指定范围都将被删除。", + "data": { + "phase_name": "定制阶段" + } + }, + "assign_profile_phases": { + "title": "分配相位范围", + "description": "配置文件的阶段预览:**{profile_name}**\n\n{timeline_svg}\n\n**当前范围:**\n{current_ranges}\n\n使用以下操作来添加、编辑、删除或保存范围。", + "menu_options": { + "assign_profile_phases_add": "添加相位范围", + "assign_profile_phases_edit_select": "编辑相位范围", + "assign_profile_phases_delete": "删除相位范围", + "phase_ranges_clear": "清除所有范围", + "assign_profile_phases_auto_detect": "自动检测相位", + "phase_ranges_save": "保存并返回", + "menu_back": "← 返回" + } + }, + "assign_profile_phases_add": { + "title": "添加相位范围", + "description": "在当前草稿中添加一个阶段范围。", + "data": { + "phase_name": "阶段", + "start_min": "开始时间", + "end_min": "结束分钟" + } + }, + "assign_profile_phases_edit_select": { + "title": "编辑相位范围", + "description": "选择要编辑的范围。", + "data": { + "range_index": "相位范围" + } + }, + "assign_profile_phases_edit": { + "title": "编辑相位范围", + "description": "更新选定的相位范围。", + "data": { + "phase_name": "阶段", + "start_min": "开始时间", + "end_min": "结束分钟" + } + }, + "assign_profile_phases_delete": { + "title": "删除相位范围", + "description": "选择要从草稿中删除的范围。", + "data": { + "range_index": "相位范围" + } + }, + "assign_profile_phases_auto_detect": { + "title": "自动检测阶段", + "description": "个人资料:***{profile_name}**\n\n{timeline_svg}\n\n**自动检测到 {detected_count} 个阶段。** 选择下面的操作。", + "data": { + "action": "行动" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "命名检测到的阶段", + "description": "个人资料:***{profile_name}**\n\n**检测到{detected_count}相:**\n{ranges_summary}\n\n为每个阶段指定一个名称。" + }, + "assign_profile_phases_select": { + "title": "分配相位范围", + "description": "选择一个配置文件来配置相位范围。", + "data": { + "profile": "轮廓" + } + }, + "profile_stats": { + "title": "个人资料统计", + "description": "{stats_table}" + }, + "create_profile": { + "title": "创建新的个人资料", + "description": "手动或从过去的周期创建新的配置文件。\n\n配置文件名称示例:“Delicates”、“Heavy Duty”、“Quick Wash”", + "data": { + "profile_name": "个人资料名称", + "reference_cycle": "参考周期(可选)", + "manual_duration": "手动持续时间(分钟)" + } + }, + "edit_profile": { + "title": "编辑个人资料", + "description": "选择要重命名的配置文件", + "data": { + "profile": "轮廓" + } + }, + "rename_profile": { + "title": "编辑个人资料详细信息", + "description": "当前个人资料:{current_name}", + "data": { + "new_name": "个人资料名称", + "manual_duration": "手动持续时间(分钟)" + } + }, + "delete_profile_select": { + "title": "删除个人资料", + "description": "选择要删除的配置文件", + "data": { + "profile": "轮廓" + } + }, + "delete_profile_confirm": { + "title": "确认删除个人资料", + "description": "⚠️ 这将永久删除个人资料:{profile_name}", + "data": { + "unlabel_cycles": "使用此配置文件从循环中删除标签" + } + }, + "auto_label_cycles": { + "title": "自动标记旧自行车", + "description": "共找到 {total_count} 个周期。个人资料:{profiles}", + "data": { + "confidence_threshold": "最低置信度 (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "选择循环标签", + "description": "✓ = 已完成,⚠ = 已恢复,✗ = 已中断", + "data": { + "cycle_id": "循环" + } + }, + "select_cycle_to_delete": { + "title": "删除循环", + "description": "⚠️这将永久删除所选周期", + "data": { + "cycle_id": "循环删除" + } + }, + "label_cycle": { + "title": "标签周期", + "description": "{cycle_info}", + "data": { + "profile_name": "轮廓", + "new_profile_name": "新配置文件名称(如果创建)" + } + }, + "post_process": { + "title": "流程历史记录(合并/拆分)", + "description": "通过合并碎片运行并在时间窗口内拆分错误合并的周期来清理周期历史记录。输入要回顾的小时数(全部使用 999999)。", + "data": { + "time_range": "回顾窗口(小时)", + "gap_seconds": "合并/拆分间隙(秒)" + }, + "data_description": { + "time_range": "扫描要合并的碎片周期的小时数。使用999999处理所有历史数据。", + "gap_seconds": "决定拆分还是合并的阈值。比这更短的间隙将被合并。比这更长的间隙被分开。降低此值可强制对错误合并的循环进行拆分。" + } + }, + "cleanup_profile": { + "title": "清理历史记录 - 选择配置文件", + "description": "选择要可视化的配置文件。这将生成一个图表,显示该配置文件的所有过去周期,以帮助识别异常值。", + "data": { + "profile": "轮廓" + } + }, + "cleanup_select": { + "title": "清理历史记录 - 图表和删除", + "description": "![图表]({graph_url})\n\n**可视化{profile_name}**\n\n该图将各个周期显示为彩色线。识别异常值(远离该组的线)并在下面的列表中匹配它们的颜色以将其删除。\n\n**选择要永久删除的周期:**", + "data": { + "cycles_to_delete": "循环删除(检查匹配颜色)" + } + }, + "interactive_editor": { + "title": "合并/拆分交互式编辑器", + "description": "选择要对您的周期历史记录执行的手动操作。", + "menu_options": { + "editor_split": "分割一个周期(查找间隙)", + "editor_merge": "合并循环(连接片段)", + "editor_delete": "删除周期", + "menu_back": "← 返回" + } + }, + "editor_select": { + "title": "选择周期", + "description": "选择要处理的周期。\n\n{info_text}", + "data": { + "selected_cycles": "周期" + } + }, + "editor_configure": { + "title": "配置和应用", + "description": "{preview_md}", + "data": { + "confirm_action": "行动", + "merged_profile": "生成的配置文件", + "new_profile_name": "新配置文件名称", + "confirm_commit": "是的,我确认此操作", + "segment_0_profile": "第 1 段简介", + "segment_1_profile": "2 段简介", + "segment_2_profile": "第 3 段简介", + "segment_3_profile": "第 4 段简介", + "segment_4_profile": "5 段简介", + "segment_5_profile": "6 段简介", + "segment_6_profile": "7 段简介", + "segment_7_profile": "8 段简介", + "segment_8_profile": "9 段简介", + "segment_9_profile": "10 段简介" + } + }, + "editor_split_params": { + "title": "分体配置", + "description": "调整分割此循环的灵敏度。任何超过此长度的空闲间隙都会导致分裂。", + "data": { + "split_mode": "分割方式" + } + }, + "editor_split_auto_params": { + "title": "自动分段配置", + "description": "调整拆分此周期的灵敏度。任何长于此值的空闲间隔都会触发拆分。", + "data": { + "min_gap_seconds": "分段间隔阈值(秒)" + } + }, + "editor_split_manual_params": { + "title": "手动分段时间戳", + "description": "{preview_md}周期窗口:**{cycle_start_wallclock} → {cycle_end_wallclock}**,日期为 {cycle_date}。\n\n输入一个或多个分段时间戳(`HH:MM` 或 `HH:MM:SS`),每行一个。周期将在每个时间戳处被切开 — N 个时间戳会产生 N+1 个片段。", + "data": { + "split_timestamps": "分割时间戳" + } + }, + "reprocess_history": { + "title": "维护:重新处理和优化数据", + "description": "对历史数据进行深度清理:\n1. 调整零功率读数(静音)。\n2. 修复循环时间/持续时间。\n3. 重新计算所有统计模型(包络线)。\n\n**运行此命令可以修复数据问题或应用新算法。**" + }, + "wipe_history": { + "title": "擦除历史记录(仅测试)", + "description": "⚠️ 这将永久删除该设备的所有存储周期和配置文件。这无法撤销!" + }, + "export_import": { + "title": "导出/导入 JSON", + "description": "复制/粘贴该设备的周期、配置文件和设置。下面导出或粘贴 JSON 进行导入。", + "data": { + "mode": "行动", + "json_payload": "完整配置 JSON" + }, + "data_description": { + "mode": "选择“导出”以复制当前数据和设置,或选择“导入”以粘贴从另一个 WashData 设备导出的数据。", + "json_payload": "对于导出:复制此 JSON(包括周期、配置文件和所有微调设置)。对于导入:粘贴从 WashData 导出的 JSON。" + } + }, + "record_cycle": { + "title": "记录周期", + "description": "手动记录一个周期以创建无干扰的干净轮廓。\n\n状态:***{status}**\n持续时间:{duration}秒\n样本:{samples}", + "menu_options": { + "record_refresh": "刷新状态", + "record_stop": "停止录制(保存并处理)", + "record_start": "开始新录音", + "record_process": "处理最后的录音(修剪和保存)", + "record_discard": "放弃最后的录音", + "menu_back": "← 返回" + } + }, + "record_process": { + "title": "过程记录", + "description": "![图表]({graph_url})\n\n**图表显示原始记录。**蓝色 = 保留,红色 = 建议的修剪。\n*图表是静态的,不会随着表单更改而更新。*\n\n录音停止了。修剪与检测到的采样率 (~{sampling_rate}s) 对齐。\n\n原始持续时间:{duration} 秒\n样本:{samples}", + "data": { + "head_trim": "修剪开始(秒)", + "tail_trim": "修剪结束(秒)", + "save_mode": "保存目的地", + "profile_name": "个人资料名称" + } + }, + "learning_feedbacks": { + "title": "学习反馈", + "description": "待处理反馈:{count}\n\n{pending_table}\n\n选择要处理的周期审核请求。", + "menu_options": { + "learning_feedbacks_pick": "查看待处理反馈", + "learning_feedbacks_dismiss_all": "忽略所有待处理反馈", + "menu_back": "← 返回" + } + }, + "learning_feedbacks_pick": { + "title": "选择要查看的反馈", + "description": "选择要打开的周期复查请求。", + "data": { + "selected_feedback": "选择要查看的周期" + } + }, + "learning_feedbacks_empty": { + "title": "学习反馈", + "description": "未找到待处理的反馈请求。", + "menu_options": { + "menu_back": "← 返回" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "忽略所有待处理反馈", + "description": "您即将忽略 **{count}** 个待处理反馈请求。被忽略的周期会保留在您的历史记录中,但不会贡献新的学习信号。此操作无法撤销。\n\n✅ 选中下面的框并单击 **提交** 以忽略全部。\n❌ 不勾选并点击 **提交** 以取消并返回。", + "data": { + "confirm_dismiss_all": "确认:忽略所有待处理的反馈请求" + } + }, + "resolve_feedback": { + "title": "解决反馈", + "description": "{comparison_img}{candidates_table}\n**检测到的配置文件**:{detected_profile} ({confidence_pct}%)\n**预计持续时间**:{est_duration_min} 分钟\n**实际持续时间**:{act_duration_min} 分钟\n\n__选择以下操作:__", + "data": { + "action": "你想做什么?", + "corrected_profile": "正确的程序(如果更正)", + "corrected_duration": "正确持续时间(以秒为单位)(如果进行更正)" + } + }, + "trim_cycle_select": { + "title": "修剪周期 - 选择周期", + "description": "选择要修剪的周期。 ✓ = 已完成,⚠ = 已恢复,✗ = 已中断", + "data": { + "cycle_id": "循环" + } + }, + "trim_cycle": { + "title": "修剪周期", + "description": "当前窗口:{trim_summary}\n\n{trim_svg}", + "data": { + "action": "行动" + } + }, + "trim_cycle_start": { + "title": "设置修剪开始", + "description": "循环窗口:{cycle_start_wallclock} → {cycle_end_wallclock}\n\n当前开始时间:**{current_wallclock}**(距周期开始时间+{current_offset_min} 分钟)\n\n使用下面的时钟选择器选择新的开始时间。", + "data": { + "trim_start_time": "新的开始时间", + "trim_start_min": "新的开始(周期开始后的分钟数)" + } + }, + "trim_cycle_end": { + "title": "设置修剪结束", + "description": "循环窗口:{cycle_start_wallclock} → {cycle_end_wallclock}\n\n当前结束:**{current_wallclock}**(距离周期开始+{current_offset_min} 分钟)\n\n使用下面的时钟选择器选择新的结束时间。", + "data": { + "trim_end_time": "新的结束时间", + "trim_end_min": "新的结束(距离周期开始的分钟数)" + } + } + }, + "error": { + "import_failed": "导入失败。请粘贴有效的 WashData 导出 JSON。", + "select_exactly_one": "请仅为此操作选择 1 个周期。", + "select_at_least_one": "请至少为此操作选择 1 个周期。", + "select_at_least_two": "请至少为此操作选择 2 个周期。", + "profile_exists": "同名的配置文件已存在", + "rename_failed": "无法重命名配置文件。配置文件可能不存在或新名称已被使用。", + "assignment_failed": "无法将配置文件分配给循环", + "duplicate_phase": "具有此名称的阶段已存在。", + "phase_not_found": "未找到所选相。", + "invalid_phase_name": "阶段名称不能为空。", + "phase_name_too_long": "相名太长。", + "invalid_phase_range": "相位范围无效。结束必须大于开始。", + "invalid_phase_timestamp": "时间戳无效。使用 YYYY-MM-DD HH:MM 或 HH:MM 格式。", + "overlapping_phase_ranges": "相位范围重叠。调整范围并重试。", + "incomplete_phase_row": "每个阶段行必须包含一个阶段以及所选模式的完整开始/结束值。", + "timestamp_mode_cycle_required": "使用时间戳模式时请选择源周期。", + "no_phase_ranges": "尚无可用于该操作的阶段范围。", + "feedback_notification_title": "WashData:验证周期 ({device})", + "feedback_notification_message": "**设备**:{device}\n**程序**:{program}({confidence}% 置信度)\n**时间**:{time}\n\nWashData 需要您的帮助来验证检测到的此周期。\n\n请转至 **设置 > 设备和服务 > WashData > 配置 > 学习反馈** 以确认或更正此结果。", + "suggestions_ready_notification_title": "WashData:建议设置已就绪 ({device})", + "suggestions_ready_notification_message": "**建议设置**传感器现在报告 **{count}** 可操作的建议。\n\n要查看并应用它们:**设置 > 设备和服务 > WashData > 配置 > 高级设置 > 应用建议值**。\n\n建议是可选的,并会在您保存之前显示以供审核。", + "trim_range_invalid": "开始必须在结束之前。请调整您的修剪点。", + "trim_failed": "修剪失败。周期可能已不存在或窗口为空。", + "invalid_split_timestamp": "时间戳无效或超出周期时间窗口。请在周期时间窗口内使用 HH:MM 或 HH:MM:SS。", + "no_split_segments_found": "无法根据这些时间戳生成有效片段。请确保每个时间戳都在周期时间窗口内,且各片段至少为 1 分钟。", + "auto_tune_suggestion": "{device_type} {device_title} 检测到幽灵周期。建议的 min_power 更改:{current_min}W -> {new_min}W(不会自动应用)。", + "auto_tune_title": "WashData 自动调整", + "auto_tune_fallback": "{device_type} {device_title} 检测到幽灵周期。建议的 min_power 更改:{current_min}W -> {new_min}W(不会自动应用)。" + }, + "abort": { + "no_cycles_found": "没有找到循环", + "no_split_segments_found": "在所选周期中未找到可分割的片段。", + "cycle_not_found": "无法加载所选周期。", + "no_power_data": "所选周期没有可用的功率跟踪数据。", + "no_profiles_found": "未找到配置文件。首先创建一个配置文件。", + "no_profiles_for_matching": "没有可用于匹配的配置文件。首先创建配置文件。", + "no_unlabeled_cycles": "所有周期均已标记", + "no_suggestions": "尚无可用的建议值。运行几个周期并重试。", + "no_predictions": "没有预测", + "no_custom_phases": "未找到自定义阶段。", + "reprocess_success": "已成功重新处理 {count} 个周期。", + "debug_data_cleared": "已成功清除 {count} 个周期的调试数据。" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "循环开始", + "cycle_finish": "循环完成", + "cycle_live": "实时进展" + } + }, + "common_text": { + "options": { + "unlabeled": "(未标记)", + "create_new_profile": "创建新的个人资料", + "remove_label": "移除标签", + "no_reference_cycle": "(无参考周期)", + "all_device_types": "所有设备类型", + "current_suffix": "(当前的)", + "editor_select_info": "选择 1 个循环进行拆分,或选择 2 个以上循环进行合并。", + "editor_select_info_split": "选择 1 个要拆分的周期。", + "editor_select_info_merge": "选择 2 个或更多要合并的周期。", + "editor_select_info_delete": "选择 1 个或更多要删除的周期。", + "no_cycles_recorded": "尚未记录任何周期。", + "profile_summary_line": "- **{name}**:{count} 个周期,平均 {avg} 分钟", + "no_profiles_created": "尚未创建配置文件。", + "phase_preview": "阶段预览", + "phase_preview_no_curve": "平均轮廓曲线尚不可用。为此配置文件运行/标记更多周期。", + "average_power_curve": "平均功率曲线", + "unit_min": "分钟", + "profile_option_fmt": "{name}({count} 个周期,平均 ~{duration}m)", + "profile_option_short_fmt": "{name}({count} 个周期,~{duration} 分钟)", + "deleted_cycles_from_profile": "已从 {profile} 中删除 {count} 个周期。", + "not_enough_data_graph": "数据不足,无法生成图表。", + "no_profiles_found": "未找到配置文件。", + "cycle_info_fmt": "周期:{start},{duration}米,电流:{label}", + "top_candidates_header": "### 最佳候选人", + "tbl_profile": "轮廓", + "tbl_confidence": "信心", + "tbl_correlation": "相关性", + "tbl_duration_match": "持续时间匹配", + "detected_profile": "检测到的配置文件", + "estimated_duration": "预计持续时间", + "actual_duration": "实际持续时间", + "choose_action": "__选择以下操作:__", + "tbl_count": "数数", + "tbl_avg": "平均", + "tbl_min": "最小", + "tbl_max": "最大限度", + "tbl_energy_avg": "能量(平均)", + "tbl_energy_total": "能量(总计)", + "tbl_consistency": "组成。", + "tbl_last_run": "最后一次运行", + "graph_legend_title": "图例", + "graph_legend_body": "蓝色带代表观察到的最小和最大功率消耗范围。该线显示平均功率曲线。", + "recording_preview": "录音预览", + "trim_start": "修剪开始", + "trim_end": "修剪末端", + "split_preview_title": "分割预览", + "split_preview_found_fmt": "找到 {count} 个分段。", + "split_preview_confirm_fmt": "单击“确认”将此周期拆分为 {count} 个单独的周期。", + "merge_preview_title": "合并预览", + "merge_preview_joining_fmt": "加入 {count} 个周期。间隙将用 0W 读数填充。", + "editor_delete_preview_title": "删除预览", + "editor_delete_preview_intro": "所选周期将被永久删除:", + "editor_delete_preview_confirm": "点击确认以永久删除这些周期记录。", + "no_power_preview": "*没有可供预览的功率数据。*", + "profile_comparison": "简介比较", + "actual_cycle_label": "本周期(实际)", + "storage_usage_header": "存储使用情况", + "storage_file_size": "文件大小", + "storage_cycles": "周期", + "storage_profiles": "型材", + "storage_debug_traces": "调试跟踪", + "overview_suffix": "概述", + "phase_preview_for_profile": "配置文件的阶段预览", + "current_ranges_header": "电流范围", + "assign_phases_help": "使用以下操作来添加、编辑、删除或保存范围。", + "profile_summary_header": "简介摘要", + "recent_cycles_header": "最近的周期", + "trim_cycle_preview_title": "循环修剪预览", + "trim_cycle_preview_no_data": "此周期没有可用的功率数据。", + "trim_cycle_preview_kept_suffix": "保留", + "table_program": "程序", + "table_when": "时间", + "table_length": "时长", + "table_match": "匹配", + "table_profile": "轮廓", + "table_cycles": "周期", + "table_avg_length": "平均时长", + "table_last_run": "最后一次运行", + "table_avg_energy": "平均能耗", + "table_detected_program": "检测到的程序", + "table_confidence": "信心", + "table_reported": "已报告", + "phase_builtin_suffix": "(内置)", + "phase_none_available": "没有可用的阶段。", + "settings_deprecation_warning": "⚠️ **已弃用的设备类型:** {device_type} 计划在未来版本中删除。 WashData 的匹配管道不会为此类设备生成可靠的结果。您的集成在弃用期内继续有效;要消除此警告,请将下面的**设备类型**切换为受支持的类型之一(洗衣机、烘干机、洗衣干衣机组合、洗碗机、空气炸锅、面包机或泵),或者如果您的设备与任何受支持的类型不匹配,则切换为**其他(高级)**。 **其他(高级)** 故意提供通用默认值,未针对任何特定设备进行调整,因此您需要自己配置阈值、超时和匹配参数;切换时,所有现有设置都会保留。", + "phase_other_device_types": "其他设备类型:" + } + }, + "interactive_editor_action": { + "options": { + "split": "分割一个周期(查找间隙)", + "merge": "合并循环(连接片段)", + "delete": "删除周期" + } + }, + "split_mode": { + "options": { + "auto": "自动检测空闲间隔", + "manual": "手动时间戳" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "维护:重新处理和优化数据", + "clear_debug_data": "清除调试数据(释放空间)", + "wipe_history": "擦除该设备的所有数据(不可逆)", + "export_import": "使用设置导出/导入 JSON(复制/粘贴)" + } + }, + "export_import_mode": { + "options": { + "export": "导出所有数据", + "import": "导入/合并数据" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "自动标记旧自行车", + "select_cycle_to_label": "标签特定周期", + "select_cycle_to_delete": "删除循环", + "interactive_editor": "合并/拆分交互式编辑器", + "trim_cycle": "修剪周期数据" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "创建新的个人资料", + "edit_profile": "编辑/重命名配置文件", + "delete_profile": "删除个人资料", + "profile_stats": "个人资料统计", + "cleanup_profile": "清理历史记录 - 图表和删除", + "assign_phases": "分配相位范围" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "创建新阶段", + "edit_custom_phase": "编辑阶段", + "delete_custom_phase": "删除阶段" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "添加相位范围", + "edit_range": "编辑相位范围", + "delete_range": "删除相位范围", + "clear_ranges": "清除所有范围", + "auto_detect_ranges": "自动检测相位", + "save_ranges": "保存并返回" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "刷新状态", + "stop_recording": "停止录制(保存并处理)", + "start_recording": "开始新录音", + "process_recording": "处理最后的录音(修剪和保存)", + "discard_recording": "放弃最后的录音" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "创建新的个人资料", + "existing_profile": "添加到现有配置文件", + "discard": "放弃录音" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "确认-检测正确", + "correct": "正确 - 选择项目/持续时间", + "ignore": "忽略 - 误报/噪声循环", + "delete": "删除 - 从历史记录中删除" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "命名和应用阶段", + "cancel": "不做任何修改返回" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "设置起点", + "set_end": "设置终点", + "reset": "重置为完整持续时间", + "apply": "应用修剪", + "cancel": "取消" + } + }, + "device_type": { + "options": { + "washing_machine": "洗衣机", + "dryer": "烘干机", + "washer_dryer": "洗衣机烘干机组合", + "dishwasher": "洗碗机", + "coffee_machine": "咖啡机(已弃用)", + "ev": "电动汽车(已弃用)", + "air_fryer": "空气炸锅", + "heat_pump": "热泵(已弃用)", + "bread_maker": "面包机", + "pump": "泵/污水泵", + "oven": "烤箱(已弃用)", + "other": "其他(高级)" + } + } + }, + "services": { + "label_cycle": { + "name": "标签周期", + "description": "将现有配置文件分配给过去的周期,或删除标签。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置要贴上标签。" + }, + "cycle_id": { + "name": "周期ID", + "description": "要标记的周期的 ID。" + }, + "profile_name": { + "name": "个人资料名称", + "description": "现有配置文件的名称(在“管理配置文件”菜单中创建配置文件)。留空以删除标签。" + } + } + }, + "create_profile": { + "name": "创建个人资料", + "description": "创建新的配置文件(独立或基于参考周期)。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置。" + }, + "profile_name": { + "name": "个人资料名称", + "description": "新配置文件的名称(例如“重型”、“熟食”)。" + }, + "reference_cycle_id": { + "name": "参考周期 ID", + "description": "此配置文件所基于的可选循环 ID。" + } + } + }, + "delete_profile": { + "name": "删除个人资料", + "description": "删除配置文件并可选择使用它取消标记循环。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置。" + }, + "profile_name": { + "name": "个人资料名称", + "description": "要删除的配置文件。" + }, + "unlabel_cycles": { + "name": "取消标签循环", + "description": "使用此轮廓从循环中删除轮廓标签。" + } + } + }, + "auto_label_cycles": { + "name": "自动标记旧自行车", + "description": "使用配置文件匹配追溯标记未标记的循环。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置。" + }, + "confidence_threshold": { + "name": "置信阈值", + "description": "应用标签的最小匹配置信度 (0.50-0.95)。" + } + } + }, + "export_config": { + "name": "导出配置", + "description": "将此设备的配置文件、周期和设置导出到 JSON 文件(每个设备)。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置出口。" + }, + "path": { + "name": "小路", + "description": "可选的要写入的绝对文件路径(默认为 /config/ha_washdata_export_{entry}.json)。" + } + } + }, + "import_config": { + "name": "导入配置", + "description": "从 JSON 导出文件导入此设备的配置文件、周期和设置(设置可能会被覆盖)。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置导入。" + }, + "path": { + "name": "小路", + "description": "要导入的导出 JSON 文件的绝对路径。" + } + } + }, + "submit_cycle_feedback": { + "name": "提交周期反馈", + "description": "完成循环后确认或更正自动检测的程序。提供“entry_id”(高级)或“device_id”(推荐)。", + "fields": { + "device_id": { + "name": "设备", + "description": "WashData 设备(推荐而不是entry_id)。" + }, + "entry_id": { + "name": "条目 ID", + "description": "设备的配置条目 ID(替代 device_id)。" + }, + "cycle_id": { + "name": "周期ID", + "description": "反馈通知/日志中显示的周期 ID。" + }, + "user_confirmed": { + "name": "确认检测到的程序", + "description": "如果检测到的程序正确,则设置为 true。" + }, + "corrected_profile": { + "name": "修正后的轮廓", + "description": "如果未确认,请提供正确的配置文件/程序名称。" + }, + "corrected_duration": { + "name": "修正的持续时间(秒)", + "description": "可选的修正持续时间(以秒为单位)。" + }, + "notes": { + "name": "笔记", + "description": "关于此周期的可选注释。" + } + } + }, + "record_start": { + "name": "记录周期开始", + "description": "开始手动记录一个干净的循环(绕过所有匹配逻辑)。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机设备进行记录。" + } + } + }, + "record_stop": { + "name": "记录循环停止", + "description": "停止手动录制。", + "fields": { + "device_id": { + "name": "设备", + "description": "洗衣机装置停止录音。" + } + } + }, + "trim_cycle": { + "name": "修剪周期", + "description": "将过去周期的功率数据修剪到特定时间窗口。", + "fields": { + "device_id": { + "name": "设备", + "description": "WashData 设备。" + }, + "cycle_id": { + "name": "周期ID", + "description": "要修剪的周期的 ID。" + }, + "trim_start_s": { + "name": "修剪开始(秒)", + "description": "在几秒钟内保留此偏移量的数据。默认 0。" + }, + "trim_end_s": { + "name": "修剪结束(秒)", + "description": "在几秒钟内将数据保持到此偏移量。默认 = 完整持续时间。" + } + } + }, + "pause_cycle": { + "name": "暂停循环", + "description": "暂停 WashData 设备的活动周期。", + "fields": { + "device_id": { + "name": "设备", + "description": "WashData 设备暂停。" + } + } + }, + "resume_cycle": { + "name": "恢复周期", + "description": "恢复 WashData 设备的暂停周期。", + "fields": { + "device_id": { + "name": "设备", + "description": "WashData 设备恢复。" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "跑步" + }, + "match_ambiguity": { + "name": "匹配歧义" + } + }, + "sensor": { + "washer_state": { + "name": "状态", + "state": { + "off": "离开", + "idle": "闲置的", + "starting": "开始", + "running": "跑步", + "paused": "已暂停", + "user_paused": "用户已暂停", + "ending": "结束", + "finished": "完成的", + "anti_wrinkle": "抗皱", + "delay_wait": "等待开始", + "interrupted": "被打断", + "force_stopped": "强制停止", + "rinse": "冲洗", + "unknown": "未知", + "clean": "干净的" + } + }, + "washer_program": { + "name": "程序" + }, + "time_remaining": { + "name": "剩余时间" + }, + "total_duration": { + "name": "总持续时间" + }, + "cycle_progress": { + "name": "进步" + }, + "current_power": { + "name": "当前功率" + }, + "elapsed_time": { + "name": "经过时间" + }, + "debug_info": { + "name": "调试信息" + }, + "match_confidence": { + "name": "比赛信心" + }, + "top_candidates": { + "name": "最佳候选人", + "state": { + "none": "没有任何" + } + }, + "current_phase": { + "name": "当前阶段" + }, + "wash_phase": { + "name": "阶段" + }, + "profile_cycle_count": { + "name": "配置文件 {profile_name} 计数", + "unit_of_measurement": "周期" + }, + "suggestions": { + "name": "建议设置" + }, + "pump_runs_today": { + "name": "泵运行(过去 24 小时)" + }, + "cycle_count": { + "name": "循环计数" + } + }, + "select": { + "program_select": { + "name": "循环计划", + "state": { + "auto_detect": "自动检测" + } + } + }, + "button": { + "force_end_cycle": { + "name": "强制结束循环" + }, + "pause_cycle": { + "name": "暂停循环" + }, + "resume_cycle": { + "name": "恢复周期" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "需要有效的 device_id。" + }, + "cycle_id_required": { + "message": "需要有效的cycle_id。" + }, + "profile_name_required": { + "message": "需要有效的 profile_name。" + }, + "device_not_found": { + "message": "未找到设备。" + }, + "no_config_entry": { + "message": "找不到设备的配置条目。" + }, + "integration_not_loaded": { + "message": "未为此设备加载集成。" + }, + "cycle_not_found_or_no_power": { + "message": "未找到周期或没有功率数据。" + }, + "trim_failed_empty_window": { + "message": "修剪失败 - 未找到周期、无功率数据或生成的窗口为空。" + }, + "trim_invalid_range": { + "message": "trim_end_s必须大于trim_start_s。" + } + } +} diff --git a/custom_components/ha_washdata/translations/zh-Hant.json b/custom_components/ha_washdata/translations/zh-Hant.json new file mode 100644 index 0000000..1ed8e4d --- /dev/null +++ b/custom_components/ha_washdata/translations/zh-Hant.json @@ -0,0 +1,1341 @@ +{ + "config": { + "step": { + "user": { + "title": "WashData 設定", + "description": "配置您的洗衣機或其他設備。\n\n需要功率感測器。\n\n**接下來,系統會詢問您是否要建立您的第一個個人資料。**", + "data": { + "name": "設備名稱", + "device_type": "設備類型", + "power_sensor": "功率感測器", + "min_power": "最小功率閾值(W)" + }, + "data_description": { + "name": "該設備的友善名稱(例如“洗衣機”、“洗碗機”)。", + "device_type": "這是什麼類型的設備?幫助定制檢測和標記。", + "power_sensor": "報告智慧插頭即時功耗(以瓦為單位)的感測器實體。", + "min_power": "功率讀數高於此閾值(以瓦為單位)表示設備正在運作。大多數設備從 2W 開始。" + } + }, + "first_profile": { + "title": "建立第一個設定檔", + "description": "**週期**是您的設備的完整運作(例如,一堆衣物)。 **設定檔**將這些週期分組(例如「棉質」、「快洗」)。現在建立設定檔可以幫助整合立即估計持續時間。\n\n如果未自動偵測到此設定文件,您可以在裝置控制項中手動選擇它。\n\n**將「設定檔名稱」留空以跳過此步驟。 **", + "data": { + "profile_name": "設定檔名稱(可選)", + "manual_duration": "預計持續時間(分鐘)" + } + } + }, + "error": { + "cannot_connect": "連線失敗", + "invalid_auth": "驗證無效", + "unknown": "意外錯誤" + }, + "abort": { + "already_configured": "設備已配置" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "learning_feedbacks": "查看學習回饋", + "settings": "設定", + "notifications": "通知", + "manage_cycles": "管理週期", + "manage_profiles": "管理個人資料", + "manage_phase_catalog": "管理階段目錄", + "record_cycle": "記錄週期(手動)", + "diagnostics": "診斷與維護" + } + }, + "apply_suggestions": { + "title": "複製建議值", + "description": "這會將目前建議值複製到您已儲存的設定中。這是一次性操作,不會啟用自動覆蓋。\n\n建議複製的值:\n{suggested}", + "data": { + "confirm": "確認複製操作" + } + }, + "apply_suggestions_confirm": { + "title": "查看建議的更改", + "description": "WashData 發現 **{pending_count}** 要套用的建議值。\n\n變化:\n{changes}\n\n✅ 選取下面的方塊並點擊 **提交** 以立即套用並儲存這些變更。\n❌ 不勾選並點選**提交**取消並返回。", + "data": { + "confirm_apply_suggestions": "應用並保存這些更改" + } + }, + "settings": { + "title": "設定", + "description": "{deprecation_warning}調整檢測閾值、學習和高階行為。預設值適用於大多數設定。\n\n目前可用的建議設定:{suggestions_count}。\n如果此值大於 0,請開啟進階設定並使用「應用建議值」來檢視建議。", + "data": { + "apply_suggestions": "應用建議值", + "device_type": "設備類型", + "power_sensor": "功率感測器實體", + "min_power": "最小功率(瓦)", + "off_delay": "週期結束延遲(秒)", + "notify_actions": "通知操作", + "notify_people": "延遲送貨,直到這些人回家", + "notify_only_when_home": "延遲通知,直到選定的人在家", + "notify_fire_events": "觸發自動化事件", + "notify_start_services": "週期開始 - 通知目標", + "notify_finish_services": "週期完成 - 通知目標", + "notify_live_services": "即時進展 - 通知目標", + "notify_before_end_minutes": "預完成通知(結束前幾分鐘)", + "notify_title": "通知標題", + "notify_icon": "通知圖示", + "notify_start_message": "啟動訊息格式", + "notify_finish_message": "完成訊息格式", + "notify_pre_complete_message": "預完成訊息格式", + "show_advanced": "編輯進階設定(下一步)" + }, + "data_description": { + "apply_suggestions": "勾選此方塊可將學習演算法建議的值複製到表單中。保存前檢查。\n\n建議(來自學習):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}", + "device_type": "選擇設備類型(洗衣機、烘乾機、洗碗機、咖啡機、電動車)。此標籤隨每個週期一起存儲,以便更好地組織。", + "power_sensor": "感測器實體報告即時功率(以瓦為單位)。您可以隨時更改此設置,而不會丟失歷史數據 - 如果您更換智慧插頭,這很有用。", + "min_power": "功率讀數高於此閾值(以瓦為單位)表示設備正在運作。大多數設備從 2W 開始。", + "off_delay": "平滑功率必須保持低於停止閾值的秒數才能標記完成。預設值:120 秒。", + "notify_actions": "用於執行通知的選用 Home Assistant 操作序列(腳本、通知、TTS 等)。如果設置,操作將在回退通知服務之前運行。", + "notify_people": "用於存在門控的人員實體。啟用後,通知發送會延遲,直到至少有一名選定的人在家。", + "notify_only_when_home": "如果啟用,則延遲通知,直到至少一個選定的人在家。", + "notify_fire_events": "如果啟用,則觸發循環開始和結束的 Home Assistant 事件(ha_washdata_cycle_started 和 ha_washdata_cycle_ended)以驅動自動化。", + "notify_start_services": "一個或多個通知服務在周期開始時發出警報。留空以跳過開始通知。", + "notify_finish_services": "一個或多個通知服務在周期完成或接近完成時發出警報。留空以跳過完成通知。", + "notify_live_services": "一項或多項通知服務在週期運行時接收即時進度更新(僅限行動配套應用程式)。留空以跳過即時更新。", + "notify_before_end_minutes": "觸發通知的預計週期結束前的分鐘數。設定為 0 以停用。預設值:0。", + "notify_title": "通知的自訂標題。支援 {device} 佔位符。", + "notify_icon": "用於通知的圖示(例如 mdi:洗衣機)。留空以不傳送圖示。", + "notify_start_message": "循環開始時發送的訊息。支援 {device} 佔位符。", + "notify_finish_message": "循環結束時發送的訊息。支援 {device}、{duration}、{program}, {energy_kwh}, {cost} 佔位符。", + "notify_pre_complete_message": "循環運行時,循環即時進度的文字會更新。支援 {device}、{minutes}、{program} 佔位符。" + } + }, + "notifications": { + "title": "通知", + "description": "配置通知頻道、模板和可選的即時行動進度更新。", + "data": { + "notify_actions": "通知操作", + "notify_people": "延遲送貨,直到這些人回家", + "notify_only_when_home": "延遲通知,直到選定的人在家", + "notify_fire_events": "觸發自動化事件", + "notify_start_services": "週期開始 - 通知目標", + "notify_finish_services": "週期完成 - 通知目標", + "notify_live_services": "即時進展 - 通知目標", + "notify_before_end_minutes": "預完成通知(結束前幾分鐘)", + "notify_live_interval_seconds": "即時更新間隔(秒)", + "notify_live_overrun_percent": "即時更新超限容限 (%)", + "notify_live_chronometer": "天文台倒數計時器", + "notify_title": "通知標題", + "notify_icon": "通知圖示", + "notify_start_message": "啟動訊息格式", + "notify_finish_message": "完成訊息格式", + "notify_pre_complete_message": "預完成訊息格式", + "energy_price_entity": "能源價格 - 實體(可選)", + "energy_price_static": "能源價格 - 靜態值(可選)", + "go_back": "不儲存並返回", + "notify_reminder_message": "提醒訊息格式", + "notify_timeout_seconds": "自動關閉後(秒)", + "notify_channel": "通知頻道(狀態/即時/提醒)", + "notify_finish_channel": "完成通知頻道" + }, + "data_description": { + "go_back": "勾選此項並點擊提交,以放棄上述任何變更並返回主選單。", + "notify_actions": "用於執行通知的選用 Home Assistant 操作序列(腳本、通知、TTS 等)。如果設置,操作將在回退通知服務之前運行。", + "notify_people": "用於存在門控的人員實體。啟用後,通知發送會延遲,直到至少有一名選定的人在家。", + "notify_only_when_home": "如果啟用,則延遲通知,直到至少一個選定的人在家。", + "notify_fire_events": "如果啟用,則觸發循環開始和結束的 Home Assistant 事件(ha_washdata_cycle_started 和 ha_washdata_cycle_ended)以驅動自動化。", + "notify_start_services": "一個或多個通知服務在周期開始時發出警報(例如notify.mobile_app_my_phone)。留空以跳過開始通知。", + "notify_finish_services": "一個或多個通知服務在周期完成或接近完成時發出警報。留空以跳過完成通知。", + "notify_live_services": "一項或多項通知服務在週期運行時接收即時進度更新(僅限行動配套應用程式)。留空以跳過即時更新。", + "notify_before_end_minutes": "觸發通知的預計週期結束前的分鐘數。設定為 0 以停用。預設值:0。", + "notify_live_interval_seconds": "運行時發送進度更新的頻率。值越低,回應速度越快,但會消耗更多通知。強制執行至少 30 秒 - 低於 30 秒的值將自動四捨五入為 30 秒。", + "notify_live_overrun_percent": "安全裕度高於長/超出週期的估計更新計數(例如 20% 允許 1.2 倍估計更新)。", + "notify_live_chronometer": "啟用後,每次即時更新都會包含預計完成時間的計時器倒數。更新之間,通知計時器會在裝置上自動計時(僅限 Android 配套應用程式)。", + "notify_title": "通知的自訂標題。支援 {device} 佔位符。", + "notify_icon": "用於通知的圖示(例如 mdi:洗衣機)。留空以不傳送圖示。", + "notify_start_message": "循環開始時發送的訊息。支援 {device} 佔位符。", + "notify_finish_message": "循環結束時發送的訊息。支援 {device}、{duration}、{program}, {energy_kwh}, {cost} 佔位符。", + "energy_price_entity": "選擇保存目前電價的數位實體(例如sensor.electricity_price、input_number.energy_rate)。設定後,啟用 {cost} 佔位符。如果兩者都配置了,則優先於靜態值。", + "energy_price_static": "每度電固定電價。設定後,啟用 {cost} 佔位符。如果上面配置了實體,則忽略。在訊息範本中加入您的貨幣符號,例如{cost} 歐元。", + "notify_reminder_message": "一次性提醒文字在預計結束前發送配置的分鐘數。與上面的定期即時更新不同。支援 {device}、{minutes}、{program} 佔位符。", + "notify_timeout_seconds": "在這麼多秒後自動關閉通知(作為配套應用程式「超時」轉發)。設定為 0 以保留它們直到被解僱。預設值:0。", + "notify_channel": "Android 配套應用程式通知管道,用於顯示狀態、即時進度和提醒通知。頻道的聲音和重要性是在第一次使用頻道名稱時在配套應用程式中配置的 - WashData 僅設定頻道名稱。對於應用程式預設值,將其留空。", + "notify_finish_channel": "單獨的 Android 通道用於已完成的警報(也由提醒和洗衣等待小車使用),以便它可以有自己的聲音。留空以重複使用上面的通道。", + "notify_pre_complete_message": "循環運行時,循環即時進度的文字會更新。支援 {device}、{minutes}、{program} 佔位符。" + } + }, + "advanced_settings": { + "title": "進階設定", + "description": "調整檢測閾值、學習和進階行為。預設值適用於大多數設定。", + "sections": { + "suggestions_section": { + "name": "建議設定", + "description": "{suggestions_count} 項已學習的建議可用。勾選套用建議值,以便在儲存前檢視建議的變更。", + "data": { + "apply_suggestions": "應用建議值" + }, + "data_description": { + "apply_suggestions": "勾選此方塊可開啟學習演算法建議值的檢查步驟。沒有任何內容會自動儲存。\n\n建議(來自學習):\n- min_power: {suggested_min_power} W\n- off_delay: {suggested_off_delay} s\n- watchdog_interval: {suggested_watchdog_interval} s\n- no_update_active_timeout: {suggested_no_update_active_timeout} s\n- profile_match_interval: {suggested_profile_match_interval} s\n- auto_label_confidence: {suggested_auto_label_confidence}\n- duration_tolerance: {suggested_duration_tolerance}\n- profile_duration_tolerance: {suggested_profile_duration_tolerance}\n- profile_match_min_duration_ratio: {suggested_profile_match_min_duration_ratio}\n- profile_match_max_duration_ratio: {suggested_profile_match_max_duration_ratio}\n- start_threshold_w: {suggested_start_threshold_w} W\n- stop_threshold_w: {suggested_stop_threshold_w} W\n- end_energy_threshold: {suggested_end_energy_threshold} Wh\n- running_dead_zone: {suggested_running_dead_zone} s\n{suggested_reason}" + } + }, + "detection_section": { + "name": "檢測與功率閾值", + "description": "裝置在何時被視為開啟或關閉、能量門檻以及狀態轉換的時序。", + "data": { + "start_duration_threshold": "開始去抖持續時間(秒)", + "start_energy_threshold": "啟動能量門(Wh)", + "completion_min_seconds": "完成最短運轉時間(秒)", + "start_threshold_w": "啟動閾值(W)", + "stop_threshold_w": "停止閾值(W)", + "end_energy_threshold": "最終能量門(Wh)", + "running_dead_zone": "運行死區(秒)", + "end_repeat_count": "結束重複計數", + "min_off_gap": "週期之間的最小間隙(秒)", + "sampling_interval": "採樣間隔(秒)" + }, + "data_description": { + "start_duration_threshold": "忽略短於此持續時間(秒)的短暫功率峰值。在實際週期開始之前過濾掉啟動尖峰(例如 60W 持續 2 秒)。預設值:5 秒。", + "start_energy_threshold": "循環必須在啟動階段累積至少這麼多的能量 (Wh) 才能得到確認。預設值:0.005 瓦時。", + "completion_min_seconds": "比這短的周期即使自然結束也會被標記為「中斷」。預設值:600 秒。", + "start_threshold_w": "功率必須上升到該閾值以上才能開始一個週期。設定高於待機功率。預設值:min_power + 1 W。", + "stop_threshold_w": "功率必須低於該閾值才能結束一個週期。設定略高於零以避免噪音觸發錯誤結束。預設值:0.6 * min_power。", + "end_energy_threshold": "只有當最後一個關閉延遲視窗中的能量低於該值 (Wh) 時,循環才會結束。如果功率波動接近零,可防止錯誤結束。預設值:0.05 瓦時。", + "running_dead_zone": "週期開始後,忽略如此多秒的功率驟降,以防止初始旋轉階段期間的錯誤結束偵測。設定為 0 以停用。預設值:0 秒。", + "end_repeat_count": "在結束週期之前必須連續滿足結束條件(功率低於 off_delay 閾值)的次數。較高的值可防止暫停期間發生錯誤結束。預設值:1。", + "min_off_gap": "如果一個週期在前一個週期結束後的這麼多秒內開始,它們將合併為一個週期。", + "sampling_interval": "最小採樣間隔(以秒為單位)。超過此速率的更新將被忽略。預設值:30 秒(洗衣機、洗衣乾衣機和洗碗機為 2 秒)。" + } + }, + "matching_section": { + "name": "設定檔比對與學習", + "description": "WashData 會以多積極的方式將執行中的週期與已學習的設定檔進行比對,以及何時要求回饋。", + "data": { + "profile_match_min_duration_ratio": "設定檔匹配最短持續時間比率(0.1-1.0)", + "profile_match_interval": "設定檔匹配間隔(秒)", + "profile_match_threshold": "設定檔匹配閾值", + "profile_unmatch_threshold": "設定檔不匹配閾值", + "auto_label_confidence": "自動標記置信度 (0-1)", + "learning_confidence": "回饋請求置信度 (0-1)", + "suppress_feedback_notifications": "抑制回饋通知", + "duration_tolerance": "持續時間容差(0-0.5)", + "smoothing_window": "平滑視窗(樣本)", + "profile_duration_tolerance": "設定檔匹配持續時間容差 (0-0.5)" + }, + "data_description": { + "profile_match_min_duration_ratio": "設定檔匹配的最小持續時間比率。運行週期必須至少是設定檔持續時間的這一部分才能匹配。較低 = 較早配對。預設值:0.50 (50%)。", + "profile_match_interval": "在一個週期內嘗試設定檔匹配的頻率(以秒為單位)。較低的值可提供更快的偵測速度,但會使用更多的 CPU。預設值:300 秒(5 分鐘)。", + "profile_match_threshold": "將設定檔視為匹配所需的最低 DTW 相似度分數 (0.0–1.0)。越高=匹配越嚴格,誤報越少。預設值:0.4。", + "profile_unmatch_threshold": "DTW 相似性得分 (0.0–1.0),低於該得分的先前匹配的配置將被拒絕。應低於匹配閾值以防止閃爍。預設值:0.35。", + "auto_label_confidence": "完成時自動標記等於或高於此置信度的迴圈 (0.0–1.0)。", + "learning_confidence": "透過事件請求使用者驗證的最低置信度 (0.0–1.0)。", + "suppress_feedback_notifications": "啟用後,WashData 仍將在內部追蹤和請求回饋,但不會顯示要求您確認週期的持續通知。當循環總是被正確檢測到並且您發現提示分散注意力時很有用。", + "duration_tolerance": "運行期間剩餘時間估計的容差(0.0–0.5 對應於 0–50%)。", + "smoothing_window": "用於移動平均平滑的最近功率讀數的數量。", + "profile_duration_tolerance": "設定檔匹配所使用的總持續時間變異數的容差 (0.0–0.5)。預設行為:±25%。" + } + }, + "timing_section": { + "name": "時序、維護與偵錯", + "description": "看門狗、進度重設、自動維護以及偵錯實體的公開。", + "data": { + "watchdog_interval": "看門狗間隔(秒)", + "no_update_active_timeout": "無更新超時(秒)", + "progress_reset_delay": "進度重置延遲(秒)", + "auto_maintenance": "啟用自動維護", + "expose_debug_entities": "公開除錯實體", + "save_debug_traces": "保存調試追蹤" + }, + "data_description": { + "watchdog_interval": "運轉時看門狗檢查之間的秒數。預設值:5 秒。警告:確保該值高於感測器的更新間隔,以避免錯誤停止(例如,如果感測器每 60 秒更新一次,則使用 65 秒以上)。", + "no_update_active_timeout": "對於更改發布感應器:只有在這麼多秒內沒有更新到達時才強制結束 ACTIVE 週期。低功耗完成仍然使用關閉延遲。", + "progress_reset_delay": "一個週期完成 (100%) 後,等待這麼多秒的空閒時間,然後再將進度重設為 0%。預設值:300 秒。", + "auto_maintenance": "啟用自動維護(修復樣本、合併片段)。", + "expose_debug_entities": "顯示高級感測器(置信度、相位、模糊度)以進行調試。", + "save_debug_traces": "在歷史記錄中儲存詳細的排名和電量追蹤資料(增加儲存使用量)。" + } + }, + "anti_wrinkle_section": { + "name": "防皺保護(烘乾機)", + "description": "忽略週期結束後的滾筒轉動,以防止幽靈週期。僅適用於烘乾機 / 洗脫烘一體機。", + "data": { + "anti_wrinkle_enabled": "抗皺護罩(僅限烘乾機)", + "anti_wrinkle_max_power": "抗皺最大功率(W)", + "anti_wrinkle_max_duration": "抗皺最大持續時間(秒)", + "anti_wrinkle_exit_power": "抗皺退出功率(W)" + }, + "data_description": { + "anti_wrinkle_enabled": "循環結束後忽略短暫的低功率滾筒旋轉,以防止幽靈循環(僅限烘乾機/洗衣烘乾機)。", + "anti_wrinkle_max_power": "等於或低於該功率的爆發被視為抗皺旋轉而不是新的循環。僅在啟用抗皺護罩時適用。", + "anti_wrinkle_max_duration": "視為抗皺旋轉的最大爆破長度。僅在啟用抗皺護罩時適用。", + "anti_wrinkle_exit_power": "如果功率低於此水平,請立即退出抗皺(真正關閉)。僅在啟用抗皺護罩時適用。" + } + }, + "delay_start_section": { + "name": "延遲啟動偵測", + "description": "在週期真正開始之前,將持續的低功耗待機視為「等待啟動」。", + "data": { + "delay_start_detect_enabled": "啟用延遲啟動偵測", + "delay_confirm_seconds": "延遲啟動:待機確認時間(秒)", + "delay_timeout_hours": "延遲啟動:最長等待時間 (h)" + }, + "data_description": { + "delay_start_detect_enabled": "啟用後,會偵測到短暫的低功耗峰值,然後持續待機功耗,作為延遲啟動。延遲期間狀態感測器顯示“等待啟動”。在周期實際開始之前,不會追蹤計劃時間或進度。要求將 start_threshold_w 設定為高於汲極尖峰功率。", + "delay_confirm_seconds": "在 WashData 進入「等待啟動」之前,功率必須在停止閾值 (W) 和啟動閾值 (W) 之間保持這麼多秒。可過濾短暫的選單導覽峰值。預設值:60 秒。", + "delay_timeout_hours": "安全逾時:如果經過這麼多小時機器仍處於「等待啟動」狀態,WashData 將重設為「關閉」。預設值:8 小時。" + } + }, + "external_triggers_section": { + "name": "外部觸發、門控與暫停", + "description": "可選的外部結束觸發感測器、用於暫停/潔淨偵測的門感測器,以及暫停斷電開關。", + "data": { + "external_end_trigger_enabled": "啟用外部循環結束觸發器", + "external_end_trigger": "外部循環結束感測器", + "external_end_trigger_inverted": "反轉觸發邏輯(觸發關閉)", + "door_sensor_entity": "門磁", + "pause_cuts_power": "暫停時切斷電源", + "switch_entity": "開關實體(用於暫停斷電)", + "notify_unload_delay_minutes": "洗衣等待通知延遲(分鐘)" + }, + "data_description": { + "external_end_trigger_enabled": "啟用對外部二進位感測器的監控以觸發循環完成。", + "external_end_trigger": "選擇二進制感測器實體。當此感測器觸發時,當前週期將以「已完成」狀態結束。", + "external_end_trigger_inverted": "預設情況下,當感測器開啟時觸發器會觸發。選取此方塊以在感測器關閉時觸發。", + "door_sensor_entity": "用於機器門的可選二進位感測器(開=打開,關=關閉)。當門在活動週期期間打開時,WashData 將確認該週期是有意暫停的。循環結束後,如果門仍然關閉,則狀態變更為“清潔”,直到門打開。注意:關上門不會自動恢復暫停的循環 - 必須透過「恢復循環」按鈕或服務手動觸發恢復。", + "pause_cuts_power": "透過「暫停循環」按鈕或服務暫停時,也要關閉已配置的開關實體。僅當為該設備配置了交換實體時才生效。", + "switch_entity": "暫停或恢復時要切換的開關實體。啟用“暫停時切斷電源”時需要。", + "notify_unload_delay_minutes": "循環結束且門在這麼多分鐘內未打開後,透過完成通知通道發送通知(需要門感應器)。設定為 0 以停用。預設值:60 分鐘" + } + }, + "pump_section": { + "name": "幫浦監控", + "description": "僅適用於幫浦裝置類型。如果幫浦週期運行超過此時長,則觸發事件。", + "data": { + "pump_stuck_duration": "泵浦卡住警報閾值(僅限泵浦)" + }, + "data_description": { + "pump_stuck_duration": "如果泵浦循環在這麼多秒後仍在運行,則會觸發一次 ha_washdata_pump_stuck 事件。用它來檢測堵塞的馬達(典型的污水泵運行時間低於 60 秒)。預設值:1800 秒(30 分鐘)。僅限泵浦設備類型。" + } + }, + "device_link_section": { + "name": "裝置連結", + "description": "(可選)將此 WashData 裝置連結到現有的 Home Assistant 裝置(例如智慧插頭或裝置本身)。然後,WashData 裝置將顯示為「透過該裝置連線」。留空可將 WashData 保留為獨立裝置。", + "data": { + "linked_device": "連結設備" + }, + "data_description": { + "linked_device": "選擇要連接 WashData 的裝置。這會在裝置登錄中新增「連線方式」參考; WashData保留自己的裝置卡和實體。清除選擇以刪除連結。" + } + } + } + }, + "diagnostics": { + "title": "診斷與維護", + "description": "運行維護操作,例如合併碎片週期或遷移儲存的資料。\n\n**儲存使用情況**\n\n- 檔案大小:{file_size_kb} KB\n- 週期:{cycle_count}\n- 個人資料:{profile_count}\n- 調試追蹤:{debug_count}", + "menu_options": { + "reprocess_history": "維護:重新處理和優化數據", + "clear_debug_data": "清除調試資料(釋放空間)", + "wipe_history": "擦除該裝置的所有資料(不可逆)", + "export_import": "使用設定導出/匯入 JSON(複製/貼上)", + "menu_back": "← 返回" + } + }, + "clear_debug_data": { + "title": "清除調試數據", + "description": "您確定要刪除所有儲存的調試追蹤嗎?這將釋放空間,但會刪除過去週期中的詳細排名資訊。" + }, + "manage_cycles": { + "title": "管理週期", + "description": "最近的周期:\n{recent_cycles}", + "menu_options": { + "auto_label_cycles": "自動標記舊自行車", + "select_cycle_to_label": "標籤特定週期", + "select_cycle_to_delete": "刪除循環", + "interactive_editor": "合併/拆分互動式編輯器", + "trim_cycle_select": "修剪週期數據", + "menu_back": "← 返回" + } + }, + "manage_cycles_empty": { + "title": "管理週期", + "description": "尚未記錄任何週期。", + "menu_options": { + "auto_label_cycles": "自動標記舊自行車", + "menu_back": "← 返回" + } + }, + "manage_profiles": { + "title": "管理個人資料", + "description": "簡介摘要:\n{profile_summary}", + "menu_options": { + "create_profile": "建立新的個人資料", + "edit_profile": "編輯/重新命名設定檔", + "delete_profile_select": "刪除個人資料", + "profile_stats": "個人資料統計", + "cleanup_profile": "清理歷史記錄 - 圖表和刪除", + "assign_profile_phases_select": "分配相位範圍", + "menu_back": "← 返回" + } + }, + "manage_profiles_empty": { + "title": "管理個人資料", + "description": "尚未建立設定檔。", + "menu_options": { + "create_profile": "建立新的個人資料", + "menu_back": "← 返回" + } + }, + "manage_phase_catalog": { + "title": "管理階段目錄", + "description": "階段目錄(該設備的預設值+自訂階段):\n{phase_summary}", + "menu_options": { + "phase_catalog_create": "建立新階段", + "phase_catalog_edit_select": "編輯階段", + "phase_catalog_delete": "刪除階段", + "menu_back": "← 返回" + } + }, + "phase_catalog_create": { + "title": "建立自訂階段", + "description": "建立自訂階段名稱和描述,然後選擇其適用的裝置類型。", + "data": { + "target_device_type": "目標設備類型", + "phase_name": "階段名稱", + "phase_description": "階段描述" + } + }, + "phase_catalog_edit_select": { + "title": "編輯自訂階段", + "description": "選擇要編輯的自訂階段。", + "data": { + "phase_name": "客製化階段" + } + }, + "phase_catalog_edit": { + "title": "編輯自訂階段", + "description": "編輯階段:{phase_name}", + "data": { + "phase_name": "階段名稱", + "phase_description": "階段描述" + } + }, + "phase_catalog_delete": { + "title": "刪除自訂階段", + "description": "選擇要刪除的自訂階段。使用此階段的任何指定範圍都將被刪除。", + "data": { + "phase_name": "客製化階段" + } + }, + "assign_profile_phases": { + "title": "分配相位範圍", + "description": "設定檔的階段預覽:**{profile_name}**\n\n{timeline_svg}\n\n**目前範圍:**\n{current_ranges}\n\n使用以下操作來新增、編輯、刪除或儲存範圍。", + "menu_options": { + "assign_profile_phases_add": "增加相位範圍", + "assign_profile_phases_edit_select": "編輯相位範圍", + "assign_profile_phases_delete": "刪除相位範圍", + "phase_ranges_clear": "清除所有範圍", + "assign_profile_phases_auto_detect": "自動檢測相位", + "phase_ranges_save": "儲存並返回", + "menu_back": "← 返回" + } + }, + "assign_profile_phases_add": { + "title": "增加相位範圍", + "description": "在目前草稿中新增一個階段範圍。", + "data": { + "phase_name": "階段", + "start_min": "開始時間", + "end_min": "結束分鐘" + } + }, + "assign_profile_phases_edit_select": { + "title": "編輯相位範圍", + "description": "選擇要編輯的範圍。", + "data": { + "range_index": "相位範圍" + } + }, + "assign_profile_phases_edit": { + "title": "編輯相位範圍", + "description": "更新選定的相位範圍。", + "data": { + "phase_name": "階段", + "start_min": "開始時間", + "end_min": "結束分鐘" + } + }, + "assign_profile_phases_delete": { + "title": "刪除相位範圍", + "description": "選擇要從草稿中刪除的範圍。", + "data": { + "range_index": "相位範圍" + } + }, + "assign_profile_phases_auto_detect": { + "title": "自動檢測階段", + "description": "個人資料:***{profile_name}**\n\n{timeline_svg}\n\n**自動偵測到 {detected_count} 個階段。 ** 選擇下面的操作。", + "data": { + "action": "行動" + } + }, + "assign_profile_phases_auto_detect_name": { + "title": "命名檢測到的階段", + "description": "個人資料:***{profile_name}**\n\n**偵測到{detected_count}相:**\n{ranges_summary}\n\n為每個階段指定一個名稱。" + }, + "assign_profile_phases_select": { + "title": "分配相位範圍", + "description": "選擇一個設定檔來配置相位範圍。", + "data": { + "profile": "輪廓" + } + }, + "profile_stats": { + "title": "個人資料統計", + "description": "{stats_table}" + }, + "create_profile": { + "title": "建立新的個人資料", + "description": "手動或從過去的周期建立新的設定檔。\n\n設定檔名稱範例:“Delicates”、“Heavy Duty”、“Quick Wash”", + "data": { + "profile_name": "個人資料名稱", + "reference_cycle": "參考週期(可選)", + "manual_duration": "手動持續時間(分鐘)" + } + }, + "edit_profile": { + "title": "編輯個人資料", + "description": "選擇要重新命名的設定文件", + "data": { + "profile": "輪廓" + } + }, + "rename_profile": { + "title": "編輯個人資料詳細信息", + "description": "目前個人資料:{current_name}", + "data": { + "new_name": "個人資料名稱", + "manual_duration": "手動持續時間(分鐘)" + } + }, + "delete_profile_select": { + "title": "刪除個人資料", + "description": "選擇要刪除的設定文件", + "data": { + "profile": "輪廓" + } + }, + "delete_profile_confirm": { + "title": "確認刪除個人資料", + "description": "⚠️ 這將永久刪除個人資料:{profile_name}", + "data": { + "unlabel_cycles": "使用此設定檔從循環中刪除標籤" + } + }, + "auto_label_cycles": { + "title": "自動標記舊自行車", + "description": "共找到 {total_count} 個週期。個人資料:{profiles}", + "data": { + "confidence_threshold": "最低信賴度 (0.50-0.95)" + } + }, + "select_cycle_to_label": { + "title": "選擇循環標籤", + "description": "✓ = 已完成,⚠ = 已恢復,✗ = 已中斷", + "data": { + "cycle_id": "循環" + } + }, + "select_cycle_to_delete": { + "title": "刪除循環", + "description": "⚠️這將永久刪除所選週期", + "data": { + "cycle_id": "循環刪除" + } + }, + "label_cycle": { + "title": "標籤週期", + "description": "{cycle_info}", + "data": { + "profile_name": "輪廓", + "new_profile_name": "新設定檔名稱(如果建立)" + } + }, + "post_process": { + "title": "流程歷史記錄(合併/拆分)", + "description": "透過合併碎片運行並在時間視窗內分割錯誤合併的周期來清理週期歷史記錄。輸入要回顧的小時數(全部使用 999999)。", + "data": { + "time_range": "回顧視窗(小時)", + "gap_seconds": "合併/拆分間隙(秒)" + }, + "data_description": { + "time_range": "掃描要合併的碎片週期的小時數。使用999999處理所有歷史資料。", + "gap_seconds": "決定拆分還是合併的閾值。比這更短的間隙將被合併。比這更長的間隙被分開。降低此值可強制對錯誤合併的循環進行拆分。" + } + }, + "cleanup_profile": { + "title": "清理歷史記錄 - 選擇設定文件", + "description": "選擇要視覺化的設定檔。這將產生一個圖表,顯示該設定檔的所有過去週期,以幫助識別異常值。", + "data": { + "profile": "輪廓" + } + }, + "cleanup_select": { + "title": "清理歷史記錄 - 圖表和刪除", + "description": "![圖表]({graph_url})\n\n**可視化{profile_name}**\n\n此圖將各個週期顯示為彩色線。識別異常值(遠離該組的線)並在下面的列表中匹配它們的顏色以將其刪除。\n\n**選擇要永久刪除的週期:**", + "data": { + "cycles_to_delete": "循環刪除(檢查匹配顏色)" + } + }, + "interactive_editor": { + "title": "合併/拆分互動式編輯器", + "description": "選擇要對您的週期歷史記錄執行的手動操作。", + "menu_options": { + "editor_split": "分割一個週期(查找間隙)", + "editor_merge": "合併循環(連結片段)", + "editor_delete": "刪除週期", + "menu_back": "← 返回" + } + }, + "editor_select": { + "title": "選擇週期", + "description": "選擇要處理的周期。\n\n{info_text}", + "data": { + "selected_cycles": "週期" + } + }, + "editor_configure": { + "title": "配置和應用", + "description": "{preview_md}", + "data": { + "confirm_action": "行動", + "merged_profile": "產生的設定檔", + "new_profile_name": "新設定檔名稱", + "confirm_commit": "是的,我確認此操作", + "segment_0_profile": "第 1 段簡介", + "segment_1_profile": "2 段簡介", + "segment_2_profile": "第 3 段簡介", + "segment_3_profile": "第 4 段簡介", + "segment_4_profile": "5 段簡介", + "segment_5_profile": "6 段簡介", + "segment_6_profile": "7 段簡介", + "segment_7_profile": "8 段簡介", + "segment_8_profile": "9 段簡介", + "segment_9_profile": "10 段簡介" + } + }, + "editor_split_params": { + "title": "分離配置", + "description": "調整分割此循環的靈敏度。任何超過此長度的空閒間隙都會導致分裂。", + "data": { + "split_mode": "分離方式" + } + }, + "editor_split_auto_params": { + "title": "自動分段設定", + "description": "調整分割此週期的靈敏度。任何長於此值的閒置間隔都會觸發分割。", + "data": { + "min_gap_seconds": "分段間隔門檻(秒)" + } + }, + "editor_split_manual_params": { + "title": "手動分段時間戳記", + "description": "{preview_md}週期視窗:**{cycle_start_wallclock} → {cycle_end_wallclock}**,日期為 {cycle_date}。\n\n輸入一個或多個分段時間戳記(`HH:MM` 或 `HH:MM:SS`),每行一個。週期會在每個時間戳記處被切開 — N 個時間戳記會產生 N+1 個片段。", + "data": { + "split_timestamps": "分割時間戳記" + } + }, + "reprocess_history": { + "title": "維護:重新處理和優化數據", + "description": "對歷史資料進行深入清理:\n1. 調整零功率讀數(靜音)。\n2. 修復循環時間/持續時間。\n3. 重新計算所有統計模型(包絡線)。\n\n**執行此命令可以修復資料問題或應用新演算法。 **" + }, + "wipe_history": { + "title": "擦除歷史記錄(僅測試)", + "description": "⚠️ 這將永久刪除該裝置的所有儲存週期和設定檔。這無法撤銷!" + }, + "export_import": { + "title": "導出/導入 JSON", + "description": "複製/貼上該設備的周期、設定檔和設定。下面匯出或貼上 JSON 進行匯入。", + "data": { + "mode": "行動", + "json_payload": "完整設定 JSON" + }, + "data_description": { + "mode": "選擇「匯出」以複製目前資料和設置,或選擇「匯入」以貼上從另一個 WashData 裝置匯出的資料。", + "json_payload": "對於匯出:複製此 JSON(包括週期、設定檔和所有微調設定)。對於導入:貼上從 WashData 匯出的 JSON。" + } + }, + "record_cycle": { + "title": "記錄週期", + "description": "手動記錄一個週期以建立無幹擾的乾淨輪廓。\n\n狀態:***{status}**\n持續時間:{duration}秒\n樣本:{samples}", + "menu_options": { + "record_refresh": "刷新狀態", + "record_stop": "停止錄影(儲存並處理)", + "record_start": "開始新錄音", + "record_process": "處理最後的錄音(修剪和保存)", + "record_discard": "放棄最後的錄音", + "menu_back": "← 返回" + } + }, + "record_process": { + "title": "流程記錄", + "description": "![圖表]({graph_url})\n\n**圖表顯示原始記錄。 **藍色 = 保留,紅色 = 建議的修剪。\n*圖表是靜態的,不會隨著表單變更而更新。 *\n\n錄音停止了。修剪與偵測到的取樣率 (~{sampling_rate}s) 對齊。\n\n原始持續時間:{duration} 秒\n樣本:{samples}", + "data": { + "head_trim": "修剪開始(秒)", + "tail_trim": "修剪結束(秒)", + "save_mode": "儲存目的地", + "profile_name": "個人資料名稱" + } + }, + "learning_feedbacks": { + "title": "學習回饋", + "description": "待處理回饋:{count}\n\n{pending_table}\n\n選擇要處理的週期審核請求。", + "menu_options": { + "learning_feedbacks_pick": "檢視待處理回饋", + "learning_feedbacks_dismiss_all": "忽略所有待處理回饋", + "menu_back": "← 返回" + } + }, + "learning_feedbacks_pick": { + "title": "選擇要查看的回饋", + "description": "選擇要開啟的週期檢查請求。", + "data": { + "selected_feedback": "選擇要查看的週期" + } + }, + "learning_feedbacks_empty": { + "title": "學習回饋", + "description": "未找到待處理的回饋請求。", + "menu_options": { + "menu_back": "← 返回" + } + }, + "learning_feedbacks_dismiss_all": { + "title": "忽略所有待處理回饋", + "description": "您即將忽略 **{count}** 個待處理回饋請求。被忽略的週期會保留在您的歷史記錄中,但不會提供新的學習訊號。此操作無法復原。\n\n✅ 選取下面的方塊並點擊 **提交** 以忽略全部。\n❌ 不勾選並點選 **提交** 以取消並返回。", + "data": { + "confirm_dismiss_all": "確認:忽略所有待處理的回饋請求" + } + }, + "resolve_feedback": { + "title": "解決回饋", + "description": "{comparison_img}{candidates_table}\n**偵測到的設定檔**:{detected_profile} ({confidence_pct}%)\n**預計持續時間**:{est_duration_min} 分鐘\n**實際持續時間**:{act_duration_min} 分鐘\n\n__選擇以下操作:__", + "data": { + "action": "你想做什麼?", + "corrected_profile": "正確的程序(如果更正)", + "corrected_duration": "正確持續時間(以秒為單位)(如果進行更正)" + } + }, + "trim_cycle_select": { + "title": "修剪週期 - 選擇週期", + "description": "選擇要修剪的周期。 ✓ = 已完成,⚠ = 已恢復,✗ = 已中斷", + "data": { + "cycle_id": "循環" + } + }, + "trim_cycle": { + "title": "修剪週期", + "description": "目前視窗:{trim_summary}\n\n{trim_svg}", + "data": { + "action": "行動" + } + }, + "trim_cycle_start": { + "title": "設定修剪開始", + "description": "循環視窗:{cycle_start_wallclock} → {cycle_end_wallclock}\n\n目前開始時間:**{current_wallclock}**(距離週期開始時間+{current_offset_min} 分鐘)\n\n使用下面的時鐘選擇器選擇新的開始時間。", + "data": { + "trim_start_time": "新的開始時間", + "trim_start_min": "新的開始(週期開始後的分鐘數)" + } + }, + "trim_cycle_end": { + "title": "設定修剪結束", + "description": "循環視窗:{cycle_start_wallclock} → {cycle_end_wallclock}\n\n目前結束:**{current_wallclock}**(距離週期開始+{current_offset_min} 分鐘)\n\n使用下面的時鐘選擇器選擇新的結束時間。", + "data": { + "trim_end_time": "新的結束時間", + "trim_end_min": "新的結束(距離週期開始的分鐘數)" + } + } + }, + "error": { + "import_failed": "導入失敗。請貼上有效的 WashData 匯出 JSON。", + "select_exactly_one": "此操作請僅選擇 1 個週期。", + "select_at_least_one": "此操作請至少選擇 1 個週期。", + "select_at_least_two": "此操作請至少選擇 2 個週期。", + "profile_exists": "同名的設定檔已存在", + "rename_failed": "無法重命名設定檔。設定檔可能不存在或新名稱已被使用。", + "assignment_failed": "無法將設定檔指派給循環", + "duplicate_phase": "具有此名稱的階段已存在。", + "phase_not_found": "未找到所選相。", + "invalid_phase_name": "階段名稱不能為空。", + "phase_name_too_long": "相名太長。", + "invalid_phase_range": "相位範圍無效。結束必須大於開始。", + "invalid_phase_timestamp": "時間戳無效。使用 YYYY-MM-DD HH:MM 或 HH:MM 格式。", + "overlapping_phase_ranges": "相位範圍重疊。調整範圍並重試。", + "incomplete_phase_row": "每個階段行必須包含一個階段以及所選模式的完整開始/結束值。", + "timestamp_mode_cycle_required": "使用時間戳模式時請選擇來源週期。", + "no_phase_ranges": "尚無可用於該操作的階段範圍。", + "feedback_notification_title": "WashData:驗證週期 ({device})", + "feedback_notification_message": "**设备**:{device}\n**程式**:{program}({confidence}% 置信度)\n**時間**:{time}\n\nWashData 需要您的協助來驗證偵測到的此週期。\n\n請前往 **設定 > 裝置與服務 > WashData > 設定 > 學習回饋** 以確認或修正此結果。", + "suggestions_ready_notification_title": "WashData:建議設定已就緒 ({device})", + "suggestions_ready_notification_message": "**建議設定**感測器現在報告 **{count}** 可操作的建議。\n\n若要查看並套用它們:**設定 > 裝置和服務 > WashData > 配置 > 進階設定 > 套用建議值**。\n\n建議是可選的,並會在您儲存之前顯示以供審核。", + "trim_range_invalid": "開始必須在結束之前。請調整您的修剪點。", + "trim_failed": "修剪失敗。循環可能不再存在或視窗為空。", + "invalid_split_timestamp": "時間戳記無效或超出週期時間視窗。請在週期時間視窗內使用 HH:MM 或 HH:MM:SS。", + "no_split_segments_found": "無法根據這些時間戳記產生有效片段。請確認每個時間戳記都在週期時間視窗內,且各片段至少為 1 分鐘。", + "auto_tune_suggestion": "{device_type} {device_title} 偵測到幽靈週期。建議的 min_power 變更:{current_min}W -> {new_min}W(不會自動套用)。", + "auto_tune_title": "WashData 自動調整", + "auto_tune_fallback": "{device_type} {device_title} 偵測到幽靈週期。建議的 min_power 變更:{current_min}W -> {new_min}W(不會自動套用)。" + }, + "abort": { + "no_cycles_found": "沒有找到循環", + "no_split_segments_found": "在所選週期中找不到可分割的片段。", + "cycle_not_found": "無法載入所選週期。", + "no_power_data": "所選週期沒有可用的功率追蹤資料。", + "no_profiles_found": "未找到設定檔。首先建立一個設定檔。", + "no_profiles_for_matching": "沒有可用於匹配的設定檔。首先建立設定檔。", + "no_unlabeled_cycles": "所有周期均已標記", + "no_suggestions": "尚無可用的建議值。運行幾個週期並重試。", + "no_predictions": "沒有預測", + "no_custom_phases": "未找到自訂階段。", + "reprocess_success": "已成功重新處理 {count} 個週期。", + "debug_data_cleared": "已成功清除 {count} 個週期的偵錯資料。" + } + }, + "selector": { + "notify_events_option": { + "options": { + "cycle_start": "循環開始", + "cycle_finish": "循環完成", + "cycle_live": "即時進展" + } + }, + "common_text": { + "options": { + "unlabeled": "(未標記)", + "create_new_profile": "建立新的個人資料", + "remove_label": "移除標籤", + "no_reference_cycle": "(無參考週期)", + "all_device_types": "所有設備類型", + "current_suffix": "(當前的)", + "editor_select_info": "選擇 1 個循環進行拆分,或選擇 2 個以上循環進行合併。", + "editor_select_info_split": "選擇 1 個要分割的週期。", + "editor_select_info_merge": "選擇 2 個或更多要合併的週期。", + "editor_select_info_delete": "選擇 1 個或更多要刪除的週期。", + "no_cycles_recorded": "尚未記錄任何週期。", + "profile_summary_line": "- **{name}**:{count} 個週期,平均 {avg} 分鐘", + "no_profiles_created": "尚未建立設定檔。", + "phase_preview": "階段預覽", + "phase_preview_no_curve": "平均輪廓曲線尚不可用。為此設定檔運行/標記更多周期。", + "average_power_curve": "平均功率曲線", + "unit_min": "分分鐘", + "profile_option_fmt": "{name}({count} 個週期,平均 ~{duration}m)", + "profile_option_short_fmt": "{name}({count} 個週期,~{duration} 分鐘)", + "deleted_cycles_from_profile": "已從 {profile} 中刪除 {count} 個週期。", + "not_enough_data_graph": "數據不足,無法產生圖表。", + "no_profiles_found": "未找到設定檔。", + "cycle_info_fmt": "週期:{start},{duration}米,電流:{label}", + "top_candidates_header": "### 最佳候選人", + "tbl_profile": "輪廓", + "tbl_confidence": "信心", + "tbl_correlation": "相關性", + "tbl_duration_match": "持續時間匹配", + "detected_profile": "偵測到的設定檔", + "estimated_duration": "預計持續時間", + "actual_duration": "實際持續時間", + "choose_action": "__選擇以下操作:__", + "tbl_count": "數數", + "tbl_avg": "平均", + "tbl_min": "最小", + "tbl_max": "最大限度", + "tbl_energy_avg": "能量(平均)", + "tbl_energy_total": "能量(總計)", + "tbl_consistency": "組成。", + "tbl_last_run": "最後一次運行", + "graph_legend_title": "圖例", + "graph_legend_body": "藍色帶代表觀察到的最小和最大功率消耗範圍。此線顯示平均功率曲線。", + "recording_preview": "錄音預覽", + "trim_start": "修剪開始", + "trim_end": "修剪末端", + "split_preview_title": "分割預覽", + "split_preview_found_fmt": "找到 {count} 個分段。", + "split_preview_confirm_fmt": "按一下「確認」將此週期拆分為 {count} 個單獨的周期。", + "merge_preview_title": "合併預覽", + "merge_preview_joining_fmt": "加入 {count} 個週期。間隙將以 0W 讀數填充。", + "editor_delete_preview_title": "刪除預覽", + "editor_delete_preview_intro": "所選週期將被永久刪除:", + "editor_delete_preview_confirm": "點擊確認以永久刪除這些週期記錄。", + "no_power_preview": "*沒有可供預覽的功率數據。 *", + "profile_comparison": "簡介比較", + "actual_cycle_label": "本週期(實際)", + "storage_usage_header": "儲存使用情況", + "storage_file_size": "文件大小", + "storage_cycles": "週期", + "storage_profiles": "型材", + "storage_debug_traces": "偵錯追蹤", + "overview_suffix": "概述", + "phase_preview_for_profile": "設定檔的階段預覽", + "current_ranges_header": "電流範圍", + "assign_phases_help": "使用以下操作來新增、編輯、刪除或儲存範圍。", + "profile_summary_header": "簡介摘要", + "recent_cycles_header": "最近的週期", + "trim_cycle_preview_title": "循環修剪預覽", + "trim_cycle_preview_no_data": "此週期沒有可用的功率資料。", + "trim_cycle_preview_kept_suffix": "保留", + "table_program": "程式", + "table_when": "時間", + "table_length": "時長", + "table_match": "匹配", + "table_profile": "輪廓", + "table_cycles": "週期", + "table_avg_length": "平均時長", + "table_last_run": "最後一次運行", + "table_avg_energy": "平均能耗", + "table_detected_program": "檢測到的程式", + "table_confidence": "信心", + "table_reported": "已回報", + "phase_builtin_suffix": "(內建)", + "phase_none_available": "沒有可用的階段。", + "settings_deprecation_warning": "⚠️ **已棄用的裝置類型:** {device_type} 計劃在未來版本中刪除。 WashData 的匹配管道不會為此類設備產生可靠的結果。您的整合在棄用期內繼續有效;要消除此警告,請將下面的**設備類型**切換為受支援的類型之一(洗衣機、烘乾機、洗衣乾衣機組合、洗碗機、空氣炸鍋、麵包機或泵),或者如果您的設備與任何受支援的類型不匹配,則切換為**其他(高級)**。 **其他(進階)** 故意提供通用預設值,未針對任何特定裝置進行調整,因此您需要自行配置閾值、逾時和匹配參數;切換時,所有現有設定都會保留。", + "phase_other_device_types": "其他設備類型:" + } + }, + "interactive_editor_action": { + "options": { + "split": "分割一個週期(查找間隙)", + "merge": "合併循環(連結片段)", + "delete": "刪除週期" + } + }, + "split_mode": { + "options": { + "auto": "自動偵測閒置間隔", + "manual": "手動時間戳記" + } + }, + "diagnostics_action": { + "options": { + "reprocess_history": "維護:重新處理和優化數據", + "clear_debug_data": "清除調試資料(釋放空間)", + "wipe_history": "擦除該裝置的所有資料(不可逆)", + "export_import": "使用設定導出/匯入 JSON(複製/貼上)" + } + }, + "export_import_mode": { + "options": { + "export": "匯出所有數據", + "import": "導入/合併數據" + } + }, + "manage_cycles_action": { + "options": { + "auto_label_cycles": "自動標記舊自行車", + "select_cycle_to_label": "標籤特定週期", + "select_cycle_to_delete": "刪除循環", + "interactive_editor": "合併/拆分互動式編輯器", + "trim_cycle": "修剪週期數據" + } + }, + "manage_profiles_action": { + "options": { + "create_profile": "建立新的個人資料", + "edit_profile": "編輯/重新命名設定檔", + "delete_profile": "刪除個人資料", + "profile_stats": "個人資料統計", + "cleanup_profile": "清理歷史記錄 - 圖表和刪除", + "assign_phases": "分配相位範圍" + } + }, + "manage_phase_catalog_action": { + "options": { + "create_custom_phase": "建立新階段", + "edit_custom_phase": "編輯階段", + "delete_custom_phase": "刪除階段" + } + }, + "assign_profile_phases_action": { + "options": { + "add_range": "增加相位範圍", + "edit_range": "編輯相位範圍", + "delete_range": "刪除相位範圍", + "clear_ranges": "清除所有範圍", + "auto_detect_ranges": "自動檢測相位", + "save_ranges": "儲存並返回" + } + }, + "record_cycle_action": { + "options": { + "refresh_status": "刷新狀態", + "stop_recording": "停止錄影(儲存並處理)", + "start_recording": "開始新錄音", + "process_recording": "處理最後的錄音(修剪和保存)", + "discard_recording": "放棄最後的錄音" + } + }, + "record_process_save_mode": { + "options": { + "new_profile": "建立新的個人資料", + "existing_profile": "新增至現有設定檔", + "discard": "放棄錄音" + } + }, + "resolve_feedback_action": { + "options": { + "confirm": "確認-檢測正確", + "correct": "正確 - 選擇項目/持續時間", + "ignore": "忽略 - 誤報/噪音循環", + "delete": "刪除 - 從歷史記錄中刪除" + } + }, + "assign_profile_phases_auto_detect_action": { + "options": { + "name_phases": "命名和應用階段", + "cancel": "不做任何修改返回" + } + }, + "trim_cycle_action": { + "options": { + "set_start": "設定起點", + "set_end": "設定終點", + "reset": "重置為完整持續時間", + "apply": "應用修剪", + "cancel": "取消" + } + }, + "device_type": { + "options": { + "washing_machine": "洗衣機", + "dryer": "烘乾機", + "washer_dryer": "洗衣機烘乾機組合", + "dishwasher": "洗碗機", + "coffee_machine": "咖啡機(已棄用)", + "ev": "電動車(已棄用)", + "air_fryer": "氣炸鍋", + "heat_pump": "熱泵(已棄用)", + "bread_maker": "麵包機", + "pump": "泵浦/污水泵", + "oven": "烤箱(已棄用)", + "other": "其他(高級)" + } + } + }, + "services": { + "label_cycle": { + "name": "標籤週期", + "description": "將現有設定檔指派給過去的周期,或刪除標籤。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置要貼上標籤。" + }, + "cycle_id": { + "name": "週期ID", + "description": "要標記的周期的 ID。" + }, + "profile_name": { + "name": "個人資料名稱", + "description": "現有設定檔的名稱(在「管理設定檔」選單中建立設定檔)。留空以刪除標籤。" + } + } + }, + "create_profile": { + "name": "建立個人資料", + "description": "建立新的設定檔(獨立或基於參考週期)。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置。" + }, + "profile_name": { + "name": "個人資料名稱", + "description": "新設定檔的名稱(例如“重型”、“熟食”)。" + }, + "reference_cycle_id": { + "name": "參考週期 ID", + "description": "此設定檔所基於的可選循環 ID。" + } + } + }, + "delete_profile": { + "name": "刪除個人資料", + "description": "刪除設定檔並可選擇使用它取消標記循環。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置。" + }, + "profile_name": { + "name": "個人資料名稱", + "description": "要刪除的設定檔。" + }, + "unlabel_cycles": { + "name": "取消標籤循環", + "description": "使用此輪廓從循環中刪除輪廓標籤。" + } + } + }, + "auto_label_cycles": { + "name": "自動標記舊自行車", + "description": "使用設定檔匹配追溯標記未標記的循環。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置。" + }, + "confidence_threshold": { + "name": "置信閾值", + "description": "應用標籤的最小匹配置信度 (0.50-0.95)。" + } + } + }, + "export_config": { + "name": "匯出配置", + "description": "將此設備的設定檔、週期和設定匯出到 JSON 檔案(每個設備)。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置出口。" + }, + "path": { + "name": "小路", + "description": "可選的要寫入的絕對檔案路徑(預設為 /config/ha_washdata_export_{entry}.json)。" + } + } + }, + "import_config": { + "name": "導入配置", + "description": "從 JSON 匯出檔案匯入此裝置的設定檔、週期和設定(設定可能會被覆寫)。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置導入。" + }, + "path": { + "name": "小路", + "description": "要匯入的匯出 JSON 檔案的絕對路徑。" + } + } + }, + "submit_cycle_feedback": { + "name": "提交週期回饋", + "description": "完成循環後確認或修正自動偵測的程序。提供“entry_id”(高級)或“device_id”(建議)。", + "fields": { + "device_id": { + "name": "裝置", + "description": "WashData 設備(建議而不是entry_id)。" + }, + "entry_id": { + "name": "條目 ID", + "description": "裝置的配置條目 ID(替代 device_id)。" + }, + "cycle_id": { + "name": "週期ID", + "description": "回饋通知/日誌中顯示的周期 ID。" + }, + "user_confirmed": { + "name": "確認偵測到的程序", + "description": "如果偵測到的程式正確,則設為 true。" + }, + "corrected_profile": { + "name": "修正後的輪廓", + "description": "如果未確認,請提供正確的設定檔/程式名稱。" + }, + "corrected_duration": { + "name": "修正的持續時間(秒)", + "description": "可選的修正持續時間(以秒為單位)。" + }, + "notes": { + "name": "筆記", + "description": "關於此週期的可選註釋。" + } + } + }, + "record_start": { + "name": "記錄週期開始", + "description": "開始手動記錄一個乾淨的循環(繞過所有匹配邏輯)。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機設備進行記錄。" + } + } + }, + "record_stop": { + "name": "記錄循環停止", + "description": "停止手動錄製。", + "fields": { + "device_id": { + "name": "裝置", + "description": "洗衣機裝置停止錄音。" + } + } + }, + "trim_cycle": { + "name": "修剪週期", + "description": "將過去週期的功率資料修剪到特定時間視窗。", + "fields": { + "device_id": { + "name": "裝置", + "description": "WashData 設備。" + }, + "cycle_id": { + "name": "週期ID", + "description": "要修剪的周期的 ID。" + }, + "trim_start_s": { + "name": "修剪開始(秒)", + "description": "在幾秒鐘內保留此偏移量的資料。預設 0。" + }, + "trim_end_s": { + "name": "修剪結束(秒)", + "description": "在幾秒鐘內將資料保持到此偏移量。預設 = 完整持續時間。" + } + } + }, + "pause_cycle": { + "name": "暫停循環", + "description": "暫停 WashData 設備的活動週期。", + "fields": { + "device_id": { + "name": "裝置", + "description": "WashData 裝置暫停。" + } + } + }, + "resume_cycle": { + "name": "恢復週期", + "description": "恢復 WashData 設備的暫停週期。", + "fields": { + "device_id": { + "name": "裝置", + "description": "WashData 設備恢復。" + } + } + } + }, + "entity": { + "binary_sensor": { + "running": { + "name": "跑步" + }, + "match_ambiguity": { + "name": "匹配歧義" + } + }, + "sensor": { + "washer_state": { + "name": "狀態", + "state": { + "off": "離開", + "idle": "閒置的", + "starting": "開始", + "running": "跑步", + "paused": "已暫停", + "user_paused": "使用者已暫停", + "ending": "結束", + "finished": "完成的", + "anti_wrinkle": "抗皺", + "delay_wait": "等待開始", + "interrupted": "被打斷", + "force_stopped": "強制停止", + "rinse": "沖洗", + "unknown": "未知", + "clean": "乾淨的" + } + }, + "washer_program": { + "name": "程式" + }, + "time_remaining": { + "name": "剩餘時間" + }, + "total_duration": { + "name": "總持續時間" + }, + "cycle_progress": { + "name": "進步" + }, + "current_power": { + "name": "當前功率" + }, + "elapsed_time": { + "name": "經過時間" + }, + "debug_info": { + "name": "偵錯資訊" + }, + "match_confidence": { + "name": "比賽信心" + }, + "top_candidates": { + "name": "最佳候選人", + "state": { + "none": "沒有任何" + } + }, + "current_phase": { + "name": "當前階段" + }, + "wash_phase": { + "name": "階段" + }, + "profile_cycle_count": { + "name": "設定檔 {profile_name} 計數", + "unit_of_measurement": "週期" + }, + "suggestions": { + "name": "建議設定" + }, + "pump_runs_today": { + "name": "泵浦運轉(過去 24 小時)" + }, + "cycle_count": { + "name": "循環計數" + } + }, + "select": { + "program_select": { + "name": "循環計劃", + "state": { + "auto_detect": "自動偵測" + } + } + }, + "button": { + "force_end_cycle": { + "name": "強制結束循環" + }, + "pause_cycle": { + "name": "暫停循環" + }, + "resume_cycle": { + "name": "恢復週期" + } + } + }, + "exceptions": { + "device_id_required": { + "message": "需要有效的 device_id。" + }, + "cycle_id_required": { + "message": "需要有效的cycle_id。" + }, + "profile_name_required": { + "message": "需要有效的 profile_name。" + }, + "device_not_found": { + "message": "未找到設備。" + }, + "no_config_entry": { + "message": "找不到設備的配置條目。" + }, + "integration_not_loaded": { + "message": "未為此設備載入整合。" + }, + "cycle_not_found_or_no_power": { + "message": "未找到週期或沒有功率數據。" + }, + "trim_failed_empty_window": { + "message": "修剪失敗 - 未找到週期、無功率資料或產生的視窗為空。" + }, + "trim_invalid_range": { + "message": "trim_end_s必須大於trim_start_s。" + } + } +} diff --git a/custom_components/ha_washdata/www/ha-washdata-card.js b/custom_components/ha_washdata/www/ha-washdata-card.js new file mode 100644 index 0000000..7684237 --- /dev/null +++ b/custom_components/ha_washdata/www/ha-washdata-card.js @@ -0,0 +1,2530 @@ +const CARD_TAG = "ha-washdata-card"; +const EDITOR_TAG = "ha-washdata-card-editor"; + +// Gesture timing (ms) and movement tolerance (px) for tap / hold / double-tap, +// chosen to match Home Assistant's own action handler conventions. +const HOLD_MS = 500; +const DOUBLE_TAP_MS = 250; +const TAP_MOVE_TOLERANCE = 10; + +const TRANSLATIONS = { + "en": { + "washer_program": "Washer Program", + "program_placeholder": "Select Program", + "duration": "Duration", + "minutes": "min", + "time_remaining": "Time Remaining", + "no_prediction": "No Prediction", + "cycle_in_progress": "Cycle in progress", + "status": "Status", + "progress": "Progress", + "select_program": "Select a program to see details", + "title": "Title", + "status_entity": "Status Entity", + "icon": "Icon", + "active_color": "Active Icon Color", + "show_state": "Show State", + "show_program": "Show Program", + "show_details": "Show Details", + "spin_icon": "Spinning Icon (While running)", + "program_entity": "Program Entity", + "pct_entity": "Progress Entity (Optional)", + "time_entity": "Time Entity (Optional)", + "display_mode": "Display Mode", + "show_time_remaining": "Show Time Remaining", + "show_percentage": "Show Percentage", + "entity_not_found": "Entity not found", + "tap_action": "Tap Action", + "hold_action": "Hold Action", + "double_tap_action": "Double Tap Action" + }, + "af": { + "washer_program": "Wasprogram", + "program_placeholder": "Kies Program", + "duration": "Duur", + "minutes": "min", + "time_remaining": "Tyd wat oorbly", + "no_prediction": "Geen voorspelling", + "cycle_in_progress": "Siklus aan die gang", + "status": "Status", + "progress": "Vordering", + "select_program": "Kies 'n program om besonderhede te sien", + "title": "Titel", + "status_entity": "Status Entiteit", + "icon": "Ikoon", + "active_color": "Aktiewe ikoonkleur", + "show_state": "Wys Staat", + "show_program": "Wys Program", + "show_details": "Wys besonderhede", + "spin_icon": "Draai-ikoon (Terwyl hardloop)", + "program_entity": "Program Entiteit", + "pct_entity": "Vorderingsentiteit (opsioneel)", + "time_entity": "Tydsentiteit (opsioneel)", + "display_mode": "Vertoonmodus", + "show_time_remaining": "Wys oorblywende tyd", + "show_percentage": "Wys persentasie", + "entity_not_found": "Entiteit nie gevind nie", + "tap_action": "Tik op Aksie", + "hold_action": "Hou Aksie", + "double_tap_action": "Dubbeltik-aksie" + }, + "ar": { + "washer_program": "برنامج الغساله", + "program_placeholder": "حدد البرنامج", + "duration": "مدة", + "minutes": "دقيقة", + "time_remaining": "الوقت المتبقي", + "no_prediction": "لا التنبؤ", + "cycle_in_progress": "الدورة قيد التقدم", + "status": "حالة", + "progress": "تقدم", + "select_program": "حدد برنامجًا لمعرفة التفاصيل", + "title": "عنوان", + "status_entity": "كيان الحالة", + "icon": "رمز", + "active_color": "لون الرمز النشط", + "show_state": "عرض الدولة", + "show_program": "عرض البرنامج", + "show_details": "إظهار التفاصيل", + "spin_icon": "أيقونة الدوران (أثناء التشغيل)", + "program_entity": "كيان البرنامج", + "pct_entity": "كيان التقدم (اختياري)", + "time_entity": "الكيان الزمني (اختياري)", + "display_mode": "وضع العرض", + "show_time_remaining": "عرض الوقت المتبقي", + "show_percentage": "إظهار النسبة المئوية", + "entity_not_found": "لم يتم العثور على الكيان", + "tap_action": "اضغط على الإجراء", + "hold_action": "توقف", + "double_tap_action": "عمل مزدوج" + }, + "bg": { + "washer_program": "Програма за пране", + "program_placeholder": "Изберете Програма", + "duration": "Продължителност", + "minutes": "мин", + "time_remaining": "Оставащо време", + "no_prediction": "Няма прогноза", + "cycle_in_progress": "Цикълът е в ход", + "status": "Статус", + "progress": "Напредък", + "select_program": "Изберете програма, за да видите подробности", + "title": "Заглавие", + "status_entity": "Състояние на обекта", + "icon": "Икона", + "active_color": "Цвят на активната икона", + "show_state": "Показване на състояние", + "show_program": "Шоу програма", + "show_details": "Показване на подробности", + "spin_icon": "Въртяща се икона (докато работи)", + "program_entity": "Програмен субект", + "pct_entity": "Обект на напредъка (по избор)", + "time_entity": "Времеви обект (по избор)", + "display_mode": "Режим на показване", + "show_time_remaining": "Показване на оставащото време", + "show_percentage": "Показване на процента", + "entity_not_found": "Обектът не е намерен", + "tap_action": "Докосване на действие", + "hold_action": "Задръж действие", + "double_tap_action": "Двойно докосване" + }, + "bn": { + "washer_program": "ওয়াশার প্রোগ্রাম", + "program_placeholder": "প্রোগ্রাম নির্বাচন করুন", + "duration": "সময়কাল", + "minutes": "মিনিট", + "time_remaining": "বাকি সময়", + "no_prediction": "কোন ভবিষ্যদ্বাণী নেই", + "cycle_in_progress": "সাইকেল চলছে", + "status": "স্ট্যাটাস", + "progress": "অগ্রগতি", + "select_program": "বিস্তারিত দেখতে একটি প্রোগ্রাম নির্বাচন করুন", + "title": "শিরোনাম", + "status_entity": "স্থিতি সত্তা", + "icon": "আইকন", + "active_color": "সক্রিয় আইকন রঙ", + "show_state": "রাজ্য দেখান", + "show_program": "প্রোগ্রাম দেখান", + "show_details": "বিস্তারিত দেখান", + "spin_icon": "স্পিনিং আইকন (চালানোর সময়)", + "program_entity": "প্রোগ্রাম সত্তা", + "pct_entity": "অগ্রগতি সত্তা (ঐচ্ছিক)", + "time_entity": "সময়ের সত্তা (ঐচ্ছিক)", + "display_mode": "প্রদর্শন মোড", + "show_time_remaining": "অবশিষ্ট সময় দেখান", + "show_percentage": "শতাংশ দেখান", + "entity_not_found": "সত্তা খুঁজে পাওয়া যায়নি", + "tap_action": "কর্ম", + "hold_action": "কর্ম স্থগিত করুন", + "double_tap_action": "দুইবার ক্লিক কর্ম" + }, + "bs": { + "washer_program": "Program za pranje", + "program_placeholder": "Odaberite Program", + "duration": "Trajanje", + "minutes": "min", + "time_remaining": "Preostalo vrijeme", + "no_prediction": "Nema predviđanja", + "cycle_in_progress": "Ciklus je u toku", + "status": "Status", + "progress": "Napredak", + "select_program": "Odaberite program da vidite detalje", + "title": "Naslov", + "status_entity": "Status Entiteta", + "icon": "Ikona", + "active_color": "Aktivna boja ikone", + "show_state": "Prikaži državu", + "show_program": "Show Program", + "show_details": "Prikaži detalje", + "spin_icon": "Ikona za okretanje (dok trčanje)", + "program_entity": "Programski entitet", + "pct_entity": "Entitet napretka (opciono)", + "time_entity": "Vremenski entitet (opcionalno)", + "display_mode": "Način prikaza", + "show_time_remaining": "Prikaži preostalo vrijeme", + "show_percentage": "Prikaži procenat", + "entity_not_found": "Entitet nije pronađen", + "tap_action": "Dodirnite Akcija", + "hold_action": "Držite akciju", + "double_tap_action": "Dvostruki dodir Akcija" + }, + "ca": { + "washer_program": "Programa de rentadora", + "program_placeholder": "Seleccioneu Programa", + "duration": "Durada", + "minutes": "min", + "time_remaining": "Temps restant", + "no_prediction": "Sense predicció", + "cycle_in_progress": "Cicle en curs", + "status": "Estat", + "progress": "Progrés", + "select_program": "Seleccioneu un programa per veure'n els detalls", + "title": "Títol", + "status_entity": "Entitat d'estat", + "icon": "Icona", + "active_color": "Color de la icona activa", + "show_state": "Mostra l'estat", + "show_program": "Programa Mostra", + "show_details": "Mostra els detalls", + "spin_icon": "Icona de gir (mentre corre)", + "program_entity": "Entitat del programa", + "pct_entity": "Entitat de progrés (opcional)", + "time_entity": "Entitat temporal (opcional)", + "display_mode": "Mode de visualització", + "show_time_remaining": "Mostra el temps restant", + "show_percentage": "Mostra el percentatge", + "entity_not_found": "No s'ha trobat l'entitat", + "tap_action": "Acció de temps", + "hold_action": "Reté acció", + "double_tap_action": "Acció doble de temps" + }, + "cs": { + "washer_program": "Program pračky", + "program_placeholder": "Vyberte Program", + "duration": "Trvání", + "minutes": "min", + "time_remaining": "Zbývající čas", + "no_prediction": "Žádná předpověď", + "cycle_in_progress": "Cyklus probíhá", + "status": "Postavení", + "progress": "Pokrok", + "select_program": "Chcete-li zobrazit podrobnosti, vyberte program", + "title": "Titul", + "status_entity": "Stavová entita", + "icon": "Ikona", + "active_color": "Barva aktivní ikony", + "show_state": "Zobrazit stav", + "show_program": "Zobrazit program", + "show_details": "Zobrazit podrobnosti", + "spin_icon": "Ikona rotace (při běhu)", + "program_entity": "Entita programu", + "pct_entity": "Entita průběhu (volitelné)", + "time_entity": "Časová entita (volitelné)", + "display_mode": "Režim zobrazení", + "show_time_remaining": "Zobrazit zbývající čas", + "show_percentage": "Zobrazit procento", + "entity_not_found": "Entita nenalezena", + "tap_action": "Klepněte na možnost Akce", + "hold_action": "Držet akci", + "double_tap_action": "Akce dvojitého klepnutí" + }, + "cy": { + "washer_program": "Rhaglen Wasier", + "program_placeholder": "Dewiswch Rhaglen", + "duration": "Hyd", + "minutes": "min", + "time_remaining": "Amser yn weddill", + "no_prediction": "Dim Rhagfynegiad", + "cycle_in_progress": "Cylch ar y gweill", + "status": "Statws", + "progress": "Cynnydd", + "select_program": "Dewiswch raglen i weld y manylion", + "title": "Teitl", + "status_entity": "Endid Statws", + "icon": "Eicon", + "active_color": "Lliw Eicon Actif", + "show_state": "Dangos Cyflwr", + "show_program": "Rhaglen Sioe", + "show_details": "Dangos Manylion", + "spin_icon": "Eicon Troelli (Wrth redeg)", + "program_entity": "Endid Rhaglen", + "pct_entity": "Endid Cynnydd (Dewisol)", + "time_entity": "Endid Amser (Dewisol)", + "display_mode": "Modd Arddangos", + "show_time_remaining": "Dangos Amser ar ôl", + "show_percentage": "Dangos Canran", + "entity_not_found": "Endid heb ei ganfod", + "tap_action": "Tap Gweithredu", + "hold_action": "Daliwch Weithredu", + "double_tap_action": "Gweithred Tap Dwbl" + }, + "da": { + "washer_program": "Vaskeprogram", + "program_placeholder": "Vælg Program", + "duration": "Varighed", + "minutes": "min", + "time_remaining": "Tid tilbage", + "no_prediction": "Ingen forudsigelse", + "cycle_in_progress": "Cyklus i gang", + "status": "Status", + "progress": "Fremskridt", + "select_program": "Vælg et program for at se detaljer", + "title": "Titel", + "status_entity": "Statusenhed", + "icon": "Ikon", + "active_color": "Aktiv ikon farve", + "show_state": "Vis tilstand", + "show_program": "Vis program", + "show_details": "Vis detaljer", + "spin_icon": "Spinning-ikon (mens du løber)", + "program_entity": "Programenhed", + "pct_entity": "Fremskridtsenhed (valgfrit)", + "time_entity": "Tidsenhed (valgfrit)", + "display_mode": "Visningstilstand", + "show_time_remaining": "Vis resterende tid", + "show_percentage": "Vis procent", + "entity_not_found": "Enheden blev ikke fundet", + "tap_action": "Tap på handling", + "hold_action": "Hold handling", + "double_tap_action": "Dobbelt tastehandling" + }, + "de": { + "washer_program": "Waschprogramm", + "program_placeholder": "Wählen Sie Programm", + "duration": "Dauer", + "minutes": "min", + "time_remaining": "Verbleibende Zeit", + "no_prediction": "Keine Vorhersage", + "cycle_in_progress": "Zyklus läuft", + "status": "Status", + "progress": "Fortschritt", + "select_program": "Wählen Sie ein Programm aus, um Details anzuzeigen", + "title": "Titel", + "status_entity": "Status-Entität", + "icon": "Symbol", + "active_color": "Aktive Symbolfarbe", + "show_state": "Status anzeigen", + "show_program": "Programm anzeigen", + "show_details": "Details anzeigen", + "spin_icon": "Spinning-Symbol (während des Laufens)", + "program_entity": "Programmeinheit", + "pct_entity": "Fortschrittsentität (optional)", + "time_entity": "Zeiteinheit (optional)", + "display_mode": "Anzeigemodus", + "show_time_remaining": "Verbleibende Zeit anzeigen", + "show_percentage": "Prozentsatz anzeigen", + "entity_not_found": "Entität nicht gefunden", + "tap_action": "Tippen Sie auf", + "hold_action": "Action spielen", + "double_tap_action": "Doppeltipp-Aktion" + }, + "el": { + "washer_program": "Πρόγραμμα πλύσης", + "program_placeholder": "Επιλέξτε Πρόγραμμα", + "duration": "Διάρκεια", + "minutes": "ελάχ", + "time_remaining": "Χρόνος που απομένει", + "no_prediction": "Καμία Πρόβλεψη", + "cycle_in_progress": "Κύκλος σε εξέλιξη", + "status": "Κατάσταση", + "progress": "Πρόοδος", + "select_program": "Επιλέξτε ένα πρόγραμμα για να δείτε λεπτομέρειες", + "title": "Τίτλος", + "status_entity": "Οντότητα κατάστασης", + "icon": "Εικόνισμα", + "active_color": "Χρώμα ενεργού εικονιδίου", + "show_state": "Εμφάνιση κατάστασης", + "show_program": "Εμφάνιση προγράμματος", + "show_details": "Εμφάνιση λεπτομερειών", + "spin_icon": "Περιστρεφόμενο εικονίδιο (Κατά την εκτέλεση)", + "program_entity": "Οντότητα προγράμματος", + "pct_entity": "Οντότητα προόδου (Προαιρετικό)", + "time_entity": "Οντότητα ώρας (Προαιρετικό)", + "display_mode": "Λειτουργία εμφάνισης", + "show_time_remaining": "Εμφάνιση χρόνου που απομένει", + "show_percentage": "Εμφάνιση ποσοστού", + "entity_not_found": "Η οντότητα δεν βρέθηκε", + "tap_action": "Πατήστε ενέργεια", + "hold_action": "Διατήρηση ενέργειας", + "double_tap_action": "Διπλή ενέργεια πατήματος" + }, + "en-GB": { + "washer_program": "Washer Program", + "program_placeholder": "Select Program", + "duration": "Duration", + "minutes": "min", + "time_remaining": "Time Remaining", + "no_prediction": "No Prediction", + "cycle_in_progress": "Cycle in progress", + "status": "Status", + "progress": "Progress", + "select_program": "Select a program to see details", + "title": "Title", + "status_entity": "Status Entity", + "icon": "Icon", + "active_color": "Active Icon Color", + "show_state": "Show State", + "show_program": "Show Program", + "show_details": "Show Details", + "spin_icon": "Spinning Icon (While running)", + "program_entity": "Program Entity", + "pct_entity": "Progress Entity (Optional)", + "time_entity": "Time Entity (Optional)", + "display_mode": "Display Mode", + "show_time_remaining": "Show Time Remaining", + "show_percentage": "Show Percentage", + "entity_not_found": "Entity not found" + }, + "eo": { + "washer_program": "Programo de Lavujo", + "program_placeholder": "Elektu Programon", + "duration": "Daŭro", + "minutes": "Mi min", + "time_remaining": "Tempo Restanta", + "no_prediction": "Neniu Antaŭdiro", + "cycle_in_progress": "Ciklo en progreso", + "status": "Statuso", + "progress": "Progreso", + "select_program": "Elektu programon por vidi detalojn", + "title": "Titolo", + "status_entity": "Statusa Ento", + "icon": "Ikono", + "active_color": "Aktiva Ikono Koloro", + "show_state": "Montru Ŝtaton", + "show_program": "Montru Programon", + "show_details": "Montru Detalojn", + "spin_icon": "Turniĝanta Ikono (Dum kurado)", + "program_entity": "Programa Ento", + "pct_entity": "Progresa Ento (Laŭvola)", + "time_entity": "Tempo-Entaĵo (Laŭvola)", + "display_mode": "Montra Reĝimo", + "show_time_remaining": "Montru Restantan Tempon", + "show_percentage": "Montru Procenton", + "entity_not_found": "Ento ne trovita", + "tap_action": "Glubenda Ago", + "hold_action": "Tenu Agon", + "double_tap_action": "Duobla Tapa Ago" + }, + "es": { + "washer_program": "Programa de lavadora", + "program_placeholder": "Seleccionar programa", + "duration": "Duración", + "minutes": "mín.", + "time_remaining": "Tiempo restante", + "no_prediction": "Sin predicción", + "cycle_in_progress": "Ciclo en progreso", + "status": "Estado", + "progress": "Progreso", + "select_program": "Selecciona un programa para ver detalles", + "title": "Título", + "status_entity": "Entidad de estado", + "icon": "Icono", + "active_color": "Color del icono activo", + "show_state": "Mostrar estado", + "show_program": "Mostrar programa", + "show_details": "Mostrar detalles", + "spin_icon": "Icono de giro (mientras se ejecuta)", + "program_entity": "Entidad del programa", + "pct_entity": "Entidad de progreso (opcional)", + "time_entity": "Entidad de tiempo (opcional)", + "display_mode": "Modo de visualización", + "show_time_remaining": "Mostrar tiempo restante", + "show_percentage": "Mostrar porcentaje", + "entity_not_found": "Entidad no encontrada", + "tap_action": "Toque Acción", + "hold_action": "Mantener acción", + "double_tap_action": "Doble toque de acción" + }, + "es-419": { + "washer_program": "Programa de lavadora", + "program_placeholder": "Seleccionar programa", + "duration": "Duración", + "minutes": "mín.", + "time_remaining": "Tiempo restante", + "no_prediction": "Sin predicción", + "cycle_in_progress": "Ciclo en progreso", + "status": "Estado", + "progress": "Progreso", + "select_program": "Selecciona un programa para ver detalles", + "title": "Título", + "status_entity": "Entidad de estado", + "icon": "Icono", + "active_color": "Color del icono activo", + "show_state": "Mostrar estado", + "show_program": "Mostrar programa", + "show_details": "Mostrar detalles", + "spin_icon": "Icono de giro (mientras se ejecuta)", + "program_entity": "Entidad del programa", + "pct_entity": "Entidad de progreso (opcional)", + "time_entity": "Entidad de tiempo (opcional)", + "display_mode": "Modo de visualización", + "show_time_remaining": "Mostrar tiempo restante", + "show_percentage": "Mostrar porcentaje", + "entity_not_found": "Entidad no encontrada", + "tap_action": "Toque Acción", + "hold_action": "Mantener acción", + "double_tap_action": "Doble toque de acción" + }, + "et": { + "washer_program": "Pesumasina programm", + "program_placeholder": "Valige Programm", + "duration": "Kestus", + "minutes": "min", + "time_remaining": "Järelejäänud aeg", + "no_prediction": "Ei mingit ennustust", + "cycle_in_progress": "Tsükkel on pooleli", + "status": "Olek", + "progress": "Edusammud", + "select_program": "Üksikasjade vaatamiseks valige programm", + "title": "Pealkiri", + "status_entity": "Olekuüksus", + "icon": "Ikoon", + "active_color": "Aktiivne ikooni värv", + "show_state": "Näita olekut", + "show_program": "Näita programmi", + "show_details": "Näita üksikasju", + "spin_icon": "Pöörlev ikoon (jooksmise ajal)", + "program_entity": "Programmi üksus", + "pct_entity": "Edenemisüksus (valikuline)", + "time_entity": "Ajaüksus (valikuline)", + "display_mode": "Kuvamisrežiim", + "show_time_remaining": "Näita järelejäänud aega", + "show_percentage": "Näita protsenti", + "entity_not_found": "Üksust ei leitud", + "tap_action": "Puudutustoiming", + "hold_action": "Hoidke tegevust", + "double_tap_action": "Topeltpuutetoiming" + }, + "eu": { + "washer_program": "Garbigailuen programa", + "program_placeholder": "Hautatu Programa", + "duration": "Iraupena", + "minutes": "min", + "time_remaining": "Gelditzen den denbora", + "no_prediction": "Iragarpenik ez", + "cycle_in_progress": "Zikloa martxan", + "status": "Egoera", + "progress": "Aurrerapena", + "select_program": "Hautatu programa bat xehetasunak ikusteko", + "title": "Izenburua", + "status_entity": "Egoera Entitatea", + "icon": "Ikonoa", + "active_color": "Ikono aktiboaren kolorea", + "show_state": "Erakutsi egoera", + "show_program": "Erakutsi programa", + "show_details": "Erakutsi xehetasunak", + "spin_icon": "Biratzen ari den ikonoa (exekutatzen ari zaren bitartean)", + "program_entity": "Programa Entitatea", + "pct_entity": "Aurrerapen-entitatea (aukerakoa)", + "time_entity": "Denbora-entitatea (aukerakoa)", + "display_mode": "Bistaratzeko modua", + "show_time_remaining": "Erakutsi falta den denbora", + "show_percentage": "Erakutsi ehunekoa", + "entity_not_found": "Ez da aurkitu entitatea", + "tap_action": "Taparen ekintza", + "hold_action": "Mantendu ekintza", + "double_tap_action": "Tap bikoitzaren ekintza" + }, + "fa": { + "washer_program": "برنامه شستشو", + "program_placeholder": "برنامه را انتخاب کنید", + "duration": "مدت زمان", + "minutes": "دقیقه", + "time_remaining": "زمان باقی مانده", + "no_prediction": "بدون پیش بینی", + "cycle_in_progress": "چرخه در حال انجام است", + "status": "وضعیت", + "progress": "پیشرفت", + "select_program": "یک برنامه را برای دیدن جزئیات انتخاب کنید", + "title": "عنوان", + "status_entity": "موجودیت وضعیت", + "icon": "نماد", + "active_color": "رنگ آیکون فعال", + "show_state": "نمایش وضعیت", + "show_program": "نمایش برنامه", + "show_details": "نمایش جزئیات", + "spin_icon": "نماد چرخان (هنگام اجرا)", + "program_entity": "نهاد برنامه", + "pct_entity": "موجودیت پیشرفت (اختیاری)", + "time_entity": "موجودیت زمان (اختیاری)", + "display_mode": "حالت نمایش", + "show_time_remaining": "نمایش زمان باقی مانده", + "show_percentage": "نمایش درصد", + "entity_not_found": "موجودیت یافت نشد", + "tap_action": "ضربه زدن به Action", + "hold_action": "اقدام", + "double_tap_action": "عملکرد دو ضربه سریع" + }, + "fi": { + "washer_program": "Pesuohjelma", + "program_placeholder": "Valitse Ohjelma", + "duration": "Kesto", + "minutes": "min", + "time_remaining": "Aikaa jäljellä", + "no_prediction": "Ei ennustetta", + "cycle_in_progress": "Kierto käynnissä", + "status": "Tila", + "progress": "Edistyminen", + "select_program": "Valitse ohjelma nähdäksesi tiedot", + "title": "Otsikko", + "status_entity": "Tilayksikkö", + "icon": "Kuvake", + "active_color": "Aktiivinen kuvakkeen väri", + "show_state": "Näytä tila", + "show_program": "Näytä ohjelma", + "show_details": "Näytä tiedot", + "spin_icon": "Pyörivä kuvake (juoksessa)", + "program_entity": "Ohjelmakokonaisuus", + "pct_entity": "Etenemiskokonaisuus (valinnainen)", + "time_entity": "Aikakokonaisuus (valinnainen)", + "display_mode": "Näyttötila", + "show_time_remaining": "Näytä jäljellä oleva aika", + "show_percentage": "Näytä prosenttiosuus", + "entity_not_found": "Kokonaisuutta ei löydy", + "tap_action": "Napauta toimintoa", + "hold_action": "Pidä toimintoa", + "double_tap_action": "Kaksoisnapaustoiminto" + }, + "fr": { + "washer_program": "Programme de laveuse", + "program_placeholder": "Sélectionnez le programme", + "duration": "Durée", + "minutes": "min", + "time_remaining": "Temps restant", + "no_prediction": "Aucune prédiction", + "cycle_in_progress": "Cycle en cours", + "status": "Statut", + "progress": "Progrès", + "select_program": "Sélectionnez un programme pour voir les détails", + "title": "Titre", + "status_entity": "Entité de statut", + "icon": "Icône", + "active_color": "Couleur de l'icône active", + "show_state": "Afficher l'état", + "show_program": "Programme du spectacle", + "show_details": "Afficher les détails", + "spin_icon": "Icône de rotation (pendant l'exécution)", + "program_entity": "Entité du programme", + "pct_entity": "Entité de progression (facultatif)", + "time_entity": "Entité temporelle (facultatif)", + "display_mode": "Mode d'affichage", + "show_time_remaining": "Afficher le temps restant", + "show_percentage": "Afficher le pourcentage", + "entity_not_found": "Entité introuvable", + "tap_action": "Appuyez sur Action", + "hold_action": "Maintenez l'action", + "double_tap_action": "Double action de la touche" + }, + "fy": { + "washer_program": "Washer programma", + "program_placeholder": "Selektearje Programma", + "duration": "Doer", + "minutes": "min", + "time_remaining": "Tiid oerbleaun", + "no_prediction": "Gjin foarsizzing", + "cycle_in_progress": "Cycle yn útfiering", + "status": "Status", + "progress": "Foarútgong", + "select_program": "Selektearje in programma om details te sjen", + "title": "Titel", + "status_entity": "Status Entiteit", + "icon": "Ikoan", + "active_color": "Aktive ikoankleur", + "show_state": "Steat sjen litte", + "show_program": "Programma sjen litte", + "show_details": "Show Details", + "spin_icon": "Spinnend ikoan (by it rinnen)", + "program_entity": "Program Entity", + "pct_entity": "Progress Entity (opsjoneel)", + "time_entity": "Tiid entiteit (opsjoneel)", + "display_mode": "Display Mode", + "show_time_remaining": "Lit de oerbleaune tiid sjen", + "show_percentage": "Persintaazje sjen litte", + "entity_not_found": "Entiteit net fûn", + "tap_action": "Tap Aksje", + "hold_action": "Hâld aksje", + "double_tap_action": "Dûbel tapaksje" + }, + "ga": { + "washer_program": "Clár níocháin", + "program_placeholder": "Roghnaigh Clár", + "duration": "Fad", + "minutes": "nóim", + "time_remaining": "Am fágtha", + "no_prediction": "Gan Tuar", + "cycle_in_progress": "Timthriall ar siúl", + "status": "Stádas", + "progress": "Dul chun cinn", + "select_program": "Roghnaigh clár chun sonraí a fheiceáil", + "title": "Teideal", + "status_entity": "Aonán Stádais", + "icon": "Deilbhín", + "active_color": "Dath Deilbhín Gníomhach", + "show_state": "Taispeáin Stáit", + "show_program": "Clár Taispeáin", + "show_details": "Taispeáin Sonraí", + "spin_icon": "Deilbhín Casadh (Agus tú ag rith)", + "program_entity": "Aonán Cláir", + "pct_entity": "Aonán Dul Chun Cinn (Roghnach)", + "time_entity": "Aonán Ama (Roghnach)", + "display_mode": "Mód Taispeána", + "show_time_remaining": "Taispeáin Am fágtha", + "show_percentage": "Taispeáin Céatadán", + "entity_not_found": "Aonán gan aimsiú", + "tap_action": "Beartaíonn", + "hold_action": "Amharc ar ár liosta iomlán de shuíomhanna", + "double_tap_action": "Gníomh Dúbailte Bearta" + }, + "gl": { + "washer_program": "Programa Lavadora", + "program_placeholder": "Seleccione Programa", + "duration": "Duración", + "minutes": "min", + "time_remaining": "Tempo Restante", + "no_prediction": "Sen predición", + "cycle_in_progress": "Ciclo en curso", + "status": "Estado", + "progress": "Progreso", + "select_program": "Seleccione un programa para ver os detalles", + "title": "Título", + "status_entity": "Entidade de estado", + "icon": "Icona", + "active_color": "Cor da icona activa", + "show_state": "Mostrar estado", + "show_program": "Programa Mostrar", + "show_details": "Mostrar detalles", + "spin_icon": "Icona xirando (mentres corres)", + "program_entity": "Entidade do programa", + "pct_entity": "Entidade de progreso (opcional)", + "time_entity": "Entidade horaria (opcional)", + "display_mode": "Modo de visualización", + "show_time_remaining": "Mostrar o tempo restante", + "show_percentage": "Mostrar porcentaxe", + "entity_not_found": "Non se atopou a entidade", + "tap_action": "Toca Acción", + "hold_action": "Manter acción", + "double_tap_action": "Double Tap Acción" + }, + "gsw": { + "washer_program": "Waschprogramm", + "program_placeholder": "Wählen Sie Programm", + "duration": "Dauer", + "minutes": "min", + "time_remaining": "Verbleibende Zeit", + "no_prediction": "Keine Vorhersage", + "cycle_in_progress": "Zyklus läuft", + "status": "Status", + "progress": "Fortschritt", + "select_program": "Wählen Sie ein Programm aus, um Details anzuzeigen", + "title": "Titel", + "status_entity": "Status-Entität", + "icon": "Symbol", + "active_color": "Aktive Symbolfarbe", + "show_state": "Status anzeigen", + "show_program": "Programm anzeigen", + "show_details": "Details anzeigen", + "spin_icon": "Spinning-Symbol (während des Laufens)", + "program_entity": "Programmeinheit", + "pct_entity": "Fortschrittsentität (optional)", + "time_entity": "Zeiteinheit (optional)", + "display_mode": "Anzeigemodus", + "show_time_remaining": "Verbleibende Zeit anzeigen", + "show_percentage": "Prozentsatz anzeigen", + "entity_not_found": "Entität nicht gefunden", + "tap_action": "Tippen Sie auf Aktion", + "hold_action": "Aktion halten", + "double_tap_action": "Doppeltipp-Aktion" + }, + "he": { + "washer_program": "תוכנית כביסה", + "program_placeholder": "בחר תוכנית", + "duration": "מֶשֶׁך", + "minutes": "דקה", + "time_remaining": "זמן שנותר", + "no_prediction": "אין תחזית", + "cycle_in_progress": "מחזור בעיצומו", + "status": "סטָטוּס", + "progress": "הִתקַדְמוּת", + "select_program": "בחר תוכנית כדי לראות פרטים", + "title": "כּוֹתֶרֶת", + "status_entity": "ישות סטטוס", + "icon": "סמל", + "active_color": "צבע סמל פעיל", + "show_state": "הצג מדינה", + "show_program": "הצג תוכנית", + "show_details": "הצג פרטים", + "spin_icon": "סמל מסתובב (תוך כדי ריצה)", + "program_entity": "ישות תוכנית", + "pct_entity": "ישות התקדמות (אופציונלי)", + "time_entity": "ישות זמן (אופציונלי)", + "display_mode": "מצב תצוגה", + "show_time_remaining": "הצג את הזמן שנותר", + "show_percentage": "הצג אחוז", + "entity_not_found": "הישות לא נמצאה", + "tap_action": "תגית: Action", + "hold_action": "תגית: Hold", + "double_tap_action": "פעולה כפולה" + }, + "hi": { + "washer_program": "वॉशर कार्यक्रम", + "program_placeholder": "प्रोग्राम चुनें", + "duration": "अवधि", + "minutes": "मिन", + "time_remaining": "शेष समय", + "no_prediction": "कोई भविष्यवाणी नहीं", + "cycle_in_progress": "चक्र चल रहा है", + "status": "स्थिति", + "progress": "प्रगति", + "select_program": "विवरण देखने के लिए किसी प्रोग्राम का चयन करें", + "title": "शीर्षक", + "status_entity": "स्थिति इकाई", + "icon": "आइकन", + "active_color": "सक्रिय चिह्न रंग", + "show_state": "राज्य दिखाएँ", + "show_program": "कार्यक्रम दिखाएँ", + "show_details": "प्रदर्शन का विवरण", + "spin_icon": "घूमता हुआ चिह्न (दौड़ते समय)", + "program_entity": "कार्यक्रम इकाई", + "pct_entity": "प्रगति इकाई (वैकल्पिक)", + "time_entity": "समय इकाई (वैकल्पिक)", + "display_mode": "प्रदर्शन मोड", + "show_time_remaining": "शेष समय दिखाएँ", + "show_percentage": "प्रतिशत दिखाएँ", + "entity_not_found": "इकाई नहीं मिली", + "tap_action": "कार्रवाई टैप करें", + "hold_action": "कार्रवाई रोकें", + "double_tap_action": "डबल टैप एक्शन" + }, + "hr": { + "washer_program": "Program za pranje", + "program_placeholder": "Odaberite Program", + "duration": "Trajanje", + "minutes": "min", + "time_remaining": "Preostalo vrijeme", + "no_prediction": "Nema predviđanja", + "cycle_in_progress": "Ciklus u tijeku", + "status": "Status", + "progress": "Napredak", + "select_program": "Odaberite program da biste vidjeli pojedinosti", + "title": "Titula", + "status_entity": "Statusni entitet", + "icon": "Ikona", + "active_color": "Boja aktivne ikone", + "show_state": "Prikaži stanje", + "show_program": "Show Program", + "show_details": "Prikaži pojedinosti", + "spin_icon": "Ikona koja se vrti (dok radi)", + "program_entity": "Programski entitet", + "pct_entity": "Entitet napretka (neobavezno)", + "time_entity": "Entitet vremena (neobavezno)", + "display_mode": "Način prikaza", + "show_time_remaining": "Prikaži preostalo vrijeme", + "show_percentage": "Prikaži postotak", + "entity_not_found": "Entitet nije pronađen", + "tap_action": "Dodirnite Akcija", + "hold_action": "Zadrži akciju", + "double_tap_action": "Akcija dvostrukog dodira" + }, + "hu": { + "washer_program": "Mosó program", + "program_placeholder": "Válassza a Program lehetőséget", + "duration": "Időtartam", + "minutes": "min", + "time_remaining": "Hátralévő idő", + "no_prediction": "Nincs előrejelzés", + "cycle_in_progress": "Ciklus folyamatban", + "status": "Állapot", + "progress": "Előrehalad", + "select_program": "Válasszon ki egy programot a részletek megtekintéséhez", + "title": "Cím", + "status_entity": "Állapot entitás", + "icon": "Ikon", + "active_color": "Aktív Ikon színe", + "show_state": "Állapot megjelenítése", + "show_program": "Program megjelenítése", + "show_details": "Részletek megjelenítése", + "spin_icon": "Pörgő ikon (futás közben)", + "program_entity": "Program entitás", + "pct_entity": "Haladási entitás (opcionális)", + "time_entity": "Idő entitás (opcionális)", + "display_mode": "Kijelző mód", + "show_time_remaining": "Mutasd a hátralévő időt", + "show_percentage": "Százalék megjelenítése", + "entity_not_found": "Az entitás nem található", + "tap_action": "Koppintson a Művelet elemre", + "hold_action": "Tartsa akciót", + "double_tap_action": "Dupla koppintás művelet" + }, + "hy": { + "washer_program": "Լվացքի ծրագիր", + "program_placeholder": "Ընտրեք Ծրագիր", + "duration": "Տևողությունը", + "minutes": "ր", + "time_remaining": "Մնացած ժամանակը", + "no_prediction": "Ոչ մի կանխատեսում", + "cycle_in_progress": "Ցիկլը ընթացքի մեջ է", + "status": "Կարգավիճակ", + "progress": "Առաջընթաց", + "select_program": "Մանրամասները տեսնելու համար ընտրեք ծրագիր", + "title": "Վերնագիր", + "status_entity": "Կարգավիճակի սուբյեկտ", + "icon": "Սրբապատկեր", + "active_color": "Ակտիվ պատկերակի գույնը", + "show_state": "Ցույց տալ վիճակը", + "show_program": "Ցույց տալ ծրագիրը", + "show_details": "Ցույց տալ մանրամասները", + "spin_icon": "Պտտվող պատկերակ (վազքի ընթացքում)", + "program_entity": "Ծրագրի սուբյեկտ", + "pct_entity": "Առաջընթաց կազմակերպություն (ըստ ցանկության)", + "time_entity": "Ժամանակի միավոր (ըստ ցանկության)", + "display_mode": "Ցուցադրման ռեժիմ", + "show_time_remaining": "Ցույց տալ Մնացած ժամանակը", + "show_percentage": "Ցույց տալ տոկոսը", + "entity_not_found": "Կազմակերպությունը չի գտնվել", + "tap_action": "Կտտացրեք Գործողություն", + "hold_action": "Անցկացրեք գործողություն", + "double_tap_action": "Կրկնակի հպեք Գործողություն" + }, + "id": { + "washer_program": "Program Mesin Cuci", + "program_placeholder": "Pilih Program", + "duration": "Lamanya", + "minutes": "menit", + "time_remaining": "Sisa Waktu", + "no_prediction": "Tidak Ada Prediksi", + "cycle_in_progress": "Siklus sedang berlangsung", + "status": "Status", + "progress": "Kemajuan", + "select_program": "Pilih program untuk melihat detailnya", + "title": "Judul", + "status_entity": "Entitas Status", + "icon": "Ikon", + "active_color": "Warna Ikon Aktif", + "show_state": "Tampilkan Negara", + "show_program": "Tampilkan Program", + "show_details": "Tampilkan Detail", + "spin_icon": "Ikon Berputar (Saat berlari)", + "program_entity": "Entitas Program", + "pct_entity": "Entitas Kemajuan (Opsional)", + "time_entity": "Entitas Waktu (Opsional)", + "display_mode": "Modus Tampilan", + "show_time_remaining": "Tampilkan Sisa Waktu", + "show_percentage": "Tampilkan Persentase", + "entity_not_found": "Entitas tidak ditemukan", + "tap_action": "Ketuk Tindakan", + "hold_action": "Tahan Aksi", + "double_tap_action": "Tindakan Ketuk Dua Kali" + }, + "is": { + "washer_program": "Þvottavélaforrit", + "program_placeholder": "Veldu Program", + "duration": "Lengd", + "minutes": "mín", + "time_remaining": "Tími sem eftir er", + "no_prediction": "Engin spá", + "cycle_in_progress": "Hringrás í gangi", + "status": "Staða", + "progress": "Framfarir", + "select_program": "Veldu forrit til að sjá upplýsingar", + "title": "Titill", + "status_entity": "Staða eining", + "icon": "Táknmynd", + "active_color": "Virkur táknlitur", + "show_state": "Sýna ástand", + "show_program": "Sýna dagskrá", + "show_details": "Sýna upplýsingar", + "spin_icon": "Snúningstákn (meðan í gangi)", + "program_entity": "Dagskráreining", + "pct_entity": "Framvindueining (valfrjálst)", + "time_entity": "Tímaeining (valfrjálst)", + "display_mode": "Sýnastilling", + "show_time_remaining": "Sýna tíma sem eftir er", + "show_percentage": "Sýna hlutfall", + "entity_not_found": "Eining fannst ekki", + "tap_action": "Bankaðu á Aðgerð", + "hold_action": "Haltu Action", + "double_tap_action": "Tvíspikkaðu á Action" + }, + "it": { + "washer_program": "Programma Lavatrice", + "program_placeholder": "Seleziona Programma", + "duration": "Durata", + "minutes": "min", + "time_remaining": "Tempo rimanente", + "no_prediction": "Nessuna previsione", + "cycle_in_progress": "Ciclo in corso", + "status": "Stato", + "progress": "Progressi", + "select_program": "Seleziona un programma per vedere i dettagli", + "title": "Titolo", + "status_entity": "Entità di stato", + "icon": "Icona", + "active_color": "Colore icona attiva", + "show_state": "Mostra stato", + "show_program": "Mostra programma", + "show_details": "Mostra dettagli", + "spin_icon": "Icona che gira (durante la corsa)", + "program_entity": "Entità del programma", + "pct_entity": "Entità di avanzamento (facoltativo)", + "time_entity": "Entità temporale (facoltativo)", + "display_mode": "Modalità di visualizzazione", + "show_time_remaining": "Mostra tempo rimanente", + "show_percentage": "Mostra percentuale", + "entity_not_found": "Entità non trovata", + "tap_action": "Tocca Azione", + "hold_action": "Mantieni Azione", + "double_tap_action": "Azione doppio tocco" + }, + "ja": { + "washer_program": "ウォッシャープログラム", + "program_placeholder": "プログラムの選択", + "duration": "間隔", + "minutes": "分", + "time_remaining": "残り時間", + "no_prediction": "予測なし", + "cycle_in_progress": "進行中のサイクル", + "status": "状態", + "progress": "進捗", + "select_program": "プログラムを選択して詳細を表示します", + "title": "タイトル", + "status_entity": "ステータスエンティティ", + "icon": "アイコン", + "active_color": "アクティブなアイコンの色", + "show_state": "状態を表示", + "show_program": "ショープログラム", + "show_details": "詳細を表示", + "spin_icon": "回転アイコン(走行中)", + "program_entity": "プログラムエンティティ", + "pct_entity": "進行状況エンティティ (オプション)", + "time_entity": "時間エンティティ (オプション)", + "display_mode": "表示モード", + "show_time_remaining": "残りの上映時間", + "show_percentage": "パーセンテージを表示", + "entity_not_found": "エンティティが見つかりません", + "tap_action": "タップアクション", + "hold_action": "ホールドアクション", + "double_tap_action": "ダブルタップアクション" + }, + "ka": { + "washer_program": "სარეცხი პროგრამა", + "program_placeholder": "აირჩიეთ პროგრამა", + "duration": "ხანგრძლივობა", + "minutes": "წთ", + "time_remaining": "დარჩენილი დრო", + "no_prediction": "არანაირი პროგნოზი", + "cycle_in_progress": "ციკლი მიმდინარეობს", + "status": "სტატუსი", + "progress": "პროგრესი", + "select_program": "აირჩიეთ პროგრამა დეტალების სანახავად", + "title": "სათაური", + "status_entity": "სტატუსის ერთეული", + "icon": "ხატულა", + "active_color": "აქტიური ხატის ფერი", + "show_state": "სახელმწიფოს ჩვენება", + "show_program": "პროგრამის ჩვენება", + "show_details": "დეტალების ჩვენება", + "spin_icon": "დაწნული ხატულა (გაშვებისას)", + "program_entity": "პროგრამის სუბიექტი", + "pct_entity": "პროგრესული ერთეული (არასავალდებულო)", + "time_entity": "დროის ერთეული (არასავალდებულო)", + "display_mode": "ჩვენების რეჟიმი", + "show_time_remaining": "დარჩენილი დროის ჩვენება", + "show_percentage": "პროცენტის ჩვენება", + "entity_not_found": "ერთეული ვერ მოიძებნა", + "tap_action": "შეეხეთ მოქმედებას", + "hold_action": "გააჩერეთ მოქმედება", + "double_tap_action": "ორმაგი შეხების მოქმედება" + }, + "ko": { + "washer_program": "세탁기 프로그램", + "program_placeholder": "프로그램 선택", + "duration": "지속", + "minutes": "분", + "time_remaining": "남은 시간", + "no_prediction": "예측 없음", + "cycle_in_progress": "사이클 진행 중", + "status": "상태", + "progress": "진전", + "select_program": "세부정보를 보려면 프로그램을 선택하세요.", + "title": "제목", + "status_entity": "상태 엔터티", + "icon": "상", + "active_color": "활성 아이콘 색상", + "show_state": "상태 표시", + "show_program": "쇼 프로그램", + "show_details": "세부정보 표시", + "spin_icon": "회전 아이콘(실행 중)", + "program_entity": "프로그램 엔터티", + "pct_entity": "진행 엔터티(선택 사항)", + "time_entity": "시간 엔터티(선택 사항)", + "display_mode": "디스플레이 모드", + "show_time_remaining": "남은 시간 표시", + "show_percentage": "백분율 표시", + "entity_not_found": "엔터티를 찾을 수 없습니다.", + "tap_action": "탭 동작", + "hold_action": "보류 조치", + "double_tap_action": "더블 탭 액션" + }, + "lb": { + "washer_program": "Wäschmaschinn Programm", + "program_placeholder": "Wielt Programm", + "duration": "Dauer", + "minutes": "min", + "time_remaining": "Zäit Rescht", + "no_prediction": "Keng Prognose", + "cycle_in_progress": "Zyklus amgaang", + "status": "Status", + "progress": "Fortschrëtt", + "select_program": "Wielt e Programm fir Detailer ze gesinn", + "title": "Titel", + "status_entity": "Status Entitéit", + "icon": "Ikon", + "active_color": "Aktiv Ikon Faarf", + "show_state": "Staat weisen", + "show_program": "Show Programm", + "show_details": "Show Detailer", + "spin_icon": "Spinning Ikon (Wärend Lafen)", + "program_entity": "Programm Entitéit", + "pct_entity": "Progress Entity (fakultativ)", + "time_entity": "Zäit Entitéit (optional)", + "display_mode": "Display Modus", + "show_time_remaining": "Show Rescht Zäit", + "show_percentage": "Show Prozentsaz", + "entity_not_found": "Entitéit net fonnt", + "tap_action": "Tippen op Aktioun", + "hold_action": "Halt Aktioun", + "double_tap_action": "Double Tap Action" + }, + "lt": { + "washer_program": "Skalbimo programa", + "program_placeholder": "Pasirinkite Programa", + "duration": "Trukmė", + "minutes": "min", + "time_remaining": "Likęs laikas", + "no_prediction": "Jokios prognozės", + "cycle_in_progress": "Vyksta ciklas", + "status": "Būsena", + "progress": "Pažanga", + "select_program": "Norėdami pamatyti išsamią informaciją, pasirinkite programą", + "title": "Pavadinimas", + "status_entity": "Būsenos subjektas", + "icon": "Piktograma", + "active_color": "Aktyvios piktogramos spalva", + "show_state": "Rodyti būseną", + "show_program": "Rodyti programą", + "show_details": "Rodyti išsamią informaciją", + "spin_icon": "Sukimo piktograma (bėgant)", + "program_entity": "Programos subjektas", + "pct_entity": "Pažangos subjektas (neprivaloma)", + "time_entity": "Laiko objektas (neprivaloma)", + "display_mode": "Ekrano režimas", + "show_time_remaining": "Rodyti likusį laiką", + "show_percentage": "Rodyti procentą", + "entity_not_found": "Subjektas nerastas", + "tap_action": "Bakstelėkite Veiksmas", + "hold_action": "Laikyti veiksmą", + "double_tap_action": "Dukart bakstelėkite veiksmas" + }, + "lv": { + "washer_program": "Mazgāšanas programma", + "program_placeholder": "Atlasiet Programma", + "duration": "Ilgums", + "minutes": "min", + "time_remaining": "Atlikušais laiks", + "no_prediction": "Nav prognožu", + "cycle_in_progress": "Notiek cikls", + "status": "Statuss", + "progress": "Progress", + "select_program": "Izvēlieties programmu, lai skatītu detalizētu informāciju", + "title": "Nosaukums", + "status_entity": "Statusa entītija", + "icon": "Ikona", + "active_color": "Aktīvās ikonas krāsa", + "show_state": "Rādīt stāvokli", + "show_program": "Rādīt programmu", + "show_details": "Rādīt detaļas", + "spin_icon": "Griešanās ikona (skrienot)", + "program_entity": "Programmas entītija", + "pct_entity": "Progresa entītija (neobligāti)", + "time_entity": "Laika entītija (neobligāti)", + "display_mode": "Displeja režīms", + "show_time_remaining": "Rādīt atlikušo laiku", + "show_percentage": "Rādīt procentus", + "entity_not_found": "Entītija nav atrasta", + "tap_action": "Pieskarieties darbībai", + "hold_action": "Aizturēt darbību", + "double_tap_action": "Dubultskāriena darbība" + }, + "mk": { + "washer_program": "Програма за перење", + "program_placeholder": "Изберете Програма", + "duration": "Времетраење", + "minutes": "мин", + "time_remaining": "Преостанато време", + "no_prediction": "Без предвидување", + "cycle_in_progress": "Циклус во тек", + "status": "Статус", + "progress": "Напредок", + "select_program": "Изберете програма за да видите детали", + "title": "Наслов", + "status_entity": "Статусен ентитет", + "icon": "Икона", + "active_color": "Активна боја на иконата", + "show_state": "Прикажи држава", + "show_program": "Прикажи програма", + "show_details": "Прикажи детали", + "spin_icon": "Икона за вртење (додека работи)", + "program_entity": "Програмски ентитет", + "pct_entity": "Ентитет за напредок (изборно)", + "time_entity": "Временски ентитет (изборно)", + "display_mode": "Режим на прикажување", + "show_time_remaining": "Прикажи преостанатото време", + "show_percentage": "Прикажи процент", + "entity_not_found": "Субјектот не е пронајден", + "tap_action": "Допрете Акција", + "hold_action": "Држете акција", + "double_tap_action": "Акција со двоен допир" + }, + "ml": { + "washer_program": "വാഷർ പ്രോഗ്രാം", + "program_placeholder": "പ്രോഗ്രാം തിരഞ്ഞെടുക്കുക", + "duration": "ദൈർഘ്യം", + "minutes": "മിനിറ്റ്", + "time_remaining": "ശേഷിക്കുന്ന സമയം", + "no_prediction": "പ്രവചനമില്ല", + "cycle_in_progress": "സൈക്കിൾ പുരോഗമിക്കുന്നു", + "status": "നില", + "progress": "പുരോഗതി", + "select_program": "വിശദാംശങ്ങൾ കാണുന്നതിന് ഒരു പ്രോഗ്രാം തിരഞ്ഞെടുക്കുക", + "title": "തലക്കെട്ട്", + "status_entity": "സ്റ്റാറ്റസ് എൻ്റിറ്റി", + "icon": "ഐക്കൺ", + "active_color": "സജീവ ഐക്കൺ നിറം", + "show_state": "സംസ്ഥാനം കാണിക്കുക", + "show_program": "പ്രോഗ്രാം കാണിക്കുക", + "show_details": "വിശദാംശങ്ങൾ കാണിക്കുക", + "spin_icon": "സ്പിന്നിംഗ് ഐക്കൺ (ഓടുമ്പോൾ)", + "program_entity": "പ്രോഗ്രാം എൻ്റിറ്റി", + "pct_entity": "പ്രോഗ്രസ് എൻ്റിറ്റി (ഓപ്ഷണൽ)", + "time_entity": "സമയ എൻ്റിറ്റി (ഓപ്ഷണൽ)", + "display_mode": "ഡിസ്പ്ലേ മോഡ്", + "show_time_remaining": "ശേഷിക്കുന്ന സമയം കാണിക്കുക", + "show_percentage": "ശതമാനം കാണിക്കുക", + "entity_not_found": "എൻ്റിറ്റി കണ്ടെത്തിയില്ല", + "tap_action": "ആക്ഷൻ ടാപ്പ് ചെയ്യുക", + "hold_action": "ഹോൾഡ് ആക്ഷൻ", + "double_tap_action": "ഡബിൾ ടാപ്പ് ആക്ഷൻ" + }, + "nb": { + "washer_program": "Vaskeprogram", + "program_placeholder": "Velg Program", + "duration": "Varighet", + "minutes": "min", + "time_remaining": "Gjenstående tid", + "no_prediction": "Ingen prediksjon", + "cycle_in_progress": "Syklus pågår", + "status": "Status", + "progress": "Framgang", + "select_program": "Velg et program for å se detaljer", + "title": "Tittel", + "status_entity": "Status Entitet", + "icon": "Ikon", + "active_color": "Aktiv ikonfarge", + "show_state": "Vis tilstand", + "show_program": "Vis program", + "show_details": "Vis detaljer", + "spin_icon": "Spinning-ikon (mens du løper)", + "program_entity": "Program Entitet", + "pct_entity": "Fremdriftsenhet (valgfritt)", + "time_entity": "Tidsenhet (valgfritt)", + "display_mode": "Visningsmodus", + "show_time_remaining": "Vis gjenværende tid", + "show_percentage": "Vis prosentandel", + "entity_not_found": "Enheten ble ikke funnet", + "tap_action": "Trykk på Handling", + "hold_action": "Hold handling", + "double_tap_action": "Dobbelttrykk på handling" + }, + "nl": { + "washer_program": "Wasprogramma", + "program_placeholder": "Selecteer Programma", + "duration": "Duur", + "minutes": "min", + "time_remaining": "Resterende tijd", + "no_prediction": "Geen voorspelling", + "cycle_in_progress": "Cyclus in uitvoering", + "status": "Status", + "progress": "Voortgang", + "select_program": "Selecteer een programma om details te bekijken", + "title": "Titel", + "status_entity": "Statusentiteit", + "icon": "Icon", + "active_color": "Actieve pictogramkleur", + "show_state": "Toon staat", + "show_program": "Programma weergeven", + "show_details": "Details tonen", + "spin_icon": "Draaiend pictogram (tijdens hardlopen)", + "program_entity": "Programma-entiteit", + "pct_entity": "Voortgangsentiteit (optioneel)", + "time_entity": "Tijdsentiteit (optioneel)", + "display_mode": "Weergavemodus", + "show_time_remaining": "Resterende tijd weergeven", + "show_percentage": "Percentage weergeven", + "entity_not_found": "Entiteit niet gevonden", + "tap_action": "Tik op Actie", + "hold_action": "Actie vasthouden", + "double_tap_action": "Dubbeltikactie" + }, + "nn": { + "washer_program": "Washer Program", + "program_placeholder": "Select Program", + "duration": "Duration", + "minutes": "min", + "time_remaining": "Time Remaining", + "no_prediction": "No Prediction", + "cycle_in_progress": "Cycle in progress", + "status": "Status", + "progress": "Progress", + "select_program": "Select a program to see details", + "title": "Title", + "status_entity": "Status Entity", + "icon": "Icon", + "active_color": "Active Icon Color", + "show_state": "Show State", + "show_program": "Show Program", + "show_details": "Show Details", + "spin_icon": "Spinning Icon (While running)", + "program_entity": "Program Entity", + "pct_entity": "Progress Entity (Optional)", + "time_entity": "Time Entity (Optional)", + "display_mode": "Display Mode", + "show_time_remaining": "Show Time Remaining", + "show_percentage": "Show Percentage", + "entity_not_found": "Entity not found" + }, + "pl": { + "washer_program": "Program prania", + "program_placeholder": "Wybierz Program", + "duration": "Czas trwania", + "minutes": "min", + "time_remaining": "Pozostały czas", + "no_prediction": "Brak przewidywania", + "cycle_in_progress": "Cykl w toku", + "status": "Status", + "progress": "Postęp", + "select_program": "Wybierz program, aby zobaczyć szczegóły", + "title": "Tytuł", + "status_entity": "Jednostka statusowa", + "icon": "Ikona", + "active_color": "Aktywny kolor ikony", + "show_state": "Pokaż stan", + "show_program": "Pokaż program", + "show_details": "Pokaż szczegóły", + "spin_icon": "Ikona obracania się (podczas biegu)", + "program_entity": "Jednostka programu", + "pct_entity": "Jednostka postępu (opcjonalnie)", + "time_entity": "Jednostka czasu (opcjonalnie)", + "display_mode": "Tryb wyświetlania", + "show_time_remaining": "Pokaż pozostały czas", + "show_percentage": "Pokaż procent", + "entity_not_found": "Nie znaleziono elementu", + "tap_action": "Kliknij Akcja", + "hold_action": "Wstrzymaj akcję", + "double_tap_action": "Akcja podwójnego dotknięcia" + }, + "pt": { + "washer_program": "Programa de lavadora", + "program_placeholder": "Selecione o programa", + "duration": "Duração", + "minutes": "min", + "time_remaining": "Tempo restante", + "no_prediction": "Sem previsão", + "cycle_in_progress": "Ciclo em andamento", + "status": "Status", + "progress": "Progresso", + "select_program": "Selecione um programa para ver detalhes", + "title": "Título", + "status_entity": "Entidade de status", + "icon": "Ícone", + "active_color": "Cor do ícone ativo", + "show_state": "Mostrar estado", + "show_program": "Mostrar programa", + "show_details": "Mostrar detalhes", + "spin_icon": "Ícone giratório (durante a execução)", + "program_entity": "Entidade do Programa", + "pct_entity": "Entidade de progresso (opcional)", + "time_entity": "Entidade de tempo (opcional)", + "display_mode": "Modo de exibição", + "show_time_remaining": "Mostrar tempo restante", + "show_percentage": "Mostrar porcentagem", + "entity_not_found": "Entidade não encontrada", + "tap_action": "Toque em Ação", + "hold_action": "Manter ação", + "double_tap_action": "Ação de toque duplo" + }, + "pt-BR": { + "washer_program": "Programa de lavadora", + "program_placeholder": "Selecione o programa", + "duration": "Duração", + "minutes": "min", + "time_remaining": "Tempo restante", + "no_prediction": "Sem previsão", + "cycle_in_progress": "Ciclo em andamento", + "status": "Status", + "progress": "Progresso", + "select_program": "Selecione um programa para ver detalhes", + "title": "Título", + "status_entity": "Entidade de status", + "icon": "Ícone", + "active_color": "Cor do ícone ativo", + "show_state": "Mostrar estado", + "show_program": "Mostrar programa", + "show_details": "Mostrar detalhes", + "spin_icon": "Ícone giratório (durante a execução)", + "program_entity": "Entidade do Programa", + "pct_entity": "Entidade de progresso (opcional)", + "time_entity": "Entidade de tempo (opcional)", + "display_mode": "Modo de exibição", + "show_time_remaining": "Mostrar tempo restante", + "show_percentage": "Mostrar porcentagem", + "entity_not_found": "Entidade não encontrada", + "tap_action": "Toque em Ação", + "hold_action": "Manter ação", + "double_tap_action": "Ação de toque duplo" + }, + "ro": { + "washer_program": "Program de spălat", + "program_placeholder": "Selectați Program", + "duration": "Durată", + "minutes": "min", + "time_remaining": "Timp rămas", + "no_prediction": "Fără predicție", + "cycle_in_progress": "Ciclu în curs", + "status": "Stare", + "progress": "Progres", + "select_program": "Selectați un program pentru a vedea detalii", + "title": "Titlu", + "status_entity": "Entitate de stare", + "icon": "Pictogramă", + "active_color": "Culoarea pictogramei active", + "show_state": "Arată stare", + "show_program": "Arată programul", + "show_details": "Afișați detalii", + "spin_icon": "Pictogramă care se învârte (în timpul alergării)", + "program_entity": "Entitatea de program", + "pct_entity": "Entitate de progres (opțional)", + "time_entity": "Entitate oră (Opțional)", + "display_mode": "Modul de afișare", + "show_time_remaining": "Arată timpul rămas", + "show_percentage": "Arată procentul", + "entity_not_found": "Entitatea nu a fost găsită", + "tap_action": "Atingeți Acțiune", + "hold_action": "Țineți Acțiune", + "double_tap_action": "Atingeți de două ori Acțiune" + }, + "ru": { + "washer_program": "Программа стирки", + "program_placeholder": "Выберите программу", + "duration": "Продолжительность", + "minutes": "мин", + "time_remaining": "Оставшееся время", + "no_prediction": "Нет прогноза", + "cycle_in_progress": "Цикл в процессе", + "status": "Статус", + "progress": "Прогресс", + "select_program": "Выберите программу, чтобы увидеть подробности", + "title": "Заголовок", + "status_entity": "Статус объекта", + "icon": "Икона", + "active_color": "Цвет активного значка", + "show_state": "Показать состояние", + "show_program": "Шоу-программа", + "show_details": "Показать детали", + "spin_icon": "Вращающийся значок (во время бега)", + "program_entity": "Программный объект", + "pct_entity": "Сущность прогресса (необязательно)", + "time_entity": "Сущность времени (необязательно)", + "display_mode": "Режим отображения", + "show_time_remaining": "Показать оставшееся время", + "show_percentage": "Показать процент", + "entity_not_found": "Объект не найден", + "tap_action": "Нажмите «Действие».", + "hold_action": "Удерживать действие", + "double_tap_action": "Двойное нажатие" + }, + "sk": { + "washer_program": "Program práčky", + "program_placeholder": "Vyberte položku Program", + "duration": "Trvanie", + "minutes": "min", + "time_remaining": "Zostávajúci čas", + "no_prediction": "Žiadna predpoveď", + "cycle_in_progress": "Prebiehajúci cyklus", + "status": "Stav", + "progress": "Pokrok", + "select_program": "Ak chcete zobraziť podrobnosti, vyberte program", + "title": "Názov", + "status_entity": "Stavová entita", + "icon": "Ikona", + "active_color": "Farba aktívnej ikony", + "show_state": "Zobraziť stav", + "show_program": "Zobraziť program", + "show_details": "Zobraziť podrobnosti", + "spin_icon": "Ikona otáčania (pri behu)", + "program_entity": "Programová entita", + "pct_entity": "Entita pokroku (voliteľné)", + "time_entity": "Časová entita (voliteľné)", + "display_mode": "Režim zobrazenia", + "show_time_remaining": "Zobraziť zostávajúci čas", + "show_percentage": "Zobraziť percento", + "entity_not_found": "Entita sa nenašla", + "tap_action": "Klepnite na položku Akcia", + "hold_action": "Hold Action", + "double_tap_action": "Akcia dvojitého klepnutia" + }, + "sl": { + "washer_program": "Program za pranje", + "program_placeholder": "Izberite Program", + "duration": "Trajanje", + "minutes": "min", + "time_remaining": "Preostali čas", + "no_prediction": "Brez napovedi", + "cycle_in_progress": "Cikel v teku", + "status": "Stanje", + "progress": "Napredek", + "select_program": "Za ogled podrobnosti izberite program", + "title": "Naslov", + "status_entity": "Statusna entiteta", + "icon": "Ikona", + "active_color": "Barva aktivne ikone", + "show_state": "Prikaži stanje", + "show_program": "Show Program", + "show_details": "Pokaži podrobnosti", + "spin_icon": "Vrteča se ikona (med tekom)", + "program_entity": "Programska entiteta", + "pct_entity": "Entiteta napredka (neobvezno)", + "time_entity": "Časovna entiteta (neobvezno)", + "display_mode": "Način prikaza", + "show_time_remaining": "Prikaži preostali čas", + "show_percentage": "Pokaži odstotek", + "entity_not_found": "Entiteta ni najdena", + "tap_action": "Tapnite Dejanje", + "hold_action": "Zadrži akcijo", + "double_tap_action": "Dejanje dvojnega dotika" + }, + "sq": { + "washer_program": "Programi i larës", + "program_placeholder": "Zgjidhni Programin", + "duration": "Kohëzgjatja", + "minutes": "min", + "time_remaining": "Koha e mbetur", + "no_prediction": "Asnjë Parashikim", + "cycle_in_progress": "Cikli në vazhdim", + "status": "Statusi", + "progress": "Përparim", + "select_program": "Zgjidhni një program për të parë detajet", + "title": "Titulli", + "status_entity": "Entiteti i statusit", + "icon": "Ikona", + "active_color": "Ngjyra e ikonës aktive", + "show_state": "Trego shtetin", + "show_program": "Shfaq programin", + "show_details": "Shfaq Detajet", + "spin_icon": "Ikona rrotulluese (Gjatë funksionimit)", + "program_entity": "Subjekti i programit", + "pct_entity": "Entiteti i progresit (opsionale)", + "time_entity": "Entiteti i kohës (opsionale)", + "display_mode": "Modaliteti i shfaqjes", + "show_time_remaining": "Shfaq kohën e mbetur", + "show_percentage": "Shfaq përqindjen", + "entity_not_found": "Subjekti nuk u gjet", + "tap_action": "Prekni Veprim", + "hold_action": "Mbajeni veprimin", + "double_tap_action": "Veprimi i prekjes së dyfishtë" + }, + "sr": { + "washer_program": "Washer Program", + "program_placeholder": "Select Program", + "duration": "Duration", + "minutes": "min", + "time_remaining": "Time Remaining", + "no_prediction": "No Prediction", + "cycle_in_progress": "Cycle in progress", + "status": "Status", + "progress": "Progress", + "select_program": "Select a program to see details", + "title": "Title", + "status_entity": "Status Entity", + "icon": "Icon", + "active_color": "Active Icon Color", + "show_state": "Show State", + "show_program": "Show Program", + "show_details": "Show Details", + "spin_icon": "Spinning Icon (While running)", + "program_entity": "Program Entity", + "pct_entity": "Progress Entity (Optional)", + "time_entity": "Time Entity (Optional)", + "display_mode": "Display Mode", + "show_time_remaining": "Show Time Remaining", + "show_percentage": "Show Percentage", + "entity_not_found": "Entity not found" + }, + "sr-Latn": { + "washer_program": "Програм за прање", + "program_placeholder": "Изаберите Програм", + "duration": "Трајање", + "minutes": "мин", + "time_remaining": "Преостало време", + "no_prediction": "Без предвиђања", + "cycle_in_progress": "Циклус је у току", + "status": "Статус", + "progress": "Напредак", + "select_program": "Изаберите програм да бисте видели детаље", + "title": "Наслов", + "status_entity": "Статус Ентитета", + "icon": "Икона", + "active_color": "Активна боја иконе", + "show_state": "Прикажи државу", + "show_program": "Схов Програм", + "show_details": "Прикажи детаље", + "spin_icon": "Икона која се окреће (док трчи)", + "program_entity": "Програм Ентите", + "pct_entity": "Ентитет напретка (опционо)", + "time_entity": "Временски ентитет (опционо)", + "display_mode": "Режим приказа", + "show_time_remaining": "Прикажи преостало време", + "show_percentage": "Прикажи проценат", + "entity_not_found": "Ентитет није пронађен", + "tap_action": "Додирните Акција", + "hold_action": "Задржите акцију", + "double_tap_action": "Радња двоструког додира" + }, + "sv": { + "washer_program": "Tvättprogram", + "program_placeholder": "Välj Program", + "duration": "Varaktighet", + "minutes": "min", + "time_remaining": "Återstående tid", + "no_prediction": "Ingen förutsägelse", + "cycle_in_progress": "Cykel pågår", + "status": "Status", + "progress": "Framsteg", + "select_program": "Välj ett program för att se detaljer", + "title": "Titel", + "status_entity": "Status Entitet", + "icon": "Ikon", + "active_color": "Aktiv ikonfärg", + "show_state": "Visa tillstånd", + "show_program": "Visa program", + "show_details": "Visa detaljer", + "spin_icon": "Spinning-ikon (medan du springer)", + "program_entity": "Program Entitet", + "pct_entity": "Progress Entity (valfritt)", + "time_entity": "Tidsenhet (valfritt)", + "display_mode": "Visningsläge", + "show_time_remaining": "Visa återstående tid", + "show_percentage": "Visa procent", + "entity_not_found": "Enheten hittades inte", + "tap_action": "Tryck på Åtgärd", + "hold_action": "Håll Action", + "double_tap_action": "Dubbeltrycksåtgärd" + }, + "ta": { + "washer_program": "வாஷர் திட்டம்", + "program_placeholder": "நிரலைத் தேர்ந்தெடுக்கவும்", + "duration": "கால அளவு", + "minutes": "நிமிடம்", + "time_remaining": "மீதமுள்ள நேரம்", + "no_prediction": "கணிப்பு இல்லை", + "cycle_in_progress": "சுழற்சி நடந்து கொண்டிருக்கிறது", + "status": "நிலை", + "progress": "முன்னேற்றம்", + "select_program": "விவரங்களைப் பார்க்க ஒரு நிரலைத் தேர்ந்தெடுக்கவும்", + "title": "தலைப்பு", + "status_entity": "நிலை நிறுவனம்", + "icon": "ஐகான்", + "active_color": "செயலில் உள்ள ஐகான் நிறம்", + "show_state": "மாநிலத்தைக் காட்டு", + "show_program": "நிகழ்ச்சி நிரல்", + "show_details": "விவரங்களைக் காட்டு", + "spin_icon": "ஸ்பின்னிங் ஐகான் (இயங்கும் போது)", + "program_entity": "நிரல் நிறுவனம்", + "pct_entity": "முன்னேற்ற நிறுவனம் (விரும்பினால்)", + "time_entity": "நேர பொருள் (விரும்பினால்)", + "display_mode": "காட்சி முறை", + "show_time_remaining": "மீதமுள்ள நேரத்தைக் காட்டு", + "show_percentage": "சதவீதத்தைக் காட்டு", + "entity_not_found": "பொருள் கிடைக்கவில்லை", + "tap_action": "செயலைத் தட்டவும்", + "hold_action": "நடவடிக்கையை பிடி", + "double_tap_action": "இருமுறை தட்டுதல் செயல்" + }, + "te": { + "washer_program": "వాషర్ ప్రోగ్రామ్", + "program_placeholder": "ప్రోగ్రామ్‌ని ఎంచుకోండి", + "duration": "వ్యవధి", + "minutes": "నిమి", + "time_remaining": "సమయం మిగిలి ఉంది", + "no_prediction": "అంచనా లేదు", + "cycle_in_progress": "చక్రం పురోగతిలో ఉంది", + "status": "స్థితి", + "progress": "పురోగతి", + "select_program": "వివరాలను చూడటానికి ప్రోగ్రామ్‌ను ఎంచుకోండి", + "title": "శీర్షిక", + "status_entity": "స్టేటస్ ఎంటిటీ", + "icon": "చిహ్నం", + "active_color": "సక్రియ చిహ్నం రంగు", + "show_state": "రాష్ట్రాన్ని చూపించు", + "show_program": "కార్యక్రమం చూపించు", + "show_details": "వివరాలను చూపించు", + "spin_icon": "స్పిన్నింగ్ ఐకాన్ (నడుస్తున్నప్పుడు)", + "program_entity": "ప్రోగ్రామ్ ఎంటిటీ", + "pct_entity": "ప్రోగ్రెస్ ఎంటిటీ (ఐచ్ఛికం)", + "time_entity": "టైమ్ ఎంటిటీ (ఐచ్ఛికం)", + "display_mode": "ప్రదర్శన మోడ్", + "show_time_remaining": "మిగిలిన సమయాన్ని చూపించు", + "show_percentage": "శాతాన్ని చూపించు", + "entity_not_found": "ఎంటిటీ కనుగొనబడలేదు", + "tap_action": "చర్యను నొక్కండి", + "hold_action": "చర్యను పట్టుకోండి", + "double_tap_action": "రెండుసార్లు నొక్కండి చర్య" + }, + "th": { + "washer_program": "โปรแกรมเครื่องซักผ้า", + "program_placeholder": "เลือกโปรแกรม", + "duration": "ระยะเวลา", + "minutes": "นาที", + "time_remaining": "เวลาที่เหลืออยู่", + "no_prediction": "ไม่มีการคาดการณ์", + "cycle_in_progress": "อยู่ระหว่างดำเนินการ", + "status": "สถานะ", + "progress": "ความคืบหน้า", + "select_program": "เลือกโปรแกรมเพื่อดูรายละเอียด", + "title": "ชื่อ", + "status_entity": "เอนทิตีสถานะ", + "icon": "ไอคอน", + "active_color": "สีไอคอนที่ใช้งานอยู่", + "show_state": "แสดงสถานะ", + "show_program": "โปรแกรมโชว์", + "show_details": "แสดงรายละเอียด", + "spin_icon": "ไอคอนหมุน (ขณะวิ่ง)", + "program_entity": "เอนทิตีของโปรแกรม", + "pct_entity": "เอนทิตีความคืบหน้า (ไม่บังคับ)", + "time_entity": "เอนทิตีเวลา (ไม่บังคับ)", + "display_mode": "โหมดการแสดงผล", + "show_time_remaining": "แสดงเวลาที่เหลืออยู่", + "show_percentage": "แสดงเปอร์เซ็นต์", + "entity_not_found": "ไม่พบเอนทิตี", + "tap_action": "แตะการดำเนินการ", + "hold_action": "ระงับการดำเนินการ", + "double_tap_action": "การกระทำแตะสองครั้ง" + }, + "tr": { + "washer_program": "Yıkama Programı", + "program_placeholder": "Program Seç", + "duration": "Süre", + "minutes": "dk.", + "time_remaining": "Kalan Süre", + "no_prediction": "Tahmin Yok", + "cycle_in_progress": "Döngü devam ediyor", + "status": "Durum", + "progress": "İlerlemek", + "select_program": "Ayrıntıları görmek için bir program seçin", + "title": "Başlık", + "status_entity": "Durum Varlığı", + "icon": "Simge", + "active_color": "Etkin Simge Rengi", + "show_state": "Durumu Göster", + "show_program": "Programı Göster", + "show_details": "Ayrıntıları Göster", + "spin_icon": "Dönen Simge (Koşarken)", + "program_entity": "Program Varlığı", + "pct_entity": "İlerleme Varlığı (İsteğe bağlı)", + "time_entity": "Zaman Varlığı (İsteğe Bağlı)", + "display_mode": "Ekran Modu", + "show_time_remaining": "Kalan Süreyi Göster", + "show_percentage": "Yüzdeyi Göster", + "entity_not_found": "Varlık bulunamadı", + "tap_action": "Eylem'e dokunun", + "hold_action": "Eylemi Beklet", + "double_tap_action": "Çift Dokunma Eylemi" + }, + "uk": { + "washer_program": "Програма прання", + "program_placeholder": "Виберіть програму", + "duration": "Тривалість", + "minutes": "хв", + "time_remaining": "Час, що залишився", + "no_prediction": "Без передбачення", + "cycle_in_progress": "Цикл триває", + "status": "Статус", + "progress": "Прогрес", + "select_program": "Виберіть програму, щоб переглянути деталі", + "title": "Назва", + "status_entity": "Status Entity", + "icon": "значок", + "active_color": "Активний колір значка", + "show_state": "Показати стан", + "show_program": "Шоу програма", + "show_details": "Показати деталі", + "spin_icon": "Піктограма обертання (під час бігу)", + "program_entity": "Програмна сутність", + "pct_entity": "Сутність прогресу (необов'язково)", + "time_entity": "Сутність часу (необов’язково)", + "display_mode": "Режим відображення", + "show_time_remaining": "Показати час, що залишився", + "show_percentage": "Показати відсоток", + "entity_not_found": "Об'єкт не знайдено", + "tap_action": "Натисніть Дія", + "hold_action": "Дія утримання", + "double_tap_action": "Подвійне торкання" + }, + "ur": { + "washer_program": "واشر پروگرام", + "program_placeholder": "پروگرام منتخب کریں۔", + "duration": "دورانیہ", + "minutes": "منٹ", + "time_remaining": "باقی وقت", + "no_prediction": "کوئی پیشین گوئی نہیں۔", + "cycle_in_progress": "سائیکل جاری ہے۔", + "status": "حیثیت", + "progress": "پیش رفت", + "select_program": "تفصیلات دیکھنے کے لیے ایک پروگرام منتخب کریں۔", + "title": "عنوان", + "status_entity": "اسٹیٹس ہستی", + "icon": "آئیکن", + "active_color": "ایکٹو آئیکن کا رنگ", + "show_state": "ریاست دکھائیں۔", + "show_program": "پروگرام دکھائیں۔", + "show_details": "تفصیلات دکھائیں۔", + "spin_icon": "گھومنے کا آئیکن (چلتے وقت)", + "program_entity": "پروگرام ہستی", + "pct_entity": "پیش رفت ہستی (اختیاری)", + "time_entity": "وقت کی ہستی (اختیاری)", + "display_mode": "ڈسپلے موڈ", + "show_time_remaining": "باقی وقت دکھائیں۔", + "show_percentage": "فیصد دکھائیں۔", + "entity_not_found": "ہستی نہیں ملی", + "tap_action": "ایکشن کو تھپتھپائیں۔", + "hold_action": "ایکشن پکڑو", + "double_tap_action": "ڈبل تھپتھپائیں کارروائی" + }, + "vi": { + "washer_program": "Chương trình máy giặt", + "program_placeholder": "Chọn chương trình", + "duration": "Khoảng thời gian", + "minutes": "phút", + "time_remaining": "Thời gian còn lại", + "no_prediction": "Không có dự đoán", + "cycle_in_progress": "Đang tiến hành chu kỳ", + "status": "Trạng thái", + "progress": "Tiến triển", + "select_program": "Chọn chương trình để xem chi tiết", + "title": "Tiêu đề", + "status_entity": "Thực thể trạng thái", + "icon": "Biểu tượng", + "active_color": "Màu biểu tượng hoạt động", + "show_state": "Hiển thị trạng thái", + "show_program": "Hiển thị chương trình", + "show_details": "Hiển thị chi tiết", + "spin_icon": "Biểu tượng quay (Trong khi chạy)", + "program_entity": "Thực thể chương trình", + "pct_entity": "Thực thể tiến độ (Tùy chọn)", + "time_entity": "Thực thể thời gian (Tùy chọn)", + "display_mode": "Chế độ hiển thị", + "show_time_remaining": "Hiển thị thời gian còn lại", + "show_percentage": "Hiển thị phần trăm", + "entity_not_found": "Không tìm thấy thực thể", + "tap_action": "Nhấn vào Hành động", + "hold_action": "Giữ hành động", + "double_tap_action": "Hành động nhấn đúp" + }, + "zh-Hans": { + "washer_program": "清洗程序", + "program_placeholder": "选择节目", + "duration": "期间", + "minutes": "分钟", + "time_remaining": "剩余时间", + "no_prediction": "没有预测", + "cycle_in_progress": "循环正在进行中", + "status": "地位", + "progress": "进步", + "select_program": "选择一个程序以查看详细信息", + "title": "标题", + "status_entity": "状态实体", + "icon": "图标", + "active_color": "活动图标颜色", + "show_state": "显示状态", + "show_program": "演出节目", + "show_details": "显示详情", + "spin_icon": "旋转图标(运行时)", + "program_entity": "程序实体", + "pct_entity": "进度实体(可选)", + "time_entity": "时间实体(可选)", + "display_mode": "显示模式", + "show_time_remaining": "显示剩余时间", + "show_percentage": "显示百分比", + "entity_not_found": "未找到实体", + "tap_action": "点击操作", + "hold_action": "保持行动", + "double_tap_action": "双击操作" + }, + "zh-Hant": { + "washer_program": "清洗程序", + "program_placeholder": "選擇節目", + "duration": "期間", + "minutes": "分分鐘", + "time_remaining": "剩餘時間", + "no_prediction": "沒有預測", + "cycle_in_progress": "循環正在進行中", + "status": "地位", + "progress": "進步", + "select_program": "選擇一個程序以查看詳細信息", + "title": "標題", + "status_entity": "狀態實體", + "icon": "圖示", + "active_color": "活動圖示顏色", + "show_state": "顯示狀態", + "show_program": "演出節目", + "show_details": "顯示詳情", + "spin_icon": "旋轉圖示(運行時)", + "program_entity": "程式實體", + "pct_entity": "進度實體(可選)", + "time_entity": "時間實體(可選)", + "display_mode": "顯示模式", + "show_time_remaining": "顯示剩餘時間", + "show_percentage": "顯示百分比", + "entity_not_found": "未找到實體", + "tap_action": "點選操作", + "hold_action": "保持行動", + "double_tap_action": "按兩下操作" + } +}; + +class WashDataCard extends HTMLElement { + _resolveLanguage() { + const raw = + (this._hass && this._hass.locale && this._hass.locale.language) || + (this._hass && this._hass.language) || + "en"; + if (!raw || typeof raw !== "string") return "en"; + return raw; + } + + static getStubConfig() { + return { + entity: "sensor.washing_machine_state", + title: "Washing Machine", + icon: "mdi:washing-machine", + display_mode: "time", + active_color: [33, 150, 243], + show_state: true, + show_program: true, + show_details: true, + spin_icon: true, + tap_action: { action: "more-info" }, + hold_action: { action: "none" }, + double_tap_action: { action: "none" } + }; + } + + static getConfigElement() { + return document.createElement(EDITOR_TAG); + } + + _getTranslation(key) { + const lang = this._resolveLanguage(); + const baseLang = lang.split("-")[0]; + const translations = TRANSLATIONS[lang] || TRANSLATIONS[baseLang] || TRANSLATIONS["en"]; + return translations[key] || TRANSLATIONS["en"][key] || key; + } + + constructor() { + super(); + this.attachShadow({ mode: "open" }); + this._rendered = false; + // Gesture state for tap / hold / double-tap recognition. + this._holdTimer = null; + this._holdTriggered = false; + this._tapTimer = null; + this._lastTapTime = 0; + this._pointerStart = null; + this._onPointerDown = this._onPointerDown.bind(this); + this._onPointerMove = this._onPointerMove.bind(this); + this._onPointerUp = this._onPointerUp.bind(this); + this._onPointerCancel = this._onPointerCancel.bind(this); + } + + disconnectedCallback() { + // Avoid stray actions firing after the card is removed from the DOM. + this._clearHoldTimer(); + if (this._tapTimer) { + window.clearTimeout(this._tapTimer); + this._tapTimer = null; + } + } + + setConfig(config) { + if (!config.entity) { + throw new Error("Please define an entity"); + } + this._cfg = { ...WashDataCard.getStubConfig(), ...config }; + this._render(); + } + + set hass(hass) { + this._hass = hass; + this._update(); + } + + getCardSize() { + return 1; + } + + _clearHoldTimer() { + if (this._holdTimer) { + window.clearTimeout(this._holdTimer); + this._holdTimer = null; + } + } + + _onPointerDown(ev) { + // Only react to the primary pointer (left mouse button / touch / pen). + if (ev.button !== undefined && ev.button !== 0) return; + this._holdTriggered = false; + this._pointerCanceled = false; + this._pointerStart = { x: ev.clientX, y: ev.clientY }; + + const holdCfg = this._cfg && this._cfg.hold_action; + if (holdCfg && holdCfg.action && holdCfg.action !== "none") { + this._clearHoldTimer(); + this._holdTimer = window.setTimeout(() => { + this._holdTimer = null; + this._holdTriggered = true; + this._fireHaptic("success"); + this._executeAction(holdCfg); + }, HOLD_MS); + } + } + + _onPointerMove(ev) { + // Cancel the gesture if the pointer drifts (e.g. the user is scrolling), so + // neither the pending hold nor the release-tap fires. + if (!this._pointerStart) return; + const dx = ev.clientX - this._pointerStart.x; + const dy = ev.clientY - this._pointerStart.y; + if (dx * dx + dy * dy > TAP_MOVE_TOLERANCE * TAP_MOVE_TOLERANCE) { + this._clearHoldTimer(); + this._pointerStart = null; + this._pointerCanceled = true; + } + } + + _onPointerCancel() { + this._clearHoldTimer(); + } + + _onPointerUp() { + this._clearHoldTimer(); + // The pointer drifted (scroll/drag): the release should not count as a tap. + if (this._pointerCanceled) { + this._pointerCanceled = false; + return; + } + // A hold already fired its action; the release should not also count as a tap. + if (this._holdTriggered) { + this._holdTriggered = false; + return; + } + + const tapCfg = (this._cfg && this._cfg.tap_action) || { action: "more-info" }; + const doubleCfg = this._cfg && this._cfg.double_tap_action; + const hasDouble = doubleCfg && doubleCfg.action && doubleCfg.action !== "none"; + + // With no double-tap action configured, fire the tap immediately (no latency). + if (!hasDouble) { + this._executeAction(tapCfg); + return; + } + + const now = Date.now(); + if (this._tapTimer && now - this._lastTapTime < DOUBLE_TAP_MS) { + window.clearTimeout(this._tapTimer); + this._tapTimer = null; + this._lastTapTime = 0; + this._executeAction(doubleCfg); + return; + } + + // First tap: wait briefly to see whether a second one arrives. + this._lastTapTime = now; + this._tapTimer = window.setTimeout(() => { + this._tapTimer = null; + this._executeAction(tapCfg); + }, DOUBLE_TAP_MS); + } + + _fireHaptic(type) { + this.dispatchEvent(new CustomEvent("haptic", { + detail: type, + bubbles: true, + composed: true, + })); + } + + _executeAction(actionCfg) { + if (!actionCfg) return; + const action = actionCfg.action || "more-info"; + const entityId = actionCfg.entity || (this._cfg && this._cfg.entity); + + switch (action) { + case "none": + return; + + case "more-info": { + if (!entityId) return; + this.dispatchEvent(new CustomEvent("hass-more-info", { + detail: { entityId }, + bubbles: true, + composed: true, + })); + return; + } + + case "toggle": { + // homeassistant.toggle routes to the correct domain service for all + // common toggleable domains, so no per-domain table is needed. + if (!this._hass || !entityId) return; + this._hass.callService("homeassistant", "toggle", { entity_id: entityId }); + return; + } + + case "call-service": + case "perform-action": { + const svc = actionCfg.perform_action || actionCfg.service; + if (!svc || !this._hass) return; + const [svcDomain, svcName] = svc.split("."); + if (!svcDomain || !svcName) return; + const data = { ...(actionCfg.data || actionCfg.service_data || {}) }; + this._hass.callService(svcDomain, svcName, data, actionCfg.target); + return; + } + + case "navigate": { + const path = actionCfg.navigation_path; + if (!path) return; + if (actionCfg.navigation_replace) { + window.history.replaceState(window.history.state, "", path); + } else { + window.history.pushState(null, "", path); + } + window.dispatchEvent(new CustomEvent("location-changed", { + detail: { replace: !!actionCfg.navigation_replace }, + })); + return; + } + + case "url": { + const url = actionCfg.url_path; + if (!url) return; + // noopener/noreferrer prevents the opened page from reaching back via + // window.opener (reverse tabnabbing). + window.open(url, "_blank", "noopener,noreferrer"); + return; + } + + default: + // Unsupported actions (e.g. "assist") are intentionally ignored: a + // standalone card resource cannot resolve Home Assistant's internal + // dialog chunks, so attempting them would only throw at runtime. + return; + } + } + + _render() { + if (!this.shadowRoot) return; + + // Only create the DOM once to avoid memory leaks from duplicate event listeners + if (!this._rendered) { + this.shadowRoot.innerHTML = ` + + +
+
+ +
+
+
+
+
+
+
+ `; + + const cardEl = this.shadowRoot.getElementById("card"); + cardEl.addEventListener("pointerdown", this._onPointerDown); + cardEl.addEventListener("pointermove", this._onPointerMove); + cardEl.addEventListener("pointerup", this._onPointerUp); + cardEl.addEventListener("pointercancel", this._onPointerCancel); + cardEl.addEventListener("pointerleave", this._onPointerCancel); + this._rendered = true; + } + + this._update(); + } + + _update() { + if (!this.shadowRoot || !this._hass || !this._cfg) return; + + const entityId = this._cfg.entity; + const stateObj = this._hass.states[entityId]; + + const titleEl = this.shadowRoot.getElementById("title"); + const stateEl = this.shadowRoot.getElementById("state"); + const iconEl = this.shadowRoot.getElementById("icon"); + const iconContainer = this.shadowRoot.getElementById("icon-container"); + + if (!stateObj) { + if (titleEl) titleEl.textContent = this._getTranslation("entity_not_found"); + if (stateEl) stateEl.textContent = entityId; + return; + } + + const title = this._cfg.title || "Washing Machine"; + const icon = this._cfg.icon || stateObj.attributes.icon || "mdi:washing-machine"; + const activeColor = this._cfg.active_color; + + const state = stateObj.state; + // Treat as inactive if off, unknown, unavailable, idle + const isInactive = ['off', 'unknown', 'unavailable', 'idle'].includes(state.toLowerCase()); + + if (isInactive) { + iconContainer.style.background = `rgba(128, 128, 128, 0.1)`; + iconContainer.style.color = `var(--disabled-text-color, grey)`; + } else { + let colorCss = "var(--primary-color)"; + let bgCss = "rgba(var(--rgb-primary-color, 33, 150, 243), 0.2)"; + + if (Array.isArray(activeColor)) { + const [r, g, b] = activeColor; + colorCss = `rgb(${r}, ${g}, ${b})`; + bgCss = `rgba(${r}, ${g}, ${b}, 0.2)`; + } else if (activeColor) { + colorCss = activeColor; + bgCss = `rgba(128, 128, 128, 0.15)`; + } + + iconContainer.style.color = colorCss; + iconContainer.style.background = bgCss; + } + + iconEl.setAttribute("icon", icon); + if (state.toLowerCase() === 'running' && this._cfg.spin_icon !== false) { + iconEl.classList.add("spinning"); + } else { + iconEl.classList.remove("spinning"); + } + titleEl.textContent = title; + + const attr = stateObj.attributes; + const parts = []; + + // 1. State / Sub-State + // Default show_state to true if undefined + if (this._cfg.show_state !== false) { + if (state.toLowerCase() === 'running') { + const subState = attr.sub_state; + if (subState) { + // If sub_state is "Running (Rinsing)", extract "Rinsing" + const match = subState.match(/Running \((.*)\)/); + if (match && match[1]) { + parts.push(match[1]); + } else { + parts.push(subState); + } + } + // If no sub_state (or just "Running"), we show NOTHING (redundant) + } else { + // Not running (e.g. Off, Completed, etc) - show standard state + parts.push(state.charAt(0).toUpperCase() + state.slice(1)); + } + } + + // 2. Program + if (this._cfg.show_program !== false) { + let program = ""; + if (this._cfg.program_entity) { + const progState = this._hass.states[this._cfg.program_entity]; + if (progState) program = progState.state; + } else if (attr.program) { + program = attr.program; + } + if (program && !["unknown", "none", "off", "unavailable"].includes(program.toLowerCase())) { + parts.push(program); + } + } + + // 3. Details (Time / Pct) + if (this._cfg.show_details !== false && !isInactive) { + let remaining = ""; + if (this._cfg.time_entity) { + remaining = this._hass.states[this._cfg.time_entity]?.state; + } else if (attr.time_remaining) { + remaining = attr.time_remaining; + } + + let pct = ""; + if (this._cfg.pct_entity) { + pct = this._hass.states[this._cfg.pct_entity]?.state; + } else if (attr.cycle_progress) { + pct = attr.cycle_progress; + } + + if (this._cfg.display_mode === 'percentage' && pct) { + parts.push(`${Math.round(pct)}%`); + } else if (remaining) { + // Append 'min' if it is a number (WashData attribute is raw minutes) + if (!isNaN(remaining)) { + parts.push(`${remaining} ${this._getTranslation("minutes")}`); + } else { + parts.push(remaining); + } + } + } + + stateEl.textContent = parts.length > 0 ? parts.join(" • ") : ""; + } +} + +class WashDataCardEditor extends HTMLElement { + _resolveLanguage() { + const raw = + (this._hass && this._hass.locale && this._hass.locale.language) || + (this._hass && this._hass.language) || + "en"; + if (!raw || typeof raw !== "string") return "en"; + return raw; + } + + _getTranslation(key) { + const lang = this._resolveLanguage(); + const baseLang = lang.split("-")[0]; + const translations = TRANSLATIONS[lang] || TRANSLATIONS[baseLang] || TRANSLATIONS["en"]; + return translations[key] || TRANSLATIONS["en"][key] || key; + } + + setConfig(config) { + this._cfg = { ...WashDataCard.getStubConfig(), ...config }; + this._render(); + } + + set hass(hass) { + this._hass = hass; + if (this._form) { + this._form.hass = hass; + } + } + + _render() { + if (!this.shadowRoot) { + this.attachShadow({ mode: "open" }); + } + + if (!this._form) { + this.shadowRoot.innerHTML = ` + +
+ `; + this._form = document.createElement("ha-form"); + this.shadowRoot.getElementById("editor-container").appendChild(this._form); + + this._form.addEventListener("value-changed", (ev) => this._valueChanged(ev)); + + this._form.schema = [ + { name: "title", selector: { text: {} } }, + { name: "entity", selector: { entity: { domain: "sensor" } } }, + { name: "icon", selector: { icon: {} } }, + { name: "active_color", selector: { color_rgb: {} } }, + { name: "show_state", selector: { boolean: {} } }, + { name: "show_program", selector: { boolean: {} } }, + { name: "show_details", selector: { boolean: {} } }, + { name: "spin_icon", selector: { boolean: {} } }, + { + name: "display_mode", + selector: { + select: { + options: [ + { value: "time", label: this._getTranslation("show_time_remaining") }, + { value: "percentage", label: this._getTranslation("show_percentage") } + ], + mode: "dropdown" + } + } + }, + { name: "program_entity", selector: { entity: { domain: ["sensor", "select", "input_select", "input_text"] } } }, + { name: "pct_entity", selector: { entity: { domain: "sensor" } } }, + { name: "time_entity", selector: { entity: { domain: "sensor" } } }, + { name: "tap_action", selector: { ui_action: {} } }, + { name: "hold_action", selector: { ui_action: {} } }, + { name: "double_tap_action", selector: { ui_action: {} } }, + ]; + + this._form.computeLabel = (schema) => { + const labels = { + title: this._getTranslation("title"), + entity: this._getTranslation("status_entity"), + icon: this._getTranslation("icon"), + active_color: this._getTranslation("active_color"), + show_state: this._getTranslation("show_state"), + show_program: this._getTranslation("show_program"), + show_details: this._getTranslation("show_details"), + spin_icon: this._getTranslation("spin_icon"), + program_entity: this._getTranslation("program_entity"), + pct_entity: this._getTranslation("pct_entity"), + time_entity: this._getTranslation("time_entity"), + display_mode: this._getTranslation("display_mode"), + tap_action: this._getTranslation("tap_action"), + hold_action: this._getTranslation("hold_action"), + double_tap_action: this._getTranslation("double_tap_action") + }; + return labels[schema.name] || schema.name; + }; + } + + this._form.data = this._cfg; + if (this._hass) { + this._form.hass = this._hass; + } + } + + _valueChanged(ev) { + if (!this._cfg || !this._hass) return; + const val = ev.detail.value; + this._cfg = { ...this._cfg, ...val }; + + const event = new CustomEvent("config-changed", { + detail: { config: this._cfg }, + bubbles: true, + composed: true, + }); + this.dispatchEvent(event); + } +} + +customElements.define(CARD_TAG, WashDataCard); +customElements.define(EDITOR_TAG, WashDataCardEditor); + +window.customCards = window.customCards || []; +window.customCards.push({ + type: CARD_TAG, + name: "WashData Tile Card", + preview: true, + description: "A compact tile-style card for washing machines.", +}); diff --git a/www/community/HA-Firemote/HA-Firemote.js b/www/community/HA-Firemote/HA-Firemote.js new file mode 100644 index 0000000..41a1c6c --- /dev/null +++ b/www/community/HA-Firemote/HA-Firemote.js @@ -0,0 +1,8557 @@ +const HAFiremoteVersion = 'v4.1.9'; + +import {LitElement, html, css, unsafeHTML, unsafeCSS, styleMap} from './lit/lit-all.min.js'; +import {launcherData, launcherCSS} from "./launcher-buttons.js?version=v4.1.9"; +import {rosettaStone} from './language-translations.js?version=v4.1.9'; +import {devices} from './supported-devices.js?version=v4.1.9'; + +console.groupCollapsed("%c 🔥 FIREMOTE-CARD 🔥 %c "+HAFiremoteVersion+" installed ", "color: orange; font-weight: bold; background: black", "color: green; font-weight: bold;"), +console.log("Readme:", "https://github.com/PRProd/HA-Firemote"), +console.groupEnd(); + +const fireEvent = (node, type, detail, options) => { + options = options || {}; + detail = detail === null || detail === undefined ? {} : detail; + const event = new Event(type, { + bubbles: options.bubbles === undefined ? true : options.bubbles, + cancelable: Boolean(options.cancelable), + composed: options.composed === undefined ? true : options.composed + }); + event.detail = detail; + node.dispatchEvent(event); + return event; +} + + +// Process the imported data +const devicemap = new Map(Object.entries(devices)); +var appmap = new Map(Object.entries(launcherData)); +const translationmap = new Map(Object.entries(rosettaStone)); + + +// Set the max number of app launcher buttons for each remote style +const appButtonMax = { "AF4": 6, "AF5": 6, "AF6": 6, "AFJTV": 6, "AFXF2": 6, "AR1": 10, "AR2": 8, "AR3": 8, + "CC1": 8, "CC2": 8, "CC3": 8, "NS2": 6, "ON1": 8, "ON2": 8, "RVRP": 10, "RHR": 10, + "RTR": 8, "RWR": 10, "RVR": 10, "RSR": 10, "XM1": 10, "XM2": 10, "HO1": 6, "HO2": 8, + "HO3": 6, "HO4": 6, "AL1": appmap.size, "AL2": appmap.size,}; + + +function deviceAttributeQuery(deviceAttribute, configvar){ + var deviceTypeRef = configvar.device_type; + if(configvar[deviceAttribute+'_override']) { + if(configvar[deviceAttribute+'_override'] != 'none') { + return configvar[deviceAttribute+'_override']; + } + } + var attributeValue = ''; + var deviceSearch = function(deviceName, jsonData) { + for (var key in jsonData) { + if(typeof(jsonData[key]) === 'object') { + if(key == deviceName) { + attributeValue = String(jsonData[key][deviceAttribute]); + } + else { + deviceSearch(deviceName, jsonData[key]); + } + } + } + return attributeValue; + } + return String(deviceSearch(deviceTypeRef, devices)); +} + + +function truncate(str, length) { + return str.length > length ? str.substr(0, length) : str; +} + + +function resetAppMap(){ + appmap = new Map(Object.entries(launcherData)); +} + + +function handlehdmi(config, inputs = 0) { + if( inputs > 0 ) { + for (let i = 1; i <= inputs; i++) { + var configitem = "hdmi_"+i; + var inputname = "HDMI "+i; + var friendlyname = "HDMI "+i; + if (config[configitem]) { + inputname = truncate(config[configitem], 8); + friendlyname = 'HDMI '+i+' - '+inputname; + } + if( config.device_type == 'fire_tv_cube_third_gen') { + appmap.set(configitem, {"button": inputname, "friendlyName": friendlyname, "androidName": "", "adbLaunchCommand": "adb shell am start -n com.amazon.tv.inputpreference.service/com.amazon.tv.inputpreference.player.InputChooserActivity"}); + return; + } + if (config.device_family == "roku") { + appmap.set(configitem, {"button": inputname, "friendlyName": friendlyname, "appName": "HDMI "+i, "remoteCommand": '{"command": "input_hdmi'+i+'", "num_repeats": 1, "hold_secs": 0}'}); + } + else { + appmap.set(configitem, {"button": inputname, "friendlyName": friendlyname, "androidName": "", "adbLaunchCommand": "HDMI"+i}); + } + } + } +} + + +function handlecustomlaunchers(config) { + // Experimental - Only one of them works through the roku api for some reason /// +// if (config.device_family == "roku") { +// appmap.set("roku-secret-screen", {"button": "Secret", "friendlyName": "Function: Secret Screen 1"}); +// } + + let customlaunchers = config.custom_launchers; + if(typeof customlaunchers == 'undefined' || customlaunchers == null) { + return; + } + if(customlaunchers.constructor !== Array) { + customlaunchers = new Array(config.custom_launchers); + } + var l = 1; + customlaunchers.forEach((launcher) => { + var style = ''; var icon = null; var imagePath = null; + if (launcher.color) { style = 'color:'+launcher.color+';'; } + if (launcher.background) { style = style+'background:'+launcher.background+';'; } + var friendlyname = launcher.friendly_name || launcher.label || "customlauncher "+l; + var label = launcher.label || launcher.friendly_name || "customlauncher "+l; + if (launcher.icon) { + icon = '
'; + } + if (launcher.image_path) { + imagePath = '
'+label+''; + } + label = '
'+truncate(label, 10)+'
'; + var buttonFace = imagePath || icon || label + appmap.set("customlauncher "+friendlyname, {"button": buttonFace, "friendlyName": "Custom: "+friendlyname, "script": launcher.script, "data": launcher.data}); + l++; + }) +} + +function handleAVInputs(config, avInputs = 0) { + if (avInputs > 0 && config.device_family == "roku") { + appmap.set("av1_input", {"button": "AV", "friendlyName": "AV Input", "remoteCommand": '{"command": "input_av1", "num_repeats": 1, "hold_secs": 0}'}); + } +} + +function handleTunerInputs(config, tunerInput) { + if (config.device_family == "roku" && (tunerInput == true || tunerInput == 'true')) { + appmap.set("tuner_input", {"button": "TV Tuner", "friendlyName": "TV Tuner", "remoteCommand": '{"command": "input_tuner", "num_repeats": 1, "hold_secs": 0}'}); + //appmap.set("roku-tv-channel", {"button": "Channel", "friendlyName": "Function: Input TV Channel"}); + return; + } + if (config.device_family == "amazon-fire" && (tunerInput == true || tunerInput == 'true')) { + appmap.set("tuner_input", {"button": '', + "friendlyName": "Antenna", + "androidName": "com.amazon.tv.livetv", + "adbLaunchCommand": "adb shell input keyevent KEYCODE_TV"} + ); + } +} + + +function refreshAppMap(config, inputs = 0, avInputs = 0, tuner = true){ + resetAppMap(); + handlehdmi(config, inputs); + handlecustomlaunchers(config); + handleAVInputs(config, avInputs); + handleTunerInputs(config, tuner); +} + + +function calculateLayoutCellHeight(h, s) { + let numOfGapPixels = (( h / 56 ) * 8)-24; + let standardHeight = ( h - numOfGapPixels ) / 56; + let scale = s / 100; + let raw = standardHeight * scale; + let calculated = Math.trunc(raw * Math.pow(10, 2)) / Math.pow(10, 2); + //console.log('scale is '+s+' so Im returning a cell height of '+calculated); + return calculated; +} + +function calculateLayoutCellWidth(w, s) { + var medianCellWidth = 104; // between 80px and 120px depending on the screen size - adjust as needed + let numOfGapPixels = (( w / medianCellWidth ) * 8)-24; + let scale = s / 100; + let raw = ( (w - numOfGapPixels) / medianCellWidth) * scale; + let calculated = Math.ceil(raw); + //console.log('scale is '+s+' so Im returning a cell width of '+calculated+' raw was '+raw); + return calculated; +} + +function calculateMasonryViewHeight(h, s) { + let scale = s / 100; + let raw = (h / 50) * scale; + let calculated = Math.ceil(raw); + //console.log('masonryViewHeight - scale is '+s+' so Im returning a cell width of '+calculated+' raw was '+raw); + return calculated; +} + + + +class FiremoteCard extends LitElement { + + // Create and return an editor element + static getConfigElement() { + return document.createElement("firemote-card-editor"); + } + + static get properties() { + return { + hass: {}, + _config: {}, + }; + } + + // Sets a default card size (height) for masonry dashboard type + getCardSize(){ + // https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card#sizing-in-masonry-view + // TODO: This does not account for added app launcher rows + var scale = this._config.scale || 100; + switch (deviceAttributeQuery('defaultRemoteStyle', this._config)) { + case "AF1": + return calculateMasonryViewHeight(588.38, scale); + break; + case "AF2": + return calculateMasonryViewHeight(634.08, scale); + break; + case "AF3": + return calculateMasonryViewHeight(657.14, scale); + break; + case "AF4": + return calculateMasonryViewHeight(715.94, scale); + break; + case "AF5": + return calculateMasonryViewHeight(772.94, scale); + break; + case "AF6": + return calculateMasonryViewHeight(750.45, scale); + break; + case "AFJTV": + case "AFXF2": + return calculateMasonryViewHeight(873.02, scale); + break; + case "AR1": + return calculateMasonryViewHeight(747.5, scale); + break; + case "AR2": + return calculateMasonryViewHeight(597, scale); + break; + case "AR3": + return calculateMasonryViewHeight(737, scale); + break; + case "CC1": + case "CC2": + case "CC3": + return calculateMasonryViewHeight(696.66, scale); + break; + case "NS1": + case "NS2": + return calculateMasonryViewHeight(681.98, scale); + break; + case "ON1": + case "ON2": + return calculateMasonryViewHeight(726.39, scale); + break; + case "RVRP": + case "RVR": + case "RSR": + return calculateMasonryViewHeight(680.56, scale); + break; + case "RHR": + case "RWR": + return calculateMasonryViewHeight(652.56, scale); + break; + case "RTR": + return calculateMasonryViewHeight(680.38, scale); + break; + case "XM1": + case "XM2": + return calculateMasonryViewHeight(793.98, scale); + break; + default: + return; + } + } + // Announces a default grid size for new experimental sections dashboard type + getLayoutOptions() { + // https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card#sizing-in-sections-view + // TODO: This does not account for added app launcher rows + var scale = this._config.scale || 100; + switch (deviceAttributeQuery('defaultRemoteStyle', this._config)) { + case "AF1": + return { + grid_rows: calculateLayoutCellHeight(588.38, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AF2": + return { + grid_rows: calculateLayoutCellHeight(634.08, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AF3": + return { + grid_rows: calculateLayoutCellHeight(657.14, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AF4": + return { + grid_rows: calculateLayoutCellHeight(715.94, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AF5": + return { + grid_rows: calculateLayoutCellHeight(772.94, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AF6": + return { + grid_rows: calculateLayoutCellHeight(750.45, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AFJTV": + case "AFXF2": + return { + grid_rows: calculateLayoutCellHeight(873.02, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "AR1": + return { + grid_rows: calculateLayoutCellHeight(747.5, scale), + grid_columns: calculateLayoutCellWidth(184, scale), + } + break; + case "AR2": + return { + grid_rows: calculateLayoutCellHeight(597, scale), + grid_columns: calculateLayoutCellWidth(184, scale), + } + break; + case "AR3": + return { + grid_rows: calculateLayoutCellHeight(737, scale), + grid_columns: calculateLayoutCellWidth(184, scale), + } + break; + case "CC1": + case "CC2": + case "CC3": + return { + grid_rows: calculateLayoutCellHeight(696.66, scale), + grid_columns: calculateLayoutCellWidth(203.36, scale), + } + break; + case "HO1": + case "HO2": + return { + grid_rows: calculateLayoutCellHeight(726.39, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "HO3": + case "HO4": + return { + grid_rows: calculateLayoutCellHeight(902.83, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + case "NS1": + case "NS2": + return { + grid_rows: calculateLayoutCellHeight(681.98, scale), + grid_columns: calculateLayoutCellWidth(137.97, scale), + } + break; + case "ON1": + case "ON2": + return { + grid_rows: calculateLayoutCellHeight(726.39, scale), + grid_columns: calculateLayoutCellWidth(137.97, scale), + } + break; + case "RVRP": + case "RVR": + case "RSR": + return { + grid_rows: calculateLayoutCellHeight(680.56, scale), + grid_columns: calculateLayoutCellWidth(183.8, scale), + } + break; + case "RHR": + case "RWR": + return { + grid_rows: calculateLayoutCellHeight(652.56, scale), + grid_columns: calculateLayoutCellWidth(183.8, scale), + } + break; + case "RTR": + return { + grid_rows: calculateLayoutCellHeight(680.38, scale), + grid_columns: calculateLayoutCellWidth(202.02, scale), + } + break; + case "XM1": + case "XM2": + return { + grid_rows: calculateLayoutCellHeight(793.98, scale), + grid_columns: calculateLayoutCellWidth(193.97, scale), + } + break; + default: + return { + //grid_rows: 12.25, + //grid_columns: 2, + }; + } + } + + + // Returns a minimal configuration that will result in a working card + static getStubConfig(e) { + var androidTVEntities = Object.keys(e.entities).filter( + (eid) => e.entities[eid].platform === 'androidtv' + ); + var appleTVEntities = Object.keys(e.entities).filter( + (eid) => e.entities[eid].platform === 'apple_tv' + ); + var rokuEntities = Object.keys(e.entities).filter( + (eid) => e.entities[eid].platform === 'roku' + ); + if (androidTVEntities.length > 0) { + return { 'entity': androidTVEntities[0], + 'device_family': 'amazon-fire', + 'device_type': 'fire_tv_4_series', + 'compatibility_mode': 'default', + }; + } + else if (appleTVEntities.length > 0) { + return { 'entity': appleTVEntities[0], + 'device_family': 'apple-tv', + 'device_type': 'appletv-4k-gen2', + 'compatibility_mode': 'default', + }; + } + else if (rokuEntities.length > 0) { + return { 'entity': rokuEntities[0], + 'device_family': 'roku', + 'device_type': 'roku-ultra-2020', + 'compatibility_mode': 'default', + }; + } + else { + return { 'entity': '', + 'device_family': 'amazon-fire', + 'device_type': 'fire_tv_4_series', + 'compatibility_mode': 'default', + }; + } + } + + setConfig(config) { + if (!config.entity) { + throw new Error("entity must be defined. You need to define an Apple TV, Chromecast, Fire TV, NVIDIA Shield, onn., Roku, Xiaomi Mi, or any other media_player entity"); + } + this._config = config; + } + + static styles = css` + + ha-card { + background: rgba(30,30,30,0); + display: grid; + justify-content: center; + padding: 0; + margin: auto; + box-shadow: none; + border: 0; + outline: 0; + isolation: isolate; + direction: ltr; + -webkit-tap-highlight-color: rgb(0 0 0 / 0%); + } + + .hidden { + display: none !important; + } + + .mdiSubstituteIconWrapper { + pointer-events: none; + transform: scale(var(--sz)); + } + + .mdiSubstitueIcon { + display: var(--ha-icon-display,inline-flex); + align-items: center; + justify-content: center; + position: relative; + vertical-align: middle; + fill: var(--icon-primary-color,currentcolor); + width: var(--mdc-icon-size,24px); + height: var(--mdc-icon-size,24px); + } + + .shield-remote-body { + background: linear-gradient(90deg, rgba(22,21,21,1) 0%, rgba(37,37,37,1) 10%, rgba(37,37,37,1) 90%, rgba(22,21,21,1) 100%); + border: solid #252525 calc(var(--sz) * 0.14rem); + padding: calc(var(--sz) * 1.428rem) calc(var(--sz) * 0.714rem) calc(var(--sz) * 2.143rem) calc(var(--sz) * 0.714rem); + display: grid; + justify-items: center; + align-content: flex-start; + grid-column-gap: calc(var(--sz) * 1.2rem); + grid-row-gap: calc(var(--sz) * 0.5rem); + grid-template-columns: 1fr 1fr; + width: calc(var(--sz) * 8.286rem); + min-height: calc(var(--sz) * 45rem); + } + + .shield-remote-body.ns1-body { + background: linear-gradient(90deg, rgb(28 28 28) 0%, rgb(37, 37, 37) 8%, rgb(40 40 40) 50%, rgb(37, 37, 37) 92%, rgb(28, 28, 28) 100%); + border: solid #1c1c1c calc(var(--sz) * 0.14rem); + border-radius: calc(var(--sz) * 1.2rem); + } + + .roku-remote-body { + display: flex; + flex-direction: column; + align-content: center; + justify-content: flex-start; + align-items: center; + gap: calc(var(--sz) * 1rem); + background: linear-gradient(90deg, rgba(42,42,42,1) 0%, rgba(58,58,58,1) 50%, rgba(42,42,42,1) 100%); + border-radius: calc(var(--sz) * 3.25rem); + border: solid #252525 calc(var(--sz) * 0.6rem); + border-top: solid #292929 calc(var(--sz) * 0.6rem); + width: calc(var(--sz) * 11.287rem); + padding: calc(var(--sz) * 0.35rem); + padding-bottom: calc(var(--sz) * 1.12rem); + min-height: calc(var(--sz) * 44rem); + } + + .roku-remote-body:has(div.rokuTag) { + margin-bottom: calc(var(--sz) * 2rem); + } + + .roku-remote-body ha-icon { + transform: scale(calc(var(--sz) * .8)); + } + + .roku-remote-body > div { + display: flex; + flex-wrap: nowrap; + flex-direction: row; + align-content: center; + justify-content: center; + align-items: center; + gap: calc(var(--sz) * 0.35rem); + } + + .roku-remote-body > div:not(.rokuDPadContainer) > button:active { + box-shadow: none !important; + transform: scale(0.975); + transform-origin: center bottom; + } + + .roku-remote-body > div:not(.rokuDPadContainer) > button:active > ha-icon { + transform: scale(0.8); + } + + .roku-remote-body.RVR > div.powerRow { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + align-items: center; + justify-items: center; + margin-bottom: calc(var(--sz) * 1rem); + } + + .roku-remote-body.RTR { + border-radius: calc(var(--sz) * 6rem); + padding: calc(var(--sz) * 1rem); + } + + .roku-remote-body.RWR > div.powerRow, + .roku-remote-body.RTR > div.powerRow, + .roku-remote-body.RHR > div.powerRow { + margin-bottom: calc(var(--sz) * 1.25rem); + } + + .roku-remote-body > div.micRow { + height: calc(var(--sz) * 0.4rem); + aspect-ratio: 1; + background: black; + border-radius: 100%; + margin-top: calc(var(--sz) * 0.4rem); + } + + .roku-remote-body.RVR div.micRow { + height: calc(var(--sz) * 0.4rem); + aspect-ratio: 1; + background: black; + border-radius: 100%; + } + + .roku-remote-body.RSR > div.backHomeRow { + padding-top: calc(var(--sz) * 5rem); + } + + .roku-remote-body > div.backHomeRow > button{ + height: calc(var(--sz) * 2.5rem); + width: calc(var(--sz) * 5.25rem); + border: solid #090909 calc(var(--sz) * 0.125rem); + border-radius: calc(var(--sz) * 0.7rem); + box-shadow: 0px 5px 2px 0px #0000001a; + } + + .roku-remote-body > div.rokuDPadContainer { + background: linear-gradient(175deg, rgb(42 42 42) 0%, rgb(51 51 51) 100%); + padding: calc(var(--sz) * 0.3rem); + border-radius: calc(var(--sz) * 1.5rem); + display: grid; + grid-template-columns: 31% 38% 31%; + grid-template-rows: 31% 38% 31%; + grid-gap: 0; + width: calc(var(--sz) * 10.15rem); + height: calc(var(--sz) * 10.15rem); + overflow: hidden; + } + + .roku-remote-body.RTR > div.rokuDPadContainer { + padding: calc(var(--sz) * 0.75rem); + border-radius: 100%; + } + + .rokuDPadContainer > button { + background: #662d91; + color: rgb(198, 198, 198); + height: 100%; + width: 100%; + margin: 0; + cursor: pointer; + line-height: normal; + user-select: none; + display: grid; + place-content: center; + border: 0; + aspect ratio: 1; + font-size: calc(var(--sz) * 1.275rem); + position: relative; + z-index: 2; + } + + .rokuDPadContainer > button > ha-icon { + transform: scale(calc(var(--sz) * 1.3)); + } + + .rokuDPadContainer > button:active > ha-icon, .rokuDPadContainer > button:active > .okCircle { + color: #bcbcbc; + transform: scale(calc(var(--sz) * 0.85)); + } + + .roku-remote-body #up-button { + transform-origin: bottom center; + border-radius: calc(var(--sz) * 1rem) calc(var(--sz) * 1rem) 0 0; + position: relative; + } + + .roku-remote-body #left-button { + transform-origin: center right; + border-radius: calc(var(--sz) * 1rem) 0 0 calc(var(--sz) * 1rem); + box-shadow: 0px 5px 2px 0px #0000001a; + } + + .roku-remote-body #right-button { + border-radius: 0 calc(var(--sz) * 1rem) calc(var(--sz) * 1rem) 0; + transform-origin: center left; + box-shadow: 0px 5px 2px 0px #0000001a; + z-index: 1; + } + + .roku-remote-body #down-button { + transform-origin: top center; + border-radius: 0 0 calc(var(--sz) * 1rem) calc(var(--sz) * 1rem); + box-shadow: 0px 5px 2px 0px #0000001a; + } + + .roku-remote-body #center-button { + z-index: 3; + } + + .roku-remote-body #up-button::before { + content: ''; + position: absolute; + margin: 0; + padding: 0; + bottom: 0; + left: calc(var(--sz) * -1.95rem); + border-radius: 100%; + height: calc(var(--sz) * 2rem); + width: calc(var(--sz) * 2rem); + background: transparent; + box-shadow: calc(var(--sz) * 1rem) calc(var(--sz) * 1rem) 0 rgb(102, 45, 145); + } + + .roku-remote-body #up-button::after { + content: ''; + position: absolute; + margin: 0; + padding: 0; + bottom: 0; + right: calc(var(--sz) * -1.95rem); + border-radius: 100%; + height: calc(var(--sz) * 2rem); + width: calc(var(--sz) * 2rem); + background: transparent; + box-shadow: calc(var(--sz) * -1rem) calc(var(--sz) * 1rem) 0 rgb(102, 45, 145); + } + + .roku-remote-body #down-button::before { + content: ''; + position: absolute; + margin: 0; + padding: 0; + top: 0; + left: calc(var(--sz) * -1.95rem); + border-radius: 100%; + height: calc(var(--sz) * 2rem); + width: calc(var(--sz) * 2rem); + background: transparent; + box-shadow: calc(var(--sz) * 1rem) calc(var(--sz) * -1rem) 0 rgb(102, 45, 145); + } + + .roku-remote-body #down-button::after { + content: ''; + position: absolute; + margin: 0; + padding: 0; + top: 0; + right: calc(var(--sz) * -1.95rem); + border-radius: 100%; + height: calc(var(--sz) * 2rem); + width: calc(var(--sz) * 2rem); + background: transparent; + box-shadow: calc(var(--sz) * -1rem) calc(var(--sz) * -1rem) 0 rgb(102, 45, 145); + } + + .rokuDPadContainer > button:not(#center-button):active { + box-shadow: unset; + transform: scale(0.98); + } + + .okCircle { + padding: calc(var(--sz) * .75rem); + border-radius: 100%; + border: calc(var(--sz) * 0.15rem) solid rgb(88 38 125); + background: linear-gradient(175deg, rgb(165 165 165 / 20%) 10%, rgb(29 29 29 / 20%) 100%); + margin: 0; + place-self: center; + display: flex; + place-content: center; + align-items: center; + user-select: none; + pointer-events: none; + aspect-ratio: 1/1; + overflow: hidden; + } + + .rokuDPadContainer > button:active > .okCircle { + color: #bcbcbc; + transform: scale(calc(var(--sz) * 0.8)); + filter: brightness(0.9); + } + + .roku-remote-body > div.replayVoiceOptionsRow, + .roku-remote-body > div.scrubRow, + .roku-remote-body > div.personalShortcutsRow { + width: calc(var(--sz) * 10.15rem); + display: grid; + grid-template-columns: 35% 22% 35%;; + justify-content: center; + place-content: center space-between; + place-items: center; + justify-items: stretch; + align-items: center; + } + + .roku-remote-body.RVR > div.replayVoiceOptionsRow { + grid-template-columns: 25% 38% 25%; + } + + .roku-remote-body.RSR > div.replayVoiceOptionsRow, + .roku-remote-body.RTR > div.replayVoiceOptionsRow { + grid-template-columns: 47% 47%; + } + + .roku-remote-body > div.replayVoiceOptionsRow button, + .roku-remote-body > div.scrubRow button, + .roku-remote-body > div.personalShortcutsRow button { + height: calc(var(--sz) * 2.25rem); + width: 100%; + border: solid #090909 calc(var(--sz) * 0.125rem); + border-radius: calc(var(--sz) * 0.7rem); + box-shadow: 0px 5px 2px 0px #0000001a; + } + + .roku-remote-body > div.scrubRow { + grid-template-columns: 22% 48% 22%; + } + + .roku-remote-body.RVR > div.scrubRow, + .roku-remote-body.RSR > div.scrubRow, + .roku-remote-body.RWR > div.scrubRow { + margin-bottom: calc(var(--sz) * 0.75rem); + } + + .roku-remote-body > div.scrubRow button { + height: calc(var(--sz) * 3rem); + } + + .roku-remote-body > div.personalShortcutsRow { + grid-template-columns: 31% 31%; + gap: calc(var(--sz) * 1.75rem); + justify-content: center; + padding: calc(var(--sz) * 0.3rem) 0 calc(var(--sz) * 0.6rem) 0; + } + + .roku-remote-body > div.personalShortcutsRow > button > ha-icon { + transform: scale(var(--sz)); + } + + .roku-remote-body .appLauncherAppsContainer { + display: grid; + grid-template-columns: 50% 50%; + grid-row-gap: calc(var(--sz) * 1rem); + align-items: center; + justify-items: center; + justify-content: center; + align-content: center; + } + + .roku-remote-body.RHR .appLauncherAppsContainer { + padding: calc(var(--sz) * 1rem) 0 calc(var(--sz) * 4rem) 0; + } + + .roku-remote-body.RWR .appLauncherAppsContainer { + padding-bottom: calc(var(--sz) * 6rem); + } + + .roku-remote-body .appLauncherAppsContainer > button { + height: calc(var(--sz) * 1.8rem); + width: calc(var(--sz) * 5.1426rem); + border-radius: calc(var(--sz) * 0.7rem); + box-sizing: border-box; + align-items: center; + } + + .roku-remote-body .appLauncherVerticalAppsContainer { + display: grid; + margin-top: calc(var(--sz) * 1.25rem); + gap: calc(var(--sz) * 1rem); + } + + .rokuBrandLogo { + padding: calc(var(--sz) * 1rem) 0; + } + + .RHR .rokuBrandLogo, + .RWR .rokuBrandLogo { + position: absolute; + bottom: calc(var(--sz) * 2rem); + } + + .RTR .rokuBrandLogo { + padding: calc(var(--sz) * 1rem) 0 calc(var(--sz) * 2.25rem) 0; + } + + .rokuBrandLogo svg { + height: calc(var(--sz) * 1.15rem); + max-width: calc(var(--sz) * 11.287rem); + } + + .RHR .rokuBrandLogo svg { + height: calc(var(--sz) * 0.8rem); + } + + .RWR .rokuBrandLogo svg { + height: calc(var(--sz) * 3.1rem); + } + + .roku-remote-body .rokuTag { + position: absolute; + height: calc(var(--sz) * 2.2rem); + width: calc(var(--sz) * 5.6rem); + background: repeating-linear-gradient(#662D91, #662D91 calc(var(--sz) * 0.15rem), #3b1955 calc(var(--sz) * 0.15rem), #3b1955 calc(var(--sz) * 0.3rem)); + bottom: 0; + overflow: hidden; + box-sizing: border-box; + padding: calc(var(--sz) * 1.1rem); + border: solid #662d91 calc(var(--sz) * 0.1rem); + border-top: none; + border-bottom: none; + -webkit-clip-path: polygon(7% 5%, 93% 5%, 100% 0, 100% 100%, 0 100%, 0 0); + clip-path: polygon(7% 5%, 93% 5%, 100% 0, 100% 100%, 0 100%, 0 0); + } + + .chromecast-remote-body { + background: #ebebea; + border-radius: calc(var(--sz) * 6rem); + border: solid #cfcfcf calc(var(--sz) * 0.1rem); + width: calc(var(--sz) * 11.287rem); + padding: calc(var(--sz) * 0.35rem); + padding-bottom: calc(var(--sz) * 4rem); + display: grid; + justify-items: center; + align-content: flex-start; + grid-column-gap: calc(var(--sz) * 1.2rem); + grid-row-gap: calc(var(--sz) * 0.6rem); + grid-template-columns: 1fr 1fr; + min-height: calc(var(--sz) * 37rem); + } + + .chromecast-remote-body.CC2 { + background: #cad4d8; + } + + .chromecast-remote-body.CC3 { + background: #e1d2cc; + } + + .apple-remote-body { + background: #b5b5b5; + background: linear-gradient(0deg, rgba(147,148,150,1) 0%, rgba(207,211,213,1) 100%); + border: solid #d1d1d1 calc(var(--sz) * 0.05rem); + border-radius: calc(var(--sz) * 1.75rem); + display: grid; + justify-items: center; + align-content: flex-start; + grid-column-gap: calc(var(--sz) * 1rem); + grid-row-gap: calc(var(--sz) * 0.5rem); + grid-template-columns: 1fr 1fr; + width: calc(var(--sz) * 13rem); + min-height: calc(var(--sz) * 51rem); + padding-bottom: calc(var(--sz) * 1.5rem); + } + + .apple-remote-body.AR1 { + padding-top: calc(var(--sz) * 4.75rem); + min-height: calc(var(--sz) * 47rem); + grid-column-gap: calc(var(--sz) * 0.25rem); + grid-row-gap: calc(var(--sz) * 0.75rem); + font-size: calc(var(--sz) * 1rem); + } + + .apple-remote-body.AR2 { + background: linear-gradient(30deg, rgb(0, 0, 0) 0%, rgb(41, 41, 41) 70%); + display: grid; + grid-template-columns: 1fr; + grid-template-rows: auto 1fr; + grid-row-gap: 0; + border: solid #2d2d2d calc(var(--sz) * 0.01rem); + min-height: calc(var(--sz) * 41rem); + overflow: hidden; + } + + .AR2TopSection, .AR2BottomSection { + display: grid; + justify-items: center; + align-content: flex-start; + grid-column-gap: calc(var(--sz) * 1.5rem); + grid-row-gap: calc(var(--sz) * 0.5rem); + grid-template-columns: 1fr 1fr; + box-sizing: border-box; + } + + .AR2TopSection { + background: linear-gradient(0deg, rgba(46,46,46,1) 0%, rgba(59,59,59,1) 100%); + height: 100%; + width: 100%; + padding-bottom: calc(var(--sz) * 0.25rem); + } + + .AR2BottomSection { + padding-top: calc(var(--sz) * 0.5rem); + } + + .apple-remote-body.AR2 > div > button:nth-of-type(odd), .apple-remote-body.AR3 > button:nth-of-type(odd) { + justify-self: self-end; + transform-origin: center center; + } + + .apple-remote-body.AR2 > div > button:nth-of-type(even), .apple-remote-body.AR3 > button:nth-of-type(even) { + justify-self: self-start; + transform-origin: center center; + } + + .remote-body { + background: linear-gradient(90deg, rgba(27,27,27,1) 0%, rgba(37,37,37,1) 8%, rgba(55,55,55,1) 50%, + rgba(37,37,37,1) 92%, rgba(27,27,27,1) 100%); + border: solid #252525 calc(var(--sz) * 0.14rem); + border-radius: calc(var(--sz) * 8rem) calc(var(--sz) * 8rem) calc(var(--sz) * 8rem) calc(var(--sz) * 8rem) / calc(var(--sz) * 2.5rem) calc(var(--sz) * 2.5rem) calc(var(--sz) * 2.5rem) calc(var(--sz) * 2.5rem); + padding: calc(var(--sz) * 1.428rem) calc(var(--sz) * 0.714rem) calc(var(--sz) * 2.143rem) calc(var(--sz) * 0.714rem); + display: grid; + justify-items: center; + grid-column-gap: calc(var(--sz) * 0.14rem); + grid-row-gap: calc(var(--sz) * 0.5rem); + grid-template-columns: 1fr 1fr 1fr; + width: calc(var(--sz) * 12.286rem); + } + + .remote-body.AF6 { + border-radius: calc(var(--sz) * 2.8rem); + } + + .XM2 { + min-height: calc(var(--sz) * 53rem); + grid-template-rows: repeat(7, auto) 1fr; + border-radius: calc(var(--sz) * 2rem); + } + + .AL1, .AL2 { + display: block; + width: unset; + border-radius: calc(var(--sz) * 0.5rem); + padding: calc(var(--sz) * 1.4rem) calc(var(--sz) * 0.714rem); + } + + .two-col-span { + grid-column-start: 1; + grid-column-end: 3; + width: 100%; + display: grid; + justify-content: center; + grid-row-gap: calc(var(--sz) * 0.143rem); + align-content: center; + } + + .three-col-span { + grid-column-start: 1; + grid-column-end: 4; + width: 100%; + display: grid; + grid-column-gap: calc(var(--sz) * 0.143rem); + grid-template-columns: 50% 50%; + align-content: center; + } + + .apple-tv-top { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + width: calc(var(--sz) * 11.286rem); + justify-items: center; + align-items: center; + justify-content: center; + align-content: center; + padding-top: calc(var(--sz) * 0.85rem); + } + + .remote-body #channel-up-button, .remote-body #channel-down-button { + --mdc-icon-size: 38px; + } + + .nsappsgrid { + display: grid; + grid-row-gap: calc(var(--sz) * 0.6rem); + } + + .afappsgrid { + display: grid; + margin-top: calc(var(--sz) * 0.57rem); + row-gap: calc(var(--sz) * 0.9rem); + justify-items: center; + } + + .XM2 .afappsgrid { + margin-bottom: calc(var(--sz) * 0.57rem); + } + + .appLauncherAppsContainer { + display: flex; + flex-wrap: wrap; + flex-direction: row; + align-content: center; + align-items: center; + justify-content: center; + gap: calc(var(--sz) * 0.9rem); + } + + .ns1-wings { + grid-column-start: 1; + grid-column-end: 3; + width: 100%; + height: calc(var(--sz) * 25rem); + margin-top: calc(var(--sz) * -1rem); + display: grid; + grid-template-columns: 1fr 27% 1fr; + } + + #ns1spine { + display: grid; + padding: calc(var(--sz) * 4rem) 0; + } + + #ns1spine button { + all: unset; + background: transparent; + } + + #ns1spine button:active { + background: #363636; + } + + #wingL { + background: rgb(28 28 28); + -webkit-clip-path: polygon(100% 15%, 100% 85%, 0 100%, 0 0); + clip-path: polygon(100% 15%, 100% 85%, 0 100%, 0 0); + margin-left: calc(var(--sz) *-.714rem); + border-right: solid #121212 calc(var(--sz) * .15rem); + } + + #wingR { + background: rgb(28 28 28); + -webkit-clip-path: polygon(100% 0, 100% 100%, 0 85%, 0 15%); + clip-path: polygon(100% 0, 100% 100%, 0 85%, 0 15%); + margin-right: calc(var(--sz) *-.714rem); + border-left: solid #121212 calc(var(--sz) * .15rem); + } + + .ALControlsContainer { + display: flex; + gap: calc(var(--sz) * .25rem); + place-items: center; + padding-top: calc(var(--sz) * 2rem); + margin-top: calc(var(--sz) * 2rem); + border-top: groove rgb(0 0 0 / 42%) calc(var(--sz) * 0.2rem); + justify-content: space-evenly; + flex-wrap: wrap; + flex-direction: row; + width: 100%; + } + + .ALControlsContainer.noApps { + padding-top: 0; + margin-top: 0; + border-top: 0; + } + + .ALControlsContainer > div { + display: grid; + justify-items: center; + justify-content: center; + } + + .left-pocket-controls { + display: flex; + column-gap: calc(var(--sz) * 2rem); + row-gap: calc(var(--sz) * 0.5rem); + flex-wrap: wrap; + justify-content: center; + } + + .ALControlsContainer .row { + display: flex; + align-items: center; + gap: calc(var(--sz) * 0.5rem); + padding: calc(var(--sz) * 0.75rem); + background: rgb(255 255 255 / 6%); + border: inset #000 calc(var(--sz) * 0.2rem); + } + + .ALControlsContainer .row.noframes { + background: none; + border: 0; + } + + .right-pocket-controls > .row { + column-gap: calc(var(--sz) * 0.5rem); + line-height: calc(var(--sz) * .4rem); + } + + .remote-button { + height: calc(var(--sz) * 3.572rem); + width: calc(var(--sz) * 3.572rem); + border: solid black calc(var(--sz) * 0.0714rem); + border-radius: 100%; + display: grid; + justify-content: center; + align-content: center; + color: rgb(198 198 198); + background: rgb(33 33 33); + box-shadow: rgb(0 0 0 / 13%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.143rem 0); + cursor: pointer; + line-height: normal; + user-select: none; + padding: calc(var(--sz) * 0.2857rem); + } + + #power-button { + height: calc(var(--sz) * 2.8rem); + width: calc(var(--sz) * 2.8rem); + margin-bottom: calc(var(--sz) * -0.643rem); + } + + .shield-remote-body .remote-button { + height: calc(var(--sz) * 3rem); + width: calc(var(--sz) * 3rem); + } + + .apple-remote-body .remote-button { + height: calc(var(--sz) * 4.75rem); + width: calc(var(--sz) * 4.75rem); + } + + .apple-remote-body.AR2 .remote-button { + height: calc(var(--sz) * 4.5rem); + width: calc(var(--sz) * 4.5rem); + } + + .apple-remote-body.AR1 #back-button.remote-button, .apple-remote-body.AR2 #back-button.remote-button { + font-size: calc(var(--sz) * 1rem); + font-weight: 600; + } + + .apple-remote-body.AR2 #search-button.remote-button, .apple-remote-body.AR3 #back-button.remote-button { + background: linear-gradient(rgb(0, 0, 0) 0%, rgb(48, 48, 48) 100%); + outline: solid #2b2b2b calc(var(--sz) * 0.01rem); + } + + + .chromecast-remote-body .remote-button, .chromecast-remote-body #keyboard-button { + background: #fff; + border: solid #bfbfbf calc(var(--sz) * 0.02em); + height: calc(var(--sz) * 4.2rem); + width: calc(var(--sz) * 4.2rem); + box-shadow: rgb(0 0 0 / 5%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.1428rem) 0; + } + + .chromecast-remote-body.CC2 .remote-button { + background: #d5e0e4; + border-color: #a6a6a6; + } + + .chromecast-remote-body.CC3 .remote-button { + background: #efddd6; + border-color: #bababa; + } + + .chromecast-remote-body .srcButton { + border: solid #bfbfbf calc(var(--sz) * 0.02em); + height: calc(var(--sz) * 4.2rem); + width: calc(var(--sz) * 4.2rem); + border-radius: 100%; + } + + .chromecast-remote-body #keyboard-button { + background: #484848; + } + + .chromecast-remote-body #keyboard-button > ha-icon { + color: rgb(235, 235, 234); + } + + .chromecast-remote-body.CC2 #keyboard-button { + background: linear-gradient(0deg, rgba(186,214,198,1) 0%, rgba(222,239,231,1) 90%); + border-color: #a6a6a6; + } + + .chromecast-remote-body.CC3 #keyboard-button { + background: linear-gradient(0deg, rgba(221,158,152,1) 0%, rgba(228,186,180,1) 90%); + border-color: #a6a6a6; + } + + .chromecast-remote-body.CC2 #keyboard-button > ha-icon, + .chromecast-remote-body.CC3 #keyboard-button > ha-icon { + color: #fff; + } + + .apple-remote-body .srcButton { + border: solid #bfbfbf calc(var(--sz) * 0.02em); + height: calc(var(--sz) * 4.75rem); + width: calc(var(--sz) * 4.75rem); + border-radius: 100%; + } + + .shield-remote-body #power-button, .XM2 #power-button{ + height: calc(var(--sz) * 3rem); + width: calc(var(--sz) * 3rem); + margin-bottom: unset; + } + + .XM2 #power-button, .XM2 #keyboard-button, .AL2 #power-button, .AL2 #keyboard-button { + height: calc(var(--sz) * 3.572rem); + width: calc(var(--sz) * 3.572rem); + } + + .roku-remote-body #keyboard-button { + height: calc(var(--sz) * 2.25rem); + width: 100%; + } + + .AL2 #power-button { + margin-bottom: unset; + } + + .right { + display: flex; + width: 100%; + justify-content: flex-end; + } + + .apple-remote-body #power-button { + background: none; + border: gray 0.01rem solid; + transform: scale(0.75); + margin: 0; + } + + .shield-remote-body #power-button > ha-icon { + color: #851313; + } + + .apple-remote-body #power-button > ha-icon { + color: rgb(33, 33, 33); + } + + .roku-remote-body #power-button > ha-icon { + color: #970000; + } + + .XM2 #power-button > ha-icon { + color: #835437; + } + + #headset-button { + height: calc(var(--sz) * 2.8rem); + width: calc(var(--sz) * 2.8rem); + margin-bottom: calc(var(--sz) * -0.643rem); + } + + .shield-remote-body #home-button { + --mdc-icon-size: 17px; + } + + .shield-remote-body.ns1-body #home-button { + --mdc-icon-size: 23px; + } + + .apple-remote-body #back-button { + --mdc-icon-size: 36px; + } + + .shield-remote-body #back-button { + --mdc-icon-size: 41px; + } + + .notch { + background: #181818; + height: calc(var(--sz) * 1rem); + width: calc(var(--sz) * 0.4rem); + margin-top: calc(var(--sz) * -0.5rem); + border-radius: calc(var(--sz) * 0.2rem); + } + + .apple-remote-body .notch { + margin: 0; + width: calc(var(--sz) * 1rem); + height: calc(var(--sz) * 0.4rem); + } + + .chromecast-remote-body .notch { + height: calc(var(--sz) * 0.8rem); + width: calc(var(--sz) * 0.3rem); + rotate: 90deg; + margin: 0; + } + + .shieldNotch { + grid-column: 1 / 3; + background: rgb(24, 24, 24); + height: calc(var(--sz) * 0.3rem); + width: calc(var(--sz) * 0.75rem); + margin-top: calc(var(--sz) * -0.5rem); + border-radius: calc(var(--sz) * 0.2rem); + } + + .notchtall { + margin-bottom: calc(var(--sz) * 1.65rem); + } + + .ns1-body .shieldNotch { + height: calc(var(--sz) * 0.75rem); + width: calc(var(--sz) * 2.2rem); + border: solid #282828 calc(var(--sz) * 0.12rem); + border-radius: calc(var(--sz) * 0.5rem); + } + + #keyboard-button { + height: calc(var(--sz) * 3rem); + width: calc(var(--sz) * 3rem); + } + + .teal { + background: #09727e; + } + + .dpadContainer{ + grid-column: 1 / 4; + display: grid; + margin-bottom: calc(var(--sz) * 0.65rem); + width: calc(var(--sz) * 11.286rem); + height: calc(var(--sz) * 11.286rem); + position: relative; + } + + .chromecast-remote-body .dpadContainer, + .apple-remote-body .dpadContainer { + grid-column: 1 / 3; + margin-bottom: 0; + } + + .apple-remote-body.AR2 .dpadContainer { + overflow: hidden; + } + + .XM2 .dpadContainer, + .XM.dpadContainer { + align-items: center; + justify-items: center; + position: relative; + } + + .shieldDpad { + grid-column: 1 / 3; + width: calc(var(--sz) * 8.2rem); + height: calc(var(--sz) * 8.2rem); + position: relative; + } + + .ALControlsContainer .dpadContainer { + grid-column: unset; + } + + .centerbutton{ + all: unset; + cursor: pointer; + border: solid black calc(var(--sz) * 0.0714rem); + margin-left: calc(var(--sz) * 2.357rem); + margin-top: calc(var(--sz) * 2.357rem); + width: calc(var(--sz) * 6.428rem); + height: calc(var(--sz) * 6.428rem); + border-radius: 100%; + position: absolute; + background: rgba(55,55,55,1); + box-shadow: inset 0 0.calc(var(--sz) * 2857rem) calc(var(--sz) * 0.1428rem) calc(var(--sz) * -0.1428rem) #000000d9; + z-index: 5; + } + + .directionButtonContainer{ + transform: rotate(45deg); + border: calc(var(--sz) * 0.0714rem) solid black; + display: grid; + grid-template-columns: auto auto; + border-radius: 100%; + overflow: hidden; + box-shadow: rgb(20 20 20) calc(var(--sz) * 0.1428rem) calc(var(--sz) * 0.1428rem) calc(var(--sz) * 0.4285rem); + position: relative; + } + + .AR2 .directionButtonContainer { + margin-top: calc(var(--sz) * -1.4rem); + margin-left: calc(var(--sz) * -1.35rem); + border-radius: 0; + border: 0; + overflow: unset; + box-shadow: none; + } + + .chromecast-remote-body .directionButtonContainer, + .CC .directionButtonContainer { + border: calc(var(--sz) * 0.0714rem) solid #9d9d9d; + box-shadow: rgb(0 0 0 / 29%) 0 0 calc(var(--sz) * 0.4rem) calc(var(--sz) * 0.02rem); + } + + .dpadbutton{ + all: unset; + cursor: pointer; + width: calc(var(--sz) * 5.5714rem); + height: calc(var(--sz) * 5.5714rem); + background: #141414; + color: white; + font-size: calc(var(--sz) * 1rem); + outline: solid #2e2e2e calc(var(--sz) * 0.0714rem); + } + + .AR2 .dpadbutton { + width: calc(var(--sz) * 7rem); + height: calc(var(--sz) * 7rem); + outline: none; + } + + .XM2 .dpadbutton, + .XM .dpadbutton { + background: rgb(28 28 28); + border: solid rgb(15 15 15) calc(var(--sz) * 0.03rem); + outline: none; + box-sizing: border-box; + } + + .XM2 .dpadbutton:active, + .XM .dpadbutton:active { + background: rgb(24 24 24); + border: solid #000 calc(var(--sz) * 0.15rem); + } + + .chromecast-remote-body.CC1 .dpadbutton, + .CC .dpadbutton { + background: #fff; + outline: solid #c5c5c5 calc(var(--sz) * 0.0714rem); + } + + .chromecast-remote-body.CC2 .dpadbutton, + .CC2 .dpadbutton { + background: #cad4d8; + outline: solid #c5c5c5 calc(var(--sz) * 0.0714rem); + } + + .chromecast-remote-body.CC3 .dpadbutton, + .CC3 .dpadbutton { + background: #e1d2cc; + outline: solid #c5c5c5 calc(var(--sz) * 0.0714rem); + } + + .AR3 .dpadbutton:nth-child(1)::after, + .AR1 .dpadbutton:nth-child(1)::after { + content: "•"; + text-align: center; + display: block; + font-weight: 600; + color: rgb(198, 198, 198); + padding-right: calc(var(--sz) * 1.2rem); + padding-bottom: calc(var(--sz) * 1.2rem); + } + + .AR3 .dpadbutton:nth-child(2)::after, + .AR1 .dpadbutton:nth-child(2)::after { + content: "•"; + text-align: center; + display: block; + font-weight: 600; + color: rgb(198, 198, 198); + padding-left: calc(var(--sz) * 1.2rem); + padding-bottom: calc(var(--sz) * 1.2rem); + } + + .AR3 .dpadbutton:nth-child(3)::after, + .AR1 .dpadbutton:nth-child(3)::after { + content: "•"; + text-align: center; + display: block; + font-weight: 600; + color: rgb(198, 198, 198); + padding-right: calc(var(--sz) * 1.2rem); + padding-top: calc(var(--sz) * 1.2rem); + } + + .AR3 .dpadbutton:nth-child(4)::after, + .AR1 .dpadbutton:nth-child(4)::after { + content: "•"; + text-align: center; + display: block; + font-weight: 600; + color: rgb(198, 198, 198); + padding-left: calc(var(--sz) * 1.2rem); + padding-top: calc(var(--sz) * 1.2rem); + } + + .dpadbutton:active { + background: #282828; + } + + .chromecast-remote-body .dpadbutton:active, + .CC .dpadbutton:active { + background: #efefef; + } + + .chromecast-remote-body.CC2 .dpadbutton:active { + background: #bfcbd0; + } + + .chromecast-remote-body.CC3 .dpadbutton:active { + background: #dbcac3; + } + + .dpadbuttonShield { + width: calc(var(--sz) * 4.101rem); + height: calc(var(--sz) * 4.101rem); + } + + .centerbuttonShield { + width: calc(var(--sz) * 5rem); + height: calc(var(--sz) * 5rem); + margin: 0px; + padding: 0px; + place-self: center; + position: absolute; + background: rgba(37,37,37,1); + background: radial-gradient(circle, rgba(28,28,28,1) 0%, rgba(37,37,37,1) 100%); + } + + .XM2 .centerbutton, + .XM .centerbutton { + background: rgb(28, 28, 28); + border: solid black calc(var(--sz) * 0.2rem); + width: calc(var(--sz) * 5rem); + height: calc(var(--sz) * 5rem); + margin: 0px; + position: absolute; + box-sizing: border-box; + } + + .chromecast-remote-body .centerbutton, + .CC .centerbutton { + background: linear-gradient(rgb(226 226 226) 0%, rgb(255, 255, 255) 70%); + box-shadow: inset rgb(0 0 0 / 10%) 0 calc(var(--sz) * 0.15rem) calc(var(--sz) * 0.4rem); + border: solid #dddddd calc(var(--sz) * 0.0714rem); + width: calc(var(--sz) * 4rem); + height: calc(var(--sz) * 4rem); + place-self: center; + position: absolute; + margin: 0; + padding: 0; + } + + .chromecast-remote-body.CC2 .centerbutton { + background: linear-gradient(0deg, rgb(208 218 225) 5%, rgb(181 194 199) 100%); + border: solid #bbc5c9 calc(var(--sz)* 0.0714rem); + } + + .chromecast-remote-body.CC3 .centerbutton { + background: linear-gradient(0deg, rgba(245,226,219,1) 0%, rgba(196,180,174,1) 100%); + border: solid #d9d1d1 calc(var(--sz)* 0.0714rem); + } + + .apple-remote-body .centerbutton, + .AR3 .centerbutton { + background: linear-gradient(180deg, rgba(0,0,0,1) 0%, rgba(48,48,48,1) 100%); + width: calc(var(--sz) * 6rem); + height: calc(var(--sz) * 6rem); + margin-left: calc(var(--sz) * 2.65rem); + margin-top: calc(var(--sz) * 2.65rem); + box-sizing: border-box; + transform-origin: center bottom; + outline: solid #2e2e2e calc(var(--sz) * 0.0714rem); + } + + .AR1 .centerbutton { + background: linear-gradient(180deg, rgb(147, 148, 150) 0%, rgb(207, 211, 213) 100%); + } + + .apple-remote-body.AR2 .centerbutton { + border-radius: 0; + border: 0; + outline: 0; + transform: scale(.75); + transform-origin: center; + text-align: center; + } + + .apple-remote-body.AR2 .dpadContainer button { + background: none; + color: transparent; + } + .apple-remote-body.AR2 .dpadContainer button:hover, + .apple-remote-body.AR2 .dpadContainer button:active { + color: #636363; + text-align: center; + } + + + .centerbutton:active { + transform: scale(95%); + } + + .chromecast-remote-body .centerbutton:active, + .CC .centerbutton:active { + transform: none; + box-shadow: inset rgb(0 0 0 / 19%) 0 calc(var(--sz) * 0.25rem) calc(var(--sz) * 0.4375rem) calc(var(--sz) * 0.125rem); + } + + + .apple-remote-body .centerbutton:active, + .AR3 .centerbutton:active { + transform: none; + border: calc(var(--sz) * 0.2rem) solid black; + } + + .AR3 .centerbutton:active { + background: linear-gradient(180deg, rgba(0,0,0,1) 15%, rgba(48,48,48,1) 100%); + } + + .XM1 .centerbutton:active, + .XM2 .centerbutton:active, + .XM .centerbutton:active { + transform: none; + border: solid #090909 calc(var(--sz) * 0.24rem); + background: rgb(26 26 26); + } + + .minimal.dpadContainer { + align-items: center; + justify-items: center; + display: grid; + grid-template-columns: auto calc(var(--sz) * 5rem) auto; + grid-template-rows: auto calc(var(--sz) * 5rem) auto; + margin: calc(var(--sz) * 1rem); + height: calc(var(--sz) * 10.3rem); + aspect-ratio: 1/1; + } + + .minimal .directionButtonContainer { + border-radius: 30%; + border: none; + box-shadow: none; + } + + .minimal .dpadbutton { + background: transparent; + outline: none; + display: grid; + height: 100%; + width: 100%; + transform-origin: center center; + align-self: start; + justify-items: center; + justify-content: center; + align-content: center; + } + + .minimal .dpadbutton > svg { + pointer-events: none; + } + + .minimal .dpadbutton:active { + transform: scale(0.8); + } + + .minimal .dpadbutton > svg { + display: inline-block; + transform: scale(calc(var(--sz) * .55)); + } + + .minimal .centerbutton { + border: calc(var(--sz) * 0.175rem) solid rgb(179 179 179); + background: transparent; + border-radius: 25%; + width: calc(var(--sz) * 3rem); + height: calc(var(--sz) * 3rem); + margin: 0; + position: unset; + } + + .minimal.dpadContainer #up-button { + align-content: flex-start; + } + + .minimal.dpadContainer #right-button { + justify-content: flex-end; + } + + .minimal.dpadContainer #down-button { + align-content: flex-end; + } + + .minimal.dpadContainer #left-button { + justify-content: flex-start; + } + + .remote-button:active { + box-shadow: inset rgb(0 0 0 / 13%) 0 calc(var(--sz) * 0.2857rem) calc(var(--sz) * 0.1428rem) 0; + } + + .remote-button > ha-icon { + color: #c6c6c6; + } + + .remote-button:active > ha-icon, .remote-button:active > .mdiSubstituteIconWrapper { + color: #bcbcbc; + transform: scale(calc(var(--sz) * 0.85)); + } + + .chromecast-remote-body ha-icon { + color: #686868; + } + + .chromecast-remote-body .remote-button:active { + box-shadow: rgb(0 0 0 / 5%) 0px calc(var(--sz) * 0.125rem) calc(var(--sz) * 0.125rem) calc(var(--sz) * 0.1875rem) inset; + } + + .chromecast-remote-body .remote-button:active > ha-icon { + color: #000; + } + + .square { + border-radius: 0; + border: 0; + padding: 0; + } + + .round-top { + border-radius: 100% 100% 0 0; + border-bottom: 0; + box-shadow: none; + height: calc(var(--sz) * 3.92857rem); + margin-bottom: calc(var(--sz) * -0.5rem); + } + + .round-bottom { + border-radius: 0 0 100% 100%; + border-top: 0; + height: calc(var(--sz) * 3.92857rem); + margin-top: calc(var(--sz) * -0.5rem); + } + + .apple-remote-body .round-bottom, .apple-remote-body.AR2 .round-bottom { + height: calc(var(--sz) * 5.25rem); + } + + .square:active, .round-bottom:active { + box-shadow: none; + } + + .remote-body #volume-up-button, .remote-body #volume-down-button { + font-size: calc(var(--sz) * 2.5rem); + } + .apple-remote-body #volume-up-button, .apple-remote-body #volume-down-button { + font-size: calc(var(--sz) * 2.25rem); + } + .remote-body #programmable-one-button, .remote-body #programmable-two-button { + font-size: calc(var(--sz) * 1.75rem); + } + + .remote-body #volume-up-button:active, .remote-body #volume-down-button:active { + font-size: calc(var(--sz) * 2rem); + } + .apple-remote-body #volume-up-button:active, .apple-remote-body #volume-down-button:active { + font-size: calc(var(--sz) * 1.75rem); + } + .remote-body #programmable-one-button:active, .remote-body #programmable-two-button:active { + font-size: calc(var(--sz) * 1.45rem); + } + + .srcButton { + height: calc(var(--sz) * 2rem); + width: calc(var(--sz) * 5.714rem); + border: solid #090909 calc(var(--sz) * 0.0714rem); + border-radius: calc(var(--sz) * 2rem); + display: grid; + justify-items: center; + align-content: center; + color: rgb(198 198 198); + background: rgb(33 33 33); + box-shadow: rgb(0 0 0 / 25%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.1428rem) 0; + cursor: pointer; + line-height: normal; + user-select: none; + font-size: calc(var(--sz) * 1.14rem); + padding: calc(var(--sz) * 0.285rem); + white-space: nowrap; + transform: translate3d(0,0,0); /* because ios safari mobile */ + overflow: hidden; + transition: filter 250ms; + filter: var(--appButtonFilter); + } + + .apple-remote-body .srcButton { + border: none; + box-shadow: rgb(0 0 0 / 13%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.143rem 0); + transition: none; + } + + .chromecast-remote-body .srcButton { + filter: grayscale(40%); + box-shadow: rgb(0 0 0 / 5%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.1428rem) 0; + } + + .srcButton:active, .srcButton.appActive { + filter: none; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.142rem) rgb(255 255 255 / 20%); + transition: filter 0s; + } + + .srcButton.appActiveUnknown { + filter: grayscale(0%) brightness(100%); + transition: filter 0s; + } + + .shield-remote-body .srcButton { + height: calc(var(--sz) * 3rem); + width: calc(var(--sz) * 8rem); + } + + .srcButton:active { + filter: brightness(90%); + transform: scale(.95); + box-shadow: none; + transform-origin: center bottom; + } + + .chromecast-remote-body .srcButton:active { + filter: none; + } + + .srcButton svg { + pointer-events: none; + max-width: calc(var(--sz) * 4.5rem); + max-height: calc(var(--sz) * 1.6rem); + } + + .shield-remote-body .srcButton svg { + max-height: calc(var(--sz) * 2.1rem); + max-width: calc(var(--sz) * 6.8rem); + } + + .chromecast-remote-body .srcButton svg, .apple-remote-body .srcButton svg { + max-width: calc(var(--sz) * 3.25rem); + max-height: calc(var(--sz) * 3.25rem); + } + + .apple-remote-body .srcButton svg { + max-width: calc(var(--sz) * 3.55rem); + max-height: calc(var(--sz) * 3.55rem); + } + + .chromecastBottomIndentedRow { + display: grid; + width: calc(100% - (var(--sz) * 1.2rem) - (var(--sz) * 0.4rem * 2)); + padding: calc(var(--sz) * 0.4rem); + box-sizing: border-box; + grid-template-columns: auto 1fr auto; + box-shadow: rgb(0 0 0 / 28%) 0 calc(var(--sz) * 0.12rem) calc(var(--sz) * 0.4rem) inset; + border-radius: calc(var(--sz) * 5rem); + grid-column: 1 / 3; + place-items: center; + margin-top: calc(var(--sz) * 1rem); + } + + .chromecastBottomIndentedRow .remote-button, .chromecastBottomIndentedRow .input-button { + height: calc(var(--sz) * 2.3rem) !important; + width: calc(var(--sz) * 2.3rem) !important; + margin: 0 !important; + } + + .chromecastVolumeRocker { + grid-column: 1 / 3; + place-items: center; + width: calc(100% - (var(--sz) * 1.2rem) - (var(--sz) * 0.4rem * 2)); + display: grid; + grid-template-columns: 1fr 1fr; + } + + .chromecastVolumeRocker > button { + height: calc(var(--sz) * 2rem) !important; + width: 100% !important; + box-shadow: rgb(0 0 0 / 5%) 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.1428rem) 0; + } + + .chromecastVolumeRocker #volume-down-button { + border-radius: calc(var(--sz) * 2rem) 0 0 calc(var(--sz) * 2rem); + border-right: calc(var(--sz) * 0.05rem) solid #ebebeb; + } + + .chromecastVolumeRocker #volume-up-button { + border-radius: 0% calc(var(--sz) * 2rem) calc(var(--sz) * 2rem) 0; + border-left: 0; + } + + .digit-pad-button { + font-size: calc(var(--sz) * 1rem); + font-weight: 600; + height: calc(var(--sz) * 2rem); + border-radius: calc(var(--sz) * 1em); + } + + .digit-pad-button:active { + font-size: calc(var(--sz) * 0.9rem); + } + + .deviceNameTop { + white-space: nowrap; + font-size: calc(var(--sz) * 1rem); + overflow: hidden; + color: var(--devicenamecolor); + margin-left: -1rem; + text-align: center; + display: grid; + height: calc(var(--sz) * 2rem); + align-items: center; + align-content: center; + justify-items: center; + } + + .AF6 .deviceNameTop, + .AFJTV .deviceNameTop, + .AFXF2 .deviceNameTop{ + grid-column: 1 / 4; + display: block; + text-align: center; + margin: 0; + padding: 0; + } + + .AL1 .deviceNameTop, .AL2 .deviceNameTop { + margin: 0rem 0px calc(var(--sz) * 1.25rem); + display: block; + height: unset; + overflow: unset; + font-size: calc(var(--sz) * 1.25rem); + } + + .roku-remote-body .deviceNameTop { + margin-left: 0; + height: auto; + } + + .roku-remote-body.RSR .deviceNameTop { + position: absolute; + } + + .shield-remote-body .deviceNameTop { + display: unset; + align-items: unset; + align-content: unset; + justify-items: unset; + margin-left: 0; + margin-top: -1rem; + } + + .shield-remote-body.ns1-body .deviceNameTop { + margin-top: 0rem; + margin-bottom: -0.75rem; + } + + .XM2 .deviceNameTop { + grid-column: 1 / 4; + margin: calc(var(--sz) * -1rem) 0 0; + } + + .chromecast-remote-body .deviceNameTop { + grid-column: 1 / 3; + margin: unset; + margin-top: calc(var(--sz) * 1rem); + height: unset; + display: block; + } + + .apple-remote-body .deviceNameTop { + grid-column: 1 / 3; + margin: 0 0 calc(var(--sz) * -1.8rem) 0; + } + + .apple-remote-body.AR1 .deviceNameTop { + margin: calc(var(--sz) * -4rem) 0 calc(var(--sz) * -2.5rem) 0; + } + + .deviceNameBottom { + grid-column: 1/4; + color: var(--devicenamecolor); + font-size: calc(var(--sz) * 1.25rem); + margin-bottom: calc(var(--sz) * -2.5rem); + margin-top: calc(var(--sz) * .5rem); + white-space: nowrap; + overflow: hidden; + width: 100%; + text-align: center; + align-self: flex-end; + } + + .AF6 .deviceNameBottom { + margin-bottom: calc(var(--sz) * -1rem); + } + + .AL1 .deviceNameBottom, .AL2 .deviceNameBottom { + margin-bottom: unset; + margin-top: calc(var(--sz) * 1.8rem); + overflow: unset; + } + + .apple-remote-body .deviceNameBottom { + grid-column: 1 / 3; + margin-bottom: calc(var(--sz) * -1.25rem); + } + + .shield-remote-body .deviceNameBottom { + grid-column: 1 / 3; + font-size: calc(var(--sz) * 1rem); + margin-top: 0px; + margin-bottom: 0px; + position: absolute; + bottom: calc(var(--sz) * 0.3rem); + } + + .chromecast-remote-body .deviceNameBottom { + grid-column: 1 / 3; + margin-right: unset; + margin-bottom: calc(var(--sz) * 1rem); + margin-left: unset; + margin-top: calc(var(--sz) * 0.5rem); + height: unset; + display: block; + } + + .roku-remote-body.RTR .deviceNameBottom { + margin-bottom: calc(var(--sz) * 0.1rem); + margin-top: calc(var(--sz) * -1.25rem); + } + + .roku-remote-body.RWR .deviceNameBottom, + .roku-remote-body.RHR .deviceNameBottom { + position: absolute; + width: calc(var(--sz) * 11.45rem); + bottom: calc(var(--sz) * 3.5rem); + } + + .firemoteVersionNumber { + text-shadow: 0px 1px 0px #1e1e1e, 0px -1px 0px #000; + color: #4e4e4e; + align-self: right; + text-align: right; + width: 100%; + justify-self: right; + display: block !important; + font-size: calc(var(--sz) * .9rem); + position: absolute; + bottom: calc(var(--sz) * .35rem); + right: calc(var(--sz) * .4rem); + } + + .customLauncherBackground { + pointer-events: none; + position: absolute; + height: 100%; + width: 100%; + transform: scale(1.25); + z-index: 1; + overflow: hidden; + } + + .customLauncherTxt { + z-index: 2; + pointer-events: none; + } + + .customLauncherIcon { + display: flex; + max-width: 100%; + max-height: 100%; + transform: scale(var(--sz)); + display: flex; + align-items: center; + z-index: 2; + } + + .customLauncherImg { + pointer-events: none; + max-width: 100%; + max-height: 100%; + overflow: hidden; + z-index: 2; + } + + .AFJTV #tv-button, + .AFXF2 #tv-button{ + font-size: calc(var(--sz) * 0.8rem); + font-weight: 600; + } + + .colorButtons { + display: flex; + justify-content: space-around; + margin: calc(var(--sz) * 0.5rem) 0 calc(var(--sz) * 4.5rem) 0;; + } + + .AFJTV div.colorButtons button, + .AFXF2 div.colorButtons button{ + border-radius: 100%; + overflow: hidden; + height: calc(var(--sz) * 2.3rem); + width: calc(var(--sz) * 2.3rem); + } + + .AFJTV div.colorButtons button svg, + .AFXF2 div.colorButtons button svg { + height: calc(var(--sz) * 1.1rem); + width: calc(var(--sz) * 1.1rem); + filter: brightness(0.8) saturate(0.8); + pointer-events: none; + } + + .AFJTV div.colorButtons button:active svg, + .AFXF2 div.colorButtons button:active svg{ + filter: none; + transform: scale(0.85) + } + + .AFJTV .jvcbrandlogo { + height: 0; + display: flex; + justify-content: center; + align-items: center; + margin-top: calc(var(--sz) * -2rem); + } + + .AFJTV .jvcbrandlogo svg { + width: calc(var(--sz) * 5rem); + height: auto; + } + + .AFXF2 .xf2brandlogo { + height: 0; + display: flex; + justify-content: center; + align-items: center; + margin-top: calc(var(--sz) * -1.5rem); + } + + .AFXF2 .xf2brandlogo svg { + width: calc(var(--sz) * 7rem); + height: auto; + } + + /* Homatics styles */ + .homatics-remote-body { + align-content: start; + grid-row-gap: calc(var(--sz)* 1rem); + background: #ebebea; + border-color: #dcdcdc; + border-radius: calc(var(--sz)* 2.8rem); + min-height: calc(var(--sz)* 48.5rem); + padding: calc(var(--sz) * 1.1rem) calc(var(--sz) * 0.714rem) calc(var(--sz) * 2.143rem) calc(var(--sz) * 0.714rem); + box-shadow: 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.214rem) rgb(0 0 0 / 10%); + } + + .homatics-remote-body.HO2, + .homatics-remote-body.HO4 { + background: #252525; + border-color: #2f2f2f; + } + + .HO1 .remote-button, + .HO3 .remote-button { + background: #fff; + color: rgb(106, 106, 106); + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + box-shadow: rgb(0 0 0 / 15%) 0px calc(var(--sz)* 0.063rem) calc(var(--sz)* 0.188rem); + } + + .HO2 .remote-button, + .HO4 .remote-button { + background: #2f2f2f; + border: solid rgb(15 15 15) calc(var(--sz)* 0.0714rem); + } + + .homatics-remote-body .remote-button:active { + box-shadow: inset rgb(0 0 0 / 42%) 0px calc(var(--sz)* 0.1rem) calc(var(--sz)* 0.188rem); + filter: brightness(0.94); + border: solid rgb(232 232 232) calc(var(--sz)* 0.0714rem); + } + + .HO2 .remote-button:active, + .HO4 .remote-button:active { + border: solid rgb(10 10 10) calc(var(--sz)* 0.0714rem); + } + + .HO2 .litbutton, + .HO4 .litbutton { + box-shadow: 0 0 calc(var(--sz)* 0.857rem) calc(var(--sz)* 0.0714rem) rgb(171 253 255 / 15%); + } + + .HO1 .litbutton > ha-icon, + .HO3 .litbutton > ha-icon { + color: #00979b !important; + } + + .HO2 .litbutton > ha-icon, + .HO4 .litbutton > ha-icon { + color: #abfdff !important + } + + .homatics-remote-body .deviceNameTop { + grid-column: 1 / 4; + margin: unset; + height: unset; + display: block; + margin-bottom: calc(var(--sz) * -1rem); + } + + .HO1 #home-button.litbutton > ha-icon, .HO3 #home-button.litbutton > ha-icon { + color: #03585b !important + } + + .HO2 #home-button.litbutton > ha-icon, .HO4 #home-button.litbutton > ha-icon { + color: #45989a !important + } + + .homatics-remote-body .micHole { + background: black; + border-radius: 100%; + height: calc(var(--sz)* 0.3rem); + aspect-ratio: 1 / 1; + align-self: end; + justify-self: center; + } + + .homatics-remote-body #power-button, .homatics-remote-body #input-button { + height: calc(var(--sz)* 2.75rem); + width: calc(var(--sz)* 2.75rem); + } + + .remote-body.HO1 .remote-button > ha-icon, + .remote-body.HO3 .remote-button > ha-icon { + color: rgb(106 106 106); + } + + .remote-body.HO2 .remote-button > ha-icon { + color: rgb(245 245 245); + } + + .homatics-remote-body .remote-button.dark { + background: linear-gradient(180deg, rgb(168 168 168) 26%, rgb(144 144 144) 50%, rgb(128 128 128) 75%); + } + + .homatics-remote-body.HO2 .remote-button.dark, + .homatics-remote-body.HO4 .remote-button.dark { + background: linear-gradient(180deg, rgb(255 255 255) 26%, rgb(221 221 221) 50%, rgb(211 211 211) 75%); + } + + .homatics-remote-body .remote-button.dark > ha-icon { + color: #000; + } + + .homatics-remote-body.HO2 .remote-button.dark > ha-icon, + .homatics-remote-body.HO4 .remote-button.dark > ha-icon { + color: #4b4b4b; + } + + .remote-body.HO2 .remote-button.light > ha-icon { + color: #464646; + } + + .homatics-remote-body .micNLight { + display: grid; + align-content: space-between; + padding: calc(var(--sz) * 0.25rem) 0; + } + + .homatics-remote-body .activityLight { + background: #ff0000; + box-shadow: #ff0000 0 0 calc(var(--sz)* 0.6rem) calc(var(--sz)* 0.05rem); + border-radius: 100%; + height: calc(var(--sz)* 0.4rem); + aspect-ratio: 1 / 1; + opacity: 0; + } + + .homatics-remote-body.HO2 .activityLight, + .homatics-remote-body.HO4 .activityLight { + background: #adff87; + box-shadow: lime 0 0 calc(var(--sz)* 0.6rem) calc(var(--sz)* 0.05rem); + } + + .homatics-remote-body .digit-pad-button { + font-size: calc(var(--sz)* 1rem); + font-weight: 600; + height: calc(var(--sz)* 2.75rem); + width: calc(var(--sz)* 2.75rem); + border-radius: 100%; + margin-bottom: calc(var(--sz)* -0.6rem); + } + + .homatics-remote-body .colorButtons { + display: flex; + justify-content: space-around; + margin: calc(var(--sz) * 0.5rem) 0 0 0; + } + + .homatics-remote-body div.colorButtons button { + border-radius: 100%; + overflow: hidden; + height: calc(var(--sz) * 2.3rem); + width: calc(var(--sz) * 2.3rem); + } + + .homatics-remote-body div.colorButtons button svg{ + height: calc(var(--sz) * 1.1rem); + width: calc(var(--sz) * 1.1rem); + filter: brightness(0.8) saturate(0.8); + pointer-events: none; + } + + .homatics-remote-body div.colorButtons button:active svg{ + filter: none; + transform: scale(0.85) + } + + + .homatics-remote-body #bookmark-button, .homatics-remote-body #settings-button { + align-self: end; + } + + .homatics-remote-body #keyboard-button { + height: calc(var(--sz)* 3.572rem); + width: calc(var(--sz)* 3.572rem); + margin-bottom: calc(var(--sz)* 1.1rem); + } + + .homatics-remote-body .directionButtonContainer { + box-shadow: rgb(0 0 0 / 15%) calc(var(--sz)* 0.025rem) calc(var(--sz)* 0.025rem) calc(var(--sz)* 0.025rem) + background: #cacaca; + border: calc(var(--sz)* 0.0714rem) solid #cacaca; + } + + .homatics-remote-body .directionButtonContainer:has(#down-button:active) { + box-shadow: none; + } + + .homatics-remote-body .dpadContainer { + align-items: center; + justify-items: center; + position: relative; + margin-top: calc(var(--sz)* -1.5rem); + margin-bottom: calc(var(--sz)* -1.5rem); + isolation: isolate; + } + + .homatics-remote-body .dpadbutton, .homatics-remote-body .dpadbutton:active { + all: unset; + cursor: pointer; + width: calc(var(--sz)* 5.5714rem); + height: calc(var(--sz)* 5.5714rem); + outline: solid #a6a6a6 calc(var(--sz)* 0.0714rem); + } + + .homatics-remote-body .dpadbutton { + background: #adadad; + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + -webkit-tap-highlight-color: transparent; + } + + .HO2 .dpadbutton, + .HO4 .dpadbutton { + background: rgb(216 216 216); + outline: solid rgb(200 200 200) calc(var(--sz)* 0.0714rem); + } + + .homatics-remote-body .dpadbutton:active { + background: rgb(153 153 153); + box-shadow: inset #4f4f4f 0 0 calc(var(--sz)* 0.35rem) 0px; + transform: none; + filter: none; + overflow: hidden; + backdrop-filter: none; + appearance: none; + } + + .HO2 .dpadbutton:active, + .HO4 .dpadbutton:active { + background: rgb(200 200 200); + box-shadow: none; + outline: solid #989898 calc(var(--sz)* 0.0714rem); + } + + .homatics-remote-body .centerbutton { + background: linear-gradient(180deg, #c8c8c8 0%, rgba(255, 255, 255, 1) 100%); + border: solid #a1a1a1 calc(var(--sz)* 0.25rem); + width: calc(var(--sz)* 5.13rem); + height: calc(var(--sz)* 5.13rem); + margin: 0px; + position: absolute; + box-sizing: border-box; + isolation: isolate; + } + + .HO2 .centerbutton, + .HO4 .centerbutton { + background: linear-gradient(0deg, rgb(48 48 48) 26%, rgb(39 39 39) 50%, rgb(33 33 33) 75%); + border: solid #252525 calc(var(--sz)* 0.25rem); + } + + .homatics-remote-body .centerbutton:active { + transform: none; + filter: brightness(0.92); + border: solid #b3b3b3 calc(var(--sz)* 0.29rem); + } + + .HO2 .centerbutton:active, + .HO4 .centerbutton:active { + filter: brightness(0.8); + border: solid #000 calc(var(--sz)* 0.29rem); + } + + .homatics-remote-body #home-button { + align-self: end; + margin-top: calc(var(--sz)* 1.1rem); + z-index: 10; /* Required for companion app to prevent accidental push of down button */ + } + + .homatics-remote-body .volumeChannelSection { + grid-column-start: 1; + grid-column-end: 4; + display: grid; + grid-template-columns: 33% 1fr 33%; + grid-template-areas: "vol-up mute ch-up" + "vol-down mute ch-down"; + grid-row-gap: 0; + width: 100%; + justify-items: center; + align-items: center; + } + + .homatics-remote-body #volume-up-button-container { + grid-area: vol-up; + margin-bottom: 0; + border-bottom: 0; + font-size: calc(var(--sz)* 2rem); + } + + .homatics-remote-body #volume-up-button-container ha-icon, + .homatics-remote-body #channel-up-button-container ha-icon { + margin-top: calc(var(--sz)* -1rem); + } + + .homatics-remote-body #voltext, + .homatics-remote-body #chtext { + max-width: calc(var(--sz)* 1.5rem); + max-height: calc(var(--sz)* 1rem); + position: absolute; + padding-left: calc(var(--sz)* 1.0rem); + pointer-events: none; + margin-top: calc(var(--sz)* -0.5rem); + } + + .homatics-remote-body #voltext > * { + fill: rgb(106 106 106); + } + + .homatics-remote-body.HO2 #voltext > *, + .homatics-remote-body.HO4 #voltext > * { + fill: rgb(245, 245, 245); + } + + .homatics-remote-body #chtext { + max-width: calc(var(--sz)* 1rem); + padding-left: calc(var(--sz)* 1.25rem); + } + + .homatics-remote-body #chtext > * { + fill: rgb(106 106 106); + } + + .homatics-remote-body.HO2 #chtext > *, + .homatics-remote-body.HO4 #chtext > * { + fill: rgb(245, 245, 245); + } + + .homatics-remote-body #channel-up-button-container { + grid-area: ch-up; + margin-bottom: 0; + border-bottom: 0; + } + + .homatics-remote-body #mute-button { + grid-area: mute; + height: calc(var(--sz)* 2.6rem); + width: calc(var(--sz)* 2.6rem); + } + + #soundeqtext { + grid-area: mute; + max-width: calc(var(--sz)* 3.5rem); + max-height: calc(var(--sz)* 0.7rem); + margin-top: calc(var(--sz)* 4.1rem); + fill: rgb(106 106 106); + } + + .HO2 #soundeqtext, .HO4 #soundeqtext { + fill: rgb(245, 245, 245); + } + + .homatics-remote-body #volume-down-button { + grid-area: vol-down; + border-top: 0; + font-size: calc(var(--sz)* 2rem); + } + + .homatics-remote-body #volume-down-button ha-icon, + .homatics-remote-body #channel-down-button ha-icon { + margin-bottom: calc(var(--sz)* -1rem); + } + + .homatics-remote-body #channel-down-button { + grid-area: ch-down; + border-top: 0; + } + + .homatics-remote-body #volume-down-button, + .homatics-remote-body #channel-down-button { + box-shadow: rgb(0 0 0 / 15%) 0px calc(var(--sz)* 0.063rem) calc(var(--sz)* 0.1rem); + } + + .homatics-remote-body #volume-up-button, + .homatics-remote-body #channel-up-button { + box-shadow: none; + } + + .homatics-remote-body #volume-up-button:active, + .homatics-remote-body #channel-up-button:active { + filter: none; + background: linear-gradient(0deg, rgba(255, 255, 255, 1) 0%, rgba(240, 240, 240, 1) 95%); + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + border-bottom: 0; + } + + .homatics-remote-body.HO2 #volume-up-button:active, + .homatics-remote-body.HO2 #channel-up-button:active, + .homatics-remote-body.HO4 #volume-up-button:active, + .homatics-remote-body.HO4 #channel-up-button:active { + filter: none; + background: linear-gradient(0deg, rgb(46 46 46) 0%, rgb(38 38 38) 95%); + border: solid rgb(0 0 0) calc(var(--sz)* 0.08rem); + border-bottom: 0; + } + + .homatics-remote-body #volume-down-button:active, + .homatics-remote-body #channel-down-button:active { + filter: none; + background: linear-gradient(0deg, rgba(240, 240, 240, 1) 0%, rgba(255, 255, 255, 1) 95%); + box-shadow: none; + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + border-top: 0; + } + + .homatics-remote-body.HO2 #volume-down-button:active, + .homatics-remote-body.HO2 #channel-down-button:active, + .homatics-remote-body.HO4 #volume-down-button:active, + .homatics-remote-body.HO4 #channel-down-button:active { + filter: none; + background: linear-gradient(180deg, rgb(46 46 46) 0%, rgb(38 38 38) 95%); + box-shadow: none; + border: solid rgb(0 0 0) calc(var(--sz)* 0.08rem); + border-top: 0; + } + + .homatics-remote-body .afappsgrid { + margin-bottom: calc(var(--sz)* 7rem); + } + + .homatics-remote-body .srcButton { + border: solid #a7a7a7 calc(var(--sz) * 0.0714rem); + } + + .homaticsLogo { + position: absolute; + bottom: calc(var(--sz)* 3rem); + width: calc(var(--sz)* 12.286rem); + text-align: center; + } + + .homaticsLogo svg { + max-width: calc(var(--sz)* 6.5rem); + max-height: calc(var(--sz)* 3.2rem); + stroke: rgb(161 161 161); + } + + .HO2 .homaticsLogo svg, + .HO4 .homaticsLogo svg { + stroke: #000; + } + + .homatics-remote-body .deviceNameBottom { + position: absolute; + bottom: calc(var(--sz)* 3.5rem); + width: calc(var(--sz)* 12.286rem); + text-align: center; + } + + .homatics-remote-body .srcButton { + border-color: rgb(255 255 255 / 0%); + } + + .homatics-remote-body .srcButton.appActive { + box-shadow: rgb(0 0 0 / 13%) 0 calc(var(--sz)* 0.214rem) calc(var(--sz)* 0.1428rem) 0, white 0 0 calc(var(--sz)* 0.857rem) calc(var(--sz)* 0.429rem); + border-color: #e6e6e6; + animation-name: homatics_flash_border; + animation-duration: 2s; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + + .HO2 .srcButton.appActive, .HO4 .srcButton.appActive { + box-shadow: rgb(255 255 255 / 0%) 0 calc(var(--sz)* 0.214rem) calc(var(--sz)* 0.1428rem) 0, rgb(255 255 255 / 31%) 0 0 calc(var(--sz)* 0.857rem) calc(var(--sz)* 0.429rem); + } + + @keyframes homatics_flash_border { + 0% { + border-color: rgb(0 0 0 / 16%); + } + 50% { + border-color: #fff; + } + 100% { + border-color: rgb(0 0 0 / 16%); + } + } + + /* end Homatics styles */ + + + /* onn. styles */ + .remote-body.ON1, .remote-body.ON2 { + align-content: start; + grid-row-gap: calc(var(--sz)* 1rem); + background: #ebebea; + border-color: #dcdcdc; + border-radius: calc(var(--sz)* 2.8rem); + min-height: calc(var(--sz)* 48.5rem); + padding: calc(var(--sz) * 1.1rem) calc(var(--sz) * 0.714rem) calc(var(--sz) * 2.143rem) calc(var(--sz) * 0.714rem); + box-shadow: 0 calc(var(--sz) * 0.214rem) calc(var(--sz) * 0.214rem) rgb(0 0 0 / 10%); + } + + .remote-body.ON2 { + background: #252525; + border-color: #2f2f2f; + } + + .ON1 .remote-button, .ON2 .remote-button { + background: #fff; + color: rgb(106, 106, 106); + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + box-shadow: rgb(0 0 0 / 15%) 0px calc(var(--sz)* 0.063rem) calc(var(--sz)* 0.188rem); + } + + .ON2 .remote-button { + background: #2f2f2f; + border: solid rgb(15 15 15) calc(var(--sz)* 0.0714rem); + } + + .ON1 .remote-button:active, .ON2 .remote-button:active { + box-shadow: inset rgb(0 0 0 / 42%) 0px calc(var(--sz)* 0.1rem) calc(var(--sz)* 0.188rem); + filter: brightness(0.94); + border: solid rgb(232 232 232) calc(var(--sz)* 0.0714rem); + } + + .ON2 .remote-button:active { + border: solid rgb(10 10 10) calc(var(--sz)* 0.0714rem); + } + + .remote-body.ON1 .remote-button.litbutton { + background: #ffffe7; + } + + .remote-body.ON1 #power-button.litbutton > ha-icon, + .remote-body.ON2 .remote-button.litbutton > ha-icon { + color: #017374 !important; + } + + .remote-body.ON2 #power-button.litbutton > ha-icon { + color: #ebef66 !important; + } + + .remote-body.ON1 .remote-button:active > ha-icon, + .remote-body.ON2 .remote-button:active > ha-icon { + transform: scale(calc(var(--sz)* 0.90)); + } + + .remote-body.ON1 .remote-button.dark, + .remote-body.ON2 .remote-button.dark { + background: linear-gradient(180deg, rgba(69,69,69,1) 26%, rgba(55,55,55,1) 50%, rgba(45,45,45,1) 75%); + } + + .remote-body.ON2 .remote-button.light { + background: linear-gradient(0deg, #c8c8c8 0%, rgba(255, 255, 255, 1) 100%); + } + + .remote-body.ON1 .remote-button > ha-icon { + color: rgb(106 106 106); + } + + .remote-body.ON2 .remote-button > ha-icon { + color: rgb(245 245 245); + } + + .remote-body.ON1 .remote-button.dark > ha-icon { + color: #ebebea; + } + + .remote-body.ON2 .remote-button.light > ha-icon { + color: #464646; + } + + .remote-body.ON1 .remote-button.dark.litbutton { + background: #004040; + } + + .remote-body.ON1 .remote-button.litbutton > ha-icon, + .remote-body.ON1 .remote-button.dark.litbutton > ha-icon, + .remote-body.ON2 .remote-button.dark.litbutton > ha-icon { + color: #ffffab; + } + + .ON1 .directionButtonContainer, .ON2 .directionButtonContainer { + box-shadow: rgb(0 0 0 / 73%) calc(var(--sz)* 0.025rem) calc(var(--sz)* 0.025rem) calc(var(--sz)* 0.025rem); + background: black; + } + + .ON1 .directionButtonContainer:has(#down-button:active), .ON2 .directionButtonContainer:has(#down-button:active) { + box-shadow: none; + } + + .ON1 .dpadContainer, .ON2 .dpadContainer { + align-items: center; + justify-items: center; + position: relative; + margin-top: calc(var(--sz)* -1.5rem); + margin-bottom: calc(var(--sz)* -1.5rem); + isolation: isolate; + } + + .ON1 .dpadbutton, .ON1 .dpadbutton:active, + .ON2 .dpadbutton, .ON2 .dpadbutton:active { + all: unset; + cursor: pointer; + width: calc(var(--sz)* 5.5714rem); + height: calc(var(--sz)* 5.5714rem); + outline: solid #2e2e2e calc(var(--sz)* 0.0714rem); + } + + .ON1 .dpadbutton, .ON2 .dpadbutton { + background: rgba(55, 55, 55, 1); + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + -webkit-tap-highlight-color: transparent; + } + + .ON2 .dpadbutton { + background: rgb(216 216 216); + outline: solid rgb(200 200 200) calc(var(--sz)* 0.0714rem); + } + + .ON1 .dpadbutton:active, .ON2 .dpadbutton:active { + background: rgba(45, 45, 45, 1); + box-shadow: inset #161616 0 0 calc(var(--sz)* 0.35rem) 0px; + transform: none; + filter: none; + overflow: hidden; + backdrop-filter: none; + appearance: none; + } + + .ON2 .dpadbutton:active { + background: rgb(200 200 200); + box-shadow: none; + outline: solid #989898 calc(var(--sz)* 0.0714rem); + } + + .ON1 .centerbutton, .ON2 .centerbutton { + background: linear-gradient(180deg, #c8c8c8 0%, rgba(255, 255, 255, 1) 100%); + border: solid #2b2b2b calc(var(--sz)* 0.25rem); + width: calc(var(--sz)* 5.13rem); + height: calc(var(--sz)* 5.13rem); + margin: 0px; + position: absolute; + box-sizing: border-box; + isolation: isolate; + } + + .ON2 .centerbutton { + background: linear-gradient(0deg, rgb(48 48 48) 26%, rgb(39 39 39) 50%, rgb(33 33 33) 75%); + } + + .ON1 .centerbutton:active, .ON2 .centerbutton:active { + transform: none; + filter: brightness(0.92); + border: solid #2b2b2b calc(var(--sz)* 0.29rem); + } + + .ON2 .centerbutton:active { + filter: brightness(0.8); + } + + .ON1 #power-button, .ON1 #input-button, + .ON2 #power-button, .ON2 #magic-star-button { + height: calc(var(--sz)* 2.5rem); + width: calc(var(--sz)* 2.5rem); + } + + .ON1 #power-button, .ON2 #power-button { + justify-self: left; + } + + .ON1 .micNLight, .ON2 .micNLight { + display: grid; + align-content: space-between; + padding: calc(var(--sz) * 0.25rem) 0; + } + + .ON1 .activityLight, .ON2 .activityLight { + background: #adff87; + box-shadow: lime 0 0 calc(var(--sz)* 0.6rem) calc(var(--sz)* 0.05rem); + border-radius: 100%; + height: calc(var(--sz)* 0.4rem); + aspect-ratio: 1 / 1; + opacity: 0; + } + + .ON1 .micHole, .ON2 .micHole { + background: black; + border-radius: 100%; + height: calc(var(--sz)* 0.3rem); + aspect-ratio: 1 / 1; + align-self: end; + justify-self: center; + } + + .ON1 #input-button, .ON2 #magic-star-button { + justify-self: right; + } + + .ON1 #profile-button, .ON1 #settings-button, + .ON2 #profile-button, .ON2 #settings-button { + align-self: end; + } + + .ON1 #keyboard-button, .ON2 #keyboard-button { + height: calc(var(--sz)* 3.572rem); + width: calc(var(--sz)* 3.572rem); + margin-bottom: calc(var(--sz)* 1.1rem); + } + + .ON1 #home-button, .ON2 #home-button { + align-self: end; + margin-top: calc(var(--sz)* 1.1rem); + z-index: 10; /* Required for companion app to prevent accidental push of down button */ + } + + .ON1 .volumeChannelSection, .ON2 .volumeChannelSection { + grid-column-start: 1; + grid-column-end: 4; + display: grid; + grid-template-columns: 33% 1fr 33%; + grid-template-areas: "vol-up mute ch-up" + "vol-down mute ch-down"; + grid-row-gap: 0; + width: 100%; + justify-items: center; + align-items: center; + } + + .ON1 #volume-up-button, .ON2 #volume-up-button { + grid-area: vol-up; + margin-bottom: 0; + border-bottom: 0; + font-size: calc(var(--sz)* 2rem); + } + + .ON1 #channel-up-button, .ON2 #channel-up-button { + grid-area: ch-up; + margin-bottom: 0; + border-bottom: 0; + } + + .ON1 #mute-button, .ON2 #mute-button { + grid-area: mute; + height: calc(var(--sz)* 2.6rem); + width: calc(var(--sz)* 2.6rem); + } + + .ON1 #volume-down-button, .ON2 #volume-down-button { + grid-area: vol-down; + border-top: 0; + font-size: calc(var(--sz)* 2rem); + } + + .ON1 #channel-down-button, .ON2 #channel-down-button { + grid-area: ch-down; + border-top: 0; + } + + .ON1 #volume-up-button:active, .ON1 #channel-up-button:active { + filter: none; + background: linear-gradient(0deg, rgba(255, 255, 255, 1) 0%, rgba(240, 240, 240, 1) 95%); + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + border-bottom: 0; + } + + .ON2 #volume-up-button:active, .ON2 #channel-up-button:active { + filter: none; + background: linear-gradient(0deg, rgb(46 46 46) 0%, rgb(38 38 38) 95%); + border-bottom: 0; + } + + .ON1 #volume-down-button:active, .ON1 #channel-down-button:active { + filter: none; + background: linear-gradient(0deg, rgba(240, 240, 240, 1) 0%, rgba(255, 255, 255, 1) 95%); + box-shadow: none; + border: solid rgb(186 186 186) calc(var(--sz)* 0.0714rem); + border-top: 0; + } + + .ON2 #volume-down-button:active, .ON2 #channel-down-button:active { + filter: none; + background: linear-gradient(180deg, rgb(46 46 46) 0%, rgb(38 38 38) 95%); + box-shadow: none; + border-top: 0; + } + + .ON1 .afappsgrid, .ON2 .afappsgrid { + align-content: start; + margin: calc(var(--sz)* 0.75rem) 0 calc(var(--sz)* 3.75rem) 0; + } + + .ON1 .srcButton { + border-color: #ccc; + } + + .ON2 .srcButton { + border-color: #292929; + } + + .ON1 .srcButton.appActive, .ON2 .srcButton.appActive { + box-shadow: rgb(0 0 0 / 13%) 0 calc(var(--sz)* 0.214rem) calc(var(--sz)* 0.1428rem) 0, white 0 0 calc(var(--sz)* 0.857rem) calc(var(--sz)* 0.429rem); + border-color: #e6e6e6; + animation-name: flash_border; + animation-duration: 2s; + animation-timing-function: linear; + animation-iteration-count: infinite; + } + + .ON2 .srcButton.appActive { + box-shadow: rgb(255 255 255 / 0%) 0 calc(var(--sz)* 0.214rem) calc(var(--sz)* 0.1428rem) 0, rgb(255 255 255 / 31%) 0 0 calc(var(--sz)* 0.857rem) calc(var(--sz)* 0.429rem); + } + + @keyframes flash_border { + 0% { + border-color: rgb(0 0 0 / 16%); + } + 50% { + border-color: #fff; + } + 100% { + border-color: rgb(0 0 0 / 16%); + } + } + + .ON1 .srcButton:active, .ON2 .srcButton:active { + box-shadow: inset rgb(0 0 0 / 25%) 0 calc(var(--sz)* 0.214rem) calc(var(--sz)* 0.1428rem) 0; + } + + .ON1 .deviceNameTop, .ON2 .deviceNameTop { + grid-column: 1 / 4; + margin: calc(var(--sz)* -1rem) 0; + } + + .ON1 .deviceNameBottom, .ON2 .deviceNameBottom { + position: absolute; + bottom: calc(var(--sz)* 0.75rem); + margin: 0; + width: calc(var(--sz)* 12.286rem); + text-align: center; + } + + .onnLogo { + position: absolute; + bottom: calc(var(--sz)* 2rem); + width: calc(var(--sz)* 12.286rem); + text-align: center; + } + + .onnLogo svg { + max-width: calc(var(--sz)* 5rem); + max-height: calc(var(--sz)* 2rem); + } + + .ON2 .onnLogo svg { + fill: #949494; + } + /* end onn. styles */ + + ${unsafeCSS(launcherCSS)} + + .functionFindRemoteButton, .functionMuteButton, .functionRebootButton, + .functionSystemUpdateButton, .functionKeyboardButton, .functionMenuButton, .functionSettingsButton { + color: #ff0000; + font-weight: bold; + background: #000; + border: solid calc(var(--sz) * 0.1rem) #850000; + display: block; + } + + .functionAppSwitchButton, .functionAppManageButton { + background: #000; + } + + .ON1 .functionRebootButton, .ON2 .functionRebootButton { + background: #fff; + } + + .ON1 .functionSearchGoogleTVButton, .ON1 .functionCaptionsButton, .ON1 .functionpairingButton, + .ON2 .functionSearchGoogleTVButton, .ON2 .functionCaptionsButton, .ON2 .functionpairingButton { + background: #fff; + color: #000; + } + + .roku-remote-body .functionMuteButton, .roku-remote-body .functionVolumeUpButton, .roku-remote-body .functionVolumeDownButton, .roku-remote-body .functionFindRemoteButton, + .roku-remote-body .searchButton, .roku-remote-body .functionChannelUpButton, .roku-remote-body .functionChannelDownButton { + color: #c6c6c6; + font-weight: bold; + background: #662d91; + border: 0; + filter: none !important; + display: grid; + } + + .functionAppleSettingsButton { + background: linear-gradient(0deg, rgba(143,143,148,1) 0%, rgba(228,228,233,1) 100%); + border: solid #838383 calc(var(--sz) * 0.05rem) !important; + } + + .functionKeyboardButton { + display: inherit; + } + + .functionFindRemoteButton > ha-icon, .functionMuteButton > ha-icon, .functionRebootButton > ha-icon, .functionSettingsButton > ha-icon { + display: none; + } + + .ON1 .functionSystemUpdateButton, .ON2 .functionSystemUpdateButton { + background: #fff; + color: #000; + } + + .functionAppSwitchButton:active, .functionFindRemoteButton:active, .functionMuteButton:active, .functionRebootButton:active, .functionSettingsButton:active { + filter: none; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.142rem) rgb(255 255 255 / 20%) !important; + } + + .ON1 .functionFindRemoteButton, + .ON2 .functionFindRemoteButton { + color: yellow; + border: solid calc(var(--sz) * 0.1rem) #017374; + } + + .shield-remote-body .functionFindRemoteButton, .shield-remote-body .functionMuteButton, .shield-remote-body .functionSystemUpdateButton, + .shield-remote-body .functionMuteButton, .shield-remote-body .functionRebootButton, .shield-remote-body .functionSettingsButton, .shield-remote-body .functionCaptionsButton { + color: rgb(153, 231, 0); + border: solid calc(var(--sz) * 0.1rem) #456800; + } + + .functionFindRemoteButton { + font-size: calc(var(--sz) * .75rem); + } + .shield-remote-body .functionFindRemoteButton { + font-size: calc(var(--sz) * .75rem); + --mdc-icon-size: calc(var(--sz) * 1.4rem); + } + .roku-remote-body .functionFindRemoteButton { + font-size: calc(var(--sz) * .6rem); + } + + .functionMuteButton { + font-size: calc(var(--sz) * 1rem); + } + .shield-remote-body .functionMuteButton { + font-size: calc(var(--sz) * 1.2rem); + --mdc-icon-size: calc(var(--sz) * 1.6rem); + } + + .functionRebootButton { + font-size: calc(var(--sz) * 1rem); + } + .shield-remote-body .functionRebootButton { + font-size: calc(var(--sz) * 1rem); + --mdc-icon-size: calc(var(--sz) * 1.6rem); + } + .chromecast-remote-body .functionRebootButton { + font-size: calc(var(--sz) * 0.95rem); + background: #fff; + } + + .apple-remote-body .functionAppSwitchButton { + background: rgb(33, 33, 33); + } + + .functionSettingsButton { + font-size: calc(var(--sz) * .75rem); + } + .shield-remote-body .functionSettingsButton { + font-size: calc(var(--sz) * 0.86rem); + --mdc-icon-size: calc(var(--sz) * 1.5rem); + } + .chromecast-remote-body .functionSettingsButton, .chromecast-remote-body .functionSystemUpdateButton { + font-size: calc(var(--sz) * 0.7rem); + background: #fff; + } + + .functionControlCenterButton { + background: rgb(215 215 215); + border: solid #838383 calc(var(--sz) * 0.05rem) !important; + } + + .remote-logo { + grid-column-start: 1; + grid-column-end: 4; + padding: calc(var(--sz) * 2.5rem) calc(var(--sz) * 2.357rem) 0 calc(var(--sz) * 2.357rem); + width: calc(var(--sz) * 7.5714rem); + } + + .AF6 .remote-logo { + padding: calc(var(--sz) * .75rem) calc(var(--sz) * 2.357rem) 0 calc(var(--sz) * 2.357rem); + } + + .miLogo, .xiaomiLogo { + align-self: flex-end; + padding-bottom: calc(var(--sz) * 1.3rem) + } + + .miLogo { + width: calc(var(--sz) * 2.75rem); + } + + .ns1-body #keyboard-button { + margin-top: calc(var(--sz) * 1rem); + height: calc(var(--sz) * 5rem); + width: calc(var(--sz) * 5rem); + --mdc-icon-size: 34px; + } + + .ns1-body .remote-button:active { + border: solid #395600 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(153 231 0 / 20%); + } + + .ns1-body .remote-button:active > ha-icon { + color: #99e700 !important; + } + + .litbutton { + border: solid #4b4c3c 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 255 25 / 15%); + } + .litbutton > ha-icon { + color: yellow !important; + } + + .shield-remote-body .litbutton { + border: solid #500101 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 25 25 / 15%); + } + + .shield-remote-body .litbutton > ha-icon, + .roku-remote-body #power-button.litbutton > ha-icon { + color: red !important; + } + + .roku-remote-body #power-button.litbutton { + border: 0.0714rem solid rgb(92 0 0); + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(229 58 58 / 31%); + } + + .roku-remote-body.RTR #power-button { + background: #810000; + } + + .roku-remote-body.RTR #power-button > ha-icon { + color: #c6c6c6; + } + + .roku-remote-body.RTR #power-button.litbutton { + background: rgb(159, 0, 0); + border: 0.0714rem solid rgb(139, 62, 62); + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(229 164 164 / 31%); + } + + .roku-remote-body.RTR #power-button.litbutton > ha-icon { + color: #fff !important; + } + + .roku-remote-body #home-button.litbutton, .roku-remote-body #playpause-button.litbutton { + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 255 255 / 20%); + } + + .roku-remote-body #home-button.litbutton > ha-icon, .roku-remote-body #playpause-button.litbutton > ha-icon { + color: white !important; + } + + .XM2 .litbutton { + border: solid #5c2b00 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(229 124 58 / 31%); + } + + .XM2 .litbutton > ha-icon, .XM2 .litbutton > .mdiSubstituteIconWrapper { + color: #ff7700 !important; + } + + + .chromecast-remote-body .litbutton, .chromecast-remote-body .appActive { + position: relative; + z-index: 2; + } + + .chromecast-remote-body .appActive { + border: transparent; + } + + .chromecast-remote-body .litbutton::before, .chromecast-remote-body .appActive::before { + content: ""; + position: absolute; + z-index: -1; + inset: 0.25rem; + background: conic-gradient(from 0deg, rgba(66,133,244,1) 0%, rgba(219,68,55,1) 25%, rgba(244,180,0,1) 50%, rgba(15,157,88,1) 75%, rgba(66,133,244,1) 100%); + filter: blur(0.3rem) contrast(3); + transition: opacity 0.3s ease 0s; + border-radius: inherit; + animation: spin 7s linear infinite; + } + + .chromecast-remote-body .appActive::before { + inset: calc(var(--sz) * -0.25rem); + filter: contrast(3); + } + + @keyframes spin { + 0 { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + .chromecast-remote-body .litbutton::after, .chromecast-remote-body .appActive::after { + content: ""; + z-index: -1; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: inherit; + border-radius: inherit; + inset: 0.15rem; + } + + .chromecast-remote-body .litbutton > ha-icon { + color: #d70000 !important; + } + + .apple-remote-body .litbutton { + background: rgb(255 255 255 / 25%) !important; + border: 0.01rem solid gray !important; + box-shadow: none; + } + + .apple-remote-body .litbutton > ha-icon { + color: rgb(33, 33, 33) !important; + } + + .shield-remote-body.ns1-body .litbutton { + border: solid #395600 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(153 231 0 / 20%); + } + + .shield-remote-body.ns1-body .litbutton > ha-icon { + color: #99e700 !important; + } + + .dimlitbutton { + border: solid #34342b calc(var(--sz) * 0.0714rem); + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 255 116 / 15%); + } + .dimlitbutton > ha-icon { + color: #e5e59a !important; + } + + .apple-remote-body .dimlitbutton { + border: 0.01rem solid gray !important; + background: none !important; + box-shadow: none; + } + + .apple-remote-body .dimlitbutton > ha-icon { + color: rgb(33, 33, 33) !important; + } + + .shield-remote-body .dimlitbutton { + border: solid #3c1818 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 25 25 / 11%); + } + + .shield-remote-body .dimlitbutton > ha-icon { + color: #ff7575 !important; + } + + .XM2 .dimlitbutton { + border: solid #463327 0.0714rem; + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.0714rem) rgb(255 143 36 / 21%); + } + + .XM2 .dimlitbutton > ha-icon { + color: #b36d41 !important; + } + + ha-icon { + pointer-events: none; + transform: scale(var(--sz)); + } + + .eightygap { + height: calc(var(--sz) * 5.7143rem); + } + `; +//` + + getState() { + if(this._config.device_family === 'none' || this._config.entity === 'none') { return; } + if(this._config.android_tv_remote_entity == '' || typeof this._config.android_tv_remote_entity == 'undefined' || this._config.device_family == 'amazon-fire' ) { + return this.hass.states[this._config.entity]; + } + else { + return this.hass.states[this._config.android_tv_remote_entity]; + } + } + + getOpenAppID() { + if(this._config.device_family === 'none' || this._config.entity === 'none') { return; } + else if(this._config.device_family == 'roku') { + return this.hass.states[this._config.entity].attributes.app_name; + } + else if(this._config.android_tv_remote_entity == '' || typeof this._config.android_tv_remote_entity == 'undefined' || this._config.device_family == 'amazon-fire' ) { + return this.hass.states[this._config.entity].attributes.app_id; + } + else { + if(this.getState().state == 'on') { + return this.hass.states[this._config.android_tv_remote_entity].attributes.current_activity; + } + } + } + + createRenderRoot() { + const root = super.createRenderRoot(); + // This section prevents unwanted scrolling on iOS devices when double clicking in a Sections view #508 + root.addEventListener( + 'dblclick', + (e) => (e.preventDefault()) + ); + return root; + } + + + render() { + if (!this.hass || !this._config) { + return html``; + } + + const stateObj = this.hass.states[this._config.entity]; + if (!stateObj && this._config.entity != 'none') { + return html` Unknown entity: ${this._config.entity} `; + } + const entityId = this._config.entity; + const state = this.getState(); + const stateStr = state ? state.state : 'off'; + const appId = this.getOpenAppID(); + const deviceType = this._config.device_type; + const scale = (parseInt(this._config.scale) || 100)/100; + var launcherscaleoffset = 0; + if (this._config.app_launcher_relative_size) { launcherscaleoffset = (parseInt(this._config.app_launcher_relative_size) || 100)/100 }; + const launcherscale = scale + launcherscaleoffset; + const overrides = this._config.button_overrides; + var buttonHidingCss = ''; + if(overrides && typeof overrides === 'object') { + for (let [key, value] of Object.entries(overrides)) { + if(value && typeof value === 'object') { + for (let [action, actionvalue] of Object.entries(value)) { + if(action == 'hidden' && actionvalue == true) { + buttonHidingCss += '#'+key+' { opacity: 0; pointer-events: none; } '; + } + } + } + } + } + var AppLaunchButtonFilterCssValue = 'grayscale(25%) brightness(58%)'; + if(this._config.device_family == 'apple-tv') { + AppLaunchButtonFilterCssValue = 'grayscale(0%) brightness(100%)'; + } + else if (['onn', 'homatics'].includes(this._config.device_family) || ['ON1', 'ON2', 'HO1', 'HO2', 'HO3', 'HO4'].includes(this._config.defaultRemoteStyle_override)) { + AppLaunchButtonFilterCssValue = 'grayscale(25%) brightness(80%)'; + } + var guiEditorDirection = this.hass.config.language == 'he' ? 'rtl' : 'ltr'; + const devicenamecolor = this._config.visible_name_text_color || '#000000'; + var backgroundInherit = ''; + if (this._config.use_theme_background == true) { + backgroundInherit = 'background: var(--ha-card-background,var(--card-background-color,#fff))!important; border: inherit !important; border-radius: inherit !important;'; + } + if (this._config.useCustomSkin===true) { + var skin = '#4682b4'; + if(this._config.skin) { skin = this._config.skin; } + backgroundInherit = 'background: '+skin+' !important;'; + } + const cssVars = html ``; + + + + // Handle standard button highlight/lit states + var powerStatusClass = '' + var homeStatusClass = ''; + var playingStatusClass = ''; + var patchWallStatusClass = ''; + if ((this._config.hide_button_highlights != true)) { + + // Determine Power On/Off Status + if(stateStr != 'off' && stateStr != 'unavailable') { + powerStatusClass = ' litbutton'; + } + if(stateStr == 'standby') { + powerStatusClass = ' dimlitbutton'; + } + + // Determine Home Status + if(['com.amazon.tv.launcher', 'com.google.android.tvlauncher', 'com.google.android.apps.tv.launcherx', 'Home'].includes(appId)) { + homeStatusClass = ' litbutton'; + } + + // Determine Play/Pause + var alwaysRegisterdAsPlaying = ['com.amazon.firebat', 'com.android.systemui', 'com.android.vending', 'com.android.tv.settings', + 'com.nvidia.shieldtech.accessoryui', 'com.esaba.downloader', 'com.amazon.venezia']; + if(stateStr == 'playing' && !(alwaysRegisterdAsPlaying.includes(appId))) { + playingStatusClass = ' litbutton'; + } + + // Determine Patchwall Status + if(appId == 'com.mitv.tvhome.atv') { + patchWallStatusClass = ' litbutton'; + } + } + + // Get current device's Attributes AND use any applicable overrides from user conf + var confRef = this._config; + function getDeviceAttribute(deviceAttribute){ + return deviceAttributeQuery(deviceAttribute, confRef); + } + + // Rebuild AppMap - allow hdmi inputs where appropriate & add configured custom launchers from YAML + refreshAppMap(this._config, getDeviceAttribute('hdmiInputs'), getDeviceAttribute('avInputs'), getDeviceAttribute('tuner')); + + // get app button details from appmap json + function getAppButtonData(config, configvalue, want) { + if(appmap.has(configvalue)) { + var deviceFamily = config["device_family"]; + var familySpecificAppData = appmap.get(configvalue)[deviceFamily]; + if(want=="active") { + if (config["device_type"]=="fire_tv_stick_4k_max_second_gen" || config["device_type"]=="fire_tv_stick_4k_second_gen") { + return "appActiveUnknown"; + } + if (typeof appId != 'string') { return }; + if(familySpecificAppData && !(appmap.get(configvalue).androidName) && !(appmap.get(configvalue).androidName2) && !(appmap.get(configvalue).appName)) { + return (appId == familySpecificAppData["androidName"] || appId == familySpecificAppData["androidName2"] || appId == familySpecificAppData["appName"]) ? "appActive" : ""; + } + else { + return (appId == appmap.get(configvalue).androidName || appId == appmap.get(configvalue).androidName2) || appId == appmap.get(configvalue).appName ? "appActive" : ""; + } + } + else { + if (appmap.get(configvalue)[want]) { + return appmap.get(configvalue)[want]; + } + else if(familySpecificAppData) { + return familySpecificAppData[want]; + } + } + } + else { + return ' '; + } + } + + function drawAppLaunchButtons(e, config, cols=3, max=6) { + var spanclass = "three-col-span afappsgrid"; + if(cols == 2) { + spanclass = "two-col-span nsappsgrid"; + } + if(cols == 1) { + spanclass = "appLauncherVerticalAppsContainer"; + } + if(cols == 'fill') { + spanclass = "appLauncherAppsContainer"; + } + function showHide(buttonKey) { + if (buttonKey === '') { + return 'hidden'; + } + } + var buttonStyle = 'button'; + const appLaunchButtons = new Map(); + for(let i=1; i<=max; i++) { + var appid = config["app_launch_"+i] || ''; + appLaunchButtons.set("confBtn"+i, appid); + } + + // Properly apply default launcher buttons + var displayedRemote = config.defaultRemoteStyle_override || getDeviceAttribute("defaultRemoteStyle") || config.device_family; + if(['AF1', 'AF2', 'AF3', 'AF4', 'AF5', 'AF6', 'AL1', 'amazon-fire'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'prime-video'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'netflix'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'disney-plus'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'hulu'); + } + else if(['AR1', 'AR2', 'AR3', 'apple-tv'].includes(displayedRemote)) { + buttonStyle = 'button-round'; + } + else if(['CC1', 'CC2', 'CC3', 'chromecast'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'youtube'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'netflix'); + buttonStyle = 'button-round'; + } + else if(['NS1', 'NS2', 'nvidia-shield'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + } + else if(['XM1', 'XM2', 'xiaomi'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'prime-video'); + } + else if(['RSR', 'RVR'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'disney-plus'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'apple-tv'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'paramount-plus'); + } + else if(['RTR'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'disney-plus'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'hulu'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'roku-channel'); + } + else if(['RHR'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'hulu'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'roku-channel'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'disney-plus'); + } + else if(['RVRP', 'RWR', 'roku'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'netflix'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'disney-plus'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'apple-tv'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'hulu'); + } + else if(['AFJTV', 'AFXF2'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'prime-video'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'netflix'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'disney-plus'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'freeview-play'); + } + else if(['ON1', 'ON2'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'youtube'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'netflix'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'disney-plus'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'paramount-plus'); + } + else if(['HO1', 'HO2', 'HO3', 'HO4'].includes(displayedRemote)) { + appLaunchButtons.set("confBtn1", config.app_launch_1 || 'youtube'); + appLaunchButtons.set("confBtn2", config.app_launch_2 || 'netflix'); + appLaunchButtons.set("confBtn3", config.app_launch_3 || 'prime-video'); + appLaunchButtons.set("confBtn4", config.app_launch_4 || 'rocket-launcher-btn'); + } + + + // Return button HTML + if(['CC1', 'CC2', 'CC3', 'AR1', 'AR2', 'AR3'].includes(config.defaultRemoteStyle_override) || (['apple-tv', 'chromecast'].includes(config.device_family) && !(config.defaultRemoteStyle_override))) { + return html ` + ${ Array.from(appLaunchButtons.keys()).map(key => { + var val = appLaunchButtons.get(key); + if(val) { + return html ``; + } + })} + `; + } + else { + return html ` +
+ ${ Array.from(appLaunchButtons.keys()).map(key => { + var val = appLaunchButtons.get(key); + return html ``; + + })} +
+ `; + } + } + + // Draw optional device name + function drawDeviceName(e, config, section){ + if(!config.visible_name_text) { return }; + if(config.name_position=='bottom' && section=='bottom') { + return html`
${config.visible_name_text}
`; + } + else if(config.name_position=='top' && section=='top') { + return html`
${config.visible_name_text}
`; + } + return; + } + + // Draw optional Firemote Version number + function drawFiremoteVersionNumber(e, config){ + if(config.show_version_number === true) { + return html `
${HAFiremoteVersion}
`; + } + } + + //Draw Optional MediaControl buttons on CC + function drawMediaControlButtons(e, config) { + if (config.show_media_controls==true) { + return html` + + + + `; + } + return; + } + + // Reused SVG Logos + function renderfiretvlogo() { + return html` + + + `; + } + + function renderAmazonArrowlogo() { + return html` + `; + } + + function renderAmazonNameWithArrowLogo() { + return html``; + } + + function renderMiLogo() { + return html``; + } + + function renderXiaomiLogo() { + return html``; + } + + // Render Amazon Fire Remote Style AF1 + // TODO: AF1 does not seem to scale properly + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF1' ) { + return html` + + + ${cssVars} + +
+ +
+
+
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ +
+
+ + + + +
+ +
+ + + + + + + + + + ${drawDeviceName(this, this._config, 'bottom')} + ${renderAmazonNameWithArrowLogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Amazon Fire Remote Style AF2 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF2' ) { + return html` + + + ${cssVars} + +
+ +
+
+
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ +
+
+ + + + +
+ +
+ + + + + + + + + +
+ +
+ +
+
+
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${renderAmazonArrowlogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Amazon Fire Remote Style AF3 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF3' ) { + return html` + + + ${cssVars} + +
+ + +
+
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ +
+
+ + + + +
+ +
+ + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${renderAmazonArrowlogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Amazon Fire Remote Style AF4 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF4' ) { + return html` + + + ${cssVars} + +
+ + +
+
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ +
+
+ + + + +
+ +
+ + + + + + + + + + + + + + +
+ +
+ + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["AF4"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderfiretvlogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Amazon Fire Remote Style AF5 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF5' ) { + return html` + + + ${cssVars} + +
+ + +
+
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ +
+ +
+ + + + +
+
+ + + + + + + + + + +
+ + + + + + + + + + + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["AF5"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderfiretvlogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Amazon Fire Remote Style AF6 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AF6' ) { + return html` + + + ${cssVars} + +
+ + ${drawDeviceName(this, this._config, 'top')} + + +
+ + +
+ +
+ +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["AF6"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderfiretvlogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + // Render Amazon Fire Remote Style AFJTV or AFXF2 + function renderBrandLogo(style) { + if (style == 'AFJTV') { + return html` + `; + } + if (style == 'AFXF2') { + return html` + `; + } + } + + if ( getDeviceAttribute('defaultRemoteStyle') == 'AFJTV' || getDeviceAttribute('defaultRemoteStyle') == 'AFXF2') { + return html` + + + ${cssVars} + +
+ + ${drawDeviceName(this, this._config, 'top')} + + +
+
+ +
+ +
+ +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["AFJTV"])} + +
+ + + + +
+ + ${renderBrandLogo(getDeviceAttribute('defaultRemoteStyle'))} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + function renderHomaticsExtras(remote, style){ + if (style == 'HO1' || style == 'HO2') { + return + }; + return html` + + + + + + + + + + + + + + + + +
+ + + + +
+ `; + + } + + // Render Homatics Remote Style HO1, HO2, HO3, HO4 + if ( ['HO1', 'HO2', 'HO3', 'HO4'].includes(getDeviceAttribute('defaultRemoteStyle'))) { + return html` + + + ${cssVars} + +
+ + ${drawDeviceName(this, this._config, 'top')} + + +
+
+
+
+ + + ${renderHomaticsExtras(this, getDeviceAttribute('defaultRemoteStyle'))} + + + + + +
+
+ + + + +
+ +
+ + + + + +
+
+ + +
+
+
+ + +
+ + + + +
+ + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["ON1"])} + + + + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + + // Render NVIDIA Shield Remote Style NS1 + if ( getDeviceAttribute('defaultRemoteStyle') == 'NS1' ) { + return html` + + + ${cssVars} + +
+ +
+ +
${drawDeviceName(this, this._config, 'top')}
+ +
+ +
+ + + + +
+
+ + + + + +
+ +
+ +
+
+
+ + +
+
+
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render NVIDIA Shield Remote Style NS2 + if ( getDeviceAttribute('defaultRemoteStyle') == 'NS2' ) { + return html` + + + ${cssVars} + +
+ +
+ +
${drawDeviceName(this, this._config, 'top')}
+ + + + + +
+ +
+ + + + +
+
+ + + + + + + + + + + + + + + ${drawAppLaunchButtons(this, this._config, 2, appButtonMax["NS2"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render onn. remote style 1 + if ( getDeviceAttribute('defaultRemoteStyle') == 'ON1' ) { + return html` + + + ${cssVars} + +
+ + ${drawDeviceName(this, this._config, 'top')} + + +
+
+
+
+ + + + + + +
+
+ + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["ON1"])} + + + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render onn. remote style 2 + if ( getDeviceAttribute('defaultRemoteStyle') == 'ON2' ) { + return html` + + + ${cssVars} + +
+ + ${drawDeviceName(this, this._config, 'top')} + + +
+
+
+
+ + + + + + +
+
+ + + + +
+ +
+ + + + + +
+ +
+ + + + + +
+ + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["ON2"])} + + + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + function renderRokuPowerRow(remote, style){ + if(style=='RVR') { + return html` +
+
+ +
+
`; + } + else { + return html` +
+ +
`; + } + } + + function renderRokuDPad(remote) { + return html` +
+
+ +
+ + + +
+ +
+
`; + } + + function renderRokuBackHomeRow(remote) { + return html` +
+ + +
`; + } + + function renderRokuReplayOptionsRow(remote, style) { + var middleButton = html ``; + if (style=='RHR'|| style=='RWR') { + var middleButton = html ``; + } + if (style=='RSR' || style=='RTR') { middleButton = '' }; + return html` +
+ + ${middleButton} + +
`; + } + + function renderRokuScrubRow(remote) { + return html` +
+ + + +
`; + } + + function renderRokuTag() { + return html`
+
`; + } + + // Render Roku Hisense Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'RHR' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuPowerRow(this, 'RHR')} + ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this, "RHR")} + ${renderRokuScrubRow(this)} + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["RHR"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + // Render Roku Simple Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'RSR' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this, 'RSR')} + ${renderRokuScrubRow(this)} + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["RSR"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderRokuTag()} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + // Render Roku TCL Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'RTR' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuPowerRow(this, 'RTR')} + ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this, "RTR")} + ${renderRokuScrubRow(this)} + ${drawAppLaunchButtons(this, this._config, 1, appButtonMax["RTR"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + // Render Roku Westinghouse Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'RWR' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuPowerRow(this, 'RWR')} + ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this, "RWR")} + ${renderRokuScrubRow(this)} + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["RWR"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + // Render Roku Voice Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'RVR' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuPowerRow(this, 'RVR')} + ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this)} + ${renderRokuScrubRow(this)} + + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["RVR"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderRokuTag()} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + // Render Roku Voice Remote Pro + if ( getDeviceAttribute('defaultRemoteStyle') == 'RVRP' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${renderRokuPowerRow(this)} +
+ ${renderRokuBackHomeRow(this)} + ${renderRokuDPad(this)} + ${renderRokuReplayOptionsRow(this)} + ${renderRokuScrubRow(this)} +
+ + +
+ ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["RVRP"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${renderRokuTag()} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + + // Render Xiaomi Remote 1 + if ( getDeviceAttribute('defaultRemoteStyle') == 'XM1' ) { + return html` + + + ${cssVars} + +
+ ${drawDeviceName(this, this._config, 'top')} + +
+ +
+ +
+ +
+ +
+ +
+ + + + +
+
+ + + + + + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["XM1"])} + +
+ +
+ +
+ +
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${renderXiaomiLogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Xiaomi Mi Remote + if ( getDeviceAttribute('defaultRemoteStyle') == 'XM2' ) { + return html` + + + ${cssVars} + +
+ ${drawDeviceName(this, this._config, 'top')} + +
+ +
+ +
+ +
+ +
+ +
+ + + + +
+
+ + + + + + ${drawAppLaunchButtons(this, this._config, 3, appButtonMax["XM2"])} + +
+ +
+ +
+ +
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${renderMiLogo()} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Chromecast 1, 2, or 3 + if ( ['CC1', 'CC2', 'CC3'].includes(getDeviceAttribute('defaultRemoteStyle'))) { + return html` + + + ${cssVars} + +
+ ${drawDeviceName(this, this._config, 'top')} + +
+ +
+ + + + +
+
+ + + + + + + + + ${drawMediaControlButtons(this, this._config)} + + + ${drawAppLaunchButtons(this, this._config, 2, appButtonMax[getDeviceAttribute('defaultRemoteStyle')])} + +
+ +
+ +
+ +
+ + +
+ + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Apple TV Remote - Style 1 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AR1' ) { + return html` + + + ${cssVars} + +
+ ${drawDeviceName(this, this._config, 'top')} + +
+ +
+ + + + +
+
+ + + + + + ${drawAppLaunchButtons(this, this._config, 2, appButtonMax["AR1"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render Apple TV Remote - Style 2 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AR2' ) { + return html` + + + ${cssVars} + +
+
+ ${drawDeviceName(this, this._config, 'top')} + +
+
+
+
+
+ +
+ +
+ + + + +
+
+ + + + +
+
+ + + + + + + + + ${drawAppLaunchButtons(this, this._config, 2, appButtonMax["AR2"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+ +
+ +
+ `; + } + + + // Render Apple TV Remote - Style 3 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AR3' ) { + return html` + + + ${cssVars} + +
+ ${drawDeviceName(this, this._config, 'top')} + +
+
+
+
+ +
+
+ +
+ +
+ + + + +
+
+ + + + + + + + + + + + + ${drawAppLaunchButtons(this, this._config, 2, appButtonMax["AR3"])} + + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} + +
+ +
+ `; + } + + + // Render App Launcher 1 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AL1' ) { + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["AL1"])} + ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + + // Render App Launcher 2 + if ( getDeviceAttribute('defaultRemoteStyle') == 'AL2' ) { + var lpb4icon = 'mdi:rewind'; + var lpb6icon = 'mdi:fast-forward'; + var rpb2icon = 'mdi:restart'; + var rpb2id = 'reboot-button'; + var hiddenclass = ''; + if(this._config.device_family == 'apple-tv') { + var lpb4icon = 'mdi:skip-backward'; + var lpb6icon = 'mdi:skip-forward'; + var rpb2icon = 'mdi:magnify'; + var rpb2id = 'search-button'; + var hiddenclass = 'hidden'; + } + + // conditionally hide the app launcher section if no apps are selected + var configuredAppLaunchButtons = 0; + for(let i=1; i<=appmap.size; i++) { + if(this._config["app_launch_"+i] && this._config["app_launch_"+i] != '') { + configuredAppLaunchButtons++; + } + } + var noAppsClass = ''; + if (configuredAppLaunchButtons==0){ + noAppsClass = 'noApps'; + } + + // conditionally hide the button frame sections and their backgrounds + var rowClass = "row"; + if(this._config.hide_button_group_frame == true) { + rowClass="row noframes"; + } + + // allow the dpad style to change for AL2 remotes + function getDpad(caller, style='amazon-fire') { + var dpadClass = ''; + switch(style) { + case 'apple-tv-black': + dpadClass = 'AR3 '; + break; + case 'apple-tv-silver': + dpadClass = 'AR1 '; + break; + case 'chromecast': + dpadClass = 'CC '; + break; + case 'xiaomi': + dpadClass = 'XM '; + break; + case 'minimal': + dpadClass = "minimal "; + break; + } + if(style=='minimal'){ + return html` + +
+
+ +
+ + + +
+ +
+
+ + `; + } + else { + return html` +
+ +
+ + + + +
+
+ `; + } + } + + return html` + + ${cssVars} +
+ ${drawDeviceName(this, this._config, 'top')} + ${drawAppLaunchButtons(this, this._config, 'fill', appButtonMax["AL2"])} + +
+ +
+
+ + + +
+
+ + + +
+
+ +
+ ${getDpad(this, this._config.dpad_style)} +
+ +
+
+
+ + +
+
+ +
+ +
+
+ +
+ +
+
+
+
+ ${drawDeviceName(this, this._config, 'bottom')} + ${drawFiremoteVersionNumber(this, this._config)} +
+
+ `; + } + + + } + + + + // Firemote Button Click or Click + Hold Handler + buttonDown(clicked) { + + // Ignore right clicks + if (clicked.which === 3) { + return; + } + + // Inspect user prefs & setup click timer + const _config = this._config; + const _hass = this.hass; + const deviceType = this._config.device_type; + const deviceFamily = this._config.device_family; + const entity = this._config.entity; + const compatibility_mode = this._config.compatibility_mode || 'default'; + const overrides = this._config.button_overrides; + const atvRemoteEntity = this._config.android_tv_remote_entity; + const rokuRemoteEntity = this._config.roku_remote_entity; + const activityLight = this.renderRoot.querySelector('.activityLight'); + const start = Date.now(); + const holdTime = 500; + const buttonID = clicked.target.id; + var pressedTarget = clicked.target; + var timer = null; + + // Rebuild AppMap - allow hdmi inputs where appropriate & add configured custom launchers from YAML + refreshAppMap(this._config, 4, 1, true); + + + // Function for buttons that need overrides because HA integration support is lacking + function unsupportedButton(){ + alert('"'+buttonID+"\"\n\nUse a button override to program this button\nhttps://github.com/PRProd/HA-Firemote/#button-overrides"); + return; + } + + // Check for user set Associated Android TV Remote Integration entity + var hasATVAssociation = true; + if(atvRemoteEntity == '' || typeof atvRemoteEntity == 'undefined' || deviceFamily == 'amazon-fire' ) { + hasATVAssociation = false; + } + + // Choose event listener path for client android device + var eventListenerBinPath = ''; + if(compatibility_mode == 'default' || compatibility_mode == 'strong' || compatibility_mode == '') { + eventListenerBinPath = deviceAttributeQuery("defaultEventListenerBinPath", this._config); + } + else { + var eventListenerBinPath = '/dev/input/'+compatibility_mode; + } + + // Function to handle translations from English to the user's language + const ha_language = this.hass.config.language; + function translateToUsrLang(englishString) { + var translatedString = englishString; + if (typeof translationmap.get(ha_language) !== 'undefined'){ + if (typeof translationmap.get(ha_language)[sourceName] !== 'undefined'){ + translatedString = translationmap.get(ha_language)[englishString]; + } + } + return translatedString; + } + + // Function: Assemble arguments for service calls + function getCustomServiceArgs(def) { + if((typeof def.action !== 'undefined' || typeof def.service !== 'undefined') && typeof def.target !== 'undefined') { + var svcarray; + if(typeof def.action !== 'undefined') { + svcarray = def.action.split("."); + } + else { + svcarray = def.service.split("."); + } + var data = Object; + if(typeof def.data !== 'undefined') { + var extraData = JSON.parse(JSON.stringify(def.data)); + var target = JSON.parse(JSON.stringify(def.target)); + data = Object.assign(target, extraData); + } + else { + data = Object.assign(def.target); + } + return new Array(svcarray[0], svcarray[1], data); + } + }; + + + + // Check for and handle button overrides before proceeding + // No HOLD actions for button overrides are supported + if(typeof overrides !== 'undefined' && overrides !== null) { + if(typeof overrides[buttonID] !== 'undefined') { + const overrideDef = overrides[buttonID]; + if(overrideDef !== null) { + if(typeof overrideDef.script !== 'undefined') { + // handle overrides via external script + try{ _hass.callService("script", overrideDef.script, overrideDef.data) } + catch { return; } + fireEvent(this, 'haptic', 'light'); // haptic feedback on success + return; + } + else if((typeof overrideDef.service !== 'undefined' || typeof overrideDef.action !== 'undefined') && typeof overrideDef.target !== 'undefined') { + // handle overrides via yaml instructions + var ServiceDetails = getCustomServiceArgs(overrideDef); + try{ _hass.callService(ServiceDetails[0], ServiceDetails[1], ServiceDetails[2]) } + catch { return; } + fireEvent(this, 'haptic', 'light'); // haptic feedback on success + return; + } + } + } + } + + + // Handle custom launcher button clicks + // No HOLD actions for custom launchers are supported + var customLauncherIDPfx = new RegExp('customlauncher ') + if(customLauncherIDPfx.test(buttonID)){ + var clickedButtonID = buttonID; + const customLauncherKey = clickedButtonID.substr(0, clickedButtonID.indexOf("-button")); + if(appmap.has(customLauncherKey)) { + if(appmap.get(customLauncherKey).script) { + try{ _hass.callService("script", appmap.get(customLauncherKey).script, appmap.get(customLauncherKey).data) } + catch { return; } + fireEvent(this, 'haptic', 'light'); + return; + } + else { + var launcher = appmap.get(customLauncherKey); + if(typeof launcher.friendlyName !== 'undefined') { + _config.custom_launchers.forEach(element => { + var name = launcher.friendlyName.substr(8); + if (name == element.friendly_name) { + var ServiceDetails = getCustomServiceArgs(element); + try{ _hass.callService(ServiceDetails[0], ServiceDetails[1], ServiceDetails[2]) } + catch { return; } + fireEvent(this, 'haptic', 'light'); + } + }); + return; + } + } + } + } + + + // Handle Built-in App launcher buttons (existing in JSON map) + // No HOLD actions for app launchers are supported + const appkey = buttonID.substr(0, buttonID.indexOf("-button")); + if(appmap.has(appkey)) { + var familySpecificAppData = appmap.get(appkey)[deviceFamily]; + if(familySpecificAppData && (familySpecificAppData.adbLaunchCommand || familySpecificAppData.appName || familySpecificAppData.remoteCommand)) { + var adbcommand = familySpecificAppData.adbLaunchCommand; + var sourceName = familySpecificAppData.appName; + var remoteCommand = familySpecificAppData.remoteCommand; + } + else { + var adbcommand = appmap.get(appkey).adbLaunchCommand; + var sourceName = appmap.get(appkey).appName; + var remoteCommand = appmap.get(appkey).remoteCommand + } + sourceName = translateToUsrLang(sourceName); + fireEvent(this, 'haptic', 'light'); + if (typeof remoteCommand != 'undefined' && ['apple-tv', 'roku'].includes(deviceFamily)) { + var data = JSON.parse(remoteCommand); + switch (deviceFamily) { + case 'apple-tv': + data['entity_id'] = _config.apple_tv_remote_entity; + break; + case 'roku': + data['entity_id'] = rokuRemoteEntity; + break; + } + _hass.callService("remote", "send_command", data); + return; + } + if (typeof adbcommand == 'undefined') { + _hass.callService("media_player", "select_source", { entity_id: _config.entity, source: sourceName}); + return; + } + else { + if(adbcommand == 'adb shell reboot') { + if(confirm('Are you sure you want to reboot '+_hass.states[_config.entity].attributes.friendly_name) == false) { + return; + } + } + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: adbcommand }); + return; + } + return; + } + + + // Catch-all for none/other configurations where a button hasn't been defined in YAML config + if(_config.entity === 'none') { + unsupportedButton(); + return; + } + + + // detect difference between click or hold actions + // then carry out the appropriate actions + const userAction = getActionType(this); + async function getActionType(that) { + let type = new Promise((resolve, reject) => { + + pressedTarget.addEventListener("mouseup", release, true); + pressedTarget.addEventListener("pointerup", release, true); + pressedTarget.addEventListener("mouseout", mouseOutHandler, true); + pressedTarget.addEventListener('touchmove', handleTouchMove, {passive: true}); + timer = setTimeout(function(){ + if(start){ + // it was a hold + pressedTarget.removeEventListener("mouseup", release, true); + pressedTarget.removeEventListener("pointerup", release, true); + pressedTarget.removeEventListener("mouseout", mouseOutHandler, true); + pressedTarget.removeEventListener('touchmove', handleTouchMove, {passive: true}); + resolve("hold"); + } + }, holdTime); + + // it was a click + function release(){ + clearTimeout(timer); + let start = null; + pressedTarget.removeEventListener("mouseup", release, true); + pressedTarget.removeEventListener("pointerup", release, true); + pressedTarget.removeEventListener("mouseout", mouseOutHandler, true); + pressedTarget.removeEventListener('touchmove', handleTouchMove, {passive: true}); + resolve("click"); + } + + // a non-event is cancelled here + function mouseOutHandler() { + clearTimeout(timer); + let start = null; + pressedTarget.removeEventListener("mouseup", release, true); + pressedTarget.removeEventListener("pointerup", release, true); + pressedTarget.removeEventListener("mouseout", mouseOutHandler, true); + pressedTarget.removeEventListener('touchmove', handleTouchMove, {passive: true}); + resolve(null); + } + function handleTouchMove() { + clearTimeout(timer); + let start = null; + pressedTarget.removeEventListener("mouseup", release, true); + pressedTarget.removeEventListener("pointerup", release, true); + pressedTarget.removeEventListener("mouseout", mouseOutHandler, true); + pressedTarget.removeEventListener('touchmove', handleTouchMove, {passive: true}); + resolve(null); + } + }); + + // After determining the button interaction type, + // send the appropriate commands + let actionType = await type; + if (actionType === null) {return;} + if(actionType == 'click' || actionType == 'hold') { + + // Flash activity light if it exists + if (activityLight) { + activityLight.style.opacity = '1'; + setTimeout(function() {activityLight.style.opacity = '0'}, 100); + } + + // provide haptic feedback for button press + fireEvent(that, 'haptic', 'light') + + + + // Power Button Click + if(buttonID == 'power-button' && actionType == 'click') { + const state = _hass.states[entity]; + const stateStr = state ? state.state : 'off'; + if(['apple-tv', 'roku'].includes(deviceFamily)) { + if(stateStr != 'off' && stateStr != 'unavailable' && stateStr != 'standby') { + _hass.callService("media_player", "turn_off", { entity_id: entity}); + } + else { + _hass.callService("media_player", "turn_on", { entity_id: entity}); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_POWER' }); + } + else if (compatibility_mode == 'strong' && eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'POWER' }); + } + else if(compatibility_mode == 'strong') { + _hass.callService("media_player", "toggle", { entity_id: entity}); + } + else if(deviceType == 'fire_stick_4k' || deviceType == 'fire_tv_stick_4k_max' || + deviceType == 'fire_tv_3rd_gen' || deviceType =='fire_stick_second_gen' || + deviceType == 'fire_tv_stick_4k_second_gen' ) { + if(stateStr != 'off' && stateStr != 'unavailable') { + _hass.callService("media_player", "turn_off", { entity_id: entity}); + } + else { + _hass.callService("media_player", "turn_on", { entity_id: entity}); + } + } + else if (deviceType == 'fire_tv_cube_third_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 116 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 116 0 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent /dev/input/event2 1 9 1 && sendevent /dev/input/event2 0 0 0 && sendevent /dev/input/event2 1 9 0 && sendevent /dev/input/event2 0 0 0' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'POWER' }); + } + return; + } + // Power Button Hold + if(buttonID == 'power-button' && actionType == 'hold') { + if(['amazon-fire', 'apple-tv', 'chromecast', 'nvidia-shield', 'onn', 'roku'].includes(deviceFamily)) { + return; + } + if(deviceFamily == 'xiaomi'){ + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_POWER', num_repeats: 1, delay_secs: 0, hold_secs: 0.75 }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent --longpress POWER'}); + } + return; + } + } + + + + // Account Button (Google TV) Click + if(buttonID == 'profile-button' && actionType == 'click') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent KEYCODE_PROFILE_SWITCH' }); + return; + }; + // Account Button (Google TV) Hold + if(buttonID == 'profile-button' && actionType == 'hold') { + // no special behaviors found for this yet + return; + } + + + + // Magic Button / Star Button (Google TV / onn pro) Click + if(buttonID == 'magic-star-button' && actionType == 'click') { + if (deviceType == 'onn-streaming-device-4k-pro') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent 313' }); + return; + } + else { + unsupportedButton(); + return; + } + }; + // Magic Button / Star Button (Google TV / onn pro) Hold + if(buttonID == 'magic-star-button' && actionType == 'hold') { + if (deviceType == 'onn-streaming-device-4k-pro') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent --longpress 313' }); + return; + } + else { + unsupportedButton(); + return; + } + }; + + + + // Keyboard button click + if(buttonID == 'keyboard-button' && actionType == 'click') { + if(['apple-tv'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + var text = prompt("Enter text to send"); + if (text && text != '') { + if(['roku'].includes(deviceFamily)) { + _hass.callService("remote", "send_command", { entity_id: rokuRemoteEntity, command: 'Lit_'+text, num_repeats: 1, delay_secs: 0, hold_secs: 0}); + } + else { + var escapedText = text.replace(/"/g, "\\\""); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'input text "'+escapedText+'"' }); + } + } + return; + }; + // Keyboard button hold + if(buttonID == 'keyboard-button' && actionType == 'hold') { + // no special behaviors dreamed up for this yet + return; + } + + + + // Up Button click + if(buttonID == 'up-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_UP' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'UP' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 103 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 103 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Up Button hold + if(buttonID == 'up-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // needs to be at least 1sec hold time for Apple TV + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'up', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'up', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + }, 1025); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 1/4 sec repeat is an ok substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_UP', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_UP', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + }, 525); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'UP' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'UP' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 103 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 103 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Left Button Click + if(buttonID == 'left-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'left', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_LEFT' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'LEFT' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 105 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 105 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Left Button Hold + if(buttonID == 'left-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // needs to be at least 1sec hold time for Apple TV + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'left', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'left', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + }, 1025); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 1/4 sec repeat is an ok substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'left', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'left', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_LEFT', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_LEFT', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + }, 525); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'LEFT' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'LEFT' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 105 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 105 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Right Button Click + if(buttonID == 'right-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'right', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_RIGHT' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'RIGHT' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 106 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 106 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Right Button Hold + if(buttonID == 'right-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // needs to be at least 1sec hold time for Apple TV + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'right', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'right', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + }, 1025); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 1/4 sec repeat is an ok substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'right', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'right', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_RIGHT', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_RIGHT', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + }, 525); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'RIGHT' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'RIGHT' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 106 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 106 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Down Button Click + if(buttonID == 'down-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_DOWN' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'DOWN' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 108 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 108 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Down Button Hold + if(buttonID == 'down-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // needs to be at least 1sec hold time for Apple TV + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'down', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'down', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + }, 1025); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 1/4 sec repeat is an ok substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_DOWN', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_DOWN', num_repeats: 1, delay_secs: 0, hold_secs: 0.5}); + }, 525); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'DOWN' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'DOWN' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 108 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 108 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Center Button Click + if(buttonID == 'center-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'select', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_CENTER' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'CENTER' }); + } + else { + if(deviceType == 'fire_tv_4_series' || deviceType == 'fire_tv_toshiba_v35' || deviceType == 'fire_tv_jvc-4k-2021') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 28 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 28 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + else if(deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 353 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 353 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 96 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 96 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + } + return; + } + // Center Button Hold + if(buttonID == 'center-button' && actionType == 'hold') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + // This strategy does not work with Roku - https://github.com/home-assistant/core/issues/123999 + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'select', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_DPAD_CENTER', num_repeats: 1, delay_secs: 0, hold_secs: 0.75 }); + return; + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + // This adb shell input keyevent --longpress 23 strategy does not seem to work with the NVIDIA Shield, onn. or Chromecast through ADB + // need to rapidfire the center button like we do the other dpad buttons + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent 23' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'adb shell input keyevent 23' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + if(deviceType == 'fire_tv_4_series' || deviceType == 'fire_tv_toshiba_v35' || deviceType == 'fire_tv_jvc-4k-2021') { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 28 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 28 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + else if(deviceType == 'mi-box-s') { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 353 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 353 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 96 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 96 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + } + return; + } + + + + // Replay Button Click + if(buttonID == 'replay-button' && actionType == 'click') { + if(['roku'].includes(deviceFamily)) { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'replay', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + else { + unsupportedButton(); + return; + } + } + // Replay Button Hold + if(buttonID == 'replay-button' && actionType == 'hold') { + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 1/4 sec repeat is an ok substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'replay', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'replay', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + return; + } + + + + // Options Button Click + if(buttonID == 'options-button' && actionType == 'click') { + if(['roku'].includes(deviceFamily)) { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'info', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + } + // Options Button Hold + if(buttonID == 'options-button' && actionType == 'hold') { + // no special behaviors found for this yet + return; + } + + + + // Apps Button Click + if(buttonID == 'apps-button' && actionType == 'click') { + if (deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start -n com.google.android.tvlauncher/.appsview.AppsViewActivity' }); + } + else if (deviceFamily == 'nvidia-shield') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_APP_SWITCH' }); + } + else if (deviceFamily == 'amazon-fire') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'am start -n com.amazon.venezia/com.amazon.venezia.grid.AppsGridLauncherActivity' }); + } + else if (deviceFamily == 'onn') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_ALL_APPS' }); + } + else if (deviceFamily == 'roku') { + unsupportedButton(); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'RECENTS' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 757 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 757 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Apps Button Hold + if(buttonID == 'apps-button' && actionType == 'hold') { + // no special behaviors found for this yet + return; + } + + + + // Back Button Click + if(buttonID == 'back-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'menu', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'back', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_BACK' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'BACK' }); + } + return; + } + // Back Button Hold + if(buttonID == 'back-button' && actionType == 'hold') { + // no special behaviors found for this with Chromecast, FireTV, onn., NVIDIA Shield, Roku, or Xiaomi mi + if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'home', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + return; + } + + + + // Home Button Click + if(buttonID == 'home-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'top_menu', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'home', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_HOME' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'HOME' }); + } + return; + } + // Home Button Hold + if(buttonID == 'home-button' && actionType == 'hold') { + // no special behavior noticed for Roku + if(deviceFamily == 'amazon-fire') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent SETTINGS' }); + } + else if(['chromecast', 'onn'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 83' }); + } + else if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'home', num_repeats: 1, delay_secs: 0, hold_secs: 1}); + } + else if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_HOME', num_repeats: 1, delay_secs: 0, hold_secs: 0.75}); + } + else if(['nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_ALL_APPS' }); + } + return; + } + + + + // Hamburger Button Click + if(buttonID == 'hamburger-button' && actionType == 'click') { + if(['apple-tv', 'roku', 'onn'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if(deviceType == 'shield-tv-pro-2019' || deviceType == 'shield-tv-2019') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'am start -a android.settings.SETTINGS' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'MENU' }); + } + else if(['xiaomi'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start -n com.android.tv.settings/com.android.tv.settings.MainSettings' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 139 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 139 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Hamburger Button Hold + if(buttonID == 'hamburger-button' && actionType == 'hold') { + // no special behaviors found for this with FireTV, or NVIDIA Shield + // the hamburger button for Xiaomi isn't actually real + return; + } + + + + // Rewind Button Click + if(buttonID == 'rewind-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'skip_backward', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'reverse', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_MEDIA_REWIND' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined' || deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'REWIND' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 168 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 168 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Rewind Button Hold + if(buttonID == 'rewind-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // n/a for Apple TV (assuming rewind translates to skip_backward) + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 0.15 sec repeat is an ok-ish substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'reverse', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'reverse', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 150); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_MEDIA_REWIND', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_MEDIA_REWIND', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 100); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'REWIND' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'REWIND' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 168 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 168 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Play/Pause Button Click + if(buttonID == 'playpause-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + var playpausecommand = 'pause'; + if(_hass.states[_config.entity].state=='paused'){ playpausecommand = 'play'; }; + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: playpausecommand, num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'play', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_MEDIA_PLAY_PAUSE' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined' || deviceType == 'mi-box-s') { + _hass.callService("media_player", "media_play_pause", { entity_id: _config.entity}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 164 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 164 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Play/Pause Button Hold + if(buttonID == 'playpause-button' && actionType == 'hold') { + // Button does not exist for apple tv, chromecast, onn., or xiaomi - so no hold behaviors are emulated + // No special behaviors noticed for this with FireTV, NVIDIA Shield, or Roku + return; + } + + + + // Fast Forward Button Click + if(buttonID == 'fastforward-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'skip_forward', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'forward', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_MEDIA_FAST_FORWARD' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined' || deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'FAST_FORWARD' }); + } + else { + if(deviceType == 'fire_tv_4_series' || deviceType == 'fire_tv_toshiba_v35' || deviceType == 'fire_tv_jvc-4k-2021') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 159 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 159 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 208 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 208 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + } + return; + } + // Fast Forward Button Hold + if(buttonID == 'fastforward-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + // n/a for Apple TV (assuming fast forward translates to skip_forward) + return; + } + if(['roku'].includes(deviceFamily)) { + // hold does not work with Roku - https://github.com/home-assistant/core/issues/123999 - using a 0.15 sec repeat is an ok-ish substitute for now + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'forward', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'forward', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 150); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_MEDIA_FAST_FORWARD', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: atvRemoteEntity, command: 'KEYCODE_MEDIA_FAST_FORWARD', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 100); + function rls() { + clearInterval(rpt); + } + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'FAST_FORWARD' }); + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'FAST_FORWARD' }); + }, 250); + function rls() { + clearInterval(rpt); + } + } + else { + if(deviceType == 'fire_tv_4_series' || deviceType == 'fire_tv_toshiba_v35' || deviceType == 'fire_tv_jvc-4k-2021') { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 159 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 159 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 208 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 208 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + } + + + + // Volume Up Button Click + if(buttonID == 'volume-up-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'volume_up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_VOLUME_UP' }); + } + else if(deviceFamily == 'nvidia-shield') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell cmd media_session volume --show --adj raise' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'VOLUME_UP' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 115 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 115 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Volume Up Button Hold + if(buttonID == 'volume-up-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'volume_up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'volume_up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_VOLUME_UP' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else if(deviceFamily == 'nvidia-shield') { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell cmd media_session volume --show --adj raise' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'VOLUME_UP' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 115 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 115 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Volume Down Button Click + if(buttonID == 'volume-down-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + var t = deviceFamily+"_remote_entity"; + var confname = t.replace(/\-/, "_"); + _hass.callService("remote", "send_command", { entity_id: _config[confname], command: 'volume_down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_VOLUME_DOWN' }); + } + else if(deviceFamily == 'nvidia-shield') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell cmd media_session volume --show --adj lower' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'VOLUME_DOWN' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 114 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 114 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Volume Down Button Hold + if(buttonID == 'volume-down-button' && actionType == 'hold') { + if(['apple-tv'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.apple_tv_remote_entity, command: 'volume_down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(['roku'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'volume_down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + if(hasATVAssociation) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_VOLUME_DOWN' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else if(deviceFamily == 'nvidia-shield') { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell cmd media_session volume --show --adj lower' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'VOLUME_DOWN' }); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 114 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 114 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + + + + // Mute Button Click + if(buttonID == 'mute-button' && actionType == 'click') { + if(deviceFamily == 'apple-tv') { + unsupportedButton(); + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'volume_mute', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if(hasATVAssociation) { + _hass.callService("remote", "send_command", { entity_id: _config.android_tv_remote_entity, command: 'KEYCODE_VOLUME_MUTE' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'MUTE' }); + } + else if (deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 164'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 113 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 113 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Mute Button Hold + if(buttonID == 'mute-button' && actionType == 'hold') { + // Button does not exist for chromecast, NVIDIA Shield, or xiaomi - so no hold behaviors are emulated + // No special behaviors noticed for this with Apple TV, FireTV, onn., or Roku + return; + } + + + + // Channel Up Button Click + if(buttonID == 'channel-up-button' && actionType == 'click') { + if(['apple-tv', 'chromecast', 'nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'channel_up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if (['homatics'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_UP'}); + return; + } + if (['fire_tv_stick_4k_second_gen', 'fire_tv_stick_4k_max_second_gen', 'onn-streaming-device-4k-pro', 'onn-4k-streaming-box', 'onn-full-hd-streaming-stick'].includes(deviceType)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_UP'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 402 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 402 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Channel Up Button Hold + if(buttonID == 'channel-up-button' && actionType == 'hold') { + // No special behaviors noticed for this with onn. + // Button does not exist for Apple TV, Chromecast, Homatics, NVIDIA Shield, or Xiaomi - so no hold behaviors are emulated + if(['onn', 'apple-tv', 'chromecast', 'homatics', 'nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + return; + } + if(['roku'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'channel_up', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + if (['fire_tv_stick_4k_second_gen', 'fire_tv_stick_4k_max_second_gen'].includes(deviceType)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_UP'}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 402 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 402 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + } + + + + // Channel Down Button Click + if(buttonID == 'channel-down-button' && actionType == 'click') { + if(['apple-tv', 'chromecast', 'nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'channel_down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + if (['homatics'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_DOWN'}); + return; + } + if (['fire_tv_stick_4k_second_gen', 'fire_tv_stick_4k_max_second_gen', 'onn-streaming-device-4k-pro', 'onn-4k-streaming-box', 'onn-full-hd-streaming-stick'].includes(deviceType)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_DOWN'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 403 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 403 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Channel Down Button Hold + if(buttonID == 'channel-down-button' && actionType == 'hold') { + // No special behaviors noticed for this with onn. + // Button does not exist for Apple TV, Chromecast, NVIDIA Shield, or Xiaomi - so no hold behaviors are emulated + if(['onn', 'apple-tv', 'chromecast', 'homatics', 'nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + return; + } + if(['roku'].includes(deviceFamily)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'channel_down', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + if (['fire_tv_stick_4k_second_gen', 'fire_tv_stick_4k_max_second_gen'].includes(deviceType)) { + clicked.target.addEventListener("pointerup", rls, true); + let rpt = setInterval( function() { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_CHANNEL_DOWN'}); + }, 250); + function rls() { + clearInterval(rpt); + } + return; + } + else { + clicked.target.addEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 403 1 && sendevent '+eventListenerBinPath+' 0 0 0' }); + function rls() { + clicked.target.removeEventListener("pointerup", rls, true); + _hass.callService("androidtv", "adb_command", { entity_id: entity, command: 'sendevent '+eventListenerBinPath+' 1 403 0 && sendevent '+eventListenerBinPath+' 0 0 0 '}); + } + } + return; + } + } + + + + // TV Button Click + if(buttonID == 'tv-button' && actionType == 'click') { + if(['apple-tv', 'roku', 'nvidia-shield'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if (deviceType == 'fire_tv_cube_third_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 297'}); + } + else if (deviceType == 'fire_tv_stick_4k_second_gen' || deviceType == 'fire_tv_stick_4k_max_second_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 300'}); + } + else if (deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start -n com.google.android.tv/com.android.tv.MainActivity' }); + } + else if (['onn', 'homatics'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_GUIDE'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 362 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 362 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // TV Button Button Hold + if(buttonID == 'tv-button' && actionType == 'hold') { + // no special behaviors found for this + return; + } + + + + // Settings Button Click + if(buttonID == 'settings-button' && actionType == 'click') { + if(['roku', 'nvidia-shield'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if (['onn'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 83' }); + } + else if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined' || deviceType == 'fire_tv_cube_third_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'SETTINGS' }); + } + else if(deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start -n com.android.tv.settings/com.android.tv.settings.MainSettings' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 249 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 249 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Settings Button Button Hold + if(buttonID == 'settings-button' && actionType == 'hold') { + // no special behaviors found for this + return; + } + + + + // App Switch (recents) Button + if(buttonID == 'app-switch-button' && actionType == 'click') { + if(compatibility_mode == 'strong') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'RECENTS' }); + } + else if (deviceType == 'fire_tv_stick_4k_second_gen' || deviceType == 'fire_tv_stick_4k_max_second_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 307'}); + } + else if(eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'RECENTS' }); + } + else if (deviceType == 'fire_tv_cube_third_gen') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 304'}); + } + else if (deviceType == 'mi-box-s') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_APP_SWITCH' }); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 757 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 757 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // App Switch (recents) Button Hold + if(buttonID == 'app-switch-button' && actionType == 'hold') { + // no special behaviors found for this + return; + } + + + + // Headset Button Click + if(buttonID == 'headset-button' && actionType == 'click') { + if (['amazon-fire'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent BUTTON_3'}); + } + else if (['onn'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_PAIRING'}); + } + else if (['chromecast', 'nvidia-shield', 'xiaomi'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start -n com.android.tv.settings/com.android.tv.settings.accessories.AddAccessoryActivity'}); + } + else { + unsupportedButton(); + return; + } + return; + } + // Headset Button Hold + if(buttonID == 'headset-button' && actionType == 'hold') { + // no special behaviors found for this + return; + } + + + + // Programmable 1 Button Click + if(buttonID == 'programmable-one-button' && actionType == 'click') { + if(['amazon-fire'].includes(deviceFamily)) { + if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent BUTTON_1'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 638 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 638 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + else { + unsupportedButton(); + return; + } + } + // Programmable 1 Button Hold + if(buttonID == 'programmable-one-button' && actionType == 'hold') { + if(['amazon-fire'].includes(deviceFamily)) { + if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + //TODO: - (spent too much time here already) + } + else { + //_hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 638 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sleep 1 && sendevent '+eventListenerBinPath+' 1 638 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); // TODO: sleep does not work as expected? (spent too much time here already) + } + return; + } + } + + + + // Programmable 2 Button Click + if(buttonID == 'programmable-two-button' && actionType == 'click') { + if(['amazon-fire'].includes(deviceFamily)) { + if(compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent BUTTON_2'}); // this is wrong for fire_tv_4_series + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 4 4 787071 && sendevent '+eventListenerBinPath+' 1 639 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 4 4 787071 && sendevent '+eventListenerBinPath+' 1 639 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); // wrong w/event0 on ftv 4 + } + return; + } + else { + unsupportedButton(); + return; + } + } + // Programmable 2 Button Hold + if(buttonID == 'programmable-two-button' && actionType == 'hold') { + // TODO - (spent too much time here already) + return; + } + + + + // Info Button Click + if(buttonID == 'info-button' && actionType == 'click') { + if(!(['homatics'].includes(deviceFamily))) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 165'}); + } + return; + } + // Info Button Click + if(buttonID == 'info-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + // Bookmark Button Click + if(buttonID == 'bookmark-button' && actionType == 'click') { + if(!(['homatics'].includes(deviceFamily))) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_BOOKMARK'}); + } + return; + } + // Bookmark Button Click + if(buttonID == 'bookmark-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + // Numeric 1 Button Click + if(buttonID == 'num1-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 8'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 2 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 2 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 1 Button Hold + if(buttonID == 'num1-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 2 Button Click + if(buttonID == 'num2-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 9'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 3 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 3 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 2 Button Hold + if(buttonID == 'num2-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 3 Button Click + if(buttonID == 'num3-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 10'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 4 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 4 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 3 Button Hold + if(buttonID == 'num3-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 4 Button Click + if(buttonID == 'num4-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 11'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 5 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 5 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 4 Button Hold + if(buttonID == 'num4-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 5 Button Click + if(buttonID == 'num5-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 12'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 6 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 6 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 5 Button Hold + if(buttonID == 'num5-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 6 Button Click + if(buttonID == 'num6-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 13'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 7 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 7 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 6 Button Hold + if(buttonID == 'num6-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 7 Button Click + if(buttonID == 'num7-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 14'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 8 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 8 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 7 Button Hold + if(buttonID == 'num7-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 8 Button Click + if(buttonID == 'num8-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 15'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 9 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 9 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 8 Button Hold + if(buttonID == 'num8-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 9 Button Click + if(buttonID == 'num9-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 16'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 10 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 10 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 9 Button Hold + if(buttonID == 'num9-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Numeric 0 Button Click + if(buttonID == 'num0-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 7'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 11 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 11 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // Numeric 0 Button Hold + if(buttonID == 'num0-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // SUBT Button Click + if(buttonID == 'subtitle-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else if (compatibility_mode == 'strong' || eventListenerBinPath == 'undefined') { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 175'}); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+eventListenerBinPath+' 1 469 1 && sendevent '+eventListenerBinPath+' 0 0 0 && sendevent '+eventListenerBinPath+' 1 469 0 && sendevent '+eventListenerBinPath+' 0 0 0' }); + } + return; + } + // SUBT Button Hold + if(buttonID == 'subtitle-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // LIVE Button Click + if(buttonID == 'live-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + if(deviceType == 'fire_tv_jvc-4k-2021') { + var tempbinpath = '/dev/input/event4' + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'sendevent '+tempbinpath+' 1 358 1 && sendevent '+tempbinpath+' 0 0 0 && sendevent '+tempbinpath+' 1 358 0 && sendevent '+tempbinpath+' 0 0 0' }); + } + else if (['fire_tv_4_series', 'fire_tv_toshiba_v35"'].includes(deviceType)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent KEYCODE_TV'}); + } + else { + unsupportedButton(); + } + } + return; + } + // LIVE Button Hold + if(buttonID == 'live-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Red Button Click + if(buttonID == 'red-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 183'}); + } + return; + } + // Red Button Hold + if(buttonID == 'red-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Green Button Click + if(buttonID == 'green-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 184'}); + } + return; + } + // Green Button Hold + if(buttonID == 'green-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Yellow Button Click + if(buttonID == 'yellow-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 185'}); + } + return; + } + // Yellow Button Hold + if(buttonID == 'yellow-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Blue Button Click + if(buttonID == 'blue-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 186'}); + } + return; + } + // Blue Button Hold + if(buttonID == 'blue-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Voice Button Click + if(buttonID == 'voice-button' && actionType == 'click') { + unsupportedButton(); + return; + } + // Voice Button Hold + if(buttonID == 'voice-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Sleep Button Click + if(buttonID == 'sleep-button' && actionType == 'click') { + if(deviceFamily == 'roku') { + _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: 'sleep', num_repeats: 1, delay_secs: 0, hold_secs: 0}); + return; + } + else { + unsupportedButton(); + return; + } + } + // Sleep Button Hold + if(buttonID == 'sleep-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // PatchWall Button Click + if(buttonID == 'patchwall-button' && actionType == 'click') { + if(['xiaomi'].includes(deviceFamily)) { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell am start com.mitv.tvhome.atv'}); + } + else { + unsupportedButton(); + } + return; + } + // PatchWall Button Hold + if(buttonID == 'patchwall-button' && actionType == 'hold') { + // no special behaviors known for this - I'm unable to test in my region + return; + } + + + + // Input Button Click + if(buttonID == 'input-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + } + else { + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell input keyevent 178'}); + } + return; + } + // Input Button Hold + if(buttonID == 'input-button' && actionType == 'hold') { + // no special behaviors known for this + return; + } + + + + // Reboot Button Click + if(buttonID == 'reboot-button' && actionType == 'click') { + if(['apple-tv', 'roku'].includes(deviceFamily)) { + unsupportedButton(); + return; + } + if(confirm('Are you sure you want to reboot '+_hass.states[_config.entity].attributes.friendly_name) == false) { + return; + } + _hass.callService("androidtv", "adb_command", { entity_id: _config.entity, command: 'adb shell reboot' }); + return; + } + // Reboot Button Hold + if(buttonID == 'reboot-button' && actionType == 'hold') { + // no special actions imagined for this yet + return; + } + + + + // Search Button Click (Apple TV Remote style 2) + if(buttonID == 'search-button' && actionType == 'click') { + if(['apple-tv'].includes(deviceFamily)) { + _hass.callService("media_player", "select_source", { entity_id: _config.entity, source: "Search"}); + } + else { + unsupportedButton(); + } + return; + } + // Search Button Hold (Apple TV Remote style 2) + if(buttonID == 'search-button' && actionType == 'hold') { + // no special behaviors known for this - I do not own this, so I'm unable to test + return; + } + + +// TODO: Not important, but also not working with the refactor +// // Roku Function: Secret Screen Click +// if(buttonID == 'roku-secret-screen-button' && actionType == 'click') { +// if(['roku'].includes(deviceFamily)) { +// var command = ['home', 'home', 'home', 'home', 'home', 'forward', 'forward', 'forward', 'reverse', 'reverse']; +// for (let index = 0, len = command.length; index < len; ++index) { +// setTimeout(() => +// _hass.callService("remote", "send_command", { entity_id: _config.roku_remote_entity, command: command[index], num_repeats: 1, delay_secs: 0, hold_secs: 0}), +// index*50); +// } +// } +// else { +// unsupportedButton(); +// } +// return; +// } +// // Roku Function: Secret Screen Hold +// if(buttonID == 'roku-secret-screen-button' && actionType == 'hold') { +// // no special actions imagined for this yet +// return; +// } + + + // uncaught button presses land here + //console.log('unhandled '+actionType+' action for '+buttonID); + return; + + + } + else { + // unhandled actiontype + return; + } + + + } + + + } + +} +customElements.define('firemote-card', FiremoteCard); + + +// Allow this card to appear in the card chooser menu +window.customCards = window.customCards || []; +window.customCards.push({ + type: "firemote-card", + name: "Firemote Card", + preview: true, + description: "Remote control card for Amazon FireTV, Apple TV, Chromecast, NVIDIA Shield, onn., Roku, and Xiaomi devices" +}); + + + +// Ceate and register the card editor +class FiremoteCardEditor extends LitElement { + + static get properties() { + return { + hass: {}, + _config: {}, + }; + } + + // setConfig works the same way as for the card itself + setConfig(config) { + this._config = config; + } + + // This function is called when the input element of the editor loses focus or is changed + configChanged(ev) { + + const _config = Object.assign({}, this._config); + _config[ev.target.name.toString()] = ev.target.value; + this._config = _config; + + if (ev.type == 'change') { + // If the Device Family changes, we need to blank the device type value, or + // if a boolean style checkbox value changes, special handling is required + switch(ev.target.id) { + case 'device_family': + this._config.device_type = null; + break; + case 'hideFramesCheckbox': + this._config.hide_button_group_frame = ev.target.checked; + break; + case 'useCustomSkinCheckbox': + this._config.useCustomSkin = ev.target.checked; + break; + case "showMediaControlsCheckbox": + this._config.show_media_controls = ev.target.checked; + break; + } + } + + // A config-changed event will tell lovelace we have made a change to the configuration + // this will make sure the changes are saved correctly later and will update the preview + const event = new CustomEvent("config-changed", { + detail: { config: _config }, + bubbles: true, + composed: true, + }); + this.dispatchEvent(event); + } + + + translateToUsrLang(englishString) { + const ha_language = this.hass.config.language; + var translatedString = englishString; + if (typeof translationmap.get(ha_language) !== 'undefined'){ + if (typeof translationmap.get(ha_language)[englishString] !== 'undefined'){ + translatedString = translationmap.get(ha_language)[englishString]; + } + } + return translatedString; + } + + removeMatching(originalArray, regex) { + var j = 0; + while (j < originalArray.length) { + if (regex.test(originalArray[j])) + originalArray.splice(j, 1); + else + j++; + } + return originalArray; + } + + getEntitiesByType(type) { + return Object.keys(this.hass.states).filter( + (eid) => eid.substr(0, eid.indexOf('.')) === type + ); + } + + getEntitiesByPlatform(platformName) { + return Object.keys(this.hass.entities).filter( + (eid) => this.hass.entities[eid].platform === platformName + ); + } + + getMediaPlayerEntitiesByPlatform(platformName) { + var entities = Object.keys(this.hass.entities).filter( + (eid) => this.hass.entities[eid].platform === platformName + ); + const re = /media_player/; + return entities.filter(a => re.exec(a)); + } + + + getRemoteEntitiesByPlatform(platformName) { + //console.log(this.hass.entities); + var entities = Object.keys(this.hass.entities).filter( + (eid) => this.hass.entities[eid].platform === platformName + ); + const re = /remote/; + const negreg = /Supports/i; + return this.removeMatching(entities.filter(a => re.exec(a)), negreg); + } + + getDeviceFamiliesDropdown(optionvalue){ + var familykeys = []; + for(var [key, value] of devicemap.entries()) { + familykeys.push(key) + } + return html ` + +
+ `; + } + + + + getMediaPlayerEntityDropdown(optionValue){ + if(['none'].includes(this._config.device_family)) { + this._config.entity = 'none'; + this.configChanged; + return; + } + var mediaPlayerEntities = []; + var heading = ''; + if(this._config.device_family == 'apple-tv') { + mediaPlayerEntities = this.getMediaPlayerEntitiesByPlatform('apple_tv'); + heading = 'Apple TV Media Player '+ this.translateToUsrLang('Entity'); + heading = this.hass.config.language == 'he' || this.hass.config.language == 'fr' ? + this.translateToUsrLang('Entity') + ' Apple TV Media Player' : 'Apple TV Media Player '+ this.translateToUsrLang('Entity'); + } + else if(this._config.device_family == 'roku') { + mediaPlayerEntities = this.getMediaPlayerEntitiesByPlatform('roku'); + heading = this.hass.config.language == 'he' || this.hass.config.language == 'fr' ? + this.translateToUsrLang('Entity') + ' Roku Media Player' : 'Roku Media Player '+ this.translateToUsrLang('Entity'); + } + else { + mediaPlayerEntities = this.getMediaPlayerEntitiesByPlatform('androidtv'); + heading = this.hass.config.language == 'he' || this.hass.config.language == 'fr' ? + this.translateToUsrLang('Entity') + ' Android Debug Bridge' : 'Android Debug Bridge '+ this.translateToUsrLang('Entity'); + } + var blankEntity = ''; + if(this._config.entity == '' || !(mediaPlayerEntities).includes(optionValue)) { + blankEntity = html ` `; + } + return html` + ${heading}:
+ +
+
` + } + + + getAssociatedRemoteEntityDropdown(optionValue){ + if(['amazon-fire', 'none'].includes(this._config.device_family)) { return; } + var blankRemoteEntity = ''; + var remoteEntities = []; + var appleTVRemoteEntities = this.getRemoteEntitiesByPlatform('apple_tv'); + if(this._config.device_family == 'apple-tv') { + var dropdownLabel = this.translateToUsrLang('Associated') + ' Apple TV Remote ' + this.translateToUsrLang('Entity'); + dropdownLabel = this.hass.config.language == 'he' ? 'ישות משויכת ל-' + 'Apple TV Remote ' : dropdownLabel; + if(this._config.apple_tv_remote_entity == '' || typeof this._config.apple_tv_remote_entity == 'undefined') { + blankRemoteEntity = html ` `; + } + remoteEntities = this.getRemoteEntitiesByPlatform('apple_tv'); + return html` + ${dropdownLabel}:
+ +
+
` + } + if(this._config.device_family == 'roku') { + var dropdownLabel = this.translateToUsrLang('Associated') + ' Roku Remote ' + this.translateToUsrLang('Entity'); + dropdownLabel = this.hass.config.language == 'he' ? 'ישות משויכת ל-' + 'Roku Remote ' : dropdownLabel; + if(this._config.roku_remote_entity == '' || typeof this._config.roku_remote_entity == 'undefined') { + blankRemoteEntity = html ` `; + } + remoteEntities = this.getRemoteEntitiesByPlatform('roku'); + return html` + ${dropdownLabel}:
+ +
+
` + } + else { + var dropdownLabel = this.translateToUsrLang('Associated') + ' Android TV Remote ' + this.translateToUsrLang('Entity') + ': (' + this.translateToUsrLang('optional') + ')'; + dropdownLabel = this.hass.config.language == 'he' ? 'ישות משויכת ל-' + 'Android TV Remote ' : dropdownLabel; + dropdownLabel = this.hass.config.language == 'fr' ? 'Entité Android TV Remote Associé (optionnel)' : dropdownLabel; + if(this._config.androidTVRemoteEntity == '' || typeof this._config.androidTVRemoteEntity == 'undefined') { + blankRemoteEntity = html ` `; + } + remoteEntities = this.getRemoteEntitiesByPlatform('androidtv_remote'); + return html` + ${dropdownLabel}
+ +
+
` + } + } + + + getDeviceTypeDropdown(optionValue, dropdownLabel){ + if (this._config.device_family === 'none') { + this._config.device_type = 'other'; + this._config.entity = 'none'; + delete this._config['apple_tv_remote_entity']; + delete this._config['android_tv_remote_entity']; + this.configChanged; + return; + } + var family = this._config.device_family; + var optionMenu = String(); + Object.entries(devices).forEach(deviceFamily => { + const [familyKey,familyValue] = deviceFamily; + if(familyKey == family) { + var blankWasDisplayed = false; + Object.entries(familyValue).forEach(deviceCategory => { + const [categorykey,categoryvalue] = deviceCategory; + if(categorykey == 'meta') {return} + if(optionValue in categoryvalue) {blankWasDisplayed = true} + if(!(optionValue in categoryvalue) && !(blankWasDisplayed)){ + optionMenu += ''; + blankWasDisplayed = true; + } + if(categorykey != 'noCategory'){optionMenu += ''} + Object.entries(categoryvalue).forEach(deviceEntry => { + const [devicekey,deviceproperties] = deviceEntry; + if(deviceproperties.supported) { + if(devicekey == this._config.device_type) { + optionMenu += ''; + } + else { + optionMenu += ''; + } + } + else { + optionMenu += ''; + } + }) + if(categorykey != 'noCategory'){optionMenu += ''} + }) + } + }) + return html ` + ${dropdownLabel}:
+ +
+
+ `; + } + + + getCompatibilityModeDropdown(optionValue, deviceFriendlyName){ + if(['apple-tv', 'chromecast', 'homatics', 'nvidia-shield', 'onn', 'roku', 'none'].includes(this._config.device_family)) { return; } + if(['xiaomi-tv-stick-4k', 'fire_tv_stick_4k_max_second_gen', 'fire_tv_stick_4k_second_gen'].includes(this._config.device_type)) { return; } + var heading = this.translateToUsrLang('Compatibility Mode'); + return html` + ${heading}:
+ +
+
+ `; + } + + + getAppChoiceOptionMenus(remoteStyle) { + if(['none'].includes(this._config.device_family)) { return; } + var family = this._config.device_family; + if(appButtonMax[remoteStyle]) { + var appkeys = []; + for (var [key, value] of appmap.entries()) { + appkeys.push(key) + } + const optionsmap = new Map(); + for(let i=1; i<=appButtonMax[remoteStyle]; i++) { + optionsmap.set(i, "app_launch_"+i); + } + var optionkeys = []; + for (var [key, value] of optionsmap.entries()) { + optionkeys.push(key) + } + + return html ` +
+ ${optionkeys.map((optionnumber) => { + var blankOption = html ``; + if(!(appmap.has(optionvalue))){ + blankOption = html ``; + } + var heading = this.translateToUsrLang('App Launch Button'); + var optionvalue = this._config[optionsmap.get(optionnumber)]; + return html ` + ${heading} ${optionnumber}: + +
+ `; + })} + `; + } + } + + getALConfigOptions(remoteStyle){ + if(remoteStyle && remoteStyle=="AL1"){ + return html ` + +



+ `; + } + if(remoteStyle && remoteStyle=="AL2"){ + return html ` + +

+ +

+ ${this.translateToUsrLang('DPad Style')}: + +



+ `; + } + } + + getChromecastMediaControls(remoteStyle) { + if (['CC1', 'CC2', 'CC3'].includes(remoteStyle)) { + return html` + +
`; + } + } + + render() { + if (!this.hass || !this._config) { + return html``; + } + + if(!this._config.device_family) { + this._config.device_family = devicemap.keys().next().value; + } + + var appLauncherRelativeScaleSlide = ''; + if(this._config.defaultRemoteStyle_override == 'AL2') { + if(!(this._config.app_launcher_relative_size)) { + this._config.app_launcher_relative_size = 0; + } + appLauncherRelativeScaleSlide = html ` +
+ +
`; + } + + // Get current device's Attributes AND use any applicable overrides from user conf + var confRef = this._config; + function getDeviceAttribute(deviceAttribute){ + return deviceAttributeQuery(deviceAttribute, confRef); + } + + // Rebuild AppMap - allow hdmi inputs where appropriate & add configured custom launchers from YAML + refreshAppMap(this._config, getDeviceAttribute('hdmiInputs'), getDeviceAttribute('avInputs'), getDeviceAttribute('tuner')); + + var deviceModelSelectorLabel = devicemap.get(this._config.device_family).meta.friendlyName + ' ' + this.translateToUsrLang('Device Model'); + if (this.hass.config.language == 'he') { + deviceModelSelectorLabel = this.translateToUsrLang('Device Model') + ' ' + devicemap.get(this._config.device_family).meta.friendlyName; + } + + if(this._config.device_type == null || this._config.device_type == '') { + return html` + ${this.translateToUsrLang('Device Family')}:
+ ${this.getDeviceFamiliesDropdown(this._config.device_family)} +
+ + ${this.getDeviceTypeDropdown(this._config.device_type, deviceModelSelectorLabel)}`; + } + else { + + return html` + ${this.translateToUsrLang('Device Family')}:
+ ${this.getDeviceFamiliesDropdown(this._config.device_family)} +
+ + ${this.getDeviceTypeDropdown(this._config.device_type, deviceModelSelectorLabel)} + + ${this.getMediaPlayerEntityDropdown(this._config.entity)} + + ${this.getAssociatedRemoteEntityDropdown(this._config.android_tv_remote_entity)} + +
+ +
+ +
+ ${appLauncherRelativeScaleSlide} + +
+ ${this.translateToUsrLang('Remote Style')}:
+ +
+
+ ${this.getChromecastMediaControls(getDeviceAttribute("defaultRemoteStyle"))} + + ${this.getCompatibilityModeDropdown(this._config.compatibility_mode, getDeviceAttribute('friendlyName'))} + + ${this.getAppChoiceOptionMenus(getDeviceAttribute("defaultRemoteStyle"))} +
+
+
+ ${this.getALConfigOptions(getDeviceAttribute("defaultRemoteStyle"))} + + +
+
+ ${this.translateToUsrLang('Name Position')}:
+
+
+ + + `; + } + } +} + +customElements.define("firemote-card-editor", FiremoteCardEditor); diff --git a/www/community/HA-Firemote/HA-Firemote.js.gz b/www/community/HA-Firemote/HA-Firemote.js.gz new file mode 100644 index 0000000..ae1fb73 Binary files /dev/null and b/www/community/HA-Firemote/HA-Firemote.js.gz differ diff --git a/www/community/HA-Firemote/language-translations.js b/www/community/HA-Firemote/language-translations.js new file mode 100644 index 0000000..154bbf2 --- /dev/null +++ b/www/community/HA-Firemote/language-translations.js @@ -0,0 +1,509 @@ +//App name translations languageCode{ englishWord:translation } + +const rosettaStone = { + + "da": { + "App Launch Button": "App-knap", + "Apple Computers": "Apple Computere", + "Apple Music": "Apple Musik", + "Apple Photos": "Apple Fotos", + "Associated": "Tilhørende", + "bottom": "bund", + "Compatibility Mode": "Kompatibilitetstilstand", + "Computers": "Computere", + "Device Family": "Enhedsfamilie", + "Device Model": "Enhedsmodel", + "Device Name Text Color": "Tekstfarve på enhedsnavn", + "Function: Control Center": "Funktion: Kontrolcenter", + "Function: Find My Remote": "Funktion: Find min fjernbetjening", + "Function: Mute": "Funktion: Slå lyden fra", + "Function: Next": "Funktion: Næste", + "Function: Previous": "Funktion: Tidligere", + "Function: Reboot": "Funktion: Genstart", + "Function: Search": "Funktion: Søg", + "Function: Settings": "Funktion: Indstillinger", + "Function: Skip Backward": "Funktion: Spring tilbage", + "Function: Skip Forward": "Funktion: Spring fremad", + "Function: Switch Apps": "Funktion: Skift apps", + "hidden": "skjult", + "Music": "Musik", + "Name Position": "Position på navn", + "Photos": "Fotos", + "Remote Style": "Fjernbetjeningsstil", + "Scale": "Størrelse", + "Settings": "Indstillinger", + "Search": "Søg", + "Visible Device Name": "Synligt enhedsnavn", + }, + + "de": { + "App Launch Button": "Anwendungsstartknopf", + "App Launcher Relative Scale": "Relativer Maßstab des Anwendungslaunchers", + "Apple Computers": "Apple Computer", + "Apple Music": "Apple Musik", + "Apple Photos": "Apple Bilder", + "Associated": "Assoziiert", + "bottom": "unten", + "Compatibility Mode": "Kompatibilitätsmodus", + "Default for": "Standard für", + "Device Family": "Gerätegruppe", + "Device Model": "Gerätemodell", + "Device Name Text Color": "Textfarbe des Gerätenamens", + "Entity": "Entität", + "Function: Control Center": "Funktion: Kontrollzentrum", + "Function: Find My Remote": "Funktion: Finde meine Fernbedienung", + "Function: Mute": "Funktion: Stummschalten", + "Function: Next": "Funktion: Weiter", + "Function: Previous": "Funktion: Zurück", + "Function: Reboot": "Funktion: Neustarten", + "Function: Search": "Funktion: Suchen", + "Function: Settings": "Funktion: Einstellungen", + "Function: Skip Backward": "Funktion: Zurückspulen", + "Function: Skip Forward": "Funktion: Vorspulen", + "Function: Switch Apps": "Funktion: Apps wechseln", + "hidden": "versteckt", + "Name Position": "Position des Namens", + "Remote Style": "Aussehen der Fernbedienung", + "Scale": "Größe", + "Settings": "Einstellungen", + "Search": "Suche", + "top": "oben", + "Visible Device Name": "Sichtbarer Gerätename", + }, + + "es": { + "App Launch Button": "Botón de Aplicación", + "App Launcher Relative Scale": "Escala Relativa del Lanzador de Aplicaciones", + "Apple Computers": "Computadoras", + "Apple Music": "Música", + "Apple Photos": "Fotos", + "Associated": "Asociado", + "bottom": "parte inferior", + "Compatibility Mode": "Modo de Compatibilidad", + "Custom Remote Skin": "Piel Personalizada del Control Remoto", + "Default for": "Predeterminado para", + "Device Family": "Familia de Dispositivos", + "Device Model": "Modelo de Dispositivo", + "Device Name Text Color": "Color del Texto del Nombre del Dispositivo", + "DPad Style": "Estilo de DPad", + "Entity": "Entidad", + "Function: Channel +": "Función: Canal +", + "Function: Channel -": "Función: Canal -", + "Function: Control Center": "Función: Centro de Control", + "Function: Find My Remote": "Función: Encontrar Mi Control Remoto", + "Function: Mute": "Función: Silenciar", + "Function: Next": "Función: Siguiente", + "Function: Previous": "Función: Anterior", + "Function: Reboot": "Función: Reiniciar", + "Function: Search": "Función: Buscar", + "Function: Settings": "Función: Ajustes", + "Function: Skip Backward": "Función: Saltar hacia atrás", + "Function: Skip Forward": "Función: Saltar hacia adelante", + "Function: Switch Apps": "Función: Cambiar de Aplicación", + "Function: Volume +": "Función: Volumen +", + "Function: Volume -": "Función: Volumen -", + "hidden": "oculto", + "Hide frames around button groups": "Ocultar Marcos alrededor de los Grupos de Botones", + "Music": "Música", + "Name Position": "Posición del Nombre", + "optional": "opcional", + "Photos": "Fotos", + "Remote Style": "Estilo del Control Remoto", + "Scale": "Escala", + "Settings": "Configuración", + "Search": "Buscar", + "top": "parte superior", + "Visible Device Name": "Nombre del Dispositivo Visible", + }, + + "es-419": { + "App Launch Button": "Botón de Aplicación", + "App Launcher Relative Scale": "Escala Relativa del Lanzador de Aplicaciones", + "Apple Computers": "Computadoras", + "Apple Music": "Música", + "Apple Photos": "Fotos", + "Associated": "Asociado", + "bottom": "parte inferior", + "Compatibility Mode": "Modo de Compatibilidad", + "Custom Remote Skin": "Piel Personalizada del Control Remoto", + "Default for": "Predeterminado para", + "Device Family": "Familia de Dispositivos", + "Device Model": "Modelo de Dispositivo", + "Device Name Text Color": "Color del Texto del Nombre del Dispositivo", + "DPad Style": "Estilo de DPad", + "Entity": "Entidad", + "Function: Channel +": "Función: Canal +", + "Function: Channel -": "Función: Canal -", + "Function: Control Center": "Función: Centro de Control", + "Function: Find My Remote": "Función: Encontrar Mi Control Remoto", + "Function: Mute": "Función: Silenciar", + "Function: Next": "Función: Siguiente", + "Function: Previous": "Función: Anterior", + "Function: Reboot": "Función: Reiniciar", + "Function: Search": "Función: Buscar", + "Function: Settings": "Función: Ajustes", + "Function: Skip Backward": "Función: Saltar hacia atrás", + "Function: Skip Forward": "Función: Saltar hacia adelante", + "Function: Switch Apps": "Función: Cambiar de Aplicación", + "Function: Volume +": "Función: Volumen +", + "Function: Volume -": "Función: Volumen -", + "hidden": "oculto", + "Hide frames around button groups": "Ocultar Marcos alrededor de los Grupos de Botones", + "Music": "Música", + "Name Position": "Posición del Nombre", + "optional": "opcional", + "Photos": "Fotos", + "Remote Style": "Estilo del Control Remoto", + "Scale": "Escala", + "Settings": "Configuración", + "Search": "Buscar", + "top": "parte superior", + "Visible Device Name": "Nombre del Dispositivo Visible", + }, + + "et": { + "App Launch Button": "Rakenduse nupp", + "App Launcher": "Rakendusekäiviti", + "App Launcher Relative Scale": "Rakenduse nupu skaleering", + "Apple Computers": "Apple Arvutid", + "Apple Music": "Apple Muusika", + "Apple Photos": "Apple Pildid", + "Associated": "Seotud", + "black": "must", + "bottom": "Jaluses", + "Compatibility Mode": "Ühilduvus režiim", + "Custom Remote Skin": "Kohandatud Puldi Kate", + "Default for": "Vaikesäte", + "Device Family": "Seadme tüüp", + "Device Model": "Seadme mudel", + "Device Name Text Color": "Seadme nime värv", + "DPad Style": "DPadi Stiil", + "Entity": "Olem", + "Function: Captions": "Funktsioon: Subtiitrid", + "Function: Channel +": "Funktsioon: Kanal ↑", + "Function: Channel -": "Funktsioon: Kanal ↓", + "Function: Control Center": "Funktsioon: Keskhaldus", + "Function: Find My Remote": "Funktsioon: Leia mu pult", + "Function: Mute": "Funktsioon: Vaigista", + "Function: Next": "Funktsioon: Järgmine", + "Function: Pairing": "Funktsioon: Paaritamine", + "Function: Previous": "Funktsioon: Eelmine", + "Function: Reboot": "Funktsioon: Taaskäivita", + "Function: Search": "Funktsioon: Otsi", + "Function: Settings": "Funktsioon: Sätted", + "Function: Skip Backward": "Funktsioon: Keri tagasi", + "Function: Skip Forward": "Funktsioon: Keri edasi", + "Function: Switch Apps": "Funktsioon: Vaheta rakendusi", + "Function: System Update": "Funktsioon: Süsteemi uuendamine", + "Function: Volume +": "Funktsioon: Volüüm +", + "Function: Volume -": "Funktsioon: Volüüm -", + "hidden": "Peidetud", + "Hide frames around button groups": "Peida raamid nuppude gruppide ümber", + "Minimal": "Minimaalne", + "Name Position": "Nime asukoht", + "optional": "Valikuline", + "remote": "pult", + "Remote Style": "Puldi stiil", + "remote style": "puldi stiil", + "Scale": "Skaala", + "silver": "hõbe", + "style": "stiil", + "top": "Päises", + "Visible Device Name": "Nähtav seadme nimi", + }, + "fr": { + "App Launch Button": "Bouton de lancement d'application", + "App Launcher Relative Scale": "Échelle relative du lanceur d'applications", + "Apple Computers": "Ordinateurs Apple", + "Associated": "Associé", + "bottom": "bas", + "Compatibility Mode": "Mode de compatibilité", + "Custom Remote Skin": "Apparence personnalisée de la télécommande", + "Default for": "Par défaut pour", + "Device Family": "Famille d'appareils", + "Device Model": "Modèle d'appareil", + "Device Name Text Color": "Couleur du texte du nom de l'appareil", + "DPad Style": "Style du DPad", + "Entity": "Entité", + "Function: Captions": "Fonction : Sous-titres", + "Function: Channel +": "Fonction : Chaîne +", + "Function: Channel -": "Fonction : Chaîne -", + "Function: Control Center": "Fonction : Centre de contrôle", + "Function: Find My Remote": "Fonction : Trouver ma télécommande", + "Function: Mute": "Fonction : Muet", + "Function: Next": "Fonction : Suivant", + "Function: Pairing": "Fonction : Appairage", + "Function: Previous": "Fonction : Précédent", + "Function: Reboot": "Fonction : Redémarrer", + "Function: Search": "Fonction : Recherche", + "Function: Settings": "Fonction : Paramètres", + "Function: Skip Backward": "Fonction : Retour rapide", + "Function: Skip Forward": "Fonction : Avance rapide", + "Function: Switch Apps": "Fonction : Changer d'application", + "Function: System Update": "Fonction : Mise à jour du système", + "Function: Volume +": "Fonction : Volume +", + "Function: Volume -": "Fonction : Volume -", + "hidden": "caché", + "Hide frames around button groups": "Masquer les cadres autour des groupes de boutons", + "Name Position": "Position du nom", + "optional": "optionnel", + "Remote Style": "Style de la télécommande", + "Scale": "Échelle", + "Search": "Recherche", + "top": "haut", + "Visible Device Name": "Nom de l'appareil visible" + }, + "he": { + "App Launch Button": "לחצן הפעלת אפליקציה", + "App Launcher Relative Scale": "קנה מידה יחסי של לחצן הפעלת אפליקציה", + "Apple Computers": "מחשבי אפל", + "Apple Music": "אפל מיוזיק", + "Apple Photos": "אפל תמונות", + "Associated": "משויך", + "bottom": "למטה", + "Compatibility Mode": "מצב תאימות", + "Default for": "ברירת מחדל עבור", + "Device Family": "משפחת מכשיר", + "Device Model": "דגם מכשיר", + "Device Name Text Color": "צבע טקסט של שם המכשיר", + "Entity": "ישות", + "Function: Channel +": "+ פונקציה: ערוץ", + "Function: Channel -": "- פונקציה: ערוץ", + "Function: Control Center": "פונקציה: מרכז בקרה", + "Function: Find My Remote": "מצא את השלט", + "Function: Mute": "פונקציה: השתק", + "Function: Next": "פונקציה: הבא", + "Function: Previous": "פונקציה: הקודם", + "Function: Reboot": "פונקציה: איתחול", + "Function: Search": "פונקציה: חיפוש", + "Function: Settings": "פונקציה: הגדרות", + "Function: Skip Backward": "פונקציה: דלג אחורה", + "Function: Skip Forward": "פונקציה: דלג קדימה", + "Function: Switch Apps": "פונקציה: החלף אפליקציה", + "Function: Volume +": "+ פונקציה: ווליום", + "Function: Volume -": "- פונקציה: ווליום", + "hidden": "מוסתר", + "Name Position": "מיקום השם", + "optional": "אופציונלי", + "Remote Style": "מראה השלט", + "Scale": "קנה מידה", + "Settings": "הגדרות", + "Search": "חיפוש", + "top": "למעלה", + "Visible Device Name": "שם מכשיר גלוי", + }, + + "lt": { + "App Launch Button": "Programos Paleidimo Mygtukas", + "App Launcher Relative Scale": "Programos Paleidėjo Realiatyvi Skalė", + "Apple Computers": "Apple Kompiuteriai", + "Apple Music": "Apple Muzika", + "Apple Photos": "Apple Nuotraukos", + "Associated": "Susiję", + "bottom": "Apačia", + "Compatibility Mode": "Suderinamumo Režimas", + "Default for": "Numatytasis", + "Device Family": "Įrenginių Šeima", + "Device Model": "Įrenginio Modelis", + "Device Name Text Color": "Įrenginio Pavadinimo Teksto Spalva", + "Entity": "Esybė", + "Function: Control Center": "Funkcija: Valdymo Centras", + "Function: Find My Remote": "Funkcija: Surask Mano Nuotolinio Valdymo Pultą", + "Function: Mute": "Funkcija: Nutildyti", + "Function: Next": "Funkcija: Kitas", + "Function: Previous": "Funkcija: Ankstesnis", + "Function: Reboot": "Funkcija: Perkrauti", + "Function: Search": "Funkcija: Paieška", + "Function: Settings": "Funkcija: Nustatymai", + "Function: Skip Backward": "Funkcija: Peršokti į Priekį", + "Function: Skip Forward": "Funkcija: Peršokti Atgal", + "Function: Switch Apps": "Funkcija: Perjungti Programas", + "hidden": "paslėpta", + "Name Position": "Pavadinimo Pozicija", + "optional": "Pasirinktinai", + "Remote Style": "Nuotolinio Pultelio Stilius", + "Scale": "Skalė", + "Settings": "Nustatymai", + "Search": "Paieška", + "top": "Viršus", + "Visible Device Name": "Matomas Įrenginio Pavadinimas", + }, + + "nl": { + "App Launch Button": "Knop Voor Het Starten van apps", + "Apple Music": "Apple Muziek", + "Apple Photos": "Apple Foto's", + "Associated": "Aangesloten", + "bottom": "onderkant", + "Compatibility Mode": "Compatibiliteitsmodus", + "Device Family": "Apparaatfamilie", + "Device Model": "Apparaat Model", + "Device Name Text Color": "Apparaatnaam Tekstkleur", + "Function: Control Center": "Functie: Controlecentrum", + "Function: Find My Remote": "Functie: Vind Mijn afstandsbediening", + "Function: Mute": "Functie: Dempen", + "Function: Next": "Functie: Volgende", + "Function: Previous": "Functie: Vorige", + "Function: Reboot": "Functie: opnieuw opstarten", + "Function: Search": "Functie: Zoeken", + "Function: Settings": "Functie: Instellingen", + "Function: Skip Backward": "Functie: Achterwaarts springen", + "Function: Skip Forward": "Functie: Vooruitspringen", + "Function: Switch Apps": "Functie: Schakel Apps", + "hidden": "verborgen", + "Music": "Muziek", + "Name Position": "Positie van Naam", + "Photos": "Foto's", + "Remote Style": "Afstandsbedieningstijl", + "Scale": "Meten", + "Settings": "Instellingen", + "Search": "Zoek", + "top": "bovenkant", + "Visible Device Name": "Zichtbare apparaatnaam", + }, + + "pl": { + "App Launch Button": "Przycisk uruchamiania aplikacji", + "App Launcher Relative Scale": "Względna Skala przycisków uruchamiania aplikacji", + "Apple Computers": "Komputery Apple", + "Apple Music": "Apple Music", + "Apple Photos": "Apple Photos", + "Associated": "Skojarzona", + "bottom": "dół", + "Compatibility Mode": "Tryb zgodności", + "Default for": "Standardowe dla", + "Device Family": "Rodzina urządzeń", + "Device Model": "Model Urządzenia", + "Device Name Text Color": "Nazwa urządzenia Kolor tekstu", + "Entity": "Encja", + "Function: Channel +": "Funkcja: kanał +", + "Function: Channel -": "Funkcja: kanał -", + "Function: Control Center": "Funkcja: centrum Sterowania", + "Function: Find My Remote": "Funkcja: Znajdź mojego pilot", + "Function: Mute": "Funkcja: Wycisz", + "Function: Next": "Funkcja: następne", + "Function: Previous": "Funkcja: poprzednie", + "Function: Reboot": "Funkcja: Ponowny rozruch", + "Function: Search": "Funkcja: Szukaj", + "Function: Settings": "Funkcja: Ustawienia", + "Function: Skip Backward": "Funkcja: przeskocz Do tyłu", + "Function: Skip Forward": "Funkcja: przeskocz Do przodu", + "Function: Switch Apps": "Funkcja: Przełącz aplikacje", + "Function: Volume +": "Funkcja: Głośność +", + "Function: Volume -": "Funkcja: Głośność -", + "hidden": "ukryty", + "Name Position": "Pozycja imienia", + "optional": "opcjonalnie", + "Remote Style": "Styl pilota", + "Scale": "Skala", + "Settings": "Ustawienia", + "Search": "Szukaj", + "top": "góra", + "Visible Device Name": "Widoczna nazwa Urządzenia", + }, + + "pt": { + "App Launch Button": "Botão de Inicialização Da Aplicação", + "Associated": "Associada", + "bottom": "fundo", + "Compatibility Mode": "Modo de compatibilidade", + "Device Family": "Família de Dispositivos", + "Device Model": "Modelo do Dispositivo", + "Device Name Text Color": "Cor do Texto do Nome do dispositivo", + "Function: Control Center": "Função: Centro de Controlo", + "Function: Find My Remote": "Função: Encontrar Controlo Remoto", + "Function: Mute": "Função: Mudo", + "Function: Next": "Função: Próximo", + "Function: Previous": "Função: Anterior", + "Function: Reboot": "Função: Reiniciar", + "Function: Search": "Função: Pesquisa", + "Function: Settings": "Função: Configurações", + "Function: Skip Backward": "Função: Saltar Para trás", + "Function: Skip Forward": "Função: Saltar par aa Frente", + "Function: Switch Apps": "Função: Alternar Aplicativos", + "hidden": "escondido", + "Name Position": "Posição do Nome", + "Remote Style": "Estilo Controlo Remoto", + "Scale": "Escala", + "top": "topo", + "Visible Device Name": "Nome do Dispositivo Visível", + }, + + "pt-BR": { + "App Launch Button": "Botão de Inicialização Do Aplicativo", + "Associated": "Associada", + "bottom": "fundo", + "Compatibility Mode": "Modo de compatibilidade", + "Device Family": "Família de Dispositivos", + "Device Model": "Modelo do Dispositivo", + "Device Name Text Color": "Cor do Texto do Nome do dispositivo", + "Function: Control Center": "Função: Centro de Controle", + "Function: Find My Remote": "Função: Encontre Meu Controle Remoto", + "Function: Mute": "Função: Mudo", + "Function: Next": "Função: Próximo", + "Function: Previous": "Função: Anterior", + "Function: Reboot": "Função: Reiniciar", + "Function: Search": "Função: Pesquisa", + "Function: Settings": "Função: Configurações", + "Function: Skip Backward": "Função: Pular Para trás", + "Function: Skip Forward": "Função: Pular Adiante", + "Function: Switch Apps": "Função: Alternar Aplicativos", + "hidden": "escondido", + "Name Position": "Posição do Nome", + "Remote Style": "Estilo Remoto", + "Scale": "Escala", + "top": "topo", + "Visible Device Name": "Nome do Dispositivo Visível", + }, + + "sv": { + "App Launch Button": "Appstartknapp", + "App Launcher Relative Scale": "Appstartknapparnas Storlek", + "Associated": "Associerad", + "black": "svart", + "bottom": "nederkant", + "Compatibility Mode": "Kompabilitetsläge", + "Custom Remote Skin": "Anpassat Skal För Fjärrkontroll", + "Default for": "Standard För", + "Device Family": "Enhetsfamilj", + "Device Model": "Enhetsmodell", + "Device Name Text Color": "Textfärg För enhetsnamn", + "DPad Style": "DPad Stil", + "Entity": "Entitet", + "Function: Captions": "Funktion: Textning", + "Function: Channel +": "Funktion: Kanal +", + "Function: Channel -": "Funktion: Kanal -", + "Function: Control Center": "Funktion: Kontrollcenter", + "Function: Find My Remote": "Funktion: Hitta Min Fjärrkontroll", + "Function: Mute": "Funktion: Tysta", + "Function: Next": "Funktion: Nästa", + "Function: Pairing": "Funktion: Parning", + "Function: Previous": "Funktion: Föregående", + "Function: Reboot": "Funktion: Starta om", + "Function: Search": "Funktion: Sök", + "Function: Settings": "Funktion: Inställningar", + "Function: Skip Backward": "Funktion: Hoppa Bakåt", + "Function: Skip Forward": "Funktion: Hoppas Framåt", + "Function: Switch Apps": "Funktion: Växla Appar", + "Function: System Update": "Funktion: Uppdatera Systemet", + "Function: Volume +": "Funktion: Volym +", + "Function: Volume -": "Funktion: Volym -", + "hidden": "dolt", + "Hide frames around button groups": "Dölj ramar runt knappgrupper", + "Name Position": "Placering Av Enhetsnamn", + "None / Other": "Ingen / Annan", + "optional": "valfri", + "Remote Style": "Fjärrkontrollsstil", + "remote style": "fjärrkontrollsstil", + "Scale": "Storlek", + "Settings": "Inställningar", + "Search": "Sök", + "Strong (Slower)": "Starkare (långsammare)", + "style": "stil", + "top": "överkant", + "Visible Device Name": "Synligt Enhetsnamn" + }, + +}; + +export { rosettaStone }; diff --git a/www/community/HA-Firemote/language-translations.js.gz b/www/community/HA-Firemote/language-translations.js.gz new file mode 100644 index 0000000..8dc7404 Binary files /dev/null and b/www/community/HA-Firemote/language-translations.js.gz differ diff --git a/www/community/HA-Firemote/launcher-buttons.js b/www/community/HA-Firemote/launcher-buttons.js new file mode 100644 index 0000000..37b9a34 --- /dev/null +++ b/www/community/HA-Firemote/launcher-buttons.js @@ -0,0 +1,12585 @@ +const launcherData = { + + "12-plus": { + "button": '', + "friendlyName": "12+", + "className": "twelvePlusButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "12+", + }, + "chromecast": { + "appName": "com.keshet.mako.VODAndroidTV", + "androidName": "com.keshet.mako.VODAndroidTV", + "adbLaunchCommand": "adb shell am start -n com.keshet.mako.VODAndroidTV/com.keshet.mako.VODAndroidTV.controller.activitiesAndFragments.Activities.MainActivity", + }, + "homatics": { + "appName": "com.keshet.mako.VODAndroidTV", + "androidName": "com.keshet.mako.VODAndroidTV", + "adbLaunchCommand": "adb shell am start -n com.keshet.mako.VODAndroidTV/com.keshet.mako.VODAndroidTV.controller.activitiesAndFragments.Activities.MainActivity", + }, + "nvidia-shield": { + "appName": "com.keshet.mako.VODAndroidTV", + "androidName": "com.keshet.mako.VODAndroidTV", + "adbLaunchCommand": "adb shell am start -n com.keshet.mako.VODAndroidTV/com.keshet.mako.VODAndroidTV.controller.activitiesAndFragments.Activities.MainActivity", + }, + "onn": { + "appName": "com.keshet.mako.VODAndroidTV", + "androidName": "com.keshet.mako.VODAndroidTV", + "adbLaunchCommand": "adb shell am start -n com.keshet.mako.VODAndroidTV/com.keshet.mako.VODAndroidTV.controller.activitiesAndFragments.Activities.MainActivity", + }, + "xiaomi": { + "appName": "com.keshet.mako.VODAndroidTV", + "androidName": "com.keshet.mako.VODAndroidTV", + "adbLaunchCommand": "adb shell am start -n com.keshet.mako.VODAndroidTV/com.keshet.mako.VODAndroidTV.controller.activitiesAndFragments.Activities.MainActivity", + }, + }, + + + + "13news-now-wvec": { + "button": '', + "button-round": '', + "friendlyName": "13News Now - WVEC", + "className": "thirteenNewsNowWVECButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "tgna.wvec.ott.firetv", + "androidName": "tgna.wvec.ott.firetv", + "adbLaunchCommand": "adb shell am start -n tgna.wvec.ott.firetv/crc64d2d3e4bd2d040db8.SplashActivity", + }, + "apple-tv": { + "appName": "13News Now - WVEC", + }, + "roku": { + "appName": "13News Now - WVEC", + "app-id": 63399, + }, + }, + + + "abc-iview": { + "button": '', + "friendlyName": "ABC iview (AU)", + "className": "abciviewButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.net.abc.iview", + "androidName": "au.net.abc.iview", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n au.net.abc.iview/.ui.router.TvLauncherActivity", + }, + "apple-tv": { + "appName": "ABC iview", + }, + "chromecast": { + "appName": "au.net.abc.iview", + "androidName": "au.net.abc.iview", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n au.net.abc.iview/.ui.router.TvLauncherActivity", + }, + "homatics": { + "appName": "au.net.abc.iview", + "androidName": "au.net.abc.iview", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n au.net.abc.iview/.ui.router.TvLauncherActivity", + }, + "nvidia-shield": { + "appName": "au.net.abc.iview", + "androidName": "au.net.abc.iview", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n au.net.abc.iview/.ui.router.TvLauncherActivity", + }, + "xiaomi": { + "appName": "au.net.abc.iview", + "androidName": "au.net.abc.iview", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n au.net.abc.iview/.ui.router.TvLauncherActivity", + }, + }, + + + "ace-stream-media": { + "button": '', + "friendlyName": "Ace Stream", + "className": "aceStreamMediaButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "org.acestream.media", + "androidName": "org.acestream.media", + }, + "chromecast": { + "appName": "org.acestream.node", + "androidName": "org.acestream.node", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n org.acestream.node/org.acestream.engine.ui.MainWebViewActivity", + }, + "homatics": { + "appName": "org.acestream.node", + "androidName": "org.acestream.node", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n org.acestream.node/org.acestream.engine.ui.MainWebViewActivity", + }, + "nvidia-shield": { + "appName": "org.acestream.node", + "androidName": "org.acestream.node", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n org.acestream.node/org.acestream.engine.ui.MainWebViewActivity", + }, + "onn": { + "appName": "org.acestream.node", + "androidName": "org.acestream.node", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n org.acestream.node/org.acestream.engine.ui.MainWebViewActivity", + }, + "xiaomi": { + "appName": "org.acestream.node", + "androidName": "org.acestream.node", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n org.acestream.node/org.acestream.engine.ui.MainWebViewActivity", + }, + }, + + + "acorn-tv": { + "button": '', + "friendlyName": "Acorn TV", + "className": "acornTvButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "apple-tv": { + "appName": "Acorn TV", + }, + "chromecast": { + "appName": "com.acorntv.androidtv", + "androidName": "com.acorntv.androidtv", + "adbLaunchCommand": "adb shell am start -n com.acorntv.androidtv/com.globallogic.acorntv.ui.MainActivity", + }, + "homatics": { + "appName": "com.acorntv.androidtv", + "androidName": "com.acorntv.androidtv", + "adbLaunchCommand": "adb shell am start -n com.acorntv.androidtv/com.globallogic.acorntv.ui.MainActivity", + }, + "nvidia-shield": { + "appName": "com.acorntv.androidtv", + "androidName": "com.acorntv.androidtv", + "adbLaunchCommand": "adb shell am start -n com.acorntv.androidtv/com.globallogic.acorntv.ui.MainActivity", + }, + "onn": { + "appName": "com.acorntv.androidtv", + "androidName": "com.acorntv.androidtv", + "adbLaunchCommand": "adb shell am start -n com.acorntv.androidtv/com.globallogic.acorntv.ui.MainActivity", + }, + "roku": { + "appName": "Acorn TV", + "app-id": 14295, + }, + "xiaomi": { + "appName": "com.acorntv.androidtv", + "androidName": "com.acorntv.androidtv", + "adbLaunchCommand": "adb shell am start -n com.acorntv.androidtv/com.globallogic.acorntv.ui.MainActivity", + }, + }, + + + "air-screen": { + "button": '', + "friendlyName": "Air Screen", + "className": "airScreenButton", + "appName": "com.ionitech.airscreen", + "androidName": "com.ionitech.airscreen", + "adbLaunchCommand": "adb shell am start com.ionitech.airscreen/.ui.activity.welcome.StreamAssistantIndexActivity", + "deviceFamily": ["amazon-fire",], }, + + + "all-4": { + "button": '', + "friendlyName": "All4", + "className": "allFourButton", + "appName": "com.channel4.ondemand", + "androidName": "com.channel4.ondemand", + "adbLaunchCommand": "adb shell am start -n com.channel4.ondemand/com.novoda.all4.launch.LaunchActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "allente": { + "button": '', + "friendlyName": "Allente", + "className": "allenteButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Allente", + }, + }, + + + "altibox": { + "button": '', + "button-round": '', + "friendlyName": "altibox", + "className": "altiboxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + "apple-tv": { + "appName": "Altibox", + }, + "chromecast": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + "onn": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "no.altibox.nh", + "androidName": "no.altibox.nh", + "adbLaunchCommand": "adb shell am start -n no.altibox.nh/altibox.newhorizon.ui.splash.SplashActivity", + }, + }, + + + "amazon-music": { + "button": '', + "friendlyName": "Amazon Music", + "className": "amazonMusicButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.amazon.bueller.music", + "androidName": "com.amazon.bueller.music", + "adbLaunchCommand": "adb shell am start -n com.amazon.bueller.music/com.amazon.music.MainActivity", + }, + "apple-tv": { + "appName": "Amazon Music", + }, + "chromecast": { + "appName": "com.amazon.music.tv", + "androidName": "com.amazon.music.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.music.tv/com.amazon.music.MainActivity", + }, + "homatics": { + "appName": "com.amazon.music.tv", + "androidName": "com.amazon.music.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.music.tv/com.amazon.music.MainActivity", + }, + "nvidia-shield": { + "appName": "com.amazon.music.tv", + "androidName": "com.amazon.music.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.music.tv/com.amazon.music.MainActivity", + }, + "onn": { + "appName": "com.amazon.music.tv", + "androidName": "com.amazon.music.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.music.tv/com.amazon.music.MainActivity", + }, + "roku": { + "appName": "Amazon Music", + "app-id": 14362, + }, + "xiaomi": { + "appName": "com.amazon.music.tv", + "androidName": "com.amazon.music.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.music.tv/com.amazon.music.MainActivity", + }, + }, + + + "amc-plus": { + "button": '', + "friendlyName": "AMC+", + "className": "amcPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.amcplus.amcfiretv", + "androidName": "com.amcplus.amcfiretv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcfiretv/com.amcplus.tv.MainActivity", + }, + "apple-tv": { + "appName": "AMC+", + }, + "chromecast": { + "appName": "com.amcplus.amcandroidtv", + "androidName": "com.amcplus.amcandroidtv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcandroidtv/com.amcplus.tv.MainActivity", + }, + "homatics": { + "appName": "com.amcplus.amcandroidtv", + "androidName": "com.amcplus.amcandroidtv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcandroidtv/com.amcplus.tv.MainActivity", + }, + "nvidia-shield": { + "appName": "com.amcplus.amcandroidtv", + "androidName": "com.amcplus.amcandroidtv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcandroidtv/com.amcplus.tv.MainActivity", + }, + "onn": { + "appName": "com.amcplus.amcandroidtv", + "androidName": "com.amcplus.amcandroidtv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcandroidtv/com.amcplus.tv.MainActivity", + }, + "roku": { + "appName": "AMC+", + "app-id": 636527, + }, + "xiaomi": { + "appName": "com.amcplus.amcandroidtv", + "androidName": "com.amcplus.amcandroidtv", + "adbLaunchCommand": "adb shell am start -n com.amcplus.amcandroidtv/com.amcplus.tv.MainActivity", + }, + }, + + + "angel-studios": { + "button": '', + "friendlyName": "Angel Studios", + "className": "angelStudiosButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + "apple-tv": { + "appName": "Angel", + }, + "chromecast": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + "homatics": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + "onn": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + "roku": { + "appName": "Angel Studios", + "app-id": 652613, + }, + "xiaomi": { + "appName": "com.angel.tv", + "androidName": "com.angel.tv", + "adbLaunchCommand": "adb shell am start -n com.angel.tv/.MainActivity", + }, + }, + + + "apollo-group-tv": { + "button": '', + "button-round": '', + "friendlyName": "Apollo Group TV", + "className": "apolloGroupTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "chromecast": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "homatics": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "nvidia-shield": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "onn": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "xiaomi": { + "appName": "ctv.apollogroup.androidtv", + "androidName": "tv.apollogroup.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.apollogroup.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + }, + + + "app-opener": { + "button": "App Opener", + "friendlyName": "App Opener", + "appName": "devsimon.appopener", + "className": "appOpenerButton", + "androidName": "devsimon.appopener", + "adbLaunchCommand": "adb shell am start -n devsimon.appopener/devsimon.appopener.MainActivity", + "deviceFamily": ["amazon-fire"], }, + + + "apple-appstore": { + "button": '', + "friendlyName": 'Apple App Store', + "className": "appleAppStoreButton", + "appName": "App Store", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv": { + "button": '', + "friendlyName": 'Apple TV', + "className": "appleTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Apple TV+ (Fire TV)", + "androidName": "com.apple.atve.amazon.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.amazon.appletv/.MainActivity", + }, + "apple-tv": { + "appName": "TV", + }, + "chromecast": { + "appName": "com.apple.atve.androidtv.appletv", + "androidName": "com.apple.atve.androidtv.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.androidtv.appletv/.MainActivity", + }, + "homatics": { + "appName": "com.apple.atve.androidtv.appletv", + "androidName": "com.apple.atve.androidtv.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.androidtv.appletv/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.apple.atve.androidtv.appletv", + "androidName": "com.apple.atve.androidtv.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.androidtv.appletv/.MainActivity", + }, + "onn": { + "appName": "com.apple.atve.androidtv.appletv", + "androidName": "com.apple.atve.androidtv.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.androidtv.appletv/.MainActivity", + }, + "roku": { + "appName": "Apple TV", + }, + "xiaomi": { + "appName": "com.apple.atve.androidtv.appletv", + "androidName": "com.apple.atve.androidtv.appletv", + "adbLaunchCommand": "adb shell am start -n com.apple.atve.androidtv.appletv/.MainActivity", + }, + }, + + + "apple-tv-arcade": { + "button": '', + "friendlyName": 'Apple Arcade', + "className": "appleArcadeButton", + "appName": "Arcade", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-computers": { + "button": '', + "friendlyName": 'Apple Computers', + "className": "appleComputersButton", + "appName": "Computers", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-facetime": { + "button": '', + "friendlyName": 'Apple FaceTime', + "className": "appleFacetimeButton", + "appName": "FaceTime", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-fitness": { + "button": '', + "friendlyName": 'Apple Fitness', + "className": "appleFitnessButton", + "appName": "Fitness", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-movies": { + "button": 'movies', + "friendlyName": 'Apple Movies - iTunes', + "className": "appleTvMoviesButton", + "appName": "Movies", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-music": { + "button": '', + "friendlyName": 'Apple Music', + "className": "appleTvMusicButton", + "deviceFamily": ["apple-tv", "roku"], + "apple-tv": { + "appName": "Music", + }, + "roku": { + "appName": 'Apple Music', + "app-id": 637193, + }, + }, + + + "apple-tv-photos": { + "button": '', + "friendlyName": 'Apple Photos', + "className": "appleTvPhotosButton", + "appName": "Photos", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-podcasts": { + "button": '', + "friendlyName": 'Apple Podcasts', + "className": "appleTvPodcastsButton", + "appName": "Podcasts", + "deviceFamily": ["apple-tv"], + }, + + + "apple-tv-tvshows": { + "button": 'tv shows', + "friendlyName": 'Apple TV Shows - iTunes', + "className": "appleTvshowsButton", + "appName": "TV Shows", + "deviceFamily": ["apple-tv"], + }, + + + "architv-for-archillect": { + "button": '', + "friendlyName": "ArchiTV for Archillect", + "className": "archiTVForArchillectButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + "chromecast": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + "homatics": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + "nvidia-shield": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + "onn": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + "xiaomi": { + "appName": "space.linuxct.architv", + "androidName": "space.linuxct.architv", + "adbLaunchCommand": "adb shell am start -n space.linuxct.architv/space.linuxct.architv.MainActivity", + }, + }, + + + "ard-mediathek": { + "button": '', + "friendlyName": "ARD Mediathek", + "className": "ardMediathekButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "de.swr.ard.avp.mobile.android.amazon", + "androidName": "de.swr.ard.avp.mobile.android.amazon", + "adbLaunchCommand": "adb shell am start -n de.swr.ard.avp.mobile.android.amazon/de.swr.ard.avp.mobile.android.amazon.TvActivity", + }, + "apple-tv": { + "appName": "ARD Mediathek", + }, + "chromecast": { + "appName": "de.swr.avp.ard.tv", + "androidName": "de.swr.avp.ard.tv", + "adbLaunchCommand": "adb shell am start -n de.swr.avp.ard.tv/de.swr.avp.ard.tv.TvActivity", + }, + "homatics": { + "appName": "de.swr.avp.ard.tv", + "androidName": "de.swr.avp.ard.tv", + "adbLaunchCommand": "adb shell am start -n de.swr.avp.ard.tv/de.swr.avp.ard.tv.TvActivity", + }, + "nvidia-shield": { + "appName": "de.swr.avp.ard.tv", + "androidName": "de.swr.avp.ard.tv", + "adbLaunchCommand": "adb shell am start -n de.swr.avp.ard.tv/de.swr.avp.ard.tv.TvActivity", + }, + "onn": { + "appName": "de.swr.avp.ard.tv", + "androidName": "de.swr.avp.ard.tv", + "adbLaunchCommand": "adb shell am start -n de.swr.avp.ard.tv/de.swr.avp.ard.tv.TvActivity", + }, + "xiaomi": { + "appName": "de.swr.avp.ard.tv", + "androidName": "de.swr.avp.ard.tv", + "adbLaunchCommand": "adb shell am start -n de.swr.avp.ard.tv/de.swr.avp.ard.tv.TvActivity", + }, + }, + + + "aptv": { + "button": '', + "friendlyName": "APTV", + "className": "aptvButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "APTV", + }, + }, + + + "arte": { + "button": '', + "friendlyName": 'ARTE', + "className": "arteButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "de.swr.ard.avp.mobile.android.amazon", + "androidName": "de.swr.ard.avp.mobile.android.amazon", + "adbLaunchCommand": "adb shell am start -n de.swr.ard.avp.mobile.android.amazon/de.swr.ard.avp.mobile.android.amazon.TvActivity", + }, + "apple-tv": { + "appName": "ARTE", + }, + "chromecast": { + "appName": "tv.arte.plus7", + "androidName": "tv.arte.plus7", + "adbLaunchCommand": "adb shell am start -n tv.arte.plus7/.leanback.MainActivity", + }, + "homatics": { + "appName": "tv.arte.plus7", + "androidName": "tv.arte.plus7", + "adbLaunchCommand": "adb shell am start -n tv.arte.plus7/.leanback.MainActivity", + }, + "nvidia-shield": { + "appName": "tv.arte.plus7", + "androidName": "tv.arte.plus7", + "adbLaunchCommand": "adb shell am start -n tv.arte.plus7/.leanback.MainActivity", + }, + "onn": { + "appName": "tv.arte.plus7", + "androidName": "tv.arte.plus7", + "adbLaunchCommand": "adb shell am start -n tv.arte.plus7/.leanback.MainActivity", + }, + "xiaomi": { + "appName": "tv.arte.plus7", + "androidName": "tv.arte.plus7", + "adbLaunchCommand": "adb shell am start -n tv.arte.plus7/.leanback.MainActivity", + }, + }, + + + "babytv": { + "button": '', + "button-round": '', + "friendlyName": 'BabyTV', + "className": "babyTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.appbabytvvideo", + "androidName": "com.appbabytvvideo", + "adbLaunchCommand": "adb shell am start -n com.appbabytvvideo/com.applicaster.ui.activities.MainActivity", + }, + "apple-tv": { + "appName": "BabyTV Video", + }, + "chromecast": { + "appName": "com.applicaster.babytv.vod", + "androidName": "com.applicaster.babytv.vod", + "adbLaunchCommand": "adb shell am start -n com.applicaster.babytv.vod/com.applicaster.ui.activities.MainActivity", + }, + "homatics": { + "appName": "com.applicaster.babytv.vod", + "androidName": "com.applicaster.babytv.vod", + "adbLaunchCommand": "adb shell am start -n com.applicaster.babytv.vod/com.applicaster.ui.activities.MainActivity", + }, + "nvidia-shield": { + "appName": "com.applicaster.babytv.vod", + "androidName": "com.applicaster.babytv.vod", + "adbLaunchCommand": "adb shell am start -n com.applicaster.babytv.vod/com.applicaster.ui.activities.MainActivity", + }, + "onn": { + "appName": "com.applicaster.babytv.vod", + "androidName": "com.applicaster.babytv.vod", + "adbLaunchCommand": "adb shell am start -n com.applicaster.babytv.vod/com.applicaster.ui.activities.MainActivity", + }, + "roku": { + "appName": 'BabyTV Video', + "app-id": 93301, + }, + "xiaomi": { + "appName": "com.applicaster.babytv.vod", + "androidName": "com.applicaster.babytv.vod", + "adbLaunchCommand": "adb shell am start -n com.applicaster.babytv.vod/com.applicaster.ui.activities.MainActivity", + }, + }, + + + "b-tv": { + "button": '', + "friendlyName": 'B tv', + "className": "bTVButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "B tv", + }, + }, + + "b-tv-fr": { + "button": '', + "button-round": '', + "friendlyName": 'B.tv (fr)', + "className": "bTVfrButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "B.tv", + }, + }, + + + "bbc-iplayer": { + "button": '', + "friendlyName": 'BBC iPlayer (UK)', + "className": "bbciplayerButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "roku"], + "amazon-fire": { + "appName": "uk.co.bbc.iplayer", + "androidName": "uk.co.bbc.iplayer", + }, + "apple-tv": { + "appName": "BBC iPlayer", + }, + "chromecast": { + "appName": "bbc.iplayer.android", + "androidName": "bbc.iplayer.android", + "adbLaunchCommand": "adb shell am start -n bbc.iplayer.android/external.androidtv.bbciplayer.deeplinking.DeepLinkActivity", + }, + "nvidia-shield": { + "appName": "com.nvidia.bbciplayer", + "androidName": "com.nvidia.bbciplayer", + "adbLaunchCommand": "adb shell am start -n com.nvidia.bbciplayer/.BaseWebViewActivity", + }, + "roku": { + "appName": 'BBC iPlayer', + "app-id": 11703, + }, + }, + + + "bell-fibe-tv": { + "button": '', + "friendlyName": 'Bell Fibe TV (Canada)', + "className": "bellFibeTVButton", + "deviceFamily": ["amazon-fire", "apple-tv"], + "amazon-fire": { + "appName": "ca.bell.tv.firetv", + "androidName": "ca.bell.tv.firetv", + "adbLaunchCommand": "adb shell am start -n ca.bell.tv.firetv/ca.bell.fiberemote.tv.MainTvActivity", + }, + "apple-tv": { + "appName": "Télé Fibe", + }, +}, + + + "bgtime-tv": { + "button": '', + "button-round": '', + "friendlyName": 'bgtime.tv', + "className": "bgtimeTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + "apple-tv": { + "appName": "bgtime.tv", + }, + "chromecast": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + "homatics": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + "nvidia-shield": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + "onn": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + "roku": { + "appName": 'bgtime.tv', + "app-id": 651426, + }, + "xiaomi": { + "appName": "bg.mytv.android", + "androidName": "bg.mytv.android", + "adbLaunchCommand": "adb shell am start -n bg.mytv.android/.activities.ActivityLogin", + }, + }, + + + "big-ten-network-plus": { + "button": '', + "friendlyName": 'Big Ten Network Plus', + "className": "bigTenNetworkPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "com.btn2go", + "androidName": "com.btn2go", + "adbLaunchCommand": "adb shell am start -n com.btn2go/com.foxxum.webapp.MainActivity", + }, + "apple-tv": { + "appName": "B1G+", + }, + "roku": { + "appName": 'B1G+', + "app-id": 66258, + }, + }, + + + "binge-au": { + "button": '', + "friendlyName": 'Binge (AU)', + "className": "bingeAuButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.streamotion.ctv.binge", + "androidName": "au.com.streamotion.ctv.binge", + "adbLaunchCommand": "adb shell am start -n au.com.streamotion.ctv.binge/au.com.streamotion.ctv.MainActivity", + }, + "apple-tv": { + "appName": "BINGE", + }, + "chromecast": { + "appName": "au.com.streamotion.ares.tv", + "androidName": "au.com.streamotion.ares.tv", + "adbLaunchCommand": "adb shell am start -n au.com.streamotion.ares.tv/au.com.streamotion.tv.SplashActivity", + }, + "nvidia-shield": { + "appName": "au.com.streamotion.ares.tv", + "androidName": "au.com.streamotion.ares.tv", + "adbLaunchCommand": "adb shell am start -n au.com.streamotion.ares.tv/au.com.streamotion.tv.SplashActivity", + }, + "xiaomi": { + "appName": "au.com.streamotion.ares.tv", + "androidName": "au.com.streamotion.ares.tv", + "adbLaunchCommand": "adb shell am start -n au.com.streamotion.ares.tv/au.com.streamotion.tv.SplashActivity", + }, + }, + + + "binge-au-alt": { + "button": '', + "friendlyName": 'Binge (AU) alt', + "className": "bingeAuButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "au.com.streamotion.ares.tv", + "androidName": "au.com.streamotion.ares.tv", + "adbLaunchCommand": "adb shell am start -n au.com.streamotion.ares.tv/au.com.streamotion.tv.SplashActivity", + }, + }, + + + "blaze-tv": { + "button": '', + "friendlyName": "BlazeTV", + "className": "blazeTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "com.amazon.rialto.cordova.webapp.webapp5b95d2945f354fc2aa8753ce5b8ffb34", + "androidName": "com.amazon.rialto.cordova.webapp.webapp5b95d2945f354fc2aa8753ce5b8ffb34", + "adbLaunchCommand": "adb shell am start -n com.amazon.rialto.cordova.webapp.webapp5b95d2945f354fc2aa8753ce5b8ffb34/.MainActivity", + }, + "apple-tv": { + "appName": "BlazeTV", + }, + "roku": { + "appName": "BlazeTV", + "app-id": 82559, + }, + }, + + + "bookmarker-1": { + "button": '', + "friendlyName": 'Bookmarker 1', + "className": "bookmarker1Button", + "appName": "com.esaba.bookmarker1", + "androidName": "com.esaba.bookmarker1", + "adbLaunchCommand": "adb shell am start -n com.esaba.bookmarker1/.MainActivity", + "deviceFamily": ["amazon-fire"], + }, + + + "britbox": { + "button": '', + "button-round": '', + "friendlyName": 'BritBox', + "className": "britboxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.britbox.us.firetv", + "androidName": "com.britbox.us.firetv", + "adbLaunchCommand": "adb shell am start -n com.britbox.us.firetv/axis.androidtv.sdk.app.MainActivity", + }, + "apple-tv": { + "appName": "Britbox", + }, + "chromecast": { + "appName": "com.britbox.tv", + "androidName": "com.britbox.tv", + "adbLaunchCommand": "adb shell am start -n com.britbox.tv/axis.androidtv.sdk.app.MainActivity", + }, + "homatics": { + "appName": "com.britbox.tv", + "androidName": "com.britbox.tv", + "adbLaunchCommand": "adb shell am start -n com.britbox.tv/axis.androidtv.sdk.app.MainActivity", + }, + "nvidia-shield": { + "appName": "com.britbox.tv", + "androidName": "com.britbox.tv", + "adbLaunchCommand": "adb shell am start -n com.britbox.tv/axis.androidtv.sdk.app.MainActivity", + }, + "onn": { + "appName": "com.britbox.tv", + "androidName": "com.britbox.tv", + "adbLaunchCommand": "adb shell am start -n com.britbox.tv/axis.androidtv.sdk.app.MainActivity", + }, + "roku": { + "appName": 'BritBox', + "app-id": 143088, + }, + "xiaomi": { + "appName": "com.britbox.tv", + "androidName": "com.britbox.tv", + "adbLaunchCommand": "adb shell am start -n com.britbox.tv/axis.androidtv.sdk.app.MainActivity", + }, + }, + + + "btv-app": { + "button": '', + "friendlyName": 'btvApp', + "className": "btvAppButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + "chromecast": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + "homatics": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + "nvidia-shield": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + "onn": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + "xiaomi": { + "appName": "com.mm.droid.livetv.btvapp", + "androidName": "com.mm.droid.livetv.btvapp", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.btvapp/com.mm.droid.livetv.load.LiveLoadActivity", + }, + }, + + + "byutv": { + "button": '', + "friendlyName": "BYUtv", + "className": "byuTvButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "BYUtv", + }, + }, + + + "caiway-tv": { + "button": '', + "friendlyName": "Caiway TV", + "className": "caiwayTVButton", + "deviceFamily": ["apple-tv", "nvidia-shield"], + "apple-tv": { + "appName": "Caiway TV", + }, + "nvidia-shield": { + "appName": "nl.nep.caiway.tv", + "androidName": "nl.nep.caiway.tv", + "adbLaunchCommand": "adb shell am start -n nl.nep.caiway.tv/com.stoneroos.deltatv.main.MainActivity", + }, + }, + + + "canal-plus": { + "button": '', + "button-round": '', + "friendlyName": 'CANAL+', + "className": "canalPlusButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + "chromecast": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + "homatics": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + "nvidia-shield": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + "onn": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + "xiaomi": { + "appName": "com.canalplus.canalplus", + "androidName": "com.canalplus.canalplus", + "adbLaunchCommand": "adb shell am start -n com.canalplus.canalplus/tv.solocoo.htmlapp.FullscreenActivity", + }, + }, + + + "cartoon-network": { + "button": '', + "friendlyName": "Cartoon Network", + "className": "cartoonNetworkButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + "apple-tv": { + "appName": "Cartoon Network", + }, + "chromecast": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + "homatics": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + "nvidia-shield": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + "onn": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + "roku": { + "appName": "Cartoon Network", + "app-id": 164003, + }, + "xiaomi": { + "appName": "com.turner.cnvideoapp", + "androidName": "com.turner.cnvideoapp", + "adbLaunchCommand": "adb shell am start -n com.turner.cnvideoapp/.tv.startup.StartupActivity", + }, + }, + + + "cbs-sports": { + "button": '', + "button-round": '', + "friendlyName": 'CBS Sports', + "className": "cbsSportsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.handmark.sportcaster", + "androidName": "com.handmark.sportcaster", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportcaster/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + "apple-tv": { + "appName": "CBS Sports", + }, + "chromecast": { + "appName": "com.handmark.sportscaster.androidtv", + "androidName": "com.handmark.sportscaster.androidtv", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportscaster.androidtv/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + "homatics": { + "appName": "com.handmark.sportscaster.androidtv", + "androidName": "com.handmark.sportscaster.androidtv", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportscaster.androidtv/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + "nvidia-shield": { + "appName": "com.handmark.sportscaster.androidtv", + "androidName": "com.handmark.sportscaster.androidtv", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportscaster.androidtv/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + "onn": { + "appName": "com.handmark.sportscaster.androidtv", + "androidName": "com.handmark.sportscaster.androidtv", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportscaster.androidtv/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + "roku": { + "appName": 'CBS Sports Stream & Watch Live', + "app-id": 17112, + }, + "xiaomi": { + "appName": "com.handmark.sportscaster.androidtv", + "androidName": "com.handmark.sportscaster.androidtv", + "adbLaunchCommand": "adb shell am start -n com.handmark.sportscaster.androidtv/com.handmark.sportcaster.ui.homepage.HomePageActivity", + }, + }, + + + "cctv-viewer": { + "button": '', + "friendlyName": 'CCTV Viewer', + "className": "cctvViewerButton", + "appName": "CCTV Viewer", + "androidName": "com.", + "deviceFamily": ["apple-tv"],}, + + + "cgates-tv": { + "button": '', + "button-round": '', + "friendlyName": 'Cgates TV', + "className": "cgatesTvButton", + "appName": "lt.cgates.app", + "androidName": "lt.cgates.app", + "adbLaunchCommand": "adb shell am start -n lt.cgates.app/com.smartivus.tvbox.MainActivityTv", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"],}, + + + "channel-four": { + "button": '', + "friendlyName": 'Channel 4', + "className": "channelFourButton", + "deviceFamily": ["apple-tv", "roku"], + "apple-tv": { + "appName": "Channel 4", + }, + "roku": { + "appName": 'Channel 4', + "app-id": 34785, + }, + }, + + + "channels-dvr": { + "button": '', + "button-round": '', + "friendlyName": 'Channels: Whole Home DVR', + "className": "channelsDVRButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + "apple-tv": { + "appName": "Channels", + }, + "chromecast": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + "homatics": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + "nvidia-shield": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + "onn": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + "xiaomi": { + "appName": "com.getchannels.dvr.app", + "androidName": "com.getchannels.dvr.app", + "adbLaunchCommand": "adb shell am start -n com.getchannels.dvr.app/com.getchannels.android.MainActivity", + }, + }, + + + "cheers-danmu-player": { + "button": '', + "friendlyName": 'Cheers - Danmu Player', + "className": "cheersDanmuPlayerButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Cheers", + }, + }, + + + "cinema-hd-v2": { + "button": '', + "friendlyName": 'Cinema HD V2', + "className": "cinemaHDV2Button", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + "chromecast": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + "homatics": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + "onn": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + "xiaomi": { + "appName": "com.yoku.marumovie", + "androidName": "com.yoku.marumovie", + "adbLaunchCommand": "adb shell am start -n com.yoku.marumovie/com.movie.ui.activity.MainActivity", + }, + }, + + + "cinema-hd-v3": { + "button": '', + "friendlyName": 'Cinema HD V3', + "className": "cinemaHDV3Button", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + "chromecast": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + "homatics": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + "onn": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + "xiaomi": { + "appName": "com.yoku.cinemahd.v3", + "androidName": "com.yoku.cinemahd.v3", + "adbLaunchCommand": "adb shell am start -n com.yoku.cinemahd.v3/com.movie.ui.activity.MainActivity", + }, + }, + + + "claro-tv-plus": { + "button": '', + "button-round": '', + "friendlyName": 'Claro tv+', + "className": "claroTvPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "apple-tv": { + "appName": "Claro TV+", + }, + "chromecast": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "homatics": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "onn": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "xiaomi": { + "appName": "br.com.claro.now.smarttvclient", + "androidName": "br.com.claro.now.smarttvclient", + "adbLaunchCommand": "adb shell am start -n br.com.claro.now.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + }, + + + "clipious": { + "button": '', + "friendlyName": 'Clipious', + "className": "clipiousButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + "chromecast": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + "homatics": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + "onn": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + "xiaomi": { + "appName": "com.github.lamarios.clipious", + "androidName": "com.github.lamarios.clipious", + "adbLaunchCommand": "adb shell am start -n com.github.lamarios.clipious/.MainActivity", + }, + }, + + + + "cloudstream": { + "button": '', + "friendlyName": 'Cloudstream', + "className": "cloudstreamButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + "chromecast": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + "homatics": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + "onn": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + "xiaomi": { + "appName": "com.lagradost.cloudstream3", + "androidName": "com.lagradost.cloudstream3", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3/.MainActivity", + }, + }, + + + "cloudstream-prerelease": { + "button": '', + "friendlyName": 'Cloudstream prerelease', + "className": "cloudstreamPrereleaseButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + "chromecast": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + "homatics": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + "nvidia-shield": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + "onn": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + "xiaomi": { + "appName": "com.lagradost.cloudstream3.prerelease", + "androidName": "com.lagradost.cloudstream3.prerelease", + "adbLaunchCommand": "adb shell am start -n com.lagradost.cloudstream3.prerelease/com.lagradost.cloudstream3.MainActivity", + }, + }, + + + "cnn": { + "button": '', + "friendlyName": 'CNN', + "className": "cnnButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.cnn.mobile.fire.tv", + "androidName": "com.cnn.mobile.fire.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.fire.tv/com.cnn.ctv.MainActivity", + }, + "apple-tv": { + "appName": "CNN", + }, + "chromecast": { + "appName": "com.cnn.mobile.android.tv", + "androidName": "com.cnn.mobile.android.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.android.tv/com.cnn.ctv.MainActivity", + }, + "homatics": { + "appName": "com.cnn.mobile.android.tv", + "androidName": "com.cnn.mobile.android.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.android.tv/com.cnn.ctv.MainActivity", + }, + "nvidia-shield": { + "appName": "com.cnn.mobile.android.tv", + "androidName": "com.cnn.mobile.android.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.android.tv/com.cnn.ctv.MainActivity", + }, + "onn": { + "appName": "com.cnn.mobile.android.tv", + "androidName": "com.cnn.mobile.android.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.android.tv/com.cnn.ctv.MainActivity", + }, + "roku": { + "appName": 'CNN', + "app-id": 65978, + }, + "xiaomi": { + "appName": "com.cnn.mobile.android.tv", + "androidName": "com.cnn.mobile.android.tv", + "adbLaunchCommand": "adb shell am start -n com.cnn.mobile.android.tv/com.cnn.ctv.MainActivity", + }, + }, + + + "corridor-digital": { + "button": '', + "button-round": '', + "friendlyName": "Corridor Digital", + "className": "corridorDigitalButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + "apple-tv": { + "appName": "Corridor", + }, + "chromecast": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + "homatics": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + "nvidia-shield": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + "onn": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + "roku": { + "appName": 'Corridor Digital', + "app-id": 151481, + }, + "xiaomi": { + "appName": "com.corridordigital.watchcorridortv", + "androidName": "com.corridordigital.watchcorridortv", + "adbLaunchCommand": "adb shell am start -n com.corridordigital.watchcorridortv/.feature.main.MainActivity", + }, + }, + + + "cosmote-tv": { + "button": '', + "friendlyName": 'COSMOTE TV', + "className": "cosmoteTVButton", + "appName": "gr.cosmote.cosmotetv.androidtv", + "androidName": "gr.cosmote.cosmotetv.androidtv", + "adbLaunchCommand": "adb shell am start -n gr.cosmote.cosmotetv.androidtv/dt.ote.poc.presentation.view.tv.TvActivity", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"],}, + + + "coupang-play": { + "button": '', + "friendlyName": "coupang play", + "className": "coupangPlayButton", + "deviceFamily": ["chromecast"], + "chromecast": { + "appName": "com.coupang.mobile.play", + "androidName": "com.coupang.mobile.play", + "adbLaunchCommand": "adb shell am start -n com.coupang.mobile.play/com.coupang.mobile.video.features.splash.SplashActivity", + }, + }, + + + "crave-tv": { + "button": '', + "friendlyName": 'Crave TV', + "className": "craveTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + "apple-tv": { + "appName": "Crave", + }, + "chromecast": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + "homatics": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + "nvidia-shield": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + "onn": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + "xiaomi": { + "appName": "Crave", + "androidName": "ca.bellmedia.cravetv", + "adbLaunchCommand": "adb shell am start -n ca.bellmedia.cravetv/axis.androidtv.sdk.app.MainActivity", + }, + }, + + + "crunchyroll": { + "button": '', + "button-round": '', + "friendlyName": 'Crunchyroll', + "className": "crunchyrollButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + "apple-tv": { + "appName": "Crunchyroll", + }, + "chromecast": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + "homatics": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + "nvidia-shield": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + "onn": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + "roku": { + "appName": 'Crunchyroll', + "app-id": 2595, + }, + "xiaomi": { + "appName": "com.crunchyroll.crunchyroid", + "androidName": "com.crunchyroll.crunchyroid", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.crunchyroll.crunchyroid/.main.ui.MainActivity", + }, + }, + + + "curiosity-stream": { + "button": '', + "friendlyName": "Curiosity Stream", + "className": "curiosityStreamButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.curiosity.curiositystream", + "androidName": "com.curiosity.curiositystream", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream/.MainActivity", + }, + "apple-tv": { + "appName": "Curiosity Stream", + }, + "chromecast": { + "appName": "com.curiosity.curiositystream.androidtv", + "androidName": "com.curiosity.curiositystream.androidtv", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream.androidtv/com.curiosity.curiositystream.MainActivity", + }, + "homatics": { + "appName": "com.curiosity.curiositystream.androidtv", + "androidName": "com.curiosity.curiositystream.androidtv", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream.androidtv/com.curiosity.curiositystream.MainActivity", + }, + "nvidia-shield": { + "appName": "com.curiosity.curiositystream.androidtv", + "androidName": "com.curiosity.curiositystream.androidtv", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream.androidtv/com.curiosity.curiositystream.MainActivity", + }, + "onn": { + "appName": "com.curiosity.curiositystream.androidtv", + "androidName": "com.curiosity.curiositystream.androidtv", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream.androidtv/com.curiosity.curiositystream.MainActivity", + }, + "roku": { + "appName": 'Curiosity Stream', + "app-id": 61657, + }, + "xiaomi": { + "appName": "com.curiosity.curiositystream.androidtv", + "androidName": "com.curiosity.curiositystream.androidtv", + "adbLaunchCommand": "adb shell am start -n com.curiosity.curiositystream.androidtv/com.curiosity.curiositystream.MainActivity", + }, + }, + + "cyberghost": { + "button": '', + "friendlyName": 'CyberGhost VPN', + "className": "cyberghostButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.cyberghostvpn.amazon", + "androidName": "com.cyberghostvpn.amazon", + "adbLaunchCommand": "adb shell am start -n com.cyberghostvpn.amazon/de.mobileconcepts.cyberghost.view.app.AppActivity", + }, + "chromecast": { + "appName": "de.mobileconcepts.cyberghost", + "androidName": "de.mobileconcepts.cyberghost", + "adbLaunchCommand": "adb shell am start -n de.mobileconcepts.cyberghost/.view.app.AppActivity", + }, + "homatics": { + "appName": "de.mobileconcepts.cyberghost", + "androidName": "de.mobileconcepts.cyberghost", + "adbLaunchCommand": "adb shell am start -n de.mobileconcepts.cyberghost/.view.app.AppActivity", + }, + "nvidia-shield": { + "appName": "de.mobileconcepts.cyberghost", + "androidName": "de.mobileconcepts.cyberghost", + "adbLaunchCommand": "adb shell am start -n de.mobileconcepts.cyberghost/.view.app.AppActivity", + }, + "onn": { + "appName": "de.mobileconcepts.cyberghost", + "androidName": "de.mobileconcepts.cyberghost", + "adbLaunchCommand": "adb shell am start -n de.mobileconcepts.cyberghost/.view.app.AppActivity", + }, + "xiaomi": { + "appName": "de.mobileconcepts.cyberghost", + "androidName": "de.mobileconcepts.cyberghost", + "adbLaunchCommand": "adb shell am start -n de.mobileconcepts.cyberghost/.view.app.AppActivity", + }, + }, + + + "daily-wire": { + "button": '', + "friendlyName": "Daily Wire", + "className": "dailyWireButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.dailywire.amazon", + "androidName": "com.dailywire.amazon", + "adbLaunchCommand": "adb shell am start -n com.dailywire.amazon/com.firetv.firetvwrapper.MainActivity", + }, + "apple-tv": { + "appName": "Daily Wire", + }, + "chromecast": { + "appName": "com.dailywire.androidtv", + "androidName": "com.dailywire.androidtv", + "adbLaunchCommand": "adb shell am start -n com.dailywire.androidtv/com.firetv.firetvwrapper.MainActivity", + }, + "homatics": { + "appName": "com.dailywire.androidtv", + "androidName": "com.dailywire.androidtv", + "adbLaunchCommand": "adb shell am start -n com.dailywire.androidtv/com.firetv.firetvwrapper.MainActivity", + }, + "nvidia-shield": { + "appName": "com.dailywire.androidtv", + "androidName": "com.dailywire.androidtv", + "adbLaunchCommand": "adb shell am start -n com.dailywire.androidtv/com.firetv.firetvwrapper.MainActivity", + }, + "onn": { + "appName": "com.dailywire.androidtv", + "androidName": "com.dailywire.androidtv", + "adbLaunchCommand": "adb shell am start -n com.dailywire.androidtv/com.firetv.firetvwrapper.MainActivity", + }, + "roku": { + "appName": 'DailyWire+', + "app-id": 600734, + }, + "xiaomi": { + "appName": "com.dailywire.androidtv", + "androidName": "com.dailywire.androidtv", + "adbLaunchCommand": "adb shell am start -n com.dailywire.androidtv/com.firetv.firetvwrapper.MainActivity", + }, + }, + + + "dakboard": { + "button": '', + "friendlyName": "DAKboard", + "className": "dakboardButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.amazon.rialto.cordova.webapp.webapp9dec859387ba4ceeba2909ab173a82e5", + "androidName": "com.amazon.rialto.cordova.webapp.webapp9dec859387ba4ceeba2909ab173a82e5", + "adbLaunchCommand": "adb shell am start -n com.amazon.rialto.cordova.webapp.webapp9dec859387ba4ceeba2909ab173a82e5/.MainActivity", + }, + }, + + + "dazn": { + "button": '', + "friendlyName": "DAZN", + "className": "daznButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + "apple-tv": { + "appName": "DAZN", + }, + "chromecast": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + "homatics": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + "nvidia-shield": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + "onn": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + "roku": { + "appName": 'DAZN Live Sports Streaming', + "app-id": 158987, + }, + "xiaomi": { + "appName": "com.dazn", + "androidName": "com.dazn", + "adbLaunchCommand": "adb shell am start -n com.dazn/com.dazn.MainActivity", + }, + }, + + + "deezer": { + "button": '', + "button-round": '', + "friendlyName": "Deezer", + "className": "deezerButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Deezer", + }, + "chromecast": { + "appName": "Deezer", + "androidName": "deezer.android.tv", + "adbLaunchCommand": "adb shell am start -n deezer.android.tv/com.deezer.android.ui.activity.LauncherActivity", + }, + "homatics": { + "appName": "Deezer", + "androidName": "deezer.android.tv", + "adbLaunchCommand": "adb shell am start -n deezer.android.tv/com.deezer.android.ui.activity.LauncherActivity", + }, + "nvidia-shield": { + "appName": "Deezer", + "androidName": "deezer.android.tv", + "adbLaunchCommand": "adb shell am start -n deezer.android.tv/com.deezer.android.ui.activity.LauncherActivity", + }, + "onn": { + "appName": "Deezer", + "androidName": "deezer.android.tv", + "adbLaunchCommand": "adb shell am start -n deezer.android.tv/com.deezer.android.ui.activity.LauncherActivity", + }, + "xiaomi": { + "appName": "Deezer", + "androidName": "deezer.android.tv", + "adbLaunchCommand": "adb shell am start -n deezer.android.tv/com.deezer.android.ui.activity.LauncherActivity", + }, + }, + + + "defsquid": { + "button": '', + "friendlyName": "DefSquid", + "className": "defSquidButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.cor.defsquid", + "androidName": "com.cor.defsquid", + "adbLaunchCommand": "adb shell am start -n com.cor.defsquid/.MainActivity", + }, + }, + + + "dgo": { + "button": '', + "friendlyName": "DGO", + "className": "dgoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + "apple-tv": { + "appName": "DGO", + }, + "chromecast": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + "homatics": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + "nvidia-shield": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + "onn": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + "xiaomi": { + "appName": 'com.directv.dtvlatam', + "androidName": 'com.directv.dtvlatam', + "adbLaunchCommand": "adb shell am start -n com.directv.dtvlatam/com.directvgo.feature.tv.splash.SplashActivity" + }, + }, + + + "directv-stream": { + "button": '', + "friendlyName": "DIRECTV stream", + "className": "direcTVStreamButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + "apple-tv": { + "appName": "DIRECTV", + }, + "chromecast": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + "homatics": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + "nvidia-shield": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + "onn": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + "roku": { + "appName": 'DIRECTV', + "app-id": 140474, + }, + "xiaomi": { + "appName": "com.att.tv", + "androidName": "com.att.tv", + "adbLaunchCommand": "adb shell am start -n com.att.tv/com.clientapp.MainActivity", + }, + }, + + + "dirtvision": { + "button": '', + "button-round": '', + "friendlyName": "DIRTVision", + "className": "dirTVisionButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + "apple-tv": { + "appName": "Monumental+", + }, + "chromecast": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + "homatics": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + "nvidia-shield": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + "onn": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + "roku": { + "appName": "DIRTVision", + "app-id": 612494, + }, + "xiaomi": { + "appName": "com.appdirtvision", + "androidName": "com.appdirtvision", + "adbLaunchCommand": "adb shell am start -n com.appdirtvision/com.viewlift.presentation.HomeActivity", + }, + }, + + + "discovery-plus": { + "button": '', + "friendlyName": "Discovery+", + "className": "discoveryPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.discovery.discoveryplus.firetv", + "androidName": "com.discovery.discoveryplus.firetv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.firetv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + "apple-tv": { + "appName": "discovery+", + }, + "chromecast": { + "appName": "com.discovery.discoveryplus.androidtv", + "androidName": "com.discovery.discoveryplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.androidtv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + "homatics": { + "appName": "com.discovery.discoveryplus.androidtv", + "androidName": "com.discovery.discoveryplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.androidtv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + "nvidia-shield": { + "appName": "com.discovery.discoveryplus.androidtv", + "androidName": "com.discovery.discoveryplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.androidtv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + "onn": { + "appName": "com.discovery.discoveryplus.androidtv", + "androidName": "com.discovery.discoveryplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.androidtv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + "roku": { + "appName": "discovery+ | Stream TV Shows", + }, + "xiaomi": { + "appName": "com.discovery.discoveryplus.androidtv", + "androidName": "com.discovery.discoveryplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoveryplus.androidtv/com.discovery.plus.presentation.activities.DeepLinkActivity", + }, + }, + + + "discovery-plus-alt": { + "button": '', + "friendlyName": "Discovery+ (alt)", + "className": "discoveryPlusButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.discovery.discoplus", + "androidName": "com.discovery.discoplus", + "adbLaunchCommand": "adb shell am start -n com.discovery.discoplus/com.discovery.luna.tv.presentation.LunaMainActivity", + }, + }, + + + "dish-anywhere": { + "button": '', + "friendlyName": "Dish Anywhere", + "className": "dishAnywhereButton", + "appName": "com.sm.SlingGuide.Dish", + "androidName": "com.sm.SlingGuide.Dish", + "adbLaunchCommand": "adb shell am start -n com.sm.SlingGuide.Dish/com.sm.SlingGuide.Dish.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "disney-plus": { + "button": '', + "friendlyName": "Disney +", + "className": "disneyPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Disney+", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + "apple-tv": { + "appName": "Disney+", + }, + "chromecast": { + "appName": "Disney+", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + "homatics": { + "appName": "Disney+", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + "nvidia-shield": { + "appName": "Disney+", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + "onn": { + "appName": "com.disney.disneyplus", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + "roku": { + "appName": "Disney Plus", + }, + "xiaomi": { + "appName": "Disney+", + "androidName": "com.disney.disneyplus", + "adbLaunchCommand": "adb shell am start -n com.disney.disneyplus/com.bamtechmedia.dominguez.main.MainActivity", + }, + }, + + + "disney-plus-hotstar": { + "button": '', + "friendlyName": "Disney + hotstar", + "className": "disneyPlusHotstarButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "Disney+ Hotstar", + "androidName": "in.startv.hotstar", + "adbLaunchCommand": "adb shell am start -n in.startv.hotstar/com.hotstar.MainActivity", + }, + }, + + + "disney-plus-middle-east": { + "button": '', + "friendlyName": "Disney + Middle East", + "className": "disneyPlusButton", + "deviceFamily": ["xiaomi"], + "xiaomi": { + "appName": "Disney+", + "androidName": "com.disneyplus.mea", + "adbLaunchCommand": "adb shell am start -n com.disneyplus.mea/com.hotstar.MainActivity", + }, + }, + + + "dog-tv": { + "button": '', + "button-round": '', + "friendlyName": "DogTV", + "className": "dogTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.ptvmedia.dogtv", + "androidName": "com.ptvmedia.dogtv", + "adbLaunchCommand": "adb shell am start -n com.ptvmedia.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + "apple-tv": { + "appName": "DOGTV", + }, + "chromecast": { + "appName": "com.latto.tv.dogtv", + "androidName": "com.latto.tv.dogtv", + "adbLaunchCommand": "adb shell am start -n com.latto.tv.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + "homatics": { + "appName": "com.latto.tv.dogtv", + "androidName": "com.latto.tv.dogtv", + "adbLaunchCommand": "adb shell am start -n com.latto.tv.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + "nvidia-shield": { + "appName": "com.latto.tv.dogtv", + "androidName": "com.latto.tv.dogtv", + "adbLaunchCommand": "adb shell am start -n com.latto.tv.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + "onn": { + "appName": "com.latto.tv.dogtv", + "androidName": "com.latto.tv.dogtv", + "adbLaunchCommand": "adb shell am start -n com.latto.tv.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + "roku": { + "appName": 'DOGTV', + "app-id": 18127, + }, + "xiaomi": { + "appName": "com.latto.tv.dogtv", + "androidName": "com.latto.tv.dogtv", + "adbLaunchCommand": "adb shell am start -n com.latto.tv.dogtv/tv.accedo.one.app.bootstrap.BootstrapActivity", + }, + }, + + + "downloader": { + "button": '', + "button-round": '', + "friendlyName": "Downloader", + "className": "downloaderButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + "chromecast": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + "homatics": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + "nvidia-shield": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + "onn": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + "xiaomi": { + "appName": "com.esaba.downloader", + "androidName": "com.esaba.downloader", + "adbLaunchCommand": "adb shell am start -n com.esaba.downloader/.ui.main.MainActivity", + }, + }, + + + "dropout": { + "button": '', + "button-round": '', + "friendlyName": "DROPOUT", + "className": "dropoutButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + "apple-tv": { + "appName": "DROPOUT", + }, + "chromecast": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + "homatics": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + "nvidia-shield": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + "onn": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + "roku": { + "appName": 'Dropout', + "app-id": 253232, + }, + "xiaomi": { + "appName": "com.collegehumor.chdropout", + "androidName": "com.collegehumor.chdropout", + "adbLaunchCommand": "adb shell am start -n com.collegehumor.chdropout/tv.vhx.tv.home.TvHomeActivity", + }, + }, + + + "drtv": { + "button": '', + "button-round": '', + "friendlyName": "DRTV", + "className": "drtvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + "apple-tv": { + "appName": "DRTV", + }, + "chromecast": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + "homatics": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + "nvidia-shield": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + "onn": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + "roku": { + "appName": 'DRTV, Danish National TV', + "app-id": 227712, + }, + "xiaomi": { + "appName": "dk.dr.tvplayer", + "androidName": "dk.dr.tvplayer", + "adbLaunchCommand": "adb shell am start -n dk.dr.tvplayer/axis.androidtv.sdk.app.MainActivity", + }, + }, + + + "ds-video": { + "button": 'DS Video', + "friendlyName": "DS Video", + "className": "dsVideoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.synology.dsvideo", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + "apple-tv": { + "appName": "DS video", + }, + "chromecast": { + "appName": "com.synology.dsvideo", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + "homatics": { + "appName": "DS video", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + "nvidia-shield": { + "appName": "com.synology.dsvideo", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + "onn": { + "appName": "DS video", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + "xiaomi": { + "appName": "com.synology.dsvideo", + "androidName": "com.synology.dsvideo", + "adbLaunchCommand": "adb shell am start -n com.synology.dsvideo/.ui.WelcomeActivity", + }, + }, + + + "elisa-elamus": { + "button": '', + "button-round": '', + "friendlyName": "Elisa Elamus", + "className": "elisaElamusButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "xiaomi"], + "chromecast": { + "appName": "ee.elisa.smarttvclient", + "androidName": "ee.elisa.smarttvclient", + "adbLaunchCommand": "adb shell am start -n ee.elisa.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "homatics": { + "appName": "ee.elisa.smarttvclient", + "androidName": "ee.elisa.smarttvclient", + "adbLaunchCommand": "adb shell am start -n ee.elisa.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "ee.elisa.smarttvclient", + "androidName": "ee.elisa.smarttvclient", + "adbLaunchCommand": "adb shell am start -n ee.elisa.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + "xiaomi": { + "appName": "ee.elisa.smarttvclient", + "androidName": "ee.elisa.smarttvclient", + "adbLaunchCommand": "adb shell am start -n ee.elisa.smarttvclient/tv.threess.threeready.ui.generic.activity.MainActivity", + }, + }, + + + "emby": { + "button": '', + "friendlyName": "Emby", + "className": "embyButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + "apple-tv": { + "appName": "Emby", + }, + "chromecast": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + "homatics": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + "nvidia-shield": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + "onn": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + "roku": { + "appName": 'Emby', + "app-id": 44191, + }, + "xiaomi": { + "appName": "tv.emby.embyatv", + "androidName": "tv.emby.embyatv", + "adbLaunchCommand": "adb shell am start -a android.intent.category.LEANBACK_LAUNCHER -d -n tv.emby.embyatv/.startup.StartupActivity", + }, + }, + + + "eon-tv": { + "button": '', + "friendlyName": "EON TV", + "className": "eonTVButton", + "appName": "com.ug.eon.android.tv", + "androidName": "com.ug.eon.android.tv", + "adbLaunchCommand": "adb shell am start -n com.ug.eon.android.tv/com.ug.eon.android.tv.TvActivity", + "deviceFamily": [ "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "err-jupiter": { + "button": '', + "button-round": '', + "friendlyName": "ERR Jupiter", + "className": "errJupiterButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Jupiter", + }, + "chromecast": { + "appName": "ee.err.jupiter", + "androidName": "ee.err.jupiter", + "adbLaunchCommand": "adb shell am start -n ee.err.jupiter/com.example.jupiter.MainActivity", + }, + "homatics": { + "appName": "ee.err.jupiter", + "androidName": "ee.err.jupiter", + "adbLaunchCommand": "adb shell am start -n ee.err.jupiter/com.example.jupiter.MainActivity", + }, + "nvidia-shield": { + "appName": "ee.err.jupiter", + "androidName": "ee.err.jupiter", + "adbLaunchCommand": "adb shell am start -n ee.err.jupiter/com.example.jupiter.MainActivity", + }, + "onn": { + "appName": "ee.err.jupiter", + "androidName": "ee.err.jupiter", + "adbLaunchCommand": "adb shell am start -n ee.err.jupiter/com.example.jupiter.MainActivity", + }, + "xiaomi": { + "appName": "ee.err.jupiter", + "androidName": "ee.err.jupiter", + "adbLaunchCommand": "adb shell am start -n ee.err.jupiter/com.example.jupiter.MainActivity", + }, + }, + + + "ertflix": { + "button": '', + "friendlyName": "ERTFLIX", + "className": "ertflixButton", + "deviceFamily": [ "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "chromecast": { + "appName": "t.yi.erthybrid", + "androidName": "t.yi.erthybrid", + "adbLaunchCommand": "adb shell am start -n t.yi.erthybrid/com.arxnet.soeasytv.MainActivity", + }, + "homatics": { + "appName": "t.yi.erthybrid", + "androidName": "t.yi.erthybrid", + "adbLaunchCommand": "adb shell am start -n t.yi.erthybrid/com.arxnet.soeasytv.MainActivity", + }, + "nvidia-shield": { + "appName": "t.yi.erthybrid", + "androidName": "t.yi.erthybrid", + "adbLaunchCommand": "adb shell am start -n t.yi.erthybrid/com.arxnet.soeasytv.MainActivity", + }, + "onn": { + "appName": "t.yi.erthybrid", + "androidName": "t.yi.erthybrid", + "adbLaunchCommand": "adb shell am start -n t.yi.erthybrid/com.arxnet.soeasytv.MainActivity", + }, + "roku": { + "appName": 'ERTFLIX', + "app-id": 655288, + }, + "xiaomi": { + "appName": "t.yi.erthybrid", + "androidName": "t.yi.erthybrid", + "adbLaunchCommand": "adb shell am start -n t.yi.erthybrid/com.arxnet.soeasytv.MainActivity", + }, + }, + + + "espn": { + "button": '', + "friendlyName": "ESPN", + "className": "espnButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.espn.gtv", + "androidName": "com.espn.gtv", + "adbLaunchCommand": "adb shell am start -n com.espn.gtv/com.espn.startup.presentation.StartupActivity", + }, + "apple-tv": { + "appName": "ESPN", + }, + "chromecast": { + "appName": "com.espn.score_center", + "androidName": "com.espn.score_center", + "adbLaunchCommand": "adb shell am start -n com.espn.score_center/com.espn.startup.presentation.StartupActivity", + }, + "homatics": { + "appName": "com.espn.score_center", + "androidName": "com.espn.score_center", + "adbLaunchCommand": "adb shell am start -n com.espn.score_center/com.espn.startup.presentation.StartupActivity", + }, + "nvidia-shield": { + "appName": "com.espn.score_center", + "androidName": "com.espn.score_center", + "adbLaunchCommand": "adb shell am start -n com.espn.score_center/com.espn.startup.presentation.StartupActivity", + }, + "onn": { + "appName": "com.espn.score_center", + "androidName": "com.espn.score_center", + "adbLaunchCommand": "adb shell am start -n com.espn.score_center/com.espn.startup.presentation.StartupActivity", + }, + "roku": { + "appName": 'ESPN', + "app-id": 34376, + }, + "xiaomi": { + "appName": "com.espn.score_center", + "androidName": "com.espn.score_center", + "adbLaunchCommand": "adb shell am start -n com.espn.score_center/com.espn.startup.presentation.StartupActivity", + }, + }, + + + "f-droid": { + "button": '', + "friendlyName": "F-Droid", + "className": "fDroidutton", + "appName": "org.fdroid.fdroid", + "androidName": "org.fdroid.fdroid", + "adbLaunchCommand": "adb shell am start -n org.fdroid.fdroid/org.fdroid.fdroid.views.main.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "f1-tv": { + "button": '', + "friendlyName": "F1 TV", + "className": "f1TVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + "apple-tv": { + "appName": "F1 TV", + }, + "chromecast": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + "onn": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + "roku": { + "appName": 'F1 TV', + "app-id": 285646, + }, + "xiaomi": { + "appName": "com.formulaone.production", + "androidName": "com.formulaone.production", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.formulaone.production/com.avs.f1.ui.splash.SplashActivity", + }, + }, + + + "fandango-at-home": { + "button": '', + "button-round": '', + "friendlyName": "Fandango at Home", + "className": "fandangoAtHomeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.fandango.vudu.firetv", + "androidName": "com.fandango.vudu.firetv", + "adbLaunchCommand": "adb shell am start -n com.fandango.vudu.firetv/air.com.vudu.air.DownloaderTablet.SplashActivity", + }, + "apple-tv": { + "appName": "Fandango at Home", + }, + "chromecast": { + "appName": "air.com.vudu.air.DownloaderTablet", + "androidName": "air.com.vudu.air.DownloaderTablet", + "adbLaunchCommand": "adb shell am start -n air.com.vudu.air.DownloaderTablet/.SplashActivity", + }, + "homatics": { + "appName": "air.com.vudu.air.DownloaderTablet", + "androidName": "air.com.vudu.air.DownloaderTablet", + "adbLaunchCommand": "adb shell am start -n air.com.vudu.air.DownloaderTablet/.SplashActivity", + }, + "nvidia-shield": { + "appName": "air.com.vudu.air.DownloaderTablet", + "androidName": "air.com.vudu.air.DownloaderTablet", + "adbLaunchCommand": "adb shell am start -n air.com.vudu.air.DownloaderTablet/.SplashActivity", + }, + "onn": { + "appName": "air.com.vudu.air.DownloaderTablet", + "androidName": "air.com.vudu.air.DownloaderTablet", + "adbLaunchCommand": "adb shell am start -n air.com.vudu.air.DownloaderTablet/.SplashActivity", + }, + "roku": { + "appName": 'Fandango at Home', + "app-id": 13842, + }, + "xiaomi": { + "appName": "air.com.vudu.air.DownloaderTablet", + "androidName": "air.com.vudu.air.DownloaderTablet", + "adbLaunchCommand": "adb shell am start -n air.com.vudu.air.DownloaderTablet/.SplashActivity", + }, + }, + + + "fanduel-sports-network": { + "button": '', + "button-round": '', + "friendlyName": 'Fanduel Sports Network', + "className": "fanduelSportsNetworkButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.foxsports.dssgo", + "androidName": "com.foxsports.dssgo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.dssgo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + "apple-tv": { + "appName": "FanDuel Sports Network", + }, + "chromecast": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + "homatics": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + "nvidia-shield": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + "onn": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + "roku": { + "appName": 'FanDuel Sports Network', + "app-id": 285486, + }, + "xiaomi": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.ballysports.tv.ui.screens.main.TvMainActivity", + }, + }, + + + "fioptics-plus": { + "button": '', + "friendlyName": "Fioptics+", + "className": "fiopticsPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + "apple-tv": { + "appName": "Fioptics Plus", + }, + "chromecast": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + "homatics": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + "nvidia-shield": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + "onn": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + "xiaomi": { + "appName": "com.tivo.android.cbt", + "androidName": "com.tivo.android.cbt", + "adbLaunchCommand": "adb shell am start -n com.tivo.android.cbt/com.tivo.hydra.app.MainActivity", + }, + }, + + + "firetv-store": { + "button": 'appstore', + "friendlyName": "Fire TV Store", + "className": "fireTVStoreButton", + "appName": "FireTV Store", + "androidName": "com.amazon.venezia", + "adbLaunchCommand": "adb shell am start -a com.amazon.venezia.ade.FIND_LAUNCH -n com.amazon.venezia/.ade.ADEHomeActivity", + "deviceFamily": ["amazon-fire"], }, + + + "flash": { + "button": '', + "friendlyName": "Flash", + "className": "flashButton", + "appName": "Flash", + "deviceFamily": ["apple-tv"], }, + + + "flo-sports": { + "button": '', + "button-round": '', + "friendlyName": "FloSports", + "className": "floSportsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.flosports.signal.tv", + "androidName": "com.flosports.signal.tv", + "adbLaunchCommand": "adb shell am start -n com.flosports.signal.tv/tv.flosports.TvActivity", + }, + "apple-tv": { + "appName": "FloSports", + }, + "chromecast": { + "appName": "tv.flosports", + "androidName": "tv.flosports", + "adbLaunchCommand": "adb shell am start -n tv.flosports/.TvActivity", + }, + "homatics": { + "appName": "tv.flosports", + "androidName": "tv.flosports", + "adbLaunchCommand": "adb shell am start -n tv.flosports/.TvActivity", + }, + "nvidia-shield": { + "appName": "tv.flosports", + "androidName": "tv.flosports", + "adbLaunchCommand": "adb shell am start -n tv.flosports/.TvActivity", + }, + "onn": { + "appName": "tv.flosports", + "androidName": "tv.flosports", + "adbLaunchCommand": "adb shell am start -n tv.flosports/.TvActivity", + }, + "roku": { + "appName": "FloSports", + "app-id": 112048, + }, + "xiaomi": { + "appName": "tv.flosports", + "androidName": "tv.flosports", + "adbLaunchCommand": "adb shell am start -n tv.flosports/.TvActivity", + }, + }, + + + "flow": { + "button": '', + "button-round": '', + "friendlyName": "flow", + "className": "flowButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + "chromecast": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + "homatics": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + "nvidia-shield": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + "onn": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + "xiaomi": { + "appName": "ar.com.flow.androidtv", + "androidName": "ar.com.flow.androidtv", + "adbLaunchCommand": "adb shell am start -n ar.com.flow.androidtv/.base.view.BaseActivity", + }, + }, + + + "fotoo": { + "button": '', + "friendlyName": "Fotoo", + "className": "fotooButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + "chromecast": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + "homatics": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + "nvidia-shield": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + "onn": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + "xiaomi": { + "appName": "com.bo.fotoo", + "androidName": "com.bo.fotoo", + "adbLaunchCommand": "adb shell am start -n com.bo.fotoo/.ui.splash.FTSplashActivity", + }, + }, + + + "fox-business": { + "button": '', + "friendlyName": "Fox Business", + "className": "foxBusinessButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.twoergo.foxbusiness", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "apple-tv": { + "appName": "Fox Business", + }, + "chromecast": { + "appName": "com.twoergo.foxbusiness", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "homatics": { + "appName": "com.twoergo.foxbusiness", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "Fox Business", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "onn": { + "appName": "com.twoergo.foxbusiness", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "roku": { + "appName": "Fox Business Network", + "app-id": 18746, + }, + "xiaomi": { + "appName": "com.twoergo.foxbusiness", + "androidName": "com.twoergo.foxbusiness", + "adbLaunchCommand": "adb shell am start -n com.twoergo.foxbusiness/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + }, + + + "fox-local": { + "button": '', + "button-round": '', + "friendlyName": "Fox Local", + "className": "foxLocalButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + "apple-tv": { + "appName": "FOX Local", + }, + "chromecast": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + "homatics": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + "onn": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + "roku": { + "appName": "FOX LOCAL: Free Live News", + "app-id": 711586, + }, + "xiaomi": { + "appName": "com.fox.fts.android", + "androidName": "com.fox.fts.android", + "adbLaunchCommand": "adb shell am start -n com.fox.fts.android/.ui.SplashActivity", + }, + }, + + + "fox-news": { + "button": '', + "friendlyName": "Fox News", + "className": "foxNewsChannelButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.amazon.rialto.cordova.webapp.webapp855cc15add08457a8a1fe62312eab585", + "androidName": "com.amazon.rialto.cordova.webapp.webapp855cc15add08457a8a1fe62312eab585", + "adbLaunchCommand": "adb shell am start -n com.amazon.rialto.cordova.webapp.webapp855cc15add08457a8a1fe62312eab585/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "apple-tv": { + "appName": "Fox News", + }, + "chromecast": { + "appName": "com.foxnews.android", + "androidName": "com.foxnews.android", + "adbLaunchCommand": "adb shell am start -n com.foxnews.android/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "homatics": { + "appName": "com.foxnews.android", + "androidName": "com.foxnews.android", + "adbLaunchCommand": "adb shell am start -n com.foxnews.android/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "com.foxnews.android", + "androidName": "com.foxnews.android", + "adbLaunchCommand": "adb shell am start -n com.foxnews.android/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "onn": { + "appName": "com.foxnews.android", + "androidName": "com.foxnews.android", + "adbLaunchCommand": "adb shell am start -n com.foxnews.android/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + "roku": { + "appName": "Fox News Channel", + "app-id": 2946, + }, + "xiaomi": { + "appName": "com.foxnews.android", + "androidName": "com.foxnews.android", + "adbLaunchCommand": "adb shell am start -n com.foxnews.android/com.foxnews.androidtv.ui.splash.SplashScreenActivity", + }, + }, + + + "fox-sports": { + "button": '', + "friendlyName": "FOX Sports", + "className": "foxSportsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.foxsports.videogo", + "androidName": "com.foxsports.videogo", + "adbLaunchCommand": "adb shell am start -n com.foxsports.videogo/com.fox.app.MainActivity", + }, + "apple-tv": { + "appName": "FOX Sports", + }, + "chromecast": { + "appName": "com.foxsports.android", + "androidName": "com.foxsports.android", + "adbLaunchCommand": "adb shell am start -n com.foxsports.android/com.fox.app.MainActivity", + }, + "homatics": { + "appName": "com.foxsports.android", + "androidName": "com.foxsports.android", + "adbLaunchCommand": "adb shell am start -n com.foxsports.android/com.fox.app.MainActivity", + }, + "nvidia-shield": { + "appName": "com.foxsports.android", + "androidName": "com.foxsports.android", + "adbLaunchCommand": "adb shell am start -n com.foxsports.android/com.fox.app.MainActivity", + }, + "onn": { + "appName": "com.foxsports.android", + "androidName": "com.foxsports.android", + "adbLaunchCommand": "adb shell am start -n com.foxsports.android/com.fox.app.MainActivity", + }, + "roku": { + "appName": 'FOX Sports', + "app-id": 95307, + }, + "xiaomi": { + "appName": "com.foxsports.android", + "androidName": "com.foxsports.android", + "adbLaunchCommand": "adb shell am start -n com.foxsports.android/com.fox.app.MainActivity", + }, + }, + + + "foxtel-au": { + "button": '', + "friendlyName": "foxtel (AU)", + "className": "foxtelAUButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + "chromecast": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + "homatics": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + "nvidia-shield": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + "onn": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + "xiaomi": { + "appName": "au.com.foxtel.atv", + "androidName": "au.com.foxtel.atv", + "adbLaunchCommand": "adb shell am start -n au.com.foxtel.atv/foxtel.play.droid.atv.SplashView", + }, + }, + + + "france-tv": { + "button": '', + "friendlyName": "france.tv", + "className": "franceTVButton", + "appName": "France TV", + "androidName": "fr.francetv.pluzz", + "adbLaunchCommand": "adb shell am start -n fr.francetv.pluzz/fr.francetv.androidtv.cmp.CmpActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], +}, + + + "free-tv": { + "button": '', + "button-round": '', + "friendlyName": "FreeTV", + "className": "freeTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + "chromecast": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + "homatics": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + "onn": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "tv.freetv.androidtv", + "androidName": "tv.freetv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.freetv.androidtv/pl.atende.mobile.tv.ui.gui.splash.SplashActivity", + }, + }, + + + "freecast": { + "button": '', + "button-round": '', + "friendlyName": "Freecast", + "className": "freecastButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + "apple-tv": { + "appName": "FreeCast", + }, + "chromecast": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + "homatics": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + "nvidia-shield": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + "onn": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + "xiaomi": { + "appName": "com.freecast.watch", + "androidName": "com.freecast.watch", + "adbLaunchCommand": "adb shell am start -n com.freecast.watch/com.freecast.features.MainActivity", + }, + }, + + + "freevee": { + "button": '', + "friendlyName": "freevee", + "className": "freeveeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "roku", "xiaomi"], + "amazon-fire": { + "appName": "IMDb TV", + "androidName": "com.amazon.imdb.tv.android.app", + "adbLaunchCommand": "adb shell am start -n com.amazon.imdb.tv.android.app/com.amazon.imdb.tv.android.app.MainActivity", + }, + "apple-tv": { + "appName": "Freevee", + }, + "chromecast": { + "appName": "Freevee", + "androidName": "com.imdbtv.livingroom", + "adbLaunchCommand": "adb shell am start -n com.imdbtv.livingroom/com.amazon.ignition.MainActivity", + }, + "homatics": { + "appName": "com.imdbtv.livingroom", + "androidName": "com.imdbtv.livingroom", + "adbLaunchCommand": "adb shell am start -n com.imdbtv.livingroom/com.amazon.ignition.MainActivity", + }, + "nvidia-shield": { + "appName": "Freevee", + "androidName": "com.imdbtv.livingroom", + "adbLaunchCommand": "adb shell am start -n com.imdbtv.livingroom/com.amazon.ignition.MainActivity", + }, + "roku": { + "appName": 'Freevee', + "app-id": 615685, + }, + "xiaomi": { + "appName": "Freevee", + "androidName": "com.imdbtv.livingroom", + "adbLaunchCommand": "adb shell am start -n com.imdbtv.livingroom/com.amazon.ignition.MainActivity", + }, + }, + + + "freeview-play": { + "button": '', + "friendlyName": "Freeview Play", + "className": "freeviewPlayButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "uk.co.freeview.firetv.explore", + "androidName": "uk.co.freeview.firetv.explore", + "adbLaunchCommand": "adb shell sendevent /dev/input/event4 1 747 1 && sendevent /dev/input/event4 0 0 0 && sendevent /dev/input/event4 1 747 0 && sendevent /dev/input/event4 0 0 0", + }, + }, + + + "fubo": { + "button": '', + "friendlyName": "fubo", + "className": "fuboButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + "apple-tv": { + "appName": "Fubo", + }, + "chromecast": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + "homatics": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + "nvidia-shield": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + "onn": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + "roku": { + "appName": 'Fubo: Watch Live TV & Sports', + "app-id": 43465, + }, + "xiaomi": { + "appName": "com.fubo.firetv.screen", + "androidName": "com.fubo.firetv.screen", + "adbLaunchCommand": "adb shell am start -n com.fubo.firetv.screen/tv.fubo.mobile.presentation.onboarding.dispatch.controller.DispatchActivity", + }, + }, + + + "gcn-plus": { + "button": '', + "friendlyName": "GCN+", + "className": "gcnPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.playsportsnetwork.tourmalet.tv", + "androidName": "com.playsportsnetwork.tourmalet.tv", + "adbLaunchCommand": "adb shell am start -n com.playsportsnetwork.tourmalet.tv/com.playsportsnetwork.tourmalet.tv.MainActivity", + }, + "apple-tv": { + "appName": "gcn", + }, + "chromecast": { + "appName": "com.playsportsnetwork.tourmalet.tv", + "androidName": "com.playsportsnetwork.tourmalet.tv", + "adbLaunchCommand": "adb shell am start -n com.playsportsnetwork.tourmalet.tv/com.playsportsnetwork.tourmalet.tv.MainActivity", + }, + "homatics": { + "appName": "com.playsportsnetwork.tourmalet.tv", + "androidName": "com.playsportsnetwork.tourmalet.tv", + "adbLaunchCommand": "adb shell am start -n com.playsportsnetwork.tourmalet.tv/com.playsportsnetwork.tourmalet.tv.MainActivity", + }, + "nvidia-shield": { + "appName": "com.playsportsnetwork.tourmalet.tv", + "androidName": "com.playsportsnetwork.tourmalet.tv", + "adbLaunchCommand": "adb shell am start -n com.playsportsnetwork.tourmalet.tv/com.playsportsnetwork.tourmalet.tv.MainActivity", + }, + "xiaomi": { + "appName": "com.playsportsnetwork.tourmalet.tv", + "androidName": "com.playsportsnetwork.tourmalet.tv", + "adbLaunchCommand": "adb shell am start -n com.playsportsnetwork.tourmalet.tv/com.playsportsnetwork.tourmalet.tv.MainActivity", + }, + }, + + + "geforce-now": { + "button": '', + "friendlyName": "GEFORCE NOW", + "className": "geForceNowButton", + "deviceFamily": ["nvidia-shield"], + "nvidia-shield": { + "appName": "com.nvidia.tegrazone3", + "androidName": "com.nvidia.tegrazone3", + "adbLaunchCommand": "adb shell am start -n com.nvidia.tegrazone3/com.nvidia.geforcenow.MallActivity", + }, + }, + + + "globoplay": { + "button": '', + "friendlyName": "Globoplay", + "className": "globoPlayButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + "apple-tv": { + "appName": "Globoplay", + }, + "chromecast": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + "homatics": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + "nvidia-shield": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + "onn": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + "roku": { + "appName": "Globoplay Internacional", + "app-id": 614260, + }, + "xiaomi": { + "appName": "com.globo.globotv", + "androidName": "com.globo.globotv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.globo.globotv/.maintv.MainActivity", + }, + }, + + + "go3-estonia": { + "button": '', + "button-round": '', + "friendlyName": "Go3 Estonia", + "className": "go3EstoniaButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "tv.go3.android.tv", + "androidName": "tv.go3.android.tv", + "adbLaunchCommand": "adb shell am start -n tv.go3.android.tv/pl.atende.mobile.tv.ui.gui.main.activity.MainActivity", + }, + "homatics": { + "appName": "tv.go3.android.tv", + "androidName": "tv.go3.android.tv", + "adbLaunchCommand": "adb shell am start -n tv.go3.android.tv/pl.atende.mobile.tv.ui.gui.main.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "tv.go3.android.tv", + "androidName": "tv.go3.android.tv", + "adbLaunchCommand": "adb shell am start -n tv.go3.android.tv/pl.atende.mobile.tv.ui.gui.main.activity.MainActivity", + }, + "onn": { + "appName": "tv.go3.android.tv", + "androidName": "tv.go3.android.tv", + "adbLaunchCommand": "adb shell am start -n tv.go3.android.tv/pl.atende.mobile.tv.ui.gui.main.activity.MainActivity", + }, + "xiaomi": { + "appName": "tv.go3.android.tv", + "androidName": "tv.go3.android.tv", + "adbLaunchCommand": "adb shell am start -n tv.go3.android.tv/pl.atende.mobile.tv.ui.gui.main.activity.MainActivity", + }, + }, + + + "go-play": { + "button": '', + "friendlyName": "GO PLAY (BE)", + "className": "goPlayButton", + "appName": "be.goplay.app.tv", + "androidName": "be.goplay.app.tv", + "adbLaunchCommand": "adb shell am start -n be.goplay.app.tv/be.goplay.app.ui.consent.ConsentActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "go-tv": { + "button": '', + "friendlyName": "GO TV", + "className": "goTVButton", + "appName": "mt.com.go.iptv.android.devices", + "androidName": "mt.com.go.iptv.android.devices", + "adbLaunchCommand": "adb shell am start -n mt.com.go.iptv.android.devices/com.minervanetworks.itvfusion.tv.yourtv.SingleActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "google-play-store": { + "button": '', + "button-round": '', + "friendlyName": "Google Play Store", + "appName": "Play Store", + "className": "googlePlayStoreButton", + "androidName": "com.android.vending", + "deviceFamily": ["nvidia-shield", "chromecast", "homatics", "onn", "xiaomi"], }, + + + "greek-tv-live-and-radio-player": { + "button": '', + "friendlyName": "Greek TV Live & Radio Player", + "className": "greekTVLiveAndRadioPlayerButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "com.nickstamp.tvradio_gr", + "androidName": "com.nickstamp.tvradio_gr", + "adbLaunchCommand": "adb shell am start -n com.nickstamp.tvradio_gr/com.nickstamp.activity.TvActivity", + }, + "homatics": { + "appName": "com.nickstamp.tvradio_gr", + "androidName": "com.nickstamp.tvradio_gr", + "adbLaunchCommand": "adb shell am start -n com.nickstamp.tvradio_gr/com.nickstamp.activity.TvActivity", + }, + "nvidia-shield": { + "appName": "com.nickstamp.tvradio_gr", + "androidName": "com.nickstamp.tvradio_gr", + "adbLaunchCommand": "adb shell am start -n com.nickstamp.tvradio_gr/com.nickstamp.activity.TvActivity", + }, + "onn": { + "appName": "com.nickstamp.tvradio_gr", + "androidName": "com.nickstamp.tvradio_gr", + "adbLaunchCommand": "adb shell am start -n com.nickstamp.tvradio_gr/com.nickstamp.activity.TvActivity", + }, + "xiaomi": { + "appName": "com.nickstamp.tvradio_gr", + "androidName": "com.nickstamp.tvradio_gr", + "adbLaunchCommand": "adb shell am start -n com.nickstamp.tvradio_gr/com.nickstamp.activity.TvActivity", + }, + }, + + + "hayu": { + "button": '', + "button-round": '', + "friendlyName": "Hayu", + "className": "hayuButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Hayu", + }, + "chromecast": { + "appName": "com.upst.hayu", + "androidName": "com.upst.hayu", + "adbLaunchCommand": "adb shell am start -n com.upst.hayu/.tv.main.MainActivity", + }, + "homatics": { + "appName": "com.upst.hayu", + "androidName": "com.upst.hayu", + "adbLaunchCommand": "adb shell am start -n com.upst.hayu/.tv.main.MainActivity", + }, + "nvidia-shield": { + "appName": "com.upst.hayu", + "androidName": "com.upst.hayu", + "adbLaunchCommand": "adb shell am start -n com.upst.hayu/.tv.main.MainActivity", + }, + "onn": { + "appName": "com.upst.hayu", + "androidName": "com.upst.hayu", + "adbLaunchCommand": "adb shell am start -n com.upst.hayu/.tv.main.MainActivity", + }, + "xiaomi": { + "appName": "com.upst.hayu", + "androidName": "com.upst.hayu", + "adbLaunchCommand": "adb shell am start -n com.upst.hayu/.tv.main.MainActivity", + }, + }, + + + "hbo-max": { + "button": '', + "friendlyName": "HBO Max - Denmark/Netherlands", + "className": "hboMaxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.wbd.hbomax", + "androidName": "com.wbd.hbomax", + "adbLaunchCommand": "adb shell am start -n com.wbd.hbomax/com.wbd.beam.BeamActivity", + }, + "apple-tv": { + "appName": "HBO Max", + }, + "chromecast": { + "appName": "com.wbd.hbomax", + "androidName": "com.wbd.hbomax", + "adbLaunchCommand": "adb shell am start -n com.wbd.hbomax/com.wbd.beam.BeamActivity", + }, + "nvidia-shield": { + "appName": "com.wbd.hbomax", + "androidName": "com.wbd.hbomax", + "adbLaunchCommand": "adb shell am start -n com.wbd.hbomax/com.wbd.beam.BeamActivity", + }, + "xiaomi": { + "appName": "com.wbd.hbomax", + "androidName": "com.wbd.hbomax", + "adbLaunchCommand": "adb shell am start -n com.wbd.hbomax/com.wbd.beam.BeamActivity", + }, + }, + + + "hdhomerun": { + "button": '', + "friendlyName": "HDHomeRun", + "className": "hdhomerunButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + "apple-tv": { + "appName": "HDHomeRun", + }, + "chromecast": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + "homatics": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + "nvidia-shield": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + "onn": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + "roku": { + "appName": 'HDHomeRun', + "app-id": 297903, + }, + "xiaomi": { + "appName": "com.silicondust.view", + "androidName": "com.silicondust.view", + "adbLaunchCommand": "adb shell am start -n com.silicondust.view/com.silicondust.view.App", + }, + }, + + + "hdo-box": { + "button": '', + "friendlyName": "HDO Box", + "className": "hdoBoxButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + "chromecast": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + "homatics": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + "onn": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + "xiaomi": { + "appName": "com.tv.hdobox", + "androidName": "com.tv.hdobox", + "adbLaunchCommand": "adb shell am start -n com.tv.hdobox/.MainActivity", + }, + }, + + + "helix-tv": { + "button": '', + "button-round": '', + "friendlyName": "Helix TV", + "className": "helixTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.videotron.helixtv.tenfoot", + "androidName": "com.videotron.helixtv.tenfoot", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv.tenfoot/com.xfinity.common.view.LaunchActivity", + }, + "chromecast": { + "appName": "com.videotron.helixtv", + "androidName": "com.videotron.helixtv", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv/com.xfinity.common.view.LaunchActivity", + }, + "homatics": { + "appName": "com.videotron.helixtv", + "androidName": "com.videotron.helixtv", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv/com.xfinity.common.view.LaunchActivity", + }, + "nvidia-shield": { + "appName": "com.videotron.helixtv", + "androidName": "com.videotron.helixtv", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv/com.xfinity.common.view.LaunchActivity", + }, + "onn": { + "appName": "com.videotron.helixtv", + "androidName": "com.videotron.helixtv", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv/com.xfinity.common.view.LaunchActivity", + }, + "xiaomi": { + "appName": "com.videotron.helixtv", + "androidName": "com.videotron.helixtv", + "adbLaunchCommand": "adb shell am start -n com.videotron.helixtv/com.xfinity.common.view.LaunchActivity", + }, + }, + + + "history-vault": { + "button": '', + "button-round": '', + "friendlyName": "History Vault", + "className": "historyVaultButton", + "deviceFamily": ["amazon-fire", "apple-tv", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.aetnd.svod.historyvault", + "androidName": "com.com.aetnd.svod.historyvault.plus", + "adbLaunchCommand": "adb shell am start -n com.aetnd.svod.historyvault/com.aetn.android.tveapps.app.MainActivity", + }, + "apple-tv": { + "appName": "HISTORY Vault", + }, + "homatics": { + "appName": "com.aetnd.svod.historyvault.androidtv", + "androidName": "com.aetnd.svod.historyvault.androidtv", + "adbLaunchCommand": "adb shell am start -n com.aetnd.svod.historyvault.androidtv/com.aetn.android.tveapps.app.MainActivity", + }, + "nvidia-shield": { + "appName": "com.aetnd.svod.historyvault.androidtv", + "androidName": "com.aetnd.svod.historyvault.androidtv", + "adbLaunchCommand": "adb shell am start -n com.aetnd.svod.historyvault.androidtv/com.aetn.android.tveapps.app.MainActivity", + }, + "onn": { + "appName": "com.aetnd.svod.historyvault.androidtv", + "androidName": "com.aetnd.svod.historyvault.androidtv", + "adbLaunchCommand": "adb shell am start -n com.aetnd.svod.historyvault.androidtv/com.aetn.android.tveapps.app.MainActivity", + }, + "roku": { + "appName": "HISTORY VAULT", + "app-id": 81920, + }, + "xiaomi": { + "appName": "com.aetnd.svod.historyvault.androidtv", + "androidName": "com.aetnd.svod.historyvault.androidtv", + "adbLaunchCommand": "adb shell am start -n com.aetnd.svod.historyvault.androidtv/com.aetn.android.tveapps.app.MainActivity", + }, + }, + + + "home-assistant": { + "button": '', + "friendlyName": "Home Assistant", + "className": "homeAssistantButton", + "appName": "io.homeassistant.companion.android", + "androidName": "io.homeassistant.companion.android", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n io.homeassistant.companion.android/.launch.LaunchActivity", + "deviceFamily": ["amazon-fire"], }, + + + "hulu": { + "button": '', + "friendlyName": "Hulu", + "className": "huluButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Hulu", + "androidName": "com.hulu.plus", + }, + "apple-tv": { + "appName": "Hulu", + }, + "chromecast": { + "appName": "com.hulu.livingroomplus", + "androidName": "com.hulu.livingroomplus", + "adbLaunchCommand": "adb shell am start -n com.hulu.livingroomplus/.WKFactivity", + }, + "homatics": { + "appName": "Hulu (2)", + "androidName": "com.hulu.livingroomplus", + "adbLaunchCommand": "adb shell am start -n com.hulu.livingroomplus/.WKFactivity", + }, + "nvidia-shield": { + "appName": "com.hulu.livingroomplus", + "androidName": "com.hulu.livingroomplus", + "adbLaunchCommand": "adb shell am start -n com.hulu.livingroomplus/.WKFactivity", + }, + "onn": { + "appName": "com.hulu.livingroomplus", + "androidName": "com.hulu.livingroomplus", + "adbLaunchCommand": "adb shell am start -n com.hulu.livingroomplus/.WKFactivity", + }, + "roku": { + "appName": "Hulu", + "app-id": 2285, + }, + "xiaomi": { + "appName": "com.hulu.livingroomplus", + "androidName": "com.hulu.livingroomplus", + "adbLaunchCommand": "adb shell am start -n com.hulu.livingroomplus/.WKFactivity", + }, + }, + + + "ibo-player-pro": { + "button": '', + "friendlyName": "Ibo Player Pro", + "className": "iboPlayerProButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + "apple-tv": { + "appName": "ibo Pro Player", + }, + "chromecast": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + "homatics": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + "onn": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + "roku": { + "appName": "Ibo Player Pro(US)", + "app-id": 763608, + }, + "xiaomi": { + "appName": "com.flextv.livestore", + "androidName": "com.flextv.livestore", + "adbLaunchCommand": "adb shell am start -n com.flextv.livestore/.MainActivity", + }, + }, + + + "ici-tou-tv": { + "button": '', + "button-round": '', + "friendlyName": "ICI TOU.TV", + "className": "icitouTVButton", + "deviceFamily": ["apple-tv", "chromecast"], + "apple-tv": { + "appName": "ICI TOU.TV", + }, + "chromecast": { + "appName": "tv.toutv.androidtv", + "androidName": "tv.toutv.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.toutv.androidtv/tv.tou.android.home.views.activities.MainActivityTv", + }, + }, + + + "ignite-tv": { + "button": '', + "friendlyName": "Ignite TV (Shaw)", + "className": "igniteTVShawButton", + "appName": "ca.shaw.freerangetv.tenfoot", + "androidName": "ca.shaw.freerangetv.tenfoot", + "deviceFamily": ["amazon-fire"], }, + + + "implayer-tv": { + "button": '', + "button-round": '', + "friendlyName": "iMPlayer", + "className": "iMPlayerButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + "chromecast": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + "homatics": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + "nvidia-shield": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + "onn": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + "xiaomi": { + "appName": "com.myiptvonline.implayer", + "androidName": "com.myiptvonline.implayer", + "adbLaunchCommand": "adb shell am start -n com.myiptvonline.implayer/com.myiptvonline.implayer.a", + }, + }, + + + "infuse": { + "button": '', + "friendlyName": "Infuse", + "className": "infuseButton", + "appName": "Infuse", + "androidName": "com.", + "deviceFamily": ["apple-tv"], }, + + + "internet-silk-browser": { + "button": '', + "friendlyName": "internet (Silk Browser)", + "className": "internetSilkBrowserButton", + "appName": "com.amazon.cloud9", + "androidName": "com.amazon.cloud9", + "adbLaunchCommand": "adb shell am start -n com.amazon.cloud9/.browsing.BrowserActivity", + "deviceFamily": ["amazon-fire"],}, + + + "i-play-tv": { + "button": '', + "friendlyName": "iPlayTV", + "className": "iPlayTVButton", + "appName": "iPlayTV", + "deviceFamily": ["apple-tv"], + }, + + + "ip-cam-viewer-free": { + "button": '', + "friendlyName": "IP Cam Viewer - Free", + "className": "ipCamViewerFreeButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.rcreations.ipcamviewerFree", + "androidName": "com.rcreations.ipcamviewerFree", + "adbLaunchCommand": "adb shell am start -n com.rcreations.ipcamviewerFree/.WebCamViewerActivity", + }, + }, + + + "ip-camera-viewer-ipcams": { + "button": '', + "friendlyName": "IP Camera Viewer - IPCams", + "className": "ipCameraViewerIPCamsButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "IPCams", + }, + }, + + + "ip-camera-viewer-basic": { + "button": '', + "friendlyName": "IP Camera Viewer - Basic", + "className": "ipCameraViewerBasicButton", + "deviceFamily": ["roku"], + "roku": { + "appName": 'IP Camera Viewer - Basic', + "app-id": 143924, + }, + }, + + + "ip-camera-viewer-pro": { + "button": '', + "friendlyName": "IP Camera Viewer - Pro", + "className": "ipCameraViewerProButton", + "deviceFamily": ["roku"], + "roku": { + "appName": 'IP Camera Viewer - Pro', + "app-id": 143683, + }, + }, + + + "iptv-extreme-pro": { + "button": '', + "friendlyName": "IPTV Extreme Pro", + "className": "ipTVExtremeProButton", + "appName": "com.pecana.iptvextremepro", + "androidName": "com.pecana.iptvextremepro", + "adbLaunchCommand": "adb shell am start -n com.pecana.iptvextremepro/.SplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"],}, + + + "iptv-smarters-pro": { + "button": '', + "button-round": '', + "friendlyName": "IPTV Smarters Pro", + "className": "ipSmartersProButton", + "appName": "IPTV Smarters Pro", + "androidName": "com.nst.iptvsmarterstvbox", + "adbLaunchCommand": "adb shell am start -n com.nst.iptvsmarterstvbox/.view.activity.SplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"],}, + + + "iptvx": { + "button": '', + "friendlyName": "IPTVX", + "className": "iptvXButton", + "appName": "IPTVX", + "deviceFamily": ["apple-tv"], }, + + + "ipvanish": { + "button": '', + "friendlyName": "IPVanish VPN", + "className": "ipVanishButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + }, + "apple-tv": { + "appName": "IPVanish", + }, + "chromecast": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + }, + "homatics": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + "adbLaunchCommand": "adb shell am start -n com.ixolit.ipvanish/.presentation.features.launch.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + }, + "onn": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + }, + "xiaomi": { + "appName": "com.ixolit.ipvanish", + "androidName": "com.ixolit.ipvanish", + }, + }, + + + "iqiyi": { + "button": '', + "friendlyName": "iQIYI", + "className": "iqiyiButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.iqiyi.i18n.tv", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + "apple-tv": { + "appName": "iQIYI", + }, + "chromecast": { + "appName": "com.iqiyi.i18n.tv", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + "homatics": { + "appName": "com.iqiyi.i18n.tv", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + "nvidia-shield": { + "appName": "com.iqiyi.i18n.tv", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + "onn": { + "appName": "iQIYI", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + "roku": { + "appName": 'iQIYI Video – Dramas & Movies', + "app-id": 641081, + }, + "xiaomi": { + "appName": "com.iqiyi.i18n.tv", + "androidName": "com.iqiyi.i18n.tv", + "adbLaunchCommand": "adb shell am start -n com.iqiyi.i18n.tv/.launch.LaunchActivity", + }, + }, + + + "itvx": { + "button": '', + "friendlyName": "ITVX", + "className": "itvxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "roku"], + "amazon-fire": { + "appName": "air.ITVMobilePlayer", + "androidName": "air.ITVMobilePlayer", + "adbLaunchCommand": "adb shell am start -n air.ITVMobilePlayer/.ITVActivity", + }, + "apple-tv": { + "appName": "ITVX", + }, + "chromecast": { + "appName": "air.ITVMobilePlayer", + "androidName": "air.ITVMobilePlayer", + "adbLaunchCommand": "adb shell am start -n air.ITVMobilePlayer/com.itv.tenft.itvhub.MainActivity", + }, + "homatics": { + "appName": "air.ITVMobilePlayer", + "androidName": "air.ITVMobilePlayer", + "adbLaunchCommand": "adb shell am start -n air.ITVMobilePlayer/com.itv.tenft.itvhub.MainActivity", + }, + "nvidia-shield": { + "appName": "air.ITVMobilePlayer", + "androidName": "air.ITVMobilePlayer", + "adbLaunchCommand": "adb shell am start -n air.ITVMobilePlayer/com.itv.tenft.itvhub.MainActivity", + }, + "roku": { + "appName": 'ITVX', + "app-id": 42329, + }, + }, + + + "israel-station": { + "button": 'Israel Station', + "friendlyName": "Israel Station", + "className": "israelStationButton", + + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "roku", "xiaomi"], + "amazon-fire": { + "appName": "israelstation.androidtv", + "androidName": "israelstation.androidtv", + }, + + "chromecast": { + "appName": "israelstation.androidtv", + "androidName": "israelstation.androidtv", + }, + "homatics": { + "appName": "israelstation.androidtv", + "androidName": "israelstation.androidtv", + "adbLaunchCommand": "adb shell am start -n israelstation.androidtv/israel14.androidradio.ui.activities.SplashActivity", + }, + "nvidia-shield": { + "appName": "israelstation.androidtv", + "androidName": "israelstation.androidtv", + }, + "roku": { + "appName": 'israel station', + "app-id": 665572, + }, + "xiaomi": { + "appName": "israelstation.androidtv", + "androidName": "israelstation.androidtv", + }, + }, + + + "iVysílání-České-televize": { + "button": '', + "friendlyName": "iVysílání", + "className": "iVysilaniButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "iVysílání", + }, + "chromecast": { + "appName": "cz.ceskatelevize.ivysilani.tvapp", + "androidName": "cz.ceskatelevize.ivysilani.tvapp", + "adbLaunchCommand": "adb shell am start -n cz.ceskatelevize.ivysilani.tvapp/.MainActivity", + }, + "homatics": { + "appName": "cz.ceskatelevize.ivysilani.tvapp", + "androidName": "cz.ceskatelevize.ivysilani.tvapp", + "adbLaunchCommand": "adb shell am start -n cz.ceskatelevize.ivysilani.tvapp/.MainActivity", + }, + "nvidia-shield": { + "appName": "cz.ceskatelevize.ivysilani.tvapp", + "androidName": "cz.ceskatelevize.ivysilani.tvapp", + "adbLaunchCommand": "adb shell am start -n cz.ceskatelevize.ivysilani.tvapp/.MainActivity", + }, + "onn": { + "appName": "cz.ceskatelevize.ivysilani.tvapp", + "androidName": "cz.ceskatelevize.ivysilani.tvapp", + "adbLaunchCommand": "adb shell am start -n cz.ceskatelevize.ivysilani.tvapp/.MainActivity", + }, + "xiaomi": { + "appName": "cz.ceskatelevize.ivysilani.tvapp", + "androidName": "cz.ceskatelevize.ivysilani.tvapp", + "adbLaunchCommand": "adb shell am start -n cz.ceskatelevize.ivysilani.tvapp/.MainActivity", + }, + }, + + + "jd-farag": { + "button": '', + "button-round": '', + "friendlyName": "JD Farag", + "className": "jdFaragButton", + "deviceFamily": ["apple-tv", "roku"], + "apple-tv": { + "appName": "JD Farag", + }, + "roku": { + "appName": "JD Farag", + "app-id": 625921, + }, + }, + + + "jellyfin": { + "button": '', + "button-round": '', + "friendlyName": "Jellyfin", + "className": "jellyfinButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Jellyfin", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + "chromecast": { + "appName": "Jellyfin", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + "homatics": { + "appName": "Jellyfin", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + "nvidia-shield": { + "appName": "org.jellyfin.androidtv", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + "onn": { + "appName": "org.jellyfin.androidtv", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + "roku": { + "appName": "Jellyfin", + "app-id": 592369, + }, + "xiaomi": { + "appName": "Jellyfin", + "androidName": "org.jellyfin.androidtv", + "adbLaunchCommand": "adb shell am start -n org.jellyfin.androidtv/.ui.startup.StartupActivity", + }, + }, + + + "JioCinema": { + "button": '', + "button-round": '', + "friendlyName": "JioCinema", + "className": "jioCinemaButton", + "deviceFamily": ["amazon-fire", "homatics"], + "amazon-fire": { + "appName": "Jio Cinema", + "androidName": "com.jio.media.stb.ondemand", + "adbLaunchCommand": "adb shell am start -n com.jio.media.stb.ondemand/com.v18.voot.ui.JVHomeActivity", + }, + "homatics": { + "appName": "Jio Cinema", + "androidName": "com.jio.media.stb.ondemand", + "adbLaunchCommand": "adb shell am start -n com.jio.media.stb.ondemand/com.v18.voot.ui.JVHomeActivity", + }, + }, + + + "joyn": { + "button": '', + "friendlyName": "Joyn", + "className": "joynButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "de.prosiebensat1digital.seventv", + "androidName": "de.prosiebensat1digital.seventv", + "adbLaunchCommand": "adb shell am start -n de.prosiebensat1digital.seventv/de.prosiebensat1digital.seventv.MainActivity", + }, + "apple-tv": { + "appName": "Joyn", + }, + "chromecast": { + "appName": "de.prosiebensat1digital.seventv", + "androidName": "de.prosiebensat1digital.seventv", + "adbLaunchCommand": "adb shell am start -n de.prosiebensat1digital.seventv/de.prosiebensat1digital.seventv.MainActivity", + }, + "homatics": { + "appName": "de.prosiebensat1digital.seventv", + "androidName": "de.prosiebensat1digital.seventv", + "adbLaunchCommand": "adb shell am start -n de.prosiebensat1digital.seventv/de.prosiebensat1digital.seventv.MainActivity", + }, + "nvidia-shield": { + "appName": "de.prosiebensat1digital.seventv", + "androidName": "de.prosiebensat1digital.seventv", + "adbLaunchCommand": "adb shell am start -n de.prosiebensat1digital.seventv/de.prosiebensat1digital.seventv.MainActivity", + }, + "xiaomi": { + "appName": "de.prosiebensat1digital.seventv", + "androidName": "de.prosiebensat1digital.seventv", + "adbLaunchCommand": "adb shell am start -n de.prosiebensat1digital.seventv/de.prosiebensat1digital.seventv.MainActivity", + }, + }, + + + "justwatch-streaming-guide": { + "button": '', + "friendlyName": "JustWatch - Streaming Guide", + "className": "justWatchStreamingGuideButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + "apple-tv": { + "appName": "JustWatchTV", + }, + "chromecast": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + "homatics": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + "nvidia-shield": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + "onn": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + "xiaomi": { + "appName": "com.justwatch.justwatch", + "androidName": "com.justwatch.justwatch", + "adbLaunchCommand": "adb shell am start -n com.justwatch.justwatch/com.justwatch.justwatch.MainActivity", + }, + }, + + + "kayo": { + "button": '', + "friendlyName": "Kayo (AU)", + "className": "kayoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.kayosports.tv", + "androidName": "au.com.kayosports.tv", + "adbLaunchCommand": "adb shell am start -n au.com.kayosports.tv/au.com.foxsports.martian.tv.main.MainActivity", + }, + "apple-tv": { + "appName": "Kayo", + }, + "chromecast": { + "appName": "au.com.kayosports.tv", + "androidName": "au.com.kayosports.tv", + "adbLaunchCommand": "adb shell am start -n au.com.kayosports.tv/au.com.foxsports.martian.tv.main.MainActivity", + }, + "homatics": { + "appName": "au.com.kayosports.tv", + "androidName": "au.com.kayosports.tv", + "adbLaunchCommand": "adb shell am start -n au.com.kayosports.tv/au.com.foxsports.martian.tv.main.MainActivity", + }, + "nvidia-shield": { + "appName": "au.com.kayosports.tv", + "androidName": "au.com.kayosports.tv", + "adbLaunchCommand": "adb shell am start -n au.com.kayosports.tv/au.com.foxsports.martian.tv.main.MainActivity", + }, + "xiaomi": { + "appName": "au.com.kayosports.tv", + "androidName": "au.com.kayosports.tv", + "adbLaunchCommand": "adb shell am start -n au.com.kayosports.tv/au.com.foxsports.martian.tv.main.MainActivity", + }, + }, + + + "kexp-tv-stream": { + "button": '', + "friendlyName": "KEXP TV Stream", + "className": "KEXPTVStreamButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "K E X P", + }, + }, + + + "kinopub": { + "button": '', + "friendlyName": "Kinopub", + "className": "kinopubbutton", + "appName": "com.kinopub", + "androidName": "com.kinopub", + "adbLaunchCommand": "adb shell am start -n com.kinopub/com.kinopub.activity.LaunchActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "КиноПоиск": { + "button": '', + "friendlyName": "КиноПоиск", + "className": "kinopoiskButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + "apple-tv": { + "appName": "Кинопоиск", + }, + "chromecast": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + "homatics": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + "onn": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + "xiaomi": { + "appName": "ru.kinopoisk.tv", + "androidName": "ru.kinopoisk.tv", + "adbLaunchCommand": "adb shell am start -n ru.kinopoisk.tv/.presentation.splash.SplashActivity", + }, + }, + + + "КиноПоиск-Яндекс-tb": { + "button": '', + "friendlyName": "КиноПоиск Яндекс ТВ", + "className": "kinopoiskButton", + "appName": "ru.kinopoisk.yandex.tv", + "androidName": "ru.kinopoisk.yandex.tv", + "deviceFamily": ["xiaomi"], }, + + + "kodi": { + "button": '', + "friendlyName": "Kodi", + "className": "kodiButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + "chromecast": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + "homatics": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + "nvidia-shield": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + "onn": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + "xiaomi": { + "appName": "Kodi", + "androidName": "org.xbmc.kodi", + "adbLaunchCommand": "adb shell am start -n org.xbmc.kodi/.Splash", + }, + }, + + + "kpn-itv": { + "button": '', + "button-round": '', + "friendlyName": "KPN iTV", + "className": "kpniTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "chromecast": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "homatics": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "nvidia-shield": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "onn": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "xiaomi": { + "appName": "com.kpn.kpnandroidtv", + "androidName": "com.kpn.kpnandroidtv", + "adbLaunchCommand": "adb shell am start -n com.kpn.kpnandroidtv/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + }, + + + "lazymedia-deluxe": { + "button": '', + "friendlyName": "LAZYMEDIA DELUXE", + "className": "lazymediaDeluxeButton", + "appName": "com.lazycatsoftware.lmd", + "androidName": "com.lazycatsoftware.lmd", + "adbLaunchCommand": "adb shell am start -n com.lazycatsoftware.lmd/com.lazycatsoftware.lazymediadeluxe.ui.tv.activities.ActivityTvMain", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "legacy-tablo": { + "button": '', + "friendlyName": 'Legacy Tablo', + "className": "legacyTabloButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Tablo", + }, + }, + + + "live-channels": { + "button": 'Live Channels', + "friendlyName": "Live Channels", + "className": "liveChannelsButton", + "appName": "Live Channels", + "androidName": "com.google.android.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.tv/com.android.tv.MainActivity", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "magenta-sport": { + "button": '', + "button-round": '', + "friendlyName": 'Magenta Sport', + "className": "magentaSportButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + "chromecast": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + "homatics": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + "nvidia-shield": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + "onn": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + "xiaomi": { + "appName": "com.telekom.androidtv.sport", + "androidName": "com.telekom.androidtv.sport", + "adbLaunchCommand": "adb shell am start -n com.telekom.androidtv.sport/com.telekom.firetv.sport.ui.launcher.LauncherActivity", + }, + }, + + + "magenta-tv": { + "button": '', + "friendlyName": 'Magenta TV', + "className": "magentaTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "tv.accedo.xdk.dtag.production", + "androidName": "tv.accedo.xdk.dtag.production", + "adbLaunchCommand": "adb shell am start -n de.telekom.magentatv.firetv/de.telekom.magentatv.androidtv.ApplicationMainActivity", + }, + "apple-tv": { + "appName": "MagentaTV", + }, + "chromecast": { + "appName": "Magenta TV", + "androidName": "de.telekom.magentatv.androidtv", + "adbLaunchCommand": "adb shell am start -n de.telekom.magentatv.androidtv/.activities.MainActivity", + }, + "nvidia-shield": { + "appName": "Magenta TV", + "androidName": "de.telekom.magentatv.androidtv", + "adbLaunchCommand": "adb shell am start -n de.telekom.magentatv.androidtv/.activities.MainActivity", + }, + "xiaomi": { + "appName": "Magenta TV", + "androidName": "de.telekom.magentatv.androidtv", + "adbLaunchCommand": "adb shell am start -n de.telekom.magentatv.androidtv/.activities.MainActivity", + }, + }, + + + "magistv": { + "button": '', + "friendlyName": 'MagisTV', + "className": "magisTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "chromecast": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "homatics": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "nvidia-shield": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "onn": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "xiaomi": { + "appName": "com.android.mgstv", + "androidName": "com.android.mgstv", + "adbLaunchCommand": "adb shell am start -n com.android.mgstv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + }, + + + "max": { + "button": '', + "friendlyName": "Max", + "className": "maxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "HBO Max", + "androidName": "com.hbo.hbonow", + "adbLaunchCommand": "adb shell am start -n com.hbo.hbonow/com.wbd.beam.BeamActivity", + }, + "apple-tv": { + "appName": "Max", + }, + "chromecast": { + "appName": "com.wbd.stream", + "androidName": "com.wbd.stream", + "adbLaunchCommand": "adb shell am start -n com.wbd.stream/com.wbd.beam.BeamActivity", + }, + "homatics": { + "appName": "com.wbd.stream", + "androidName": "com.wbd.stream", + "adbLaunchCommand": "adb shell am start -n com.wbd.stream/com.wbd.beam.BeamActivity", + }, + "nvidia-shield": { + "appName": "com.wbd.stream", + "androidName": "com.wbd.stream", + "adbLaunchCommand": "adb shell am start -n com.wbd.stream/com.wbd.beam.BeamActivity", + }, + "onn": { + "appName": "com.wbd.stream", + "androidName": "com.wbd.stream", + "adbLaunchCommand": "adb shell am start -n com.wbd.stream/com.wbd.beam.BeamActivity", + }, + "roku": { + "appName": "Max", + "app-id": 61322, + }, + "xiaomi": { + "appName": "com.wbd.stream", + "androidName": "com.wbd.stream", + "adbLaunchCommand": "adb shell am start -n com.wbd.stream/com.wbd.beam.BeamActivity", + }, + }, + + + "max-player": { + "button": '', + "button-round": '', + "friendlyName": 'MaxPlayer', + "className": "maxPlayerButton", + "appName": "tv.maxplayer.android", + "androidName": "tv.maxplayer.android", + "adbLaunchCommand": "adb shell am start -n tv.maxplayer.android/tv.maxplayer.android.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "mediaset-infinity": { + "button": '', + "friendlyName": "Mediaset Infinity", + "className": "mediasetInfinityButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "it.mediaset.infinitytv", + "androidName": "it.mediaset.infinitytv", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv/it.mediaset.mediasetplay.ctv.MainActivity", + }, + "apple-tv": { + "appName": "Mediaset Infinity", + }, + "chromecast": { + "appName": "it.mediaset.infinitytv", + "androidName": "it.mediaset.infinitytv", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv/it.mediaset.mediasetplay.ctv.MainActivity", + }, + "homatics": { + "appName": "it.mediaset.infinitytv", + "androidName": "it.mediaset.infinitytv", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv/it.mediaset.mediasetplay.ctv.MainActivity", + }, + "nvidia-shield": { + "appName": "it.mediaset.infinitytv", + "androidName": "it.mediaset.infinitytv", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv/it.mediaset.mediasetplay.ctv.MainActivity", + }, + "xiaomi": { + "appName": "it.mediaset.infinitytv", + "androidName": "it.mediaset.infinitytv", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv/it.mediaset.mediasetplay.ctv.MainActivity", + }, + }, + + + "mediaset-infinity-alt": { + "button": '', + "friendlyName": "Mediaset Infinity (alt)", + "className": "mediasetInfinityButton", + "appName": "it.mediaset.infinitytv1", + "androidName": "it.mediaset.infinitytv1", + "adbLaunchCommand": "adb shell am start -n it.mediaset.infinitytv1/it.mediaset.mediasetplay.ctv.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "meo-go": { + "button": '', + "friendlyName": "MEO Go", + "className": "meoGoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.alticelabs.meo.androidtv", + "androidName": "com.alticelabs.meo.androidtv", + "adbLaunchCommand": "adb shell am start -n com.alticelabs.meo.androidtv/com.alticelabs.meo.androidtv.features.splash.ui.SplashActivity", + }, + "apple-tv": { + "appName": "MEO", + }, + "chromecast": { + "appName": "com.alticelabs.meo.androidtv", + "androidName": "com.alticelabs.meo.androidtv", + "adbLaunchCommand": "adb shell am start -n com.alticelabs.meo.androidtv/com.alticelabs.meo.androidtv.features.splash.ui.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.alticelabs.meo.androidtv", + "androidName": "com.alticelabs.meo.androidtv", + "adbLaunchCommand": "adb shell am start -n com.alticelabs.meo.androidtv/com.alticelabs.meo.androidtv.features.splash.ui.SplashActivity", + }, + "xiaomi": { + "appName": "com.alticelabs.meo.androidtv", + "androidName": "com.alticelabs.meo.androidtv", + "adbLaunchCommand": "adb shell am start -n com.alticelabs.meo.androidtv/com.alticelabs.meo.androidtv.features.splash.ui.SplashActivity", + }, + }, + + + "mitele": { + "button": '', + "button-round": '', + "friendlyName": "MiTele", + "className": "miTeleButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + "apple-tv": { + "appName": "MiteleTV", + }, + "chromecast": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + "onn": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "tv.accedo.mitele_xdk", + "androidName": "tv.accedo.mitele_xdk", + "adbLaunchCommand": "adb shell am start -n tv.accedo.mitele_xdk/es.mediaset.apptv.ui.splash.SplashActivity", + }, + }, + + + "mlb": { + "button": '', + "friendlyName": "MLB", + "className": "mlbButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + "apple-tv": { + "appName": "MLB", + }, + "chromecast": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + "homatics": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + "onn": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + "roku": { + "appName": 'MLB', + "app-id": 14, + }, + "xiaomi": { + "appName": "com.bamnetworks.mobile.android.gameday.atbat", + "androidName": "com.bamnetworks.mobile.android.gameday.atbat", + "adbLaunchCommand": "adb shell am start -n com.bamnetworks.mobile.android.gameday.atbat/mlb.atbat.activity.MainActivity", + }, + }, + + + "molotov": { + "button": '', + "button-round": '', + "friendlyName": "Molotov", + "className": "molotovButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "tv.molotov.app", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + "apple-tv": { + "appName": "MolotovTV", + }, + "chromecast": { + "appName": "tv.molotov.app", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + "homatics": { + "appName": "Molotov", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + "nvidia-shield": { + "appName": "tv.molotov.app", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + "onn": { + "appName": "tv.molotov.app", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + "xiaomi": { + "appName": "tv.molotov.app", + "androidName": "tv.molotov.app", + "adbLaunchCommand": "adb shell am start -n tv.molotov.app/tv.molotov.android.main.MainActivity", + }, + }, + + + "moonlight-game-streaming": { + "button": '', + "button-round": '', + "friendlyName": "Moonlight Game Streaming", + "className": "moonlightGameStreamingButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + "apple-tv": { + "appName": "Moonlight", + }, + "chromecast": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + "homatics": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + "nvidia-shield": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + "onn": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + "xiaomi": { + "appName": "com.limelight", + "androidName": "com.limelight", + "adbLaunchCommand": "adb shell am start -n com.limelight/com.limelight.PcView", + }, + }, + + + "movistar-plus": { + "button": '', + "friendlyName": "Movistar Plus+", + "className": "movistarPlusButton", + "appName": "com.movistarplus.androidtv", + "androidName": "com.movistarplus.androidtv", + "adbLaunchCommand": "adb shell am start -n com.movistarplus.androidtv/com.movistarplus.androidtv.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "mrmc": { + "button": '', + "button-round": '', + "friendlyName": "MrMC", + "className": "mrMcButton", + "appName": "tv.mrmc.mrmc", + "androidName": "tv.mrmc.mrmc", + "adbLaunchCommand": "adb shell am start -n tv.mrmc.mrmc/tv.mrmc.mrmc.Main", + "deviceFamily": ["nvidia-shield", "xiaomi"], }, + + + "msg-plus": { + "button": '', + "button-round": '', + "friendlyName": "msg+", + "className": "msgPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + "apple-tv": { + "appName": "MSG+", + }, + "chromecast": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + "homatics": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + "onn": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + "roku": { + "appName": 'MSG+', + "app-id": 691286, + }, + "xiaomi": { + "appName": "com.msgi.msggo", + "androidName": "com.msgi.msggo", + "adbLaunchCommand": "adb shell am start -n com.msgi.msggo/com.msgn.msgplustv.ui.mainactivity.MainActivity", + }, + }, + + + "my5": { + "button": '', + "friendlyName": 'My5', + "className": "my5Button", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + "apple-tv": { + "appName": "My5", + }, + "chromecast": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + "homatics": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + "onn": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + "roku": { + "appName": 'My5', + "app-id": 27424, + }, + "xiaomi": { + "appName": "com.channel5.my5", + "androidName": "com.channel5.my5", + "adbLaunchCommand": "adb shell am start -n com.channel5.my5/.tv.ui.splash.view.SplashActivity", + }, + }, + + + "myCanal": { + "button": '', + "friendlyName": 'my CANAL', + "className": "myCanalButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + }, + "apple-tv": { + "appName": "myCANAL", + }, + "chromecast": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + }, + "homatics": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + "adbLaunchCommand": "adb shell am start -n com.canal.android.canal/com.canal.ui.tv.TvMainActivity", + }, + "nvidia-shield": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + }, + "onn": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + }, + "xiaomi": { + "appName": "com.canal.android.canal", + "androidName": "com.canal.android.canal", + }, + }, + + + "my-family-cinema": { + "button": '', + "friendlyName": 'My Family Cinema', + "className": "myFamilyCinemaButton", + "appName": "com.valor.mfc.droid.tvapp.generic", + "androidName": "com.valor.mfc.droid.tvapp.generic", + "adbLaunchCommand": "adb shell am start -n com.valor.mfc.droid.tvapp.generic/com.cv.media.app.ui.SplashStub", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "national-theatre-at-home": { + "button": '', + "friendlyName": 'National Theatre at Home', + "className": "nationalTheatreAtHomeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + "apple-tv": { + "appName": "National Theatre at Home", + }, + "chromecast": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + "homatics": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + "nvidia-shield": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + "onn": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + "roku": { + "appName": 'National Theatre at Home', + "app-id": 607047, + }, + "xiaomi": { + "appName": "com.ntathome", + "androidName": "com.ntathome", + "adbLaunchCommand": "adb shell am start -n com.ntathome/tv.vhx.tv.home.TvHomeActivity", + }, + }, + + + "nba": { + "button": '', + "friendlyName": 'NBA', + "className": "nbaOnFireTvButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "apple-tv": { + "appName": "NBA", + }, + "chromecast": { + "appName": "com.nbaimd.gametime.nba2011", + "androidName": "com.nbaimd.gametime.nba2011", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011/com.nba.tv.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "com.nbaimd.gametime.nba2011", + "androidName": "com.nbaimd.gametime.nba2011", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011/com.nba.tv.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.nbaimd.gametime.nba2011", + "androidName": "com.nbaimd.gametime.nba2011", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011/com.nba.tv.ui.splash.SplashActivity", + }, + "onn": { + "appName": "com.nbaimd.gametime.nba2011", + "androidName": "com.nbaimd.gametime.nba2011", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011/com.nba.tv.ui.splash.SplashActivity", + }, + "roku": { + "appName": 'NBA', + "app-id": 73249, + }, + "xiaomi": { + "appName": "com.nbaimd.gametime.nba2011", + "androidName": "com.nbaimd.gametime.nba2011", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011/com.nba.tv.ui.splash.SplashActivity", + }, + }, + + + "nba-on-fire-tv": { + "button": '', + "friendlyName": 'NBA on Fire TV', + "className": "nbaOnFireTvButton", + "appName": "com.nbaimd.gametime.nba2011.amazon", + "androidName": "com.nbaimd.gametime.nba2011.amazon", + "adbLaunchCommand": "adb shell am start -n com.nbaimd.gametime.nba2011.amazon/com.nba.tv.ui.splash.SplashActivity", + "deviceFamily": ["amazon-fire"], }, + + + "nbc-news": { + "button": '', + "button-round": '', + "friendlyName": 'NBC News', + "className": "nbcNewsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + "apple-tv": { + "appName": "NBC News", + }, + "chromecast": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + "homatics": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + "nvidia-shield": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + "onn": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + "roku": { + "appName": 'NBC News', + "app-id": 9581, + }, + "xiaomi": { + "appName": "com.zumobi.msnbc", + "androidName": "com.zumobi.msnbc", + "adbLaunchCommand": "adb shell am start -n com.zumobi.msnbc/com.nbc.androidottweb.main.MainActivity", + }, + }, + + + "nbc-sports": { + "button": '', + "friendlyName": 'NBC Sports', + "className": "nbcSportsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.nbcuni.com.nbcsports.liveextra.firetv", + "androidName": "com.nbcuni.com.nbcsports.liveextra.firetv", + "adbLaunchCommand": "adb shell am start -n com.nbcuni.com.nbcsports.liveextra.firetv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + "apple-tv": { + "appName": "NBC Sports", + }, + "chromecast": { + "appName": "com.nbcsports.apps.tv", + "androidName": "com.nbcsports.apps.tv", + "adbLaunchCommand": "adb shell am start -n com.nbcsports.apps.tv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + "homatics": { + "appName": "com.nbcsports.apps.tv", + "androidName": "com.nbcsports.apps.tv", + "adbLaunchCommand": "adb shell am start -n com.nbcsports.apps.tv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + "nvidia-shield": { + "appName": "com.nbcsports.apps.tv", + "androidName": "com.nbcsports.apps.tv", + "adbLaunchCommand": "adb shell am start -n com.nbcsports.apps.tv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + "onn": { + "appName": "com.nbcsports.apps.tv", + "androidName": "com.nbcsports.apps.tv", + "adbLaunchCommand": "adb shell am start -n com.nbcsports.apps.tv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + "roku": { + "appName": 'NBC Sports', + "app-id": 53725, + }, + "xiaomi": { + "appName": "com.nbcsports.apps.tv", + "androidName": "com.nbcsports.apps.tv", + "adbLaunchCommand": "adb shell am start -n com.nbcsports.apps.tv/com.nbc.nbctvapp.ui.main.view.MainActivity", + }, + }, + + + "nebula": { + "button": '', + "friendlyName": "Nebula", + "className": "nebulaButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + "apple-tv": { + "appName": "Nebula", + }, + "chromecast": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + "homatics": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + "nvidia-shield": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + "onn": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + "roku": { + "appName": 'Nebula', + "app-id": 624064, + }, + "xiaomi": { + "appName": "tv.standard.nebula", + "androidName": "tv.standard.nebula", + "adbLaunchCommand": "adb shell am start -n tv.standard.nebula/.tv.features.splash.view.activities.SplashActivity", + }, + }, + + + "netflix": { + "button": '', + "friendlyName": "Netflix", + "className": "netflixButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + "apple-tv": { + "appName": "Netflix", + }, + "chromecast": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + "homatics": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + "nvidia-shield": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + "onn": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + "roku": { + "appName": "Netflix", + "app-id": 12, + }, + "xiaomi": { + "appName": "Netflix", + "androidName": "com.netflix.ninja", + "adbLaunchCommand": "adb shell am start -n com.netflix.ninja/.MainActivity", + }, + }, + + + "news": { + "button": "news", + "friendlyName": "News by Fire TV", + "className": "newsButton", + "appName": "com.amazon.hedwig", + "androidName": "com.amazon.hedwig", + "deviceFamily": ["amazon-fire"],}, + + + "newsmax": { + "button": '', + "friendlyName": "Newsmax", + "className": "newsmaxButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "apple-tv": { + "appName": "Newsmax TV", + }, + "chromecast": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "homatics": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "nvidia-shield": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "onn": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + "roku": { + "appName": 'Newsmax', + "app-id": 24699, + }, + "xiaomi": { + "appName": "com.fli.android.newsmaxapp", + "androidName": "com.fli.android.newsmaxapp", + "adbLaunchCommand": "adb shell am start -n com.fli.android.newsmaxapp/com.twentyfouri.tvbridge.webview.view.WebViewActivity", + }, + }, + + + "nfl": { + "button": '', + "friendlyName": "NFL", + "className": "nflButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + "apple-tv": { + "appName": "NFL", + }, + "chromecast": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + "homatics": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + "onn": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + "roku": { + "appName": 'NFL', + "app-id": 44856, + }, + "xiaomi": { + "appName": "com.gotv.nflgamecenter.us.lite", + "androidName": "com.gotv.nflgamecenter.us.lite", + "adbLaunchCommand": "adb shell am start -n com.gotv.nflgamecenter.us.lite/com.nfl.connected.SplashActivity", + }, + }, + + + "nine-now": { + "button": '', + "friendlyName": "9now", + "className": "nineNowButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.ninenow.ctv", + "androidName": "au.com.ninenow.ctv", + "adbLaunchCommand": "adb shell am start -n au.com.ninenow.ctv/au.com.ninenow.ctv.TvActivity", + }, + "apple-tv": { + "appName": "9Now", + }, + "chromecast": { + "appName": "au.com.ninenow.ctv", + "androidName": "au.com.ninenow.ctv", + "adbLaunchCommand": "adb shell am start -n au.com.ninenow.ctv/au.com.ninenow.ctv.TvActivity", + }, + "homatics": { + "appName": "au.com.ninenow.ctv", + "androidName": "au.com.ninenow.ctv", + "adbLaunchCommand": "adb shell am start -n au.com.ninenow.ctv/au.com.ninenow.ctv.TvActivity", + }, + "nvidia-shield": { + "appName": "au.com.ninenow.ctv", + "androidName": "au.com.ninenow.ctv", + "adbLaunchCommand": "adb shell am start -n au.com.ninenow.ctv/au.com.ninenow.ctv.TvActivity", + }, + "xiaomi": { + "appName": "au.com.ninenow.ctv", + "androidName": "au.com.ninenow.ctv", + "adbLaunchCommand": "adb shell am start -n au.com.ninenow.ctv/au.com.ninenow.ctv.TvActivity", + }, + }, + + + "nlziet": { + "button": '', + "friendlyName": "NLZIET", + "className": "nlzietButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "nl.nlziet", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + "apple-tv": { + "appName": "NLZIET", + }, + "chromecast": { + "appName": "nl.nlziet", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + "homatics": { + "appName": "NLZIET", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + "nvidia-shield": { + "appName": "nl.nlziet", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + "onn": { + "appName": "NLZIET", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + "xiaomi": { + "appName": "nl.nlziet", + "androidName": "nl.nlziet", + "adbLaunchCommand": "adb shell am start -n nl.nlziet/.tv.app.di.tv.InjectActivity", + }, + }, + + + "noovo": { + "button": '', + "button-round": '', + "friendlyName": "noovo", + "className": "noovoButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Noovo", + }, + }, + + + "nordvpn": { + "button": '', + "friendlyName": "Nord VPN", + "className": "nordVPNButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + "apple-tv": { + "appName": "NordVPN", + }, + "chromecast": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + "homatics": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + "nvidia-shield": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + "onn": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + "xiaomi": { + "appName": "com.nordvpn.android", + "androidName": "com.nordvpn.android", + "adbLaunchCommand": "adb shell am start -n com.nordvpn.android/.tv.MainActivity", + }, + }, + + + "norlys-play": { + "button": '', + "friendlyName": "Norlys Play", + "className": "norlysPlayButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Norlys Play", + }, + }, + + + "nostv": { + "button": '', + "friendlyName": "NOSTV", + "className": "nostvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "pt.nostv.tv", + "androidName": "pt.nostv.tv", + "adbLaunchCommand": "adb shell am start -n pt.nostv.tv/pt.nos.iris.online.tv.MainTvActivity", + }, + "apple-tv": { + "appName": "NOS TV", + }, + "chromecast": { + "appName": "pt.nostv.tv", + "androidName": "pt.nostv.tv", + "adbLaunchCommand": "adb shell am start -n pt.nostv.tv/pt.nos.iris.online.tv.MainTvActivity", + }, + "homatics": { + "appName": "pt.nostv.tv", + "androidName": "pt.nostv.tv", + "adbLaunchCommand": "adb shell am start -n pt.nostv.tv/pt.nos.iris.online.tv.MainTvActivity", + }, + "nvidia-shield": { + "appName": "pt.nostv.tv", + "androidName": "pt.nostv.tv", + "adbLaunchCommand": "adb shell am start -n pt.nostv.tv/pt.nos.iris.online.tv.MainTvActivity", + }, + "xiaomi": { + "appName": "pt.nostv.tv", + "androidName": "pt.nostv.tv", + "adbLaunchCommand": "adb shell am start -n pt.nostv.tv/pt.nos.iris.online.tv.MainTvActivity", + }, + }, + + + "nova-video-player": { + "button": '', + "button-round": '', + "friendlyName": "Nova Video Player", + "className": "novaVideoPlayerButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + "chromecast": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + "homatics": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + "nvidia-shield": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + "onn": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + "xiaomi": { + "appName": "org.courville.nova", + "androidName": "org.courville.nova", + "adbLaunchCommand": "adb shell am start -n org.courville.nova/com.archos.mediacenter.video.leanback.MainActivityLeanback", + }, + }, + + + "now-tv": { + "button": '', + "friendlyName": "NOW", + "className": "nowTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "NOW", + "androidName": "com.bskyb.nowtv.beta", + "adbLaunchCommand": "adb shell am start -n com.bskyb.nowtv.beta/sky.wrapper.tv.AmazonMainActivity", + }, + "apple-tv": { + "appName": "NOW", + }, + "roku": { + "appName": 'NOW', + "app-id": 20242, + }, + }, + + + "now-tv-it": { + "button": '', + "friendlyName": "NOW (IT)", + "className": "nowTVButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.nowtv.it", + "androidName": "com.nowtv.it", + "adbLaunchCommand": "adb shell am start -n com.nowtv.it/sky.wrapper.tv.AmazonMainActivity", + }, + }, + + + "npo": { + "button": '', + "friendlyName": "NPO (NL)", + "appName": "NPO", + "className": "npoButton", + "androidName": "nl.uitzendinggemist", + "deviceFamily": ["chromecast", "apple-tv", "nvidia-shield", "onn", "xiaomi"], }, + + + "nrk-tv": { + "button": '', + "friendlyName": "NRK TV", + "className": "nrkTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + "apple-tv": { + "appName": "NRK TV", + }, + "chromecast": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + "homatics": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + "nvidia-shield": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + "onn": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + "xiaomi": { + "appName": "no.nrk.tv", + "androidName": "no.nrk.tv", + "adbLaunchCommand": "adb shell am start -n no.nrk.tv/no.nrk.tv.androidtv.AndroidTvMainActivity", + }, + }, + + + "odeon-vod": { + "button": '', + "friendlyName": 'ODEON VOD', + "className": "odeonVodButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + "chromecast": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + "homatics": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + "onn": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + "xiaomi": { + "appName": "com.tv.odeon", + "androidName": "com.tv.odeon", + "adbLaunchCommand": "adb shell am start -n com.tv.odeon/.ui.splash.SplashScreenActivity", + }, + }, + + + "odido": { + "button": '', + "friendlyName": "Odido", + "className": "odidoButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Odido TV", + }, + }, + + + "okoo": { + "button": '', + "friendlyName": "Okoo dessins animés et vidéos", + "className": "okooButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Okoo", + }, + }, + + + "optimum-tv": { + "button": '', + "friendlyName": 'Optimum TV', + "className": "optimumTVButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Optimum TV", + }, + }, + + + "optus-sport": { + "button": '', + "friendlyName": "Optus Sport (AU)", + "className": "optusSportButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.optus.sport.androidtv", + "androidName": "au.com.optus.sport.androidtv", + "adbLaunchCommand": "adb shell am start -n au.com.optus.sport.androidtv/.MainActivity", + }, + "apple-tv": { + "appName": "Optus Sport", + }, + "chromecast": { + "appName": "au.com.optus.sport.androidtv", + "androidName": "au.com.optus.sport.androidtv", + "adbLaunchCommand": "adb shell am start -n au.com.optus.sport.androidtv/.MainActivity", + }, + "homatics": { + "appName": "au.com.optus.sport.androidtv", + "androidName": "au.com.optus.sport.androidtv", + "adbLaunchCommand": "adb shell am start -n au.com.optus.sport.androidtv/.MainActivity", + }, + "nvidia-shield": { + "appName": "au.com.optus.sport.androidtv", + "androidName": "au.com.optus.sport.androidtv", + "adbLaunchCommand": "adb shell am start -n au.com.optus.sport.androidtv/.MainActivity", + }, + "xiaomi": { + "appName": "au.com.optus.sport.androidtv", + "androidName": "au.com.optus.sport.androidtv", + "adbLaunchCommand": "adb shell am start -n au.com.optus.sport.androidtv/.MainActivity", + }, + }, + + + "orange-tv-fr": { + "button": '', + "friendlyName": "Orange TV (FR)", + "className": "orangeTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.orange.owtv.tv", + "androidName": "com.orange.owtv.tv", + "adbLaunchCommand": "adb shell am start -n com.orange.owtv.tv/com.orange.otvp.SplashScreen", + }, + "chromecast": { + "appName": "com.orange.owtv.tv", + "androidName": "com.orange.owtv.tv", + "adbLaunchCommand": "adb shell am start -n com.orange.owtv.tv/com.orange.otvp.SplashScreen", + }, + "homatics": { + "appName": "com.orange.owtv.tv", + "androidName": "com.orange.owtv.tv", + "adbLaunchCommand": "adb shell am start -n com.orange.owtv.tv/com.orange.otvp.SplashScreen", + }, + "nvidia-shield": { + "appName": "com.orange.owtv.tv", + "androidName": "com.orange.owtv.tv", + "adbLaunchCommand": "adb shell am start -n com.orange.owtv.tv/com.orange.otvp.SplashScreen", + }, + "xiaomi": { + "appName": "com.orange.owtv.tv", + "androidName": "com.orange.owtv.tv", + "adbLaunchCommand": "adb shell am start -n com.orange.owtv.tv/com.orange.otvp.SplashScreen", + }, + }, + + + "ott-navigator": { + "button": '', + "friendlyName": "OTT Navigator", + "className": "ottNavigatorButton", + "appName": "studio.scillarium.ottnavigator", + "androidName": "studio.scillarium.ottnavigator", + "adbLaunchCommand": "adb shell am start -n studio.scillarium.ottnavigator/.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "oqee-by-free": { + "button": '', + "friendlyName": "OQEE by Free (FR)", + "className": "oqeeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "net.oqee.androidtv.store", + "androidName": "net.oqee.androidtv.store", + "adbLaunchCommand": "adb shell am start -n net.oqee.androidtv.store/net.oqee.androidtv.MainActivity", + }, + "apple-tv": { + "appName": "OQEE by Free", + }, + "chromecast": { + "appName": "net.oqee.androidtv.store", + "androidName": "net.oqee.androidtv.store", + "adbLaunchCommand": "adb shell am start -n net.oqee.androidtv.store/net.oqee.androidtv.MainActivity", + }, + "homatics": { + "appName": "net.oqee.androidtv.store", + "androidName": "net.oqee.androidtv.store", + "adbLaunchCommand": "adb shell am start -n net.oqee.androidtv.store/net.oqee.androidtv.MainActivity", + }, + "nvidia-shield": { + "appName": "net.oqee.androidtv.store", + "androidName": "net.oqee.androidtv.store", + "adbLaunchCommand": "adb shell am start -n net.oqee.androidtv.store/net.oqee.androidtv.MainActivity", + }, + "xiaomi": { + "appName": "net.oqee.androidtv.store", + "androidName": "net.oqee.androidtv.store", + "adbLaunchCommand": "adb shell am start -n net.oqee.androidtv.store/net.oqee.androidtv.MainActivity", + }, + }, + + + "orf-on": { + "button": '', + "button-round": '', + "friendlyName": "ORF ON", + "className": "orfOnButton", + "deviceFamily": ["amazon-fire", "apple-tv", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.nousguide.android.tvthek.fire", + "androidName": "com.nousguide.android.tvthek.fire", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.tvthek.fire/com.nousguide.android.orftvthek.MainActivity", + }, + "apple-tv": { + "appName": "ORF ON", + }, + "homatics": { + "appName": "com.nousguide.android.orftvthek", + "androidName": "com.nousguide.android.orftvthek", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.orftvthek/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.nousguide.android.orftvthek", + "androidName": "com.nousguide.android.orftvthek", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.orftvthek/.MainActivity", + }, + "onn": { + "appName": "com.nousguide.android.orftvthek", + "androidName": "com.nousguide.android.orftvthek", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.orftvthek/.MainActivity", + }, + "xiaomi": { + "appName": "com.nousguide.android.orftvthek", + "androidName": "com.nousguide.android.orftvthek", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.orftvthek/.MainActivity", + }, + }, + + + "pandora": { + "button": '', + "friendlyName": "Pandora", + "className": "pandoraButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.pandora.android.gtv", + "androidName": "com.pandora.android.gtv", + }, + "apple-tv": { + "appName": "Pandora", + }, + "chromecast": { + "appName": "com.pandora.android.atv", + "androidName": "com.pandora.android.atv", + "adbLaunchCommand": "adb shell am start -n com.pandora.android.atv/com.pandora.android.MainActivity", + }, + "homatics": { + "appName": "com.pandora.android.atv", + "androidName": "com.pandora.android.atv", + "adbLaunchCommand": "adb shell am start -n com.pandora.android.atv/com.pandora.android.MainActivity", + }, + "nvidia-shield": { + "appName": "com.pandora.android.atv", + "androidName": "com.pandora.android.atv", + "adbLaunchCommand": "adb shell am start -n com.pandora.android.atv/com.pandora.android.MainActivity", + }, + "onn": { + "appName": "Pandora", + "androidName": "com.pandora.android.atv", + "adbLaunchCommand": "adb shell am start -n com.pandora.android.atv/com.pandora.android.MainActivity", + }, + "roku": { + "appName": "Pandora", + "app-id": 28, + }, + "xiaomi": { + "appName": "com.pandora.android.atv", + "androidName": "com.pandora.android.atv", + "adbLaunchCommand": "adb shell am start -n com.pandora.android.atv/com.pandora.android.MainActivity", + }, + }, + + + "paramount-plus": { + "button": '', + "friendlyName": 'Paramount+', + "className": "paramountPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.cbs.ott", + "androidName": "com.cbs.ott", + }, + "apple-tv": { + "appName": "Paramount+", + }, + "chromecast": { + "appName": "com.cbs.ott", + "androidName": "com.cbs.ott", + }, + "homatics": { + "appName": "com.cbs.ott", + "androidName": "com.cbs.ott", + "adbLaunchCommand": "adb shell am start -n com.cbs.ott/com.paramount.android.pplus.features.splash.tv.SplashMediatorActivity", + }, + "nvidia-shield": { + "appName": "com.cbs.ott", + "androidName": "com.cbs.ott", + }, + "onn": { + "appName": "Paramount+", + "androidName": "com.cbs.ott", + }, + "roku": { + "appName": 'Paramount Plus', + "app-id": 31440, + }, + "xiaomi": { + "appName": "com.cbs.ott", + "androidName": "com.cbs.ott", + }, + }, + + + "paramount-plus-de": { + "button": '', + "friendlyName": 'Paramount+ (alt)', + "appName": "com.cbs.ca", + "className": "paramountPlusButton", + "androidName": "com.cbs.ca", + "deviceFamily": ["amazon-fire", "chromecast", "nvidia-shield", "xiaomi"], }, + + + "pathe-thuis": { + "button": '', + "friendlyName": 'Pathé Thuis', + "className": "patheThuisButton", + "deviceFamily": ["apple-tv", "chromecast", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Pathé Thuis", + }, + }, + + + "pbs": { + "button": '', + "friendlyName": 'PBS', + "className": "pbsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/com.pbs.video.tv.ui.home.TvMainActivity", + }, + "apple-tv": { + "appName": "PBS", + }, + "chromecast": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/com.pbs.video.tv.ui.home.TvMainActivity", + }, + "homatics": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/.StartupActivity", + }, + "nvidia-shield": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/com.pbs.video.tv.ui.home.TvMainActivity", + }, + "onn": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/com.pbs.video.tv.ui.home.TvMainActivity", + }, + "roku": { + "appName": 'PBS', + "app-id": 23353, + }, + "xiaomi": { + "appName": "com.pbs.video", + "androidName": "com.pbs.video", + "adbLaunchCommand": "adb shell am start com.pbs.video/com.pbs.video.tv.ui.home.TvMainActivity", + }, + }, + + + "pbs-kids-video": { + "button": '', + "friendlyName": 'PBS KIDS Video', + "className": "pbsKidsVideoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.splash.ui.SplashScreenActivity", + }, + "apple-tv": { + "appName": "PBS KIDS", + }, + "chromecast": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.ui.root.MainActivity", + }, + "homatics": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.ui.root.MainActivity", + }, + "nvidia-shield": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.ui.root.MainActivity", + }, + "onn": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.ui.root.MainActivity", + }, + "roku": { + "appName": 'PBS KIDS', + "app-id": 23333, + }, + "xiaomi": { + "appName": "org.pbskids.video", + "androidName": "org.pbskids.video", + "adbLaunchCommand": "adb shell am start -n org.pbskids.video/.ui.root.MainActivity", + }, + }, + + + "peacock": { + "button": '', + "friendlyName": 'Peacock', + "className": "peacockButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.peacock.peacockfiretv", + "androidName": "com.peacock.peacockfiretv", + "adbLaunchCommand": "adb shell am start com.peacock.peacockfiretv/com.peacock.peacocktv.AmazonMainActivity", + }, + "apple-tv": { + "appName": "Peacock", + }, + "chromecast": { + "appName": "com.peacocktv.peacockandroid", + "androidName": "com.peacocktv.peacockandroid", + "adbLaunchCommand": "adb shell am start com.peacocktv.peacockandroid/com.peacock.peacocktv.GoogleMainActivity", + }, + "homatics": { + "appName": "Peacock", + "androidName": "com.peacocktv.peacockandroid", + "adbLaunchCommand": "adb shell am start com.peacocktv.peacockandroid/com.peacock.peacocktv.GoogleMainActivity", + }, + "nvidia-shield": { + "appName": "com.peacocktv.peacockandroid", + "androidName": "com.peacocktv.peacockandroid", + "adbLaunchCommand": "adb shell am start com.peacocktv.peacockandroid/com.peacock.peacocktv.GoogleMainActivity", + }, + "onn": { + "appName": "com.peacocktv.peacockandroid", + "androidName": "com.peacocktv.peacockandroid", + "adbLaunchCommand": "adb shell am start com.peacocktv.peacockandroid/com.peacock.peacocktv.GoogleMainActivity", + }, + "roku": { + "appName": 'Peacock TV', + "app-id": 593099, + }, + "xiaomi": { + "appName": "com.peacocktv.peacockandroid", + "androidName": "com.peacocktv.peacockandroid", + "adbLaunchCommand": "adb shell am start com.peacocktv.peacockandroid/com.peacock.peacocktv.GoogleMainActivity", + }, + }, + + + "peivo-media": { + "button": '', + "friendlyName": 'Peivo Media', + "className": "peivoMediaButton", + "deviceFamily": ["roku"], + "roku": { + "appName": 'Peivo Media', + "app-id": 649029, + }, + }, + + + "philo": { + "button": '', + "friendlyName": 'Philo', + "className": "philoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.philo.philo", + "androidName": "com.philo.philo", + "adbLaunchCommand": "adb shell am start com.philo.philo/.app.activity.MainActivity", + }, + "apple-tv": { + "appName": "Philo", + }, + "chromecast": { + "appName": "com.philo.philo.google", + "androidName": "com.philo.philo.google", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.philo.philo.google/com.philo.philo.app.activity.MainActivity", + }, + "homatics": { + "appName": "com.philo.philo.google", + "androidName": "com.philo.philo.google", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.philo.philo.google/com.philo.philo.app.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.philo.philo.google", + "androidName": "com.philo.philo.google", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.philo.philo.google/com.philo.philo.app.activity.MainActivity", + }, + "onn": { + "appName": "com.philo.philo.google", + "androidName": "com.philo.philo.google", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.philo.philo.google/com.philo.philo.app.activity.MainActivity", + }, + "roku": { + "appName": 'Philo', + "app-id": 196460, + }, + "xiaomi": { + "appName": "com.philo.philo.google", + "androidName": "com.philo.philo.google", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.philo.philo.google/com.philo.philo.app.activity.MainActivity", + }, + }, + + + "planetapl-tv": { + "button": '', + "button-round": '', + "friendlyName": "PlanetaPL TV", + "className": "planetaPlTvButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + "chromecast": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + "homatics": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + "nvidia-shield": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + "onn": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + "xiaomi": { + "appName": "com.planetapl.tv.PlayerCW", + "androidName": "com.planetapl.tv.PlayerCW", + "adbLaunchCommand": "adb shell am start -n com.planetapl.tv.PlayerCW/com.planetapl.tv.basePlayer.MainActivity", + }, + }, + + + "plex": { + "button": '', + "friendlyName": "plex", + "className": "plexButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Plex", + "androidName": "com.plexapp.android", + "adbLaunchCommand": "adb shell am start -n com.plexapp.android/com.plexapp.plex.activities.SplashActivity", + }, + "apple-tv": { + "appName": "Plex", + }, + "chromecast": { + "appName": "Plex", + "androidName": "com.plexapp.android", + }, + "homatics": { + "appName": "Plex", + "androidName": "com.plexapp.android", + "adbLaunchCommand": "adb shell am start -n com.plexapp.android/com.plexapp.plex.activities.SplashActivity", + }, + "nvidia-shield": { + "appName": "Plex", + "androidName": "com.plexapp.android", + }, + "onn": { + "appName": "Plex", + "androidName": "com.plexapp.android", + }, + "roku": { + "appName": 'Plex - Free Movies & TV', + "app-id": 13535, + }, + "xiaomi": { + "appName": "Plex", + "androidName": "com.plexapp.android", + }, + }, + + + "pluto-tv": { + "button": '', + "friendlyName": "Pluto TV", + "className": "plutoTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + "apple-tv": { + "appName": "Pluto TV", + }, + "chromecast": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + "homatics": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + "nvidia-shield": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + "onn": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + "roku": { + "appName": "Pluto TV - It's Free TV", + "app-id": 74519, + }, + "xiaomi": { + "appName": "tv.pluto.android", + "androidName": "tv.pluto.android", + "adbLaunchCommand": "adb shell am start tv.pluto.android/.EntryPoint", + }, + }, + + + "poda-tv": { + "button": '', + "friendlyName": "PODA.tv", + "className": "podaTVButton", + "appName": "tv.poda.tv", + "androidName": "tv.poda.tv", + "adbLaunchCommand": "adb shell am start tv.poda.tv/cz.sledovanitv.androidtv.entry.EntryActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "prime-video" : { + "button": '', + "friendlyName": "Prime Video", + "className": "primeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Prime Video (FireTV)", + "androidName": "com.amazon.avod", + "androidName2": "com.amazon.firebat", + "adbLaunchCommand": "adb shell am start com.amazon.firebat/com.amazon.firebatcore.deeplink.DeepLinkRoutingActivity", + }, + "apple-tv": { + "appName": "Prime Video", + }, + "chromecast": { + "appName": "Prime Video", + "androidName": "com.amazon.amazonvideo.livingroom", + "adbLaunchCommand": "adb shell am start com.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivity", + }, + "homatics": { + "appName": "Prime Video", + "androidName": "com.amazon.amazonvideo.livingroom", + "adbLaunchCommand": "adb shell am start com.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivity", + }, + "nvidia-shield": { + "appName": "Prime Video", + "androidName": "com.amazon.amazonvideo.livingroom", + "adbLaunchCommand": "adb shell am start com.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivity", + }, + "onn": { + "appName": "Prime Video", + "androidName": "com.amazon.amazonvideo.livingroom", + "adbLaunchCommand": "adb shell am start com.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivity", + }, + "roku": { + "appName": "Prime Video", + "app-id": 13, + }, + "xiaomi": { + "appName": "Prime Video", + "androidName": "com.amazon.amazonvideo.livingroom", + "adbLaunchCommand": "adb shell am start com.amazon.amazonvideo.livingroom/com.amazon.ignition.IgnitionActivity", + }, + }, + + + "private-internet-access": { + "button": '', + "friendlyName": "Private Internet Access", + "className": "privateInternetAccessButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + "apple-tv": { + "appName": "PIA VPN", + }, + "chromecast": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + "homatics": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + "nvidia-shield": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + "onn": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + "xiaomi": { + "appName": "com.privateinternetaccess.android", + "androidName": "com.privateinternetaccess.android", + }, + }, + + + "proximus-pickx": { + "button": '', + "friendlyName": "Proximus Pickx", + "appName": "Proximus Pickx", + "className": "proximusPickxButton", + "androidName": "", + "deviceFamily": ["apple-tv"], }, + + + "purple-cheetah": { + "button": '', + "friendlyName": "Purple Cheetah", + "className": "purpleCheetahButton", + "appName": "com.purple.cheetah.player", + "androidName": "com.purple.cheetah.player", + "adbLaunchCommand": "adb shell am start com.purple.cheetah.player/com.purpleplayer.iptv.android.activities.SplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "qmusic-be": { + "button": '', + "friendlyName": "Qmusic-BE", + "className": "qmusicButton", + "appName": "Qmusic-BE", + "deviceFamily": ["apple-tv"], + }, + + + "radio-paradise": { + "button": '', + "friendlyName": "Radio Paradise", + "className": "radioParadiseButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.earthflare.anddroid.radioparadisewidget", + "androidName": "com.earthflare.anddroid.radioparadisewidget", + "adbLaunchCommand": "adb shell am start -n com.earthflare.anddroid.radioparadisewidget/com.cratorsoft.act.ActRPHD_", + }, + "apple-tv": { + "appName": "Radio Paradise Mk2", + }, + "chromecast": { + "appName": "com.earthflare.android.radioparadisewidget.gpv2", + "androidName": "com.earthflare.android.radioparadisewidget.gpv2", + "adbLaunchCommand": "adb shell am start -n com.earthflare.android.radioparadisewidget.gpv2/com.cratorsoft.act.ActRPHD_", + }, + "homatics": { + "appName": "com.earthflare.android.radioparadisewidget.gpv2", + "androidName": "com.earthflare.android.radioparadisewidget.gpv2", + "adbLaunchCommand": "adb shell am start -n com.earthflare.android.radioparadisewidget.gpv2/com.cratorsoft.act.ActRPHD_", + }, + "nvidia-shield": { + "appName": "com.earthflare.android.radioparadisewidget.gpv2", + "androidName": "com.earthflare.android.radioparadisewidget.gpv2", + "adbLaunchCommand": "adb shell am start -n com.earthflare.android.radioparadisewidget.gpv2/com.cratorsoft.act.ActRPHD_", + }, + "onn": { + "appName": "RP", + "androidName": "com.earthflare.android.radioparadisewidget.gpv2", + "adbLaunchCommand": "adb shell am start -n com.earthflare.android.radioparadisewidget.gpv2/com.cratorsoft.act.ActRPHD_", + }, + "roku": { + "appName": 'Radio Paradise', + "app-id": 1619, + }, + "xiaomi": { + "appName": "com.earthflare.android.radioparadisewidget.gpv2", + "androidName": "com.earthflare.android.radioparadisewidget.gpv2", + "adbLaunchCommand": "adb shell am start -n com.earthflare.android.radioparadisewidget.gpv2/com.cratorsoft.act.ActRPHD_", + }, + }, + + + "radioplayer-uk": { + "button": '', + "button-round": '', + "friendlyName": "Radioplayer (UK)", + "className": "radioPlayerUKButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + "chromecast": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + "homatics": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + "nvidia-shield": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + "onn": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + "xiaomi": { + "appName": "uk.co.radioplayer", + "androidName": "uk.co.radioplayer", + "adbLaunchCommand": "adb shell am start -n uk.co.radioplayer/.IntroActivity", + }, + }, + + + "raiplay": { + "button": '', + "friendlyName": "RaiPlay (IT)", + "className": "raiPlayButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + "apple-tv": { + "appName": "RaiPlay", + }, + "chromecast": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + "homatics": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + "nvidia-shield": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + "onn": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + "xiaomi": { + "appName": "it.rainet.androidtv", + "androidName": "it.rainet.androidtv", + "adbLaunchCommand": "adb shell am start -n it.rainet.androidtv/.ui.MainLeanbackActivity", + }, + }, + + + "red-bull-tv": { + "button": '', + "friendlyName": "Red Bull TV", + "className": "redBullTvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.redbull.rbtv", + "androidName": "com.redbull.rbtv", + "adbLaunchCommand": "adb shell am start -n com.redbull.rbtv/com.redbull.launch.SplashActivity", + }, + "apple-tv": { + "appName": "Red Bull TV", + }, + "chromecast": { + "appName": "com.nousguide.android.rbtv", + "androidName": "com.nousguide.android.rbtv", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.rbtv/com.redbull.launch.SplashActivity", + }, + "homatics": { + "appName": "com.nousguide.android.rbtv", + "androidName": "com.nousguide.android.rbtv", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.rbtv/com.redbull.launch.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.nousguide.android.rbtv", + "androidName": "com.nousguide.android.rbtv", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.rbtv/com.redbull.launch.SplashActivity", + }, + "onn": { + "appName": "com.nousguide.android.rbtv", + "androidName": "com.nousguide.android.rbtv", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.rbtv/com.redbull.launch.SplashActivity", + }, + "roku": { + "appName": 'Red Bull TV', + "app-id": 51320, + }, + "xiaomi": { + "appName": "com.nousguide.android.rbtv", + "androidName": "com.nousguide.android.rbtv", + "adbLaunchCommand": "adb shell am start -n com.nousguide.android.rbtv/com.redbull.launch.SplashActivity", + }, + }, + + + "redplay": { + "button": '', + "friendlyName": "RedPlay Live", + "className": "redPlayButton", + "appName": "com.mm.droid.livetv.redplaybox", + "androidName": "com.mm.droid.livetv.redplaybox", + "adbLaunchCommand": "adb shell am start -n com.mm.droid.livetv.redplaybox/com.mm.droid.livetv.load.LiveLoadActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "retroarch": { + "button": '', + "friendlyName": "RetroArch", + "className": "retroArchButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + "apple-tv": { + "appName": "RetroArch", + }, + "chromecast": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + "homatics": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + "nvidia-shield": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + "onn": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + "xiaomi": { + "appName": "com.retroarch", + "androidName": "com.retroarch", + "adbLaunchCommand": "adb shell am start -n com.retroarch/.browser.mainmenu.MainMenuActivity", + }, + }, + + + "rocket-launcher-btn": { + "button": '', + "friendlyName": "Rocket App Launcher", + "className": "rocketButton", + "deviceFamily": ["homatics"], + "homatics": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_BUTTON_16", + }, + }, + + + "roku-channel": { + "button": '', + "button-round": '', + "friendlyName": "Roku Channel", + "className": "rokuChannelButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + "chromecast": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + "homatics": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + "nvidia-shield": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + "onn": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + "roku": { + "appName": 'The Roku Channel', + "app-id": 151908, + }, + "xiaomi": { + "appName": "com.roku.web.trc", + "androidName": "com.roku.web.trc", + "adbLaunchCommand": "adb shell am start -n com.roku.web.trc/com.roku.web.trc.MainActivity", + }, + }, + + + "rtl-play-for-tv": { + "button": '', + "friendlyName": "RTLPlay for TV", + "className": "rtlPlayForTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + "apple-tv": { + "appName": "RTL Play", + }, + "chromecast": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + "homatics": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + "nvidia-shield": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + "onn": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + "xiaomi": { + "appName": "lu.rtl.newmedia.rtlplaytv", + "androidName": "lu.rtl.newmedia.rtlplaytv", + "adbLaunchCommand": "adb shell am start -n lu.rtl.newmedia.rtlplaytv/.MainActivity", + }, + }, + + + "rtl-plus": { + "button": '', + "friendlyName": "RTL+", + "className": "rtlPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "de.cbc.tvnow.firetv", + "androidName": "de.cbc.tvnow.firetv", + "adbLaunchCommand": "adb shell am start -n de.cbc.tvnow.firetv/de.rtli.everest.activity.MainActivity", + }, + "apple-tv": { + "appName": "RTL+", + }, + "chromecast": { + "appName": "de.rtli.tvnow", + "androidName": "de.rtli.tvnow", + "adbLaunchCommand": "adb shell am start -n de.rtli.tvnow/de.rtli.everest.activity.MainActivity", + }, + "homatics": { + "appName": "de.rtli.tvnow", + "androidName": "de.rtli.tvnow", + "adbLaunchCommand": "adb shell am start -n de.rtli.tvnow/de.rtli.everest.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "de.rtli.tvnow", + "androidName": "de.rtli.tvnow", + "adbLaunchCommand": "adb shell am start -n de.rtli.tvnow/de.rtli.everest.activity.MainActivity", + }, + "xiaomi": { + "appName": "de.rtli.tvnow", + "androidName": "de.rtli.tvnow", + "adbLaunchCommand": "adb shell am start -n de.rtli.tvnow/de.rtli.everest.activity.MainActivity", + }, + }, + + + "rtl-plus-hungary": { + "button": '', + "friendlyName": "RTL+ Magyarország", + "className": "rtlPlusHungaryButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + }, + "chromecast": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n hu.telekomnewmedia.android.rtlmost/fr.m6.m6replay.tv.activity.SplashActivity", + }, + "homatics": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n hu.telekomnewmedia.android.rtlmost/fr.m6.m6replay.tv.activity.SplashActivity", + }, + "nvidia-shield": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n hu.telekomnewmedia.android.rtlmost/fr.m6.m6replay.tv.activity.SplashActivity", + }, + "onn": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n hu.telekomnewmedia.android.rtlmost/fr.m6.m6replay.tv.activity.SplashActivity", + }, + "xiaomi": { + "appName": "hu.telekomnewmedia.android.rtlmost", + "androidName": "hu.telekomnewmedia.android.rtlmost", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n hu.telekomnewmedia.android.rtlmost/fr.m6.m6replay.tv.activity.SplashActivity", + }, + }, + + + "rumble": { + "button": '', + "button-round": '', + "friendlyName": "rumble", + "className": "rumbleButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.rumble.firetv", + "androidName": "com.rumble.firetv", + "adbLaunchCommand": "adb shell am start com.rumble.firetv/com.rumble.firetv.LauncherActivity", + }, + "apple-tv": { + "appName": "Rumble", + }, + "chromecast": { + "appName": "com.rumble.battles", + "androidName": "com.rumble.battles", + "adbLaunchCommand": "adb shell am start com.rumble.battles/com.rumble.battles.LauncherActivity", + }, + "homatics": { + "appName": "com.rumble.battles", + "androidName": "com.rumble.battles", + "adbLaunchCommand": "adb shell am start -n com.rumble.battles/com.rumble.battles.LauncherActivity", + }, + "nvidia-shield": { + "appName": "com.rumble.battles", + "androidName": "com.rumble.battles", + "adbLaunchCommand": "adb shell am start com.rumble.battles/com.rumble.battles.LauncherActivity", + }, + "onn": { + "appName": "com.rumble.battles", + "androidName": "com.rumble.battles", + "adbLaunchCommand": "adb shell am start com.rumble.battles/com.rumble.battles.LauncherActivity", + }, + "roku": { + "appName": 'Rumble', + "app-id": 278623, + }, + "xiaomi": { + "appName": "com.rumble.battles", + "androidName": "com.rumble.battles", + "adbLaunchCommand": "adb shell am start com.rumble.battles/com.rumble.battles.LauncherActivity", + }, + }, + + + "s0und-tv": { + "button": '', + "friendlyName": "S0undTV", + "className": "s0undTVButton", + "appName": "com.s0und.s0undtv", + "androidName": "com.s0und.s0undtv", + "adbLaunchCommand": "adb shell am start -n com.s0und.s0undtv/com.s0und.s0undtv.activities.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "salt-tv": { + "button": '', + "friendlyName": "Salt.tv", + "className": "saltTvButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Salt TV", + }, + }, + + + "sbs-on-demand": { + "button": '', + "button-round": '', + "friendlyName": "SBS On Demand (AU)", + "className": "sbsOnDemandButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.sbs.ondemand.tv", + "androidName": "com.sbs.ondemand.tv", + "adbLaunchCommand": "adb shell am start com.sbs.ondemand.tv/.MainActivity", + }, + "apple-tv": { + "appName": "SBS On Demand", + }, + "chromecast": { + "appName": "com.sbs.ondemand.tv", + "androidName": "com.sbs.ondemand.tv", + "adbLaunchCommand": "adb shell am start com.sbs.ondemand.tv/.MainActivity", + }, + "homatics": { + "appName": "com.sbs.ondemand.tv", + "androidName": "com.sbs.ondemand.tv", + "adbLaunchCommand": "adb shell am start com.sbs.ondemand.tv/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.sbs.ondemand.tv", + "androidName": "com.sbs.ondemand.tv", + "adbLaunchCommand": "adb shell am start com.sbs.ondemand.tv/.MainActivity", + }, + "xiaomi": { + "appName": "com.sbs.ondemand.tv", + "androidName": "com.sbs.ondemand.tv", + "adbLaunchCommand": "adb shell am start com.sbs.ondemand.tv/.MainActivity", + }, + }, + + + "screencloud": { + "button": '', + "button-round": '', + "friendlyName": "ScreenCloud", + "className": "screenCloudButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + "chromecast": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + "homatics": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + "nvidia-shield": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + "onn": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + "xiaomi": { + "appName": "io.screencloud.player", + "androidName": "io.screencloud.player", + "adbLaunchCommand": "adb shell am start io.screencloud.player/.MainActivity", + }, + }, + + + "seasons4u": { + "button": '', + "button-round": '', + "friendlyName": "Seasons4U", + "className": "seasons4UButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + "chromecast": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + "homatics": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + "nvidia-shield": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + "onn": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + "xiaomi": { + "appName": "com.android.s4u", + "androidName": "com.android.s4u", + "adbLaunchCommand": "adb shell am start com.android.s4u/com.android.s4u.ui.MainActivity", + }, + }, + + + "seven-plus": { + "button": '', + "friendlyName": "7plus", + "className": "sevenPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + "apple-tv": { + "appName": "7plus", + }, + "chromecast": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + "homatics": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + "nvidia-shield": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + "onn": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + "xiaomi": { + "appName": "com.swm.live", + "androidName": "com.swm.live", + "adbLaunchCommand": "adb shell am start -n com.swm.live/au.com.seven.inferno.ui.tv.setup.SetupTvActivity", + }, + }, + + + "seznam": { + "button": '', + "friendlyName": "Seznam.cz", + "className": "seznamButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "cz.seznam.seznam", + "androidName": "cz.seznam.seznam", + "adbLaunchCommand": "adb shell am start -n cz.seznam.seznam/cz.seznam.seznam.MainActivity", + }, + "homatics": { + "appName": "cz.seznam.seznam", + "androidName": "cz.seznam.seznam", + "adbLaunchCommand": "adb shell am start -n cz.seznam.seznam/cz.seznam.seznam.MainActivity", + }, + "nvidia-shield": { + "appName": "cz.seznam.seznam", + "androidName": "cz.seznam.seznam", + "adbLaunchCommand": "adb shell am start -n cz.seznam.seznam/cz.seznam.seznam.MainActivity", + }, + "onn": { + "appName": "cz.seznam.seznam", + "androidName": "cz.seznam.seznam", + "adbLaunchCommand": "adb shell am start -n cz.seznam.seznam/cz.seznam.seznam.MainActivity", + }, + "xiaomi": { + "appName": "cz.seznam.seznam", + "androidName": "cz.seznam.seznam", + "adbLaunchCommand": "adb shell am start -n cz.seznam.seznam/cz.seznam.seznam.MainActivity", + }, + }, + + + "shahid": { + "button": '', + "friendlyName": "Shahid", + "className": "shahidButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + "apple-tv": { + "appName": "Shahid", + }, + "chromecast": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + "homatics": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + "nvidia-shield": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + "onn": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + "xiaomi": { + "appName": "net.mbc.shahidTV", + "androidName": "net.mbc.shahidTV", + "adbLaunchCommand": "adb shell am start -n net.mbc.shahidTV/net.mbc.shahidTV.MainActivity", + }, + }, + + + "shophq": { + "button": "ShopHQ", + "friendlyName": "ShopHQ", + "className": "shopHQButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "com.amazon.rialto.cordova.webapp.webappb656e5788fd9475ea16e928d2c034d68", + "androidName": "com.amazon.rialto.cordova.webapp.webappb656e5788fd9475ea16e928d2c034d68", + "adbLaunchCommand": "adb shell am start -n com.amazon.rialto.cordova.webapp.webappb656e5788fd9475ea16e928d2c034d68/.MainActivity", + }, + "apple-tv": { + "appName": "ShopHQ", + }, + "roku": { + "appName": "ShopHQ", + "app-id": 17996, + }, + }, + + + "showtime": { + "button": '', + "friendlyName": "Showtime", + "className": "showtimeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "apple-tv": { + "appName": "Showtime", + }, + "chromecast": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "homatics": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "nvidia-shield": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "onn": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "roku": { + "appName": 'SHOWTIME', + "app-id": 8838, + }, + "xiaomi": { + "appName": "com.showtime.standalone", + "androidName": "com.showtime.standalone", + "adbLaunchCommand": "adb shell am start -n com.showtime.standalone/com.showtime.showtimeanytime.activities.IntroActivity", + }, + }, + + + "showtime-anytime": { + "button": '', + "friendlyName": "Showtime Anytime", + "className": "showtimeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.showtime.showtimeanytime", + "androidName": "com.showtime.showtimeanytime", + "adbLaunchCommand": "adb shell am start -n com.showtime.showtimeanytime/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "apple-tv": { + "appName": "Showtime Anytime", + }, + "chromecast": { + "appName": "com.showtime.showtimeanytime", + "androidName": "com.showtime.showtimeanytime", + "adbLaunchCommand": "adb shell am start -n com.showtime.showtimeanytime/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "homatics": { + "appName": "com.showtime.showtimeanytime", + "androidName": "com.showtime.showtimeanytime", + "adbLaunchCommand": "adb shell am start -n com.showtime.showtimeanytime/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "nvidia-shield": { + "appName": "com.showtime.showtimeanytime", + "androidName": "com.showtime.showtimeanytime", + "adbLaunchCommand": "adb shell am start -n com.showtime.showtimeanytime/com.showtime.showtimeanytime.activities.IntroActivity", + }, + "roku": { + "appName": 'Showtime Anytime', + "app-id": 38820, + }, + "xiaomi": { + "appName": "com.showtime.showtimeanytime", + "androidName": "com.showtime.showtimeanytime", + "adbLaunchCommand": "adb shell am start -n com.showtime.showtimeanytime/com.showtime.showtimeanytime.activities.IntroActivity", + }, + }, + + + "siriusxm": { + "button": '', + "friendlyName": "SiriusXM", + "className": "siriusXMButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.amazon.rialto.cordova.webapp.webapp7b743ed0e02e48178fb2bf55dbb38517", + "androidName": "com.amazon.rialto.cordova.webapp.webapp7b743ed0e02e48178fb2bf55dbb38517", + "adbLaunchCommand": "adb shell am start -n com.amazon.rialto.cordova.webapp.webapp7b743ed0e02e48178fb2bf55dbb38517/sxmp.app.welcome.WelcomeActivity", + }, + "apple-tv": { + "appName": "SiriusXM Radio for TV", + }, + "chromecast": { + "appName": "com.sirius", + "androidName": "com.sirius", + "adbLaunchCommand": "adb shell am start -n com.sirius/sxmp.app.welcome.WelcomeActivity", + }, + "homatics": { + "appName": "com.sirius", + "androidName": "com.sirius", + "adbLaunchCommand": "adb shell am start -n com.sirius/sxmp.app.welcome.WelcomeActivity", + }, + "nvidia-shield": { + "appName": "com.sirius", + "androidName": "com.sirius", + "adbLaunchCommand": "adb shell am start -n com.sirius/sxmp.app.welcome.WelcomeActivity", + }, + "onn": { + "appName": "com.sirius", + "androidName": "com.sirius", + "adbLaunchCommand": "adb shell am start -n com.sirius/sxmp.app.welcome.WelcomeActivity", + }, + "roku": { + "appName": 'SiriusXM', + "app-id": 38345, + }, + "xiaomi": { + "appName": "com.sirius", + "androidName": "com.sirius", + "adbLaunchCommand": "adb shell am start -n com.sirius/sxmp.app.welcome.WelcomeActivity", + }, + }, + + + "sky-go" : { + "button": '', + "friendlyName": "Sky Go", + "className": "skyGoButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Sky Go", + }, + }, + + + + "sky-news" : { + "button": '', + "friendlyName": "Sky News", + "className": "skyNewsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.onemainstream.skynews.android", + "androidName": "com.onemainstream.skynews.android", + }, + "apple-tv": { + "appName": "Sky News", + }, + "chromecast": { + "appName": "com.sky.news.androidtv", + "androidName": "com.sky.news.androidtv", + }, + "homatics": { + "appName": "com.sky.news.androidtv", + "androidName": "com.sky.news.androidtv", + "adbLaunchCommand": "adb shell am start -n com.sky.news.androidtv/com.easeltv.falconheavy.tv.splash.view.SplashIntroVideoActivity", + }, + "nvidia-shield": { + "appName": "com.sky.news.androidtv", + "androidName": "com.sky.news.androidtv", + }, + "onn": { + "appName": "com.sky.news.androidtv", + "androidName": "com.sky.news.androidtv", + }, + "roku": { + "appName": 'Sky News', + "app-id": 27181, + }, + "xiaomi": { + "appName": "com.sky.news.androidtv", + "androidName": "com.sky.news.androidtv", + }, + }, + + + "sky-plus-br" : { + "button": '', + "friendlyName": "Sky + (BR)", + "className": "skyPlusButton", + "deviceFamily": ["chromecast"], + "chromecast": { + "appName": "br.com.skymais", + "androidName": "br.com.skymais", + }, + }, + + + "sky-showtime": { + "button": '', + "friendlyName": "skySHOWTIME", + "className": "skyShowtimeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "homatics", "nvidia-shield"], + "amazon-fire": { + "appName": "com.skyshowtime.skyshowtime.google", + "androidName": "com.skyshowtime.skyshowtime.google", + "adbLaunchCommand": "adb shell am start -n com.skyshowtime.skyshowtime.google/com.peacock.peacocktv.GoogleMainActivity", + }, + "apple-tv": { + "appName": "SkyShowtime", + }, + "homatics": { + "appName": "com.skyshowtime.skyshowtime.google", + "androidName": "com.skyshowtime.skyshowtime.google", + "adbLaunchCommand": "adb shell am start -n com.skyshowtime.skyshowtime.google/com.peacock.peacocktv.GoogleMainActivity", + }, + "nvidia-shield": { + "appName": "com.skyshowtime.skyshowtime.google", + "androidName": "com.skyshowtime.skyshowtime.google", + "adbLaunchCommand": "adb shell am start -n com.skyshowtime.skyshowtime.google/com.peacock.peacocktv.GoogleMainActivity", + }, +}, + + + "sky-sport-now": { + "button": '', + "button-round": '', + "friendlyName": "Sky Sport Now (NZ)", + "className": "skySportNowButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "nz.co.skytv.fanpass.ced", + "androidName": "nz.co.skytv.fanpass.ced", + "adbLaunchCommand": "adb shell am start -n nz.co.skytv.fanpass.ced/com.dicetv.MainActivity", + }, + "apple-tv": { + "appName": "Sky Sport Now", + }, + "chromecast": { + "appName": "nz.co.skytv.fanpass.ced", + "androidName": "nz.co.skytv.fanpass.ced", + "adbLaunchCommand": "adb shell am start -n nz.co.skytv.fanpass.ced/com.dicetv.MainActivity", + }, + "homatics": { + "appName": "nz.co.skytv.fanpass.ced", + "androidName": "nz.co.skytv.fanpass.ced", + "adbLaunchCommand": "adb shell am start -n nz.co.skytv.fanpass.ced/com.dicetv.MainActivity", + }, + "nvidia-shield": { + "appName": "nz.co.skytv.fanpass.ced", + "androidName": "nz.co.skytv.fanpass.ced", + "adbLaunchCommand": "adb shell am start -n nz.co.skytv.fanpass.ced/com.dicetv.MainActivity", + }, + "xiaomi": { + "appName": "nz.co.skytv.fanpass.ced", + "androidName": "nz.co.skytv.fanpass.ced", + "adbLaunchCommand": "adb shell am start -n nz.co.skytv.fanpass.ced/com.dicetv.MainActivity", + }, +}, + + + "sky-q": { + "button": '', + "friendlyName": "Sky Q", + "className": "skyQButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Sky Q", + }, + }, + + + "skylink-sk": { + "button": '', + "button-round": '', + "friendlyName": "Skylink SK", + "className": "skylinkSKButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Skylink SK", + }, + "chromecast": { + "appName": "nl.streamgroup.skylinksk", + "androidName": "nl.streamgroup.skylinksk", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.skylinksk/tv.solocoo.htmlapp.FullscreenActivity", + }, + "homatics": { + "appName": "nl.streamgroup.skylinksk", + "androidName": "nl.streamgroup.skylinksk", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.skylinksk/tv.solocoo.htmlapp.FullscreenActivity", + }, + "nvidia-shield": { + "appName": "nl.streamgroup.skylinksk", + "androidName": "nl.streamgroup.skylinksk", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.skylinksk/tv.solocoo.htmlapp.FullscreenActivity", + }, + "onn": { + "appName": "nl.streamgroup.skylinksk", + "androidName": "nl.streamgroup.skylinksk", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.skylinksk/tv.solocoo.htmlapp.FullscreenActivity", + }, + "xiaomi": { + "appName": "nl.streamgroup.skylinksk", + "androidName": "nl.streamgroup.skylinksk", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.skylinksk/tv.solocoo.htmlapp.FullscreenActivity", + }, +}, + + + "sling": { + "button": '', + "friendlyName": "Sling", + "className": "slingButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + "apple-tv": { + "appName": "Sling TV", + }, + "chromecast": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + "homatics": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + "nvidia-shield": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + "onn": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + "roku": { + "appName": 'Sling TV - Live Sports, News, Shows + Freestream', + "app-id": 46041, + }, + "xiaomi": { + "appName": "com.sling", + "androidName": "com.sling", + "adbLaunchCommand": "adb shell am start -n com.sling/com.sling.MainActivity", + }, + }, + + + "smart-tube-next": { + "button": '', + "button-round": '', + "friendlyName": "SmartTube", + "className": "smartTubeNextButton", + "appName": "com.teamsmart.videomanager.tv", + "androidName": "com.teamsmart.videomanager.tv", + "adbLaunchCommand": "adb shell am start -n com.teamsmart.videomanager.tv/com.liskovsoft.smartyoutubetv2.tv.ui.main.SplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "smart-tube-next-beta": { + "button": '', + "button-round": '', + "friendlyName": "SmartTube Beta", + "className": "smartTubeNextButton", + "appName": "com.liskovsoft.smarttubetv.beta", + "androidName": "com.liskovsoft.smarttubetv.beta", + "adbLaunchCommand": "adb shell am start com.liskovsoft.smarttubetv.beta", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "smart-tv-client-for-twitch": { + "button": '', + "className": "smartTvClientForTwitchButton", + "friendlyName": "SmartTV Client for Twitch", + "appName": "com.fgl27.twitch", + "androidName": "com.fgl27.twitch", + "adbLaunchCommand": "adb shell am start -n com.fgl27.twitch/com.fgl27.twitch.PlayerActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield","xiaomi"], + }, + + + "smarters-pro": { + "button": '', + "button-round": '', + "friendlyName": "Smarters Pro", + "className": "smartersProButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": 'SmartersPro', + }, + "chromecast": { + "appName": "com.smarterspro.smartersprotv", + "androidName": "com.smarterspro.smartersprotv", + "adbLaunchCommand": "adb shell am start -n com.smarterspro.smartersprotv/.activity.SplashActivity", + }, + "homatics": { + "appName": "com.smarterspro.smartersprotv", + "androidName": "com.smarterspro.smartersprotv", + "adbLaunchCommand": "adb shell am start -n com.smarterspro.smartersprotv/.activity.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.smarterspro.smartersprotv", + "androidName": "com.smarterspro.smartersprotv", + "adbLaunchCommand": "adb shell am start -n com.smarterspro.smartersprotv/.activity.SplashActivity", + }, + "onn": { + "appName": "com.smarterspro.smartersprotv", + "androidName": "com.smarterspro.smartersprotv", + "adbLaunchCommand": "adb shell am start -n com.smarterspro.smartersprotv/.activity.SplashActivity", + }, + "xiaomi": { + "appName": "com.smarterspro.smartersprotv", + "androidName": "com.smarterspro.smartersprotv", + "adbLaunchCommand": "adb shell am start -n com.smarterspro.smartersprotv/.activity.SplashActivity", + }, + }, + + + "snappier-iptv": { + "button": '', + "friendlyName": "Snappier IPTV", + "className": "snappierIPTVButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Snappier IPTV", + }, + }, + + + "somafm": { + "button": '', + "friendlyName": "SomaFM", + "className": "somaFMButton", + "deviceFamily": ["amazon-fire", "roku"], + "amazon-fire": { + "appName": "com.somafm.tv", + "androidName": "com.somafm.tv", + "adbLaunchCommand": "adb shell am start -n com.somafm.tv/tv.somafm.com.somafm.ui.tv.TvBrowseActivity", + }, + "roku": { + "appName": 'SomaFM', + "app-id": 2941, + }, + }, + + + "sony-liv": { + "button": '', + "button-round": '', + "friendlyName": "Sony LIV", + "className": "sonyLivButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "SonyLIV", + "androidName": "com.onemainstream.sonyliv.android", + "adbLaunchCommand": "adb shell am start -n com.onemainstream.sonyliv.android/com.sonyliv.ui.home.HomeActivity", + }, + "apple-tv": { + "appName": "Sony LIV", + }, + "chromecast": { + "appName": "SonyLIV", + "androidName": "com.sonyliv", + "adbLaunchCommand": "adb shell am start -n com.sonyliv/.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "SonyLIV", + "androidName": "com.sonyliv", + "adbLaunchCommand": "adb shell am start -n com.sonyliv/.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "SonyLIV", + "androidName": "com.sonyliv", + "adbLaunchCommand": "adb shell am start -n com.sonyliv/.ui.splash.SplashActivity", + }, + "onn": { + "appName": "SonyLIV", + "androidName": "com.sonyliv", + "adbLaunchCommand": "adb shell am start -n com.sonyliv/.ui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "SonyLIV", + "androidName": "com.sonyliv", + "adbLaunchCommand": "adb shell am start -n com.sonyliv/.ui.splash.SplashActivity", + }, + }, + + + "spacedesk-display": { + "button": '', + "friendlyName": "spacedesk - USB", + "className": "spacedeskDisplayButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + "chromecast": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + "homatics": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + "nvidia-shield": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + "onn": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + "xiaomi": { + "appName": "ph.spacedesk.beta", + "androidName": "ph.spacedesk.beta", + "adbLaunchCommand": "adb shell am start -n ph.spacedesk.beta/ph.spacedesk.httpwww.spacedesk.SAActivityConnect", + }, + }, + + + "sparkle-tv": { + "button": '', + "friendlyName": "Sparkle TV", + "className": "sparkleTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + "chromecast": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + "homatics": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + "nvidia-shield": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + "onn": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + "xiaomi": { + "appName": "se.hedekonsult.sparkle", + "androidName": "se.hedekonsult.sparkle", + "adbLaunchCommand": "adb shell am start -n se.hedekonsult.sparkle/.MainActivity", + }, + }, + + + "spectrum-tv": { + "button": '', + "friendlyName": "Spectrum TV", + "className": "spectrumTVButton", + "deviceFamily": ["apple-tv", "roku"], + "apple-tv": { + "appName": "Spectrum TV", + }, + "roku": { + "appName": 'Spectrum TV', + "app-id": 23048, + }, + }, + + + "sport-tv": { + "button": '', + "button-round": '', + "friendlyName": "sport tv", + "className": "sportTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + "apple-tv": { + "appName": "SPORT TV", + }, + "chromecast": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + "homatics": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + "nvidia-shield": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + "onn": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + "xiaomi": { + "appName": "pt.sporttv.app.androidtv", + "androidName": "pt.sporttv.app.androidtv", + "adbLaunchCommand": "adb shell am start -n pt.sporttv.app.androidtv/pt.sporttv.app.ui.tv.home.HomeTVActivity", + }, + }, + + + "sportsnet": { + "button": '', + "button-round": '', + "friendlyName": "Sportsnet+", + "className": "sportsnetButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.rogers.sportsnet.tv.firetv", + "androidName": "com.rogers.sportsnet.tv.firetv", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.tv.firetv/com.rogers.sportsnet.tv.ui.AppActivity", + }, + "chromecast": { + "appName": "com.rogers.sportsnet.sportsnet", + "androidName": "com.rogers.sportsnet.sportsnet", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.sportsnet/com.rogers.sportsnet.tv.ui.AppActivity", + }, + "homatics": { + "appName": "com.rogers.sportsnet.sportsnet", + "androidName": "com.rogers.sportsnet.sportsnet", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.sportsnet/com.rogers.sportsnet.tv.ui.AppActivity", + }, + "nvidia-shield": { + "appName": "com.rogers.sportsnet.sportsnet", + "androidName": "com.rogers.sportsnet.sportsnet", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.sportsnet/com.rogers.sportsnet.tv.ui.AppActivity", + }, + "onn": { + "appName": "com.rogers.sportsnet.sportsnet", + "androidName": "com.rogers.sportsnet.sportsnet", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.sportsnet/com.rogers.sportsnet.tv.ui.AppActivity", + }, + "xiaomi": { + "appName": "com.rogers.sportsnet.sportsnet", + "androidName": "com.rogers.sportsnet.sportsnet", + "adbLaunchCommand": "adb shell am start -n com.rogers.sportsnet.sportsnet/com.rogers.sportsnet.tv.ui.AppActivity", + }, + }, + + + "spotify": { + "button": '', + "friendlyName": "Spotify", + "className": "spotifyButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.spotify.tv.android", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + "apple-tv": { + "appName": "Spotify", + }, + "chromecast": { + "appName": "com.spotify.tv.android", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + "homatics": { + "appName": "com.spotify.tv.android", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + "nvidia-shield": { + "appName": "com.spotify.tv.android", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + "onn": { + "appName": "Spotify", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + "roku": { + "appName": 'Spotify Music', + "app-id": 22297, + }, + "xiaomi": { + "appName": "com.spotify.tv.android", + "androidName": "com.spotify.tv.android", + "adbLaunchCommand": "adb shell am start -n com.spotify.tv.android/com.spotify.tv.android.SpotifyTVActivity", + }, + }, + + + "stan": { + "button": '', + "friendlyName": "Stan (AU)", + "className": "stanButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.stan.and", + "androidName": "au.com.stan.and", + }, + "apple-tv": { + "appName": "Stan", + }, + "chromecast": { + "appName": "au.com.stan.and", + "androidName": "au.com.stan.and", + }, + "homatics": { + "appName": "au.com.stan.and", + "androidName": "au.com.stan.and", + "adbLaunchCommand": "adb shell am start -n au.com.stan.and/au.com.stan.presentation.tv.splash.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "au.com.stan.and", + "androidName": "au.com.stan.and", + }, + "xiaomi": { + "appName": "au.com.stan.and", + "androidName": "au.com.stan.and", + }, + }, + + + "startup-show": { + "button": '', + "friendlyName": "StartupShow", + "className": "startupShowButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Startup Show", + }, + "chromecast": { + "appName": "tv.startupshow.android", + "androidName": "tv.startupshow.android", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.android/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + "homatics": { + "appName": "tv.startupshow.android", + "androidName": "tv.startupshow.android", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.android/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + "nvidia-shield": { + "appName": "tv.startupshow.android", + "androidName": "tv.startupshow.android", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.android/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + "onn": { + "appName": "tv.startupshow.android", + "androidName": "tv.startupshow.android", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.android/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + "xiaomi": { + "appName": "tv.startupshow.android", + "androidName": "tv.startupshow.android", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.android/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + }, + + + "startup-show-tv": { + "button": '', + "friendlyName": "Startup Show TV", + "className": "startupShowTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.TvSplashActivity", + }, + "chromecast": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "homatics": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "nvidia-shield": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "onn": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + "xiaomi": { + "appName": "tv.startupshow.androidtv", + "androidName": "tv.startupshow.androidtv", + "adbLaunchCommand": "adb shell am start -n tv.startupshow.androidtv/io.nitrix.tvstartupshow.ui.activity.SplashActivity", + }, + }, + + + "starz": { + "button": '', + "friendlyName": "Starz", + "className": "starzButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.starz.starzplay.firetv", + "androidName": "com.starz.starzplay.firetv", + }, + "apple-tv": { + "appName": "STARZ", + }, + "chromecast": { + "appName": "com.bydeluxe.d3.android.program.starz", + "androidName": "com.bydeluxe.d3.android.program.starz", + }, + "homatics": { + "appName": "com.bydeluxe.d3.android.program.starz", + "androidName": "com.bydeluxe.d3.android.program.starz", + "adbLaunchCommand": "adb shell am start -n com.bydeluxe.d3.android.program.starz/com.starz.android.starzcommon.IntegrationActivity", + }, + "nvidia-shield": { + "appName": "com.bydeluxe.d3.android.program.starz", + "androidName": "com.bydeluxe.d3.android.program.starz", + }, + "onn": { + "appName": "com.bydeluxe.d3.android.program.starz", + "androidName": "com.bydeluxe.d3.android.program.starz", + }, + "roku": { + "appName": 'STARZ', + "app-id": 65067, + }, + "xiaomi": { + "appName": "com.bydeluxe.d3.android.program.starz", + "androidName": "com.bydeluxe.d3.android.program.starz", + }, + }, + + + "steam-link": { + "button": '', + "friendlyName": "Steam Link", + "className": "steamLinkButton", + "appName": "Steam Link", + "androidName": "com.valvesoftware.steamlink", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "adbLaunchCommand": "adb shell am start -n com.valvesoftware.steamlink/com.valvesoftware.steamlink.SteamShellActivity", + }, + "homatics": { + "adbLaunchCommand": "adb shell am start -n com.valvesoftware.steamlink/com.valvesoftware.steamlink.SteamShellActivity", + }, + "nvidia-shield": { + "adbLaunchCommand": "adb shell am start -n com.valvesoftware.steamlink/com.valvesoftware.steamlink.SteamShellActivity", + }, + "onn": { + "adbLaunchCommand": "adb shell am start -n com.valvesoftware.steamlink/com.valvesoftware.steamlink.SteamShellActivity", + }, + "xiaomi": { + "adbLaunchCommand": "adb shell am start -n com.valvesoftware.steamlink/com.valvesoftware.steamlink.SteamShellActivity", + }, + }, + + + "sting-tv": { + "button": '', + "friendlyName": "STINGTV / STING+", + "className": "stingTVButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "STINGTV", + }, + "chromecast": { + "appName": "il.co.stingtv.atv", + "androidName": "il.co.stingtv.atv", + "adbLaunchCommand": "adb shell am start -n il.co.stingtv.atv/com.cisco.ivp.atv.client.MainActivity", + }, + "homatics": { + "appName": "il.co.stingtv.atv", + "androidName": "il.co.stingtv.atv", + "adbLaunchCommand": "adb shell am start -n il.co.stingtv.atv/com.cisco.ivp.atv.client.MainActivity", + }, + "nvidia-shield": { + "appName": "il.co.stingtv.atv", + "androidName": "il.co.stingtv.atv", + "adbLaunchCommand": "adb shell am start -n il.co.stingtv.atv/com.cisco.ivp.atv.client.MainActivity", + }, + "onn": { + "appName": "il.co.stingtv.atv", + "androidName": "il.co.stingtv.atv", + "adbLaunchCommand": "adb shell am start -n il.co.stingtv.atv/com.cisco.ivp.atv.client.MainActivity", + }, + "xiaomi": { + "appName": "il.co.stingtv.atv", + "androidName": "il.co.stingtv.atv", + "adbLaunchCommand": "adb shell am start -n il.co.stingtv.atv/com.cisco.ivp.atv.client.MainActivity", + }, + }, + + + "stream-tv": { + "button": '', + "friendlyName": "StreamTV", + "className": "streamTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + "chromecast": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + "homatics": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + "nvidia-shield": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + "onn": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + "roku": { + "appName": 'StreamTV powered by Buckeye', + "app-id": 581139, + }, + "xiaomi": { + "appName": "com.buckeye.tv.platform", + "androidName": "com.buckeye.tv.platform", + "adbLaunchCommand": "adb shell am start -n com.buckeye.tv.platform/com.mobitv.client.connect.mobile.MainShellActivity", + }, + }, + + + "streamz": { + "button": '', + "friendlyName": "streamz (BE)", + "className": "streamzButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "apple-tv": { + "appName": "Streamz", + }, + "chromecast": { + "appName": "be.dpgmedia.streamz", + "androidName": "be.dpgmedia.streamz", + }, + "homatics": { + "appName": "be.dpgmedia.streamz", + "androidName": "be.dpgmedia.streamz", + }, + "nvidia-shield": { + "appName": "be.dpgmedia.streamz", + "androidName": "be.dpgmedia.streamz", + }, + "xiaomi": { + "appName": "be.dpgmedia.streamz", + "androidName": "be.dpgmedia.streamz", + }, + }, + + + "stremio": { + "button": '', + "friendlyName": "Stremio", + "className": "stremioButton", + "appName": "com.stremio.one", + "androidName": "com.stremio.one", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + "chromecast": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + "homatics": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + "nvidia-shield": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + "onn": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + "xiaomi": { + "adbLaunchCommand": "adb shell am start -n com.stremio.one/com.stremio.tv.MainActivity", + }, + }, + + + "strim": { + "button": '', + "friendlyName": "Strim", + "className": "strimButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + "apple-tv": { + "appName": "Strim", + }, + "chromecast": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + "homatics": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + "nvidia-shield": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + "onn": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + "xiaomi": { + "appName": "no.strim.atv", + "androidName": "no.strim.atv", + "adbLaunchCommand": "adb shell am start -n no.strim.atv/no.rikstv.atv.MainActivity", + }, + }, + + + "surfshark-vpn": { + "button": '', + "friendlyName": "Surfshark VPN", + "className": "surfsharkButton", + "appName": "com.surfshark.vpnclient.android", + "androidName": "com.surfshark.vpnclient.android", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "svt-play": { + "button": '', + "friendlyName": "SVT Play", + "className": "svtPlayButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "SVT Play", + }, + "chromecast": { + "appName": "se.svt.android.svtplay", + "androidName": "se.svt.android.svtplay", + "adbLaunchCommand": "adb shell am start -n se.svt.android.svtplay/se.svt.svtplay.ui.tv.MainActivity", + }, + "homatics": { + "appName": "se.svt.android.svtplay", + "androidName": "se.svt.android.svtplay", + "adbLaunchCommand": "adb shell am start -n se.svt.android.svtplay/se.svt.svtplay.ui.tv.MainActivity", + }, + "nvidia-shield": { + "appName": "se.svt.android.svtplay", + "androidName": "se.svt.android.svtplay", + "adbLaunchCommand": "adb shell am start -n se.svt.android.svtplay/se.svt.svtplay.ui.tv.MainActivity", + }, + "onn": { + "appName": "se.svt.android.svtplay", + "androidName": "se.svt.android.svtplay", + "adbLaunchCommand": "adb shell am start -n se.svt.android.svtplay/se.svt.svtplay.ui.tv.MainActivity", + }, + "xiaomi": { + "appName": "se.svt.android.svtplay", + "androidName": "se.svt.android.svtplay", + "adbLaunchCommand": "adb shell am start -n se.svt.android.svtplay/se.svt.svtplay.ui.tv.MainActivity", + }, + }, + + + "swiftfin": { + "button": '', + "friendlyName": "Swiftfin", + "className": "swiftfinButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Jellyfin", + }, + }, + + + "swisscom-blue-tv": { + "button": '', + "friendlyName": 'Swisscom blue TV', + "className": "swisscomblueTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + "apple-tv": { + "appName": "blue TV", + }, + "chromecast": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + "homatics": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + "nvidia-shield": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + "onn": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + "xiaomi": { + "appName": "com.swisscom.tv2", + "androidName": "com.swisscom.tv2", + "adbLaunchCommand": "adb shell am start -n com.swisscom.tv2/swisscom.bluetv.app.ott.activity.MainActivity", + }, + }, + + + "syncler": { + "button": '', + "friendlyName": "SYNCLER", + "className": "synclerButton", + "appName": "com.syncler", + "androidName": "com.syncler", + "adbLaunchCommand": "adb shell am start -n com.syncler/urbanMedia.android.touchDevice.ui.activities.StartUpSplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + }, + + + "syncnext": { + "button": 'Syncnext', + "friendlyName": "Syncnext", + "className": "syncnextButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Syncnext", + }, + }, + + + "synology-photos": { + "button": '', + "friendlyName": "Synology Photos", + "className": "synologyPhotosButton", + "appName": "Synology Photos", + "androidName": "", + "deviceFamily": ["apple-tv"], }, + + + "t2-tv": { + "button": '', + "friendlyName": "T2 TV", + "className": "tTwoTVButton", + "appName": "T-2 TV", + "androidName": "tv.perception.clients.tv.android", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "tablo-tv": { + "button": '', + "friendlyName": "Tablo TV", + "className": "tabloTVButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + }, + "chromecast": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + }, + "homatics": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + }, + "nvidia-shield": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + }, + "onn": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + }, + "roku": { + "appName": 'Tablo TV', + "app-id": 734366, + }, + "xiaomi": { + "appName": "com.nuvyyo.TabloFAST", + "androidName": "com.nuvyyo.TabloFAST", + "adbLaunchCommand": "adb shell am start -n com.nuvyyo.TabloFAST/com.nuvyyo.tablo.LaunchActivity", + } + }, + + + "tailscale": { + "button": '', + "button-round": '', + "friendlyName": "Tailscale", + "className": "tailscaleButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + }, + "apple-tv": { + "appName": "Tailscale", + }, + "chromecast": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + }, + "homatics": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + }, + "nvidia-shield": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + }, + "onn": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + }, + "xiaomi": { + "appName": "com.tailscale.ipn", + "androidName": "com.tailscale.ipn", + "adbLaunchCommand": "adb shell am start -n com.tailscale.ipn/.MainActivity", + } + }, + + + "tbs": { + "button": '', + "friendlyName": "tbs", + "className": "tbsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + "apple-tv": { + "appName": "TBS", + }, + "chromecast": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + "homatics": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + "nvidia-shield": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + "onn": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + "roku": { + "appName": 'Watch TBS', + "app-id": 154149, + }, + "xiaomi": { + "appName": "com.turner.tbs.android.networkapp", + "androidName": "com.turner.tbs.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tbs.android.networkapp/com.wme.app.MainActivityTv", + }, + }, + + + "teleboy-tv": { + "button": '', + "button-round": '', + "friendlyName": "Teleboy TV", + "className": "teleboyTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + "apple-tv": { + "appName": "Teleboy TV", + }, + "chromecast": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + "homatics": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + "onn": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + "xiaomi": { + "appName": "ch.teleboy.androidtv", + "androidName": "ch.teleboy.androidtv", + "adbLaunchCommand": "adb shell am start -n ch.teleboy.androidtv/ch.teleboy.splashscreen.SplashScreenActivity", + }, + }, + + + "telenet-tv": { + "button": '', + "friendlyName": "Telenet TV (BE)", + "className": "telenetTvButton", + "appName": "be.telenet.tv", + "androidName": "be.telenet.tv", + "adbLaunchCommand": "adb shell am start -n be.telenet.tv/com.libertyglobal.horizonx.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "telia-play": { + "button": '', + "friendlyName": "Telia Play", + "className": "teliaPlayButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "Telia Play", + }, + }, + + + "telia-play-sweden": { + "button": '', + "friendlyName": "Telia Play (Sweden)", + "className": "teliaPlayButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + "chromecast": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + "homatics": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + "onn": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + "xiaomi": { + "appName": "com.teliasonera.telia.playplus", + "androidName": "com.teliasonera.telia.playplus", + "adbLaunchCommand": "adb shell am start -n com.teliasonera.telia.playplus/se.telia.teliaplay.androidtv.splash.SplashActivity", + }, + }, + + + "telia-tv-estonia": { + "button": '', + "button-round": '', + "friendlyName": "Telia TV Estonia", + "className": "teliaTvEstoniaButton", + "appName": "ee.telia.teliatv", + "androidName": "ee.telia.teliatv", + "adbLaunchCommand": "adb shell am start -n ee.telia.teliatv/ee.telia.teliatv.activity.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "telus-tv-plus": { + "button": '', + "friendlyName": "TELUS tv+", + "className": "telusTvPlusButton", + "appName": "com.telus.mediaroom.tvx.byod", + "androidName": "com.telus.mediaroom.tvx.byod", + "adbLaunchCommand": "adb shell am start -n com.telus.mediaroom.tvx.byod/tv.threess.threeready.ui.generic.activity.MainActivity", + "deviceFamily": ["chromecast", "nvidia-shield"], }, + + + "ten-play": { + "button": '', + "friendlyName": "10play", + "className": "tenPlayButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "au.com.tenplay", + "androidName": "au.com.tenplay", + "adbLaunchCommand": "adb shell am start -n au.com.tenplay/tv.youi.networktentv.MainActivity", + }, + "apple-tv": { + "appName": "10 play", + }, + "chromecast": { + "appName": "au.com.tenplay", + "androidName": "au.com.tenplay", + "adbLaunchCommand": "adb shell am start -n au.com.tenplay/tv.youi.networktentv.MainActivity", + }, + "nvidia-shield": { + "appName": "au.com.tenplay", + "androidName": "au.com.tenplay", + "adbLaunchCommand": "adb shell am start -n au.com.tenplay/tv.youi.networktentv.MainActivity", + }, + "xiaomi": { + "appName": "au.com.tenplay", + "androidName": "au.com.tenplay", + "adbLaunchCommand": "adb shell am start -n au.com.tenplay/tv.youi.networktentv.MainActivity", + }, + }, + + + "tennis-channel": { + "button": '', + "friendlyName": "Tennis Channel", + "className": "tennisChannelButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "com.tennischannel.tceverywhere.amazon", + "androidName": "com.tennischannel.tceverywhere.amazon", + }, + "apple-tv": { + "appName": "Tennis Channel US", + }, + "roku": { + "appName": 'Tennis Channel', + "app-id": 55268, + }, + }, + + + "testflight": { + "button": '', + "className": "testflightButton", + "friendlyName": "TestFlight", + "appName": "TestFlight", + "deviceFamily": ["apple-tv"], }, + + + "tf1-plus": { + "button": '', + "friendlyName": "TF1+", + "className": "tf1PlusButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + "chromecast": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + "homatics": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + "nvidia-shield": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + "onn": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + "xiaomi": { + "appName": "fr.tf1.mytf1", + "androidName": "fr.tf1.mytf1", + "adbLaunchCommand": "adb shell am start -n fr.tf1.mytf1/fr.tf1.mytf1.MainActivity", + }, + }, + + + "threenow": { + "button": '', + "friendlyName": "Three Now (NZ)", + "className": "threenowButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.mediaworks.android.tv", + "androidName": "com.mediaworks.android.tv", + }, + "apple-tv": { + "appName": "ThreeNow", + }, + "chromecast": { + "appName": "com.mediaworks.android.tv", + "androidName": "com.mediaworks.android.tv", + }, + "homatics": { + "appName": "com.mediaworks.android.tv", + "androidName": "com.mediaworks.android.tv", + }, + "nvidia-shield": { + "appName": "com.mediaworks.android.tv", + "androidName": "com.mediaworks.android.tv", + }, + "xiaomi": { + "appName": "com.mediaworks.android.tv", + "androidName": "com.mediaworks.android.tv", + }, +}, + + + "tidal": { + "button": '', + "friendlyName": "TIDAL", + "className": "tidalButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + "apple-tv": { + "appName": "TIDAL", + }, + "chromecast": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + "homatics": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + "nvidia-shield": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + "onn": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + "roku": { + "appName": 'TIDAL', + "app-id": 294792, + }, + "xiaomi": { + "appName": "com.aspiro.tidal", + "androidName": "com.aspiro.tidal", + "adbLaunchCommand": "adb shell am start -n com.aspiro.tidal/com.aspiro.wamp.tv.TvLauncherActivity", + }, + }, + + + "tinycam-monitor-pro": { + "button": '', + "friendlyName": "tinyCam Monitor PRO", + "className": "tinyCamPROButton", + "deviceFamily": ["nvidia-shield", "onn"], + "nvidia-shield": { + "appName": "com.alexvas.dvr.pro", + "androidName": "com.alexvas.dvr.pro", + "adbLaunchCommand": "adb shell am start -n com.alexvas.dvr.pro/com.alexvas.dvr.activity.LiveViewActivity", + }, + "onn": { + "appName": "com.alexvas.dvr.pro", + "androidName": "com.alexvas.dvr.pro", + "adbLaunchCommand": "adb shell am start -n com.alexvas.dvr.pro/com.alexvas.dvr.activity.LiveViewActivity", + }, + }, + + + "tivimate": { + "button": '', + "friendlyName": "TiviMate IPTV Player", + "className": "tiviMateButton", + "appName": "ar.tvplayer.tv", + "androidName": "ar.tvplayer.tv", + "adbLaunchCommand": "adb shell am start -n ar.tvplayer.tv/ar.tvplayer.tv.ui.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], }, + + + "tivimax-premium": { + "button": '', + "friendlyName": "TiviMax IPTV Player (Premium)", + "className": "tiviMaxPremiumButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "TiviMax", + }, + }, + + + "tnt": { + "button": '', + "friendlyName": "TNT", + "className": "tntButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + "apple-tv": { + "appName": "TNT", + }, + "chromecast": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + "homatics": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + "nvidia-shield": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + "onn": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + "roku": { + "appName": 'Watch TNT', + "app-id": 154157, + }, + "xiaomi": { + "appName": "Watch TNT", + "androidName": "com.turner.tnt.android.networkapp", + "adbLaunchCommand": "adb shell am start -n com.turner.tnt.android.networkapp/com.wme.app.MainActivityTv", + }, + }, + + + "towis-x-pro": { + "button": '', + "friendlyName": "towis X pro", + "className": "towisXProButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.boxbr.xc725T5ban", + "androidName": "com.boxbr.xc725T5ban", + "adbLaunchCommand": "adb shell am start -n com.boxbr.xc725T5ban/com.nathnetwork.orplayer.SplashVideoActivity", + }, + }, + + + "triller-tv": { + "button": '', + "button-round": '', + "friendlyName": "TrillerTV", + "className": "trillerTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + "apple-tv": { + "appName": "TrillerTV", + }, + "chromecast": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + "homatics": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + "nvidia-shield": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + "onn": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + "roku": { + "appName": 'TrillerTV: Live Sports', + "app-id": 174995, + }, + "xiaomi": { + "appName": "com.flipps.fitetv", + "androidName": "com.flipps.fitetv", + "adbLaunchCommand": "adb shell am start -n com.flipps.fitetv/com.bianor.ams.androidtv.activity.TvMainActivity", + }, + }, + + + "tubi": { + "button": '', + "friendlyName": "tubi", + "className": "tubiButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.tubitv.ott", + "androidName": "com.tubitv.ott", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv.ott/com.tubitv.activities.FireTVMainActivity", + }, + "apple-tv": { + "appName": "Tubi", + }, + "chromecast": { + "appName": "com.tubitv", + "androidName": "com.tubitv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv/com.tubitv.activities.MainActivity", + }, + "homatics": { + "appName": "com.tubitv", + "androidName": "com.tubitv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv/com.tubitv.activities.MainActivity", + }, + "nvidia-shield": { + "appName": "com.tubitv", + "androidName": "com.tubitv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv/com.tubitv.activities.MainActivity", + }, + "onn": { + "appName": "com.tubitv", + "androidName": "com.tubitv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv/com.tubitv.activities.MainActivity", + }, + "roku": { + "appName": 'Tubi - Free Movies & TV', + "app-id": 41468, + }, + "xiaomi": { + "appName": "com.tubitv", + "androidName": "com.tubitv", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.tubitv/com.tubitv.activities.MainActivity", + }, + }, + + + "tv-browser": { + "button": '', + "friendlyName": "TV Browser", + "className": "tvBrowserButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "com.altamirano.fabricio.tvbrowser", + "androidName": "com.altamirano.fabricio.tvbrowser", + "adbLaunchCommand": "adb shell am start -n com.altamirano.fabricio.tvbrowser/.ui.activities.SplashActivity", + }, + "homatics": { + "appName": "com.altamirano.fabricio.tvbrowser", + "androidName": "com.altamirano.fabricio.tvbrowser", + "adbLaunchCommand": "adb shell am start -n com.altamirano.fabricio.tvbrowser/.ui.activities.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.altamirano.fabricio.tvbrowser", + "androidName": "com.altamirano.fabricio.tvbrowser", + "adbLaunchCommand": "adb shell am start -n com.altamirano.fabricio.tvbrowser/.ui.activities.SplashActivity", + }, + "onn": { + "appName": "com.altamirano.fabricio.tvbrowser", + "androidName": "com.altamirano.fabricio.tvbrowser", + "adbLaunchCommand": "adb shell am start -n com.altamirano.fabricio.tvbrowser/.ui.activities.SplashActivity", + }, + "xiaomi": { + "appName": "com.altamirano.fabricio.tvbrowser", + "androidName": "com.altamirano.fabricio.tvbrowser", + "adbLaunchCommand": "adb shell am start -n com.altamirano.fabricio.tvbrowser/.ui.activities.SplashActivity", + }, + }, + + + "tv-vlaanderen": { + "button": '', + "button-round": '', + "friendlyName": "TV VLAANDEREN (BE)", + "className": "tvVlaanderenButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "nl.streamgroup.vlaanderen", + "androidName": "nl.streamgroup.vlaanderen", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.vlaanderen/tv.solocoo.htmlapp.FullscreenActivity", + }, + "homatics": { + "appName": "nl.streamgroup.vlaanderen", + "androidName": "nl.streamgroup.vlaanderen", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.vlaanderen/tv.solocoo.htmlapp.FullscreenActivity", + }, + "nvidia-shield": { + "appName": "nl.streamgroup.vlaanderen", + "androidName": "nl.streamgroup.vlaanderen", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.vlaanderen/tv.solocoo.htmlapp.FullscreenActivity", + }, + "onn": { + "appName": "nl.streamgroup.vlaanderen", + "androidName": "nl.streamgroup.vlaanderen", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.vlaanderen/tv.solocoo.htmlapp.FullscreenActivity", + }, + "xiaomi": { + "appName": "nl.streamgroup.vlaanderen", + "androidName": "nl.streamgroup.vlaanderen", + "adbLaunchCommand": "adb shell am start -n nl.streamgroup.vlaanderen/tv.solocoo.htmlapp.FullscreenActivity", + }, + }, + + + "tv-2go": { + "button": '', + "button-round": '', + "friendlyName": "TV-2GO", + "className": "tv2GoButton", + "deviceFamily": ["homatics", "nvidia-shield", "onn", "xiaomi"], + "homatics": { + "appName": "nl.tv2go.tvplay", + "androidName": "nl.tv2go.tvplay", + "adbLaunchCommand": "adb shell am start -n nl.tv2go.tvplay/com.zattoo.tv.TvActivity", + }, + "nvidia-shield": { + "appName": "nl.tv2go.tvplay", + "androidName": "nl.tv2go.tvplay", + "adbLaunchCommand": "adb shell am start -n nl.tv2go.tvplay/com.zattoo.tv.TvActivity", + }, + "onn": { + "appName": "nl.tv2go.tvplay", + "androidName": "nl.tv2go.tvplay", + "adbLaunchCommand": "adb shell am start -n nl.tv2go.tvplay/com.zattoo.tv.TvActivity", + }, + "xiaomi": { + "appName": "nl.tv2go.tvplay", + "androidName": "nl.tv2go.tvplay", + "adbLaunchCommand": "adb shell am start -n nl.tv2go.tvplay/com.zattoo.tv.TvActivity", + }, + }, + + + "tv2-play": { + "button": '', + "friendlyName": "TV 2 Play", + "className": "tv2PlayButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "TV 2 Play", + }, + "chromecast": { + "appName": "dk.tv2.tv2playtv", + "androidName": "dk.tv2.tv2playtv", + "adbLaunchCommand": "adb shell am start -n dk.tv2.tv2playtv/dk.tv2.tv2playtv.main.MainActivity", + }, + "homatics": { + "appName": "dk.tv2.tv2playtv", + "androidName": "dk.tv2.tv2playtv", + "adbLaunchCommand": "adb shell am start -n dk.tv2.tv2playtv/dk.tv2.tv2playtv.main.MainActivity", + }, + "nvidia-shield": { + "appName": "dk.tv2.tv2playtv", + "androidName": "dk.tv2.tv2playtv", + "adbLaunchCommand": "adb shell am start -n dk.tv2.tv2playtv/dk.tv2.tv2playtv.main.MainActivity", + }, + "onn": { + "appName": "dk.tv2.tv2playtv", + "androidName": "dk.tv2.tv2playtv", + "adbLaunchCommand": "adb shell am start -n dk.tv2.tv2playtv/dk.tv2.tv2playtv.main.MainActivity", + }, + "xiaomi": { + "appName": "dk.tv2.tv2playtv", + "androidName": "dk.tv2.tv2playtv", + "adbLaunchCommand": "adb shell am start -n dk.tv2.tv2playtv/dk.tv2.tv2playtv.main.MainActivity", + }, + }, + + + "tv2-play-norway": { + "button": '', + "friendlyName": "TV 2 Play (Norway)", + "className": "tv2PlayNorwayButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "TV 2 Play", + }, + "chromecast": { + "appName": "no.tv2.sumo", + "androidName": "no.tv2.sumo", + "adbLaunchCommand": "adb shell am start -n no.tv2.sumo/no.tv2.android.ui.LauncherBridgeActivity", + }, + "homatics": { + "appName": "no.tv2.sumo", + "androidName": "no.tv2.sumo", + "adbLaunchCommand": "adb shell am start -n no.tv2.sumo/no.tv2.android.ui.LauncherBridgeActivity", + }, + "nvidia-shield": { + "appName": "no.tv2.sumo", + "androidName": "no.tv2.sumo", + "adbLaunchCommand": "adb shell am start -n no.tv2.sumo/no.tv2.android.ui.LauncherBridgeActivity", + }, + "onn": { + "appName": "no.tv2.sumo", + "androidName": "no.tv2.sumo", + "adbLaunchCommand": "adb shell am start -n no.tv2.sumo/no.tv2.android.ui.LauncherBridgeActivity", + }, + "xiaomi": { + "appName": "no.tv2.sumo", + "androidName": "no.tv2.sumo", + "adbLaunchCommand": "adb shell am start -n no.tv2.sumo/no.tv2.android.ui.LauncherBridgeActivity", + }, + }, + + + "tv4-play": { + "button": '', + "friendlyName": "TV4 Play", + "className": "tv4PlayButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + "apple-tv": { + "appName": "TV4 Play", + }, + "chromecast": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + "homatics": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + "nvidia-shield": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + "onn": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + "xiaomi": { + "appName": "se.tv4.tv4playtab", + "androidName": "se.tv4.tv4playtab", + "adbLaunchCommand": "adb shell am start -c android.intent.category.LEANBACK_LAUNCHER -n se.tv4.tv4playtab/se.tv4.tv4play.ui.common.startup.SplashActivity", + }, + }, + + "tver": { + "button": '', + "friendlyName": "TVer", + "className": "tverButton", + "appName": "jp.co.tver.tvapp", + "androidName": "jp.co.tver.tvapp", + "adbLaunchCommand": "adb shell am start -n jp.co.tver.tvapp/.tver.ui.splash.TVerSplashActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"],}, + + + "tvexpress-br": { + "button": '', + "friendlyName": "TVExpress (BR)", + "className": "tvExpressBRButton", + "appName": "com.mm.droid.livetv.tve", + "androidName": "com.mm.droid.livetv.tve", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -n com.mm.droid.livetv.tve/com.mm.droid.livetv.load.LiveLoadActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "tving": { + "button": '', + "friendlyName": "TVING", + "className": "tvingButton", + "appName": "net.cj.em.tving", + "androidName": "net.cj.em.tving", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "tvnz-plus": { + "button": '', + "friendlyName": "TVNZ+ (NZ)", + "className": "tvnzPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "nz.co.tvnz.ondemand.tv", + "androidName": "nz.co.tvnz.ondemand.tv", + "adbLaunchCommand": "adb shell am start -n nz.co.tvnz.ondemand.tv/nz.co.tvnz.ondemand.MainTVActivity", + }, + "apple-tv": { + "appName": "TVNZ+", + }, + "chromecast": { + "appName": "nz.co.tvnz.ondemand.tv", + "androidName": "nz.co.tvnz.ondemand.tv", + "adbLaunchCommand": "adb shell am start -n nz.co.tvnz.ondemand.tv/nz.co.tvnz.ondemand.MainTVActivity", + }, + "homatics": { + "appName": "nz.co.tvnz.ondemand.tv", + "androidName": "nz.co.tvnz.ondemand.tv", + "adbLaunchCommand": "adb shell am start -n nz.co.tvnz.ondemand.tv/nz.co.tvnz.ondemand.MainTVActivity", + }, + "nvidia-shield": { + "appName": "nz.co.tvnz.ondemand.tv", + "androidName": "nz.co.tvnz.ondemand.tv", + "adbLaunchCommand": "adb shell am start -n nz.co.tvnz.ondemand.tv/nz.co.tvnz.ondemand.MainTVActivity", + }, + "xiaomi": { + "appName": "nz.co.tvnz.ondemand.tv", + "androidName": "nz.co.tvnz.ondemand.tv", + "adbLaunchCommand": "adb shell am start -n nz.co.tvnz.ondemand.tv/nz.co.tvnz.ondemand.MainTVActivity", + }, + }, + + "twire": { + "button": '', + "friendlyName": "Twire", + "className": "twireButton", + "appName": "com.perflyst.twire", + "androidName": "com.perflyst.twire", + "adbLaunchCommand": "adb shell am start -a android.intent.action.MAIN -n com.perflyst.twire/.activities.StartUpActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"], }, + + + "twitch": { + "button": '', + "friendlyName": 'Twitch', + "className": "twitchButton", + "adbLaunchCommand": "adb shell am start -n tv.twitch.android.viewer/tv.twitch.starshot64.app.StarshotActivity", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "Twitch (FireTV)", + "androidName": "tv.twitch.android.viewer", + }, + "apple-tv": { + "appName": "Twitch", + }, + "chromecast": { + "appName": "Twitch", + "androidName": "tv.twitch.android.app", + }, + "homatics": { + "appName": "Twitch", + "androidName": "tv.twitch.android.app", + }, + "nvidia-shield": { + "appName": "Twitch", + "androidName": "tv.twitch.android.app", + }, + "onn": { + "appName": "Twitch", + "androidName": "tv.twitch.android.app", + }, + "xiaomi": { + "appName": "Twitch", + "androidName": "tv.twitch.android.app", + }, + }, + + + "u-next": { + "button": '', + "button-round": '', + "friendlyName": "U-NEXT", + "className": "uNextButton", + "appName": "U-NEXT", + "androidName": "com.", + "deviceFamily": ["apple-tv"], }, + + + "uhf": { + "button": '', + "friendlyName": "UHF", + "className": "uhfButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "UHF", + }, + }, + + + "uktv-play": { + "button": '', + "friendlyName": "UKTV Play", + "className": "uktvPlayButton", + "deviceFamily": ["apple-tv", "roku"], + "apple-tv": { + "appName": "UKTV Play", + }, + "roku": { + "appName": 'UKTV Play', + "app-id": 181173, + }, + }, + + + "unifi-protect": { + "button": '', + "friendlyName": "Unifi Protect", + "className": "unifiProtectButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "Protect", + }, + "chromecast": { + "appName": "com.ubnt.unifi.protect", + "androidName": "com.ubnt.unifi.protect", + "adbLaunchCommand": "adb shell am start -n com.ubnt.unifi.protect/com.ubnt.sections.splash.AuthenticationActivity", + }, + "homatics": { + "appName": "com.ubnt.unifi.protect", + "androidName": "com.ubnt.unifi.protect", + "adbLaunchCommand": "adb shell am start -n com.ubnt.unifi.protect/com.ubnt.sections.splash.AuthenticationActivity", + }, + "nvidia-shield": { + "appName": "com.ubnt.unifi.protect", + "androidName": "com.ubnt.unifi.protect", + "adbLaunchCommand": "adb shell am start -n com.ubnt.unifi.protect/com.ubnt.sections.splash.AuthenticationActivity", + }, + "onn": { + "appName": "com.ubnt.unifi.protect", + "androidName": "com.ubnt.unifi.protect", + "adbLaunchCommand": "adb shell am start -n com.ubnt.unifi.protect/com.ubnt.sections.splash.AuthenticationActivity", + }, + "xiaomi": { + "appName": "com.ubnt.unifi.protect", + "androidName": "com.ubnt.unifi.protect", + "adbLaunchCommand": "adb shell am start -n com.ubnt.unifi.protect/com.ubnt.sections.splash.AuthenticationActivity", + }, + }, + + + "viaplay": { + "button": '', + "friendlyName": "Viaplay", + "className": "viaplayButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.viaplay.android", + "androidName": "com.viaplay.android", + "adbLaunchCommand": "adb shell am start -n com.viaplay.android/.features.home.HomeActivity", + }, + "apple-tv": { + "appName": "Viaplay", + }, + "chromecast": { + "appName": "com.viaplay.android", + "androidName": "com.viaplay.android", + "adbLaunchCommand": "adb shell am start -n com.viaplay.android/.features.home.HomeActivity", + }, + "homatics": { + "appName": "com.viaplay.android", + "androidName": "com.viaplay.android", + "adbLaunchCommand": "adb shell am start -n com.viaplay.android/.features.home.HomeActivity", + }, + "nvidia-shield": { + "appName": "com.viaplay.android", + "androidName": "com.viaplay.android", + "adbLaunchCommand": "adb shell am start -n com.viaplay.android/.features.home.HomeActivity", + }, + "xiaomi": { + "appName": "com.viaplay.android", + "androidName": "com.viaplay.android", + "adbLaunchCommand": "adb shell am start -n com.viaplay.android/.features.home.HomeActivity", + }, + }, + + + "videoland": { + "button": '', + "friendlyName": "Videoland (NL)", + "className": "videolandButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "nl.rtl.videoland.v2.firetv", + "androidName": "nl.rtl.videoland.v2.firetv", + "adbLaunchCommand": "adb shell am start -n nl.rtl.videoland.v2.firetv/fr.m6.m6replay.tv.feature.home.HomeActivity", + }, + "apple-tv": { + "appName": "Videoland", + }, + "chromecast": { + "appName": "nl.rtl.videoland.v2", + "androidName": "nl.rtl.videoland.v2", + }, + "homatics": { + "appName": "nl.rtl.videoland.v2", + "androidName": "nl.rtl.videoland.v2", + "adbLaunchCommand": "adb shell am start -n nl.rtl.videoland.v2/com.bedrockstreaming.shared.tv.activity.SplashActivity", + }, + "nvidia-shield": { + "appName": "nl.rtl.videoland.v2", + "androidName": "nl.rtl.videoland.v2", + }, + "onn": { + "appName": "nl.rtl.videoland.v2", + "androidName": "nl.rtl.videoland.v2", + }, + "xiaomi": { + "appName": "nl.rtl.videoland.v2", + "androidName": "nl.rtl.videoland.v2", + }, + }, + + + "vidhub": { + "button": '', + "friendlyName": "VidHub", + "className": "vidhubButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName": "VidHub", + }, + }, + + + "viki": { + "button": '', + "button-round": '', + "friendlyName": "Viki", + "className": "vikiButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.viki.kindle", + "androidName": "com.viki.kindle", + "adbLaunchCommand": "adb shell am start -n com.viki.kindle/com.viki.android.ui.splash.SplashActivity", + }, + "apple-tv": { + "appName": "Viki", + }, + "chromecast": { + "appName": "Rakuten Viki", + "androidName": "com.viki.android", + "adbLaunchCommand": "adb shell am start -n com.viki.android/.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "Rakuten Viki", + "androidName": "com.viki.android", + "adbLaunchCommand": "adb shell am start -n com.viki.android/.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "Rakuten Viki", + "androidName": "com.viki.android", + "adbLaunchCommand": "adb shell am start -n com.viki.android/.ui.splash.SplashActivity", + }, + "onn": { + "appName": "Rakuten Viki", + "androidName": "com.viki.android", + "adbLaunchCommand": "adb shell am start -n com.viki.android/.ui.splash.SplashActivity", + }, + "roku": { + "appName": 'Viki', + "app-id": 48537, + }, + "xiaomi": { + "appName": "Rakuten Viki", + "androidName": "com.viki.android", + "adbLaunchCommand": "adb shell am start -n com.viki.android/.ui.splash.SplashActivity", + }, + }, + + + "vivo-play": { + "button": '', + "friendlyName": "Vivo Play", + "className": "vivoPlayButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.tid.enjoy.firetv", + "androidName": "com.tid.enjoy.firetv", + "adbLaunchCommand": "adb shell am start -n com.tid.enjoy.firetv/com.telefonica.video.stv.launcher.LauncherActivity", + }, + }, + + + "vlc": { + "button": '', + "friendlyName": "VLC", + "className": "vlcButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + }, + "apple-tv": { + "appName": "VLC", + }, + "chromecast": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + }, + "homatics": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + "adbLaunchCommand": "adb shell am start -n org.videolan.vlc/.StartActivity", + }, + "nvidia-shield": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + }, + "onn": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + }, + "xiaomi": { + "appName": "org.videolan.vlc", + "androidName": "org.videolan.vlc", + }, + }, + + + "volleyball-tv": { + "button": '', + "friendlyName": "Volleyball TV", + "className": "volleyballTVButton", + "deviceFamily": ["chromecast", "apple-tv", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "VBTV", + }, + "chromecast": { + "appName": "dce.fivb.volleyballtv", + "androidName": "dce.fivb.volleyballtv", + "adbLaunchCommand": "adb shell am start -n dce.fivb.volleyballtv/com.applicaster.ui.activities.MainActivity", + }, + "homatics": { + "appName": "dce.fivb.volleyballtv", + "androidName": "dce.fivb.volleyballtv", + "adbLaunchCommand": "adb shell am start -n dce.fivb.volleyballtv/com.applicaster.ui.activities.MainActivity", + }, + "nvidia-shield": { + "appName": "dce.fivb.volleyballtv", + "androidName": "dce.fivb.volleyballtv", + "adbLaunchCommand": "adb shell am start -n dce.fivb.volleyballtv/com.applicaster.ui.activities.MainActivity", + }, + "onn": { + "appName": "dce.fivb.volleyballtv", + "androidName": "dce.fivb.volleyballtv", + "adbLaunchCommand": "adb shell am start -n dce.fivb.volleyballtv/com.applicaster.ui.activities.MainActivity", + }, + "xiaomi": { + "appName": "dce.fivb.volleyballtv", + "androidName": "dce.fivb.volleyballtv", + "adbLaunchCommand": "adb shell am start -n dce.fivb.volleyballtv/com.applicaster.ui.activities.MainActivity", + }, + }, + + + "voyo-cz": { + "button": '', + "friendlyName": "Voyo.cz", + "className": "voyoCzVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "net.cme.voyo.cz.tvapp", + "androidName": "net.cme.voyo.cz.tvapp", + "adbLaunchCommand": "adb shell am start -n net.cme.voyo.cz.tvapp/net.cme.voyo.cz.tvapp.MainActivity", + }, + "apple-tv": { + "appName": "Voyo.cz", + }, + "chromecast": { + "appName": "net.cme.voyo.cz.tvapp", + "androidName": "net.cme.voyo.cz.tvapp", + "adbLaunchCommand": "adb shell am start -n net.cme.voyo.cz.tvapp/net.cme.voyo.cz.tvapp.MainActivity", + }, + "homatics": { + "appName": "net.cme.voyo.cz.tvapp", + "androidName": "net.cme.voyo.cz.tvapp", + "adbLaunchCommand": "adb shell am start -n net.cme.voyo.cz.tvapp/net.cme.voyo.cz.tvapp.MainActivity", + }, + "nvidia-shield": { + "appName": "net.cme.voyo.cz.tvapp", + "androidName": "net.cme.voyo.cz.tvapp", + "adbLaunchCommand": "adb shell am start -n net.cme.voyo.cz.tvapp/net.cme.voyo.cz.tvapp.MainActivity", + }, + "xiaomi": { + "appName": "net.cme.voyo.cz.tvapp", + "androidName": "net.cme.voyo.cz.tvapp", + "adbLaunchCommand": "adb shell am start -n net.cme.voyo.cz.tvapp/net.cme.voyo.cz.tvapp.MainActivity", + }, + }, + + + "vtm-go": { + "button": '', + "friendlyName": "VTM GO (BE)", + "className": "vtmGoButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "apple-tv": { + "appName": "VTM GO", + }, + "chromecast": { + "appName": "be.vmma.vtm.zenderapp", + "androidName": "be.vmma.vtm.zenderapp", + }, + "homatics": { + "appName": "be.vmma.vtm.zenderapp", + "androidName": "be.vmma.vtm.zenderapp", + }, + "nvidia-shield": { + "appName": "be.vmma.vtm.zenderapp", + "androidName": "be.vmma.vtm.zenderapp", + }, + "xiaomi": { + "appName": "be.vmma.vtm.zenderapp", + "androidName": "be.vmma.vtm.zenderapp", + }, + }, + + + "vrt-max": { + "button": '', + "friendlyName": "VRT MAX (BE)", + "className": "vrtMaxButton", + "deviceFamily": ["apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "apple-tv": { + "appName": "VRT MAX", + }, + "chromecast": { + "appName": "be.vrt.vrtnu", + "androidName": "be.vrt.vrtnu", + }, + "homatics": { + "appName": "be.vrt.vrtnu", + "androidName": "be.vrt.vrtnu", + "adbLaunchCommand": "adb shell am start -n be.vrt.vrtnu/.splash.SplashScreenActivity", + }, + "nvidia-shield": { + "appName": "be.vrt.vrtnu", + "androidName": "be.vrt.vrtnu", + }, + "onn": { + "appName": "be.vrt.vrtnu", + "androidName": "be.vrt.vrtnu", + }, + "xiaomi": { + "appName": "be.vrt.vrtnu", + "androidName": "be.vrt.vrtnu", + }, + }, + + + "waipuTV": { + "button": '', + "friendlyName": "Waipu TV (DE)", + "className": "waipuTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "Waipu TV", + "androidName": "de.exaring.waipu.firetv.live", + }, + "apple-tv": { + "appName": "waipu.tv", + }, + "chromecast": { + "appName": "de.exaring.waipu", + "androidName": "de.exaring.waipu", + }, + "homatics": { + "appName": "de.exaring.waipu", + "androidName": "de.exaring.waipu", + }, + "nvidia-shield": { + "appName": "de.exaring.waipu", + "androidName": "de.exaring.waipu", + }, + "xiaomi": { + "appName": "de.exaring.waipu", + "androidName": "de.exaring.waipu", + }, +}, + + + "watch-it": { + "button": '', + "button-round": '', + "friendlyName": "WATCH IT", + "className": "watchItButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + "apple-tv": { + "appName": "WATCH IT", + }, + "chromecast": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + "homatics": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + "onn": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + "xiaomi": { + "appName": "com.watchit.vod", + "androidName": "com.watchit.vod", + "adbLaunchCommand": "adb shell am start -n com.watchit.vod/.refactor.splash.ui.SplashActivity", + }, + }, + + + "watched": { + "button": '', + "friendlyName": "WATCHED", + "className": "watchedButton", + "appName": "com.watched.play", + "androidName": "com.watched.play", + "adbLaunchCommand": "adb shell am start -n com.watched.play/com.watched.play.MainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"],}, + + + "weyd": { + "button": '', + "friendlyName": "weyd", + "className": "weydButton", + "appName": "app.weyd.player", + "androidName": "app.weyd.player", + "adbLaunchCommand": "adb shell am start -n app.weyd.player/.ui.SplashScreenActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "xiaomi"],}, + + + "willow": { + "button": '', + "friendlyName": "Willow", + "className": "willowButton", + "deviceFamily": ["amazon-fire", "apple-tv", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "tv.willow", + "androidName": "tv.willow", + "adbLaunchCommand": "adb shell am start -n tv.willow/com.willow.android.tv.ui.splash.SplashActivity", + }, + "apple-tv": { + "appName": "Willow", + }, + "homatics": { + "appName": "tv.willow", + "androidName": "tv.willow", + "adbLaunchCommand": "adb shell am start -n tv.willow/com.willow.android.tv.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "tv.willow", + "androidName": "tv.willow", + "adbLaunchCommand": "adb shell am start -n tv.willow/com.willow.android.tv.ui.splash.SplashActivity", + }, + "onn": { + "appName": "tv.willow", + "androidName": "tv.willow", + "adbLaunchCommand": "adb shell am start -n tv.willow/com.willow.android.tv.ui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "tv.willow", + "androidName": "tv.willow", + "adbLaunchCommand": "adb shell am start -n tv.willow/com.willow.android.tv.ui.splash.SplashActivity", + }, + }, + + + "windscribe-vpn": { + "button": '', + "button-round": '', + "friendlyName": "Windscribe VPN", + "className": "windscribeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + "apple-tv": { + "appName" : "Windscribe", + }, + "chromecast": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + "homatics": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + "onn": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + "xiaomi": { + "appName": "com.windscribe.vpn", + "androidName": "com.windscribe.vpn", + "adbLaunchCommand": "adb shell am start -n com.windscribe.vpn/com.windscribe.tv.splash.SplashActivity", + }, + }, + + + "wireguard": { + "button": '', + "button-round": '', + "friendlyName": "WireGuard", + "className": "wireGuardButton", + "appName": "com.wireguard.android", + "androidName": "com.wireguard.android", + "adbLaunchCommand": "adb shell am start -n com.wireguard.android/com.wireguard.android.activity.TvMainActivity", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"],}, + + + "wnba": { + "button": '', + "button-round": '', + "friendlyName": "WNBA", + "className": "wnbaButton", + "deviceFamily": ["amazon-fire", "apple-tv", "roku"], + "amazon-fire": { + "appName": "com.wnba.centercourt", + "androidName": "com.wnba.centercourt", + "adbLaunchCommand": "adb shell am start -n com.wnba.centercourt/.SplashActivity", + }, + "apple-tv": { + "appName": "WNBA", + }, + "roku": { + "appName": 'WNBA - Live Games & Scores', + "app-id": 669757, + }, + }, + + + + "wow": { + "button": '', + "friendlyName": "WOW", + "className": "wowButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "de.sky.online", + "androidName": "de.sky.online", + "adbLaunchCommand": "adb shell am start -n de.sky.online/sky.wrapper.tv.GoogleMainActivity", + }, + "apple-tv": { + "appName": "WOW", + }, + "chromecast": { + "appName": "de.sky.online", + "androidName": "de.sky.online", + "adbLaunchCommand": "adb shell am start -n de.sky.online/sky.wrapper.tv.GoogleMainActivity", + }, + "nvidia-shield": { + "appName": "de.sky.online", + "androidName": "de.sky.online", + "adbLaunchCommand": "adb shell am start -n de.sky.online/sky.wrapper.tv.GoogleMainActivity", + }, + "xiaomi": { + "appName": "de.sky.online", + "androidName": "de.sky.online", + "adbLaunchCommand": "adb shell am start -n de.sky.online/sky.wrapper.tv.GoogleMainActivity", + }, + }, + + + "xciptv-player": { + "button": 'XCIPTV', + "friendlyName": "XCIPTV Player", + "className": "xciptvButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.nathnetwork.xctvplayerofficial", + "androidName": "com.nathnetwork.xctvplayerofficial", + }, + "apple-tv": { + "appName": "XCIPTV", + }, + "chromecast": { + "appName": "com.nathnetwork.xciptv", + "androidName": "com.nathnetwork.xciptv", + }, + "homatics": { + "appName": "com.nathnetwork.xciptv", + "androidName": "com.nathnetwork.xciptv", + "adbLaunchCommand": "adb shell am start -n com.nathnetwork.xciptv/.SplashVideoActivity", + }, + "nvidia-shield": { + "appName": "com.nathnetwork.xciptv", + "androidName": "com.nathnetwork.xciptv", + }, + "xiaomi": { + "appName": "com.nathnetwork.xciptv", + "androidName": "com.nathnetwork.xciptv", + }, + }, + + + "xfinityStream": { + "button": '', + "friendlyName": "Xfinity Stream", + "className": "xfinityStreamButton", + "deviceFamily": ["amazon-fire", "apple-tv", "homatics", "nvidia-shield", "roku"], + "amazon-fire": { + "appName": "com.xfinity.cloudtvr.tenfoot", + "androidName": "com.xfinity.cloudtvr.tenfoot", + }, + "apple-tv": { + "appName": "Stream", + }, + "homatics": { + "appName": "com.xfinity.cloudtvr.tenfoot", + "androidName": "com.xfinity.cloudtvr.tenfoot", + "adbLaunchCommand": "adb shell am start -n com.xfinity.cloudtvr.tenfoot/com.xfinity.common.view.LaunchActivity", + }, + "nvidia-shield": { + "appName": "com.xfinity.cloudtvr", + "androidName": "com.xfinity.cloudtvr", + }, + "roku": { + "appName": 'Xfinity Stream', + "app-id": 123132, + }, + }, + + + "yes-plus": { + "button": '', + "friendlyName": "yes+", + "className": "yesPlusButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + "apple-tv": { + "appName": "yes", + }, + "chromecast": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + "homatics": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + "onn": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + "xiaomi": { + "appName": "il.co.yes.yesplus", + "androidName": "il.co.yes.yesplus", + "adbLaunchCommand": "adb shell am start -n il.co.yes.yesplus/com.onoapps.yestv.ui.activities.splash.SplashActivity", + }, + }, + + + "youcine": { + "button": '', + "friendlyName": "YouCine", + "className": "youCineButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "chromecast": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "homatics": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "nvidia-shield": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "onn": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + "xiaomi": { + "appName": "com.world.youcinetv", + "androidName": "com.world.youcinetv", + "adbLaunchCommand": "adb shell am start -n com.world.youcinetv/com.interactive.brasiliptv.ui.activity.WelcomeActivity", + }, + }, + + + "youtube": { + "button": '', + "friendlyName": "YouTube", + "className": "youtubeButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "YouTube (FireTV)", + "androidName": "com.amazon.firetv.youtube", + "adbLaunchCommand": "adb shell am start -n com.amazon.firetv.youtube/dev.cobalt.app.MainActivity", + }, + "apple-tv": { + "appName": "YouTube", + }, + "chromecast": { + "appName": "YouTube", + "androidName": "com.google.android.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivity" + }, + "homatics": { + "appName": "YouTube", + "androidName": "com.google.android.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivity", + }, + "nvidia-shield": { + "appName": "YouTube", + "androidName": "com.google.android.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivity" + }, + "onn": { + "appName": "YouTube", + "androidName": "com.google.android.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivity" + }, + "roku": { + "appName": 'YouTube', + "app-id": 837, + }, + "xiaomi": { + "appName": "YouTube", + "androidName": "com.google.android.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tv/com.google.android.apps.youtube.tv.activity.ShellActivity" + }, + }, + + + "youtubekids": { + "button": '', + "friendlyName": "YouTube Kids", + "className": "youtubekidsButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.google.android.youtube.tvkids", + "androidName": "com.amazon.firetv.youtube.kids", + "adbLaunchCommand": "adb shell am start -n com.amazon.firetv.youtube.kids/dev.cobalt.app.MainActivity", + }, + "apple-tv": { + "appName" : "YouTube Kids", + }, + "chromecast": { + "appName" : "YouTube Kids", + "androidName": "com.google.android.youtube.tvkids", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvkids/com.google.android.apps.youtube.tvkids.activity.MainActivity", + }, + "homatics": { + "appName" : "YouTube Kids", + "androidName": "com.google.android.youtube.tvkids", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvkids/com.google.android.apps.youtube.tvkids.activity.MainActivity", + }, + "nvidia-shield": { + "appName" : "YouTube Kids", + "androidName": "com.google.android.youtube.tvkids", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvkids/com.google.android.apps.youtube.tvkids.activity.MainActivity", + }, + "onn": { + "appName" : "YouTube Kids", + "androidName": "com.google.android.youtube.tvkids", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvkids/com.google.android.apps.youtube.tvkids.activity.MainActivity", + }, + "xiaomi": { + "appName" : "YouTube Kids", + "androidName": "com.google.android.youtube.tvkids", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvkids/com.google.android.apps.youtube.tvkids.activity.MainActivity", + }, + }, + + + "youtube-revanced": { + "button": '', + "friendlyName": "YouTube ReVanced", + "className": "youtubeButton", + "appName": "app.revanced.android.youtube", + "adbLaunchCommand": "adb shell am start -n app.revanced.android.youtube/com.google.android.apps.youtube.app.application.Shell_HomeActivity", + "deviceFamily": ["amazon-fire", "chromecast", "nvidia-shield", "xiaomi"], + }, + + + "youtubeTV": { + "button": '', + "friendlyName": "YouTubeTV", + "className": "youtubeTVButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "roku", "xiaomi"], + "amazon-fire": { + "appName": "com.amazon.firetv.youtube.tv", + "androidName": "com.amazon.firetv.youtube.tv", + "adbLaunchCommand": "adb shell am start -n com.amazon.firetv.youtube.tv/dev.cobalt.app.MainActivity", + }, + "apple-tv": { + "appName" : "YouTube TV", + }, + "chromecast": { + "appName" : "YouTube TV", + "androidName": "com.google.android.youtube.tvunplugged", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity", + }, + "homatics": { + "appName" : "YouTube TV", + "androidName": "com.google.android.youtube.tvunplugged", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity", + }, + "nvidia-shield": { + "appName" : "YouTube TV", + "androidName": "com.google.android.youtube.tvunplugged", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity", + }, + "onn": { + "appName" : "YouTube TV", + "androidName": "com.google.android.youtube.tvunplugged", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity", + }, + "roku": { + "appName": 'YouTube TV', + "app-id": 195316, + }, + "xiaomi": { + "appName" : "YouTube TV", + "androidName": "com.google.android.youtube.tvunplugged", + "adbLaunchCommand": "adb shell am start -n com.google.android.youtube.tvunplugged/com.google.android.apps.youtube.tvunplugged.activity.MainActivity", + }, + }, + + + "zattoo": { + "button": '', + "button-round": '', + "friendlyName": "Zattoo", + "className": "zattooButton", + "deviceFamily": ["amazon-fire", "apple-tv"], + "amazon-fire": { + "appName": "com.zattoo.player.firetv", + "androidName": "com.zattoo.player.firetv", + }, + "apple-tv": { + "appName": "Zattoo", + }, + }, + + + "zdf-heute": { + "button": '', + "button-round": '', + "friendlyName": "ZDFheute", + "className": "zdfheuteButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + "chromecast": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + "homatics": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + "nvidia-shield": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + "onn": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + "xiaomi": { + "appName": "de.heute.mobile", + "androidName": "de.heute.mobile", + "adbLaunchCommand": "adb shell am start -n de.heute.mobile/.ui.splash.SplashActivity", + }, + }, + + + "zdf-mediathek": { + "button": '', + "friendlyName": "ZDF Mediathek", + "className": "zdfMediathekButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + "apple-tv": { + "appName" : "ZDFmediathek", + }, + "chromecast": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + "homatics": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + "nvidia-shield": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + "onn": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + "xiaomi": { + "appName": "com.zdf.android.mediathek", + "androidName": "com.zdf.android.mediathek", + "adbLaunchCommand": "adb shell am start -n com.zdf.android.mediathek/com.zdf.android.mediathek.ui.common.MainActivity", + }, + }, + + + "ziggo-go": { + "button": '', + "friendlyName": "Ziggo Go", + "className": "ziggoGoButton", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "appName": "com.ziggo.tv.firetv", + "androidName": "com.ziggo.tv.firetv", + "adbLaunchCommand": "adb shell am start -n com.ziggo.tv.firetv/com.libertyglobal.horizonx.MainActivity", + }, + "apple-tv": { + "appName" : "Ziggo GO TV", + }, + "chromecast": { + "appName": "com.ziggo.tv", + "androidName": "com.ziggo.tv", + "adbLaunchCommand": "adb shell am start -n com.ziggo.tv/com.libertyglobal.horizonx.MainActivity", + }, + "homatics": { + "appName": "com.ziggo.tv", + "androidName": "com.ziggo.tv", + "adbLaunchCommand": "adb shell am start -n com.ziggo.tv/com.libertyglobal.horizonx.MainActivity", + }, + "nvidia-shield": { + "appName": "com.ziggo.tv", + "androidName": "com.ziggo.tv", + "adbLaunchCommand": "adb shell am start -n com.ziggo.tv/com.libertyglobal.horizonx.MainActivity", + }, + "xiaomi": { + "appName": "com.ziggo.tv", + "androidName": "com.ziggo.tv", + "adbLaunchCommand": "adb shell am start -n com.ziggo.tv/com.libertyglobal.horizonx.MainActivity", + }, + }, + + + "zinema-5-0": { + "button": '', + "friendlyName": "ZINEMA 5.0", + "className": "zinema5Button", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "appName": "com.cinema.hdintro", + "androidName": "com.cinema.hdintro", + "adbLaunchCommand": "adb shell am start -n com.cinema.hdintro/com.rtx.login.MOD.activity.splash", + }, + }, + + + "zwift": { + "button": '', + "button-round": '', + "friendlyName": "Zwift", + "className": "zwiftButton", + "deviceFamily": ["apple-tv"], + "apple-tv": { + "appName" : "Zwift", + }, + }, + + + "function-app-manage": { + "button": '', + "friendlyName": "Function: Manage Apps", + "className": "functionAppManageButton", + "deviceFamily": ["amazon-fire"], + "amazon-fire": { + "adbLaunchCommand": "adb shell am start -a android.settings.APPLICATION_SETTINGS -n com.amazon.tv.settings.v2/.tv.applications.ApplicationsActivity", + }, + }, + + + "function-app-switch": { + "button": '', + "friendlyName": "Function: Switch Apps", + "className": "functionAppSwitchButton", + "adbLaunchCommand": "adb shell input keyevent KEYCODE_APP_SWITCH", + "deviceFamily": ["amazon-fire", "apple-tv", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "adbLaunchCommand": "am start -n com.amazon.venezia/com.amazon.venezia.grid.AppsGridLauncherActivity", + }, + "apple-tv": { + "remoteCommand": '{"command": "home", "num_repeats": 2, "delay_secs": 0.2, "hold_secs": 0}', + }, + "chromecast": { + "adbLaunchCommand": "adb shell input keyevent 284", + }, + "homatics": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_ALL_APPS", + }, + "nvidia-shield": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_APP_SWITCH", + }, + "onn": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_ALL_APPS", + }, + "xiaomi": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_APP_SWITCH", + }, + }, + + + "function-check-for-os-update": { + "button": 'Update', + "friendlyName": "Function: System Update", + "className": "functionSystemUpdateButton", + "deviceFamily": ["chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "chromecast": { + "appName": "com.google.android.gms", + "androidName": "com.google.android.gms", + "adbLaunchCommand": "adb shell am start -n com.google.android.gms/com.google.android.gms.update.SystemUpdatePanoActivity", + }, + "homatics": { + "appName": "com.google.android.gms", + "androidName": "com.google.android.gms", + "adbLaunchCommand": "adb shell am start -n com.google.android.gms/com.google.android.gms.update.SystemUpdatePanoActivity", + }, + "nvidia-shield": { + "appName": "com.google.android.gms", + "androidName": "com.google.android.gms", + "adbLaunchCommand": "adb shell am start -n com.google.android.gms/com.google.android.gms.update.SystemUpdatePanoActivity", + }, + "onn": { + "appName": "com.google.android.gms", + "androidName": "com.google.android.gms", + "adbLaunchCommand": "adb shell am start -n com.google.android.gms/com.google.android.gms.update.SystemUpdatePanoActivity", + }, + "xiaomi": { + "appName": "com.google.android.gms", + "androidName": "com.google.android.gms", + "adbLaunchCommand": "adb shell am start -n com.google.android.gms/com.google.android.gms.update.SystemUpdatePanoActivity", + }, + }, + + + "function-control-center": { + "button": '', + "friendlyName": "Function: Control Center", + "className": "functionControlCenterButton", + "remoteCommand": '{"command": "home", "num_repeats": 1, "hold_secs": 1.5}', + "deviceFamily": ["apple-tv"], + }, + + + "function-find-my-remote": { + "button": " Find Remote", + "friendlyName": "Function: Find My Remote", + "className": "functionFindRemoteButton", + "deviceFamily": ["nvidia-shield", "onn", "roku"], + "nvidia-shield": { + "appName": "com.nvidia.remotelocator", + "androidName": "com.nvidia.remotelocator", + "adbLaunchCommand": "adb shell am start -a android.intent.action.VIEW -d -n com.nvidia.remotelocator/.ShieldRemoteLocatorActivity", + }, + "onn": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_BUTTON_11", + }, + "roku": { + "remoteCommand": '{"command": "find_remote", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + + "function-volume-up": { + "button": "", + "friendlyName": "Function: Volume +", + "className": "functionVolumeUpButton", + "deviceFamily": ["roku",], + "roku": { + "remoteCommand": '{"command": "volume_up", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + "function-volume-down": { + "button": "", + "friendlyName": "Function: Volume -", + "className": "functionVolumeDownButton", + "deviceFamily": ["roku",], + "roku": { + "remoteCommand": '{"command": "volume_down", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + "function-captions": { + "button": "Captions", + "friendlyName": "Function: Captions", + "className": "functionCaptionsButton", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"], + "amazon-fire": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_CAPTIONS", + }, + "chromecast": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_CAPTIONS", + }, + "homatics": { + "adbLaunchCommand": "adb shell input keyevent 175", + }, + "nvidia-shield": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_CAPTIONS", + }, + "onn": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_CAPTIONS", + }, + "xiaomi": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_CAPTIONS", + }, + }, + + "function-channel-up": { + "button": "", + "friendlyName": "Function: Channel +", + "className": "functionChannelUpButton", + "deviceFamily": ["roku",], + "roku": { + "remoteCommand": '{"command": "channel_up", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + "function-channel-down": { + "button": "", + "friendlyName": "Function: Channel -", + "className": "functionChannelDownButton", + "deviceFamily": ["roku",], + "roku": { + "remoteCommand": '{"command": "channel_down", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + "function-mute": { + "button": " Mute", + "friendlyName": "Function: Mute", + "className": "functionMuteButton", + "deviceFamily": ["amazon-fire", "nvidia-shield", "roku", "xiaomi"], + "amazon-fire": { + "adbLaunchCommand": "adb shell input keyevent 164", + }, + "nvidia-shield": { + "adbLaunchCommand": "adb shell input keyevent 164", + }, + "roku": { + "remoteCommand": '{"command": "volume_mute", "num_repeats": 1, "hold_secs": 0}', + }, + "xiaomi": { + "adbLaunchCommand": "adb shell input keyevent 164", + }, + }, + + + "function-next": { + "button": '', + "friendlyName": "Function: Next", + "className": "functionNextButton", + "remoteCommand": '{"command": "next", "num_repeats": 1, "hold_secs": 0}', + "deviceFamily": ["apple-tv"], + }, + + + "function-previous": { + "button": '', + "friendlyName": "Function: Previous", + "className": "functionPreviousButton", + "remoteCommand": '{"command": "previous", "num_repeats": 1, "hold_secs": 0}', + "deviceFamily": ["apple-tv"], + }, + + "function-reboot": { + "button": "Reboot", + "friendlyName": "Function: Reboot", + "className": "functionRebootButton", + "adbLaunchCommand": "adb shell reboot", + "deviceFamily": ["amazon-fire", "chromecast", "homatics", "nvidia-shield", "onn", "xiaomi"],}, + + + "function-recents": { + "button": "Recents", + "friendlyName": "Function: Recents", + "className": "functionRecentsButton", + "deviceFamily": ["homatics"], + "homatics": { + "appName": "com.nes.recents", + "androidName": "ccom.nes.recents", + "adbLaunchCommand": "adb shell am start -n com.nes.recents/com.nes.recents.RecentsActivity", + }, + }, + + + "function-search": { + "button": '', + "friendlyName": 'Function: Search', + "className": "appleTvSearchButton", + "appName": "Search", + "deviceFamily": ["apple-tv"], + }, + + "function-search-googletv": { + "button": "Search", + "friendlyName": "Function: Search", + "className": "functionSearchGoogleTVButton", + "deviceFamily": ["homatics", "onn"], + "homatics": { + "adbLaunchCommand": "adb shell input keyevent 117", + }, + "onn": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_SEARCH", + } + }, + + "function-search-roku": { + "button": 'Search', + "friendlyName": 'Function: Search', + "className": "searchButton", + "deviceFamily": ["roku"], + "roku": { + "remoteCommand": '{"command": "search", "num_repeats": 1, "hold_secs": 0}', + }, + }, + + "function-pairing": { + "button": "Pair", + "friendlyName": "Function: Pairing", + "className": "functionpairingButton", + "deviceFamily": ["homatics", "onn"], + "homatics": { + "adbLaunchCommand": "adb shell am start -n com.android.tv.settings/.accessories.AddAccessoryActivity", + }, + "onn": { + "adbLaunchCommand": "adb shell input keyevent KEYCODE_PAIRING", + } + }, + + "function-settings": { + "button": "Settings", + "friendlyName": "Function: Settings", + "className": "functionSettingsButton", + "deviceFamily": ["amazon-fire", "chromecast", "nvidia-shield", "xiaomi"], + "amazon-fire": { + "adbLaunchCommand": "adb shell input keyevent SETTINGS", + }, + "chromecast": { + "appName": "com.android.tv.settings", + "androidName": "com.android.tv.settings", + }, + "nvidia-shield": { + "appName": "com.android.tv.settings", + "androidName": "com.android.tv.settings", + }, + "xiaomi": { + "appName": "com.android.tv.settings", + "androidName": "com.android.tv.settings", + "adbLaunchCommand": "adb shell am start -a android.settings.SETTINGS -d -n com.android.tv.settings/.MainSettings", + }, + }, + + + "function-apple-settings": { + "button": '', + "friendlyName": "Function: Settings", + "className": "functionAppleSettingsButton", + "appName": "Settings", + "deviceFamily": ["apple-tv"], + }, + + + "function-skip-backward": { + "button": '', + "friendlyName": "Function: Skip Backward", + "className": "functionSkipBackwardButton", + "remoteCommand": '{"command": "skip_backward", "num_repeats": 1, "hold_secs": 0}', + "deviceFamily": ["apple-tv"], + }, + + "function-skip-forward": { + "button": '', + "friendlyName": "Function: Skip Forward", + "className": "functionSkipBForwardButton", + "remoteCommand": '{"command": "skip_forward", "num_repeats": 1, "hold_secs": 0}', + "deviceFamily": ["apple-tv"], + }, + +}; + +const launcherCSS = ` + .twelvePlusButton { + background: radial-gradient(circle at top, rgba(142, 122, 181, 1) 2%, rgba(35, 23, 48, 1) 90%); + } + + .thirteenNewsNowWVECButton { + background: #0550ab; + } + + .abciviewButton{ + background: #fff; + } + + .aceStreamMediaButton { + background: rgb(33, 33, 33); + } + + .acornTvButton { + background: linear-gradient(45deg, rgba(12, 162, 151, 1) 1%, rgba(5, 62, 58, 1) 75%); + } + + .airScreenButton { + background: #fff; + } + + .allFourButton { + background: #1e2226; + } + + .allenteButton { + background: #fd6100; + } + + .altiboxButton { + background: #d73523; + } + + .amazonMusicButton { + background: #373e8f; + } + + .amcPlusButton { + background: #192f54; + } + + .amcPlusButton svg { + width: calc(var(--sz) * 2.7rem); + } + .shield-remote-body .amcPlusButton svg { + width: calc(var(--sz) * 3.75rem); + } + + .angelStudiosButton { + background: #000; + } + + .apolloGroupTVButton { + background: #fff; + } + + .appleArcadeButton { + background: linear-gradient(0deg, rgba(255,45,80,1) 0%, rgba(255,115,85,1) 100%); + } + + .appleComputersButton { + background: linear-gradient(0deg, rgba(255,114,49,1) 0%, rgba(255,175,63,1) 100%); + } + + .appleFacetimeButton { + background: linear-gradient(0deg, rgba(11,188,41,1) 0%, rgba(93,247,119,1) 100%); + } + + .appleFitnessButton { + background: #000; + } + + .appleTvMusicButton { + background: linear-gradient(0deg, rgba(250,35,59,1) 0%, rgba(251,92,116,1) 100%); + } + + .appleTvMoviesButton { + color: #fff; + background: linear-gradient(135deg, rgba(44,240,255,1) 0%, rgba(38,174,255,1) 100%); + font-size: calc(var(--sz) * 1rem); + font-weight: bold; + } + + .appleTvPhotosButton { + background: #fff; + } + + .appleTvPodcastsButton { + background: linear-gradient(0deg, rgba(123,107,255,1) 0%, rgba(252,97,255,1) 100%); + } + + .appleTvSearchButton { + background: linear-gradient(0deg, rgba(138,138,138,1) 0%, rgba(200,200,200,1) 100%); + border: solid #838383 calc(var(--sz) * 0.05rem) !important; + } + + .appleTvshowsButton { + color: #fff; + background: linear-gradient(228deg, rgba(237,115,225,1) 0%, rgba(162,120,190,1) 100%); + font-size: calc(var(--sz) * 1rem); + text-wrap: wrap; + font-weight: bold; + } + + .appOpenerButton { + font-size: calc(var(--sz) * .75rem); + color: #fff; + background: #400080; + } + + .appleAppStoreButton { + background: linear-gradient(0deg, rgba(12,88,239,1) 0%, rgba(14,212,252,1) 100%); + } + + .appleTvButton { + background: rgb(46, 46, 46); + } + .appleTvButton svg { + width: calc(var(--sz) * 2.4rem); + margin-top: calc(var(--sz) * -0.2rem); + } + .shield-remote-body .appleTvButton svg { + width: calc(var(--sz) * 3rem); + } + + .archiTVForArchillectButton { + background: #000; + } + + .ardMediathekButton { + background: #003480; + } + + .aptvButton { + background: #fff; + } + + .arteButton { + background: #141414; + } + + .babyTvButton { + background: #fff; + } + + .bTVButton { + background: #fff; + } + + .bTVfrButton { + background: #fff; + } + + .bbciplayerButton { + background: #fff; + } + + .bellFibeTVButton { + background: #fff; + } + .bellFibeTVButton svg { + width: calc(var(--sz) * 2.5rem); + } + + .bgtimeTvButton { + background: linear-gradient(90deg, #12365c, #171f42); + } + + .bigTenNetworkPlusButton { + background: #fff; + } + + .bingeAuButton { + background: #fff; + } + + .blazeTVButton { + background: #000; + } + + .bookmarker1Button { + background: rgb(109 109 109); + } + + .britboxButton { + background: #00202b; + } + + .btvAppButton { + background: #3c3939; + } + + .byuTvButton { + background: #0fa6e2; + } + + .caiwayTVButton { + background: #fff; + } + + .canalPlusButton { + background: #000; + } + + .cartoonNetworkButton { + background: #fff; + } + + .cbsSportsButton { + background: #004ace; + } + + .cctvViewerButton { + background: radial-gradient(circle, rgba(149,220,233,1) 40%, rgba(44,76,82,1) 100%); + } + + .cgatesTvButton { + background: #25b14b; + } + + .channelFourButton { + background: #aaff89; + } + + .channelsDVRButton { + background: linear-gradient(42deg, rgba(49,42,171,1) 0%, rgba(76,24,133,1) 87%); + } + + .cheersDanmuPlayerButton { + background: linear-gradient(0deg, rgba(103,219,237,1) 0%, rgba(105,200,248,1) 100%); + } + + .cinemaHDV2Button, .cinemaHDV3Button { + background: #fff; + } + + .claroTvPlusButton { + background: #000; + } + + .clipiousButton { + background: #171c3a; + } + + .cloudstreamButton { + background: #000; + } + + .cloudstreamPrereleaseButton { + background: #000; + } + + .cnnButton { + background: linear-gradient(0deg, rgba(110,0,0,1) 0%, rgba(227,0,0,1) 100%); + } + + .corridorDigitalButton { + background: #000; + } + + .cosmoteTVButton { + background: #091016; + } + .cosmoteTVButton svg { + height: calc(var(--sz) * 1.15rem); + } + .shield-remote-body .cosmoteTVButton svg { + height: calc(var(--sz) * 1.6rem); + } + .chromecast-remote-body .cosmoteTVButton svg { + height: calc(var(--sz) * .75rem); + } + + .coupangPlayButton { + background: #fff; + } + + .craveTVButton { + background: #00083f; + } + + .crunchyrollButton { + background: #23252b; + } + + .curiosityStreamButton { + background: #04070d; + } + + .cyberghostButton { + background: #242538; + } + + .dailyWireButton { + background: radial-gradient(circle, rgba(20,20,20,1) 0%, rgba(0,0,0,1) 100%); + } + + .dakboardButton { + background: #fff; + } + + .daznButton { + background: #1b3848; + } + .daznButton svg { + width: calc(var(--sz) * 4.5rem); + height: calc(var(--sz) * 1.6rem); + } + .shield-remote-body .daznButton svg { + height: calc(var(--sz) * 2.1rem); + } + .apple-remote-body .daznButton svg { + height: calc(var(--sz) * 2.25rem); + } + + .deezerButton { + background: #000; + } + + .defSquidButton { + background: #01252c; + } + + .dgoButton { + background: #101010; + } + + .direcTVStreamButton { + background: #fff; + } + .direcTVStreamButton svg { + width: calc(var(--sz) * 3.4rem); + } + .shield-remote-body .direcTVStreamButton svg { + width: calc(var(--sz) * 4.6rem); + } + + .dirTVisionButton { + background: #1d1d1d; + } + .apple-remote-body .dirTVisionButton, .chromecast-remote-body .dirTVisionButton { + background: linear-gradient(180deg, rgba(254,0,0,1) 17%, rgba(0,0,0,1) 17%); + } + + .discoveryPlusButton { + background: #042482; + } + + .dishAnywhereButton { + background: #fff; + } + + .disneyPlusButton { + background: #07183f; + } + .AF6 .disneyPlusButton { + background: rgb(37, 37, 37); + } + .ON1 .disneyPlusButton, .ON2 .disneyPlusButton { + background: #fff; + } + .disneyPlusButton svg { + margin-top: calc(var(--sz) * 0.2rem); + width: calc(var(--sz) * 3.6rem); + } + .shield-remote-body .disneyPlusButton svg { + width: calc(var(--sz) * 4.8rem); + } + .ON1 .disneyPlusButton > svg > g > *, .ON2 .disneyPlusButton > svg > g > * { + fill: #0c2661 !important; + } + + .disneyPlusHotstarButton { + background: linear-gradient(0deg, rgba(27,91,191,1) 0%, rgba(6,9,43,1) 100%); + } + + .dogTVButton { + background: #f58021; + } + + .downloaderButton { + background: #f37623; + } + + .dropoutButton { + background: #fbeb3c; + } + + .drtvButton { + background: #ff001e; + } + + .dsVideoButton { + color: #fff; + background: #cd4242; + font-weight: bold; + font-size: calc(var(--sz) * 1rem); + } + .chromecast-remote-body .dsVideoButton { + font-size: calc(var(--sz) * .8rem); + } + + .elisaElamusButton { + background: #0019af; + } + + .embyButton { + background: #fff; + } + + .eonTVButton{ + background: #fff + } + + .errJupiterButton { + background: #1141e9; + } + + .ertflixButton { + background: #07141c; + } + .shield-remote-body .ertflixButton svg { + width: calc(var(--sz) * 4.8rem); + } + + .espnButton { + background: #fff; + } + .espnButton svg { + width: calc(var(--sz) * 3.6rem); + } + .shield-remote-body .espnButton svg { + width: calc(var(--sz) * 4.8rem); + } + + .eurosportPlayerButton { + background: #fff; + } + + .fanduelSportsNetworkButton { + background: #0078ff; + } + + .fDroidutton { + background: #fff; + } + + .f1TVButton { + background: #e10600; + } + + .fandangoAtHomeButton { + background: #3478C1; + } + + .fiopticsPlusButton { + background: radial-gradient(at center top, rgba(35,95,155,1) 0%, rgba(1,38,57,1) 80%); + } + + .fireTVStoreButton { + background: linear-gradient(0deg, rgba(255,159,82,1) 0%, rgba(193,87,0,1) 40%, rgba(193,87,0,1) 60%, rgba(255,159,82,1) 100%); + font-weight: bold; + color: #fff; + font-size: calc(var(--sz) * 0.9rem); + } + + .flashButton { + background: #0049ff; + } + + .floSportsButton { + background: #ff140f; + } + + .flowButton { + background: #000; + } + + .fotooButton { + background: #705f4b; + } + + .foxBusinessButton { + background: #fff; + } + + .foxLocalButton { + background: #022859; + } + + .foxNewsChannelButton { + background: #036; + } + + .foxSportsButton { + background: #000; + } + + .foxtelAUButton { + background: #f2320a; + } + + .franceTVButton { + background: #172128; + } + + .freeTVButton { + background: linear-gradient(152deg, rgba(170,226,0,1) 0%, rgba(111,217,51,1) 88%, rgba(6,201,142,1) 100%); + } + + .freecastButton { + background: linear-gradient(45deg, rgba(0,7,102,1) 0%, rgba(21,21,21,1) 100%); + } + + .freeveeButton { + background: #D8FF03; + } + .freeveeButton svg { + width: calc(var(--sz) * 3.6rem); + } + .shield-remote-body .freeveeButton svg { + width: calc(var(--sz) * 4.8rem); + } + + .freeviewPlayButton{ + background: #fff; + } + + .fuboButton { + background: linear-gradient(0deg, rgba(227,0,27,1) 0%, rgba(255,58,11,1) 100%); + } + + .gcnPlusButton { + background: #ba0f15; + } + + .geForceNowButton { + background: #77ba00; + } + + .globoPlayButton { + background: #000; + } + + .go3EstoniaButton { + background: #000; + } + + .goPlayButton { + background: #001e23; + } + + .goTVButton { + background: #fff; + } + + .googlePlayStoreButton { + background: rgb(255, 255, 255); + } + + .greekTVLiveAndRadioPlayerButton { + background: linear-gradient(0deg, rgba(56,104,88,1) 35%, rgba(0,204,153,1) 35%); + } + + .hboMaxButton { + background: radial-gradient(circle, rgba(1,51,255,1) 0%, rgba(1,2,107,1) 100%); + } + + .hayuButton { + background: #26263e; + } + + .hdhomerunButton { + background: #fff; + } + + .hdoBoxButton { + background: #e45201; + } + + .historyVaultButton { + background: #000; + } + + .helixTVButton { + background: #000; + } + + .homeAssistantButton { + background: #fff; + } + + .huluButton { + background: #1ce783; + } + .AF6 .huluButton { + background: rgb(37, 37, 37); + } + .AF6 .huluButtonSVG { + fill: rgb(28, 231, 131)!important; + } + .huluButton svg { + width: calc(var(--sz) * 2.8rem); + } + + .iboPlayerProButton { + background: #100e36; + } + + .icitouTVButton { + background: linear-gradient(0deg, rgba(0,104,134,1) 0%, rgba(61,201,214,1) 100%); + } + + .igniteTVShawButton { + background: #fff; + } + + .iMPlayerButton { + background: #000; + } + + .infuseButton { + background: #fff; + } + + .internetSilkBrowserButton { + background: linear-gradient(218deg, rgba(0,179,188,1) 36%, rgba(5,111,107,1) 100%); + } + + .iPlayTVButton { + background: linear-gradient(0deg, rgba(92,92,92,1) 0%, rgba(127,127,127,1) 100%); + } + + .ipCamViewerFreeButton { + background: #393939; + } + + .ipCameraViewerIPCamsButton { + background: linear-gradient(0deg, rgba(60,131,102,1) 0%, rgba(79,167,131,1) 100%); + } + + .ipCameraViewerBasicButton { + background: #fff; + } + + .ipCameraViewerProButton { + background: #fff; + } + + .ipSmartersProButton { + background: linear-gradient(148deg, rgba(75,52,222,1) 0%, rgba(31,15,135,1) 100%); + } + + .ipTVExtremeProButton { + background: #fff; + } + + .iptvXButton { + background: #000; + } + + .ipVanishButton { + background: #fff; + } + .ipVanishButton svg { + width: calc(var(--sz) * 4rem); + } + .shield-remote-body .ipVanishButton svg { + width: calc(var(--sz) * 5.25rem); + } + + .iqiyiButton { + background: #fff; + } + + .itvxButton { + background: #deeb52; + } + + .israelStationButton { + font-size: calc(var(--sz) * .8rem); + display: flow-root; + color: #fff; + background: #2f2f2f; + } + .shield-remote-body .israelStationButton { + font-size: calc(var(--sz) * 1rem); + } + .chromecast-remote-body .israelStationButton { + font-size: calc(var(--sz) * .55rem); + } + + .iVysilaniButton { + background: #fff; + } + + .jdFaragButton { + background: #c7a65e; + } + + .jellyfinButton { + background: #000b25; + } + + .jioCinemaButton { + background: #000; + } + + .joynButton { + background: #000; + } + + .justWatchStreamingGuideButton { + background: #000; + } + + .kayoButton { + background: #fff; + } + .kayoButton svg { + height: calc(var(--sz) * 1.55rem); + } + .shield-remote-body .kayoButton svg { + height: calc(var(--sz) * 2.1rem); + } + + .KEXPTVStreamButton { + background: #feac31; + } + + .kinopubbutton { + background: #202632; + } + + .kinopoiskButton { + background: #000; + } + + .kodiButton { + background: rgba(27,67,82,1); + } + .kodiButton svg { + width: calc(var(--sz) * 3.8rem); + } + .shield-remote-body .kodiButton svg { + width: calc(var(--sz) * 5.25rem); + } + + .kpniTVButton { + background: #fff; + } + + .lazymediaDeluxeButton { + background: #1d821d; + } + + .legacyTabloButton { + background: #fff; + } + + .liveChannelsButton { + color: #fff; + background: #17b0f5; + font-size: calc(var(--sz) * .75rem); + } + .shield-remote-body .liveChannelsButton { + font-size: calc(var(--sz) * 1rem); + } + .chromecast-remote-body .liveChannelsButton { + font-size: calc(var(--sz) * .55rem); + } + + .magentaSportButton{ + background: #e20074; + } + + .magentaTVButton { + background: #fff; + } + + .magisTVButton { + background: #fff; + } + + .maxButton { + background: radial-gradient(circle, rgba(0,51,255,1) 0%, rgba(0,0,103,1) 100%); + } + + .maxPlayerButton { + background: rgb(1, 23, 60); + } + + .mediasetInfinityButton { + background: #0c0c0c; + } + + .meoGoButton { + background: linear-gradient(90deg, rgba(66,185,194,1) 0%, rgba(27,86,120,1) 100%); + } + + .miTeleButton { + background: #1d1d1b; + } + + .mlbButton { + background: #fff; + } + + .molotovButton { + background: #ffc107; + } + + .moonlightGameStreamingButton { + background: #535b63; + } + + .movistarPlusButton { + background: #2b2b2b; + } + .movistarPlusButton svg { + height: calc(var(--sz) * 1.2rem); + } + .shield-remote-body .movistarPlusButton svg { + height: calc(var(--sz) * 2rem); + } + + .mrMcButton { + background: #231f20; + } + + .msgPlusButton { + background: linear-gradient(0deg, rgba(11,59,99,1) 35%, rgba(2,6,13,1) 100%); + } + + .my5Button { + background: #1d262c; + } + + .myCanalButton { + background: #fff; + } + + .myFamilyCinemaButton { + background: #fff; + } + + .nationalTheatreAtHomeButton { + background: #002b2e; + } + + .nbaOnFireTvButton { + background: #11212f; + } + + .nbcNewsButton { + background: #fff; + } + + .nbcSportsButton { + background: #fff; + } + + .nebulaButton { + background: #fff; + } + + .netflixButton { + background: #fff; + } + .shield-remote-body .netflixButton { + background: #252525; + } + .AF6 .netflixButton { + background: rgb(37, 37, 37); + } + .netflixButton > svg { + width: calc(var(--sz) * 3.8rem); + } + .shield-remote-body .netflixButton > svg { + width: calc(var(--sz) * 4rem); + } + .shield-remote-body .netflixButton:active, .shield-remote-body .netflixButton.appActive { + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.142rem) rgb(255 87 87 / 15%); + border: solid #480a0a calc(var(--sz) * 0.0714rem); + } + .chromecast-remote-body.CC2 .netflixButton { + background: #d5e0e4; + filter: none !important; + } + .chromecast-remote-body.CC3 .netflixButton { + background: #efddd6; + filter: none !important; + } + .chromecast-remote-body.CC2 .netflixButton svg { + filter: grayscale(1) contrast(.3); + } + .chromecast-remote-body.CC3 .netflixButton svg { + filter: grayscale(1) contrast(.3); + } + + .newsButton { + font-weight: bold; + font-size: calc(var(--sz) * 1.4rem); + text-shadow: 0 0 calc(var(--sz) * 0.5rem) black; + color: #ffffff; + background: linear-gradient(45deg, rgba(255,184,81,1) 31%, rgba(202,21,205,1) 100%); + } + .newsButton:active, .newsButton.appActive { + box-shadow: 0 0 calc(var(--sz) * 0.857rem) calc(var(--sz) * 0.142rem) rgb(255 255 255 / 15%); + } + + .newsmaxButton { + background: #fff; + } + + .nflButton { + background: #013369; + } + + .nineNowButton { + background: #000; + } + + .nlzietButton { + background: #1a191c; + } + + .noovoButton { + background: linear-gradient(315deg, rgba(47,45,253,1) 0%, rgba(6,42,156,1) 100%); + } + + .nordVPNButton { + background: #fff; + } + + .norlysPlayButton { + background: #ea0029; + } + + .nostvButton { + background: #fff; + } + + .novaVideoPlayerButton { + background: #01579b; + } + + .nowTVButton { + background: linear-gradient(220deg, rgba(4,121,129,1) 57%, rgba(6,30,31,1) 100%); + } + .nowTVButton svg { + width: calc(var(--sz) * 3rem); + } + .shield-remote-body .nowTVButton svg { + width: calc(var(--sz) * 4.5rem); + } + + .npoButton { + background: #fff; + } + + .nrkTvButton { + background: #073b84; + } + + .odeonVodButton { + background: #000; + } + + .odidoButton { + background: #000; + } + + .okooButton { + background: #8c08ff; + } + + .optimumTVButton { + background: #002865; + } + + .optusSportButton { + background: #1b3246; + } + + .orangeTVButton { + background: #000; + } + + .ottNavigatorButton { + background: #22518f; + } + .chromecast-remote-body .ottNavigatorButton svg{ + width: calc(var(--sz) * 2.25rem); + } + + .oqeeButton { + background: #161616; + } + + .orfOnButton { + background: #1a1c20; + } + + .pandoraButton { + background: #3668ff; + } + .pandoraButton > svg { + width: calc(var(--sz) * 4rem); + } + .shield-remote-body .pandoraButton > svg { + width: calc(var(--sz) * 5.5rem); + } + + .paramountPlusButton { + background: #0667fc; + } + .ON1 .paramountPlusButton, .ON2 .paramountPlusButton { + background: #fff; + } + .ON1 .paramountPlusButton > svg > path, .ON2 .paramountPlusButton > svg > path { + fill: #0667fc !important; + } + + .patheThuisButton { + background: #fff; + } + + .pbsButton { + background: #2638c4; + } + + .pbsKidsVideoButton { + background: #f7e03b; + } + + .peacockButton { + background: linear-gradient(45deg, rgba(0,0,0,1) 0%, rgba(59,59,59,1) 100%); + } + .peacockButton svg { + width: calc(var(--sz) * 4rem); + } + .shield-remote-body .peacockButton svg { + width: calc(var(--sz) * 5.5rem); + } + + .peivoMediaButton { + background: linear-gradient(19deg, rgba(13,31,55,1) 14%, rgba(19,63,66,1) 81%); + } + + .philoButton { + background: #000; + } + + .planetaPlTvButton { + background: #fff; + } + + .plexButton { + background: radial-gradient(circle at 100% 0%, rgb(132 132 132) 0%, rgba(0, 0, 0, 1) 50%); + } + + .plutoTvButton { + background: #000; + } + + .podaTVButton { + background: #fff; + } + + .primeButton { + background: rgb(58 94 114); + } + .XM2 .primeButton, .homatics-remote-body .primeButton { + background: #fff; + } + .AF6 .primeButton { + background: rgb(37, 37, 37); + } + .primeButton svg { + width: calc(var(--sz) * 3.8rem); + } + .shield-remote-body .primeButton > svg { + width: calc(var(--sz) * 5rem); + } + .XM2 .primeButton .pv0 { + fill: #00a8e1; + } + .XM2 .primeButton .pv1 { + fill: #232f3e; + } + .AF6 .primeButton .pv0, .AF6 .primeButton .pv1 { + fill: #99dcff; + } + .homatics-remote-body .primeButton .pv0, .homatics-remote-body .primeButton .pv1{ + fill: #000; + } + + .privateInternetAccessButton { + font-size: calc(var(--sz) * 1.2rem); + color: #56b14d; + font-weight: bold; + background: #fff; + } + + .proximusPickxButton { + background: #000; + } + + .purpleCheetahButton { + background: #fff; + } + + .qmusicButton { + background: #fff; + } + + .radioParadiseButton { + background: #000; + } + + .radioPlayerUKButton { + background: #c03; + } + + .raiPlayButton { + background: #fff; + } + + .redBullTvButton { + background: #002649; + } + + .redPlayButton { + background: #fff; + } + + .retroArchButton { + background: #333; + } + + .rocketButton { + background: #fff; + } + + .rokuChannelButton { + background: #fff; + } + + .rtlPlayForTVButton { + background: #000; + } + + .rtlPlusButton { + background: #000; + } + + .rtlPlusHungaryButton { + background: #fff; + } + + .rumbleButton { + background: #fff; + } + + .s0undTVButton { + background: #a30f2c; + } + + .saltTvButton { + background: #000; + } + + .sbsOnDemandButton { + background: #000; + } + .chromecast-remote-body .sbsOnDemandButton, + .apple-remote-body .sbsOnDemandButton { + background: #FDB715; + } + + .screenCloudButton { + background: #fff; + } + + .seasons4UButton { + background: #005977; + } + + .sevenPlusButton { + background: #000; + } + + .seznamButton { + background: #fff; + } + + .shahidButton { + background: linear-gradient(45deg, rgba(3,210,97,1) 0%, rgba(2,163,238,1) 100%); + } + + .shopHQButton { + color: yellow; + background: #2e2e2e; + font-weight: bold; + font-size: calc(var(--sz) * 0.857rem); + } + + .showtimeButton { + background: black; + } + .showtimeButton svg { + width: calc(var(--sz) * 3.3rem); + } + .shield-remote-body .showtimeButton svg { + width: calc(var(--sz) * 5rem); + } + + .siriusXMButton { + background: #fff; + } + + .skyGoButton { + background: #fff; + } + + .skyNewsButton { + background: #fff; + } + + .skyPlusButton { + background: #000; + } + + .skyShowtimeButton { + background: linear-gradient(180deg, rgba(113,1,181,1) 0%, rgba(0,0,0,1) 35%, rgba(142,12,0,1) 100%); + } + + .skySportNowButton { + background: #fff; + font-size: calc(var(--sz) * 1rem); + } + + .skyQButton { + background: #fff; + } + + .skylinkSKButton { + background: #fe2a00; + } + + .slingButton { + background: linear-gradient(90deg, rgba(0,16,65,1) 0%, rgba(0,51,179,1) 75%); + } + + .smartTubeNextButton { + background: #000; + } + + .smartTvClientForTwitchButton { + background: #444; + } + + .smartersProButton { + background: #182044; + } + + .snappierIPTVButton { + background: #000; + } + + .somaFMButton { + background: #000; + } + + .sonyLivButton { + background: #000; + } + + .spacedeskDisplayButton { + background: #2b9433; + } + + .sparkleTVButton { + background: #fefefe; + } + + .spectrumTVButton { + background: #fff; + } + + .sportTVButton { + background: #252525; + } + .apple-remote-body .sportTVButton, .chromecast-remote-body .sportTVButton { + background: #fdea00; + } + + .sportsnetButton { + background: linear-gradient(0deg, rgba(11,34,62,1) 42%, rgba(0,56,111,1) 76%, rgba(0,94,184,1) 100%); + } + + .spotifyButton { + background: #fff; + } + + .stanButton { + background: #fff; + } + .stanButton svg { + width: calc(var(--sz) * 3.25rem); + } + .shield-remote-body .stanButton svg { + width: calc(var(--sz) * 5rem); + } + + .startupShowButton { + background: #fff; + } + + .startupShowTVButton { + background: #000; + } + + .stbEmuProButton { + background: #fff; + } + + .steamLinkButton { + background: linear-gradient(0deg, rgba(9,37,70,1) 0%, rgba(15,105,149,1) 50%); + } + + .stingTVButton { + background: #464646; + } + + .streamTVButton { + background: #fff; + } + + .starzButton { + background: radial-gradient(circle at 15% 0%, rgba(30, 71, 73, 1) 0%, rgba(16, 16, 16, 1) 40%); + } + + .streamzButton { + background: #020012; + } + .streamzButton svg { + width: calc(var(--sz) * 5.5rem); + } + + .stremioButton { + background: #fff; + } + + .strimButton { + background: #FAED6F; + } + + .surfsharkButton { + background: #fff; + } + + .svtPlayButton { + background: #000; + } + .svtPlayButton svg { + height: calc(var(--sz) * 1.2rem); + } + .shield-remote-body .svtPlayButton svg { + height: calc(var(--sz) * 1.6rem); + } + + .swiftfinButton { + background: #000b25; + } + + .swisscomblueTVButton { + background: #172565 + } + + .syncnextButton { + font-size: 0.75rem; + } + + .synclerButton { + background: #000); + } + + .synologyPhotosButton { + background: #fff; + } + + .tTwoTVButton { + background: #fff; + } + + .tabloTVButton { + background: #101e3c; + } + + .tailscaleButton { + background: #232323; + } + + .tbsButton { + background: linear-gradient(67deg, rgba(250,203,149,1) 0%, rgba(223,107,147,1) 44%, rgba(46,21,60,1) 100%); + } + + .teleboyTVButton { + background: #fff; + } + + .telenetTvButton { + background: #332822; + } + + .teliaPlayButton { + background: #230031; + } + + .teliaTvEstoniaButton { + background: #990ae3; + } + + .telusTvPlusButton { + background: #fff; + } + + .tenPlayButton { + background: #fff; + } + + .tennisChannelButton { + background: #fff; + } + + .testflightButton { + background: linear-gradient(0deg, rgba(29,133,243,1) 0%, rgba(0,212,255,1) 100%); + } + + .tf1PlusButton { + background: linear-gradient(180deg, rgba(25,55,255,1) 0%, rgba(15,29,147,1) 100%); + } + + .threenowButton { + background: #fff; + } + .threenowButton svg { + height: calc(var(--sz) * 2rem); + } + + .tidalButton { + background: #000; + } + + .tinyCamPROButton { + background: #fff; + } + + .tiviMateButton { + font-size: calc(var(--sz) * 1rem); + color: #33a8ff; + font-weight: bold; + background: #fff; + } + .chromecast-remote-body .tiviMateButton { + font-size: calc(var(--sz) * 0.85rem); + } + + .tiviMaxPremiumButton { + background: #fff; + } + + .tntButton { + background: #000; + } + + .towisXProButton { + background: #000; + } + + .trillerTVButton { + background: linear-gradient(74deg, #334877 6%, #35497c00 40%),linear-gradient(351deg, #F30B0B4F 0%, #35497C00 35%),linear-gradient(111deg, #1a2846 9%, #0d1424 100%); + } + + .tubiButton { + background: radial-gradient(circle at 80% -20%, rgba(140,0,229,1) 0%, rgba(69,0,157,1) 100%); + } + + .tvBrowserButton { + background: #1c1b21; + } + + .tvVlaanderenButton { + background: linear-gradient(133deg, rgba(222,0,0,1) 0%, rgba(254,72,0,1) 100%); + } + + .tv2GoButton { + background: #fff; + } + + .tv2PlayButton { + background: #000523; + } + + .tv2PlayNorwayButton { + background: #6f02ff; + } + + .tv4PlayButton { + background: #e0001c; + } + + .tverButton { + background: #fff; + } + + .tvExpressBRButton { + background: #7b5f9e; + } + + .tvingButton { + background: #fff; + } + + .tvnzPlusButton { + background: #000; + } + .tvnzPlusButton svg { + height: calc(var(--sz) * 1rem); + } + .shield-remote-body .tvnzPlusButton svg { + height: calc(var(--sz) * 1.25rem); + } + + .twireButton { + background: #2195f1; + } + + .twitchButton { + background: #6441a5; + } + .twitchButton svg { + width: calc(var(--sz) * 3.75rem); + margin-bottom: calc(var(--sz) * -0.3rem); + } + .shield-remote-body .twitchButton svg { + width: calc(var(--sz) * 5.5rem); + } + + .uNextButton { + background: linear-gradient(45deg, rgba(0,0,0,1) 0%, rgba(0,0,0,1) 50%, rgba(26,34,46,1) 50%, rgba(26,34,46,1) 75%, rgba(51,59,69,1) 75%); + } + + .uhfButton { + background: linear-gradient(135deg, rgba(255,129,55,1) 0%, rgba(237,52,6,1) 100%);; + } + + .unifiProtectButton { + background: #0258d3; + } + + .uktvPlayButton { + background: #131834; + } + + .viaplayButton { + background: #1d1d27; + } + + .videolandButton { + background: #ff3746; + } + .chromecast-remote-body .videolandButton { + font-size: calc(var(--sz) * 0.75rem); + font-weight: bold; + color: #fff; + } + + .vidhubButton { + background: #fff; + } + + .vivoPlayButton { + background: linear-gradient(135deg, rgba(120,0,157,1) 32%, rgba(213,34,142,1) 75%, rgba(255,151,0,1) 91%); + } + + .vikiButton { + background: #0c9bff; + } + + .vlcButton { + background: #fff; + } + + .volleyballTVButton { + background: #0f201f; + } + + .voyoCzVButton { + background: linear-gradient(90deg, rgba(48,227,234,1) 0%, rgba(103,34,255,1) 100%); + } + + .vrtMaxButton { + background: #020b26; + } + .vrtMaxButton svg { + width: calc(var(--sz) * 5.5rem) !important; + height: calc(var(--sz) * 0.95rem) !important; + } + .shield-remote-body .vrtMaxButton svg { + width: calc(var(--sz) * 5.5rem) !important; + height: calc(var(--sz) * 1.5rem) !important; + } + + .vtmGoButton { + background: linear-gradient(180deg, rgba(15,15,15,1) 0%, rgba(78,32,116,1) 100%);; + } + + .waipuTVButton { + font-size: calc(var(--sz) * 0.85rem); + line-height: 0.75rem; + color: #fff; + font-weight: bold; + background: linear-gradient(to right,#30182d 0,#0f2c4c 100%); + } + + .watchItButton { + background: #000; + } + + .watchedButton { + background: #000; + } + + .weydButton { + background: #2d2e2e; + } + + .willowButton { + background: #1d202d; + } + + .windscribeButton { + background: #04367d; + } + + .wireGuardButton { + background: #88171a; + } + + .wnbaButton { + background: #ec602e; + } + + .wowButton { + background: #fff; + } + + .xciptvButton { + background: #00cafd; + color: #084192; + font-weight: bold; + } + .chromecast-remote-body .xciptvButton { + font-size: calc(var(--sz) * .9rem); + } + + .xfinityStreamButton { + font-size: calc(var(--sz) * 0.7rem); + color: #fff; + font-weight: bold; + background: linear-gradient(150deg, rgba(59,48,173,1) 0%, rgba(101,168,250,1) 100%); + } + + .yesPlusButton { + background: #000; + } + + .youCineButton { + background: linear-gradient(0deg, rgba(0,0,0,1) 0%, rgba(66,66,66,1) 100%); + } + + .youtubeButton { + background: #fff; + } + .chromecast-remote-body.CC2 .youtubeButton { + background: #d5e0e4; + filter: none !important; + } + .chromecast-remote-body.CC3 .youtubeButton { + background: #efddd6; + filter: none !important; + } + .chromecast-remote-body.CC2 .youtubeButton svg { + filter: grayscale(1) contrast(.3); + } + .chromecast-remote-body.CC3 .youtubeButton svg { + filter: grayscale(1) contrast(.3); + } + .shield-remote-body .youtubeButton svg { + width: calc(var(--sz) * 5.75rem); + } + + .youtubekidsButton { + background: #fff; + } + .youtubekidsButton svg { + width: calc(var(--sz) * 4.5rem); + height: calc(var(--sz) * 1.6rem); + } + + .youtubeTVButton { + background: #fff; + } + + .zattooButton { + background: #fff; + } + .zattooButton svg { + margin-top: calc(var(--sz) * -0.2rem); + } + + .zdfMediathekButton { + background: #34393f; + } + + .zdfheuteButton { + background: #fff; + } + + .ziggoGoButton { + background: #1a1a1a; + } + + .zinema5Button { + background: #2e2e2e; + } + + .zwiftButton { + background: #f26722; + } +`; + + +export { launcherData, launcherCSS }; \ No newline at end of file diff --git a/www/community/HA-Firemote/launcher-buttons.js.gz b/www/community/HA-Firemote/launcher-buttons.js.gz new file mode 100644 index 0000000..d7db402 Binary files /dev/null and b/www/community/HA-Firemote/launcher-buttons.js.gz differ diff --git a/www/community/HA-Firemote/lit/lit-all.min.js b/www/community/HA-Firemote/lit/lit-all.min.js new file mode 100644 index 0000000..a343f6e --- /dev/null +++ b/www/community/HA-Firemote/lit/lit-all.min.js @@ -0,0 +1,127 @@ +/** + * @license + * Copyright 2019 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const t=window,i=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),e=new WeakMap;class n{constructor(t,i,e){if(this._$cssResult$=!0,e!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=i}get styleSheet(){let t=this.i;const s=this.t;if(i&&void 0===t){const i=void 0!==s&&1===s.length;i&&(t=e.get(s)),void 0===t&&((this.i=t=new CSSStyleSheet).replaceSync(this.cssText),i&&e.set(s,t))}return t}toString(){return this.cssText}}const o=t=>new n("string"==typeof t?t:t+"",void 0,s),r=(t,...i)=>{const e=1===t.length?t[0]:i.reduce(((i,s,e)=>i+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[e+1]),t[0]);return new n(e,t,s)},l=(s,e)=>{i?s.adoptedStyleSheets=e.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):e.forEach((i=>{const e=document.createElement("style"),n=t.litNonce;void 0!==n&&e.setAttribute("nonce",n),e.textContent=i.cssText,s.appendChild(e)}))},h=i?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let i="";for(const s of t.cssRules)i+=s.cssText;return o(i)})(t):t +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */;var u;const c=window,a=c.trustedTypes,d=a?a.emptyScript:"",v=c.reactiveElementPolyfillSupport,f={toAttribute(t,i){switch(i){case Boolean:t=t?d:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,i){let s=t;switch(i){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t)}catch(t){s=null}}return s}},p=(t,i)=>i!==t&&(i==i||t==t),y={attribute:!0,type:String,converter:f,reflect:!1,hasChanged:p},b="finalized";class m extends HTMLElement{constructor(){super(),this.o=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this.l=null,this.u()}static addInitializer(t){var i;this.finalize(),(null!==(i=this.v)&&void 0!==i?i:this.v=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((i,s)=>{const e=this.p(s,i);void 0!==e&&(this.m.set(e,s),t.push(e))})),t}static createProperty(t,i=y){if(i.state&&(i.attribute=!1),this.finalize(),this.elementProperties.set(t,i),!i.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,e=this.getPropertyDescriptor(t,s,i);void 0!==e&&Object.defineProperty(this.prototype,t,e)}}static getPropertyDescriptor(t,i,s){return{get(){return this[i]},set(e){const n=this[t];this[i]=e,this.requestUpdate(t,n,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||y}static finalize(){if(this.hasOwnProperty(b))return!1;this[b]=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.v&&(this.v=[...t.v]),this.elementProperties=new Map(t.elementProperties),this.m=new Map,this.hasOwnProperty("properties")){const t=this.properties,i=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of i)this.createProperty(s,t[s])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const i=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const t of s)i.unshift(h(t))}else void 0!==t&&i.push(h(t));return i}static p(t,i){const s=i.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this.g(),this.requestUpdate(),null===(t=this.constructor.v)||void 0===t||t.forEach((t=>t(this)))}addController(t){var i,s;(null!==(i=this.S)&&void 0!==i?i:this.S=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t))}removeController(t){var i;null===(i=this.S)||void 0===i||i.splice(this.S.indexOf(t)>>>0,1)}g(){this.constructor.elementProperties.forEach(((t,i)=>{this.hasOwnProperty(i)&&(this.o.set(i,this[i]),delete this[i])}))}createRenderRoot(){var t;const i=null!==(t=this.shadowRoot)&&void 0!==t?t:this.attachShadow(this.constructor.shadowRootOptions);return l(i,this.constructor.elementStyles),i}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostConnected)||void 0===i?void 0:i.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostDisconnected)||void 0===i?void 0:i.call(t)}))}attributeChangedCallback(t,i,s){this._$AK(t,s)}$(t,i,s=y){var e;const n=this.constructor.p(t,s);if(void 0!==n&&!0===s.reflect){const o=(void 0!==(null===(e=s.converter)||void 0===e?void 0:e.toAttribute)?s.converter:f).toAttribute(i,s.type);this.l=t,null==o?this.removeAttribute(n):this.setAttribute(n,o),this.l=null}}_$AK(t,i){var s;const e=this.constructor,n=e.m.get(t);if(void 0!==n&&this.l!==n){const t=e.getPropertyOptions(n),o="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:f;this.l=n,this[n]=o.fromAttribute(i,t.type),this.l=null}}requestUpdate(t,i,s){let e=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||p)(this[t],i)?(this._$AL.has(t)||this._$AL.set(t,i),!0===s.reflect&&this.l!==t&&(void 0===this.C&&(this.C=new Map),this.C.set(t,s))):e=!1),!this.isUpdatePending&&e&&(this._=this.T())}async T(){this.isUpdatePending=!0;try{await this._}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this.o&&(this.o.forEach(((t,i)=>this[i]=t)),this.o=void 0);let i=!1;const s=this._$AL;try{i=this.shouldUpdate(s),i?(this.willUpdate(s),null===(t=this.S)||void 0===t||t.forEach((t=>{var i;return null===(i=t.hostUpdate)||void 0===i?void 0:i.call(t)})),this.update(s)):this.P()}catch(t){throw i=!1,this.P(),t}i&&this._$AE(s)}willUpdate(t){}_$AE(t){var i;null===(i=this.S)||void 0===i||i.forEach((t=>{var i;return null===(i=t.hostUpdated)||void 0===i?void 0:i.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}P(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._}shouldUpdate(t){return!0}update(t){void 0!==this.C&&(this.C.forEach(((t,i)=>this.$(i,this[i],t))),this.C=void 0),this.P()}updated(t){}firstUpdated(t){}} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +var g;m[b]=!0,m.elementProperties=new Map,m.elementStyles=[],m.shadowRootOptions={mode:"open"},null==v||v({ReactiveElement:m}),(null!==(u=c.reactiveElementVersions)&&void 0!==u?u:c.reactiveElementVersions=[]).push("1.6.1");const w=window,_=w.trustedTypes,$=_?_.createPolicy("lit-html",{createHTML:t=>t}):void 0,S="$lit$",T=`lit$${(Math.random()+"").slice(9)}$`,x="?"+T,E=`<${x}>`,C=document,A=()=>C.createComment(""),k=t=>null===t||"object"!=typeof t&&"function"!=typeof t,M=Array.isArray,P=t=>M(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]),U="[ \t\n\f\r]",V=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,R=/-->/g,N=/>/g,O=RegExp(`>|${U}(?:([^\\s"'>=/]+)(${U}*=${U}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),L=/'/g,j=/"/g,z=/^(?:script|style|textarea|title)$/i,H=t=>(i,...s)=>({_$litType$:t,strings:i,values:s}),I=H(1),B=H(2),D=Symbol.for("lit-noChange"),W=Symbol.for("lit-nothing"),Z=new WeakMap,q=C.createTreeWalker(C,129,null,!1),F=(t,i)=>{const s=t.length-1,e=[];let n,o=2===i?"":"",r=V;for(let i=0;i"===h[0]?(r=null!=n?n:V,u=-1):void 0===h[1]?u=-2:(u=r.lastIndex-h[2].length,l=h[1],r=void 0===h[3]?O:'"'===h[3]?j:L):r===j||r===L?r=O:r===R||r===N?r=V:(r=O,n=void 0);const a=r===O&&t[i+1].startsWith("/>")?" ":"";o+=r===V?s+E:u>=0?(e.push(l),s.slice(0,u)+S+s.slice(u)+T+a):s+T+(-2===u?(e.push(void 0),i):a)}const l=o+(t[s]||"")+(2===i?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==$?$.createHTML(l):l,e]};class G{constructor({strings:t,_$litType$:i},s){let e;this.parts=[];let n=0,o=0;const r=t.length-1,l=this.parts,[h,u]=F(t,i);if(this.el=G.createElement(h,s),q.currentNode=this.el.content,2===i){const t=this.el.content,i=t.firstChild;i.remove(),t.append(...i.childNodes)}for(;null!==(e=q.nextNode())&&l.length0){e.textContent=_?_.emptyScript:"";for(let s=0;s2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=W}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,i=this,s,e){const n=this.strings;let o=!1;if(void 0===n)t=J(this,t,i,0),o=!k(t)||t!==this._$AH&&t!==D,o&&(this._$AH=t);else{const e=t;let r,l;for(t=n[0],r=0;r{var e,n;const o=null!==(e=null==s?void 0:s.renderBefore)&&void 0!==e?e:i;let r=o._$litPart$;if(void 0===r){const t=null!==(n=null==s?void 0:s.renderBefore)&&void 0!==n?n:null;o._$litPart$=r=new Y(i.insertBefore(A(),t),t,void 0,null!=s?s:{})}return r._$AI(t),r}; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */var lt,ht;const ut=m;class ct extends m{constructor(){super(...arguments),this.renderOptions={host:this},this.st=void 0}createRenderRoot(){var t,i;const s=super.createRenderRoot();return null!==(t=(i=this.renderOptions).renderBefore)&&void 0!==t||(i.renderBefore=s.firstChild),s}update(t){const i=this.render();this.hasUpdated||(this.renderOptions.isConnected=this.isConnected),super.update(t),this.st=rt(i,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this.st)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this.st)||void 0===t||t.setConnected(!1)}render(){return D}}ct.finalized=!0,ct._$litElement$=!0,null===(lt=globalThis.litElementHydrateSupport)||void 0===lt||lt.call(globalThis,{LitElement:ct});const at=globalThis.litElementPolyfillSupport;null==at||at({LitElement:ct});const dt={_$AK:(t,i,s)=>{t._$AK(i,s)},_$AL:t=>t._$AL};(null!==(ht=globalThis.litElementVersions)&&void 0!==ht?ht:globalThis.litElementVersions=[]).push("3.3.1"); +/** + * @license + * Copyright 2022 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +const vt=!1,{G:ft}=nt,pt=t=>null===t||"object"!=typeof t&&"function"!=typeof t,yt={HTML:1,SVG:2},bt=(t,i)=>void 0===i?void 0!==(null==t?void 0:t._$litType$):(null==t?void 0:t._$litType$)===i,mt=t=>void 0!==(null==t?void 0:t._$litDirective$),gt=t=>null==t?void 0:t._$litDirective$,wt=t=>void 0===t.strings,_t=()=>document.createComment(""),$t=(t,i,s)=>{var e;const n=t._$AA.parentNode,o=void 0===i?t._$AB:i._$AA;if(void 0===s){const i=n.insertBefore(_t(),o),e=n.insertBefore(_t(),o);s=new ft(i,e,t,t.options)}else{const i=s._$AB.nextSibling,r=s._$AM,l=r!==t;if(l){let i;null===(e=s._$AQ)||void 0===e||e.call(s,t),s._$AM=t,void 0!==s._$AP&&(i=t._$AU)!==r._$AU&&s._$AP(i)}if(i!==o||l){let t=s._$AA;for(;t!==i;){const i=t.nextSibling;n.insertBefore(t,o),t=i}}}return s},St=(t,i,s=t)=>(t._$AI(i,s),t),Tt={},xt=(t,i=Tt)=>t._$AH=i,Et=t=>t._$AH,Ct=t=>{var i;null===(i=t._$AP)||void 0===i||i.call(t,!1,!0);let s=t._$AA;const e=t._$AB.nextSibling;for(;s!==e;){const t=s.nextSibling;s.remove(),s=t}},At=t=>{t._$AR()},kt={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6},Mt=t=>(...i)=>({_$litDirective$:t,values:i});class Pt{constructor(t){}get _$AU(){return this._$AM._$AU}_$AT(t,i,s){this.et=t,this._$AM=i,this.nt=s}_$AS(t,i){return this.update(t,i)}update(t,i){return this.render(...i)}} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Ut=(t,i)=>{var s,e;const n=t._$AN;if(void 0===n)return!1;for(const t of n)null===(e=(s=t)._$AO)||void 0===e||e.call(s,i,!1),Ut(t,i);return!0},Vt=t=>{let i,s;do{if(void 0===(i=t._$AM))break;s=i._$AN,s.delete(t),t=i}while(0===(null==s?void 0:s.size))},Rt=t=>{for(let i;i=t._$AM;t=i){let s=i._$AN;if(void 0===s)i._$AN=s=new Set;else if(s.has(t))break;s.add(t),Lt(i)}};function Nt(t){void 0!==this._$AN?(Vt(this),this._$AM=t,Rt(this)):this._$AM=t}function Ot(t,i=!1,s=0){const e=this._$AH,n=this._$AN;if(void 0!==n&&0!==n.size)if(i)if(Array.isArray(e))for(let t=s;t{var i,s,e,n;2==t.type&&(null!==(i=(e=t)._$AP)&&void 0!==i||(e._$AP=Ot),null!==(s=(n=t)._$AQ)&&void 0!==s||(n._$AQ=Nt))};class jt extends Pt{constructor(){super(...arguments),this._$AN=void 0}_$AT(t,i,s){super._$AT(t,i,s),Rt(this),this.isConnected=t._$AU}_$AO(t,i=!0){var s,e;t!==this.isConnected&&(this.isConnected=t,t?null===(s=this.reconnected)||void 0===s||s.call(this):null===(e=this.disconnected)||void 0===e||e.call(this)),i&&(Ut(this,t),Vt(this))}setValue(t){if(wt(this.et))this.et._$AI(t,this);else{const i=[...this.et._$AH];i[this.nt]=t,this.et._$AI(i,this,0)}}disconnected(){}reconnected(){}} +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */class zt{constructor(t){this.ot=t}disconnect(){this.ot=void 0}reconnect(t){this.ot=t}deref(){return this.ot}}class Ht{constructor(){this.rt=void 0,this.lt=void 0}get(){return this.rt}pause(){var t;null!==(t=this.rt)&&void 0!==t||(this.rt=new Promise((t=>this.lt=t)))}resume(){var t;null===(t=this.lt)||void 0===t||t.call(this),this.rt=this.lt=void 0}} +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */class It extends jt{constructor(){super(...arguments),this.ht=new zt(this),this.ut=new Ht}render(t,i){return D}update(t,[i,s]){if(this.isConnected||this.disconnected(),i===this.ct)return;this.ct=i;let e=0;const{ht:n,ut:o}=this;return(async(t,i)=>{for await(const s of t)if(!1===await i(s))return})(i,(async t=>{for(;o.get();)await o.get();const r=n.deref();if(void 0!==r){if(r.ct!==i)return!1;void 0!==s&&(t=s(t,e)),r.commitValue(t,e),e++}return!0})),D}commitValue(t,i){this.setValue(t)}disconnected(){this.ht.disconnect(),this.ut.pause()}reconnected(){this.ht.reconnect(this),this.ut.resume()}}const Bt=Mt(It),Dt=Mt( +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +class extends It{constructor(t){if(super(t),2!==t.type)throw Error("asyncAppend can only be used in child expressions")}update(t,i){return this.st=t,super.update(t,i)}commitValue(t,i){0===i&&At(this.st);const s=$t(this.st);St(s,t)}}),Wt=Mt( +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +class extends Pt{constructor(t){super(t),this.dt=new WeakMap}render(t){return[t]}update(t,[i]){if(bt(this.vt)&&(!bt(i)||this.vt.strings!==i.strings)){const i=Et(t).pop();let s=this.dt.get(this.vt.strings);if(void 0===s){const t=document.createDocumentFragment();s=rt(W,t),s.setConnected(!1),this.dt.set(this.vt.strings,s)}xt(s,[i]),$t(s,void 0,i)}if(bt(i)){if(!bt(this.vt)||this.vt.strings!==i.strings){const s=this.dt.get(i.strings);if(void 0!==s){const i=Et(s).pop();At(t),$t(t,void 0,i),xt(t,[i])}}this.vt=i}else this.vt=void 0;return this.render(i)}}),Zt=(t,i,s)=>{for(const s of i)if(s[0]===t)return(0,s[1])();return null==s?void 0:s()},qt=Mt( +/** + * @license + * Copyright 2018 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +class extends Pt{constructor(t){var i;if(super(t),1!==t.type||"class"!==t.name||(null===(i=t.strings)||void 0===i?void 0:i.length)>2)throw Error("`classMap()` can only be used in the `class` attribute and must be the only part in the attribute.")}render(t){return" "+Object.keys(t).filter((i=>t[i])).join(" ")+" "}update(t,[i]){var s,e;if(void 0===this.ft){this.ft=new Set,void 0!==t.strings&&(this.yt=new Set(t.strings.join(" ").split(/\s/).filter((t=>""!==t))));for(const t in i)i[t]&&!(null===(s=this.yt)||void 0===s?void 0:s.has(t))&&this.ft.add(t);return this.render(i)}const n=t.element.classList;this.ft.forEach((t=>{t in i||(n.remove(t),this.ft.delete(t))}));for(const t in i){const s=!!i[t];s===this.ft.has(t)||(null===(e=this.yt)||void 0===e?void 0:e.has(t))||(s?(n.add(t),this.ft.add(t)):(n.remove(t),this.ft.delete(t)))}return D}}),Ft={},Gt=Mt(class extends Pt{constructor(){super(...arguments),this.bt=Ft}render(t,i){return i()}update(t,[i,s]){if(Array.isArray(i)){if(Array.isArray(this.bt)&&this.bt.length===i.length&&i.every(((t,i)=>t===this.bt[i])))return D}else if(this.bt===i)return D;return this.bt=Array.isArray(i)?Array.from(i):i,this.render(i,s)}}),Jt=t=>null!=t?t:W +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */;function*Kt(t,i){const s="function"==typeof i;if(void 0!==t){let e=-1;for(const n of t)e>-1&&(yield s?i(e):i),e++,yield n}} +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */const Yt=Mt(class extends Pt{constructor(){super(...arguments),this.key=W}render(t,i){return this.key=t,i}update(t,[i,s]){return i!==this.key&&(xt(t),this.key=i),s}}),Qt=Mt( +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +class extends Pt{constructor(t){if(super(t),3!==t.type&&1!==t.type&&4!==t.type)throw Error("The `live` directive is not allowed on child or event bindings");if(!wt(t))throw Error("`live` bindings can only contain a single expression")}render(t){return t}update(t,[i]){if(i===D||i===W)return i;const s=t.element,e=t.name;if(3===t.type){if(i===s[e])return D}else if(4===t.type){if(!!i===s.hasAttribute(e))return D}else if(1===t.type&&s.getAttribute(e)===i+"")return D;return xt(t),i}}); +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +function*Xt(t,i){if(void 0!==t){let s=0;for(const e of t)yield i(e,s++)}} +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */function*ti(t,i,s=1){const e=void 0===i?0:t;null!=i||(i=t);for(let t=e;s>0?tnew si;class si{}const ei=new WeakMap,ni=Mt(class extends jt{render(t){return W}update(t,[i]){var s;const e=i!==this.ot;return e&&void 0!==this.ot&&this.gt(void 0),(e||this.wt!==this._t)&&(this.ot=i,this.$t=null===(s=t.options)||void 0===s?void 0:s.host,this.gt(this._t=t.element)),W}gt(t){var i;if("function"==typeof this.ot){const s=null!==(i=this.$t)&&void 0!==i?i:globalThis;let e=ei.get(s);void 0===e&&(e=new WeakMap,ei.set(s,e)),void 0!==e.get(this.ot)&&this.ot.call(this.$t,void 0),e.set(this.ot,t),void 0!==t&&this.ot.call(this.$t,t)}else this.ot.value=t}get wt(){var t,i,s;return"function"==typeof this.ot?null===(i=ei.get(null!==(t=this.$t)&&void 0!==t?t:globalThis))||void 0===i?void 0:i.get(this.ot):null===(s=this.ot)||void 0===s?void 0:s.value}disconnected(){this.wt===this._t&&this.gt(void 0)}reconnected(){this.gt(this._t)}}),oi=(t,i,s)=>{const e=new Map;for(let n=i;n<=s;n++)e.set(t[n],n);return e},ri=Mt(class extends Pt{constructor(t){if(super(t),2!==t.type)throw Error("repeat() can only be used in text expressions")}St(t,i,s){let e;void 0===s?s=i:void 0!==i&&(e=i);const n=[],o=[];let r=0;for(const i of t)n[r]=e?e(i,r):r,o[r]=s(i,r),r++;return{values:o,keys:n}}render(t,i,s){return this.St(t,i,s).values}update(t,[i,s,e]){var n;const o=Et(t),{values:r,keys:l}=this.St(i,s,e);if(!Array.isArray(o))return this.Tt=l,r;const h=null!==(n=this.Tt)&&void 0!==n?n:this.Tt=[],u=[];let c,a,d=0,v=o.length-1,f=0,p=r.length-1;for(;d<=v&&f<=p;)if(null===o[d])d++;else if(null===o[v])v--;else if(h[d]===l[f])u[f]=St(o[d],r[f]),d++,f++;else if(h[v]===l[p])u[p]=St(o[v],r[p]),v--,p--;else if(h[d]===l[p])u[p]=St(o[d],r[p]),$t(t,u[p+1],o[d]),d++,p--;else if(h[v]===l[f])u[f]=St(o[v],r[f]),$t(t,o[d],o[v]),v--,f++;else if(void 0===c&&(c=oi(l,f,p),a=oi(h,d,v)),c.has(h[d]))if(c.has(h[v])){const i=a.get(l[f]),s=void 0!==i?o[i]:null;if(null===s){const i=$t(t,o[d]);St(i,r[f]),u[f]=i}else u[f]=St(s,r[f]),$t(t,o[d],s),o[i]=null;f++}else Ct(o[v]),v--;else Ct(o[d]),d++;for(;f<=p;){const i=$t(t,u[p+1]);St(i,r[f]),u[f++]=i}for(;d<=v;){const t=o[d++];null!==t&&Ct(t)}return this.Tt=l,xt(t,u),D}}),li="important",hi=" !"+li,ui=Mt(class extends Pt{constructor(t){var i;if(super(t),1!==t.type||"style"!==t.name||(null===(i=t.strings)||void 0===i?void 0:i.length)>2)throw Error("The `styleMap` directive must be used in the `style` attribute and must be the only part in the attribute.")}render(t){return Object.keys(t).reduce(((i,s)=>{const e=t[s];return null==e?i:i+`${s=s.replace(/(?:^(webkit|moz|ms|o)|)(?=[A-Z])/g,"-$&").toLowerCase()}:${e};`}),"")}update(t,[i]){const{style:s}=t.element;if(void 0===this.xt){this.xt=new Set;for(const t in i)this.xt.add(t);return this.render(i)}this.xt.forEach((t=>{null==i[t]&&(this.xt.delete(t),t.includes("-")?s.removeProperty(t):s[t]="")}));for(const t in i){const e=i[t];if(null!=e){this.xt.add(t);const i=e.endsWith(hi);t.includes("-")||i?s.setProperty(t,i?e.slice(0,-11):e,i?li:""):s[t]=e}}return D}}),ci=Mt( +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */ +class extends Pt{constructor(t){if(super(t),2!==t.type)throw Error("templateContent can only be used in child bindings")}render(t){return this.Et===t?D:(this.Et=t,document.importNode(t.content,!0))}});class ai extends Pt{constructor(t){if(super(t),this.vt=W,2!==t.type)throw Error(this.constructor.directiveName+"() can only be used in child bindings")}render(t){if(t===W||null==t)return this.Ct=void 0,this.vt=t;if(t===D)return t;if("string"!=typeof t)throw Error(this.constructor.directiveName+"() called with a non-string value");if(t===this.vt)return this.Ct;this.vt=t;const i=[t];return i.raw=i,this.Ct={_$litType$:this.constructor.resultType,strings:i,values:[]}}}ai.directiveName="unsafeHTML",ai.resultType=1;const di=Mt(ai); +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */class vi extends ai{}vi.directiveName="unsafeSVG",vi.resultType=2;const fi=Mt(vi),pi=t=>!pt(t)&&"function"==typeof t.then,yi=1073741823; +/** + * @license + * Copyright 2017 Google LLC + * SPDX-License-Identifier: BSD-3-Clause + */class bi extends jt{constructor(){super(...arguments),this.At=yi,this.kt=[],this.ht=new zt(this),this.ut=new Ht}render(...t){var i;return null!==(i=t.find((t=>!pi(t))))&&void 0!==i?i:D}update(t,i){const s=this.kt;let e=s.length;this.kt=i;const n=this.ht,o=this.ut;this.isConnected||this.disconnected();for(let t=0;tthis.At);t++){const r=i[t];if(!pi(r))return this.At=t,r;t{for(;o.get();)await o.get();const i=n.deref();if(void 0!==i){const s=i.kt.indexOf(r);s>-1&&s{if((null==t?void 0:t.r)===wi)return null==t?void 0:t._$litStatic$},$i=t=>({_$litStatic$:t,r:wi}),Si=(t,...i)=>({_$litStatic$:i.reduce(((i,s,e)=>i+(t=>{if(void 0!==t._$litStatic$)return t._$litStatic$;throw Error(`Value passed to 'literal' function must be a 'literal' result: ${t}. Use 'unsafeStatic' to pass non-literal values, but\n take care to ensure page security.`)})(s)+t[e+1]),t[0]),r:wi}),Ti=new Map,xi=t=>(i,...s)=>{const e=s.length;let n,o;const r=[],l=[];let h,u=0,c=!1;for(;u;\n\n/**\n * A single CSSResult, CSSStyleSheet, or an array or nested arrays of those.\n */\nexport type CSSResultGroup = CSSResultOrNative | CSSResultArray;\n\nconst constructionToken = Symbol();\n\nconst cssTagCache = new WeakMap();\n\n/**\n * A container for a string of CSS text, that may be used to create a CSSStyleSheet.\n *\n * CSSResult is the return value of `css`-tagged template literals and\n * `unsafeCSS()`. In order to ensure that CSSResults are only created via the\n * `css` tag and `unsafeCSS()`, CSSResult cannot be constructed directly.\n */\nexport class CSSResult {\n // This property needs to remain unminified.\n ['_$cssResult$'] = true;\n readonly cssText: string;\n private _styleSheet?: CSSStyleSheet;\n private _strings: TemplateStringsArray | undefined;\n\n private constructor(\n cssText: string,\n strings: TemplateStringsArray | undefined,\n safeToken: symbol\n ) {\n if (safeToken !== constructionToken) {\n throw new Error(\n 'CSSResult is not constructable. Use `unsafeCSS` or `css` instead.'\n );\n }\n this.cssText = cssText;\n this._strings = strings;\n }\n\n // This is a getter so that it's lazy. In practice, this means stylesheets\n // are not created until the first element instance is made.\n get styleSheet(): CSSStyleSheet | undefined {\n // If `supportsAdoptingStyleSheets` is true then we assume CSSStyleSheet is\n // constructable.\n let styleSheet = this._styleSheet;\n const strings = this._strings;\n if (supportsAdoptingStyleSheets && styleSheet === undefined) {\n const cacheable = strings !== undefined && strings.length === 1;\n if (cacheable) {\n styleSheet = cssTagCache.get(strings);\n }\n if (styleSheet === undefined) {\n (this._styleSheet = styleSheet = new CSSStyleSheet()).replaceSync(\n this.cssText\n );\n if (cacheable) {\n cssTagCache.set(strings, styleSheet);\n }\n }\n }\n return styleSheet;\n }\n\n toString(): string {\n return this.cssText;\n }\n}\n\ntype ConstructableCSSResult = CSSResult & {\n new (\n cssText: string,\n strings: TemplateStringsArray | undefined,\n safeToken: symbol\n ): CSSResult;\n};\n\nconst textFromCSSResult = (value: CSSResultGroup | number) => {\n // This property needs to remain unminified.\n if ((value as CSSResult)['_$cssResult$'] === true) {\n return (value as CSSResult).cssText;\n } else if (typeof value === 'number') {\n return value;\n } else {\n throw new Error(\n `Value passed to 'css' function must be a 'css' function result: ` +\n `${value}. Use 'unsafeCSS' to pass non-literal values, but take care ` +\n `to ensure page security.`\n );\n }\n};\n\n/**\n * Wrap a value for interpolation in a {@linkcode css} tagged template literal.\n *\n * This is unsafe because untrusted CSS text can be used to phone home\n * or exfiltrate data to an attacker controlled site. Take care to only use\n * this with trusted input.\n */\nexport const unsafeCSS = (value: unknown) =>\n new (CSSResult as ConstructableCSSResult)(\n typeof value === 'string' ? value : String(value),\n undefined,\n constructionToken\n );\n\n/**\n * A template literal tag which can be used with LitElement's\n * {@linkcode LitElement.styles} property to set element styles.\n *\n * For security reasons, only literal string values and number may be used in\n * embedded expressions. To incorporate non-literal values {@linkcode unsafeCSS}\n * may be used inside an expression.\n */\nexport const css = (\n strings: TemplateStringsArray,\n ...values: (CSSResultGroup | number)[]\n): CSSResult => {\n const cssText =\n strings.length === 1\n ? strings[0]\n : values.reduce(\n (acc, v, idx) => acc + textFromCSSResult(v) + strings[idx + 1],\n strings[0]\n );\n return new (CSSResult as ConstructableCSSResult)(\n cssText,\n strings,\n constructionToken\n );\n};\n\n/**\n * Applies the given styles to a `shadowRoot`. When Shadow DOM is\n * available but `adoptedStyleSheets` is not, styles are appended to the\n * `shadowRoot` to [mimic spec behavior](https://wicg.github.io/construct-stylesheets/#using-constructed-stylesheets).\n * Note, when shimming is used, any styles that are subsequently placed into\n * the shadowRoot should be placed *before* any shimmed adopted styles. This\n * will match spec behavior that gives adopted sheets precedence over styles in\n * shadowRoot.\n */\nexport const adoptStyles = (\n renderRoot: ShadowRoot,\n styles: Array\n) => {\n if (supportsAdoptingStyleSheets) {\n (renderRoot as ShadowRoot).adoptedStyleSheets = styles.map((s) =>\n s instanceof CSSStyleSheet ? s : s.styleSheet!\n );\n } else {\n styles.forEach((s) => {\n const style = document.createElement('style');\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const nonce = (global as any)['litNonce'];\n if (nonce !== undefined) {\n style.setAttribute('nonce', nonce);\n }\n style.textContent = (s as CSSResult).cssText;\n renderRoot.appendChild(style);\n });\n }\n};\n\nconst cssResultFromStyleSheet = (sheet: CSSStyleSheet) => {\n let cssText = '';\n for (const rule of sheet.cssRules) {\n cssText += rule.cssText;\n }\n return unsafeCSS(cssText);\n};\n\nexport const getCompatibleStyle =\n supportsAdoptingStyleSheets ||\n (NODE_MODE && global.CSSStyleSheet === undefined)\n ? (s: CSSResultOrNative) => s\n : (s: CSSResultOrNative) =>\n s instanceof CSSStyleSheet ? cssResultFromStyleSheet(s) : s;\n","/**\n * @license\n * Copyright 2017 Google LLC\n * SPDX-License-Identifier: BSD-3-Clause\n */\n\n/**\n * Use this module if you want to create your own base class extending\n * {@link ReactiveElement}.\n * @packageDocumentation\n */\n\nimport {\n getCompatibleStyle,\n adoptStyles,\n CSSResultGroup,\n CSSResultOrNative,\n} from './css-tag.js';\nimport type {\n ReactiveController,\n ReactiveControllerHost,\n} from './reactive-controller.js';\n\n// In the Node build, this import will be injected by Rollup:\n// import {HTMLElement, customElements} from '@lit-labs/ssr-dom-shim';\n\nexport * from './css-tag.js';\nexport type {\n ReactiveController,\n ReactiveControllerHost,\n} from './reactive-controller.js';\n\nconst NODE_MODE = false;\nconst global = NODE_MODE ? globalThis : window;\n\nif (NODE_MODE) {\n global.customElements ??= customElements;\n}\n\nconst DEV_MODE = true;\n\nlet requestUpdateThenable: (name: string) => {\n then: (\n onfulfilled?: (value: boolean) => void,\n _onrejected?: () => void\n ) => void;\n};\n\nlet issueWarning: (code: string, warning: string) => void;\n\nconst trustedTypes = (global as unknown as {trustedTypes?: {emptyScript: ''}})\n .trustedTypes;\n\n// Temporary workaround for https://crbug.com/993268\n// Currently, any attribute starting with \"on\" is considered to be a\n// TrustedScript source. Such boolean attributes must be set to the equivalent\n// trusted emptyScript value.\nconst emptyStringForBooleanAttribute = trustedTypes\n ? (trustedTypes.emptyScript as unknown as '')\n : '';\n\nconst polyfillSupport = DEV_MODE\n ? global.reactiveElementPolyfillSupportDevMode\n : global.reactiveElementPolyfillSupport;\n\nif (DEV_MODE) {\n // Ensure warnings are issued only 1x, even if multiple versions of Lit\n // are loaded.\n const issuedWarnings: Set = (global.litIssuedWarnings ??=\n new Set());\n\n // Issue a warning, if we haven't already.\n issueWarning = (code: string, warning: string) => {\n warning += ` See https://lit.dev/msg/${code} for more information.`;\n if (!issuedWarnings.has(warning)) {\n console.warn(warning);\n issuedWarnings.add(warning);\n }\n };\n\n issueWarning(\n 'dev-mode',\n `Lit is in dev mode. Not recommended for production!`\n );\n\n // Issue polyfill support warning.\n if (global.ShadyDOM?.inUse && polyfillSupport === undefined) {\n issueWarning(\n 'polyfill-support-missing',\n `Shadow DOM is being polyfilled via \\`ShadyDOM\\` but ` +\n `the \\`polyfill-support\\` module has not been loaded.`\n );\n }\n\n requestUpdateThenable = (name) => ({\n then: (\n onfulfilled?: (value: boolean) => void,\n _onrejected?: () => void\n ) => {\n issueWarning(\n 'request-update-promise',\n `The \\`requestUpdate\\` method should no longer return a Promise but ` +\n `does so on \\`${name}\\`. Use \\`updateComplete\\` instead.`\n );\n if (onfulfilled !== undefined) {\n onfulfilled(false);\n }\n },\n });\n}\n\n/**\n * Contains types that are part of the unstable debug API.\n *\n * Everything in this API is not stable and may change or be removed in the future,\n * even on patch releases.\n */\n// eslint-disable-next-line @typescript-eslint/no-namespace\nexport namespace ReactiveUnstable {\n /**\n * When Lit is running in dev mode and `window.emitLitDebugLogEvents` is true,\n * we will emit 'lit-debug' events to window, with live details about the update and render\n * lifecycle. These can be useful for writing debug tooling and visualizations.\n *\n * Please be aware that running with window.emitLitDebugLogEvents has performance overhead,\n * making certain operations that are normally very cheap (like a no-op render) much slower,\n * because we must copy data and dispatch events.\n */\n // eslint-disable-next-line @typescript-eslint/no-namespace\n export namespace DebugLog {\n export type Entry = Update;\n export interface Update {\n kind: 'update';\n }\n }\n}\n\ninterface DebugLoggingWindow {\n // Even in dev mode, we generally don't want to emit these events, as that's\n // another level of cost, so only emit them when DEV_MODE is true _and_ when\n // window.emitLitDebugEvents is true.\n emitLitDebugLogEvents?: boolean;\n}\n\n/**\n * Useful for visualizing and logging insights into what the Lit template system is doing.\n *\n * Compiled out of prod mode builds.\n */\nconst debugLogEvent = DEV_MODE\n ? (event: ReactiveUnstable.DebugLog.Entry) => {\n const shouldEmit = (global as unknown as DebugLoggingWindow)\n .emitLitDebugLogEvents;\n if (!shouldEmit) {\n return;\n }\n global.dispatchEvent(\n new CustomEvent('lit-debug', {\n detail: event,\n })\n );\n }\n : undefined;\n\n/*\n * When using Closure Compiler, JSCompiler_renameProperty(property, object) is\n * replaced at compile time by the munged name for object[property]. We cannot\n * alias this function, so we have to use a small shim that has the same\n * behavior when not compiling.\n */\n/*@__INLINE__*/\nconst JSCompiler_renameProperty =

(\n prop: P,\n _obj: unknown\n): P => prop;\n\n/**\n * Converts property values to and from attribute values.\n */\nexport interface ComplexAttributeConverter {\n /**\n * Called to convert an attribute value to a property\n * value.\n */\n fromAttribute?(value: string | null, type?: TypeHint): Type;\n\n /**\n * Called to convert a property value to an attribute\n * value.\n *\n * It returns unknown instead of string, to be compatible with\n * https://github.com/WICG/trusted-types (and similar efforts).\n */\n toAttribute?(value: Type, type?: TypeHint): unknown;\n}\n\ntype AttributeConverter =\n | ComplexAttributeConverter\n | ((value: string | null, type?: TypeHint) => Type);\n\n/**\n * Defines options for a property accessor.\n */\nexport interface PropertyDeclaration {\n /**\n * When set to `true`, indicates the property is internal private state. The\n * property should not be set by users. When using TypeScript, this property\n * should be marked as `private` or `protected`, and it is also a common\n * practice to use a leading `_` in the name. The property is not added to\n * `observedAttributes`.\n */\n readonly state?: boolean;\n\n /**\n * Indicates how and whether the property becomes an observed attribute.\n * If the value is `false`, the property is not added to `observedAttributes`.\n * If true or absent, the lowercased property name is observed (e.g. `fooBar`\n * becomes `foobar`). If a string, the string value is observed (e.g\n * `attribute: 'foo-bar'`).\n */\n readonly attribute?: boolean | string;\n\n /**\n * Indicates the type of the property. This is used only as a hint for the\n * `converter` to determine how to convert the attribute\n * to/from a property.\n */\n readonly type?: TypeHint;\n\n /**\n * Indicates how to convert the attribute to/from a property. If this value\n * is a function, it is used to convert the attribute value a the property\n * value. If it's an object, it can have keys for `fromAttribute` and\n * `toAttribute`. If no `toAttribute` function is provided and\n * `reflect` is set to `true`, the property value is set directly to the\n * attribute. A default `converter` is used if none is provided; it supports\n * `Boolean`, `String`, `Number`, `Object`, and `Array`. Note,\n * when a property changes and the converter is used to update the attribute,\n * the property is never updated again as a result of the attribute changing,\n * and vice versa.\n */\n readonly converter?: AttributeConverter;\n\n /**\n * Indicates if the property should reflect to an attribute.\n * If `true`, when the property is set, the attribute is set using the\n * attribute name determined according to the rules for the `attribute`\n * property option and the value of the property converted using the rules\n * from the `converter` property option.\n */\n readonly reflect?: boolean;\n\n /**\n * A function that indicates if a property should be considered changed when\n * it is set. The function should take the `newValue` and `oldValue` and\n * return `true` if an update should be requested.\n */\n hasChanged?(value: Type, oldValue: Type): boolean;\n\n /**\n * Indicates whether an accessor will be created for this property. By\n * default, an accessor will be generated for this property that requests an\n * update when set. If this flag is `true`, no accessor will be created, and\n * it will be the user's responsibility to call\n * `this.requestUpdate(propertyName, oldValue)` to request an update when\n * the property changes.\n */\n readonly noAccessor?: boolean;\n}\n\n/**\n * Map of properties to PropertyDeclaration options. For each property an\n * accessor is made, and the property is processed according to the\n * PropertyDeclaration options.\n */\nexport interface PropertyDeclarations {\n readonly [key: string]: PropertyDeclaration;\n}\n\ntype PropertyDeclarationMap = Map;\n\ntype AttributeMap = Map;\n\n/**\n * A Map of property keys to values.\n *\n * Takes an optional type parameter T, which when specified as a non-any,\n * non-unknown type, will make the Map more strongly-typed, associating the map\n * keys with their corresponding value type on T.\n *\n * Use `PropertyValues` when overriding ReactiveElement.update() and\n * other lifecycle methods in order to get stronger type-checking on keys\n * and values.\n */\n// This type is conditional so that if the parameter T is not specified, or\n// is `any`, the type will include `Map`. Since T is not\n// given in the uses of PropertyValues in this file, all uses here fallback to\n// meaning `Map`, but if a developer uses\n// `PropertyValues` (or any other value for T) they will get a\n// strongly-typed Map type.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type PropertyValues = T extends object\n ? PropertyValueMap\n : Map;\n\n/**\n * Do not use, instead prefer {@linkcode PropertyValues}.\n */\n// This type must be exported such that JavaScript generated by the Google\n// Closure Compiler can import a type reference.\nexport interface PropertyValueMap extends Map {\n get(k: K): T[K];\n set(key: K, value: T[K]): this;\n has(k: K): boolean;\n delete(k: K): boolean;\n}\n\nexport const defaultConverter: ComplexAttributeConverter = {\n toAttribute(value: unknown, type?: unknown): unknown {\n switch (type) {\n case Boolean:\n value = value ? emptyStringForBooleanAttribute : null;\n break;\n case Object:\n case Array:\n // if the value is `null` or `undefined` pass this through\n // to allow removing/no change behavior.\n value = value == null ? value : JSON.stringify(value);\n break;\n }\n return value;\n },\n\n fromAttribute(value: string | null, type?: unknown) {\n let fromValue: unknown = value;\n switch (type) {\n case Boolean:\n fromValue = value !== null;\n break;\n case Number:\n fromValue = value === null ? null : Number(value);\n break;\n case Object:\n case Array:\n // Do *not* generate exception when invalid JSON is set as elements\n // don't normally complain on being mis-configured.\n // TODO(sorvell): Do generate exception in *dev mode*.\n try {\n // Assert to adhere to Bazel's \"must type assert JSON parse\" rule.\n fromValue = JSON.parse(value!) as unknown;\n } catch (e) {\n fromValue = null;\n }\n break;\n }\n return fromValue;\n },\n};\n\nexport interface HasChanged {\n (value: unknown, old: unknown): boolean;\n}\n\n/**\n * Change function that returns true if `value` is different from `oldValue`.\n * This method is used as the default for a property's `hasChanged` function.\n */\nexport const notEqual: HasChanged = (value: unknown, old: unknown): boolean => {\n // This ensures (old==NaN, value==NaN) always returns false\n return old !== value && (old === old || value === value);\n};\n\nconst defaultPropertyDeclaration: PropertyDeclaration = {\n attribute: true,\n type: String,\n converter: defaultConverter,\n reflect: false,\n hasChanged: notEqual,\n};\n\n/**\n * The Closure JS Compiler doesn't currently have good support for static\n * property semantics where \"this\" is dynamic (e.g.\n * https://github.com/google/closure-compiler/issues/3177 and others) so we use\n * this hack to bypass any rewriting by the compiler.\n */\nconst finalized = 'finalized';\n\n/**\n * A string representing one of the supported dev mode warning categories.\n */\nexport type WarningKind = 'change-in-update' | 'migration';\n\nexport type Initializer = (element: ReactiveElement) => void;\n\n/**\n * Base element class which manages element properties and attributes. When\n * properties change, the `update` method is asynchronously called. This method\n * should be supplied by subclassers to render updates as desired.\n * @noInheritDoc\n */\nexport abstract class ReactiveElement\n // In the Node build, this `extends` clause will be substituted with\n // `(globalThis.HTMLElement ?? HTMLElement)`.\n //\n // This way, we will first prefer any global `HTMLElement` polyfill that the\n // user has assigned, and then fall back to the `HTMLElement` shim which has\n // been imported (see note at the top of this file about how this import is\n // generated by Rollup). Note that the `HTMLElement` variable has been\n // shadowed by this import, so it no longer refers to the global.\n extends HTMLElement\n implements ReactiveControllerHost\n{\n // Note: these are patched in only in DEV_MODE.\n /**\n * Read or set all the enabled warning categories for this class.\n *\n * This property is only used in development builds.\n *\n * @nocollapse\n * @category dev-mode\n */\n static enabledWarnings?: WarningKind[];\n\n /**\n * Enable the given warning category for this class.\n *\n * This method only exists in development builds, so it should be accessed\n * with a guard like:\n *\n * ```ts\n * // Enable for all ReactiveElement subclasses\n * ReactiveElement.enableWarning?.('migration');\n *\n * // Enable for only MyElement and subclasses\n * MyElement.enableWarning?.('migration');\n * ```\n *\n * @nocollapse\n * @category dev-mode\n */\n static enableWarning?: (warningKind: WarningKind) => void;\n\n /**\n * Disable the given warning category for this class.\n *\n * This method only exists in development builds, so it should be accessed\n * with a guard like:\n *\n * ```ts\n * // Disable for all ReactiveElement subclasses\n * ReactiveElement.disableWarning?.('migration');\n *\n * // Disable for only MyElement and subclasses\n * MyElement.disableWarning?.('migration');\n * ```\n *\n * @nocollapse\n * @category dev-mode\n */\n static disableWarning?: (warningKind: WarningKind) => void;\n\n /**\n * Adds an initializer function to the class that is called during instance\n * construction.\n *\n * This is useful for code that runs against a `ReactiveElement`\n * subclass, such as a decorator, that needs to do work for each\n * instance, such as setting up a `ReactiveController`.\n *\n * ```ts\n * const myDecorator = (target: typeof ReactiveElement, key: string) => {\n * target.addInitializer((instance: ReactiveElement) => {\n * // This is run during construction of the element\n * new MyController(instance);\n * });\n * }\n * ```\n *\n * Decorating a field will then cause each instance to run an initializer\n * that adds a controller:\n *\n * ```ts\n * class MyElement extends LitElement {\n * @myDecorator foo;\n * }\n * ```\n *\n * Initializers are stored per-constructor. Adding an initializer to a\n * subclass does not add it to a superclass. Since initializers are run in\n * constructors, initializers will run in order of the class hierarchy,\n * starting with superclasses and progressing to the instance's class.\n *\n * @nocollapse\n */\n static addInitializer(initializer: Initializer) {\n this.finalize();\n (this._initializers ??= []).push(initializer);\n }\n\n static _initializers?: Initializer[];\n\n /*\n * Due to closure compiler ES6 compilation bugs, @nocollapse is required on\n * all static methods and properties with initializers. Reference:\n * - https://github.com/google/closure-compiler/issues/1776\n */\n\n /**\n * Maps attribute names to properties; for example `foobar` attribute to\n * `fooBar` property. Created lazily on user subclasses when finalizing the\n * class.\n * @nocollapse\n */\n private static __attributeToPropertyMap: AttributeMap;\n\n /**\n * Marks class as having finished creating properties.\n */\n protected static [finalized] = true;\n\n /**\n * Memoized list of all element properties, including any superclass properties.\n * Created lazily on user subclasses when finalizing the class.\n * @nocollapse\n * @category properties\n */\n static elementProperties: PropertyDeclarationMap = new Map();\n\n /**\n * User-supplied object that maps property names to `PropertyDeclaration`\n * objects containing options for configuring reactive properties. When\n * a reactive property is set the element will update and render.\n *\n * By default properties are public fields, and as such, they should be\n * considered as primarily settable by element users, either via attribute or\n * the property itself.\n *\n * Generally, properties that are changed by the element should be private or\n * protected fields and should use the `state: true` option. Properties\n * marked as `state` do not reflect from the corresponding attribute\n *\n * However, sometimes element code does need to set a public property. This\n * should typically only be done in response to user interaction, and an event\n * should be fired informing the user; for example, a checkbox sets its\n * `checked` property when clicked and fires a `changed` event. Mutating\n * public properties should typically not be done for non-primitive (object or\n * array) properties. In other cases when an element needs to manage state, a\n * private property set with the `state: true` option should be used. When\n * needed, state properties can be initialized via public properties to\n * facilitate complex interactions.\n * @nocollapse\n * @category properties\n */\n static properties: PropertyDeclarations;\n\n /**\n * Memoized list of all element styles.\n * Created lazily on user subclasses when finalizing the class.\n * @nocollapse\n * @category styles\n */\n static elementStyles: Array = [];\n\n /**\n * Array of styles to apply to the element. The styles should be defined\n * using the {@linkcode css} tag function, via constructible stylesheets, or\n * imported from native CSS module scripts.\n *\n * Note on Content Security Policy:\n *\n * Element styles are implemented with `