224 files

This commit is contained in:
Home Assistant Version Control
2026-07-27 13:30:34 +00:00
parent b5723aa856
commit e36b8a1a22
224 changed files with 9967 additions and 2663 deletions
+29 -9
View File
@@ -103,6 +103,7 @@ from .const import (
DEVICE_TYPE_OTHER,
DEFAULT_START_DURATION_THRESHOLD,
CONF_START_DURATION_THRESHOLD,
CONF_RUNNING_DEAD_ZONE,
)
from .log_utils import DeviceLoggerAdapter
@@ -137,7 +138,7 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
)
return False
if version == 3 and minor_version >= 7:
if version == 3 and minor_version >= 8:
return True
# 3.6 → 3.7: remove initial_profile stub key from entry.data.
@@ -149,7 +150,17 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
minor_version = 7
_log.debug("Migrated WashData entry from 3.6 to 3.7")
if version == 3 and minor_version >= 7:
# 3.7 → 3.8: drop running_dead_zone from options (the setting was never
# wired to any detection logic and has been retired in 0.5.3).
if version == 3 and minor_version == 7:
new_opts = {k: v for k, v in entry.options.items() if k != CONF_RUNNING_DEAD_ZONE}
hass.config_entries.async_update_entry(
entry, options=new_opts, minor_version=8
)
minor_version = 8
_log.debug("Migrated WashData entry from 3.7 to 3.8 (removed running_dead_zone)")
if version == 3 and minor_version >= 8:
return True
data: dict[str, Any] = dict(entry.data)
@@ -279,12 +290,16 @@ async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
# so no stale removed value can linger there; the flow/manager read it from
# options (options-first).
# Strip the now-retired running_dead_zone from options in the bulk path too
# (covers entries that migrate straight from v1/v2/early-v3 in one pass).
options.pop(CONF_RUNNING_DEAD_ZONE, None)
hass.config_entries.async_update_entry(
entry,
data=data,
options=options,
version=3,
minor_version=7,
minor_version=8,
)
_log.info(
"Migrated WashData entry from version %s.%s to 3.7", version, minor_version
@@ -403,13 +418,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
manager = hass.data[DOMAIN][entry_id]
# Assign existing profile or remove label
target_profile = profile_name if profile_name else None
try:
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)
await manager.profile_store.assign_profile_to_cycle(
cycle_id, target_profile
)
except ValueError as exc:
raise ServiceValidationError(
translation_domain=DOMAIN,
@@ -417,6 +430,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
translation_placeholders={"error": str(exc)},
) from exc
# A manual (re)label answers the "was this detected right?" question,
# so clear any pending feedback and drop it from the review queue (#331).
if hasattr(manager, "learning_manager"):
await manager.learning_manager.async_resolve_pending_from_label(
cycle_id, target_profile
)
manager.notify_update()
hass.services.async_register(DOMAIN, "label_cycle", handle_label_cycle)
+147 -54
View File
@@ -162,6 +162,32 @@ def find_best_alignment(
return float(score), {"mae": float(mae), "corr": float(corr)}, final_offset
def _dtw_lite_scalar(x: np.ndarray, y: np.ndarray, n: int, m: int, w: int) -> float:
"""Verbatim original scalar fill for :func:`compute_dtw_lite` — kept as the
correctness reference and automatic fallback on unexpected errors."""
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"))
val_x = x[i - 1]
for j in range(start_j, end_j + 1):
cost = abs(float(val_x - y[j - 1]))
m1 = prev_row[j]
m2 = curr_row[j - 1]
m3 = prev_row[j - 1]
if m1 < m2:
best_prev = m1 if m1 < m3 else m3
else:
best_prev = m2 if m2 < m3 else m3
curr_row[j] = cost + best_prev
prev_row[:] = curr_row[:]
return float(prev_row[m])
def compute_dtw_lite(
x: np.ndarray, y: np.ndarray, band_width_ratio: float = 0.1,
derivative: bool = False,
@@ -173,6 +199,16 @@ def compute_dtw_lite(
When ``derivative`` is True this warps on the first derivative (slope) of the
two curves (Derivative DTW): alignment is driven by shape/transitions rather
than absolute power level, which is robust to amplitude offset and scale.
The inner loop operates on Python-native float lists (converted via ``.tolist()``
once per row) to avoid per-element NumPy scalar boxing overhead. The results
for each row are written back as a single slice assignment. For the typical
matching case (n=m=200, band=0.1 → w=20, ~41 cells/row) this is ~1.9× faster
than the original element-by-element NumPy indexing loop. The anti-diagonal
vectorized fill from :func:`_dtw_cost_matrix_vectorized` is NOT used here
because its per-diagonal Python setup overhead dominates for small n (it is
2× *slower* than the scalar loop for n=200 — the opposite of its large-n
envelope-rebuild behaviour where it wins by 1.68×).
"""
if derivative:
x = np.gradient(np.asarray(x, dtype=float)) if len(x) > 1 else np.asarray(x, dtype=float)
@@ -181,56 +217,60 @@ def compute_dtw_lite(
if n == 0 or m == 0:
return float("inf")
# Band width
xf = np.asarray(x, dtype=float)
yf = np.asarray(y, dtype=float)
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
try:
# Precompute band bounds for all rows (eliminates per-row int/max/min calls).
i_idx = np.arange(1, n + 1, dtype=float)
centers = (i_idx * (m / n)).astype(np.intp)
start_js = np.maximum(1, centers - w)
end_js = np.minimum(m, centers + w + 1)
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)
# Convert y to a plain Python list once so that inner-loop element access
# is native float retrieval rather than NumPy scalar unboxing.
ylist = yf.tolist()
curr_row.fill(float("inf"))
prev_row = np.full(m + 1, np.inf)
curr_row = np.full(m + 1, np.inf)
prev_row[0] = 0.0
# 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 i in range(n):
sj = int(start_js[i])
ej = int(end_js[i])
curr_row[:] = np.inf
val_x = float(xf[i])
for j in range(start_j, end_j + 1):
cost = abs(float(val_x - y[j - 1]))
# Convert the relevant prev_row slice to Python lists once per row.
# prev_prev[k] == prev_row[sj - 1 + k] (diagonal predecessor of cell j=sj+k)
# prev_curr[k] == prev_row[sj + k] (up-predecessor of cell j=sj+k)
prev_prev = prev_row[sj - 1 : ej].tolist() # length = ej - sj + 1
prev_curr = prev_row[sj : ej + 1].tolist() # length = ej - sj + 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]
row_vals: list[float] = []
prev_j_val = np.inf # curr_row[sj - 1] — left predecessor, maintained locally
for y_val, pr_j1, pr_j in zip(ylist[sj - 1 : ej], prev_prev, prev_curr):
cost = abs(val_x - y_val)
# min(up=pr_j, left=prev_j_val, diag=pr_j1)
best = pr_j if pr_j < prev_j_val else prev_j_val
if pr_j1 < best:
best = pr_j1
prev_j_val = cost + best
row_vals.append(prev_j_val)
# Use a slightly faster min implementation if possible
m1 = prev_row[j]
m2 = curr_row[j - 1]
m3 = prev_row[j - 1]
curr_row[sj : ej + 1] = row_vals # single slice write
if m1 < m2:
if m1 < m3:
best_prev = m1
else:
best_prev = m3
else:
if m2 < m3:
best_prev = m2
else:
best_prev = m3
prev_row, curr_row = curr_row, prev_row # swap without copy
curr_row[j] = cost + best_prev
# Swap rows
prev_row[:] = curr_row[:]
return float(prev_row[m])
return float(prev_row[m])
except Exception: # pylint: disable=broad-exception-caught
# The scalar reference is byte-identical (proven by tests), so degrade to it on
# any unexpected error rather than propagating out of the unguarded Stage-3
# refinement loop in compute_matches_worker. Mirrors compute_dtw_path.
_LOGGER.debug("compute_dtw_lite vectorized path failed; using scalar fallback", exc_info=True)
return _dtw_lite_scalar(xf, yf, n, m, w)
def _resample_to(arr: np.ndarray, n: int) -> np.ndarray:
"""Linearly resample a 1-D array to exactly ``n`` points over its index span.
@@ -403,6 +443,69 @@ def compute_matches_worker(
return candidates
def _dtw_cost_matrix_scalar(
x: np.ndarray, y: np.ndarray, n: int, m: int, w: int
) -> np.ndarray:
"""Reference (scalar) Sakoe-Chiba DTW cost-matrix fill. Kept verbatim as the
fallback for :func:`_dtw_cost_matrix_vectorized` so behavior can never regress."""
cost_matrix = np.full((n + 1, m + 1), float("inf"))
cost_matrix[0, 0] = 0
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]
)
return cost_matrix
def _dtw_cost_matrix_vectorized(
x: np.ndarray, y: np.ndarray, n: int, m: int, w: int
) -> np.ndarray:
"""Bit-identical vectorized fill of the scalar cost matrix.
The DTW recurrence is sequential, but all cells on one anti-diagonal
(``i + j`` constant) depend only on earlier anti-diagonals, so each diagonal
is one vectorized NumPy update instead of thousands of Python ``min``/``abs``
calls. The Sakoe-Chiba band, the per-row bounds (``int`` truncation), the
``local + min(up, left, diag)`` recurrence and out-of-band ``inf`` cells all
match the scalar loop exactly, so the resulting matrix - and the backtracked
path - is identical. (#311 follow-up: this fill dominates envelope rebuilds.)
"""
xf = np.asarray(x, dtype=float)
yf = np.asarray(y, dtype=float)
cost_matrix = np.full((n + 1, m + 1), np.inf)
cost_matrix[0, 0] = 0.0
# Per-row band bounds, identical to the scalar start_j/end_j (int truncates
# toward zero, matching Python int()).
i_idx = np.arange(1, n + 1)
center = i_idx * (m / n)
lo = np.maximum(1, (center - w).astype(np.int64))
hi = np.minimum(m, (center + w).astype(np.int64) + 1)
for d in range(2, n + m + 1):
i_lo = max(1, d - m)
i_hi = min(n, d - 1)
if i_lo > i_hi:
continue
ii = np.arange(i_lo, i_hi + 1)
jj = d - ii
inb = (jj >= lo[ii - 1]) & (jj <= hi[ii - 1])
if not inb.any():
continue
ib = ii[inb]
jb = jj[inb]
local = np.abs(xf[ib - 1] - yf[jb - 1])
best = np.minimum(
np.minimum(cost_matrix[ib - 1, jb], cost_matrix[ib, jb - 1]),
cost_matrix[ib - 1, jb - 1],
)
cost_matrix[ib, jb] = local + best
return cost_matrix
def compute_dtw_path(
x: np.ndarray, y: np.ndarray, band_width_ratio: float = 0.1
) -> list[tuple[int, int]]:
@@ -415,20 +518,10 @@ def compute_dtw_path(
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]
)
try:
cost_matrix = _dtw_cost_matrix_vectorized(x, y, n, m, w)
except Exception: # pylint: disable=broad-exception-caught
cost_matrix = _dtw_cost_matrix_scalar(x, y, n, m, w)
# Backtracking
if np.isinf(cost_matrix[n, m]):
+1 -1
View File
@@ -147,7 +147,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): # pylint: disable=a
"""Handle a config flow for WashData."""
VERSION = 3
MINOR_VERSION = 7
MINOR_VERSION = 8
def __init__(self) -> None:
"""Initialize the config flow."""
+22 -3
View File
@@ -80,7 +80,7 @@ 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_RUNNING_DEAD_ZONE = "running_dead_zone" # Seconds after start to ignore power dips
CONF_RUNNING_DEAD_ZONE = "running_dead_zone" # REMOVED in 0.5.3 — was never wired to detection
CONF_END_REPEAT_COUNT = "end_repeat_count" # Number of times end condition must be met
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
@@ -131,6 +131,7 @@ CONF_ANTI_WRINKLE_ENABLED = "anti_wrinkle_enabled" # Dryer anti-wrinkle shieldi
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_ANTI_WRINKLE_IDLE_TIMEOUT = "anti_wrinkle_idle_timeout" # Seconds below exit power before anti-wrinkle ends
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
@@ -268,7 +269,6 @@ 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
@@ -379,6 +379,12 @@ 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
# Quiet gap a machine may leave between two anti-wrinkle pulses before the mode
# ends. Keeps the previous hardcoded behaviour as the default; dryers whose
# pulses sit further apart (a heat-pump dryer measured 130-660 s) need a higher
# value, otherwise the mode drops out after the first pulse and every later one
# surfaces as an aborted false start.
DEFAULT_ANTI_WRINKLE_IDLE_TIMEOUT = 120.0 # s
# Delayed-start detection defaults (disabled by default).
#
@@ -879,7 +885,6 @@ SHAREABLE_SETTING_KEYS: tuple[str, ...] = (
CONF_START_DURATION_THRESHOLD,
CONF_START_ENERGY_THRESHOLD,
CONF_COMPLETION_MIN_SECONDS,
CONF_RUNNING_DEAD_ZONE,
CONF_MIN_OFF_GAP,
CONF_END_ENERGY_THRESHOLD,
CONF_POWER_OFF_THRESHOLD_W,
@@ -994,3 +999,17 @@ MAINTENANCE_EVENT_TYPES = (
# A logged maintenance event of a matching type within this many days suppresses
# the "needs maintenance" nag advisory (duration-trend / shape-drift).
MAINTENANCE_RECENT_SUPPRESS_DAYS = 30
# ─── Playground stress-tail constants (never used by the live integration) ─────
# These govern the synthetic idle continuation in the "Test idle termination"
# Playground toggle. All times are in seconds.
PLAYGROUND_STRESS_TRAILING_WINDOW_S: float = 60.0 # window for idle-floor derivation
PLAYGROUND_STRESS_FLOOR_PERCENTILE: float = 0.07 # p7 of window readings = standby floor
PLAYGROUND_STRESS_FLUCT_FALLBACK_FRAC: float = 0.12 # ±12% fallback when window is flat
PLAYGROUND_STRESS_DENSE_STEP_S: float = 30.0 # dense pre-fill cadence
PLAYGROUND_STRESS_DENSE_DURATION_S: float = 1200.0 # dense pre-fill length (20 min)
PLAYGROUND_STRESS_SPARSE_STEP_S: float = 1800.0 # sparse main step (30 min)
PLAYGROUND_STRESS_MAX_SPARSE_STEPS: int = 15 # max sparse steps → max 7.5 h extra
PLAYGROUND_STRESS_MAX_IDLE_W: float = 100000.0 # upper bound for a manual idle override
# (far beyond any appliance; guards against
# inf/absurd values corrupting synthesis)
@@ -124,7 +124,6 @@ class CycleDetectorConfig:
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
@@ -141,6 +140,7 @@ class CycleDetectorConfig:
anti_wrinkle_max_power: float = 400.0
anti_wrinkle_max_duration: float = 60.0
anti_wrinkle_exit_power: float = 0.8
anti_wrinkle_idle_timeout: float = 120.0
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.
@@ -307,7 +307,6 @@ class CycleDetector:
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
@@ -847,7 +846,7 @@ class CycleDetector:
self._anti_wrinkle_idle_time += dt
anti_wrinkle_end_threshold = max(
self._dynamic_end_threshold,
self._anti_wrinkle_idle_timeout,
float(self._config.anti_wrinkle_idle_timeout),
)
if self._anti_wrinkle_idle_time >= anti_wrinkle_end_threshold:
self._transition_to(STATE_OFF, timestamp)
+134 -83
View File
@@ -16,6 +16,7 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Frontend card and panel registration for WashData."""
import asyncio
import logging
import os
from pathlib import Path
@@ -36,12 +37,19 @@ PANEL_JS_URL = f"/{LOCAL_SUBDIR}/{PANEL_JS_NAME}"
PANEL_ELEMENT = "ha-washdata-panel"
PANEL_URL_PATH = "ha-washdata"
PANEL_REGISTERED_KEY = "ha_washdata_panel_registered"
PANEL_STATIC_REGISTERED = "ha_washdata_panel_static_registered"
# Per-language panel translations are served straight from the integration's
# translations/panel/ directory (one {lang}.json per language). The panel fetches
# only the user's language + en fallback, instead of one monolithic bundle.
PANEL_TRANSLATIONS_DIRNAME = "panel"
PANEL_TRANSLATIONS_URL = f"/{LOCAL_SUBDIR}/panel-translations"
BRAND_ICON_URL = f"/{LOCAL_SUBDIR}/icon.png"
# Set in hass.data after _do_register_panel confirms the icon file exists and was
# served; ws_get_constants reads it so it never advertises an unreachable URL.
BRAND_ICON_REGISTERED_KEY = "ha_washdata_brand_icon_registered"
# Single task in hass.data that covers the entire panel-registration lifecycle
# (static paths + sidebar). Concurrent setup_entry calls share it; teardown
# cancels it before clearing state.
PANEL_TASK_KEY = "ha_washdata_panel_task"
CARD_DEFERRED = "deferred"
CARD_FAILED = "failed"
CardRegisterResult = Literal["registered", "deferred", "failed"]
@@ -195,13 +203,13 @@ class WashDataCardRegistration:
async def async_register(self) -> CardRegisterResult:
"""Register card assets/resources and report registration outcome."""
src = self._src_path()
if not src.exists():
if not await self.hass.async_add_executor_job(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()
version = await self.hass.async_add_executor_job(get_cache_buster)
# Try auto-registration of the lovelace resource
# If lovelace is not yet loaded, wait for it
@@ -266,80 +274,66 @@ class WashDataCardRegistration:
return CARD_FAILED
async def async_register_panel(hass: HomeAssistant) -> bool:
"""Serve ha-washdata-panel.js and register a sidebar panel with Home Assistant.
async def _async_register_path(hass: HomeAssistant, url_path: str, path: str) -> None:
"""Register one static path.
Safe to call on every integration setup; subsequent calls are no-ops once
hass.data[PANEL_REGISTERED_KEY] is set. Returns True on success.
Uses the modern ``async_register_static_paths`` API when available. Falls
back to the legacy sync helper only when that API is absent — not on a
genuine registration failure. An already-registered path is treated as
success (benign on integration reload); any other exception propagates so
the caller can decide whether to report failure.
"""
if hass.data.get(PANEL_REGISTERED_KEY):
return True
try:
from homeassistant.components.http import StaticPathConfig # pylint: disable=import-outside-toplevel
except ImportError:
_register_static_path(hass, url_path, path)
return
src = Path(__file__).parent / "www" / PANEL_JS_NAME
# Path.exists() hits the filesystem; offload it so the event loop is not
# blocked on I/O during setup.
if not await hass.async_add_executor_job(src.exists):
_LOGGER.warning("Panel JS not found at %s — sidebar panel not registered", src)
if not hasattr(hass.http, "async_register_static_paths"):
_register_static_path(hass, url_path, path)
return
try:
await hass.http.async_register_static_paths(
[StaticPathConfig(url_path, path, cache_headers=True)]
)
except Exception as exc: # pylint: disable=broad-exception-caught
if "already" in str(exc).lower():
_LOGGER.debug("Static path already registered (ok): %s", url_path)
return
raise
async def _do_register_panel(hass: HomeAssistant, src: Path) -> bool:
"""Register all static paths AND the sidebar panel as one atomic operation.
Covers the full panel-registration lifecycle so concurrent callers awaiting
the same shared task are all serialized through both phases.
"""
# ── Phase 1: static paths ────────────────────────────────────────────────
try:
# Panel JS (primary asset — must be available before the sidebar fires).
await _async_register_path(hass, PANEL_JS_URL, str(src))
# Per-language translation files.
trans_src = Path(__file__).parent / "translations" / PANEL_TRANSLATIONS_DIRNAME
if await hass.async_add_executor_job(trans_src.is_dir):
await _async_register_path(hass, PANEL_TRANSLATIONS_URL, str(trans_src))
# Brand icon (panel header). Track registration so ws_get_constants
# never advertises an unreachable URL.
icon_src = Path(__file__).parent / "brand" / "icon.png"
if await hass.async_add_executor_job(icon_src.is_file):
await _async_register_path(hass, BRAND_ICON_URL, str(icon_src))
hass.data[BRAND_ICON_REGISTERED_KEY] = True
else:
hass.data[BRAND_ICON_REGISTERED_KEY] = False
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.warning("WashData panel static path registration failed: %s", exc)
return False
# Serve the JS module under /ha_washdata/ha-washdata-panel.js.
# Await the static path registration directly so the file is available
# before HA fires EVENT_PANELS_UPDATED and the frontend tries to import it.
# Guard with a flag set *before* the await so two concurrent setup_entry
# calls (multiple devices) don't both try to register the same route and
# log a benign "method GET is already registered" debug line.
if not hass.data.get(PANEL_STATIC_REGISTERED):
hass.data[PANEL_STATIC_REGISTERED] = True
# Outer guard: a static-path registration failure (including a raising
# fallback) must not escape and abort setup -- degrade gracefully like the
# sidebar-registration section below.
try:
try:
from homeassistant.components.http import StaticPathConfig # pylint: disable=import-outside-toplevel
if hasattr(hass.http, "async_register_static_paths"):
await hass.http.async_register_static_paths(
[StaticPathConfig(PANEL_JS_URL, str(src), True)]
)
else:
_register_static_path(hass, PANEL_JS_URL, str(src))
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug("Panel static path registration failed, falling back: %s", exc)
_register_static_path(hass, PANEL_JS_URL, str(src))
# Serve the translations/panel/ directory for per-user-language loading.
# The panel fetches /ha_washdata/panel-translations/{lang}.json (+ en.json
# fallback) on demand, so browsers only download the language(s) in use
# rather than a monolithic all-languages bundle.
trans_src = Path(__file__).parent / "translations" / PANEL_TRANSLATIONS_DIRNAME
# Filesystem check offloaded to the executor (see note above).
if await hass.async_add_executor_job(trans_src.is_dir):
try:
from homeassistant.components.http import StaticPathConfig # pylint: disable=import-outside-toplevel
if hasattr(hass.http, "async_register_static_paths"):
await hass.http.async_register_static_paths(
[StaticPathConfig(PANEL_TRANSLATIONS_URL, str(trans_src), True)]
)
else:
_register_static_path(hass, PANEL_TRANSLATIONS_URL, str(trans_src))
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug("Panel translations path registration failed: %s", exc)
_register_static_path(hass, PANEL_TRANSLATIONS_URL, str(trans_src))
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.warning("WashData panel static path registration failed: %s", exc)
hass.data.pop(PANEL_STATIC_REGISTERED, None)
return False
# Re-check after the await: with multiple WashData devices, all concurrent
# setup_entry calls pass the initial guard before any one of them sets the
# key. The first to resume after the await wins; the rest bail out here.
if hass.data.get(PANEL_REGISTERED_KEY):
return True
# Register the sidebar panel using the built-in "custom" component type.
# ha-panel-custom reads panel.config.module_url and imports it dynamically,
# then instantiates the element named in panel.config.name.
# ── Phase 2: sidebar panel ───────────────────────────────────────────────
try:
from homeassistant.components import frontend # pylint: disable=import-outside-toplevel
@@ -379,23 +373,80 @@ async def async_register_panel(hass: HomeAssistant) -> bool:
return False
async def async_register_panel(hass: HomeAssistant) -> bool:
"""Serve ha-washdata-panel.js and register a sidebar panel with Home Assistant.
Safe to call on every integration setup; subsequent calls are no-ops once
hass.data[PANEL_REGISTERED_KEY] is set. Returns True on success.
"""
if hass.data.get(PANEL_REGISTERED_KEY):
return True
src = Path(__file__).parent / "www" / PANEL_JS_NAME
# Path.exists() hits the filesystem; offload it so the event loop is not
# blocked on I/O during setup.
if not await hass.async_add_executor_job(src.exists):
_LOGGER.warning("Panel JS not found at %s — sidebar panel not registered", src)
return False
# Coalesce concurrent setup_entry calls (multiple WashData devices) onto a
# single shared Task covering the full registration lifecycle (static paths
# + sidebar). The first caller creates and stores the task; all callers —
# including the first — await it. A completed task re-awaited returns its
# result immediately, so later callers (e.g. a second device added after
# boot) are fast no-ops.
if PANEL_TASK_KEY not in hass.data:
hass.data[PANEL_TASK_KEY] = hass.async_create_task(
_do_register_panel(hass, src)
)
task = hass.data[PANEL_TASK_KEY]
try:
result = bool(await asyncio.shield(task))
if not result and hass.data.get(PANEL_TASK_KEY) is task:
# Task completed but registration failed; clear so a later
# setup_entry can create a fresh task and retry.
hass.data.pop(PANEL_TASK_KEY, None)
return result
except asyncio.CancelledError:
# Re-raise when this caller was cancelled so HA setup propagates correctly;
# otherwise a CancelledError came from the shielded inner task (not us),
# and we return False so other callers remain unaffected.
if asyncio.current_task().cancelling():
raise
return False
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.warning("WashData panel registration failed: %s", exc)
if hass.data.get(PANEL_TASK_KEY) is task:
hass.data.pop(PANEL_TASK_KEY, None)
return False
async def async_unregister_panel(hass: HomeAssistant) -> None:
"""Tear down the WashData sidebar panel and its static routes.
Integration teardown counterpart to :func:`async_register_panel`. Intended to
be called from ``async_unload_entry`` when the *final* WashData config entry
is removed, so no stale panel registration, sidebar entry, or static route is
left behind. Mirrors the register flow and is guarded by the same
once-per-boot keys (``PANEL_REGISTERED_KEY`` / ``PANEL_STATIC_REGISTERED``),
so it is a no-op when the panel was never registered and is safe to call
repeatedly. After clearing the guards a later setup revalidates the assets
and registers the panel + routes again.
left behind. Cancels any in-flight registration task before clearing state
to prevent registration completing after teardown. After clearing the guards
a later setup revalidates the assets and registers the panel + routes again.
"""
if not hass.data.get(PANEL_REGISTERED_KEY) and not hass.data.get(
PANEL_STATIC_REGISTERED
):
if not hass.data.get(PANEL_REGISTERED_KEY) and PANEL_TASK_KEY not in hass.data:
return
# Cancel and drain any in-flight registration task so it cannot complete
# and set PANEL_REGISTERED_KEY after we clear it below.
task: asyncio.Task[bool] | None = hass.data.pop(PANEL_TASK_KEY, None)
if task is not None and not task.done():
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug("WashData panel task ended with error during teardown: %s", exc)
# Remove the sidebar panel using Home Assistant's supported API.
if hass.data.get(PANEL_REGISTERED_KEY):
try:
@@ -408,7 +459,7 @@ async def async_unregister_panel(hass: HomeAssistant) -> None:
# Home Assistant exposes no public API to unregister a previously registered
# static path; clearing the guards lets a later setup revalidate the assets
# and re-register the routes (a benign "already registered" debug line at
# worst) rather than leaving a stale registration flag behind.
# and re-register the routes (a benign "already registered" on the next
# setup is handled by _async_register_path) rather than leaving stale flags.
hass.data.pop(PANEL_REGISTERED_KEY, None)
hass.data.pop(PANEL_STATIC_REGISTERED, None)
hass.data.pop(BRAND_ICON_REGISTERED_KEY, None)
+87 -10
View File
@@ -722,16 +722,14 @@ class LearningManager:
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(),
}
feedback_record = self._build_feedback_record(
cycle_id,
pending,
user_confirmed=user_confirmed,
corrected_profile=corrected_profile,
corrected_duration=duration_sec,
notes=notes,
)
self.profile_store.get_feedback_history()[cycle_id] = feedback_record
@@ -805,6 +803,85 @@ class LearningManager:
return True
def _build_feedback_record(
self,
cycle_id: str,
pending: dict[str, Any],
*,
user_confirmed: bool,
corrected_profile: Optional[str] = None,
corrected_duration: Optional[float] = None,
notes: str = "",
) -> dict[str, Any]:
"""Build a feedback_history record from a pending request + the response.
Shared by ``async_submit_cycle_feedback`` (explicit confirm/correct/ignore)
and ``async_resolve_pending_from_label`` (manual relabel), so both write
an identically-shaped record.
"""
return {
"cycle_id": cycle_id,
"original_detected_profile": pending.get("detected_profile"),
"original_confidence": pending.get("confidence"),
"user_confirmed": user_confirmed,
"corrected_profile": corrected_profile,
"corrected_duration": corrected_duration,
"notes": notes,
"submitted_at": dt_util.now().isoformat(),
}
async def async_resolve_pending_from_label(
self, cycle_id: str, applied_profile: Optional[str]
) -> bool:
"""Resolve a pending feedback when the user manually (re)labels a cycle.
Manually labelling a cycle that is awaiting verification IS the user's
answer to "did WashData detect the right program?", so it must clear the
pending feedback and drop the cycle from the review queue (issue #331).
The label itself has already been applied by ``assign_profile_to_cycle`` /
``create_profile`` (which preserve ``label_source="manual"`` and rebuild the
affected envelopes), so this only records the feedback response and removes
the pending entry - it deliberately does NOT re-label or rebuild again.
Returns True when a pending entry existed and was resolved.
"""
pending = self.profile_store.get_pending_feedback().get(cycle_id)
if not pending:
return False
detected = pending.get("detected_profile")
if applied_profile and applied_profile == detected:
# User picked the same program WashData detected -> confirmation.
user_confirmed, corrected_profile = True, None
elif applied_profile:
# User picked a different program -> correction.
user_confirmed, corrected_profile = False, applied_profile
else:
# Label removed: the detection was rejected without naming a program.
user_confirmed, corrected_profile = False, None
self.profile_store.get_feedback_history()[cycle_id] = self._build_feedback_record(
cycle_id,
pending,
user_confirmed=user_confirmed,
corrected_profile=corrected_profile,
)
del self.profile_store.get_pending_feedback()[cycle_id]
await self.profile_store.async_save()
async_dispatcher_send(self.hass, f"ha_washdata_update_{self.entry_id}")
self._logger.info(
"Resolved pending feedback for cycle %s from manual label "
"(detected='%s', applied='%s', confirmed=%s)",
cycle_id,
detected,
applied_profile,
user_confirmed,
)
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.get("id") == cycle_id), None)
+60 -40
View File
@@ -91,7 +91,6 @@ from .const import (
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,
@@ -110,6 +109,7 @@ from .const import (
CONF_ANTI_WRINKLE_MAX_POWER,
CONF_ANTI_WRINKLE_MAX_DURATION,
CONF_ANTI_WRINKLE_EXIT_POWER,
CONF_ANTI_WRINKLE_IDLE_TIMEOUT,
CONF_DELAY_START_DETECT_ENABLED,
CONF_DELAY_CONFIRM_SECONDS,
CONF_DELAY_TIMEOUT_HOURS,
@@ -151,6 +151,7 @@ from .const import (
DEFAULT_ANTI_WRINKLE_MAX_POWER,
DEFAULT_ANTI_WRINKLE_MAX_DURATION,
DEFAULT_ANTI_WRINKLE_EXIT_POWER,
DEFAULT_ANTI_WRINKLE_IDLE_TIMEOUT,
DEFAULT_DELAY_START_DETECT_ENABLED,
DEFAULT_DELAY_CONFIRM_SECONDS,
DEFAULT_DELAY_TIMEOUT_HOURS,
@@ -214,7 +215,6 @@ from .const import (
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,
@@ -268,6 +268,21 @@ _QUIET_HOURS_EVENT_TYPES = frozenset(
)
# Detector states in which the power sensor must not be swapped out. Every state
# with an in-flight cycle, plus ANTI_WRINKLE: its tumble pulses are still being
# attributed to the cycle that just finished, so re-pointing the listener there
# would splice a different appliance into that tail.
_SENSOR_SWAP_BLOCKED_STATES = frozenset(
{
STATE_STARTING,
STATE_RUNNING,
STATE_PAUSED,
STATE_ENDING,
STATE_ANTI_WRINKLE,
}
)
def _sanitize_ranking(raw_list: list[dict[str, Any]], limit: int = 5) -> list[dict[str, Any]]:
"""Top-N ranking candidates stripped of the heavy `current`/`sample` power
arrays, safe to persist on cycle_data and to include in the 32KB-limited
@@ -634,9 +649,6 @@ class WashDataManager:
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)
)
@@ -655,7 +667,6 @@ class WashDataManager:
interrupted_min_seconds=interrupted_min_seconds,
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(
@@ -727,6 +738,11 @@ class WashDataManager:
CONF_ANTI_WRINKLE_EXIT_POWER, DEFAULT_ANTI_WRINKLE_EXIT_POWER
)
),
anti_wrinkle_idle_timeout=float(
config_entry.options.get(
CONF_ANTI_WRINKLE_IDLE_TIMEOUT, DEFAULT_ANTI_WRINKLE_IDLE_TIMEOUT
)
),
delay_detect_enabled=bool(
config_entry.options.get(
CONF_DELAY_START_DETECT_ENABLED, DEFAULT_DELAY_START_DETECT_ENABLED
@@ -1858,40 +1874,42 @@ class WashDataManager:
type(d_state),
STATE_RUNNING,
)
if d_state == STATE_RUNNING:
if d_state in _SENSOR_SWAP_BLOCKED_STATES:
# Skip the sensor change but continue with the other config
# updates: returning here would silently drop every setting
# saved alongside the sensor in the same submission.
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.",
"Cannot change power sensor from %s to %s while the "
"detector is in state %s. Please wait for the current "
"cycle to complete before changing the power sensor.",
self.power_sensor_entity_id,
new_sensor,
d_state,
)
# 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,
)
else:
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(
@@ -1943,9 +1961,6 @@ class WashDataManager:
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)
)
@@ -2002,6 +2017,11 @@ class WashDataManager:
CONF_ANTI_WRINKLE_EXIT_POWER, DEFAULT_ANTI_WRINKLE_EXIT_POWER
)
)
new_anti_wrinkle_idle_timeout = float(
config_entry.options.get(
CONF_ANTI_WRINKLE_IDLE_TIMEOUT, DEFAULT_ANTI_WRINKLE_IDLE_TIMEOUT
)
)
new_delay_detect_enabled = bool(
config_entry.options.get(
CONF_DELAY_START_DETECT_ENABLED, DEFAULT_DELAY_START_DETECT_ENABLED
@@ -2025,7 +2045,6 @@ class WashDataManager:
self.detector.config.interrupted_min_seconds = new_interrupted_min
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
@@ -2037,6 +2056,7 @@ class WashDataManager:
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.anti_wrinkle_idle_timeout = new_anti_wrinkle_idle_timeout
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
+1 -1
View File
@@ -21,5 +21,5 @@
"requirements": [
"numpy"
],
"version": "0.5.2"
"version": "0.5.3"
}
@@ -172,17 +172,22 @@ def _classify(power: np.ndarray, model: PhaseModel) -> tuple[np.ndarray, float]:
def _runs(reg: np.ndarray) -> list[list[int]]:
"""Contiguous same-regime runs as ``[regime, start_idx, end_idx]`` (inclusive)."""
runs: list[list[int]] = []
n = len(reg)
i = 0
while i < n:
j = i
while j + 1 < n and reg[j + 1] == reg[i]:
j += 1
runs.append([int(reg[i]), i, j])
i = j + 1
return runs
"""Contiguous same-regime runs as ``[regime, start_idx, end_idx]`` (inclusive).
Replaces the nested while-loop (O(n) Python iterations) with np.diff +
np.flatnonzero (C-level), then a short list comprehension over only the
run-boundary indices (~10-30 for a real cycle vs 1 400+ element visits).
"""
if len(reg) == 0:
return []
changes = np.flatnonzero(np.diff(reg)) # indices where value changes
starts = np.empty(len(changes) + 1, dtype=np.intp)
ends = np.empty(len(changes) + 1, dtype=np.intp)
starts[0] = 0
starts[1:] = changes + 1
ends[:-1] = changes
ends[-1] = len(reg) - 1
return [[int(reg[s]), int(s), int(e)] for s, e in zip(starts, ends)]
def _merge_short(runs: list[list[int]], t: np.ndarray, min_run_s: float) -> list[list[int]]:
+255 -4
View File
@@ -36,6 +36,7 @@ All top-level entry points are defensive: they never raise, returning an
from __future__ import annotations
import logging
import math
from dataclasses import replace
from datetime import datetime, timedelta, timezone
from typing import Any, Callable
@@ -49,6 +50,11 @@ from . import notification_rules as notif_rules
from . import progress as progress_mod
from .phase_segmenter import phase_matching_enabled
from .const import (
CONF_ANTI_WRINKLE_ENABLED,
CONF_ANTI_WRINKLE_EXIT_POWER,
CONF_ANTI_WRINKLE_IDLE_TIMEOUT,
CONF_ANTI_WRINKLE_MAX_DURATION,
CONF_ANTI_WRINKLE_MAX_POWER,
CONF_COMPLETION_MIN_SECONDS,
CONF_END_REPEAT_COUNT,
CONF_INTERRUPTED_MIN_SECONDS,
@@ -63,7 +69,6 @@ from .const import (
CONF_OFF_DELAY,
CONF_PROFILE_MATCH_MAX_DURATION_RATIO,
CONF_PROFILE_MATCH_MIN_DURATION_RATIO,
CONF_RUNNING_DEAD_ZONE,
CONF_START_DURATION_THRESHOLD,
CONF_START_THRESHOLD_W,
CONF_STOP_THRESHOLD_W,
@@ -85,6 +90,14 @@ from .const import (
MATCH_MAE_PEAK_FLOOR,
MATCH_MAE_REF_PEAK,
MATCH_MAE_SCALE,
PLAYGROUND_STRESS_DENSE_DURATION_S,
PLAYGROUND_STRESS_DENSE_STEP_S,
PLAYGROUND_STRESS_FLOOR_PERCENTILE,
PLAYGROUND_STRESS_FLUCT_FALLBACK_FRAC,
PLAYGROUND_STRESS_MAX_IDLE_W,
PLAYGROUND_STRESS_MAX_SPARSE_STEPS,
PLAYGROUND_STRESS_SPARSE_STEP_S,
PLAYGROUND_STRESS_TRAILING_WINDOW_S,
STATE_ENDING,
STATE_FINISHED,
STATE_IDLE,
@@ -112,18 +125,48 @@ MAX_EVENTS_PER_CYCLE = 300
# so the shape is preserved rather than truncated.
MAX_SERIES_PER_CYCLE = 600
def _coerce_bool(value: Any) -> bool:
"""Strict bool coercion for override values.
Plain ``bool()`` would read the string ``"false"`` as True, so a toggle sent
as a string could switch a mode *on* when the user asked for it off. Unknown
values raise, which ``build_sim_config`` turns into "ignore this override".
"""
if isinstance(value, bool):
return value
if isinstance(value, (int, float)):
# Only the two values that actually mean a toggle. Anything else (2, -1,
# NaN, inf) is a malformed override, not an intent to switch a mode on.
if value == 0:
return False
if value == 1:
return True
raise ValueError(f"not a boolean: {value!r}")
if isinstance(value, str):
low = value.strip().lower()
if low in ("true", "1", "yes", "on"):
return True
if low in ("false", "0", "no", "off"):
return False
raise ValueError(f"not a boolean: {value!r}")
# Override keys the Playground honours, mapped to CycleDetectorConfig fields.
# Only detection-relevant knobs matter; everything else in settings_override is
# ignored safely.
_OVERRIDE_FIELD_MAP: dict[str, tuple[str, Callable[[Any], Any]]] = {
CONF_MIN_POWER: ("min_power", float),
CONF_ANTI_WRINKLE_ENABLED: ("anti_wrinkle_enabled", _coerce_bool),
CONF_ANTI_WRINKLE_MAX_POWER: ("anti_wrinkle_max_power", float),
CONF_ANTI_WRINKLE_MAX_DURATION: ("anti_wrinkle_max_duration", float),
CONF_ANTI_WRINKLE_EXIT_POWER: ("anti_wrinkle_exit_power", float),
CONF_ANTI_WRINKLE_IDLE_TIMEOUT: ("anti_wrinkle_idle_timeout", float),
CONF_OFF_DELAY: ("off_delay", int),
CONF_MIN_OFF_GAP: ("min_off_gap", int),
CONF_COMPLETION_MIN_SECONDS: ("completion_min_seconds", int),
CONF_END_REPEAT_COUNT: ("end_repeat_count", int),
CONF_START_THRESHOLD_W: ("start_threshold_w", float),
CONF_STOP_THRESHOLD_W: ("stop_threshold_w", float),
CONF_RUNNING_DEAD_ZONE: ("running_dead_zone", int),
CONF_START_DURATION_THRESHOLD: ("start_duration_threshold", float),
CONF_INTERRUPTED_MIN_SECONDS: ("interrupted_min_seconds", int),
}
@@ -373,6 +416,8 @@ def simulate_cycle_detail(
price: float | None = None,
compute_series: bool = True,
prebuilt: tuple[Any, Any, Any, Any] | None = None,
stress_tail: bool = False,
stress_idle_w: float | None = None,
) -> dict[str, Any]:
"""Faithful single-cycle replay for the Playground "Simulate" view.
@@ -391,7 +436,7 @@ def simulate_cycle_detail(
try:
return _simulate_cycle_detail_inner(
cycle, base_config, settings_override, store, options, price,
compute_series, prebuilt,
compute_series, prebuilt, stress_tail, stress_idle_w,
)
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug("Playground detail sim failed for %s: %s", cycle.get("id"), exc)
@@ -435,6 +480,8 @@ def build_cycle_detail_sim_by_id(
settings_override: dict[str, Any] | None,
options: dict[str, Any] | None,
price: float | None = None,
stress_tail: bool = False,
stress_idle_w: float | None = None,
) -> "_DetailSim | dict[str, Any]":
"""Look up a stored cycle by id and build a resumable :class:`_DetailSim`.
@@ -456,6 +503,7 @@ def build_cycle_detail_sim_by_id(
try:
return _DetailSim(
cycle, base_config, settings_override, store, options, price,
stress_tail=stress_tail, stress_idle_w=stress_idle_w,
)
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug("Playground detail sim build failed for %s: %s", cycle_id, exc)
@@ -471,6 +519,8 @@ def _simulate_cycle_detail_inner(
price: float | None,
compute_series: bool = True,
prebuilt: tuple[Any, Any, Any, Any] | None = None,
stress_tail: bool = False,
stress_idle_w: float | None = None,
) -> dict[str, Any]:
"""One-shot faithful replay: build the resumable sim and run it to completion.
@@ -483,11 +533,15 @@ def _simulate_cycle_detail_inner(
sim = _DetailSim(
cycle, base_config, settings_override, store, options, price,
compute_series, prebuilt,
stress_tail=stress_tail, stress_idle_w=stress_idle_w,
)
if not sim.ready:
return sim.empty_payload()
sim.step(0, sim.n_readings)
sim.run_tail()
if sim.stress_tail:
sim.run_stress_tail()
else:
sim.run_tail()
return sim.finalize()
@@ -518,6 +572,8 @@ class _DetailSim:
price: float | None,
compute_series: bool = True,
prebuilt: tuple[Any, Any, Any, Any] | None = None,
stress_tail: bool = False,
stress_idle_w: float | None = None,
) -> None:
self.cycle = cycle
self.store = store
@@ -543,7 +599,11 @@ class _DetailSim:
"overrun_ratio": None,
"projected_energy_wh": None,
"projected_cost": None,
"stress": None,
}
self.stress_tail = stress_tail
self.stress_idle_override = stress_idle_w
self._stress_outcome: dict[str, Any] | None = None
if prebuilt is not None:
snapshots, match_config, group_members, member_snaps = prebuilt
else:
@@ -833,6 +893,34 @@ class _DetailSim:
"Playground detail replay failed for %s: %s", self.cycle.get("id"), exc
)
def _derive_idle_level(self) -> tuple[float, float]:
"""Derive (idle_w, fluct_w) from the standby floor of the real cycle tail.
idle_w = p7 of readings in the last PLAYGROUND_STRESS_TRAILING_WINDOW_S;
this is the between-burst standby floor, not a contaminated mean.
fluct_w = std-dev of the low band (readings ≤ idle_w × 1.5); falls back
to ±PLAYGROUND_STRESS_FLUCT_FALLBACK_FRAC when the tail is flat.
"""
if not self.readings:
return 3.0, 0.36
cutoff = self.readings[-1][0] - timedelta(seconds=PLAYGROUND_STRESS_TRAILING_WINDOW_S)
window = [p for ts, p in self.readings if ts >= cutoff]
if not window:
window = [self.readings[-1][1]]
sorted_w = sorted(window)
p7_idx = max(0, int(len(sorted_w) * PLAYGROUND_STRESS_FLOOR_PERCENTILE))
idle_w = float(sorted_w[p7_idx])
low_band = [p for p in window if p <= idle_w * 1.5]
if len(low_band) >= 2:
mean_lb = sum(low_band) / len(low_band)
variance = sum((p - mean_lb) ** 2 for p in low_band) / len(low_band)
fluct_w = variance ** 0.5
if fluct_w < 0.01:
fluct_w = idle_w * PLAYGROUND_STRESS_FLUCT_FALLBACK_FRAC
else:
fluct_w = idle_w * PLAYGROUND_STRESS_FLUCT_FALLBACK_FRAC
return max(0.0, idle_w), max(0.01, fluct_w)
def run_tail(self) -> None:
"""Synthetic quiet tail so a natural end can fire."""
if self._aborted or not self.ready:
@@ -860,6 +948,117 @@ class _DetailSim:
"Playground detail replay failed for %s: %s", self.cycle.get("id"), exc
)
def run_stress_tail(self) -> None:
"""Synthetic idle continuation for the idle termination test.
Replaces the quiet tail when ``stress_tail=True``. Never raises.
Two phases keep CPU near zero while preserving detector fidelity:
* **Dense pre-fill** (DENSE_DURATION_S at DENSE_STEP_S): populates
``_power_readings`` so ``_is_standby_band_stuck`` (10-min plateau check)
and ``_window_has_outage_gap`` can fire correctly.
* **Sparse main** (SPARSE_STEP_S steps): ``_time_below_threshold += dt``
accumulates correctly for large dt; the 8h hard cap fires normally.
``_sample()`` is called on every step so the series carries progress /
remaining_s / phase through the overrun, identical to the live estimator.
"""
if self._aborted or not self.ready:
return
try:
idle_w, fluct_w = self._derive_idle_level()
override_applied = False
if self.stress_idle_override is not None:
# The schema accepts any float. Reject non-finite (nan/inf) by keeping the
# auto-derived floor, and clamp to [0, MAX] so a negative can't surface as a
# nonsensical "-Xw" draw and a huge/inf value can't corrupt the synthetic
# power samples (NaN/inf detector math). fluct_w stays from the call above.
ov = float(self.stress_idle_override)
if math.isfinite(ov):
idle_w = max(0.0, min(ov, PLAYGROUND_STRESS_MAX_IDLE_W))
override_applied = True
synthetic_from_s = self.cursor["t"]
stop_thresh = float(getattr(self.config, "stop_threshold_w", 2.0))
idle_above = idle_w >= stop_thresh
seed = sum(ord(c) for c in (self.cycle.get("id") or "pg")) & 0xFFFF
self._emit(
"stress_tail_start",
f"idle ~{idle_w:.1f}W fluct={fluct_w:.2f}W above_stop_thresh={idle_above}",
)
last_ts = self.readings[-1][0]
captured_before = len(self.captured)
n_dense = int(PLAYGROUND_STRESS_DENSE_DURATION_S / PLAYGROUND_STRESS_DENSE_STEP_S)
dense_end_ts = last_ts
for i in range(1, n_dense + 1):
ts = last_ts + timedelta(seconds=PLAYGROUND_STRESS_DENSE_STEP_S * i)
power = max(0.0, idle_w + fluct_w * math.sin(seed + i * 1.1))
self.cursor["t"] = (ts - self.base).total_seconds()
self.detector.process_reading(power, ts)
self._sample(ts)
dense_end_ts = ts
if len(self.captured) > captured_before:
break
if len(self.captured) == captured_before:
for i in range(1, PLAYGROUND_STRESS_MAX_SPARSE_STEPS + 1):
ts = dense_end_ts + timedelta(seconds=PLAYGROUND_STRESS_SPARSE_STEP_S * i)
power = max(0.0, idle_w + fluct_w * math.sin(seed + n_dense + i * 1.1))
self.cursor["t"] = (ts - self.base).total_seconds()
self.detector.process_reading(power, ts)
self._sample(ts)
if len(self.captured) > captured_before:
break
if len(self.captured) == captured_before:
flush_ts = dense_end_ts + timedelta(
seconds=PLAYGROUND_STRESS_SPARSE_STEP_S * (PLAYGROUND_STRESS_MAX_SPARSE_STEPS + 2)
)
self.cursor["t"] = (flush_ts - self.base).total_seconds()
self.detector.force_end(flush_ts)
# Record a series sample at the forced-stop timestamp so the plotted tail
# reaches the reported elapsed time (otherwise the last point sits up to two
# sparse steps short of terminated_after_s).
self._sample(flush_ts)
terminated = len(self.captured) > captured_before
terminated_after_s: float | None = None
term_reason: Any = None
hit_cap = False
if terminated:
primary = max(
self.captured[captured_before:],
key=lambda c: float(c.get("duration") or 0.0),
)
term_reason = primary.get("termination_reason")
hit_cap = str(term_reason) == str(TerminationReason.FORCE_STOPPED)
terminated_after_s = self.cursor["t"] - synthetic_from_s
self._stress_outcome = {
"enabled": True,
"idle_w": round(idle_w, 2),
"fluct_w": round(fluct_w, 3),
"manual_override": override_applied,
"synthetic_from_s": round(synthetic_from_s, 1),
"stop_threshold_w": round(stop_thresh, 2),
"idle_above_threshold": idle_above,
"terminated": terminated,
"terminated_after_s": (
round(terminated_after_s, 1) if terminated_after_s is not None else None
),
"termination_reason": str(term_reason) if term_reason is not None else None,
"hit_cap": hit_cap,
}
except Exception as exc: # pylint: disable=broad-exception-caught
_LOGGER.debug(
"Playground stress tail failed for %s: %s", self.cycle.get("id"), exc
)
def finalize(self) -> dict[str, Any]:
outcome = self.outcome
last_match = self.last_match
@@ -958,6 +1157,53 @@ class _DetailSim:
alerts.append({"code": "underrun", "severity": "warn",
"detail": f"Finished at {ratio:.0%} of typical duration."})
# --- stress-tail verdict ---
st = self._stress_outcome
if st and st.get("enabled"):
outcome["stress"] = st
idle_w = st["idle_w"]
stop_thresh = st["stop_threshold_w"]
if st.get("terminated") and not st.get("hit_cap"):
after_s = float(st.get("terminated_after_s") or 0.0)
h, rem = divmod(int(after_s), 3600)
m = rem // 60
reason = st.get("termination_reason") or "?"
alerts.append({
"code": "stress_terminated", "severity": "info",
"detail_key": "msg.pg_stress_terminated_detail",
"detail_params": {"idle": f"{idle_w:.1f}", "h": h, "m": m, "reason": reason},
"detail": (
f"Settled to ~{idle_w:.1f}W idle -- cycle ended "
f"{h}h {m}m later via {reason}."
),
})
elif st.get("hit_cap"):
if st.get("idle_above_threshold"):
alerts.append({
"code": "stress_above_threshold", "severity": "warn",
"detail_key": "msg.pg_stress_above_threshold_detail",
"detail_params": {"idle": f"{idle_w:.1f}", "stop": f"{stop_thresh:.1f}"},
"detail": (
f"Idle draw ~{idle_w:.1f}W is at or above the effective stop "
f"threshold ({stop_thresh:.1f}W) -- the cycle never registered "
f"as quiet. Raise stop_threshold_w to fix."
),
})
# Report the actual elapsed synthetic time (the cap is ~7h50m of synthetic
# tail plus the pre-tail cycle length, not a flat 8 h).
after_s = float(st.get("terminated_after_s") or 0.0)
h, rem = divmod(int(after_s), 3600)
m = rem // 60
alerts.append({
"code": "stress_hit_cap", "severity": "error",
"detail_key": "msg.pg_stress_hit_cap_detail",
"detail_params": {"h": h, "m": m, "idle": f"{idle_w:.1f}", "stop": f"{stop_thresh:.1f}"},
"detail": (
f"Cycle ran {h}h {m}m without stopping -- force-stopped by the safety cap. "
f"Idle draw {idle_w:.1f}W vs stop threshold {stop_thresh:.1f}W."
),
})
series = self.series
if len(series) > MAX_SERIES_PER_CYCLE:
# Thin evenly so the shape is preserved (first + last always kept).
@@ -987,6 +1233,11 @@ def _sim_config_summary(config: CycleDetectorConfig) -> dict[str, Any]:
"min_off_gap": getattr(config, "min_off_gap", None),
"start_threshold_w": getattr(config, "start_threshold_w", None),
"stop_threshold_w": getattr(config, "stop_threshold_w", None),
"anti_wrinkle_enabled": getattr(config, "anti_wrinkle_enabled", None),
"anti_wrinkle_max_power": getattr(config, "anti_wrinkle_max_power", None),
"anti_wrinkle_max_duration": getattr(config, "anti_wrinkle_max_duration", None),
"anti_wrinkle_exit_power": getattr(config, "anti_wrinkle_exit_power", None),
"anti_wrinkle_idle_timeout": getattr(config, "anti_wrinkle_idle_timeout", None),
}
File diff suppressed because it is too large Load Diff
+136 -60
View File
@@ -314,76 +314,152 @@ def estimate_phase_progress(
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
# Slide the current window across the whole envelope grid and keep the
# best-scoring alignment. The scalar form below is the reference; the
# vectorized form computes the identical per-offset score in bulk (the grid is
# O(cycle length), so for a multi-hour cycle this scalar loop is ~thousands of
# corrcoef calls per update - the #311 live/Playground hot spot). The vectorized
# path falls back to the scalar loop on any error, so behavior can never regress.
def _scan_scalar() -> tuple[float | None, float, bool, float | None]:
b_progress: float | None = None
b_score = -1.0
b_in_bounds = False
b_tws: float | None = None
for i in range(len(time_grid) - 1):
time_window_start = float(time_grid[i])
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]
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)
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)
)
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 = 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)
score = (
0.4 * max(correlation, 0.0)
+ 0.3 * mae_normalized
+ 0.3 * bounds_score
)
time_diff = abs(time_window_start - current_duration)
time_penalty = min(1.0, time_diff / (target_duration * 0.3))
score = score * (1.0 - 0.4 * time_penalty)
if score > b_score:
b_score = score
b_progress = (time_window_start / target_duration) * 100.0
b_in_bounds = bool(within_bounds)
b_tws = float(time_window_start)
except Exception: # pylint: disable=broad-exception-caught
continue
return b_progress, b_score, b_in_bounds, b_tws
for i in range(len(time_grid) - 1):
time_window_start = float(time_grid[i])
def _scan_vectorized() -> tuple[float | None, float, bool, float | None]:
from numpy.lib.stride_tricks import sliding_window_view
envelope_window_start = i
envelope_window_end = min(
i + len(current_window_values), len(envelope_arrays["avg"])
)
cur = np.asarray(current_window_values, dtype=float)
w = len(cur)
avg_arr = envelope_arrays["avg"]
min_arr = envelope_arrays["min"]
max_arr = envelope_arrays["max"]
length = len(avg_arr)
n = len(time_grid) - 1
if n <= 0 or w == 0:
return _scan_scalar()
if envelope_window_end <= envelope_window_start:
continue
scores = np.full(n, -np.inf, dtype=float)
within = np.zeros(n, dtype=bool)
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
]
cur_mean = float(cur.mean())
cur_c = cur - cur_mean
cur_ss = float(cur_c @ cur_c) # Σ(x-x̄)² (== np.corrcoef numerator basis)
cur_std_pos = cur_ss > 0.0 # equivalent to np.std(cur) > 0
cur_max = float(cur.max())
tg = np.asarray(time_grid[:n], dtype=float)
time_penalty = np.minimum(1.0, np.abs(tg - current_duration) / (target_duration * 0.3))
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)
# Interior: full-width windows (no interpolation). i in [0, hi].
hi = min(length - w, n - 1)
if hi >= 0 and length >= w:
rows = hi + 1
A = sliding_window_view(avg_arr, w)[:rows]
Mn = sliding_window_view(min_arr, w)[:rows]
Mx = sliding_window_view(max_arr, w)[:rows]
row_mean = A.mean(axis=1)
A_c = A - row_mean[:, None]
row_ss = np.einsum("ij,ij->i", A_c, A_c)
dot = A_c @ cur_c
corr = np.zeros(rows, dtype=float)
good = (row_ss > 0.0) & cur_std_pos
corr[good] = dot[good] / np.sqrt(row_ss[good] * cur_ss)
mae = np.mean(np.abs(A - cur[None, :]), axis=1)
row_max = A.max(axis=1)
max_power = np.maximum(np.maximum(row_max, cur_max), 1.0)
mae_norm = 1.0 - np.minimum(mae / max_power, 1.0)
bounds_score = np.mean((cur[None, :] >= Mn) & (cur[None, :] <= Mx), axis=1)
within[:rows] = np.all(
(cur[None, :] >= Mn * 0.8) & (cur[None, :] <= Mx * 1.2), axis=1
)
sc = 0.4 * np.maximum(corr, 0.0) + 0.3 * mae_norm + 0.3 * bounds_score
scores[:rows] = sc * (1.0 - 0.4 * time_penalty[:rows])
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)
)
try:
if np.std(current_window_values) > 0 and np.std(avg_window) > 0:
correlation = np.corrcoef(current_window_values, avg_window)[0, 1]
# Tail: partial windows (i + w > length) need the same interp as the scalar
# path; there are at most w-1 of these, so a small loop is fine.
for i in range(max(hi + 1, 0), min(length, n)):
avg_window = np.interp(
np.linspace(0, 1, w), np.linspace(0, 1, length - i), avg_arr[i:length]
)
min_window = np.interp(
np.linspace(0, 1, w), np.linspace(0, 1, length - i), min_arr[i:length]
)
max_window = np.interp(
np.linspace(0, 1, w), np.linspace(0, 1, length - i), max_arr[i:length]
)
within[i] = bool(np.all((cur >= min_window * 0.8) & (cur <= max_window * 1.2)))
bounds_score = float(np.mean((cur >= min_window) & (cur <= max_window)))
if cur_std_pos and np.std(avg_window) > 0:
correlation = float(np.corrcoef(cur, avg_window)[0, 1])
else:
correlation = 0.0
mae = float(np.mean(np.abs(cur - avg_window)))
max_power = max(float(np.max(avg_window)), cur_max, 1.0)
mae_norm = 1.0 - min(mae / max_power, 1.0)
score = 0.4 * max(correlation, 0.0) + 0.3 * mae_norm + 0.3 * bounds_score
scores[i] = score * (1.0 - 0.4 * float(time_penalty[i]))
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)
best_i = int(np.argmax(scores)) # first max -> matches scalar `>` tie-break
b_score = float(scores[best_i])
if not np.isfinite(b_score):
return None, -1.0, False, None
b_tws = float(time_grid[best_i])
return (b_tws / target_duration) * 100.0, b_score, bool(within[best_i]), b_tws
score = (
0.4 * max(correlation, 0.0)
+ 0.3 * mae_normalized
+ 0.3 * bounds_score
)
time_diff = abs(time_window_start - current_duration)
time_penalty = min(1.0, time_diff / (target_duration * 0.3))
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
try:
best_progress, best_score, in_bounds, best_time_window_start = _scan_vectorized()
except Exception as e: # pylint: disable=broad-exception-caught
logger.debug("Vectorized phase scan failed (%s); using scalar path", e)
best_progress, best_score, in_bounds, best_time_window_start = _scan_scalar()
if best_progress is None or best_score < 0.4:
logger.debug("Phase detection failed: best_score=%.3f", best_score)
-2
View File
@@ -50,7 +50,6 @@ from .const import (
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,
@@ -903,7 +902,6 @@ class WasherSuggestionsSensor(WasherBaseSensor):
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:
+158 -24
View File
@@ -32,6 +32,7 @@ import logging
import re
import time
import unicodedata
from collections.abc import Callable
from typing import Any
from homeassistant.core import HomeAssistant
@@ -167,6 +168,22 @@ class StoreClient:
_FS = "https://firestore.googleapis.com/v1"
_TOKEN = "https://securetoken.googleapis.com/v1/token"
# The community catalog (brands + device searches) is public and changes rarely, but
# the panel re-queries it every time the Settings/Store tab is (re-)opened. On the
# store's Firebase free tier (50k document reads/day) that brand-list query alone was
# the single largest read source. Cache these reads in memory for a short TTL so a
# burst of panel opens collapses to at most one Firestore query per key per window.
# The client is a long-lived singleton (one per manager) so the cache survives panel
# reloads. Writes that add brands/devices invalidate it (see _commit_create) so a
# freshly-contributed entry still appears immediately for the user who added it.
_CATALOG_CACHE_TTL_S = 900.0 # brands + device searches (15 min)
_CONFIG_CACHE_TTL_S = 3600.0 # config/site (maintenance flag + confirm threshold)
# Hard cap on distinct cached read keys. Device searches key on the brand term, so a
# long-lived session that issues many distinct searches would otherwise accumulate an
# unbounded number of (mostly expired) entries. When the cap is exceeded we evict the
# soonest-to-expire entries, keeping the live TTL guarantee while bounding memory.
_MAX_READ_CACHE_ENTRIES = 256
def __init__(
self,
hass: HomeAssistant,
@@ -183,6 +200,18 @@ class StoreClient:
self._id_token_rt: str | None = None # refresh token that produced the cached id_token
self._last_error: str | None = None # short reason for the last failed write, for the UI
self._base = f"{self._FS}/projects/{project_id}/databases/(default)/documents"
# key -> (expiry_epoch, value). Read-only catalog/config responses; see class docstring.
self._read_cache: dict[str, tuple[float, Any]] = {}
# Bumped on every catalog invalidation; a read captures it before its query and only
# caches the result if it is unchanged afterwards, so an in-flight read that spans an
# invalidation cannot re-cache a pre-write snapshot.
self._cache_gen: int = 0
# Single-flight: one in-flight (generation, task) per cache key so concurrent panel
# misses share a single Firestore read instead of each issuing one (matters on the
# free-tier budget). The generation is stored so a query started before an
# invalidation is not joined by a post-invalidation caller (which would receive a
# pre-write snapshot); such a caller starts a fresh query instead.
self._inflight: dict[str, "tuple[int, asyncio.Future[list[dict[str, Any]]]]"] = {}
def last_error(self) -> str | None:
return self._last_error
@@ -192,6 +221,35 @@ class StoreClient:
self._session = async_get_clientsession(self._hass)
return self._session
# ── read cache (public catalog/config; see class docstring) ─────────────────
def _cache_get(self, key: str) -> Any | None:
ent = self._read_cache.get(key)
if ent is None:
return None
expiry, value = ent
# Monotonic clock: immune to NTP/wall-clock steps that could otherwise extend or
# truncate the TTL (put + get must use the same clock).
if time.monotonic() >= expiry:
self._read_cache.pop(key, None)
return None
return value
def _cache_put(self, key: str, value: Any, ttl: float) -> None:
self._read_cache[key] = (time.monotonic() + ttl, value)
if len(self._read_cache) > self._MAX_READ_CACHE_ENTRIES:
# Drop the soonest-to-expire entries (expired ones first) to stay bounded.
overflow = len(self._read_cache) - self._MAX_READ_CACHE_ENTRIES
for k, _ in sorted(self._read_cache.items(), key=lambda kv: kv[1][0])[:overflow]:
self._read_cache.pop(k, None)
def _invalidate_catalog_cache(self) -> None:
"""Drop cached brand/device catalog reads (call after a create/upload/promote write so
a just-contributed or newly-approved entry appears immediately, not after the TTL)."""
self._cache_gen += 1
for key in [k for k in self._read_cache if k.startswith(("brands:", "devices:"))]:
self._read_cache.pop(key, None)
# ── auth ──────────────────────────────────────────────────────────────────
async def ensure_id_token(self, refresh_token: str) -> str | None:
@@ -231,7 +289,10 @@ class StoreClient:
# ── reads (public, no token) ────────────────────────────────────────────────
async def _run_query(self, sq: dict[str, Any], parent: str = "") -> list[dict[str, Any]]:
async def _run_query(self, sq: dict[str, Any], parent: str = "") -> list[dict[str, Any]] | None:
"""Run a structured query. Returns the decoded rows on success (possibly an empty
list), or ``None`` on any HTTP error / network failure so callers can distinguish a
genuinely-empty result from a transient failure and avoid caching the latter."""
url = f"{self._base}/{parent}:runQuery" if parent else f"{self._base}:runQuery"
try:
async with self._sess().post(url, json={"structuredQuery": sq}, timeout=15) as resp:
@@ -241,13 +302,53 @@ class StoreClient:
_LOGGER.warning("Store query HTTP %s: %s", resp.status, body)
except Exception:
_LOGGER.warning("Store query HTTP %s (no body)", resp.status)
return []
return None
rows = await resp.json()
except Exception as exc: # noqa: BLE001
_LOGGER.warning("Store query error: %s", exc)
return []
return None
return [_decode_doc(r["document"]) for r in rows if isinstance(r, dict) and "document" in r]
async def _cached_catalog_query(
self, key: str, build_sq: "Callable[[], dict[str, Any]]"
) -> list[dict[str, Any]]:
"""Shared cache-get -> (on miss) run query -> conditionally-cache flow for the public
catalog reads. ``build_sq`` is called only on a cache miss (kept lazy). A successful
result is cached only if no invalidation happened while the query was in flight (the
generation guard), so an in-flight read cannot re-cache a pre-write snapshot; a
transient failure (None) is never cached. Callers apply their own in-memory prefix
filter to the returned full list."""
cached = self._cache_get(key)
if cached is not None:
return cached
# Single-flight: join an in-flight query for the CURRENT generation; otherwise start
# a fresh one. An entry from an older generation (started before an invalidation) is
# deliberately not joined -- it may carry a pre-write snapshot.
entry = self._inflight.get(key)
if entry is None or entry[0] != self._cache_gen:
gen = self._cache_gen
entry = (gen, asyncio.ensure_future(self._fetch_and_cache(key, gen, build_sq)))
self._inflight[key] = entry
# Shield so one waiter's cancellation neither cancels the shared query nor the other
# waiters coalesced onto it.
return await asyncio.shield(entry[1])
async def _fetch_and_cache(
self, key: str, gen: int, build_sq: "Callable[[], dict[str, Any]]"
) -> list[dict[str, Any]]:
"""The shared single-flight body: run the query, cache only a successful, non-
superseded result, and clear the in-flight slot when done (if it is still ours)."""
try:
fetched = await self._run_query(build_sq())
result = fetched if fetched is not None else []
if fetched is not None and gen == self._cache_gen:
self._cache_put(key, result, self._CATALOG_CACHE_TTL_S)
return result
finally:
cur = self._inflight.get(key)
if cur is not None and cur[1] is asyncio.current_task():
self._inflight.pop(key, None)
@staticmethod
def _field_filter(field: str, op: str, value: Any) -> dict[str, Any]:
return {"fieldFilter": {"field": {"fieldPath": field}, "op": op, "value": _encode(value)}}
@@ -269,33 +370,50 @@ class StoreClient:
async def search_devices(
self, brand: str | None = None, appliance_type: str | None = None,
model_query: str | None = None, include_pending: bool = False, page_size: int = 60,
model_query: str | None = None, *, include_pending: bool = False, page_size: int = 500,
) -> list[dict[str, Any]]:
filters = [self._status_filter(include_pending)]
if appliance_type:
filters.append(self._field_filter("applianceType", "EQUAL", appliance_type))
if brand:
filters.append(self._field_filter("brand_lc", "EQUAL", brand.lower()))
sq = {
"from": [{"collectionId": "devices"}],
"where": self._where(filters),
"orderBy": [{"field": {"fieldPath": "favoriteCount"}, "direction": "DESCENDING"}],
"limit": page_size,
}
rows = await self._run_query(sq)
# Cache the full per-brand/type device list (the model_query prefix filter is applied
# in memory below), so one Firestore query serves every model prefix. page_size is a
# full-catalog ceiling, not a UI page size: the in-memory filter can only match what
# was fetched, so caching a truncated list would hide models past the limit for the
# whole TTL. A query reads only the docs that exist, so the high ceiling adds no reads
# for today's catalog while staying complete as it grows.
key = f"devices:{(brand or '').lower()}:{appliance_type or ''}:{int(include_pending)}:{page_size}"
def _build() -> dict[str, Any]:
filters = [self._status_filter(include_pending)]
if appliance_type:
filters.append(self._field_filter("applianceType", "EQUAL", appliance_type))
if brand:
filters.append(self._field_filter("brand_lc", "EQUAL", brand.lower()))
return {
"from": [{"collectionId": "devices"}],
"where": self._where(filters),
"orderBy": [{"field": {"fieldPath": "favoriteCount"}, "direction": "DESCENDING"}],
"limit": page_size,
}
rows = await self._cached_catalog_query(key, _build)
if model_query:
p = model_query.lower()
rows = [r for r in rows if str(r.get("model_lc", "")).startswith(p)]
return rows
async def list_brands(self, q: str | None = None, include_pending: bool = True, page_size: int = 60) -> list[dict[str, Any]]:
sq = {
async def list_brands(self, q: str | None = None, *, include_pending: bool = True, page_size: int = 500) -> list[dict[str, Any]]:
# Cache the unfiltered brand list (the q prefix filter is applied in memory below),
# so one Firestore query serves every search prefix for this key. The limit is a
# full-catalog ceiling (not a UI page size): the in-memory prefix filter can only
# match what was fetched, so a low cap would make brands past it unsearchable for
# the whole cache TTL. The brand collection is small with tiny docs, and a query
# only reads the docs that exist, so this ceiling does not add reads for today's
# catalog while staying correct as it grows.
key = f"brands:{int(include_pending)}:{page_size}"
rows = await self._cached_catalog_query(key, lambda: {
"from": [{"collectionId": "brands"}],
"where": self._where([self._status_filter(include_pending)]),
"orderBy": [{"field": {"fieldPath": "brand_lc"}, "direction": "ASCENDING"}],
"limit": page_size,
}
rows = await self._run_query(sq)
})
if q:
p = q.lower()
rows = [r for r in rows if str(r.get("brand_lc", "")).startswith(p)]
@@ -315,15 +433,24 @@ class StoreClient:
return _decode_doc(doc)
async def get_config(self) -> dict[str, Any]:
"""Public config/site (maintenance flag + confirmThreshold). {} on failure."""
"""Public config/site (maintenance flag + confirmThreshold). {} on failure.
Cached for a long TTL: this is read on every confirm_device to resolve the
confirmThreshold, which changes almost never. Only successful reads are cached,
so a transient failure never pins an empty config."""
cached = self._cache_get("config:site")
if cached is not None:
return cached
try:
async with self._sess().get(f"{self._base}/config/site", timeout=15) as resp:
if resp.status != 200:
return {}
return _decode_doc(await resp.json())
cfg = _decode_doc(await resp.json())
except Exception as exc: # noqa: BLE001
_LOGGER.debug("Store get_config error: %s", exc)
return {}
self._cache_put("config:site", cfg, self._CONFIG_CACHE_TTL_S)
return cfg
async def _rating_agg(self, parent_path: str) -> dict[str, Any]:
"""count + average over the `ratings` subcollection under ``parent_path``.
@@ -376,7 +503,7 @@ class StoreClient:
"orderBy": [{"field": {"fieldPath": "createdAt"}, "direction": "DESCENDING"}],
"limit": page_size,
}
return await self._run_query(sq)
return await self._run_query(sq) or []
async def device_profiles(self, brand: str, model: str, appliance_type: str) -> dict[str, Any]:
"""Resolve the store deviceId from brand/model/type and return its profiles
@@ -433,7 +560,7 @@ class StoreClient:
"orderBy": [{"field": {"fieldPath": "createdAt"}, "direction": "DESCENDING"}],
"limit": page_size,
}
cycles = [self._with_decoded_trace(c) for c in await self._run_query(sq)]
cycles = [self._with_decoded_trace(c) for c in (await self._run_query(sq) or [])]
# Attach each cycle's 5-star rating summary (info-only; the aggregation lives
# in a subcollection so it can't ride the list query). Bound concurrency with
# a semaphore so a large page can't fan out into dozens of simultaneous
@@ -513,6 +640,10 @@ class StoreClient:
timeout=15,
) as resp:
if resp.status == 200:
# A newly-created brand/device changes the catalog listing; drop the
# cached brand/device reads so it appears immediately for this user.
if path.split("/", 1)[0] in ("brands", "devices"):
self._invalidate_catalog_cache()
return (True, True)
body = await resp.text()
# Precondition failure => the doc already exists; that is fine (no-op).
@@ -785,6 +916,9 @@ class StoreClient:
}]
if (await self._commit(token, promote))[0]:
status = "approved"
# Promotion changes catalog visibility (pending -> approved); drop cached
# listings so the newly-approved device shows in approved-only searches now.
self._invalidate_catalog_cache()
return {"confirmed": True, "confirmCount": count, "status": status}
async def rate_device(self, refresh_token: str, uid: str, device_id: str, rating: int) -> bool:
@@ -38,7 +38,6 @@ from .const import (
CONF_STOP_THRESHOLD_W,
CONF_POWER_OFF_THRESHOLD_W,
CONF_END_ENERGY_THRESHOLD,
CONF_RUNNING_DEAD_ZONE,
CONF_MIN_OFF_GAP,
CONF_MIN_POWER,
CONF_SAMPLING_INTERVAL,
@@ -1249,17 +1248,6 @@ class SuggestionEngine:
# 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,
@@ -1279,12 +1267,6 @@ class SuggestionEngine:
"reason_key": "suggestion.reason.end_energy_default",
"reason_params": {},
},
CONF_RUNNING_DEAD_ZONE: {
"value": suggested_dead_zone,
"reason": f"Based on early power dip detected at {suggested_dead_zone}s.",
"reason_key": "suggestion.reason.dead_zone_from_dip",
"reason_params": {"s": suggested_dead_zone},
},
}
def run_batch_simulation(self, cycles: list[dict[str, Any]]) -> dict[str, Any]:
@@ -1342,8 +1324,6 @@ class SuggestionEngine:
lowest_active: list[float] = []
cycle_energies: list[float] = [] # per-cycle total energy (Wh) for proportional floor
false_end_energies: list[float] = []
dead_zone_candidates: list[int] = []
max_gap_s = _MAX_PAUSE_GAP_H * 3600
for readings in valid_cycles:
powers = np.array([p for _, p in readings])
@@ -1353,22 +1333,6 @@ class SuggestionEngine:
if active.size > 0:
lowest_active.append(float(np.min(active)))
# Running dead zone: length of the early-instability window - the
# LAST time power dipped below the active threshold within the first
# 10 minutes after it had already become active. This covers fill /
# initial-pause transients so they do not trigger a premature end.
active_seen = False
last_early_dip = 0
for ts_offset, p in readings:
if ts_offset > 600:
break
if p >= active_thr:
active_seen = True
elif active_seen and ts_offset > 5.0:
last_early_dip = int(ts_offset)
if last_early_dip > 0:
dead_zone_candidates.append(last_early_dip)
# Per-cycle total energy (trapezoidal, gap-guarded) for the
# proportional end-energy floor.
cycle_wh = 0.0
@@ -1472,22 +1436,6 @@ class SuggestionEngine:
"reason_params": reason_end_params,
}
if dead_zone_candidates:
# Goal: as SHORT as safely possible so a real end is detected
# promptly. Use the median early-instability window (covers the
# typical startup dip) rather than a conservative p75.
p50_dz = int(np.percentile(dead_zone_candidates, 50))
suggested_dz = min(300, p50_dz)
suggestions[CONF_RUNNING_DEAD_ZONE] = {
"value": suggested_dz,
"reason": (
f"Kept short (median startup-instability window across "
f"{len(dead_zone_candidates)} cycles = {suggested_dz}s) so a real end is detected promptly."
),
"reason_key": "suggestion.reason.dead_zone_batch",
"reason_params": {"cycles": len(dead_zone_candidates), "s": suggested_dz},
}
min_off_gap = self._suggest_min_off_gap(cycles)
if min_off_gap is not None:
suggestions[CONF_MIN_OFF_GAP] = min_off_gap
@@ -17,14 +17,6 @@
"min_power": "Отчитанията на мощност над този праг (във ватове) показват, че уредът работи. Започнете с 2W за повечето устройства."
}
},
"first_profile": {
"title": "Създаване на първи профил",
"description": "**Цикъл** е пълна работа на вашия уред (напр. зареждане с пране). **Профил** групира тези цикли по тип (напр. „Памук“, „Бързо пране“). Създаването на профил сега помага незабавно да оцени продължителността на интеграцията.\n\nМожете ръчно да изберете този профил в контролите на устройството, ако не бъде открит автоматично.\n\n**Оставете „Име на профил“ празно, за да пропуснете тази стъпка.**",
"data": {
"profile_name": "Име на профил (по избор)",
"manual_duration": "Очаквана продължителност (минути)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Обновяване на името на устройството, типа на уреда, или сензор за мощност.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"min_power": "Μετρήσεις ισχύος πάνω από αυτό το όριο (σε watt) υποδεικνύουν ότι η συσκευή λειτουργεί. Ξεκινήστε με 2 W για τις περισσότερες συσκευές."
}
},
"first_profile": {
"title": "Δημιουργία πρώτου προφίλ",
"description": "Ένας **Κύκλος** είναι μια πλήρης λειτουργία της συσκευής σας (π.χ. ένα φορτίο ρούχων). Ένα **Προφίλ** ομαδοποιεί αυτούς τους κύκλους ανά τύπο (π.χ. «Βαμβάκι», «Γρήγορο πλύσιμο»). Η δημιουργία ενός προφίλ τώρα βοηθά την ενσωμάτωση να εκτιμά αμέσως τη διάρκεια.\n\nΜπορείτε να επιλέξετε χειροκίνητα αυτό το προφίλ από τα χειριστήρια της συσκευής, εάν δεν εντοπιστεί αυτόματα.\n\n**Αφήστε το «Όνομα προφίλ» κενό για να παραλείψετε αυτό το βήμα.**",
"data": {
"profile_name": "Όνομα προφίλ (Προαιρετικό)",
"manual_duration": "Εκτιμώμενη διάρκεια (λεπτά)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Ενημέρωση του ονόματος της συσκευής, του τύπου συσκευής ή του αισθητήρα ισχύος.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigurar WashData",
"description": "Actualiza el nombre del dispositivo, el tipo de electrodoméstico o el sensor de potencia.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "WashData uuesti seadistamine",
"description": "Uuendage seadme nime, seadmetüüpi või võimsusandurit.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Konfiguroi WashData uudelleen",
"description": "Päivitä laitteen nimi, laitteen tyyppi tai tehoanturi.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigurer WashData",
"description": "Mettez à jour le nom de l'appareil, le type d'appareil ou le capteur de puissance.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"min_power": "このしきい値(ワット)を超える電力値は、家電製品が動作中であることを示します。ほとんどのデバイスでは 2W から始めてください。"
}
},
"first_profile": {
"title": "最初のプロファイルを作成",
"description": "**サイクル**とは、家電製品の1回の完全な動作です(例:洗濯1回分)。**プロファイル**は、これらのサイクルをタイプ別にグループ化します(例:「綿」、「おいそぎ」)。今プロファイルを作成すると、インテグレーションが直ちに所要時間を推定できるようになります。\n\n自動で検出されない場合は、デバイスコントロールで手動でこのプロファイルを選択できます。\n\n**「プロファイル名」を空白のままにするとこの手順をスキップできます。**",
"data": {
"profile_name": "プロファイル名(任意)",
"manual_duration": "推定所要時間(分)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "デバイス名、アプライアンスのタイプ、または電力センサーを更新します。",
@@ -17,14 +17,6 @@
"min_power": "이 임계값(와트)보다 높은 전력 수치는 가전제품이 작동 중임을 나타냅니다. 대부분의 장치에서는 2W로 시작하세요."
}
},
"first_profile": {
"title": "첫 번째 프로필 만들기",
"description": "**사이클**은 가전제품의 완전한 실행을 의미합니다(예: 세탁 한 회). **프로필**은 이러한 사이클을 유형별로 그룹화합니다(예: '면', '빠른 세탁'). 지금 프로필을 생성하면 통합이 즉시 소요 시간을 예측할 수 있습니다.\n\n프로필이 자동으로 감지되지 않는 경우 장치 컨트롤에서 수동으로 선택할 수 있습니다.\n\n**이 단계를 건너뛰려면 '프로필 이름'을 비워두세요.**",
"data": {
"profile_name": "프로필 이름 (선택사항)",
"manual_duration": "예상 소요 시간 (분)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "장치 이름, 기기 유형 또는 전원 센서를 업데이트합니다.",
@@ -17,14 +17,6 @@
"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.)"
}
},
"reconfigure": {
"title": "WashData rekonfigūracija",
"description": "Atnaujinkite įrenginio pavadinimą, prietaiso tipą arba galios jutiklį.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "WashData pārkonfigurēšana",
"description": "Atjauniniet ierīces nosaukumu, ierīces veidu vai jaudas sensoru.",
@@ -17,14 +17,6 @@
"min_power": "Читањата на моќноста над овој праг (во вати) покажуваат дека апаратот работи. Започнете со 2W за повеќето уреди."
}
},
"first_profile": {
"title": "Направете прв профил",
"description": "**Циклус** е целосна работа на вашиот апарат (на пр., товар алишта). **Профил** ги групира овие циклуси по тип (на пр., „Памук“, „Брзо перење“). Создавањето профил сега помага веднаш да се процени времетраењето на интеграцијата.\n\nМожете рачно да го изберете овој профил во контролите на уредот ако не се открие автоматски.\n\n**Оставете „Име на профилот“ празно за да го прескокнете овој чекор.**",
"data": {
"profile_name": "Име на профилот (изборно)",
"manual_duration": "Проценето времетраење (минути)"
}
},
"reconfigure": {
"title": "Реконфигурирај WashData",
"description": "Ажурирајте го името на уредот, типот на апаратот или сензорот за напојување.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Rekonfigurer WashData",
"description": "Oppdater enhetsnavnet, apparattype eller strømsensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "WashData opnieuw configureren",
"description": "Werk de apparaatnaam, het type apparaat of de vermogenssensor bij.",
@@ -28,7 +28,9 @@
"awaiting_n": "Изчаква одобрение · {n} потвърдени",
"approved": "Одобрено",
"awaiting": "Изчаква одобрение",
"imported_tip": "Импортирано от магазина на общността. Използва се само за съпоставяне, не се брои в статистиката."
"imported_tip": "Импортирано от магазина на общността. Използва се само за съпоставяне, не се брои в статистиката.",
"not_importable": "неприложимо",
"exists": "съществува"
},
"btn": {
"add_device": "+ Добавяне на устройство",
@@ -169,7 +171,14 @@
"download_device": "Изтегли настройките на устройството",
"share_device": "Сподели настройките на устройството",
"share_device_tip": "Споделете програмите и настройките на това устройство с общността",
"share_n": "Сподели {n} програм{s}"
"share_n": "Сподели {n} програм{s}",
"export_selected": "Експорт (избор на данни)",
"export_all": "Бърз експорт на всичко",
"import_raw": "Разширено: замяна на всичко от JSON",
"download_export": "Изтегляне на експорта",
"analyze_import": "Анализ на файл",
"import_selected": "Импортиране на избраното",
"back": "Назад"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,7 @@
"pg_group_detection": "Тригери за откриване",
"pg_group_timing": "Правила за време",
"pg_idle": "Празен ход",
"pg_anti_wrinkle": "Против бръчки",
"pg_last_run": "Последно изпълнение: {date}",
"pg_match_score": "Оценка на съвпадение",
"pg_pct_of": "{p}% от {total}",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Интерактивна графика на мощността на цикъла: посочете, за да видите време/мощност, превъртете за мащабиране, плъзнете за преместване",
"pg_alert_timeout_end": "Прекратено поради изтичане на времето, а не по прогноза",
"pg_alert_indefinite": "Би работил безкрайно",
"pg_alert_stress_cap": "Достигнат защитен лимит",
"pg_alert_stress_ok": "Открит режим на готовност: цикълът е спрян",
"pg_alert_stress_warn": "Консумацията в режим на готовност е над прага за спиране",
"pg_stress_group": "Тест за спиране при готовност",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Ниво в режим на готовност (Вт)",
"pg_stress_idle_w_desc": "Заменете автоматично определения минимален праг на консумация в режим на готовност (оставете празно за автоматично определяне).",
"pg_stress_toggle": "Тествайте спирането при готовност",
"pg_stress_toggle_desc": "Симулира, че уредът остава при консумацията си в режим на готовност след края на записа -- показва дали и кога WashData ще спре цикъла.",
"task_pg_history": "Тест върху историята",
"task_pg_sweep": "Оптимизиране",
"task_reprocess": "Повторна обработка",
@@ -566,7 +585,31 @@
"task_split": "Разделяне на цикъл",
"task_trim": "Изрязване на цикъл",
"task_merge": "Обединяване на цикли",
"task_rebuild": "Преизчисляване на обвивките"
"task_rebuild": "Преизчисляване на обвивките",
"cat_profiles": "Профили (програми)",
"cat_real_cycles": "Цикли (история на изпълненията)",
"cat_reference_cycles": "Справочни цикли (импортирани)",
"cat_custom_phases": "Персонализирани фази",
"cat_profile_groups": "Групи профили",
"cat_settings": "Настройки за откриване и разпознаване",
"cat_matching_config": "Фина настройка на разпознаването",
"cat_ml_models": "ML модели",
"cat_feedback": "Обратна връзка и етикети за преглед",
"cat_suggestions": "Предложения",
"cat_maintenance_log": "Дневник за поддръжка",
"cat_history_logs": "Дневници за история и промени",
"cat_lifetime_stats": "Общи суми за целия период",
"cycles_short": "цикли",
"import_mode": "Как да се комбинира",
"mode_merge": "Сливане (запази моите)",
"mode_replace": "Замяна на избраното",
"cycle_destination": "Къде да отидат импортираните цикли?",
"dest_reference": "Справочни (само форма)",
"dest_real_history": "Реална история (брои се в статистиката)",
"conflict_resolution": "Конфликти на имена",
"conflict_import_copy": "Импортиране като копие",
"conflict_keep_mine": "Запази моите",
"conflict_overwrite": "Презапис"
},
"log": {
"all_levels": "Всички нива",
@@ -643,7 +686,9 @@
"force_stop_msg": "Да се спре ли принудително активният цикъл сега? Цикълът ще бъде запазен като прекъснат.",
"store_import": "Импортиране на еталонен цикъл",
"store_share": "Споделяне в магазина на общността",
"store_share_device": "Сподели настройките на устройството"
"store_share_device": "Сподели настройките на устройството",
"export_select": "Експорт - избор на данни",
"import_wizard": "Импорт - избор на данни"
},
"msg": {
"appliance_monitor": "Монитор на уреди",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} сходни немаркирани цикъла намерени – създайте профил, за да започнете тяхното съпоставяне.",
"cycles_deleted": "Изтрити цикли: {count}",
"enough_data": "Достатъчно данни за обучение ({current}/{min} цикъла).",
"export_description": "Експортирайте всички профили и цикли в JSON или възстановете от предишен експорт.",
"export_description": "Изберете точно кои профили, цикли, настройки и други да се експортират в JSON, или анализирайте файл и импортирайте само желаните части.",
"feedback_cycles_pending": "{n} цикъл{s} за преглед",
"feedback_prompt": "Потвърдете, че е правилно, коригирайте програмата или игнорирайте.",
"feedback_relabel_hint": "Преетикетирането на този цикъл също го разрешава.",
"filter_by_profile": "Филтриране по профил…",
"group_modal_help": "Групирайте програми с еднаква форма, които се различават по температура/центрофуга (продължителността може да варира). Съпоставянето оценява групата като един кандидат, след което избира най-подходящия член. Изберете поне 2; наслагването показва колко си приличат.",
"group_not_cohesive": "Тези профили не са достатъчно сходни, за да бъдат групирани надеждно, така че съпоставянето ги третира индивидуално, докато не премахнете отклонението или не разделите групата.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Рестартирайте Home Assistant, за да активирате инструментите за симулация.",
"pg_sim_progress": "{done} / {total} цикъла",
"pg_starting": "Стартиране…",
"pg_stress_synthetic": "тук започва симулацията на готовност",
"pg_sweep_intro": "Какво ако {param} беше различен? Тествайте {steps} стойности върху последните ви {cycles} цикъла, за да намерите настройката, при която най-много цикли съвпадат правилно.",
"pg_sweep_step": "Стъпка {done} / {total}",
"pg_undetected": "{n} неоткрит цикъл{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "Симулиране на цикъл…",
"pg_canvas_empty2": "Изберете цикъл по-горе и натиснете „Стартиране“, за да го симулирате. След това посочете, за да видите стойностите, превъртете за мащабиране и плъзнете за преместване.",
"pg_apply_settings_confirm": "Да се запазят ли тези {n} настройки на това устройство? {list}",
"pg_stress_terminated_detail": "Установи се на ~{idle}W в режим на готовност -- цикълът приключи {h} ч {m} мин по-късно чрез {reason}.",
"pg_stress_above_threshold_detail": "Консумацията в режим на готовност ~{idle}W е равна или над ефективния праг за спиране ({stop}W) -- цикълът никога не беше отчетен като спокоен. За да поправите това, увеличете stop_threshold_w.",
"pg_stress_hit_cap_detail": "Цикълът работи {h} ч {m} мин без да спре -- принудително спрян от защитния лимит. Консумация в режим на готовност {idle}W спрямо прага за спиране {stop}W.",
"no_logs_match": "Няма записи в дневника, отговарящи на тези филтри.",
"pg_analysis_hint2": "Изберете цикъл и натиснете Изпълни, за да заредите анализа на съвпадението.",
"pg_analysis_empty2": "Натиснете Изпълни, за да видите анализа на съвпадението.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Споделете програмите на вашето устройство (профили + референтни цикли) с общността. Настройките са незадължителни.",
"share_profile_no_cycles": "Профилът '{p}' няма ⭐ референтни цикли -- ще бъде пропуснат, освен ако нямате {n}+ потвърдени стартирания",
"advisory_phase_inconsistent": "Изглежда, че '{name}' смесва различни програми или температури - циклите му загряват за много различно време. Разделянето му на отделни профили (напр. по температура) ще подобри съпоставянето и оценките на времето.",
"advisory_phase_inconsistent_title": "⚠ Възможно смесени програми"
"advisory_phase_inconsistent_title": "⚠ Възможно смесени програми",
"export_select_intro": "Отбележете какво точно да се включи. Изборът на профили без техните цикли пак експортира разпознаваема програма (научената ѝ форма се пренася заедно с нея).",
"import_analyze_hint": "Заредете експортиран файл (или поставете неговия JSON). WashData го анализира и показва какво точно може да се импортира, преди нещо да се промени.",
"analyzing": "Анализиране…",
"device_type_mismatch_warn": "Този експорт е от друг тип уред ({src} вместо {local}). Програмите и циклите пак могат да се импортират като справочни данни, но специфичните за устройството настройки и импортът на реална история са изключени.",
"merge_hint": "Импортираните елементи се добавят; нищо локално не се губи. Конфликтите на имена се разрешават по-долу.",
"replace_warn": "Всяка отбелязана категория се изтрива и заменя от файла. Неотбелязаните категории остават непроменени.",
"dest_reference_hint": "Импортираните цикли само подобряват разпознаването на програми и никога не влияят на статистиката за използване/енергия.",
"dest_real_history_hint": "Импортираните цикли се броят като собствена история на това устройство и захранват статистиката за енергия/използване. Използвайте за преместване на един уред към нова инсталация."
},
"phase_desc": {
"anti_crease": "Случайни кратки движения след завършване за намаляване на бръчките.",
@@ -962,6 +1020,10 @@
"doc": "Мощността трябва да падне под това между импулсите, за да остане активен режимът против бръчки.",
"label": "Праг на изходна мощност"
},
"anti_wrinkle_idle_timeout": {
"label": "Макс. пауза между импулсите",
"doc": "Колко дълго машината може да остане в покой между два импулса на въртене на барабана, преди режимът против бръчки да приключи. Задайте я над най-дългата пауза, която сушилнята ви оставя между импулсите, иначе всеки следващ импулс се чете като фалшив старт."
},
"anti_wrinkle_max_duration": {
"doc": "Импулсите, по-дълги от това, се третират като истински цикъл, а не като търкаляне против бръчки.",
"label": "Максимална продължителност"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "Частично споделяне: {n} програм{s} качена, {failed} пропуснати",
"store_device_shared_some_dup": "Настройките на устройството са споделени: {n} програм{s} качена ({dup} вече съществуваше)",
"store_download_failed": "Грешка при изтегляне: {error}",
"store_download_nothing": "Няма какво да се изтегля -- всички профили вече съществуват на това устройство"
"store_download_nothing": "Няма какво да се изтегля -- всички профили вече съществуват на това устройство",
"export_selective_done": "Експортът е изтеглен",
"import_selective_done": "Импортирани са {profiles} профил(а) и {cycles} цикъл(а)"
},
"suggestion": {
"both_agree": "WashData препоръчва",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Секунди над прага за потвърждаване на старта",
"start_threshold_w": "Минимални ватове, за да се счита за стартирал",
"stop_threshold_w": "Под тази стойност машината се счита за изключена",
"anti_wrinkle_enabled": "Поглъщайте импулсите на въртене на барабана след основната фаза, вместо да ги четете като нови цикли",
"anti_wrinkle_exit_power": "Мощността между импулсите трябва да падне под това, за да остане активен режимът против бръчки",
"anti_wrinkle_idle_timeout": "Разрешено спокойствие между два импулса на въртене на барабана, преди режимът против бръчки да приключи",
"anti_wrinkle_max_duration": "Импулс, по-дълъг от това, приключва режима против бръчки и отваря нов цикъл",
"anti_wrinkle_max_power": "Импулс над това приключва режима против бръчки и отваря нов цикъл",
"dtw_bandwidth": "Етап 3: лента за деформация на Sakoe-Chiba (0 = DTW изключено; по подразбиране 0,2 = 20% от дължината на цикъла)",
"corr_weight": "Етап 2: баланс между формата на кривата (корелация) и нивото на мощността (MAE); по подразбиране 0,45",
"duration_weight": "Етап 4: колко силно съответствието на продължителността влияе върху крайния резултат (по подразбиране 0,22)",
@@ -28,7 +28,9 @@
"awaiting_n": "Čeka odobrenje · potvrđeno {n}",
"approved": "Odobreno",
"awaiting": "Čeka odobrenje",
"imported_tip": "Uvezeno iz zajedničke trgovine. Koristi se samo za podudaranje, ne broji se u statistiku."
"imported_tip": "Uvezeno iz zajedničke trgovine. Koristi se samo za podudaranje, ne broji se u statistiku.",
"not_importable": "nije dostupno",
"exists": "postoji"
},
"btn": {
"add_device": "+ Dodaj uređaj",
@@ -169,7 +171,14 @@
"download_device": "Preuzmi postavke uređaja",
"share_device": "Podijeli postavke uređaja",
"share_device_tip": "Podijelite programe i postavke ovog uređaja sa zajednicom",
"share_n": "Podijeli {n} program{s}"
"share_n": "Podijeli {n} program{s}",
"export_selected": "Izvoz (odaberi podatke)",
"export_all": "Brzi izvoz svega",
"import_raw": "Napredno: zamijeni sve iz JSON datoteke",
"download_export": "Preuzmi izvoz",
"analyze_import": "Analiziraj datoteku",
"import_selected": "Uvezi odabrano",
"back": "Nazad"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Pronađena najbolja vrijednost",
"pg_current_value": "Trenutna vrijednost:",
"pg_drag_hint": "Povucite liniju na grafikonu",
"pg_anti_wrinkle": "Anti-gužvanje",
"pg_dtw": "DTW",
"pg_ev_ending": "Završava",
"pg_ev_running": "U toku",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktivni grafikon snage ciklusa: pređite mišem za prikaz vremena/snage, pomjerajte točkić za zumiranje, povucite za pomjeranje",
"pg_alert_timeout_end": "Završeno zbog isteka vremena, a ne na osnovu predviđanja",
"pg_alert_indefinite": "Radio bi beskonačno",
"pg_alert_stress_cap": "Dostignut sigurnosni limit",
"pg_alert_stress_ok": "Otkrivanje mirovanja: ciklus zaustavljen",
"pg_alert_stress_warn": "Potrošnja u mirovanju iznad praga zaustavljanja",
"pg_stress_group": "Test otkrivanja mirovanja",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Razina mirovanja (W)",
"pg_stress_idle_w_desc": "Zamijeni automatski otkrivenu donju granicu u stanju pripravnosti (ostavi prazno za automatsko otkrivanje).",
"pg_stress_toggle": "Testiraj otkrivanje mirovanja",
"pg_stress_toggle_desc": "Simulira da uređaj ostaje na potrošnji u stanju pripravnosti nakon završetka snimanja -- prikazuje ako i kada WashData zaustavi ciklus.",
"task_pg_history": "Test na historiji",
"task_pg_sweep": "Optimiziraj",
"task_reprocess": "Ponovna obrada",
@@ -566,7 +585,31 @@
"task_split": "Dijeljenje ciklusa",
"task_trim": "Obrezivanje ciklusa",
"task_merge": "Spajanje ciklusa",
"task_rebuild": "Ponovna izgradnja omotača"
"task_rebuild": "Ponovna izgradnja omotača",
"cat_profiles": "Profili (programi)",
"cat_real_cycles": "Ciklusi (historija rada)",
"cat_reference_cycles": "Referentni ciklusi (uvezeni)",
"cat_custom_phases": "Prilagođene faze",
"cat_profile_groups": "Grupe profila",
"cat_settings": "Postavke otkrivanja i prepoznavanja",
"cat_matching_config": "Podešavanje prepoznavanja",
"cat_ml_models": "ML modeli",
"cat_feedback": "Oznake povratnih informacija i pregleda",
"cat_suggestions": "Prijedlozi",
"cat_maintenance_log": "Dnevnik održavanja",
"cat_history_logs": "Dnevnici historije i promjena",
"cat_lifetime_stats": "Ukupni zbirovi",
"cycles_short": "ciklusa",
"import_mode": "Kako kombinovati",
"mode_merge": "Spoji (zadrži moje)",
"mode_replace": "Zamijeni odabrano",
"cycle_destination": "Gdje trebaju ići uvezeni ciklusi?",
"dest_reference": "Referenca (samo oblik)",
"dest_real_history": "Stvarna historija (računa se u statistici)",
"conflict_resolution": "Sukobi naziva",
"conflict_import_copy": "Uvezi kao kopiju",
"conflict_keep_mine": "Zadrži moje",
"conflict_overwrite": "Prepiši"
},
"log": {
"all_levels": "Svi nivoi",
@@ -643,7 +686,9 @@
"force_stop_msg": "Prisilno zaustaviti aktivni ciklus sada? Ciklus će biti sačuvan kao prekinut.",
"store_import": "Uvezi referentni ciklus",
"store_share": "Podijeli u zajedničku trgovinu",
"store_share_device": "Podijeli postavke uređaja"
"store_share_device": "Podijeli postavke uređaja",
"export_select": "Izvoz - odaberi podatke",
"import_wizard": "Uvoz - odaberi podatke"
},
"msg": {
"appliance_monitor": "Monitor uređaja",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Pronađeno {count} sličnih neoznačenih ciklusa kreirajte profil da počnete s njihovim uparivanjem.",
"cycles_deleted": "Izbrisano ciklusa: {count}",
"enough_data": "Dovoljno podataka za učenje ({current}/{min} ciklusa).",
"export_description": "Izvezite sve profile i cikluse u JSON ili vratite iz prethodnog izvoza.",
"export_description": "Odaberite tačno koje profile, cikluse, postavke i ostalo želite izvesti u JSON datoteku ili analizirajte datoteku i uvezite samo željene dijelove.",
"feedback_cycles_pending": "{n} ciklus{s} za pregled",
"feedback_prompt": "Potvrdite da je ispravno, ispravite program ili zanemarite.",
"feedback_relabel_hint": "Ponovno označavanje ovog ciklusa takođe ga rješava.",
"filter_by_profile": "Filtriraj po profilu…",
"group_modal_help": "Grupni programi istog oblika koji se razlikuju po temperaturi/centru (trajanja mogu varirati). Uparivanje boduje grupu kao jednog kandidata, a zatim bira najprikladnijeg člana. Odaberite najmanje 2; prekrivanje pokazuje koliko su slični.",
"group_not_cohesive": "Ovi profili nisu dovoljno slični da bi se pouzdano grupirali, tako da ih uparivanje tretira pojedinačno sve dok ne uklonite izuzetak ili podijelite grupu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Ponovo pokrenite Home Assistant da omogućite alate za simulaciju.",
"pg_sim_progress": "{done} / {total} ciklusa",
"pg_starting": "Pokretanje…",
"pg_stress_synthetic": "simulacija mirovanja počinje ovdje",
"pg_sweep_intro": "Šta ako bi {param} bio drugačiji? Testirajte {steps} vrijednosti na vaših posljednjih {cycles} ciklusa da pronađete postavku pri kojoj se najviše ciklusa ispravno podudara.",
"pg_sweep_step": "Korak {done} / {total}",
"pg_undetected": "{n} neotkrivenih ciklusa",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuliranje ciklusa…",
"pg_canvas_empty2": "Odaberite ciklus iznad i pritisnite Pokreni da ga simulirate. Zatim pređite mišem za prikaz vrijednosti, pomjerajte točkić za zumiranje i povucite za pomjeranje.",
"pg_apply_settings_confirm": "Sačuvati ovih {n} postavki na ovaj uređaj? {list}",
"pg_stress_terminated_detail": "Ustalilo se na ~{idle}W mirovanja -- ciklus je završio {h} h {m} min kasnije putem {reason}.",
"pg_stress_above_threshold_detail": "Potrošnja u mirovanju ~{idle}W jednaka je ili viša od stvarnog praga zaustavljanja ({stop}W) -- ciklus nikad nije zabilježen kao miran. Za ispravku povećajte stop_threshold_w.",
"pg_stress_hit_cap_detail": "Ciklus je radio {h} h {m} min bez zaustavljanja -- prisilno zaustavljen sigurnosnim limitom. Potrošnja u mirovanju {idle}W u odnosu na prag zaustavljanja {stop}W.",
"no_logs_match": "Nijedan zapis dnevnika ne odgovara ovim filterima.",
"pg_analysis_hint2": "Odaberite ciklus i pritisnite Pokreni za učitavanje analize podudaranja.",
"pg_analysis_empty2": "Pritisnite Pokreni za prikaz analize podudaranja.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Podijelite programe svog uređaja (profile + referentne cikluse) sa zajednicom. Postavke su opcione.",
"share_profile_no_cycles": "Profil '{p}' nema ⭐ referentnih ciklusa -- bit će preskočen osim ako nemate {n}+ potvrđenih pokretanja",
"advisory_phase_inconsistent": "Čini se da '{name}' miješa različite programe ili temperature - njegovi ciklusi se griju vrlo različito dugo. Podjela na zasebne profile (npr. po temperaturi) poboljšat će podudaranje i procjene vremena.",
"advisory_phase_inconsistent_title": "⚠ Možda pomiješani programi"
"advisory_phase_inconsistent_title": "⚠ Možda pomiješani programi",
"export_select_intro": "Označite tačno šta treba uključiti. Odabir profila bez njihovih ciklusa i dalje izvozi prepoznatljiv program (njegov naučeni oblik prenosi se s njim).",
"import_analyze_hint": "Učitajte izvezenu datoteku (ili zalijepite njen JSON). WashData je analizira i tačno prikazuje šta se može uvesti prije nego što se išta promijeni.",
"analyzing": "Analiziranje…",
"device_type_mismatch_warn": "Ovaj izvoz je s druge vrste uređaja ({src} vs {local}). Programe i cikluse i dalje je moguće uvesti kao referentne podatke, ali postavke specifične za uređaj i uvoz stvarne historije su onemogućeni.",
"merge_hint": "Uvezene stavke se dodaju; ništa lokalno se ne gubi. Sukobi naziva rješavaju se u nastavku.",
"replace_warn": "Svaka označena kategorija briše se i zamjenjuje podacima iz datoteke. Neoznačene kategorije ostaju nepromijenjene.",
"dest_reference_hint": "Uvezeni ciklusi samo poboljšavaju prepoznavanje programa i nikada ne utiču na statistiku potrošnje/energije.",
"dest_real_history_hint": "Uvezeni ciklusi računaju se kao vlastita historija ovog uređaja i doprinose statistici energije/potrošnje. Koristite pri premještanju jednog uređaja na novu instalaciju."
},
"phase_desc": {
"anti_crease": "Povremena kratka spuštanja nakon završetka za smanjenje bora.",
@@ -962,6 +1020,10 @@
"doc": "Snaga mora pasti ispod ove između impulsa da bi režim protiv bora ostao aktivan.",
"label": "Prag izlazne snage"
},
"anti_wrinkle_idle_timeout": {
"doc": "Koliko dugo mašina smije ostati mirna između dva impulsa bubnja prije nego što se režim anti-gužvanja završi. Postavite ga iznad najdužeg razmaka koji vaša sušilica ostavlja između impulsa, inače se svaki naredni impuls čita kao lažni početak.",
"label": "Maksimalni razmak između impulsa"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsevi duži od ovog tretiraju se kao pravi ciklus, a ne kao nalet protiv bora.",
"label": "Maksimalno trajanje"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Djelimično dijeljenje: {n} program{s} učitan, {failed} preskočeno",
"store_device_shared_some_dup": "Postavke uređaja podijeljene: {n} program{s} učitan ({dup} već postoji)",
"store_download_failed": "Preuzimanje nije uspjelo: {error}",
"store_download_nothing": "Nema ništa za preuzimanje -- svi profili već postoje na ovom uređaju"
"store_download_nothing": "Nema ništa za preuzimanje -- svi profili već postoje na ovom uređaju",
"export_selective_done": "Izvoz preuzet",
"import_selective_done": "Uvezeno {profiles} profila i {cycles} ciklusa"
},
"trend": {
"down": "Opadajući trend",
@@ -1706,6 +1770,11 @@
"other": "Ostalo"
},
"pg_desc": {
"anti_wrinkle_enabled": "Upij impulse bubnja nakon glavne faze, a ne čitaj ih kao nove cikluse",
"anti_wrinkle_exit_power": "Snaga mora pasti ispod ove vrijednosti između impulsa da bi anti-gužvanje ostalo aktivno",
"anti_wrinkle_idle_timeout": "Dozvoljeno vrijeme mirovanja između dva impulsa bubnja prije nego što se anti-gužvanje završi",
"anti_wrinkle_max_duration": "Duži impuls završava anti-gužvanje i otvara novi ciklus",
"anti_wrinkle_max_power": "Impuls iznad ove vrijednosti završava anti-gužvanje i otvara novi ciklus",
"completion_min_seconds": "Najkraće pokretanje koje se računa kao pravi ciklus",
"end_repeat_count": "Niska očitanja zaredom prije završetka",
"interrupted_min_seconds": "Kratki ciklusi označeni kao prekinuti",
@@ -28,7 +28,9 @@
"awaiting_n": "Čeká na schválení · potvrzeno {n}",
"approved": "Schváleno",
"awaiting": "Čeká na schválení",
"imported_tip": "Importováno z komunitního obchodu. Používá se pouze pro porovnávání, nezapočítává se do statistik."
"imported_tip": "Importováno z komunitního obchodu. Používá se pouze pro porovnávání, nezapočítává se do statistik.",
"not_importable": "nedostupné",
"exists": "existuje"
},
"btn": {
"add_device": "+ Přidat zařízení",
@@ -169,7 +171,14 @@
"download_device": "Stáhnout nastavení zařízení",
"share_device": "Sdílet nastavení zařízení",
"share_device_tip": "Sdílet programy a nastavení tohoto zařízení s komunitou",
"share_n": "Sdílet {n} program{s}"
"share_n": "Sdílet {n} program{s}",
"export_selected": "Export (vybrat data)",
"export_all": "Rychlý export všeho",
"import_raw": "Pokročilé: nahradit vše ze souboru JSON",
"download_export": "Stáhnout export",
"analyze_import": "Analyzovat soubor",
"import_selected": "Importovat vybrané",
"back": "Zpět"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Nalezena nejlepší hodnota",
"pg_current_value": "Aktuální hodnota:",
"pg_drag_hint": "Táhněte čáru v grafu",
"pg_anti_wrinkle": "Ochrana před zmačkáním",
"pg_dtw": "DTW",
"pg_ev_ending": "Končí",
"pg_ev_running": "Běží",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktivní graf výkonu cyklu: najetím zobrazíte čas/výkon, kolečkem přiblížíte, tažením posunete",
"pg_alert_timeout_end": "Ukončeno časovým limitem, ne predikcí",
"pg_alert_indefinite": "Běželo by donekonečna",
"pg_alert_stress_cap": "Dosažen bezpečnostní limit",
"pg_alert_stress_ok": "Detekce nečinnosti: cyklus zastaven",
"pg_alert_stress_warn": "Příkon v nečinnosti nad prahem zastavení",
"pg_stress_group": "Test detekce nečinnosti",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Úroveň nečinnosti (W)",
"pg_stress_idle_w_desc": "Přepsat automaticky zjištěnou spodní hranici pohotovostního příkonu (ponechte prázdné pro automatické zjištění).",
"pg_stress_toggle": "Testovat detekci nečinnosti",
"pg_stress_toggle_desc": "Simuluje, že spotřebič zůstane při svém pohotovostním příkonu po skončení záznamu -- zobrazí, zda a kdy WashData cyklus zastaví.",
"task_pg_history": "Test na historii",
"task_pg_sweep": "Optimalizovat",
"task_reprocess": "Opětovné zpracování",
@@ -566,7 +585,31 @@
"task_split": "Rozdělování cyklu",
"task_trim": "Ořezávání cyklu",
"task_merge": "Slučování cyklů",
"task_rebuild": "Přepočítávání obálek"
"task_rebuild": "Přepočítávání obálek",
"cat_profiles": "Profily (programy)",
"cat_real_cycles": "Cykly (historie provozu)",
"cat_reference_cycles": "Referenční cykly (importované)",
"cat_custom_phases": "Vlastní fáze",
"cat_profile_groups": "Skupiny profilů",
"cat_settings": "Nastavení detekce a rozpoznávání",
"cat_matching_config": "Ladění rozpoznávání",
"cat_ml_models": "Modely ML",
"cat_feedback": "Zpětná vazba a hodnoticí štítky",
"cat_suggestions": "Návrhy",
"cat_maintenance_log": "Protokol údržby",
"cat_history_logs": "Protokoly historie a změn",
"cat_lifetime_stats": "Celkové součty",
"cycles_short": "cyklů",
"import_mode": "Jak zkombinovat",
"mode_merge": "Sloučit (ponechat moje)",
"mode_replace": "Nahradit vybrané",
"cycle_destination": "Kam mají importované cykly patřit?",
"dest_reference": "Referenční (pouze křivka)",
"dest_real_history": "Skutečná historie (počítá se do statistik)",
"conflict_resolution": "Konflikty názvů",
"conflict_import_copy": "Importovat jako kopii",
"conflict_keep_mine": "Ponechat moje",
"conflict_overwrite": "Přepsat"
},
"log": {
"all_levels": "Všechny úrovně",
@@ -643,7 +686,9 @@
"force_stop_msg": "Vynutit nyní zastavení aktivního cyklu? Cyklus bude uložen jako přerušený.",
"store_import": "Importovat referenční cyklus",
"store_share": "Sdílet do komunitního obchodu",
"store_share_device": "Sdílet nastavení zařízení"
"store_share_device": "Sdílet nastavení zařízení",
"export_select": "Export - vybrat data",
"import_wizard": "Import - vybrat data"
},
"msg": {
"appliance_monitor": "Monitor spotřebiče",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Nalezeno {count} podobných neoznačených cyklů vytvořte profil, abyste je začali párovat.",
"cycles_deleted": "Smazáno cyklů: {count}",
"enough_data": "Dostatek dat k učení ({current}/{min} cyklů).",
"export_description": "Exportujte všechny profily a cykly do JSON nebo obnovte z předchozího exportu.",
"export_description": "Vyberte přesně, které profily, cykly, nastavení a další data exportovat do souboru JSON, nebo analyzujte soubor a importujte jen ty části, které chcete.",
"feedback_cycles_pending": "{n} cyklus{s} ke kontrole",
"feedback_prompt": "Potvrďte, že to bylo správné, opravte program nebo jej ignorujte.",
"feedback_relabel_hint": "Přeznačení tohoto cyklu jej také vyřeší.",
"filter_by_profile": "Filtrovat podle profilu…",
"group_modal_help": "Skupinové programy se stejným tvarem, které se liší teplotou/odstřeďováním (doba trvání se může lišit). Shoda hodnotí skupinu jako jednoho kandidáta a poté vybere nejlépe padnoucího člena. Vyberte alespoň 2; překrytí ukazuje, jak jsou si podobné.",
"group_not_cohesive": "Tyto profily nejsou dostatečně podobné, aby se daly spolehlivě seskupit, takže shoda s nimi zachází individuálně, dokud neodstraníte odlehlou hodnotu nebo nerozdělíte skupinu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Restartujte Home Assistant, abyste povolili nástroje simulace.",
"pg_sim_progress": "{done} / {total} cyklů",
"pg_starting": "Spouštění…",
"pg_stress_synthetic": "simulace nečinnosti začíná zde",
"pg_sweep_intro": "Co kdyby {param} byl jiný? Otestujte {steps} hodnot na vašich posledních {cycles} cyklech a najděte nastavení, při kterém je správně spárováno nejvíce cyklů.",
"pg_sweep_step": "Krok {done} / {total}",
"pg_undetected": "{n} nedetekovaných cyklů",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulace cyklu…",
"pg_canvas_empty2": "Vyberte cyklus výše a stisknutím Spustit jej nasimulujte. Poté najetím zobrazíte hodnoty, kolečkem přiblížíte a tažením posunete.",
"pg_apply_settings_confirm": "Uložit těchto {n} nastavení do tohoto zařízení? {list}",
"pg_stress_terminated_detail": "Ustálil se na ~{idle}W v nečinnosti -- cyklus skončil o {h} h {m} min později přes {reason}.",
"pg_stress_above_threshold_detail": "Příkon v nečinnosti ~{idle}W je roven nebo vyšší než efektivní práh zastavení ({stop}W) -- cyklus se nikdy nezaregistroval jako klidový. Opravte to zvýšením stop_threshold_w.",
"pg_stress_hit_cap_detail": "Cyklus běžel {h} h {m} min bez zastavení -- vynuceně zastaven bezpečnostním limitem. Příkon v nečinnosti {idle}W oproti prahu zastavení {stop}W.",
"no_logs_match": "Těmto filtrům neodpovídají žádné záznamy protokolu.",
"pg_analysis_hint2": "Vyberte cyklus a stiskněte Spustit pro načtení analýzy shody.",
"pg_analysis_empty2": "Stiskněte Spustit pro zobrazení analýzy shody.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Sdílet programy vašeho zařízení (profily + referenční cykly) s komunitou. Nastavení jsou volitelná.",
"share_profile_no_cycles": "Profil '{p}' nemá žádné ⭐ referenční cykly -- bude přeskočen, pokud nemáte {n}+ potvrzených spuštění",
"advisory_phase_inconsistent": "Zdá se, že '{name}' míchá různé programy nebo teploty - jeho cykly se ohřívají po velmi různě dlouhou dobu. Rozdělení na samostatné profily (např. podle teploty) zlepší shodu i odhady času.",
"advisory_phase_inconsistent_title": "⚠ Možná smíšené programy"
"advisory_phase_inconsistent_title": "⚠ Možná smíšené programy",
"export_select_intro": "Zaškrtněte přesně to, co má být zahrnuto. Výběr profilů bez jejich cyklů stále exportuje rozpoznatelný program (jeho naučená křivka se přenese s ním).",
"import_analyze_hint": "Načtěte exportovaný soubor (nebo vložte jeho JSON). WashData jej analyzuje a přesně ukáže, co lze importovat, dříve než dojde k jakékoli změně.",
"analyzing": "Analyzuji…",
"device_type_mismatch_warn": "Tento export pochází z jiného typu spotřebiče ({src} vs {local}). Programy a cykly lze stále importovat jako referenční data, ale nastavení specifická pro zařízení a import skutečné historie jsou zakázány.",
"merge_hint": "Importované položky se přidají; nic místního se neztratí. Konflikty názvů se řeší níže.",
"replace_warn": "Každá zaškrtnutá kategorie se vymaže a nahradí daty ze souboru. Nezaškrtnuté kategorie zůstanou beze změny.",
"dest_reference_hint": "Importované cykly pouze zlepšují rozpoznávání programů a nikdy neovlivňují statistiky spotřeby/energie.",
"dest_real_history_hint": "Importované cykly se počítají jako vlastní historie tohoto zařízení a přispívají do statistik energie/spotřeby. Použijte při přesunu jednoho spotřebiče do nové instalace."
},
"phase_desc": {
"anti_crease": "Občasné krátké otáčení bubnu po dokončení cyklu pro prevenci mačkání.",
@@ -962,6 +1020,10 @@
"doc": "Příkon musí klesnout pod tuto hodnotu mezi impulzy, aby režim ochrany před mačkáním zůstal aktivní.",
"label": "Práh výkonu pro ukončení"
},
"anti_wrinkle_idle_timeout": {
"doc": "Jak dlouho může stroj mezi dvěma impulzy bubnu zůstat v klidu, než skončí režim ochrany před zmačkáním. Nastavte ji nad nejdelší mezeru, kterou vaše sušička mezi impulzy nechává, jinak se každý další impulz vyhodnotí jako falešné spuštění.",
"label": "Max. mezera mezi impulzy"
},
"anti_wrinkle_max_duration": {
"doc": "Impulzy delší než toto jsou považovány za skutečný cyklus spíše než za otočení ochrany před mačkáním.",
"label": "Max. délka trvání"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Částečné sdílení: {n} program{s} nahrán, {failed} přeskočeno",
"store_device_shared_some_dup": "Nastavení zařízení sdíleno: {n} program{s} nahrán ({dup} již existovalo)",
"store_download_failed": "Stahování selhalo: {error}",
"store_download_nothing": "Nic ke stažení -- všechny profily na tomto zařízení již existují"
"store_download_nothing": "Nic ke stažení -- všechny profily na tomto zařízení již existují",
"export_selective_done": "Export stažen",
"import_selective_done": "Importováno {profiles} profilů a {cycles} cyklů"
},
"trend": {
"down": "Klesající trend",
@@ -1706,6 +1770,11 @@
"other": "Jiné"
},
"pg_desc": {
"anti_wrinkle_enabled": "Pohltit impulzy bubnu po hlavní fázi místo čtení jako nové cykly",
"anti_wrinkle_exit_power": "Výkon musí mezi impulzy klesnout pod tuto hodnotu, aby ochrana před zmačkáním zůstala aktivní",
"anti_wrinkle_idle_timeout": "Povolený klid mezi dvěma impulzy bubnu, než ochrana před zmačkáním skončí",
"anti_wrinkle_max_duration": "Delší impulz ukončí ochranu před zmačkáním a otevře nový cyklus",
"anti_wrinkle_max_power": "Impulz nad touto hodnotou ukončí ochranu před zmačkáním a otevře nový cyklus",
"completion_min_seconds": "Nejkratší běh, který se počítá jako skutečný cyklus",
"end_repeat_count": "Nízká čtení za sebou před ukončením",
"interrupted_min_seconds": "Krátké cykly označené jako přerušené",
@@ -28,7 +28,9 @@
"awaiting_n": "Afventer godkendelse · {n} bekræftet",
"approved": "Godkendt",
"awaiting": "Afventer godkendelse",
"imported_tip": "Importeret fra fællesskabsbutikken. Bruges kun til matchning, tælles ikke med i statistikken."
"imported_tip": "Importeret fra fællesskabsbutikken. Bruges kun til matchning, tælles ikke med i statistikken.",
"not_importable": "ikke her",
"exists": "findes"
},
"btn": {
"add_device": "+ Tilføj enhed",
@@ -169,7 +171,14 @@
"download_device": "Download denne opsætning",
"share_device": "⬆ Del dette apparat",
"share_device_tip": "Del dette apparat og dets optagede referencecyklusser til fællesskabsbutikken, så andre med samme maskine kan overtage dem",
"share_n": "Del {n} cyklus/cyklusser"
"share_n": "Del {n} cyklus/cyklusser",
"export_selected": "Eksportér (vælg data)",
"export_all": "Hurtig eksport af alt",
"import_raw": "Avanceret: erstat alt fra JSON",
"download_export": "Download eksport",
"analyze_import": "Analysér fil",
"import_selected": "Importér valgte",
"back": "Tilbage"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Registreringsudløsere",
"pg_group_timing": "Tidsregler",
"pg_idle": "Inaktiv",
"pg_stress_group": "Test af inaktiv afslutning",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Inaktivt niveau (W)",
"pg_stress_idle_w_desc": "Tilsidesæt den automatisk registrerede standby-minimumsgrænse (lad feltet være tomt for automatisk).",
"pg_stress_toggle": "Test inaktiv afslutning",
"pg_stress_toggle_desc": "Simulerer at apparatet forbliver ved sit standbyforbrug efter optagelsens afslutning - viser om og hvornår WashData stopper cyklussen.",
"pg_last_run": "Seneste kørsel: {date}",
"pg_match_score": "Matchscore",
"pg_pct_of": "{p}% af {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Interaktiv effektgraf for cyklussen: hold markøren over for at aflæse tid/effekt, rul for at zoome, træk for at panorere",
"pg_alert_timeout_end": "Afsluttet ved timeout, ikke ved forudsigelse",
"pg_alert_indefinite": "Ville køre i det uendelige",
"pg_alert_stress_cap": "Sikkerhedsgrænse nået",
"pg_alert_stress_ok": "Inaktiv afslutning: cyklus stoppet",
"pg_alert_stress_warn": "Standbyforbrug over stoptærskel",
"task_pg_history": "Test på historik",
"task_pg_sweep": "Optimér",
"task_reprocess": "Genbehandling",
@@ -566,7 +584,32 @@
"task_split": "Opdeler cyklus",
"task_trim": "Beskærer cyklus",
"task_merge": "Sammenfletter cyklusser",
"task_rebuild": "Genopbygger envelopes"
"task_rebuild": "Genopbygger envelopes",
"cat_profiles": "Profiler (programmer)",
"cat_real_cycles": "Cyklusser (kørselshistorik)",
"cat_reference_cycles": "Referencecyklusser (importeret)",
"cat_custom_phases": "Brugerdefinerede faser",
"cat_profile_groups": "Profilgrupper",
"cat_settings": "Registrerings- & genkendelsesindstillinger",
"cat_matching_config": "Finjustering af genkendelse",
"cat_ml_models": "ML-modeller",
"cat_feedback": "Feedback & gennemgangsetiketter",
"cat_suggestions": "Forslag",
"cat_maintenance_log": "Vedligeholdelseslog",
"cat_history_logs": "Historik- & ændringslogger",
"cat_lifetime_stats": "Totaler (levetid)",
"cycles_short": "cyklusser",
"import_mode": "Sådan kombineres",
"mode_merge": "Flet (behold mine)",
"mode_replace": "Erstat valgte",
"cycle_destination": "Hvor skal importerede cyklusser placeres?",
"dest_reference": "Reference (kun form)",
"dest_real_history": "Rigtig historik (tæller i statistik)",
"conflict_resolution": "Navnekonflikter",
"conflict_import_copy": "Importér som kopi",
"conflict_keep_mine": "Behold mine",
"conflict_overwrite": "Overskriv",
"pg_anti_wrinkle": "Anti-rynke"
},
"log": {
"all_levels": "Alle niveauer",
@@ -643,7 +686,9 @@
"force_stop_msg": "Tving den aktive cyklus til at stoppe nu? Cyklussen gemmes som afbrudt.",
"store_import": "Importér referencecyklus",
"store_share": "Del i fællesskabsbutikken",
"store_share_device": "Del dette apparat"
"store_share_device": "Del dette apparat",
"export_select": "Eksport - vælg data",
"import_wizard": "Import - vælg data"
},
"msg": {
"appliance_monitor": "Apparat monitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} lignende umærkede cyklusser fundet opret en profil for at begynde at matche dem.",
"cycles_deleted": "{count} cyklus(ser) slettet",
"enough_data": "Nok data til at lære af ({current}/{min} cyklusser).",
"export_description": "Eksporter alle profiler og cyklusser til JSON, eller gendan fra en tidligere eksport.",
"export_description": "Vælg præcis hvilke profiler, cyklusser, indstillinger og mere der skal eksporteres til JSON, eller analysér en fil og importér kun de dele, du vil have.",
"feedback_cycles_pending": "{n} cyklus{s} til gennemgang",
"feedback_prompt": "Bekræft, at det var rigtigt, ret programmet, eller ignorer.",
"feedback_relabel_hint": "At mærke denne cyklus om løser også gennemgangen.",
"filter_by_profile": "Filtrer efter profil…",
"group_modal_help": "Gruppér programmer med samme form, der adskiller sig i temperatur/centrifugering (varigheden kan variere). Matching scorer gruppen som én kandidat, og vælger derefter det bedst passende medlem. Vælg mindst 2; overlejringen viser, hvor ens de er.",
"group_not_cohesive": "Disse profiler er ikke ens nok til at gruppere pålideligt, så matching behandler dem individuelt, indtil du fjerner afvigelsen eller opdeler gruppen.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Genstart Home Assistant for at aktivere simuleringsværktøjerne.",
"pg_sim_progress": "{done} / {total} cyklusser",
"pg_starting": "Starter…",
"pg_stress_synthetic": "inaktiv simulering begynder her",
"pg_sweep_intro": "Hvad hvis {param} var anderledes? Test {steps} værdier på tværs af dine seneste {cycles} cyklusser for at finde indstillingen, hvor flest cyklusser matches korrekt.",
"pg_sweep_step": "Trin {done} / {total}",
"pg_undetected": "{n} cyklus{s} ikke registreret",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulerer cyklus…",
"pg_canvas_empty2": "Vælg en cyklus ovenfor, og tryk på Kør for at simulere den. Hold derefter markøren over for at aflæse værdier, rul for at zoome, og træk for at panorere.",
"pg_apply_settings_confirm": "Gem disse {n} indstilling(er) på denne enhed? {list}",
"pg_stress_terminated_detail": "Stabiliserede sig på ~{idle}W inaktiv -- cyklussen sluttede {h} t {m} min senere via {reason}.",
"pg_stress_above_threshold_detail": "Standbyforbruget på ~{idle}W er på eller over den effektive stoptærskel ({stop}W) -- cyklussen blev aldrig registreret som inaktiv. Hæv stop_threshold_w for at rette dette.",
"pg_stress_hit_cap_detail": "Cyklussen kørte {h} t {m} min uden at stoppe -- tvangsstoppet af sikkerhedsgrænsen. Standbyforbrug {idle}W mod stoptærskel {stop}W.",
"no_logs_match": "Ingen logposter matcher disse filtre.",
"pg_analysis_hint2": "Vælg en cyklus, og tryk på Kør for at indlæse matchanalysen.",
"pg_analysis_empty2": "Tryk på Kør for at se matchanalysen.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Upload {brand} {model} med de referencecyklusser, du vælger. Andre med samme apparat kan overtage dine programmer. Bidrag gennemgås inden offentliggørelse.",
"share_profile_no_cycles": "Ingen referencecyklusser -- markér en cyklus som ⭐ under fanen Cyklusser for at inkludere denne profil",
"advisory_phase_inconsistent": "'{name}' ser ud til at blande forskellige programmer eller temperaturer - dens cyklusser varmer op i meget forskellige tidsrum. Hvis du deler den op i separate profiler (f.eks. pr. temperatur), forbedres matchningen og tidsestimaterne.",
"advisory_phase_inconsistent_title": "⚠ Muligvis blandede programmer"
"advisory_phase_inconsistent_title": "⚠ Muligvis blandede programmer",
"export_select_intro": "Markér præcis hvad der skal inkluderes. At vælge profiler uden deres cyklusser eksporterer stadig et genkendeligt program (den lærte form følger med).",
"import_analyze_hint": "Indlæs en eksporteret fil (eller indsæt dens JSON). WashData analyserer den og viser præcis hvad der kan importeres, før noget ændres.",
"analyzing": "Analyserer…",
"device_type_mismatch_warn": "Denne eksport er fra en anden apparattype ({src} vs {local}). Programmer og cyklusser kan stadig importeres som referencedata, men apparatspecifikke indstillinger og import af rigtig historik er deaktiveret.",
"merge_hint": "Importerede elementer tilføjes; intet lokalt går tabt. Navnekonflikter løses nedenfor.",
"replace_warn": "Hver markeret kategori slettes og erstattes fra filen. Ikke-markerede kategorier forbliver uændrede.",
"dest_reference_hint": "Importerede cyklusser forbedrer kun programgenkendelse og påvirker aldrig forbrugs-/energistatistik.",
"dest_real_history_hint": "Importerede cyklusser tæller som dette apparats egen historik og indgår i energi-/forbrugsstatistik. Brug dette til at flytte ét apparat til en ny installation."
},
"pg_desc": {
"completion_min_seconds": "Korteste kørsel, der tæller som en rigtig cyklus",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Trin 3: DDTW-halvmætningsafstand; mindre = mere formfølsom (standard 30)",
"dtw_refine_top_n": "Trin 3: kandidater DTW gen-scorer; hæv til 7-9 hvis korrekt profil placerer sig 4.-5. (standard 5)",
"duration_scale": "Trin 4: log-forhold der varighedsoverensstemmelse halveres; lavere = strengere straf (standard 0,175)",
"energy_scale": "Trin 4: log-forhold der energioverensstemmelse halveres; lavere = strengere straf (standard 0,25)"
"energy_scale": "Trin 4: log-forhold der energioverensstemmelse halveres; lavere = strengere straf (standard 0,25)",
"anti_wrinkle_idle_timeout": "Tilladt stilletid mellem to tumle-pulser, før anti-rynke slutter",
"anti_wrinkle_max_power": "En puls over dette afslutter anti-rynke og åbner en ny cyklus",
"anti_wrinkle_enabled": "Opfange tumle-pulserne efter hovedfasen i stedet for at læse dem som nye cyklusser",
"anti_wrinkle_max_duration": "En puls længere end dette afslutter anti-rynke og åbner en ny cyklus",
"anti_wrinkle_exit_power": "Effekten skal falde under dette mellem pulserne, for at anti-rynke forbliver aktiv"
},
"phase_desc": {
"anti_crease": "Lejlighedsvis korte tumblinger efter færdiggørelse for at reducere rynker.",
@@ -985,6 +1048,10 @@
"doc": "Strømmen skal falde under dette mellem pulserne for at anti-rynketilstand forbliver aktiv.",
"label": "Udgangstærskel for effekt"
},
"anti_wrinkle_idle_timeout": {
"doc": "Hvor længe maskinen må forblive stille mellem to tumle-pulser, før anti-rynketilstand slutter. Sæt den højere end den længste pause, din tørretumbler efterlader mellem to pulser, ellers læses hver efterfølgende puls som en falsk start.",
"label": "Maks. pulspause"
},
"anti_wrinkle_max_duration": {
"doc": "Pulser længere end dette behandles som en ægte cyklus snarere end en anti-rynke tumbling.",
"label": "Maks. varighed"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} cyklus/cyklusser delt; {failed} kunne ikke uploades.",
"store_device_shared_some_dup": "{created} cyklus/cyklusser delt; {dup} var allerede i butikken.",
"store_download_failed": "Download mislykkedes: {error}",
"store_download_nothing": "Intet nyt at downloade -- denne opsætning er allerede på dit apparat."
"store_download_nothing": "Intet nyt at downloade -- denne opsætning er allerede på dit apparat.",
"export_selective_done": "Eksport downloadet",
"import_selective_done": "Importerede {profiles} profil(er) og {cycles} cyklus(ser)"
},
"trend": {
"down": "Trend nedad",
@@ -28,7 +28,9 @@
"awaiting_n": "Warten auf Freigabe · {n} bestätigt",
"approved": "Freigegeben",
"awaiting": "Warten auf Freigabe",
"imported_tip": "Aus dem Community-Store importiert. Wird nur für den Abgleich verwendet, zählt nicht in die Statistik."
"imported_tip": "Aus dem Community-Store importiert. Wird nur für den Abgleich verwendet, zählt nicht in die Statistik.",
"not_importable": "hier n. v.",
"exists": "vorhanden"
},
"btn": {
"add_device": "+ Gerät hinzufügen",
@@ -169,7 +171,14 @@
"download_device": "Dieses Setup herunterladen",
"share_device": "⬆ Dieses Gerät teilen",
"share_device_tip": "Dieses Gerät und seine aufgezeichneten Referenzzyklen im Community-Store teilen, damit andere Nutzer mit demselben Gerät sie übernehmen können",
"share_n": "{n} Zyklen teilen"
"share_n": "{n} Zyklen teilen",
"export_selected": "Exportieren (Daten wählen)",
"export_all": "Alles schnell exportieren",
"import_raw": "Erweitert: alles aus JSON ersetzen",
"download_export": "Export herunterladen",
"analyze_import": "Datei analysieren",
"import_selected": "Auswahl importieren",
"back": "Zurück"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Erkennungsauslöser",
"pg_group_timing": "Zeitregeln",
"pg_idle": "Leerlauf",
"pg_stress_group": "Leerlauferkennungstest",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Leerlaufpegel (W)",
"pg_stress_idle_w_desc": "Automatisch erkannten Bereitschafts-Basiswert überschreiben (leer lassen für automatisch).",
"pg_stress_toggle": "Leerlauferkennung testen",
"pg_stress_toggle_desc": "Simuliert, dass das Gerät nach dem Ende der Aufnahme weiterhin Bereitschaftsstrom verbraucht - zeigt, ob und wann WashData den Zyklus beendet.",
"pg_last_run": "Letzter Lauf: {date}",
"pg_match_score": "Übereinstimmungswert",
"pg_pct_of": "{p}% von {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Interaktives Leistungsdiagramm des Zyklus: zum Ablesen von Zeit/Leistung mit dem Zeiger darüberfahren, zum Zoomen scrollen, zum Verschieben ziehen",
"pg_alert_timeout_end": "Durch Zeitüberschreitung beendet, nicht durch Vorhersage",
"pg_alert_indefinite": "Würde unbegrenzt weiterlaufen",
"pg_alert_stress_cap": "Sicherheitsstopp erreicht",
"pg_alert_stress_ok": "Ruhezustandserkennung: Zyklus gestoppt",
"pg_alert_stress_warn": "Ruhestromverbrauch über Stoppgrenze",
"task_pg_history": "Mit Verlauf testen",
"task_pg_sweep": "Optimieren",
"task_reprocess": "Neuverarbeitung",
@@ -566,7 +584,32 @@
"task_split": "Zyklus wird geteilt",
"task_trim": "Zyklus wird zugeschnitten",
"task_merge": "Zyklen werden zusammengeführt",
"task_rebuild": "Hüllkurven werden neu aufgebaut"
"task_rebuild": "Hüllkurven werden neu aufgebaut",
"cat_profiles": "Profile (Programme)",
"cat_real_cycles": "Zyklen (Nutzungsverlauf)",
"cat_reference_cycles": "Referenzzyklen (importiert)",
"cat_custom_phases": "Benutzerdefinierte Phasen",
"cat_profile_groups": "Profilgruppen",
"cat_settings": "Erkennungs- & Zuordnungseinstellungen",
"cat_matching_config": "Feinabstimmung der Zuordnung",
"cat_ml_models": "ML-Modelle",
"cat_feedback": "Feedback & Prüf-Labels",
"cat_suggestions": "Vorschläge",
"cat_maintenance_log": "Wartungsprotokoll",
"cat_history_logs": "Verlaufs- & Änderungsprotokolle",
"cat_lifetime_stats": "Gesamtsummen (Lebensdauer)",
"cycles_short": "Zyklen",
"import_mode": "Wie kombinieren",
"mode_merge": "Zusammenführen (meine behalten)",
"mode_replace": "Auswahl ersetzen",
"cycle_destination": "Wohin sollen importierte Zyklen?",
"dest_reference": "Referenz (nur Kurve)",
"dest_real_history": "Echter Verlauf (zählt in Statistik)",
"conflict_resolution": "Namenskonflikte",
"conflict_import_copy": "Als Kopie importieren",
"conflict_keep_mine": "Meine behalten",
"conflict_overwrite": "Überschreiben",
"pg_anti_wrinkle": "Knitterschutz"
},
"log": {
"all_levels": "Alle Ebenen",
@@ -643,7 +686,9 @@
"force_stop_msg": "Den aktiven Zyklus jetzt zwangsweise stoppen? Der Zyklus wird als unterbrochen gespeichert.",
"store_import": "Referenzzyklus importieren",
"store_share": "Im Community-Store teilen",
"store_share_device": "Dieses Gerät teilen"
"store_share_device": "Dieses Gerät teilen",
"export_select": "Export - Daten wählen",
"import_wizard": "Import - Daten wählen"
},
"msg": {
"appliance_monitor": "Gerätemonitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} ähnliche unbeschriftete Zyklen gefunden erstellen Sie ein Profil, um diese zu erkennen.",
"cycles_deleted": "{count} Zyklen gelöscht",
"enough_data": "Genügend Daten zum Lernen ({current}/{min} Zyklen).",
"export_description": "Exportieren Sie alle Profile und Zyklen nach JSON oder stellen Sie einen vorherigen Export wieder her.",
"export_description": "Wählen Sie genau aus, welche Profile, Zyklen, Einstellungen und mehr nach JSON exportiert werden sollen, oder analysieren Sie eine Datei und importieren Sie nur die gewünschten Teile.",
"feedback_cycles_pending": "{n} Zyklus{s} zur Überprüfung",
"feedback_prompt": "Bestätigen Sie, dass es richtig war, korrigieren Sie das Programm oder ignorieren Sie es.",
"feedback_relabel_hint": "Das erneute Beschriften dieses Zyklus erledigt die Überprüfung ebenfalls.",
"filter_by_profile": "Nach Profil filtern…",
"group_modal_help": "Gruppieren Sie Programme mit derselben Form, die sich in Temperatur/Schleudergang unterscheiden (Dauer kann variieren). Matching bewertet die Gruppe als einen Kandidaten und wählt dann das am besten passende Mitglied aus. Wählen Sie mindestens 2; Die Überlagerung zeigt, wie ähnlich sie sind.",
"group_not_cohesive": "Diese Profile sind nicht ähnlich genug, um zuverlässig gruppiert zu werden. Beim Matching werden sie daher einzeln behandelt, bis Sie den Ausreißer entfernen oder die Gruppe aufteilen.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Starten Sie Home Assistant neu, um die Simulationstools zu aktivieren.",
"pg_sim_progress": "{done} / {total} Zyklen",
"pg_starting": "Wird gestartet…",
"pg_stress_synthetic": "Leerlaufsimulation beginnt hier",
"pg_sweep_intro": "Was wäre, wenn {param} anders wäre? Testen Sie {steps} Werte über Ihre letzten {cycles} Zyklen, um die Einstellung zu finden, bei der die meisten Zyklen korrekt zugeordnet werden.",
"pg_sweep_step": "Schritt {done} / {total}",
"pg_undetected": "{n} Zyklus{s} nicht erkannt",
@@ -837,6 +884,9 @@
"pg_simulating": "Zyklus wird simuliert…",
"pg_canvas_empty2": "Wähle oben einen Zyklus und drücke auf Ausführen, um ihn zu simulieren. Fahre dann mit dem Zeiger darüber, um Werte abzulesen, scrolle zum Zoomen und ziehe zum Verschieben.",
"pg_apply_settings_confirm": "Diese {n} Einstellung(en) für dieses Gerät speichern? {list}",
"pg_stress_terminated_detail": "Auf ~{idle}W Leerlauf eingependelt -- Zyklus endete {h} Std. {m} Min. später über {reason}.",
"pg_stress_above_threshold_detail": "Der Ruhestromverbrauch von ~{idle}W liegt auf oder über der effektiven Stoppgrenze ({stop}W) -- der Zyklus wurde nie als ruhig erkannt. Erhöhe stop_threshold_w zur Behebung.",
"pg_stress_hit_cap_detail": "Der Zyklus lief {h} Std. {m} Min. ohne anzuhalten -- durch den Sicherheitsstopp zwangsweise beendet. Ruhestromverbrauch {idle}W gegenüber Stoppgrenze {stop}W.",
"no_logs_match": "Keine Protokolleinträge entsprechen diesen Filtern.",
"pg_analysis_hint2": "Wählen Sie einen Zyklus und klicken Sie auf Ausführen, um die Zuordnungsanalyse zu laden.",
"pg_analysis_empty2": "Klicken Sie auf Ausführen, um die Zuordnungsanalyse anzuzeigen.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Laden Sie {brand} {model} mit den von Ihnen ausgewählten Referenzzyklen hoch. Andere mit demselben Gerät können Ihre Programme übernehmen. Einträge werden vor der öffentlichen Erscheinung geprüft.",
"share_profile_no_cycles": "Keine Referenzzyklen -- markieren Sie einen Zyklus als ⭐ im Tab Zyklen, um dieses Profil einzubeziehen",
"advisory_phase_inconsistent": "'{name}' scheint verschiedene Programme oder Temperaturen zu vermischen - seine Zyklen heizen unterschiedlich lange. Wenn Sie es in separate Profile aufteilen (z. B. pro Temperatur), verbessern sich Abgleich und Zeitschätzungen.",
"advisory_phase_inconsistent_title": "⚠ Möglicherweise gemischte Programme"
"advisory_phase_inconsistent_title": "⚠ Möglicherweise gemischte Programme",
"export_select_intro": "Kreuzen Sie genau an, was enthalten sein soll. Wenn Sie Profile ohne ihre Zyklen auswählen, wird trotzdem ein erkennbares Programm exportiert (seine gelernte Kurve wird mit übertragen).",
"import_analyze_hint": "Laden Sie eine exportierte Datei (oder fügen Sie ihr JSON ein). WashData analysiert sie und zeigt genau, was importiert werden kann, bevor sich etwas ändert.",
"analyzing": "Analysiere…",
"device_type_mismatch_warn": "Dieser Export stammt von einem anderen Gerätetyp ({src} vs. {local}). Programme und Zyklen können weiterhin als Referenzdaten importiert werden, aber gerätespezifische Einstellungen und der Import echter Verläufe sind deaktiviert.",
"merge_hint": "Importierte Elemente werden hinzugefügt; nichts Lokales geht verloren. Namenskonflikte werden unten aufgelöst.",
"replace_warn": "Jede angekreuzte Kategorie wird gelöscht und aus der Datei ersetzt. Nicht angekreuzte Kategorien bleiben unverändert.",
"dest_reference_hint": "Importierte Zyklen verbessern nur die Programmerkennung und beeinflussen niemals die Nutzungs-/Energiestatistik.",
"dest_real_history_hint": "Importierte Zyklen zählen als eigener Verlauf dieses Geräts und fließen in die Energie-/Nutzungsstatistik ein. Verwenden Sie dies, um ein Gerät in eine neue Installation zu übertragen."
},
"pg_desc": {
"completion_min_seconds": "Kürzester Lauf, der als echter Zyklus zählt",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Stufe 3: DDTW-Halbsättigungsabstand; kleiner = formempfindlicher (Standard 30)",
"dtw_refine_top_n": "Stufe 3: per DTW neu bewertete Kandidaten; auf 7-9 erhöhen, wenn das richtige Profil auf Platz 4-5 liegt (Standard 5)",
"duration_scale": "Stufe 4: Log-Verhältnis, bei dem die Dauerübereinstimmung halbiert wird; kleiner = strengere Strafe (Standard 0,175)",
"energy_scale": "Stufe 4: Log-Verhältnis, bei dem die Energieübereinstimmung halbiert wird; kleiner = strengere Strafe (Standard 0,25)"
"energy_scale": "Stufe 4: Log-Verhältnis, bei dem die Energieübereinstimmung halbiert wird; kleiner = strengere Strafe (Standard 0,25)",
"anti_wrinkle_idle_timeout": "Erlaubte Stille zwischen zwei Trommelimpulsen, bevor der Knitterschutz endet",
"anti_wrinkle_max_power": "Ein Impuls darüber beendet den Knitterschutz und öffnet einen neuen Zyklus",
"anti_wrinkle_enabled": "Die Trommelimpulse nach der Hauptphase schlucken, statt sie als neue Zyklen zu lesen",
"anti_wrinkle_max_duration": "Ein längerer Impuls beendet den Knitterschutz und öffnet einen neuen Zyklus",
"anti_wrinkle_exit_power": "Die Leistung muss zwischen den Impulsen darunter fallen, damit der Knitterschutz aktiv bleibt"
},
"phase_desc": {
"anti_crease": "Gelegentliche kurze Trommelumwälzungen nach Abschluss, um Knitterung zu vermeiden.",
@@ -1367,6 +1430,10 @@
"energy_scale": {
"label": "Energie-Trennschärfe",
"doc": "Schärfe der Energieübereinstimmungsstrafe. Kleiner = strenger: eine Energieabweichung schadet mehr. Standard 0,25 ist absichtlich nachsichtiger als die Dauerschärfe, da Energie lastabhängig schwankt. Gemeinsam mit der Energiegewichtung anpassen."
},
"anti_wrinkle_idle_timeout": {
"label": "Maximale Impulspause",
"doc": "Wie lange das Gerät zwischen zwei Trommelimpulsen still bleiben darf, bevor der Knitterschutz-Modus endet. Höher setzen als die längste Pause, die Ihr Trockner zwischen zwei Impulsen lässt sonst wird jeder weitere Impuls als Fehlstart gewertet."
}
},
"setting_group": {
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} Zyklus/Zyklen geteilt; {failed} konnten nicht hochgeladen werden.",
"store_device_shared_some_dup": "{created} Zyklus/Zyklen geteilt; {dup} waren bereits im Store.",
"store_download_failed": "Download fehlgeschlagen: {error}",
"store_download_nothing": "Nichts Neues zum Herunterladen -- dieses Setup ist bereits auf Ihrem Gerät."
"store_download_nothing": "Nichts Neues zum Herunterladen -- dieses Setup ist bereits auf Ihrem Gerät.",
"export_selective_done": "Export heruntergeladen",
"import_selective_done": "{profiles} Profil(e) und {cycles} Zyklus/Zyklen importiert"
},
"trend": {
"down": "Abwärtstrend",
@@ -28,7 +28,9 @@
"awaiting_n": "Αναμονή έγκρισης · {n} επιβεβαιώσεις",
"approved": "Εγκρίθηκε",
"awaiting": "Αναμονή έγκρισης",
"imported_tip": "Εισαγωγή από το κοινοτικό κατάστημα. Χρησιμοποιείται μόνο για αντιστοίχιση, δεν προσμετράται στα στατιστικά."
"imported_tip": "Εισαγωγή από το κοινοτικό κατάστημα. Χρησιμοποιείται μόνο για αντιστοίχιση, δεν προσμετράται στα στατιστικά.",
"not_importable": "μη διαθέσιμο εδώ",
"exists": "υπάρχει"
},
"btn": {
"add_device": "+ Προσθήκη συσκευής",
@@ -169,7 +171,14 @@
"download_device": "Λήψη αυτής της ρύθμισης",
"share_device": "⬆ Κοινοποίηση αυτής της συσκευής",
"share_device_tip": "Κοινοποιήστε αυτή τη συσκευή και τους καταγεγραμμένους κύκλους αναφοράς της στο κοινοτικό κατάστημα, ώστε άλλοι με το ίδιο μηχάνημα να μπορούν να τα υιοθετήσουν",
"share_n": "Κοινοποίηση {n} κύκλου(ων)"
"share_n": "Κοινοποίηση {n} κύκλου(ων)",
"export_selected": "Εξαγωγή (επιλογή δεδομένων)",
"export_all": "Γρήγορη εξαγωγή όλων",
"import_raw": "Για προχωρημένους: αντικατάσταση όλων από JSON",
"download_export": "Λήψη εξαγωγής",
"analyze_import": "Ανάλυση αρχείου",
"import_selected": "Εισαγωγή επιλεγμένων",
"back": "Πίσω"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Προεπιλεγμένη καρτέλα",
"panel_language": "Γλώσσα πίνακα",
"per_cycle_short": "κύκλος",
"pg_anti_wrinkle": "Κατά τσακίσματος",
"pg_autofill_tip": "Αυτόματη συμπλήρωση εύρους από την τρέχουσα τιμή ({lo}{hi})",
"pg_best_value": "Καλύτερη τιμή που βρέθηκε",
"pg_current_value": "Τρέχουσα τιμή:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Παράμετρος",
"pg_objective": "Στόχος",
"pg_steps": "Βήματα",
"pg_stress_group": "Δοκιμή τερματισμού αδράνειας",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Επίπεδο αδράνειας (W)",
"pg_stress_idle_w_desc": "Παρακάμψτε το αυτόματα ανιχνευθέν κατώφλι κατάστασης αναμονής (αφήστε κενό για αυτόματο).",
"pg_stress_toggle": "Δοκιμή τερματισμού αδράνειας",
"pg_stress_toggle_desc": "Προσομοιώνει τη συσκευή που παραμένει στο επίπεδο κατανάλωσης αναμονής μετά το τέλος της εγγραφής - δείχνει αν και πότε το WashData σταματά τον κύκλο.",
"pg_sweep_2d": "Χάρτης θερμότητας 2D",
"pg_sweep_param_y": "2η παράμετρος",
"pg_obj_match": "Ακρίβεια αντιστοίχισης",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Διαδραστικό γράφημα ισχύος κύκλου: τοποθετήστε τον δείκτη για ανάγνωση χρόνου/ισχύος, κυλήστε για ζουμ, σύρετε για μετακίνηση",
"pg_alert_timeout_end": "Τερματίστηκε λόγω λήξης χρόνου, όχι από πρόβλεψη",
"pg_alert_indefinite": "Θα εκτελούνταν επ' αόριστον",
"pg_alert_stress_cap": "Επιτεύχθηκε το όριο ασφαλείας",
"pg_alert_stress_ok": "Τερματισμός λόγω αδράνειας: ο κύκλος σταμάτησε",
"pg_alert_stress_warn": "Κατανάλωση αδράνειας πάνω από το κατώφλι διακοπής",
"task_pg_history": "Δοκιμή στο ιστορικό",
"task_pg_sweep": "Βελτιστοποίηση",
"task_reprocess": "Επανεπεξεργασία",
@@ -566,7 +585,31 @@
"task_split": "Διαχωρισμός κύκλου",
"task_trim": "Περικοπή κύκλου",
"task_merge": "Συγχώνευση κύκλων",
"task_rebuild": "Ανακατασκευή φακέλων"
"task_rebuild": "Ανακατασκευή φακέλων",
"cat_profiles": "Προφίλ (προγράμματα)",
"cat_real_cycles": "Κύκλοι (ιστορικό εκτελέσεων)",
"cat_reference_cycles": "Κύκλοι αναφοράς (εισαγμένοι)",
"cat_custom_phases": "Προσαρμοσμένες φάσεις",
"cat_profile_groups": "Ομάδες προφίλ",
"cat_settings": "Ρυθμίσεις ανίχνευσης & αναγνώρισης",
"cat_matching_config": "Ρύθμιση αναγνώρισης",
"cat_ml_models": "Μοντέλα ML",
"cat_feedback": "Ετικέτες σχολίων & αξιολόγησης",
"cat_suggestions": "Προτάσεις",
"cat_maintenance_log": "Αρχείο συντήρησης",
"cat_history_logs": "Ιστορικό & αρχεία αλλαγών",
"cat_lifetime_stats": "Συγκεντρωτικά σύνολα",
"cycles_short": "κύκλοι",
"import_mode": "Τρόπος συνδυασμού",
"mode_merge": "Συγχώνευση (διατήρηση δικών μου)",
"mode_replace": "Αντικατάσταση επιλεγμένων",
"cycle_destination": "Πού να πάνε οι εισαγόμενοι κύκλοι;",
"dest_reference": "Αναφορά (μόνο καμπύλη)",
"dest_real_history": "Πραγματικό ιστορικό (μετρά στα στατιστικά)",
"conflict_resolution": "Συγκρούσεις ονομάτων",
"conflict_import_copy": "Εισαγωγή ως αντίγραφο",
"conflict_keep_mine": "Διατήρηση δικών μου",
"conflict_overwrite": "Αντικατάσταση"
},
"log": {
"all_levels": "Όλα τα επίπεδα",
@@ -643,7 +686,9 @@
"force_stop_msg": "Αναγκαστική διακοπή του ενεργού κύκλου τώρα; Ο κύκλος θα αποθηκευτεί ως διακεκομμένος.",
"store_import": "Εισαγωγή κύκλου αναφοράς",
"store_share": "Κοινοποίηση στο κοινοτικό κατάστημα",
"store_share_device": "Κοινοποίηση αυτής της συσκευής"
"store_share_device": "Κοινοποίηση αυτής της συσκευής",
"export_select": "Εξαγωγή - επιλογή δεδομένων",
"import_wizard": "Εισαγωγή - επιλογή δεδομένων"
},
"msg": {
"appliance_monitor": "Οθόνη συσκευής",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Βρέθηκαν {count} παρόμοιοι κύκλοι χωρίς ετικέτα – δημιουργήστε ένα προφίλ για να αρχίσετε την αντιστοίχισή τους.",
"cycles_deleted": "Διαγράφηκαν {count} κύκλοι",
"enough_data": "Αρκετά δεδομένα για εκμάθηση ({current}/{min} κύκλοι).",
"export_description": "Εξαγωγή όλων των προφίλ και των κύκλων σε JSON ή επαναφορά από προηγούμενη εξαγωγή.",
"export_description": "Επιλέξτε ακριβώς ποια προφίλ, κύκλους, ρυθμίσεις και άλλα θα εξαχθούν σε JSON, ή αναλύστε ένα αρχείο και εισάγετε μόνο τα μέρη που θέλετε.",
"feedback_cycles_pending": "{n} κύκλος{s} για αξιολόγηση",
"feedback_prompt": "Επιβεβαιώστε ότι ήταν σωστό, διορθώστε το πρόγραμμα ή αγνοήστε.",
"feedback_relabel_hint": "Η επανεπισήμανση αυτού του κύκλου τον επιλύει επίσης.",
"filter_by_profile": "Φιλτράρισμα κατά προφίλ…",
"group_modal_help": "Ομαδοποιήστε προγράμματα με το ίδιο σχήμα που διαφέρουν σε θερμοκρασία/περιστροφή (οι διάρκειες μπορεί να διαφέρουν). Η αντιστοίχιση βαθμολογεί την ομάδα ως έναν υποψήφιο και, στη συνέχεια, επιλέγει το μέλος που ταιριάζει καλύτερα. Διαλέξτε τουλάχιστον 2. η επικάλυψη δείχνει πόσο μοιάζουν.",
"group_not_cohesive": "Αυτά τα προφίλ δεν είναι αρκετά παρόμοια ώστε να ομαδοποιούνται αξιόπιστα, επομένως η αντιστοίχιση τα αντιμετωπίζει μεμονωμένα μέχρι να αφαιρέσετε το ακραίο στοιχείο ή να χωρίσετε την ομάδα.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Επανεκκινήστε το Home Assistant για να ενεργοποιήσετε τα εργαλεία προσομοίωσης.",
"pg_sim_progress": "{done} / {total} κύκλοι",
"pg_starting": "Εκκίνηση…",
"pg_stress_synthetic": "η προσομοίωση αδράνειας ξεκινά εδώ",
"pg_sweep_intro": "Τι θα γινόταν αν το {param} ήταν διαφορετικό; Δοκιμάστε {steps} τιμές στους τελευταίους {cycles} κύκλους για να βρείτε τη ρύθμιση όπου αντιστοιχίζονται σωστά οι περισσότεροι κύκλοι.",
"pg_sweep_step": "Βήμα {done} / {total}",
"pg_undetected": "{n} μη εντοπισμένος κύκλος{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "Προσομοίωση κύκλου…",
"pg_canvas_empty2": "Επιλέξτε έναν κύκλο παραπάνω και πατήστε Εκτέλεση για να τον προσομοιώσετε. Στη συνέχεια τοποθετήστε τον δείκτη για ανάγνωση τιμών, κυλήστε για ζουμ και σύρετε για μετακίνηση.",
"pg_apply_settings_confirm": "Αποθήκευση αυτών των {n} ρυθμίσεων σε αυτήν τη συσκευή; {list}",
"pg_stress_terminated_detail": "Σταθεροποιήθηκε σε περίπου {idle} W αδράνειας -- ο κύκλος τερμάτισε {h} ώρ. {m} λεπ. αργότερα μέσω {reason}.",
"pg_stress_above_threshold_detail": "Η κατανάλωση αδράνειας (~{idle} W) είναι ίση ή μεγαλύτερη από το πραγματικό κατώφλι διακοπής ({stop} W) -- ο κύκλος δεν καταγράφηκε ποτέ ως ήσυχος. Για διόρθωση, αυξήστε το stop_threshold_w.",
"pg_stress_hit_cap_detail": "Ο κύκλος έτρεξε {h} ώρ. {m} λεπ. χωρίς να σταματήσει -- διακόπηκε αναγκαστικά από το όριο ασφαλείας. Κατανάλωση αδράνειας {idle} W έναντι κατωφλίου διακοπής {stop} W.",
"no_logs_match": "Καμία εγγραφή καταγραφής δεν ταιριάζει με αυτά τα φίλτρα.",
"pg_analysis_hint2": "Επιλέξτε έναν κύκλο και πατήστε Εκτέλεση για να φορτώσετε την ανάλυση αντιστοίχισης.",
"pg_analysis_empty2": "Πατήστε Εκτέλεση για να δείτε την ανάλυση αντιστοίχισης.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Ανεβάστε {brand} {model} με τους κύκλους αναφοράς που επιλέγετε. Άλλοι με την ίδια συσκευή μπορούν να υιοθετήσουν τα προγράμματά σας. Οι καταχωρίσεις ελέγχονται πριν εμφανιστούν δημόσια.",
"share_profile_no_cycles": "Δεν υπάρχουν κύκλοι αναφοράς -- επισημάνετε έναν κύκλο ως ⭐ στην καρτέλα Κύκλοι για να συμπεριλάβετε αυτό το προφίλ",
"advisory_phase_inconsistent": "Το '{name}' φαίνεται να αναμειγνύει διαφορετικά προγράμματα ή θερμοκρασίες - οι κύκλοι του θερμαίνονται για πολύ διαφορετικά χρονικά διαστήματα. Ο διαχωρισμός του σε ξεχωριστά προφίλ (π.χ. ανά θερμοκρασία) θα βελτιώσει την αντιστοίχιση και τις εκτιμήσεις χρόνου.",
"advisory_phase_inconsistent_title": "⚠ Πιθανώς μεικτά προγράμματα"
"advisory_phase_inconsistent_title": "⚠ Πιθανώς μεικτά προγράμματα",
"export_select_intro": "Τσεκάρετε ακριβώς τι θα συμπεριληφθεί. Η επιλογή προφίλ χωρίς τους κύκλους τους εξάγει και πάλι ένα αναγνωρίσιμο πρόγραμμα (η μαθημένη του καμπύλη μεταφέρεται μαζί).",
"import_analyze_hint": "Φορτώστε ένα εξαγόμενο αρχείο (ή επικολλήστε το JSON του). Το WashData το αναλύει και δείχνει ακριβώς τι μπορεί να εισαχθεί πριν αλλάξει οτιδήποτε.",
"analyzing": "Ανάλυση…",
"device_type_mismatch_warn": "Αυτή η εξαγωγή προέρχεται από διαφορετικό τύπο συσκευής ({src} έναντι {local}). Τα προγράμματα και οι κύκλοι μπορούν και πάλι να εισαχθούν ως δεδομένα αναφοράς, αλλά οι ρυθμίσεις που αφορούν τη συσκευή και η εισαγωγή πραγματικού ιστορικού είναι απενεργοποιημένες.",
"merge_hint": "Τα εισαγόμενα στοιχεία προστίθενται και τίποτα τοπικό δεν χάνεται. Οι συγκρούσεις ονομάτων επιλύονται παρακάτω.",
"replace_warn": "Κάθε επιλεγμένη κατηγορία διαγράφεται και αντικαθίσταται από το αρχείο. Οι μη επιλεγμένες κατηγορίες παραμένουν ανέπαφες.",
"dest_reference_hint": "Οι εισαγόμενοι κύκλοι βελτιώνουν μόνο την αναγνώριση προγράμματος και δεν επηρεάζουν ποτέ τα στατιστικά χρήσης/ενέργειας.",
"dest_real_history_hint": "Οι εισαγόμενοι κύκλοι υπολογίζονται ως ιστορικό της ίδιας της συσκευής και τροφοδοτούν τα στατιστικά ενέργειας/χρήσης. Χρησιμοποιήστε το για τη μεταφορά μιας συσκευής σε νέα εγκατάσταση."
},
"phase_desc": {
"anti_crease": "Περιστασιακά σύντομες ανατροπές μετά την ολοκλήρωση για μείωση των ρυτίδων.",
@@ -962,6 +1020,10 @@
"doc": "Η ισχύς πρέπει να πέσει κάτω από αυτό μεταξύ των παλμών για να παραμείνει ενεργή η λειτουργία κατά των ρυτίδων.",
"label": "Κατώφλι Ισχύος Εξόδου"
},
"anti_wrinkle_idle_timeout": {
"label": "Μέγιστο Κενό Παλμών",
"doc": "Πόση ώρα μπορεί να παραμείνει ήσυχη η συσκευή ανάμεσα σε δύο παλμούς ανατροπής προτού λήξει η λειτουργία κατά τσακίσματος. Ορίστε το πάνω από το μεγαλύτερο κενό που αφήνει το στεγνωτήριό σας ανάμεσα στους παλμούς, αλλιώς κάθε επόμενος παλμός διαβάζεται ως εσφαλμένη εκκίνηση."
},
"anti_wrinkle_max_duration": {
"doc": "Οι παλμοί που είναι μεγαλύτεροι από αυτό αντιμετωπίζονται ως πραγματικός κύκλος και όχι ως αντιρυτιδικό περιτύλιγμα.",
"label": "Μέγιστη Διάρκεια"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "Κοινοποιήθηκαν {n} κύκλος(οι)· {failed} δεν ήταν δυνατό να ανεβούν.",
"store_device_shared_some_dup": "Κοινοποιήθηκαν {created} κύκλος(οι)· {dup} υπήρχαν ήδη στο κατάστημα.",
"store_download_failed": "Η λήψη απέτυχε: {error}",
"store_download_nothing": "Δεν υπάρχει τίποτα νέο για λήψη - αυτή η ρύθμιση βρίσκεται ήδη στη συσκευή σας."
"store_download_nothing": "Δεν υπάρχει τίποτα νέο για λήψη - αυτή η ρύθμιση βρίσκεται ήδη στη συσκευή σας.",
"export_selective_done": "Η εξαγωγή λήφθηκε",
"import_selective_done": "Εισήχθησαν {profiles} προφίλ και {cycles} κύκλοι"
},
"suggestion": {
"both_agree": "Το WashData προτείνει",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Δευτερόλεπτα πάνω από το κατώφλι για επιβεβαίωση εκκίνησης",
"start_threshold_w": "Ελάχιστα watt για να θεωρηθεί ότι ξεκίνησε",
"stop_threshold_w": "Κάτω από αυτό, το μηχάνημα θεωρείται εκτός λειτουργίας",
"anti_wrinkle_enabled": "Απορροφά τους παλμούς ανατροπής μετά την κύρια φάση αντί να τους διαβάζει ως νέους κύκλους",
"anti_wrinkle_exit_power": "Η ισχύς πρέπει να πέσει κάτω από αυτό μεταξύ των παλμών για να παραμείνει ενεργή η λειτουργία κατά τσακίσματος",
"anti_wrinkle_idle_timeout": "Ο χρόνος ησυχίας που επιτρέπεται ανάμεσα σε δύο παλμούς ανατροπής προτού λήξει η λειτουργία κατά τσακίσματος",
"anti_wrinkle_max_duration": "Ένας παλμός μεγαλύτερος από αυτό τερματίζει τη λειτουργία κατά τσακίσματος και ανοίγει νέο κύκλο",
"anti_wrinkle_max_power": "Ένας παλμός πάνω από αυτό τερματίζει τη λειτουργία κατά τσακίσματος και ανοίγει νέο κύκλο",
"dtw_bandwidth": "Στάδιο 3: ζώνη παραμόρφωσης Sakoe-Chiba (0 = DTW ανενεργό; προεπιλογή 0.2 = 20% μήκους κύκλου)",
"corr_weight": "Στάδιο 2: ισορροπία μεταξύ σχήματος καμπύλης (συσχέτιση) και επιπέδου ισχύος (MAE); προεπιλογή 0.45",
"duration_weight": "Στάδιο 4: πόσο επηρεάζει η συμφωνία διάρκειας τον τελικό βαθμό (προεπιλογή 0.22)",
@@ -1,34 +1,36 @@
{
"badge": {
"approved": "Approved",
"artifact_tip": "{n} anomalies detected (e.g. door opened mid-cycle) open to see them on the graph",
"auto": "(auto-detected)",
"awaiting": "Awaiting approval",
"awaiting_n": "Awaiting approval · {n} confirmed",
"awaiting_tip": "Awaiting community approval",
"built_in_tag": "built-in",
"declining": "↘ declining",
"energy_low": "Lower energy than usual",
"energy_spike": "Higher energy than usual",
"exists": "exists",
"fair_fit": "acceptable quality",
"fair_fit_tip": "Moderate match consistency some cycles assigned to this profile have lower confidence scores. Label more cycles or re-record the profile to improve accuracy.",
"feedback_requested": "Feedback requested",
"golden_cycle": "Recorded reference cycle",
"imported_tip": "Imported from the community store. Used for matching only, not counted in stats.",
"improving": "↗ improving",
"manual": "(manually selected)",
"needs_review": "Needs review",
"not_importable": "n/a here",
"overrun": "Ran longer than usual",
"underrun": "Finished faster than usual",
"energy_spike": "Higher energy than usual",
"energy_low": "Lower energy than usual",
"overrun_ratio": "({x}x expected)",
"poor_fit": "poor quality",
"poor_fit_tip": "Inconsistent match history consider rebuilding this profile",
"restart_gap_tip": "{n} HA restart gap: power trace has a hole",
"reviewed": "Reviewed",
"steady": "→ steady",
"using_defaults": "Using shipped defaults",
"using_tuned": "Using tuned weights",
"overrun_ratio": "({x}x expected)",
"underrun": "Finished faster than usual",
"underrun_ratio": "({pct}% of expected)",
"awaiting_tip": "Awaiting community approval",
"awaiting_n": "Awaiting approval · {n} confirmed",
"approved": "Approved",
"awaiting": "Awaiting approval",
"imported_tip": "Imported from the community store. Used for matching only, not counted in stats."
"using_defaults": "Using shipped defaults",
"using_tuned": "Using tuned weights"
},
"btn": {
"add_device": "+ Add device",
@@ -36,6 +38,7 @@
"add_maintenance": "Add maintenance event",
"add_phase": "+ Add phase",
"add_timer": "+ Add timer",
"analyze_import": "Analyze file",
"apply_all": "Apply all",
"apply_label": "Apply Label",
"apply_set_b": "Apply set B",
@@ -44,12 +47,17 @@
"auto_detect_split": "Auto-detect",
"auto_label_cycles": "Auto-label cycles",
"auto_label_cycles_tip": "Automatically assign profile names to unlabelled cycles whose match confidence clears the threshold",
"back": "Back",
"browse_community_setups": "Browse community setups",
"cancel": "Cancel",
"clear_debug": "Clear Debug Data",
"clear_splits": "Clear",
"close": "Close",
"compare": "Compare",
"confirm": "Confirm",
"confirm_appliance": "Confirm this appliance",
"confirmed": "Confirmed",
"connect_github": "Connect GitHub",
"convert_to_automation": "Convert to automation",
"correct": "Correct…",
"create": "Create",
@@ -59,19 +67,29 @@
"delete_group": "Delete Group",
"delete_group_tip": "Delete this group only - the member profiles are kept",
"delete_profile": "Delete Profile",
"delete_selected": "Delete selected ({n})",
"discard": "Discard",
"discard_tip": "Discard the recorded trace without saving",
"disconnect": "Disconnect",
"dismiss": "Dismiss",
"done": "Done",
"download_device": "Download this setup",
"download_export": "Download export",
"edit": "Edit",
"export": "Export",
"export_all": "Quick export everything",
"export_json": "Export to JSON",
"export_selected": "Export (choose data)",
"force_stop": "Force Stop",
"force_stop_tip": "Immediately end the current cycle and mark it as force-stopped",
"from_template": "From template ▾",
"group_suggest": "Group {n}: {members}",
"ignore": "Ignore",
"import": "Import",
"import_json": "Import from JSON",
"import_overwrite": "Import (overwrites data)",
"import_raw": "Advanced: replace all from JSON",
"import_selected": "Import selected",
"inspect": "Inspect",
"label": "Label",
"load": "Load",
@@ -89,6 +107,8 @@
"pause_cycle": "Pause",
"pause_cycle_tip": "Pause the running cycle the appliance will resume where it left off",
"pg_apply_device": "Apply to device",
"pg_apply_to_settings": "Save to settings",
"pg_apply_to_settings_tip": "Copy the values you edited here into this device's live settings",
"pg_autofill": "Auto-fill",
"play": "Play",
"process": "Process",
@@ -116,10 +136,10 @@
"resume_cycle": "Resume cycle",
"resume_cycle_tip": "Resume the paused cycle",
"revert": "Revert",
"revert_to_previous": "Restore this setting to its previous value",
"revert_settings": "Revert changes",
"revert_settings_tip": "Restore settings from before your last save",
"revert_settings_tip_none": "Save first to enable undo",
"revert_to_previous": "Restore this setting to its previous value",
"review": "Review",
"run": "Run",
"run_analysis": "🔍 Run suggestion analysis",
@@ -129,6 +149,7 @@
"run_simulation": "Run simulation",
"save": "Save",
"save_access_control": "Save Access Control",
"save_appliance": "Save appliance",
"save_group": "Save Group",
"save_panel_settings": "Save Panel Settings",
"save_phases": "Save phases",
@@ -136,10 +157,16 @@
"save_reminders": "Save reminders",
"save_review": "Save Review",
"save_settings": "Save Settings",
"search": "Search",
"select": "Select",
"select_all": "Select all",
"select_last20": "Last 20",
"select_none": "Clear",
"share": "Share",
"share_device": "⬆ Share this device",
"share_device_tip": "Share this appliance and its recorded reference cycles to the community store so others with the same machine can adopt them",
"share_n": "Share {n} cycle(s)",
"share_to_store": "Share to store",
"show_only": "Show only",
"skip_setup": "Skip setup",
"split": "Split",
@@ -150,25 +177,7 @@
"trim_split": "Trim / Split",
"undo": "Undo",
"use": "Use",
"wipe_all": "Wipe All Data",
"delete_selected": "Delete selected ({n})",
"group_suggest": "Group {n}: {members}",
"pg_apply_to_settings": "Save to settings",
"pg_apply_to_settings_tip": "Copy the values you edited here into this device's live settings",
"search": "Search",
"import": "Import",
"disconnect": "Disconnect",
"connect_github": "Connect GitHub",
"save_appliance": "Save appliance",
"share_to_store": "Share to store",
"share": "Share",
"confirm_appliance": "Confirm this appliance",
"confirmed": "Confirmed",
"share_device": "⬆ Share this device",
"share_device_tip": "Share this appliance and its recorded reference cycles to the community store so others with the same machine can adopt them",
"share_n": "Share {n} cycle(s)",
"download_device": "Download this setup",
"browse_community_setups": "Browse community setups"
"wipe_all": "Wipe All Data"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -290,11 +299,15 @@
"actions": "Actions",
"activity": "Activity",
"administrators": "Administrators",
"adopt_settings": "Also adopt settings",
"anonymous": "Anonymous",
"approved_only": "Approved only",
"aria_compare_chart": "Cycle comparison chart",
"aria_cycle_chart": "Cycle power trace",
"aria_envelope_chart": "Profile power envelope chart",
"aria_phase_chart": "Phase editor chart",
"aria_playground_chart": "Cycle power trace with draggable detection thresholds",
"aria_playground_chart2": "Interactive cycle power graph: hover to read time/power, scroll to zoom, drag to pan",
"aria_power_chart": "Power consumption chart",
"aria_spaghetti_chart": "Overlaid cycle power traces",
"aria_sweep_chart": "Parameter sweep results chart",
@@ -302,11 +315,26 @@
"artifact_high_power": "High power",
"artifact_interruption": "Interruption",
"artifact_low_power": "Low power",
"at": "at",
"auto_detect": "Auto-detect",
"auto_fine_tune_off": "auto fine-tune off",
"auto_fine_tune_on": "auto fine-tune on (around {hour}:00)",
"avg_cost": "Avg cost",
"cat_custom_phases": "Custom phases",
"cat_feedback": "Feedback & review labels",
"cat_history_logs": "History & change logs",
"cat_lifetime_stats": "Lifetime totals",
"cat_maintenance_log": "Maintenance log",
"cat_matching_config": "Matcher tuning",
"cat_ml_models": "ML models",
"cat_profile_groups": "Profile groups",
"cat_profiles": "Profiles (programs)",
"cat_real_cycles": "Cycles (run history)",
"cat_reference_cycles": "Reference cycles (imported)",
"cat_settings": "Detection & matching settings",
"cat_suggestions": "Suggestions",
"change": "Change",
"click_to_select": "click to select",
"clock_time": "Clock time",
"cohesion_good": "cohesion {pct}%",
"cohesion_low": "⚠ low cohesion {pct}%",
@@ -314,6 +342,10 @@
"concurrency": "Concurrency",
"confidence": "Confidence",
"confidence_threshold": "Confidence threshold",
"conflict_import_copy": "Import as copy",
"conflict_keep_mine": "Keep mine",
"conflict_overwrite": "Overwrite",
"conflict_resolution": "Name clashes",
"correct_duration": "Correct Duration (min, optional)",
"correct_profile": "Correct Profile",
"correlation": "Correlation",
@@ -323,11 +355,14 @@
"curves": "Curves",
"cycle": "Cycle",
"cycle_date_display": "Cycle date display",
"cycle_destination": "Where should imported cycles go?",
"cycle_health": "Cycle health",
"cycle_index": "cycle index →",
"cycles": "Cycles",
"cycles_count": "Cycles",
"cycles_lc": "cycles",
"cycles_short": "cycles",
"cycles_title": "Cycles ({n})",
"dashed": "dashed",
"date": "Date",
"debug_traces": "Debug Traces",
@@ -337,6 +372,8 @@
"default_tab": "Default tab when opening the panel",
"delta": "Δ",
"description": "Description",
"dest_real_history": "Real history (counts in stats)",
"dest_reference": "Reference (shape only)",
"drag_to_resize": "Drag to resize",
"dtw_blend_w": "Blend weight",
"dtw_blended": "Blended score",
@@ -345,64 +382,104 @@
"dtw_matched_profile": "(cycle's matched profile)",
"duration": "Duration",
"duration_agreement": "Duration agreement",
"duration_avg": "~{v}m avg",
"emphasis": "Emphasis",
"enable_access_control": "Enable per-user access control",
"enable_online": "Enable online features",
"end": "End",
"energy": "Energy",
"energy_agreement": "Energy agreement",
"envelope": "Envelope",
"eta_mins": "~{n}m left",
"eta_secs": "~{n}s left",
"event_timeline": "Event timeline",
"event_type": "Event type",
"expected": "Expected",
"expected_duration": "Expected Duration (min)",
"file_kb": "File (kB)",
"final_score": "Final score",
"flags": "Flags",
"from": "From",
"from_start": "From start",
"gap_s": "Gap (s)",
"group_name": "Group name",
"ha_restarted": "HA restarted",
"head_trim": "Head Trim (s)",
"health": "Health",
"flags": "Flags",
"hide_tabs": "Hide tabs for non-admins",
"import_mode": "How to combine",
"in_use": "In use",
"include_phase_map": "Include phase map",
"include_settings": "Include detection & matching settings",
"input": "Input:",
"json_data": "JSON Data",
"label": "Matched program",
"last": "Last",
"last_checked": "Last checked:",
"load_from_file": "Load from file",
"loading_paren": "(loading…)",
"mae_score": "MAE score",
"manual_duration": "Manual Duration (min, optional)",
"match": "Match",
"members": "Members",
"metric": "Metric",
"mode_existing_profile": "Add to Existing Profile",
"mode_merge": "Merge (keep mine)",
"mode_new_profile": "Create New Profile",
"mode_replace": "Replace selected",
"models_fine_tuned": "({count} model{plural} fine-tuned)",
"n_classic_suggestions": "{n} classic",
"n_imported_note": ", {n} imported",
"n_ml_suggestions": "{n} ML",
"n_selected": "{n} selected",
"n_shown": ", {n} shown",
"n_tuning_suggestions": "{n} tuning suggestion",
"na": "n/a",
"name_placeholder": "(name)",
"new_profile_name": "New Profile Name",
"no_duration": "no duration",
"notes": "Notes",
"of": "of",
"outcome": "Outcome",
"panel_default_tab": "Default tab",
"panel_language": "Panel language",
"pct_match_confidence": "{pct}% match confidence",
"per_cycle_short": "cycle",
"pg_alert_indefinite": "Would run indefinitely",
"pg_alert_stress_cap": "Hit safety cap",
"pg_alert_stress_ok": "Idle termination: cycle stopped",
"pg_alert_stress_warn": "Idle draw above stop threshold",
"pg_alert_timeout_end": "Ended by timeout, not prediction",
"pg_autofill_tip": "Auto-fill range from current value ({lo}{hi})",
"pg_best_marker": "Best: {v}",
"pg_best_value": "Best value found",
"pg_current_value": "Current value:",
"pg_detecting": "Detecting",
"pg_drag_hint": "Drag line on graph",
"pg_dtw": "DTW",
"pg_envelope_fit": "Envelope fit",
"pg_envelope_fit_tip": "How closely the cycle sits inside this profile's saved power envelope - a different lens than match confidence.",
"pg_ev_ending": "Ending",
"pg_ev_running": "Running",
"pg_group_advanced": "Edge cases",
"pg_group_detection": "Detection triggers",
"pg_group_matching": "Program matching",
"pg_group_timing": "Timing rules",
"pg_idle": "Idle",
"pg_stress_group": "Idle termination test",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Idle level (W)",
"pg_stress_idle_w_desc": "Override the auto-detected standby floor (leave blank for auto).",
"pg_stress_toggle": "Test idle termination",
"pg_stress_toggle_desc": "Simulates the appliance staying at its standby draw after recording ends -- shows if and when WashData stops the cycle.",
"pg_last_run": "Last run: {date}",
"pg_load_run": "Reload this run",
"pg_match_confidence": "Match confidence",
"pg_match_score": "Match score",
"pg_mode_optimize": "Optimize",
"pg_pct_of": "{p}% of {total}",
"pg_poor_match": "Poor match",
"pg_recent_runs": "Recent runs",
"pg_strong_match": "Strong match",
"pg_tile_ambiguous": "Ambiguous",
"pg_tile_ambiguous_tip": "Cycles with a near-tie between two programs",
@@ -412,6 +489,7 @@
"pg_tile_matched_tip": "Cycles correctly attributed to a known program",
"pg_time_left": "Time left",
"pg_weak_match": "Weak match",
"pg_zoom_hint": "double-click to reset zoom",
"phase_name": "Name",
"phase_timeline": "Phase timeline",
"power": "Power",
@@ -421,6 +499,9 @@
"program": "Program",
"program_tip": "Override which profile is matched to the current cycle. Auto-detect lets the integration pick the best match automatically. Pin a specific program to force-match it when auto-detect is wrong or you know what is running.",
"progress": "Progress",
"quality": "Quality",
"rate_n": "{n} stars",
"rate_quality": "Rate quality",
"raw_socket": "Raw socket",
"read_only": "Read-only",
"ref_cycle": "Reference Cycle (optional)",
@@ -431,6 +512,7 @@
"rename_profile": "Rename Profile",
"replay_duration": "Replay",
"resulting_profile": "Resulting profile",
"reviewed_on": "reviewed {date}",
"save_mode": "Save Mode",
"score": "Score",
"score_breakdown": "Score breakdown",
@@ -445,6 +527,7 @@
"settings_basic": "Basic",
"settings_detail_level": "Settings detail level",
"settings_overrides": "Settings overrides",
"show_contributor": "Show contributor names",
"show_debug": "Show live match debug card on the Status page (confidence, ambiguity, top candidates)",
"show_expected": "Show expected curve overlay (matched profile, orange)",
"show_raw": "Show raw socket toggle in live power graph",
@@ -452,75 +535,35 @@
"stage2": "Stage 2 core similarity",
"stage3": "Stage 3 DTW",
"stage4": "Stage 4 agreement",
"start": "Start",
"status": "Status",
"store_brand": "Appliance brand",
"store_model": "Appliance model",
"tags": "Tags",
"tail_trim": "Tail Trim (s)",
"task_merge": "Merging cycles",
"task_ml_training": "Learning",
"task_pg_detail": "Simulate cycle",
"task_pg_history": "Test on history",
"task_pg_sweep": "Optimize",
"task_rebuild": "Rebuilding envelopes",
"task_reprocess": "Reprocessing",
"task_split": "Splitting cycle",
"task_trim": "Trimming cycle",
"time_remaining": "~{v} remaining",
"timer_auto_pause": "Auto-pause",
"timer_min": "min",
"timer_msg_placeholder": "Message (optional, {device}/{program}/{minutes})",
"to": "To",
"to_end": "To end",
"ungrouped": "Ungrouped",
"unit_s": "(s)",
"unknown": "Unknown",
"unlabelled": "Unlabelled",
"unlabelled_paren": "(unlabelled)",
"warp_path": "Warp path",
"input": "Input:",
"start": "Start",
"end": "End",
"unit_s": "(s)",
"reviewed_on": "reviewed {date}",
"unknown": "Unknown",
"quality": "Quality",
"tags": "Tags",
"at": "at",
"pct_match_confidence": "{pct}% match confidence",
"ha_restarted": "HA restarted",
"from_start": "From start",
"to_end": "To end",
"click_to_select": "click to select",
"envelope": "Envelope",
"zoom_hint": "scroll to zoom · dblclick to reset",
"loading_paren": "(loading…)",
"time_remaining": "~{v} remaining",
"no_duration": "no duration",
"duration_avg": "~{v}m avg",
"name_placeholder": "(name)",
"n_shown": ", {n} shown",
"n_imported_note": ", {n} imported",
"cycles_title": "Cycles ({n})",
"pg_detecting": "Detecting",
"pg_best_marker": "Best: {v}",
"pg_mode_optimize": "Optimize",
"pg_group_matching": "Program matching",
"pg_zoom_hint": "double-click to reset zoom",
"aria_playground_chart2": "Interactive cycle power graph: hover to read time/power, scroll to zoom, drag to pan",
"pg_alert_timeout_end": "Ended by timeout, not prediction",
"pg_alert_indefinite": "Would run indefinitely",
"task_pg_history": "Test on history",
"task_pg_sweep": "Optimize",
"task_pg_detail": "Simulate cycle",
"task_split": "Splitting cycle",
"task_trim": "Trimming cycle",
"task_merge": "Merging cycles",
"task_rebuild": "Rebuilding envelopes",
"task_reprocess": "Reprocessing",
"task_ml_training": "Learning",
"eta_secs": "~{n}s left",
"eta_mins": "~{n}m left",
"pg_recent_runs": "Recent runs",
"pg_load_run": "Reload this run",
"pg_match_confidence": "Match confidence",
"pg_envelope_fit": "Envelope fit",
"pg_envelope_fit_tip": "How closely the cycle sits inside this profile's saved power envelope - a different lens than match confidence.",
"enable_online": "Enable online features",
"show_contributor": "Show contributor names",
"store_brand": "Appliance brand",
"store_model": "Appliance model",
"anonymous": "Anonymous",
"rate_quality": "Rate quality",
"rate_n": "{n} stars",
"approved_only": "Approved only",
"include_phase_map": "Include phase map",
"include_settings": "Include detection & matching settings",
"adopt_settings": "Also adopt settings"
"pg_anti_wrinkle": "Anti-wrinkle"
},
"log": {
"all_levels": "All levels",
@@ -567,14 +610,29 @@
},
"modal": {
"auto_label": "Auto-Label Cycles",
"clear_debug_msg": "Delete all stored debug traces?",
"clear_debug_title": "Clear Debug Data",
"correct_feedback": "Correct Feedback",
"create_profile": "Create Profile",
"cycle": "Cycle",
"delete_automation_msg": "Delete the automation \"{name}\" from Home Assistant? This cannot be undone.",
"delete_automation_title": "Delete Automation",
"delete_maintenance_msg": "Delete this maintenance record? This cannot be undone.",
"delete_maintenance_title": "Delete Maintenance Event",
"delete_phase_msg": "Delete phase \"{name}\"?",
"delete_phase_title": "Delete Phase",
"discard_recording_msg": "Discard the saved recording? This cannot be undone.",
"discard_recording_title": "Discard Recording",
"dismiss_all_msg": "Dismiss all {count} pending feedback requests?",
"dismiss_all_ok": "Dismiss All",
"dismiss_all_title": "Dismiss All Feedbacks",
"edit_group": "Edit profile group",
"edit_phase": "Edit Phase",
"export_select": "Export - choose data",
"force_stop_msg": "Force-stop the active cycle now? The cycle will be saved as interrupted.",
"force_stop_title": "Force Stop Cycle",
"import_config": "Import Configuration",
"import_wizard": "Import - choose data",
"label_cycle": "Label Cycle",
"merge_cycles": "Merge {n} Cycles",
"new_group": "New profile group",
@@ -584,29 +642,25 @@
"process_history_title": "Process History",
"process_recording": "Process Recording",
"relabel_cycles": "Relabel {count} cycles",
"delete_automation_title": "Delete Automation",
"delete_automation_msg": "Delete the automation \"{name}\" from Home Assistant? This cannot be undone.",
"remove_legacy_title": "Remove Legacy Actions",
"remove_legacy_msg": "Remove the legacy custom actions? They will stop firing on cycle events. This cannot be undone from the panel.",
"discard_recording_title": "Discard Recording",
"discard_recording_msg": "Discard the saved recording? This cannot be undone.",
"dismiss_all_title": "Dismiss All Feedbacks",
"dismiss_all_msg": "Dismiss all {count} pending feedback requests?",
"dismiss_all_ok": "Dismiss All",
"delete_phase_title": "Delete Phase",
"delete_phase_msg": "Delete phase \"{name}\"?",
"clear_debug_title": "Clear Debug Data",
"clear_debug_msg": "Delete all stored debug traces?",
"wipe_all_title": "Wipe All Data",
"wipe_all_msg": "⚠️ This permanently deletes ALL cycles and profiles. This cannot be undone.",
"wipe_all_ok": "Wipe Everything",
"force_stop_title": "Force Stop Cycle",
"force_stop_msg": "Force-stop the active cycle now? The cycle will be saved as interrupted.",
"remove_legacy_title": "Remove Legacy Actions",
"store_import": "Import reference cycle",
"store_share": "Share to community store",
"store_share_device": "Share this device"
"store_share_device": "Share this device",
"wipe_all_msg": "⚠️ This permanently deletes ALL cycles and profiles. This cannot be undone.",
"wipe_all_ok": "Wipe Everything",
"wipe_all_title": "Wipe All Data"
},
"msg": {
"adopt_settings_hint": "Overwrite this device's detection & matching thresholds with the shared ones. Your notifications, entities and energy price are never changed.",
"advisory_duration_trend_up": "'{name}' cycles are running progressively longer (about +{pct}% per cycle). If the appliance's behaviour changed, re-record or rebuild this profile.",
"advisory_energy_trend_up": "'{name}' is drawing progressively more energy (about +{pct}% per cycle) - worth checking the appliance if that is unexpected.",
"advisory_phase_inconsistent": "'{name}' looks like it mixes different programs or temperatures - its cycles heat for very different lengths of time. Splitting it into separate profiles (e.g. per temperature) will improve matching and time estimates.",
"advisory_phase_inconsistent_title": "⚠ Possibly mixed programs",
"advisory_poor_health": "'{name}' has a low fit score - its recent cycles vary a lot or match weakly. Review its cycles or re-record the profile so matching and time estimates stay accurate.",
"advisory_shape_drift": "'{name}' has drifted significantly from its original power shape. The appliance may have changed behaviour over time (e.g. limescale, wear). Consider re-recording this profile with recent cycles.",
"advisory_shape_drift_corr": "'{name}' has drifted significantly from its original power shape (correlation {corr}). The appliance may have changed behaviour over time (e.g. limescale, wear). Consider re-recording this profile with recent cycles.",
"analyzing": "Analyzing…",
"appliance_monitor": "Appliance monitor",
"artifact_dip_detail": "Dropped below the usual power band for ~{n}s.",
"artifact_footer": "Highlighted on the graph above. These are transient artifacts (e.g. the door opened mid-cycle), not necessarily problems.",
@@ -619,21 +673,37 @@
"clear_debug_hint": "Remove stored debug data to free space.",
"collecting_data": "Collecting data {need} more cycle{plural} before fine-tuning can start ({current}/{min}).",
"compare_overlay_profiles": "Overlay profiles (faint)",
"compare_overlay_tip": "Overlay learned profile envelopes to see which program each cycle resembles.",
"compare_profiles_tip": "Overlay other profile envelopes on the chart above to see which one best fits this cycle.",
"compare_selected_cycles": "Selected cycles (solid) show / hide",
"connect_to_confirm": "Connect in the settings gear to confirm or rate.",
"consider_new_profile": "Consider creating a new profile.",
"coverage_gap": "recent cycles have no matching profile ({pct}% of last 30).",
"coverage_gap_similar_cycles": "{count} similar unlabelled cycles found create a profile to start matching them.",
"cycles_deleted": "{count} cycle(s) deleted",
"dest_real_history_hint": "Imported cycles count as this device's own history and feed energy/usage stats. Use for moving one appliance to a new install.",
"dest_reference_hint": "Imported cycles only improve program matching and never affect usage/energy statistics.",
"device_type_mismatch_warn": "This export is from a different appliance type ({src} vs {local}). Programs and cycles can still be imported as reference data, but device-specific settings and real-history import are disabled.",
"diagnostics_load_failed": "Could not load diagnostics: {error}",
"duration_trend_down_tip": "Duration down ({pct}%/cycle)",
"duration_trend_up_tip": "Duration up ({pct}%/cycle)",
"energy_trend_down_tip": "Energy down ({pct}%/cycle)",
"energy_trend_up_tip": "Energy up ({pct}%/cycle)",
"enough_data": "Enough data to learn from ({current}/{min} cycles).",
"export_description": "Export all profiles and cycles to JSON, or restore from a previous export.",
"export_description": "Choose exactly which profiles, cycles, settings and more to export to JSON, or analyze a file and import only the parts you want.",
"export_select_intro": "Tick exactly what to include. Selecting profiles without their cycles still exports a matchable program (its learned shape travels along).",
"feedback_cycles_pending": "{n} cycle{s} to review",
"feedback_prompt": "Confirm it was right, correct the program, or ignore.",
"feedback_relabel_hint": "Re-labelling this cycle resolves it too.",
"filter_by_profile": "Filter by profile…",
"group_modal_help": "Group programs with the same shape that differ in temperature/spin (durations may vary). Matching scores the group as one candidate, then picks the best-fitting member. Pick at least 2; the overlay shows how alike they are.",
"group_not_cohesive": "These profiles aren't similar enough to group reliably, so matching treats them individually until you remove the outlier or split the group.",
"group_preview_hint": "Tick 2+ members to preview and compare their power curves.",
"head_trim_hint": "Remove this many seconds from the start",
"import_analyze_hint": "Load an exported file (or paste its JSON). WashData analyzes it and shows exactly what can be imported before anything changes.",
"import_intro": "Load an exported file or paste a JSON payload below.",
"imported_readonly": "Imported from the community store. Shown for reference and matching. It is not counted in your stats and cannot be edited.",
"include_settings_hint": "Share this device's recognition and matching thresholds (not your notifications, entities or energy price). Adopters choose whether to apply them.",
"legacy_actions_title": "{count} legacy custom action still running",
"live_chart_loading": "Live power chart appears as readings arrive.",
"loading": "Loading…",
@@ -646,26 +716,29 @@
"maintenance_load_error": "Could not load maintenance data: {error}",
"maintenance_requires_access": "Maintenance and export/import require full access.",
"manual_duration_hint": "The profile's average/expected cycle length, used for time-remaining estimates. Edit to set it; leaving it unchanged keeps the current value.",
"manual_duration_ref_hint": "Only used when no reference cycle is selected a reference cycle sets the duration from its own length.",
"matching_tuning_intro": "When learning, WashData also adjusts how much program matching weighs shape versus duration and energy.",
"merge_hint": "Imported items are added; nothing local is lost. Name clashes are resolved below.",
"merge_intro": "The selected cycles are combined into one (chronological order; gaps filled with 0 W). Pick the resulting profile.",
"merge_no_imports": "Imported recordings cannot be merged into a real cycle. Deselect them to merge.",
"ml_intro": "WashData ships with smart models that work out of the box.",
"ml_learned_intro": "Models fine-tuned to this machine.",
"ml_loading": "loading ML…",
"ml_settings_intro": "Two independent switches: one applies the models while a cycle runs, the other lets WashData fine-tune them to your machine over time.",
"model_not_found": "Not in the catalog yet.",
"name_first_program": "You have enough cycles name your first program to start matching.",
"near_duplicate_cluster": "near-duplicate profile cluster detected. Grouping lets matching reliably pick between look-alikes (e.g. same program at different temperature/spin).",
"no_cycles_match": "No cycles match the current filter.",
"no_cycles_profile": "No cycles for this profile.",
"no_cycles_selected": "Select at least one cycle first.",
"no_cycles_yet": "No cycles recorded yet.",
"imported_readonly": "Imported from the community store. Shown for reference and matching. It is not counted in your stats and cannot be edited.",
"merge_no_imports": "Imported recordings cannot be merged into a real cycle. Deselect them to merge.",
"no_device_selected": "No device selected.",
"no_devices": "No WashData devices configured yet.",
"no_envelope": "No envelope yet - rebuild after labelling cycles.",
"no_envelope_overlay": "No envelope available to overlay.",
"no_fine_tuned": "Nothing fine-tuned yet WashData is using its built-in models.",
"no_logs": "No log records buffered yet.",
"no_logs_match": "No log records match these filters.",
"no_maintenance": "No maintenance recorded yet.",
"no_match_yet": "No match attempt yet - this populates during a running cycle.",
"no_other_users": "No other Home Assistant users found.",
@@ -680,19 +753,40 @@
"no_suggestions": "No active suggestions.",
"notify_services_hint": "Use {entity} service IDs (comma-separated for multiple). Template variables: {vars}.",
"old_actions_warning": "Configured with the old actions editor (now removed). They still fire on cycle events but can no longer be edited here. Convert them into a normal automation, or remove them.",
"onboard_download": "New device? Adopt a ready-made setup (programs, reference cycles and phases) from another WashData user with the same appliance.",
"onboarding_progress": "{n} / 3 cycles observed",
"onboarding_watching": "Run your appliance normally WashData is watching. After 3 cycles, program matching will begin.",
"online_intro": "Share and download reference cycles with other WashData users. All online features are opt-in and off by default.",
"online_intro_global": "Browse and share reference recordings with other WashData users, and confirm appliance entries. One connection applies to your whole WashData integration; appliance brand and model are set per device under Basic. All online features are opt-in and off by default.",
"online_unavailable": "Online features are not available on this server.",
"pending_feedback": "Pending detection feedback",
"performance_trend": "Performance trend ({n} cycles)",
"performance_trending": "Performance trending",
"pg_analysis_empty": "Load a cycle to see match analysis.",
"pg_analysis_empty2": "Press Run to see match analysis.",
"pg_analysis_hint": "Select a cycle and click ↺ to load analysis.",
"pg_analysis_hint2": "Pick a cycle and press Run to load match analysis.",
"pg_apply_confirm": "Apply best value: {label} = {value}?",
"pg_apply_settings_confirm": "Save these {n} setting(s) to this device? {list}",
"pg_stress_terminated_detail": "Settled to ~{idle}W idle -- cycle ended {h}h {m}m later via {reason}.",
"pg_stress_above_threshold_detail": "Idle draw ~{idle}W is at or above the effective stop threshold ({stop}W) -- the cycle never registered as quiet. Raise stop_threshold_w to fix.",
"pg_stress_hit_cap_detail": "Cycle ran {h}h {m}m without stopping -- force-stopped by the safety cap. Idle draw {idle}W vs stop threshold {stop}W.",
"pg_canvas_empty": "Select a cycle above, then press Load to see its power trace.",
"pg_canvas_empty2": "Pick a cycle above and press Run to simulate it. Then hover to read values, scroll to zoom, and drag to pan.",
"pg_chart_caption": "Detection/match rate across values of {param}",
"pg_history_intro2": "Replay your recent cycles through the real detector and matcher with the settings above. Click any row to load that cycle in the graph; edit a setting to see a before/after comparison.",
"pg_match_rate": "{pct}% match rate on {total} cycles",
"pg_restart_note": "Restart Home Assistant to enable simulation tools.",
"pg_row_load_hint": "Load this cycle in the graph above",
"pg_stress_synthetic": "idle simulation starts here",
"pg_running_history": "Replaying your cycles…",
"pg_running_sweep": "Testing values across your cycles…",
"pg_select_cycle_hint": "Select a cycle and click ↺ to load",
"pg_sim_progress": "{done} / {total} cycles",
"pg_simulating": "Simulating cycle…",
"pg_starting": "Starting…",
"pg_sweep_intro": "What if {param} were different? Test {steps} values across your last {cycles} cycles to find the setting where the most cycles are correctly matched.",
"pg_sweep_progress": "Step {done} / {total}",
"pg_sweep_step": "Step {done} / {total}",
"pg_undetected": "{n} cycle{s} undetected",
"pg_verdict_bad": "Needs attention: many cycles are going undetected.",
@@ -700,6 +794,7 @@
"pg_verdict_ok": "Acceptable: some cycles missed. Try lowering the start threshold.",
"phase_catalog_intro": "Named segments of a cycle (Pre-wash, Heating, Spin…). Assign them to a profile from its control panel.",
"phase_ranges_intro": "Phase ranges (minutes from cycle start) overlaid on the average curve. Edit values to preview live.",
"pick_brand_first": "Pick an appliance brand first.",
"playground_intro": "A safe sandbox to replay past cycles with different settings, compare two setups side by side, and inspect how a cycle aligns to a profile. Nothing here changes your live data until you explicitly apply it.",
"playground_needs_restart": "Restart Home Assistant to enable the Playground tools.",
"preferences_admin": "Preferences, panel & access control",
@@ -713,6 +808,7 @@
"recent_logs": "Recent ha_washdata records",
"recording_in_progress": "Recording in progress",
"reminders_intro": "Show a reminder in the panel this many cycles after the last service. Leave blank or 0 to turn a reminder off.",
"replace_warn": "Each ticked category is wiped and replaced from the file. Unticked categories are left untouched.",
"restart_gap_footer": "Highlighted on the graph. Power data is missing for these intervals; matching used only real readings.",
"restart_gap_header": "{n} HA restart gap during this cycle",
"restart_gap_item": "{dur} gap",
@@ -732,19 +828,32 @@
"settings_basic_note": "Showing essential settings. Switch to Advanced for the full list.",
"shape_drift_advisory": "⚠ Shape drifting",
"shape_drift_detail": "The power pattern for this profile has shifted over time possible appliance wear or maintenance needed (e.g. descaling, filter cleaning).",
"share_consent": "I confirm these cycles ran to normal completion without interruption",
"share_device_none": "No shareable cycles yet. Mark a recorded or hand-picked cycle as a reference cycle (⭐) in the Cycles tab first.",
"share_guideline_naming": "Name each profile exactly as shown on the appliance dial or display (e.g. 'Cotton 40', 'Eco 60').",
"share_guideline_quality": "Only share cycles that completed normally -- no mid-cycle interruptions, door-open events, or power blips.",
"share_guideline_review": "Your upload starts as pending and appears publicly once enough community members confirm it.",
"share_guidelines_title": "Before you share",
"share_profile_no_cycles": "No reference cycles mark a cycle as ⭐ in the Cycles tab to include this profile",
"show_all_settings": "Show all settings",
"showing_suggestions": "Showing {count} setting with suggestions.",
"split_intro": "Click the graph to add or remove a split point, or auto-detect by idle gaps. Each resulting segment can get its own profile.",
"storage_diagnostics": "Storage stats, maintenance, export/import",
"store_download_device_intro": "Adopt every shared program and its reference cycles onto your device. Your own recorded cycles and stats are not affected.",
"store_enable_hint": "Enable online features in Settings to browse and import community reference cycles.",
"store_picker_offline": "Enable online features in the settings gear to pick from the community catalog.",
"store_share_device_intro": "Upload {brand} {model} with the reference cycles you select. Others with the same appliance can adopt your programs. Entries are reviewed before appearing publicly.",
"store_share_intro": "Upload this reference cycle so others with the same appliance can use it. It is reviewed before appearing publicly.",
"sug_staged": "Set {key} = {val}. Save to apply.",
"tail_trim_hint": "Remove this many seconds from the end",
"toast_auto_detect_enabled": "Auto-detect enabled",
"toast_auto_label_complete": "Auto-label complete",
"toast_auto_label_failed": "Auto-label failed: {error}",
"toast_automation_failed": "Could not create automation: {error}",
"toast_automation_migrated": "Actions migrated to an automation; opening editor",
"toast_convert_failed": "Convert failed: {error}",
"toast_convert_rolled_back": "Migration failed and was rolled back (no automation left behind): {error}",
"toast_convert_orphan": "The automation was created, but clearing the old actions failed. Do not retry: remove the legacy actions manually to avoid a duplicate automation.",
"toast_convert_rolled_back": "Migration failed and was rolled back (no automation left behind): {error}",
"toast_create_failed": "Create failed: {error}",
"toast_delete_failed": "Delete failed: {error}",
"toast_error": "Error: {error}",
@@ -769,58 +878,7 @@
"updated": "Updated",
"warmup_badge": "Still learning ({done}/{needed} cycles)",
"warmup_detail": "This profile needs {needed} labelled cycles before auto-matching begins. Every confirmed cycle helps it learn.",
"wipe_history_warning": "Permanently delete all cycles and profiles. Cannot be undone.",
"compare_overlay_tip": "Overlay learned profile envelopes to see which program each cycle resembles.",
"duration_trend_up_tip": "Duration up ({pct}%/cycle)",
"duration_trend_down_tip": "Duration down ({pct}%/cycle)",
"energy_trend_up_tip": "Energy up ({pct}%/cycle)",
"energy_trend_down_tip": "Energy down ({pct}%/cycle)",
"performance_trending": "Performance trending",
"pg_select_cycle_hint": "Select a cycle and click ↺ to load",
"pg_sweep_progress": "Step {done} / {total}",
"pg_analysis_hint": "Select a cycle and click ↺ to load analysis.",
"diagnostics_load_failed": "Could not load diagnostics: {error}",
"manual_duration_ref_hint": "Only used when no reference cycle is selected a reference cycle sets the duration from its own length.",
"head_trim_hint": "Remove this many seconds from the start",
"tail_trim_hint": "Remove this many seconds from the end",
"advisory_poor_health": "'{name}' has a low fit score - its recent cycles vary a lot or match weakly. Review its cycles or re-record the profile so matching and time estimates stay accurate.",
"advisory_shape_drift": "'{name}' has drifted significantly from its original power shape. The appliance may have changed behaviour over time (e.g. limescale, wear). Consider re-recording this profile with recent cycles.",
"advisory_shape_drift_corr": "'{name}' has drifted significantly from its original power shape (correlation {corr}). The appliance may have changed behaviour over time (e.g. limescale, wear). Consider re-recording this profile with recent cycles.",
"advisory_duration_trend_up": "'{name}' cycles are running progressively longer (about +{pct}% per cycle). If the appliance's behaviour changed, re-record or rebuild this profile.",
"advisory_energy_trend_up": "'{name}' is drawing progressively more energy (about +{pct}% per cycle) - worth checking the appliance if that is unexpected.",
"advisory_phase_inconsistent": "'{name}' looks like it mixes different programs or temperatures - its cycles heat for very different lengths of time. Splitting it into separate profiles (e.g. per temperature) will improve matching and time estimates.",
"advisory_phase_inconsistent_title": "⚠ Possibly mixed programs",
"pg_running_history": "Replaying your cycles…",
"pg_running_sweep": "Testing values across your cycles…",
"pg_row_load_hint": "Load this cycle in the graph above",
"pg_history_intro2": "Replay your recent cycles through the real detector and matcher with the settings above. Click any row to load that cycle in the graph; edit a setting to see a before/after comparison.",
"pg_simulating": "Simulating cycle…",
"pg_canvas_empty2": "Pick a cycle above and press Run to simulate it. Then hover to read values, scroll to zoom, and drag to pan.",
"pg_apply_settings_confirm": "Save these {n} setting(s) to this device? {list}",
"no_logs_match": "No log records match these filters.",
"pg_analysis_hint2": "Pick a cycle and press Run to load match analysis.",
"pg_analysis_empty2": "Press Run to see match analysis.",
"store_enable_hint": "Enable online features in Settings to browse and import community reference cycles.",
"online_intro": "Share and download reference cycles with other WashData users. All online features are opt-in and off by default.",
"store_share_intro": "Upload this reference cycle so others with the same appliance can use it. It is reviewed before appearing publicly.",
"online_intro_global": "Browse and share reference recordings with other WashData users, and confirm appliance entries. One connection applies to your whole WashData integration; appliance brand and model are set per device under Basic. All online features are opt-in and off by default.",
"online_unavailable": "Online features are not available on this server.",
"store_picker_offline": "Enable online features in the settings gear to pick from the community catalog.",
"pick_brand_first": "Pick an appliance brand first.",
"model_not_found": "Not in the catalog yet.",
"connect_to_confirm": "Connect in the settings gear to confirm or rate.",
"store_share_device_intro": "Upload {brand} {model} with the reference cycles you select. Others with the same appliance can adopt your programs. Entries are reviewed before appearing publicly.",
"share_device_none": "No shareable cycles yet. Mark a recorded or hand-picked cycle as a reference cycle (⭐) in the Cycles tab first.",
"store_download_device_intro": "Adopt every shared program and its reference cycles onto your device. Your own recorded cycles and stats are not affected.",
"onboard_download": "New device? Adopt a ready-made setup (programs, reference cycles and phases) from another WashData user with the same appliance.",
"include_settings_hint": "Share this device's recognition and matching thresholds (not your notifications, entities or energy price). Adopters choose whether to apply them.",
"adopt_settings_hint": "Overwrite this device's detection & matching thresholds with the shared ones. Your notifications, entities and energy price are never changed.",
"share_guidelines_title": "Before you share",
"share_guideline_naming": "Name each profile exactly as shown on the appliance dial or display (e.g. 'Cotton 40', 'Eco 60').",
"share_guideline_quality": "Only share cycles that completed normally -- no mid-cycle interruptions, door-open events, or power blips.",
"share_guideline_review": "Your upload starts as pending and appears publicly once enough community members confirm it.",
"share_consent": "I confirm these cycles ran to normal completion without interruption",
"share_profile_no_cycles": "No reference cycles mark a cycle as ⭐ in the Cycles tab to include this profile"
"wipe_history_warning": "Permanently delete all cycles and profiles. Cannot be undone."
},
"pg_desc": {
"completion_min_seconds": "Shortest run that counts as a real cycle",
@@ -843,7 +901,12 @@
"dtw_ddtw_scale": "Stage 3: DDTW half-saturation distance; smaller = more shape-sensitive (default 30)",
"dtw_refine_top_n": "Stage 3: candidates DTW re-scores; raise to 7-9 if correct profile ranks 4th-5th (default 5)",
"duration_scale": "Stage 4: log-ratio where duration agreement halves; smaller = stricter penalty (default 0.175)",
"energy_scale": "Stage 4: log-ratio where energy agreement halves; smaller = stricter penalty (default 0.25)"
"energy_scale": "Stage 4: log-ratio where energy agreement halves; smaller = stricter penalty (default 0.25)",
"anti_wrinkle_idle_timeout": "Quiet time allowed between two tumble pulses before anti-wrinkle ends",
"anti_wrinkle_max_power": "A pulse above this ends anti-wrinkle and opens a new cycle",
"anti_wrinkle_enabled": "Absorb the tumble pulses after the main phase instead of reading them as new cycles",
"anti_wrinkle_max_duration": "A pulse longer than this ends anti-wrinkle and opens a new cycle",
"anti_wrinkle_exit_power": "Power must fall below this between pulses for anti-wrinkle to stay active"
},
"phase_desc": {
"anti_crease": "Occasional short tumbles after completion to reduce wrinkles.",
@@ -1225,10 +1288,6 @@
"doc": "Seconds a pump may run continuously before it is flagged as possibly stuck (fires the stuck-pump event).",
"label": "Pump Stuck Duration"
},
"running_dead_zone": {
"doc": "After a cycle starts, power dips within this window are ignored. Washing machines fill with cold water (dropping near 0 W before heating) - without this protection that fill phase looks like a cycle end. This does NOT skip data: the full power trace is recorded from T=0. The suggestion engine measures your machine's actual startup pattern and sizes this automatically.",
"label": "Running Dead Zone"
},
"sampling_interval": {
"doc": "Expected time between sensor readings - used to size the smoothing window and start debounce correctly. Every sensor update is captured regardless of this value; it only calibrates the downstream calculations. The suggestion engine measures your sensor's actual cadence from past cycles and sets this automatically.",
"label": "Sampling Interval"
@@ -1323,6 +1382,10 @@
"energy_scale": {
"label": "Energy Sharpness",
"doc": "Sharpness of the Stage 4 energy agreement penalty. Smaller = stricter: an energy mismatch hurts more. Default 0.25 is intentionally more forgiving than Duration Sharpness because energy varies with load. Pair with Energy Weight."
},
"anti_wrinkle_idle_timeout": {
"label": "Max Pulse Gap",
"doc": "How long the machine may stay quiet between two tumble pulses before anti-wrinkle mode ends. Set it above the longest gap your dryer leaves between pulses, otherwise every later pulse is read as a false start."
}
},
"setting_group": {
@@ -1477,10 +1540,6 @@
"higher": "Commits only high-confidence matches; more cycles stay unlabelled",
"lower": "Matches more cycles; some may be slightly wrong"
},
"running_dead_zone": {
"higher": "Ignores brief power spikes during idle less noise-sensitive",
"lower": "Responds to shorter power bursts catches fast transient activity"
},
"start_threshold_w": {
"higher": "Avoids false starts from brief power spikes",
"lower": "Catches low-power programs; may trigger on noise"
@@ -1575,9 +1634,11 @@
"access_saved": "Access control saved",
"all_wiped": "All data wiped",
"analysis_complete_none": "Analysis complete: no new suggestions",
"appliance_added": "Appliance added - awaiting approval",
"apply_failed": "Apply failed: {error}",
"auto_label_complete": "Auto-label complete",
"automation_deleted": "Automation deleted",
"brand_added": "Brand added - awaiting approval",
"correction_submitted": "Correction submitted",
"cycle_deleted": "Cycle deleted",
"cycle_force_stopped": "Cycle force-stopped",
@@ -1588,15 +1649,18 @@
"cycles_merged": "Cycles merged",
"delete_failed": "Delete failed: {error}",
"delete_partial_failed": "Some items could not be deleted and were restored",
"device_approved": "Approved by the community",
"envelope_rebuilt": "Envelope rebuilt",
"envelopes_rebuilt": "Envelopes rebuilt",
"export_downloaded": "Export downloaded",
"export_selective_done": "Export downloaded",
"feedback_all_dismissed": "All feedbacks dismissed",
"feedback_confirmed": "Feedback confirmed",
"feedback_dismissed": "Feedback dismissed",
"group_deleted": "Group deleted",
"group_name_required": "Group name is required",
"group_saved": "Group saved",
"import_selective_done": "Imported {profiles} profile(s) and {cycles} cycle(s)",
"import_successful": "Import successful; integration reloading",
"json_required": "JSON data is required",
"legacy_removed": "Legacy actions removed",
@@ -1613,12 +1677,20 @@
"name_required": "Name required",
"no_split_found": "No idle gaps found to split on",
"panel_settings_saved": "Panel settings saved",
"pause_no_cycle": "No active cycle to pause",
"pg_run_gone": "That run is no longer available.",
"phase_name_required": "Phase name is required",
"phase_updated": "Phase updated",
"phases_saved": "Phases saved",
"preferences_saved": "Preferences saved",
"processed": "Processed {bits}",
"processed_cycles": "{n} cycles",
"processed_models": "{n} model(s) promoted",
"processed_suggestions": "{n} suggestion(s)",
"profile_added": "Profile added - awaiting approval",
"profile_name_required": "Profile name is required",
"profile_renamed": "Profile renamed",
"rating_saved": "Quality rating saved",
"recording_discarded": "Recording discarded",
"recording_saved": "Recording saved to profile",
"recording_started": "Recording started",
@@ -1627,51 +1699,40 @@
"relabel_failed": "Relabel failed: {error}",
"reminders_save_failed": "Could not save reminders: {error}",
"reminders_saved": "Service reminders saved",
"resume_no_cycle": "No paused cycle to resume",
"review_saved": "Review saved",
"settings_conflicts": "Fix the highlighted setting conflicts before saving.",
"saved_except_conflicts": "Saved. Fix the highlighted conflicts to save the rest.",
"settings_conflicts": "Fix the highlighted setting conflicts before saving.",
"settings_reverted": "Settings reverted; integration reloading",
"settings_saved": "Settings saved; integration reloading",
"suggestions_applied": "Suggestions applied; integration reloading",
"suggestions_dismissed": "Suggestions dismissed",
"pause_no_cycle": "No active cycle to pause",
"resume_no_cycle": "No paused cycle to resume",
"pg_run_gone": "That run is no longer available.",
"task_cancelled": "Cancelled.",
"processed": "Processed {bits}",
"processed_cycles": "{n} cycles",
"processed_suggestions": "{n} suggestion(s)",
"processed_models": "{n} model(s) promoted",
"store_search_failed": "Search failed: {error}",
"share_device_none_sel": "Select at least one cycle to share",
"store_connect_failed": "Connect failed: {error}",
"store_connected": "Connected to the community store",
"store_device_downloaded": "{p} program(s), {c} recording(s) added",
"store_device_downloaded_phases": "{p} program(s), {c} recording(s), {ph} phase map(s) added",
"store_device_downloaded_settings": "{p} program(s), {c} recording(s), {ph} phase map(s), {s} setting(s) added",
"store_device_shared": "Shared {n} cycle(s) to the community store - pending review.",
"store_device_shared_all_dup": "All {n} cycle(s) were already in the community store.",
"store_device_shared_partial": "Shared {n} cycle(s); {failed} could not be uploaded.",
"store_device_shared_some_dup": "Shared {created} cycle(s); {dup} were already in the store.",
"store_disconnected": "Disconnected from the community store",
"store_download_failed": "Download failed: {error}",
"store_download_nothing": "Nothing new to download - this setup is already on your device.",
"store_error": "Error: {error}",
"store_unavailable": "The community store is not available.",
"store_pick_profile": "Pick a profile to merge into",
"store_name_required": "Enter a profile name",
"store_import_failed": "Import failed: {error}",
"store_imported": "Imported into {profile}",
"store_program_required": "Enter a program name",
"store_name_required": "Enter a profile name",
"store_no_appliance": "Set your appliance brand and model in Settings first.",
"store_pick_profile": "Pick a profile to merge into",
"store_program_required": "Enter a program name",
"store_search_failed": "Search failed: {error}",
"store_share_failed": "Share failed: {error}",
"store_shared": "Shared to the community store - pending review.",
"appliance_added": "Appliance added - awaiting approval",
"thanks_confirming": "Thanks for confirming",
"device_approved": "Approved by the community",
"rating_saved": "Quality rating saved",
"brand_added": "Brand added - awaiting approval",
"profile_added": "Profile added - awaiting approval",
"store_device_shared": "Shared {n} cycle(s) to the community store - pending review.",
"share_device_none_sel": "Select at least one cycle to share",
"store_device_downloaded": "{p} program(s), {c} recording(s) added",
"store_download_failed": "Download failed: {error}",
"store_device_shared_partial": "Shared {n} cycle(s); {failed} could not be uploaded.",
"store_download_nothing": "Nothing new to download - this setup is already on your device.",
"store_device_shared_all_dup": "All {n} cycle(s) were already in the community store.",
"store_device_shared_some_dup": "Shared {created} cycle(s); {dup} were already in the store.",
"store_device_downloaded_phases": "{p} program(s), {c} recording(s), {ph} phase map(s) added",
"store_device_downloaded_settings": "{p} program(s), {c} recording(s), {ph} phase map(s), {s} setting(s) added"
"store_unavailable": "The community store is not available.",
"suggestions_applied": "Suggestions applied; integration reloading",
"suggestions_dismissed": "Suggestions dismissed",
"task_cancelled": "Cancelled.",
"thanks_confirming": "Thanks for confirming"
},
"trend": {
"down": "Trending down",
@@ -28,7 +28,9 @@
"awaiting_n": "A la espera de aprobación · {n} confirmados",
"approved": "Aprobado",
"awaiting": "A la espera de aprobación",
"imported_tip": "Importado de la tienda de la comunidad. Se usa solo para la coincidencia, no cuenta en las estadísticas."
"imported_tip": "Importado de la tienda de la comunidad. Se usa solo para la coincidencia, no cuenta en las estadísticas.",
"not_importable": "n/d aquí",
"exists": "ya existe"
},
"btn": {
"add_device": "+ Agregar dispositivo",
@@ -169,7 +171,14 @@
"download_device": "Descargar esta configuración",
"share_device": "⬆ Compartir este dispositivo",
"share_device_tip": "Comparte este electrodoméstico y sus ciclos de referencia grabados en la tienda de la comunidad para que otros con el mismo aparato puedan adoptarlos",
"share_n": "Compartir {n} ciclo(s)"
"share_n": "Compartir {n} ciclo(s)",
"export_selected": "Exportar (elegir datos)",
"export_all": "Exportar todo rápido",
"import_raw": "Avanzado: reemplazar todo desde JSON",
"download_export": "Descargar exportación",
"analyze_import": "Analizar archivo",
"import_selected": "Importar lo seleccionado",
"back": "Atrás"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Pestaña predeterminada",
"panel_language": "Idioma del panel",
"per_cycle_short": "ciclo",
"pg_anti_wrinkle": "Antiarrugas",
"pg_autofill_tip": "Autocompletar el rango a partir del valor actual ({lo}{hi})",
"pg_best_value": "Mejor valor encontrado",
"pg_current_value": "Valor actual:",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Sin coincidencia",
"pg_alert_ambiguous": "Coincidencia ambigua",
"pg_alert_energy": "Anomalía de energía",
"pg_alert_stress_cap": "Límite de seguridad alcanzado",
"pg_alert_stress_ok": "Terminación en reposo: ciclo detenido",
"pg_alert_stress_warn": "Consumo en reposo por encima del umbral de parada",
"pg_newly_correct": "ahora correcto",
"pg_regressed": "empeoró",
"pg_end_timing_changed": "momento de fin cambiado",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Parámetro",
"pg_objective": "Objetivo",
"pg_steps": "Pasos",
"pg_stress_group": "Prueba de terminación en reposo",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Nivel en reposo (W)",
"pg_stress_idle_w_desc": "Anula el nivel mínimo de reposo detectado automáticamente (dejar en blanco para automático).",
"pg_stress_toggle": "Probar terminación en reposo",
"pg_stress_toggle_desc": "Simula el electrodoméstico manteniéndose en su consumo en reposo tras el final de la grabación muestra si WashData detiene el ciclo y cuándo.",
"pg_sweep_2d": "Mapa de calor 2D",
"pg_sweep_param_y": "2.º parámetro",
"pg_obj_match": "Precisión de coincidencia",
@@ -566,7 +585,31 @@
"task_split": "Dividiendo el ciclo",
"task_trim": "Recortando el ciclo",
"task_merge": "Combinando ciclos",
"task_rebuild": "Reconstruyendo envolventes"
"task_rebuild": "Reconstruyendo envolventes",
"cat_profiles": "Perfiles (programas)",
"cat_real_cycles": "Ciclos (historial de uso)",
"cat_reference_cycles": "Ciclos de referencia (importados)",
"cat_custom_phases": "Fases personalizadas",
"cat_profile_groups": "Grupos de perfiles",
"cat_settings": "Ajustes de detección y reconocimiento",
"cat_matching_config": "Ajuste del reconocimiento",
"cat_ml_models": "Modelos de ML",
"cat_feedback": "Comentarios y etiquetas de revisión",
"cat_suggestions": "Sugerencias",
"cat_maintenance_log": "Registro de mantenimiento",
"cat_history_logs": "Historial y registros de cambios",
"cat_lifetime_stats": "Totales acumulados",
"cycles_short": "ciclos",
"import_mode": "Cómo combinar",
"mode_merge": "Combinar (conservar los míos)",
"mode_replace": "Reemplazar lo seleccionado",
"cycle_destination": "¿Dónde deben ir los ciclos importados?",
"dest_reference": "Referencia (solo forma)",
"dest_real_history": "Historial real (cuenta en estadísticas)",
"conflict_resolution": "Conflictos de nombres",
"conflict_import_copy": "Importar como copia",
"conflict_keep_mine": "Conservar el mío",
"conflict_overwrite": "Sobrescribir"
},
"log": {
"all_levels": "Todos los niveles",
@@ -643,7 +686,9 @@
"force_stop_msg": "¿Forzar la parada del ciclo activo ahora? El ciclo se guardará como interrumpido.",
"store_import": "Importar ciclo de referencia",
"store_share": "Compartir en la tienda de la comunidad",
"store_share_device": "Compartir este dispositivo"
"store_share_device": "Compartir este dispositivo",
"export_select": "Exportar - elegir datos",
"import_wizard": "Importar - elegir datos"
},
"msg": {
"appliance_monitor": "monitor de electrodomésticos",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} ciclos similares sin etiquetar encontrados cree un perfil para comenzar a identificarlos.",
"cycles_deleted": "{count} ciclo(s) eliminado(s)",
"enough_data": "Suficientes datos para aprender ({current}/{min} ciclos).",
"export_description": "Exporte todos los perfiles y ciclos a JSON o restaure desde una exportación anterior.",
"export_description": "Elige exactamente qué perfiles, ciclos, ajustes y más exportar a JSON, o analiza un archivo e importa solo las partes que quieras.",
"feedback_cycles_pending": "{n} ciclo{s} por revisar",
"feedback_prompt": "Confirme que era correcto, corrija el programa o ignórelo.",
"feedback_relabel_hint": "Volver a etiquetar este ciclo también lo resuelve.",
"filter_by_profile": "Filtrar por perfil…",
"group_modal_help": "Programas grupales con la misma forma que difieren en temperatura/centrifugado (las duraciones pueden variar). El emparejamiento califica al grupo como un candidato y luego elige al miembro que mejor se adapta. Elija al menos 2; la superposición muestra cuán parecidos son.",
"group_not_cohesive": "Estos perfiles no son lo suficientemente similares como para agruparlos de manera confiable, por lo que la comparación los trata individualmente hasta que se elimina el valor atípico o se divide el grupo.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Reinicie Home Assistant para habilitar las herramientas de simulación.",
"pg_sim_progress": "{done} / {total} ciclos",
"pg_starting": "Iniciando…",
"pg_stress_synthetic": "la simulación en reposo empieza aquí",
"pg_sweep_intro": "¿Y si {param} fuera diferente? Pruebe {steps} valores en sus últimos {cycles} ciclos para encontrar el ajuste con el que más ciclos se reconocen correctamente.",
"pg_sweep_step": "Paso {done} / {total}",
"pg_undetected": "{n} ciclo{s} no detectado{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulando el ciclo…",
"pg_canvas_empty2": "Elige un ciclo de arriba y pulsa Ejecutar para simularlo. Después, pasa el cursor para leer los valores, desplázate para hacer zoom y arrastra para desplazarte.",
"pg_apply_settings_confirm": "¿Guardar estos {n} ajuste(s) en este dispositivo? {list}",
"pg_stress_terminated_detail": "Se estabilizó en ~{idle}W en reposo -- el ciclo terminó {h}h {m}m después mediante {reason}.",
"pg_stress_above_threshold_detail": "El consumo en reposo ~{idle}W es igual o superior al umbral de parada efectivo ({stop}W) -- el ciclo nunca se consideró inactivo. Aumente stop_threshold_w para corregirlo.",
"pg_stress_hit_cap_detail": "El ciclo duró {h}h {m}m sin detenerse -- detenido a la fuerza por el límite de seguridad. Consumo en reposo {idle}W frente al umbral de parada {stop}W.",
"no_logs_match": "Ningún registro coincide con estos filtros.",
"pg_analysis_hint2": "Seleccione un ciclo y pulse Ejecutar para cargar el análisis de coincidencia.",
"pg_analysis_empty2": "Pulse Ejecutar para ver el análisis de coincidencia.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Sube {brand} {model} con los ciclos de referencia que selecciones. Otros con el mismo electrodoméstico podrán adoptar tus programas. Las entradas se revisan antes de aparecer públicamente.",
"share_profile_no_cycles": "Sin ciclos de referencia -- marca un ciclo como ⭐ en la pestaña Ciclos para incluir este perfil",
"advisory_phase_inconsistent": "'{name}' parece mezclar diferentes programas o temperaturas - sus ciclos calientan durante tiempos muy distintos. Dividirlo en perfiles separados (p. ej. por temperatura) mejorará la coincidencia y las estimaciones de tiempo.",
"advisory_phase_inconsistent_title": "⚠ Posibles programas mezclados"
"advisory_phase_inconsistent_title": "⚠ Posibles programas mezclados",
"export_select_intro": "Marca exactamente qué incluir. Seleccionar perfiles sin sus ciclos igualmente exporta un programa reconocible (su forma aprendida viaja con él).",
"import_analyze_hint": "Carga un archivo exportado (o pega su JSON). WashData lo analiza y muestra exactamente qué se puede importar antes de que nada cambie.",
"analyzing": "Analizando…",
"device_type_mismatch_warn": "Esta exportación es de otro tipo de electrodoméstico ({src} en lugar de {local}). Los programas y ciclos aún pueden importarse como datos de referencia, pero los ajustes específicos del dispositivo y la importación al historial real están deshabilitados.",
"merge_hint": "Los elementos importados se añaden; no se pierde nada local. Los conflictos de nombres se resuelven abajo.",
"replace_warn": "Cada categoría marcada se borra y se reemplaza con el contenido del archivo. Las categorías sin marcar quedan intactas.",
"dest_reference_hint": "Los ciclos importados solo mejoran el reconocimiento de programas y nunca afectan a las estadísticas de uso/energía.",
"dest_real_history_hint": "Los ciclos importados cuentan como el historial propio de este dispositivo y alimentan las estadísticas de energía/uso. Úsalo para mover un electrodoméstico a una instalación nueva."
},
"pg_desc": {
"anti_wrinkle_enabled": "Absorber los pulsos de volteo tras la fase principal en lugar de leerlos como nuevos ciclos",
"anti_wrinkle_exit_power": "La potencia debe caer por debajo de este valor entre pulsos para que el modo antiarrugas siga activo",
"anti_wrinkle_idle_timeout": "Tiempo de silencio permitido entre dos pulsos de volteo antes de que finalice el modo antiarrugas",
"anti_wrinkle_max_duration": "Un pulso más largo que esto finaliza el modo antiarrugas y abre un nuevo ciclo",
"anti_wrinkle_max_power": "Un pulso por encima de esto finaliza el modo antiarrugas y abre un nuevo ciclo",
"completion_min_seconds": "Ejecución más corta que cuenta como ciclo real",
"end_repeat_count": "Lecturas bajas consecutivas antes de terminar",
"interrupted_min_seconds": "Ciclos cortos marcados como interrumpidos",
@@ -985,6 +1048,10 @@
"doc": "La energía debe caer por debajo de esto entre pulsos para que el modo antiarrugas permanezca activo.",
"label": "Umbral de potencia de salida"
},
"anti_wrinkle_idle_timeout": {
"doc": "Cuánto tiempo puede permanecer en silencio la máquina entre dos pulsos de volteo antes de que finalice el modo antiarrugas. Ajústelo por encima del intervalo más largo que su secadora deja entre pulsos; de lo contrario, cada pulso posterior se lee como un falso inicio.",
"label": "Intervalo máximo entre pulsos"
},
"anti_wrinkle_max_duration": {
"doc": "Los pulsos más largos se tratan como un ciclo real en lugar de una caída antiarrugas.",
"label": "Duración máxima"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n} ciclo(s) compartido(s); no se pudo subir {failed}.",
"store_device_shared_some_dup": "{created} ciclo(s) compartido(s); {dup} ya estaba(n) en la tienda.",
"store_download_failed": "Error al descargar: {error}",
"store_download_nothing": "Nada nuevo que descargar - esta configuración ya está en tu dispositivo."
"store_download_nothing": "Nada nuevo que descargar - esta configuración ya está en tu dispositivo.",
"export_selective_done": "Exportación descargada",
"import_selective_done": "Se importaron {profiles} perfil(es) y {cycles} ciclo(s)"
},
"suggestion": {
"both_agree": "WashData recomienda",
@@ -28,7 +28,9 @@
"awaiting_n": "Ootab heakskiitu · {n} kinnitust",
"approved": "Heaks kiidetud",
"awaiting": "Ootab heakskiitu",
"imported_tip": "Imporditud kogukonna poest. Kasutatakse ainult sobitamiseks, ei arvestata statistikas."
"imported_tip": "Imporditud kogukonna poest. Kasutatakse ainult sobitamiseks, ei arvestata statistikas.",
"not_importable": "pole saadaval",
"exists": "on olemas"
},
"btn": {
"add_device": "+ Lisa seade",
@@ -169,7 +171,14 @@
"download_device": "Laadi see seadistus alla",
"share_device": "⬆ Jaga seda seadet",
"share_device_tip": "Jaga seda kodumasinat ja selle salvestatud võrdlustsükleid kogukonna poodi, et teised sama masina omanikud saaksid neid kasutusele võtta",
"share_n": "Jaga {n} tsüklit"
"share_n": "Jaga {n} tsüklit",
"export_selected": "Ekspordi (vali andmed)",
"export_all": "Kiirekspordi kõik",
"import_raw": "Täpsem: asenda kõik JSON-ist",
"download_export": "Laadi eksport alla",
"analyze_import": "Analüüsi faili",
"import_selected": "Impordi valitud",
"back": "Tagasi"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -291,6 +300,7 @@
"poor": "⚠ Kehv profiili kvaliteet"
},
"lbl": {
"pg_anti_wrinkle": "Kortsudevastane",
"actions": "Tegevused",
"activity": "Tegevus",
"administrators": "Administraatorid",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parameeter",
"pg_objective": "Eesmärk",
"pg_steps": "Sammud",
"pg_stress_group": "Tühikäigu lõpetamise test",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Tühikäigu tase (W)",
"pg_stress_idle_w_desc": "Defineeri automaatselt tuvastatud ooterežiimi põhi käsitsi (jäta tühjaks automaatse jaoks).",
"pg_stress_toggle": "Testi tühikäigu lõpetamist",
"pg_stress_toggle_desc": "Simuleerib seadet, mis jääb salvestuse lõpus ooterežiimi tarbimise tasemele - näitab, kas ja millal WashData tsükli peatab.",
"pg_sweep_2d": "2D soojuskaart",
"pg_sweep_param_y": "2. parameeter",
"pg_obj_match": "Vaste täpsus",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Interaktiivne tsükli võimsusgraafik: hõljuta aja/võimsuse lugemiseks, keri suumimiseks, lohista liigutamiseks",
"pg_alert_timeout_end": "Lõpetati aegumisega, mitte ennustuse põhjal",
"pg_alert_indefinite": "Töötaks lõputult",
"pg_alert_stress_cap": "Ohutuspiir saavutatud",
"pg_alert_stress_ok": "Tühikäigu lõpetamine: tsükkel peatatud",
"pg_alert_stress_warn": "Tühikäigu tarbimine ületab peatumisläve",
"task_pg_history": "Testi ajaloos",
"task_pg_sweep": "Optimeeri",
"task_reprocess": "Uuesti töötlemine",
@@ -566,7 +585,31 @@
"task_split": "Tsükli poolitamine",
"task_trim": "Tsükli kärpimine",
"task_merge": "Tsüklite ühendamine",
"task_rebuild": "Võimsusvahemike taasloomine"
"task_rebuild": "Võimsusvahemike taasloomine",
"cat_profiles": "Profiilid (programmid)",
"cat_real_cycles": "Tsüklid (kasutusajalugu)",
"cat_reference_cycles": "Viitetsüklid (imporditud)",
"cat_custom_phases": "Kohandatud faasid",
"cat_profile_groups": "Profiilirühmad",
"cat_settings": "Tuvastus- ja sobitamisseaded",
"cat_matching_config": "Sobitamise häälestus",
"cat_ml_models": "ML-mudelid",
"cat_feedback": "Tagasiside- ja ülevaatussildid",
"cat_suggestions": "Soovitused",
"cat_maintenance_log": "Hoolduslogi",
"cat_history_logs": "Ajaloo- ja muudatuselogid",
"cat_lifetime_stats": "Eluaegsed kogusummad",
"cycles_short": "tsüklit",
"import_mode": "Kuidas ühendada",
"mode_merge": "Ühenda (säilita minu omad)",
"mode_replace": "Asenda valitud",
"cycle_destination": "Kuhu peaksid imporditud tsüklid minema?",
"dest_reference": "Viide (ainult kuju)",
"dest_real_history": "Tegelik ajalugu (arvestatakse statistikasse)",
"conflict_resolution": "Nimekonfliktid",
"conflict_import_copy": "Impordi koopiana",
"conflict_keep_mine": "Säilita minu omad",
"conflict_overwrite": "Kirjuta üle"
},
"log": {
"all_levels": "Kõik tasemed",
@@ -643,7 +686,9 @@
"force_stop_msg": "Kas peatada aktiivne tsükkel kohe jõuga? Tsükkel salvestatakse katkestatuna.",
"store_import": "Impordi võrdlustsükkel",
"store_share": "Jaga kogukonna poodi",
"store_share_device": "Jaga seda seadet"
"store_share_device": "Jaga seda seadet",
"export_select": "Eksport - vali andmed",
"import_wizard": "Import - vali andmed"
},
"msg": {
"appliance_monitor": "Seadmete monitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} sarnast märgistamata tsüklit leitud loo profiil, et neid sobitama hakata.",
"cycles_deleted": "{count} tsüklit kustutatud",
"enough_data": "Piisavalt andmeid õppimiseks ({current}/{min} tsüklit).",
"export_description": "Eksportige kõik profiilid ja tsüklid JSON-i või taastage eelmisest ekspordist.",
"export_description": "Vali täpselt, millised profiilid, tsüklid, seaded ja muu JSON-i eksportida, või analüüsi fail ja impordi ainult soovitud osad.",
"feedback_cycles_pending": "{n} tsükkel{s} ülevaatamiseks",
"feedback_prompt": "Kinnitage, et see oli õige, parandage programmi või ignoreerige.",
"feedback_relabel_hint": "Selle tsükli uuesti sildistamine lahendab ka selle.",
"filter_by_profile": "Filtreeri profiili järgi…",
"group_modal_help": "Rühmitage sama kujuga programme, mis erinevad temperatuuri/tsentrifuugimise poolest (kestus võib erineda). Matching annab rühmale ühe kandidaadi ja valib seejärel kõige sobivama liikme. Valige vähemalt 2; ülekate näitab, kui sarnased need on.",
"group_not_cohesive": "Need profiilid ei ole piisavalt sarnased, et neid usaldusväärselt rühmitada, nii et sobitamine käsitleb neid üksikult, kuni eemaldate kõrvalekalde või jagate rühma.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Simulatsioonitööriistade lubamiseks taaskäivita Home Assistant.",
"pg_sim_progress": "{done} / {total} tsüklit",
"pg_starting": "Alustamine…",
"pg_stress_synthetic": "tühikäigu simulatsioon algab siit",
"pg_sweep_intro": "Mis siis, kui {param} oleks teistsugune? Testi {steps} väärtust oma viimase {cycles} tsükli ulatuses, et leida seade, mille juures kõige rohkem tsükleid sobitatakse õigesti.",
"pg_sweep_step": "Samm {done} / {total}",
"pg_undetected": "{n} tsüklit tuvastamata",
@@ -837,6 +884,9 @@
"pg_simulating": "Tsükli simuleerimine…",
"pg_canvas_empty2": "Vali ülalt tsükkel ja vajuta Käivita, et seda simuleerida. Seejärel hõljuta väärtuste lugemiseks, keri suumimiseks ja lohista liigutamiseks.",
"pg_apply_settings_confirm": "Kas salvestada need {n} sätet sellesse seadmesse? {list}",
"pg_stress_terminated_detail": "Stabiliseerus umbes {idle} W tühikäigule -- tsükkel lõppes {h} h {m} min hiljem põhjusel {reason}.",
"pg_stress_above_threshold_detail": "Tühikäigu tarbimine umbes {idle} W on peatumislävel ({stop} W) või sellest kõrgemal -- tsüklit ei registreeritud kunagi vaikseks. Paranda, tõstes väärtust stop_threshold_w.",
"pg_stress_hit_cap_detail": "Tsükkel töötas {h} h {m} min peatumata -- ohutuspiir peatas selle sunniviisiliselt. Tühikäigu tarbimine {idle} W vs peatumislävi {stop} W.",
"no_logs_match": "Ükski logikirje ei vasta nendele filtritele.",
"pg_analysis_hint2": "Vali tsükkel ja vajuta Käivita, et laadida vaste analüüs.",
"pg_analysis_empty2": "Vajuta Käivita, et näha vaste analüüsi.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Laadige {brand} {model} üles valitud võrdlustsüklitega. Teised sama kodumasina kasutajad saavad teie programme kasutusele võtta. Kirjeid vaadatakse üle enne avalikustamist.",
"share_profile_no_cycles": "Võrdlustsükleid pole -- märkige tsükkel ⭐-ks Tsüklid-vahekaardil, et lisada see profiil",
"advisory_phase_inconsistent": "'{name}' näib segavat erinevaid programme või temperatuure - selle tsüklid kuumutavad väga erineva aja jooksul. Selle jagamine eraldi profiilideks (nt temperatuuri järgi) parandab sobitamist ja ajahinnanguid.",
"advisory_phase_inconsistent_title": "⚠ Võimalik programmide segu"
"advisory_phase_inconsistent_title": "⚠ Võimalik programmide segu",
"export_select_intro": "Märgi täpselt, mida kaasata. Profiilide valimine ilma nende tsükliteta ekspordib siiski tuvastatava programmi (selle õpitud kuju liigub kaasa).",
"import_analyze_hint": "Laadi eksporditud fail (või kleebi selle JSON). WashData analüüsib seda ja näitab täpselt, mida saab importida, enne kui midagi muutub.",
"analyzing": "Analüüsimine…",
"device_type_mismatch_warn": "See eksport on erinevast seadmetüübist ({src} vs {local}). Programme ja tsükleid saab siiski importida viiteandmetena, kuid seadmepõhised seaded ja tegeliku ajaloo import on keelatud.",
"merge_hint": "Imporditud üksused lisatakse; kohalikest andmetest ei lähe midagi kaotsi. Nimekonfliktid lahendatakse allpool.",
"replace_warn": "Iga märgitud kategooria kustutatakse ja asendatakse failist. Märkimata kategooriaid ei puudutata.",
"dest_reference_hint": "Imporditud tsüklid ainult parandavad programmide tuvastamist ega mõjuta kunagi kasutus- ega energiastatistikat.",
"dest_real_history_hint": "Imporditud tsüklid arvestatakse selle seadme enda ajalooks ja need toidavad energia- ja kasutusstatistikat. Kasuta ühe seadme uude paigaldusse teisaldamiseks."
},
"pg_desc": {
"anti_wrinkle_enabled": "Neelab pärast põhifaasi trummelimpulsid, selle asemel et lugeda neid uute tsüklitena",
"anti_wrinkle_exit_power": "Kortsudevastase režiimi aktiivsena püsimiseks peab võimsus impulsside vahel sellest allapoole langema",
"anti_wrinkle_idle_timeout": "Kahe trummelimpulsi vahel lubatud vaikuseaeg enne kortsudevastase režiimi lõppu",
"anti_wrinkle_max_duration": "Sellest pikem impulss lõpetab kortsudevastase režiimi ja avab uue tsükli",
"anti_wrinkle_max_power": "Sellest kõrgem impulss lõpetab kortsudevastase režiimi ja avab uue tsükli",
"completion_min_seconds": "Lühim käivitus, mis loetakse päris tsükliks",
"end_repeat_count": "Madalate näitude arv järjest enne lõpetamist",
"interrupted_min_seconds": "Lühikesed tsüklid märgitakse katkestatuks",
@@ -985,6 +1048,10 @@
"doc": "Kortsudevastase režiimi aktiivseks püsimiseks peab võimsus impulsside vahel langema sellest allapoole.",
"label": "Väljumise Võimsuse Lävi"
},
"anti_wrinkle_idle_timeout": {
"doc": "Kui kaua tohib masin kahe trummelimpulsi vahel vaikida, enne kui kortsudevastane režiim lõpeb. Seadke see kõrgemaks kui pikim paus, mille teie kuivati impulsside vahele jätab, muidu loetakse iga hilisem impulss valestardiks.",
"label": "Maksimaalne Impulsivahe"
},
"anti_wrinkle_max_duration": {
"doc": "Sellest pikemaid kaunvilju käsitletakse pigem tõelise tsüklina kui kortsudevastasena.",
"label": "Maksimaalne Kestus"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n} tsüklit jagatud; {failed} ei saanud üles laadida.",
"store_device_shared_some_dup": "{created} tsüklit jagatud; {dup} olid juba poes.",
"store_download_failed": "Allalaadimine ebaõnnestus: {error}",
"store_download_nothing": "Midagi uut alla laadida pole - see seadistus on juba teie seadmel."
"store_download_nothing": "Midagi uut alla laadida pole - see seadistus on juba teie seadmel.",
"export_selective_done": "Eksport alla laaditud",
"import_selective_done": "Imporditud {profiles} profiili ja {cycles} tsüklit"
},
"suggestion": {
"both_agree": "WashData soovitab",
@@ -28,7 +28,9 @@
"awaiting_n": "Odottaa hyväksyntää · {n} vahvistusta",
"approved": "Hyväksytty",
"awaiting": "Odottaa hyväksyntää",
"imported_tip": "Tuotu yhteisökaupasta. Käytetään vain täsmäytykseen, ei lasketa tilastoihin."
"imported_tip": "Tuotu yhteisökaupasta. Käytetään vain täsmäytykseen, ei lasketa tilastoihin.",
"not_importable": "ei käytettävissä",
"exists": "on jo"
},
"btn": {
"add_device": "+ Lisää laite",
@@ -169,7 +171,14 @@
"download_device": "Lataa tämä asetus",
"share_device": "⬆ Jaa tämä laite",
"share_device_tip": "Jaa tämä laite ja sen tallennetut viitesyklit yhteisökauppaan, jotta muut saman koneen omistajat voivat ottaa ne käyttöön",
"share_n": "Jaa {n} sykli(ä)"
"share_n": "Jaa {n} sykli(ä)",
"export_selected": "Vie (valitse tiedot)",
"export_all": "Pikavienti: vie kaikki",
"import_raw": "Lisäasetukset: korvaa kaikki JSONista",
"download_export": "Lataa vienti",
"analyze_import": "Analysoi tiedosto",
"import_selected": "Tuo valitut",
"back": "Takaisin"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -291,6 +300,7 @@
"poor": "⚠ Heikko profiililaatu"
},
"lbl": {
"pg_anti_wrinkle": "Ryppyjenesto",
"actions": "Toiminnot",
"activity": "Toiminta",
"administrators": "Järjestelmänvalvojat",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parametri",
"pg_objective": "Tavoite",
"pg_steps": "Askeleet",
"pg_stress_group": "Valmiustilan päätöstesti",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Valmiustila (W)",
"pg_stress_idle_w_desc": "Ohita automaattisesti havaittu valmiustilasyöte (jätä tyhjäksi automaattista varten).",
"pg_stress_toggle": "Testaa valmiustilan päätöstä",
"pg_stress_toggle_desc": "Simuloi laitetta, joka pysyy valmiustilan kulutustasollaan tallennuksen jälkeen - näyttää, pysäyttääkö WashData syklin ja milloin.",
"pg_sweep_2d": "2D-lämpökartta",
"pg_sweep_param_y": "2. parametri",
"pg_obj_match": "Osumatarkkuus",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Interaktiivinen jakson tehokaavio: vie osoitin päälle lukeaksesi ajan/tehon, vieritä zoomataksesi, vedä panoroidaksesi",
"pg_alert_timeout_end": "Päättyi aikakatkaisuun, ei ennusteeseen",
"pg_alert_indefinite": "Kävisi loputtomiin",
"pg_alert_stress_cap": "Turvaraja saavutettu",
"pg_alert_stress_ok": "Valmiustilan päätös: sykli pysäytetty",
"pg_alert_stress_warn": "Valmiustilan kulutus ylittää pysäytyskynnyksen",
"task_pg_history": "Testaa historialla",
"task_pg_sweep": "Optimoi",
"task_reprocess": "Uudelleenkäsittely",
@@ -566,7 +585,31 @@
"task_split": "Jaetaan sykliä",
"task_trim": "Rajataan sykliä",
"task_merge": "Yhdistetään syklejä",
"task_rebuild": "Rakennetaan tehoalueita uudelleen"
"task_rebuild": "Rakennetaan tehoalueita uudelleen",
"cat_profiles": "Profiilit (ohjelmat)",
"cat_real_cycles": "Jaksot (käyttöhistoria)",
"cat_reference_cycles": "Viitejaksot (tuodut)",
"cat_custom_phases": "Mukautetut vaiheet",
"cat_profile_groups": "Profiiliryhmät",
"cat_settings": "Havaitsemis- ja tunnistusasetukset",
"cat_matching_config": "Tunnistuksen hienosäätö",
"cat_ml_models": "ML-mallit",
"cat_feedback": "Palaute- ja tarkistusmerkinnät",
"cat_suggestions": "Ehdotukset",
"cat_maintenance_log": "Huoltoloki",
"cat_history_logs": "Historia- ja muutoslokit",
"cat_lifetime_stats": "Elinkaaren kokonaissummat",
"cycles_short": "jaksoa",
"import_mode": "Yhdistämistapa",
"mode_merge": "Yhdistä (säilytä omat)",
"mode_replace": "Korvaa valitut",
"cycle_destination": "Minne tuodut jaksot sijoitetaan?",
"dest_reference": "Viite (vain muoto)",
"dest_real_history": "Todellinen historia (lasketaan tilastoihin)",
"conflict_resolution": "Nimien törmäykset",
"conflict_import_copy": "Tuo kopiona",
"conflict_keep_mine": "Säilytä omat",
"conflict_overwrite": "Korvaa"
},
"log": {
"all_levels": "Kaikki tasot",
@@ -643,7 +686,9 @@
"force_stop_msg": "Pakotetaanko aktiivinen sykli pysähtymään nyt? Sykli tallennetaan keskeytyneenä.",
"store_import": "Tuo viitesykli",
"store_share": "Jaa yhteisökauppaan",
"store_share_device": "Jaa tämä laite"
"store_share_device": "Jaa tämä laite",
"export_select": "Vienti - valitse tiedot",
"import_wizard": "Tuonti - valitse tiedot"
},
"msg": {
"appliance_monitor": "Laitteen näyttö",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} samanlaista merkitsemätöntä sykliä löydetty luo profiili, jotta voit aloittaa niiden täsmäämisen.",
"cycles_deleted": "{count} sykli(ä) poistettu",
"enough_data": "Tarpeeksi tietoja oppimiseen ({current}/{min} sykliä).",
"export_description": "Vie kaikki profiilit ja syklit JSONiin tai palauta edellisestä viennistä.",
"export_description": "Valitse tarkalleen, mitkä profiilit, jaksot, asetukset ja muut viedään JSON-tiedostoon, tai analysoi tiedosto ja tuo vain haluamasi osat.",
"feedback_cycles_pending": "{n} sykliä tarkistettavana",
"feedback_prompt": "Vahvista, että se oli oikein, korjaa ohjelma tai jätä huomiotta.",
"feedback_relabel_hint": "Tämän syklin uudelleenmerkintä ratkaisee sen myös.",
"filter_by_profile": "Suodata profiilin mukaan…",
"group_modal_help": "Ryhmäohjelmat, joilla on sama muoto ja jotka eroavat lämpötilassa/linkouksessa (kesto voi vaihdella). Matching antaa ryhmän yhdeksi ehdokkaaksi ja valitsee sitten parhaiten sopivan jäsenen. Valitse vähintään 2; peittokuva osoittaa, kuinka samanlaisia ne ovat.",
"group_not_cohesive": "Nämä profiilit eivät ole riittävän samankaltaisia ryhmittelemään niitä luotettavasti, joten vastaavuudessa käsitellään niitä yksitellen, kunnes poistat poikkeaman tai jaat ryhmän.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Käynnistä Home Assistant uudelleen ottaaksesi simulointityökalut käyttöön.",
"pg_sim_progress": "{done} / {total} sykliä",
"pg_starting": "Aloitetaan…",
"pg_stress_synthetic": "valmiustilan simulaatio alkaa tästä",
"pg_sweep_intro": "Entä jos {param} olisi erilainen? Testaa {steps} arvoa viimeisten {cycles} syklin perusteella löytääksesi asetuksen, jolla useimmat syklit täsmätään oikein.",
"pg_sweep_step": "Vaihe {done} / {total}",
"pg_undetected": "{n} sykliä tunnistamatta",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuloidaan jaksoa…",
"pg_canvas_empty2": "Valitse jakso yltä ja paina Suorita simuloidaksesi sen. Vie sitten osoitin päälle lukeaksesi arvoja, vieritä zoomataksesi ja vedä panoroidaksesi.",
"pg_apply_settings_confirm": "Tallennetaanko nämä {n} asetusta tähän laitteeseen? {list}",
"pg_stress_terminated_detail": "Asettui noin {idle} W:n valmiustilaan -- sykli päättyi {h} h {m} min myöhemmin syystä {reason}.",
"pg_stress_above_threshold_detail": "Valmiustilan kulutus noin {idle} W on pysäytyskynnyksen ({stop} W) tasolla tai sen yläpuolella -- sykliä ei koskaan rekisteröity hiljaiseksi. Korjaa nostamalla arvoa stop_threshold_w.",
"pg_stress_hit_cap_detail": "Sykli kävi {h} h {m} min pysähtymättä -- turvaraja pakotti sen pysähtymään. Valmiustilan kulutus {idle} W vs. pysäytyskynnys {stop} W.",
"no_logs_match": "Mikään lokitietue ei vastaa näitä suodattimia.",
"pg_analysis_hint2": "Valitse sykli ja paina Aja-painiketta ladataksesi osuma-analyysin.",
"pg_analysis_empty2": "Paina Aja-painiketta nähdäksesi osuma-analyysin.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Lataa {brand} {model} valitsemiesi viitesyklien kanssa. Muut saman laitteen omistajat voivat ottaa ohjelmiasi käyttöön. Merkinnät tarkistetaan ennen julkaisua.",
"share_profile_no_cycles": "Ei viitesyklejä -- merkitse sykli ⭐:ksi Syklit-välilehdessä sisällyttääksesi tämän profiilin",
"advisory_phase_inconsistent": "'{name}' vaikuttaa sekoittavan eri ohjelmia tai lämpötiloja - sen syklit lämmittävät hyvin eripituisia aikoja. Sen jakaminen erillisiin profiileihin (esim. lämpötilan mukaan) parantaa täsmäytystä ja aika-arvioita.",
"advisory_phase_inconsistent_title": "⚠ Mahdollisesti sekoittuneet ohjelmat"
"advisory_phase_inconsistent_title": "⚠ Mahdollisesti sekoittuneet ohjelmat",
"export_select_intro": "Rastita tarkalleen, mitä sisällytetään. Kun valitset profiilit ilman niiden jaksoja, viedään silti tunnistettava ohjelma (sen opittu muoto kulkee mukana).",
"import_analyze_hint": "Lataa viety tiedosto (tai liitä sen JSON). WashData analysoi sen ja näyttää tarkalleen, mitä voidaan tuoda, ennen kuin mitään muuttuu.",
"analyzing": "Analysoidaan…",
"device_type_mismatch_warn": "Tämä vienti on eri laitetyypistä ({src} vs {local}). Ohjelmat ja jaksot voidaan silti tuoda viitedataksi, mutta laitekohtaiset asetukset ja todellisen historian tuonti on poistettu käytöstä.",
"merge_hint": "Tuodut kohteet lisätään; mitään paikallista ei menetetä. Nimien törmäykset ratkaistaan alla.",
"replace_warn": "Jokainen rastitettu luokka tyhjennetään ja korvataan tiedostosta. Rastittamattomia luokkia ei kosketa.",
"dest_reference_hint": "Tuodut jaksot vain parantavat ohjelmien tunnistusta eivätkä koskaan vaikuta käyttö- tai energiatilastoihin.",
"dest_real_history_hint": "Tuodut jaksot lasketaan tämän laitteen omaksi historiaksi ja syöttävät energia- ja käyttötilastoja. Käytä tätä, kun siirrät yhden laitteen uuteen asennukseen."
},
"pg_desc": {
"anti_wrinkle_enabled": "Sulauta päävaiheen jälkeiset rumpupulssit sen sijaan, että lukisit ne uusina sykleinä",
"anti_wrinkle_exit_power": "Tehon on laskettava tämän alle pulssien välillä, jotta ryppyjenesto pysyy aktiivisena",
"anti_wrinkle_idle_timeout": "Kahden rumpupulssin välillä sallittu hiljainen aika ennen kuin ryppyjenesto päättyy",
"anti_wrinkle_max_duration": "Tätä pidempi pulssi päättää ryppyjeneston ja aloittaa uuden syklin",
"anti_wrinkle_max_power": "Tätä suurempi pulssi päättää ryppyjeneston ja aloittaa uuden syklin",
"completion_min_seconds": "Lyhin ajo, joka lasketaan oikeaksi sykliksi",
"end_repeat_count": "Peräkkäisten matalien lukemien määrä ennen päättymistä",
"interrupted_min_seconds": "Lyhyet syklit merkitään keskeytyneiksi",
@@ -985,6 +1048,10 @@
"doc": "Tehon on oltava tämän alapuolella pulssien välillä, jotta ryppyjä estävä tila pysyy aktiivisena.",
"label": "Poistumistehokynnys"
},
"anti_wrinkle_idle_timeout": {
"doc": "Kuinka kauan kone saa pysyä hiljaa kahden rumpupulssin välillä ennen kuin ryppyjenesto-tila päättyy. Aseta se pidemmäksi kuin pisin väli, jonka kuivausrumpu jättää pulssien välille, muuten jokainen myöhempi pulssi luetaan vääräksi käynnistykseksi.",
"label": "Pulssien enimmäisväli"
},
"anti_wrinkle_max_duration": {
"doc": "Tätä pidempiä pulsseja käsitellään pikemminkin todellisena syklinä kuin ryppyjä ehkäisevänä ryppynä.",
"label": "Enimmäiskesto"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} sykliä jaettu; {failed} ei voitu ladata.",
"store_device_shared_some_dup": "{created} sykliä jaettu; {dup} olivat jo kaupassa.",
"store_download_failed": "Lataus epäonnistui: {error}",
"store_download_nothing": "Ei uutta ladattavaa - tämä asetus on jo laitteellasi."
"store_download_nothing": "Ei uutta ladattavaa - tämä asetus on jo laitteellasi.",
"export_selective_done": "Vienti ladattu",
"import_selective_done": "Tuotu {profiles} profiilia ja {cycles} jaksoa"
},
"trend": {
"down": "Trendikäs alaspäin",
@@ -28,7 +28,9 @@
"awaiting_n": "En attente d'approbation · {n} confirmés",
"approved": "Approuvé",
"awaiting": "En attente d'approbation",
"imported_tip": "Importé depuis la boutique communautaire. Utilisé uniquement pour la correspondance, non compté dans les statistiques."
"imported_tip": "Importé depuis la boutique communautaire. Utilisé uniquement pour la correspondance, non compté dans les statistiques.",
"not_importable": "n/a ici",
"exists": "existe déjà"
},
"btn": {
"add_device": "+ Ajouter un appareil",
@@ -169,7 +171,14 @@
"download_device": "Télécharger cette configuration",
"share_device": "⬆ Partager cet appareil",
"share_device_tip": "Partager cet appareil et ses cycles de référence enregistrés sur la boutique communautaire pour que d'autres utilisateurs du même modèle puissent les adopter",
"share_n": "Partager {n} cycle(s)"
"share_n": "Partager {n} cycle(s)",
"export_selected": "Exporter (choisir les données)",
"export_all": "Exporter tout rapidement",
"import_raw": "Avancé : tout remplacer depuis un JSON",
"download_export": "Télécharger l'export",
"analyze_import": "Analyser le fichier",
"import_selected": "Importer la sélection",
"back": "Retour"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Onglet par défaut",
"panel_language": "Langue du panneau",
"per_cycle_short": "cycle",
"pg_anti_wrinkle": "Anti-froissement",
"pg_autofill_tip": "Remplir automatiquement la plage à partir de la valeur actuelle ({lo}{hi})",
"pg_best_value": "Meilleure valeur trouvée",
"pg_current_value": "Valeur actuelle :",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Sans correspondance",
"pg_alert_ambiguous": "Correspondance ambiguë",
"pg_alert_energy": "Anomalie d'énergie",
"pg_alert_stress_cap": "Limite de sécurité atteinte",
"pg_alert_stress_ok": "Terminaison au repos : cycle arrêté",
"pg_alert_stress_warn": "Consommation en veille supérieure au seuil d'arrêt",
"pg_newly_correct": "désormais correct",
"pg_regressed": "régressé",
"pg_end_timing_changed": "moment de fin modifié",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Paramètre",
"pg_objective": "Objectif",
"pg_steps": "Étapes",
"pg_stress_group": "Test de terminaison au repos",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Niveau de veille (W)",
"pg_stress_idle_w_desc": "Remplace le plancher de veille détecté automatiquement (laisser vide pour auto).",
"pg_stress_toggle": "Tester la terminaison au repos",
"pg_stress_toggle_desc": "Simule l'appareil maintenu à sa consommation en veille après la fin de l'enregistrement indique si et quand WashData arrête le cycle.",
"pg_sweep_2d": "Carte thermique 2D",
"pg_sweep_param_y": "2e paramètre",
"pg_obj_match": "Précision de correspondance",
@@ -566,7 +585,31 @@
"task_split": "Division du cycle",
"task_trim": "Rognage du cycle",
"task_merge": "Fusion des cycles",
"task_rebuild": "Reconstruction des enveloppes"
"task_rebuild": "Reconstruction des enveloppes",
"cat_profiles": "Profils (programmes)",
"cat_real_cycles": "Cycles (historique d'utilisation)",
"cat_reference_cycles": "Cycles de référence (importés)",
"cat_custom_phases": "Phases personnalisées",
"cat_profile_groups": "Groupes de profils",
"cat_settings": "Réglages de détection & de reconnaissance",
"cat_matching_config": "Réglage de la reconnaissance",
"cat_ml_models": "Modèles ML",
"cat_feedback": "Retours & étiquettes de révision",
"cat_suggestions": "Suggestions",
"cat_maintenance_log": "Journal de maintenance",
"cat_history_logs": "Historique & journaux de modifications",
"cat_lifetime_stats": "Totaux cumulés",
"cycles_short": "cycles",
"import_mode": "Comment combiner",
"mode_merge": "Fusionner (garder les miens)",
"mode_replace": "Remplacer la sélection",
"cycle_destination": "Où placer les cycles importés ?",
"dest_reference": "Référence (forme uniquement)",
"dest_real_history": "Historique réel (compte dans les stats)",
"conflict_resolution": "Conflits de noms",
"conflict_import_copy": "Importer comme copie",
"conflict_keep_mine": "Garder le mien",
"conflict_overwrite": "Écraser"
},
"log": {
"all_levels": "Tous niveaux",
@@ -643,7 +686,9 @@
"force_stop_msg": "Forcer l'arrêt du cycle actif maintenant ? Le cycle sera enregistré comme interrompu.",
"store_import": "Importer un cycle de référence",
"store_share": "Partager sur la boutique communautaire",
"store_share_device": "Partager cet appareil"
"store_share_device": "Partager cet appareil",
"export_select": "Exporter - choisir les données",
"import_wizard": "Importer - choisir les données"
},
"msg": {
"appliance_monitor": "Moniteur d'appareils",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} cycles similaires non étiquetés trouvés créez un profil pour commencer à les identifier.",
"cycles_deleted": "{count} cycle(s) supprimé(s)",
"enough_data": "Suffisamment de données pour apprendre ({current}/{min} cycles).",
"export_description": "Exportez tous les profils et cycles vers JSON, ou restaurez à partir d'une exportation précédente.",
"export_description": "Choisissez précisément quels profils, cycles, réglages et autres exporter vers un JSON, ou analysez un fichier pour n'importer que les éléments souhaités.",
"feedback_cycles_pending": "{n} cycle{s} à examiner",
"feedback_prompt": "Confirmez que c'était correct, corrigez le programme ou ignorez-le.",
"feedback_relabel_hint": "Réétiqueter ce cycle le résout également.",
"filter_by_profile": "Filtrer par profil…",
"group_modal_help": "Programmes groupés de même forme qui diffèrent en température/essorage (les durées peuvent varier). L'appariement note le groupe comme un seul candidat, puis sélectionne le membre le mieux adapté. Choisissez-en au moins 2 ; la superposition montre à quel point ils se ressemblent.",
"group_not_cohesive": "Ces profils ne sont pas suffisamment similaires pour être regroupés de manière fiable. La correspondance les traite donc individuellement jusqu'à ce que vous supprimiez la valeur aberrante ou divisiez le groupe.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Redémarrez Home Assistant pour activer les outils de simulation.",
"pg_sim_progress": "{done} / {total} cycles",
"pg_starting": "Démarrage…",
"pg_stress_synthetic": "la simulation de repos commence ici",
"pg_sweep_intro": "Et si {param} était différent ? Testez {steps} valeurs sur vos {cycles} derniers cycles pour trouver le réglage où le plus de cycles sont correctement reconnus.",
"pg_sweep_step": "Étape {done} / {total}",
"pg_undetected": "{n} cycle{s} non détecté{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulation du cycle…",
"pg_canvas_empty2": "Choisissez un cycle ci-dessus et appuyez sur Exécuter pour le simuler. Survolez ensuite pour lire les valeurs, faites défiler pour zoomer et faites glisser pour vous déplacer.",
"pg_apply_settings_confirm": "Enregistrer ces {n} réglage(s) sur cet appareil ? {list}",
"pg_stress_terminated_detail": "Stabilisé à ~{idle}W en veille -- le cycle s'est terminé {h}h {m}m plus tard via {reason}.",
"pg_stress_above_threshold_detail": "La consommation en veille ~{idle}W est égale ou supérieure au seuil d'arrêt effectif ({stop}W) -- le cycle n'a jamais été considéré comme inactif. Augmentez stop_threshold_w pour corriger.",
"pg_stress_hit_cap_detail": "Le cycle a duré {h}h {m}m sans s'arrêter -- arrêté de force par la limite de sécurité. Consommation en veille {idle}W contre un seuil d'arrêt de {stop}W.",
"no_logs_match": "Aucun enregistrement de journal ne correspond à ces filtres.",
"pg_analysis_hint2": "Sélectionnez un cycle et cliquez sur Exécuter pour charger l'analyse de correspondance.",
"pg_analysis_empty2": "Cliquez sur Exécuter pour voir l'analyse de correspondance.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Envoyez {brand} {model} avec les cycles de référence que vous sélectionnez. D'autres utilisateurs du même appareil peuvent adopter vos programmes. Les entrées sont examinées avant d'apparaître publiquement.",
"share_profile_no_cycles": "Aucun cycle de référence -- marquez un cycle comme ⭐ dans l'onglet Cycles pour inclure ce profil",
"advisory_phase_inconsistent": "'{name}' semble mélanger différents programmes ou températures - ses cycles chauffent pendant des durées très variables. Le diviser en profils distincts (p. ex. par température) améliorera la reconnaissance et les estimations de temps.",
"advisory_phase_inconsistent_title": "⚠ Programmes peut-être mélangés"
"advisory_phase_inconsistent_title": "⚠ Programmes peut-être mélangés",
"export_select_intro": "Cochez exactement ce qu'il faut inclure. Sélectionner des profils sans leurs cycles exporte tout de même un programme reconnaissable (sa forme apprise l'accompagne).",
"import_analyze_hint": "Chargez un fichier exporté (ou collez son JSON). WashData l'analyse et montre exactement ce qui peut être importé avant tout changement.",
"analyzing": "Analyse en cours…",
"device_type_mismatch_warn": "Cet export provient d'un autre type d'appareil ({src} au lieu de {local}). Les programmes et cycles peuvent tout de même être importés comme données de référence, mais les réglages spécifiques à l'appareil et l'import dans l'historique réel sont désactivés.",
"merge_hint": "Les éléments importés sont ajoutés ; rien de local n'est perdu. Les conflits de noms sont résolus ci-dessous.",
"replace_warn": "Chaque catégorie cochée est effacée et remplacée par le contenu du fichier. Les catégories non cochées restent intactes.",
"dest_reference_hint": "Les cycles importés améliorent uniquement la reconnaissance des programmes et n'affectent jamais les statistiques d'utilisation/d'énergie.",
"dest_real_history_hint": "Les cycles importés comptent comme l'historique propre de cet appareil et alimentent les statistiques d'énergie/d'utilisation. À utiliser pour déplacer un appareil vers une nouvelle installation."
},
"pg_desc": {
"anti_wrinkle_enabled": "Absorber les impulsions de culbutage après la phase principale au lieu de les lire comme de nouveaux cycles",
"anti_wrinkle_exit_power": "La puissance doit tomber sous ce seuil entre les impulsions pour que l'anti-froissement reste actif",
"anti_wrinkle_idle_timeout": "Temps de silence autorisé entre deux impulsions de culbutage avant la fin de l'anti-froissement",
"anti_wrinkle_max_duration": "Une impulsion plus longue met fin à l'anti-froissement et ouvre un nouveau cycle",
"anti_wrinkle_max_power": "Une impulsion au-dessus de ce seuil met fin à l'anti-froissement et ouvre un nouveau cycle",
"completion_min_seconds": "Plus courte exécution comptant comme un vrai cycle",
"end_repeat_count": "Lectures basses consécutives avant la fin",
"interrupted_min_seconds": "Cycles courts signalés comme interrompus",
@@ -985,6 +1048,10 @@
"doc": "La puissance doit tomber en dessous entre les impulsions pour que le mode antirides reste actif.",
"label": "Seuil de puissance de sortie"
},
"anti_wrinkle_idle_timeout": {
"doc": "Durée pendant laquelle l'appareil peut rester silencieux entre deux impulsions de culbutage avant la fin du mode anti-froissement. Réglez-la au-dessus de l'intervalle le plus long que votre sèche-linge laisse entre les impulsions, sinon chaque impulsion ultérieure est lue comme un faux départ.",
"label": "Intervalle maximal entre impulsions"
},
"anti_wrinkle_max_duration": {
"doc": "Les impulsions plus longues sont traitées comme un véritable cycle plutôt que comme une chute antirides.",
"label": "Durée maximale"
@@ -1604,7 +1671,9 @@
"store_device_shared_partial": "{n} cycle(s) partagé(s) ; {failed} n'ont pas pu être envoyés.",
"store_device_shared_some_dup": "{created} cycle(s) partagé(s) ; {dup} étaient déjà dans la boutique.",
"store_download_failed": "Échec du téléchargement : {error}",
"store_download_nothing": "Rien de nouveau à télécharger -- cette configuration est déjà sur votre appareil."
"store_download_nothing": "Rien de nouveau à télécharger -- cette configuration est déjà sur votre appareil.",
"export_selective_done": "Export téléchargé",
"import_selective_done": "{profiles} profil(s) et {cycles} cycle(s) importés"
},
"suggestion": {
"both_agree": "WashData recommande",
@@ -28,7 +28,9 @@
"awaiting_n": "Čeka odobrenje · potvrđeno {n}",
"approved": "Odobreno",
"awaiting": "Čeka odobrenje",
"imported_tip": "Uvezeno iz zajedničke trgovine. Koristi se samo za podudaranje, ne broji se u statistiku."
"imported_tip": "Uvezeno iz zajedničke trgovine. Koristi se samo za podudaranje, ne broji se u statistiku.",
"not_importable": "nije dostupno",
"exists": "postoji"
},
"btn": {
"add_device": "+ Dodaj uređaj",
@@ -169,7 +171,14 @@
"download_device": "Preuzmi postavke uređaja",
"share_device": "Podijeli postavke uređaja",
"share_device_tip": "Podijelite programe i postavke ovog uređaja sa zajednicom",
"share_n": "Podijeli {n} program{s}"
"share_n": "Podijeli {n} program{s}",
"export_selected": "Izvoz (odaberi podatke)",
"export_all": "Brzi izvoz svega",
"import_raw": "Napredno: zamijeni sve iz JSON datoteke",
"download_export": "Preuzmi izvoz",
"analyze_import": "Analiziraj datoteku",
"import_selected": "Uvezi odabrano",
"back": "Natrag"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Pronađena najbolja vrijednost",
"pg_current_value": "Trenutna vrijednost:",
"pg_drag_hint": "Povucite liniju na grafikonu",
"pg_anti_wrinkle": "Anti-bora",
"pg_dtw": "DTW",
"pg_ev_ending": "Završava",
"pg_ev_running": "U tijeku",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktivni grafikon snage ciklusa: prijeđite mišem za prikaz vremena/snage, pomičite kotačić za zumiranje, povucite za pomicanje",
"pg_alert_timeout_end": "Završeno zbog isteka vremena, ne na temelju predviđanja",
"pg_alert_indefinite": "Radio bi beskonačno",
"pg_alert_stress_cap": "Dostignut sigurnosni limit",
"pg_alert_stress_ok": "Detekcija mirovanja: ciklus zaustavljen",
"pg_alert_stress_warn": "Potrošnja u mirovanju iznad praga zaustavljanja",
"pg_stress_group": "Test detekcije mirovanja",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Razina mirovanja (W)",
"pg_stress_idle_w_desc": "Prepiši automatski otkrivenu donju granicu u stanju pripravnosti (ostavi prazno za automatsko otkrivanje).",
"pg_stress_toggle": "Testiraj detekciju mirovanja",
"pg_stress_toggle_desc": "Simulira da uređaj ostaje na potrošnji u stanju pripravnosti nakon završetka snimanja -- prikazuje ako i kada WashData zaustavi ciklus.",
"task_pg_history": "Test na povijesti",
"task_pg_sweep": "Optimiziraj",
"task_reprocess": "Ponovna obrada",
@@ -566,7 +585,31 @@
"task_split": "Dijeljenje ciklusa",
"task_trim": "Obrezivanje ciklusa",
"task_merge": "Spajanje ciklusa",
"task_rebuild": "Ponovna izgradnja ovojnica"
"task_rebuild": "Ponovna izgradnja ovojnica",
"cat_profiles": "Profili (programi)",
"cat_real_cycles": "Ciklusi (povijest rada)",
"cat_reference_cycles": "Referentni ciklusi (uvezeni)",
"cat_custom_phases": "Prilagođene faze",
"cat_profile_groups": "Grupe profila",
"cat_settings": "Postavke otkrivanja i prepoznavanja",
"cat_matching_config": "Ugađanje prepoznavanja",
"cat_ml_models": "ML modeli",
"cat_feedback": "Oznake povratnih informacija i pregleda",
"cat_suggestions": "Prijedlozi",
"cat_maintenance_log": "Dnevnik održavanja",
"cat_history_logs": "Dnevnici povijesti i promjena",
"cat_lifetime_stats": "Ukupni zbrojevi",
"cycles_short": "ciklusa",
"import_mode": "Kako kombinirati",
"mode_merge": "Spoji (zadrži moje)",
"mode_replace": "Zamijeni odabrano",
"cycle_destination": "Kamo trebaju ići uvezeni ciklusi?",
"dest_reference": "Referenca (samo oblik)",
"dest_real_history": "Stvarna povijest (računa se u statistici)",
"conflict_resolution": "Sukobi naziva",
"conflict_import_copy": "Uvezi kao kopiju",
"conflict_keep_mine": "Zadrži moje",
"conflict_overwrite": "Prepiši"
},
"log": {
"all_levels": "Sve razine",
@@ -643,7 +686,9 @@
"force_stop_msg": "Prisilno zaustaviti aktivni ciklus sada? Ciklus će biti spremljen kao prekinut.",
"store_import": "Uvezi referentni ciklus",
"store_share": "Podijeli u zajedničku trgovinu",
"store_share_device": "Podijeli postavke uređaja"
"store_share_device": "Podijeli postavke uređaja",
"export_select": "Izvoz - odaberi podatke",
"import_wizard": "Uvoz - odaberi podatke"
},
"msg": {
"appliance_monitor": "Monitor uređaja",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Pronađeno {count} sličnih neoznačenih ciklusa izradite profil da biste ih počeli podudarati.",
"cycles_deleted": "{count} ciklus(a) izbrisano",
"enough_data": "Dovoljno podataka za učenje ({current}/{min} ciklusa).",
"export_description": "Izvezite sve profile i cikluse u JSON ili vratite iz prethodnog izvoza.",
"export_description": "Odaberite točno koje profile, cikluse, postavke i ostalo želite izvesti u JSON datoteku ili analizirajte datoteku i uvezite samo željene dijelove.",
"feedback_cycles_pending": "{n} ciklus{s} za pregled",
"feedback_prompt": "Potvrdite da je ispravno, ispravite program ili zanemarite.",
"feedback_relabel_hint": "Ponovno označavanje ovog ciklusa također ga rješava.",
"filter_by_profile": "Filtriraj po profilu…",
"group_modal_help": "Grupirajte programe istog oblika koji se razlikuju po temperaturi/centrifugi (trajanje može varirati). Spajanje ocjenjuje grupu kao jednog kandidata, a zatim odabire člana koji najbolje odgovara. Odaberite barem 2; sloj pokazuje koliko su slični.",
"group_not_cohesive": "Ovi profili nisu dovoljno slični da bi se pouzdano grupirali, pa ih uparivanje tretira pojedinačno dok ne uklonite izuzetak ili ne podijelite grupu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Ponovno pokrenite Home Assistant kako biste omogućili alate za simulaciju.",
"pg_sim_progress": "{done} / {total} ciklusa",
"pg_starting": "Pokretanje…",
"pg_stress_synthetic": "simulacija mirovanja počinje ovdje",
"pg_sweep_intro": "Što ako bi {param} bio drugačiji? Testirajte {steps} vrijednosti na svojih zadnjih {cycles} ciklusa kako biste pronašli postavku pri kojoj se najviše ciklusa ispravno podudara.",
"pg_sweep_step": "Korak {done} / {total}",
"pg_undetected": "{n} neotkrivenih ciklusa",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuliranje ciklusa…",
"pg_canvas_empty2": "Odaberite ciklus iznad i pritisnite Pokreni da ga simulirate. Zatim prijeđite mišem za prikaz vrijednosti, pomičite kotačić za zumiranje i povucite za pomicanje.",
"pg_apply_settings_confirm": "Spremiti ovih {n} postavki na ovaj uređaj? {list}",
"pg_stress_terminated_detail": "Ustalilo se na ~{idle}W mirovanja -- ciklus je završio {h} h {m} min kasnije putem {reason}.",
"pg_stress_above_threshold_detail": "Potrošnja u mirovanju ~{idle}W jednaka je ili viša od stvarnog praga zaustavljanja ({stop}W) -- ciklus nikad nije zabilježen kao miran. Za ispravak povećajte stop_threshold_w.",
"pg_stress_hit_cap_detail": "Ciklus je radio {h} h {m} min bez zaustavljanja -- prisilno zaustavljen sigurnosnim limitom. Potrošnja u mirovanju {idle}W u odnosu na prag zaustavljanja {stop}W.",
"no_logs_match": "Nijedan zapis dnevnika ne odgovara ovim filtrima.",
"pg_analysis_hint2": "Odaberite ciklus i pritisnite Pokreni za učitavanje analize podudaranja.",
"pg_analysis_empty2": "Pritisnite Pokreni za prikaz analize podudaranja.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Podijelite programe svog uređaja (profile + referentne cikluse) sa zajednicom. Postavke su opcionalne.",
"share_profile_no_cycles": "Profil '{p}' nema ⭐ referentnih ciklusa -- bit će preskočen osim ako nemate {n}+ potvrđenih pokretanja",
"advisory_phase_inconsistent": "Čini se da '{name}' miješa različite programe ili temperature - njegovi se ciklusi griju vrlo različito dugo. Podjela na zasebne profile (npr. po temperaturi) poboljšat će podudaranje i procjene vremena.",
"advisory_phase_inconsistent_title": "⚠ Možda pomiješani programi"
"advisory_phase_inconsistent_title": "⚠ Možda pomiješani programi",
"export_select_intro": "Označite točno što treba uključiti. Odabir profila bez njihovih ciklusa i dalje izvozi prepoznatljiv program (njegov naučeni oblik prenosi se s njim).",
"import_analyze_hint": "Učitajte izvezenu datoteku (ili zalijepite njezin JSON). WashData je analizira i točno prikazuje što se može uvesti prije nego što se išta promijeni.",
"analyzing": "Analiziranje…",
"device_type_mismatch_warn": "Ovaj je izvoz s druge vrste uređaja ({src} vs {local}). Programe i cikluse i dalje je moguće uvesti kao referentne podatke, ali postavke specifične za uređaj i uvoz stvarne povijesti onemogućeni su.",
"merge_hint": "Uvezene stavke se dodaju; ništa lokalno se ne gubi. Sukobi naziva rješavaju se u nastavku.",
"replace_warn": "Svaka označena kategorija briše se i zamjenjuje podacima iz datoteke. Neoznačene kategorije ostaju nepromijenjene.",
"dest_reference_hint": "Uvezeni ciklusi samo poboljšavaju prepoznavanje programa i nikada ne utječu na statistiku potrošnje/energije.",
"dest_real_history_hint": "Uvezeni ciklusi računaju se kao vlastita povijest ovog uređaja i pridonose statistici energije/potrošnje. Koristite pri premještanju jednog uređaja na novu instalaciju."
},
"phase_desc": {
"anti_crease": "Povremena kratka prevrtanja nakon završetka kako bi se smanjile bore.",
@@ -962,6 +1020,10 @@
"doc": "Snaga mora pasti ispod ove između impulsa kako bi način protiv bora ostao aktivan.",
"label": "Prag snage za izlaz"
},
"anti_wrinkle_idle_timeout": {
"doc": "Koliko dugo stroj smije ostati miran između dva impulsa bubnja prije nego što se način anti-bora završi. Postavite ga iznad najdužeg razmaka koji vaša sušilica ostavlja između impulsa, inače se svaki sljedeći impuls čita kao lažni početak.",
"label": "Maks. razmak između impulsa"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsevi dulji od ovoga tretiraju se kao pravi ciklus, a ne kao sredstvo protiv bora.",
"label": "Maks. trajanje"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Djelomično dijeljenje: {n} program{s} učitan, {failed} preskočeno",
"store_device_shared_some_dup": "Postavke uređaja podijeljene: {n} program{s} učitan ({dup} već postoji)",
"store_download_failed": "Preuzimanje nije uspjelo: {error}",
"store_download_nothing": "Nema ništa za preuzimanje -- svi profili već postoje na ovom uređaju"
"store_download_nothing": "Nema ništa za preuzimanje -- svi profili već postoje na ovom uređaju",
"export_selective_done": "Izvoz preuzet",
"import_selective_done": "Uvezeno {profiles} profila i {cycles} ciklusa"
},
"trend": {
"down": "Trend pada",
@@ -1706,6 +1770,11 @@
"other": "Ostalo"
},
"pg_desc": {
"anti_wrinkle_enabled": "Upij impulse bubnja nakon glavne faze, a ne čitaj ih kao nove cikluse",
"anti_wrinkle_exit_power": "Snaga mora pasti ispod ove vrijednosti između impulsa da bi anti-bora ostala aktivna",
"anti_wrinkle_idle_timeout": "Dopušteno vrijeme mirovanja između dva impulsa bubnja prije nego što anti-bora završi",
"anti_wrinkle_max_duration": "Duži impuls završava anti-boru i otvara novi ciklus",
"anti_wrinkle_max_power": "Impuls iznad ove vrijednosti završava anti-boru i otvara novi ciklus",
"completion_min_seconds": "Najkraći rad koji se broji kao pravi ciklus",
"end_repeat_count": "Uzastopna niska očitanja prije završetka",
"interrupted_min_seconds": "Kratki ciklusi označeni kao prekinuti",
@@ -28,7 +28,9 @@
"awaiting_n": "Jóváhagyásra vár · {n} megerősítés",
"approved": "Jóváhagyva",
"awaiting": "Jóváhagyásra vár",
"imported_tip": "A közösségi boltból importálva. Csak egyeztetéshez használatos, a statisztikába nem számít bele."
"imported_tip": "A közösségi boltból importálva. Csak egyeztetéshez használatos, a statisztikába nem számít bele.",
"not_importable": "itt nem elérhető",
"exists": "létezik"
},
"btn": {
"add_device": "+ Eszköz hozzáadása",
@@ -169,7 +171,14 @@
"download_device": "Beállítás letöltése",
"share_device": "⬆ Eszköz megosztása",
"share_device_tip": "Ossza meg ezt a készüléket és a rögzített referenciaciklusait a közösségi boltban, hogy mások ugyanazzal a géppel magukévá tehessék",
"share_n": "{n} ciklus megosztása"
"share_n": "{n} ciklus megosztása",
"export_selected": "Exportálás (adatok kiválasztása)",
"export_all": "Gyorsexport: minden",
"import_raw": "Speciális: mindent lecserél a JSON-ból",
"download_export": "Export letöltése",
"analyze_import": "Fájl elemzése",
"import_selected": "Kiválasztottak importálása",
"back": "Vissza"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -291,6 +300,7 @@
"poor": "⚠ Gyenge profilminőség"
},
"lbl": {
"pg_anti_wrinkle": "Ránctalanítás",
"actions": "Akciók",
"activity": "Tevékenység",
"administrators": "Rendszergazdák",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Paraméter",
"pg_objective": "Cél",
"pg_steps": "Lépések",
"pg_stress_group": "Üresjárati leállás tesztje",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Üresjárati szint (W)",
"pg_stress_idle_w_desc": "Felülbírálja az automatikusan észlelt készenléti teljesítmény alsó értékét (hagyja üresen az automatikus beállításhoz).",
"pg_stress_toggle": "Üresjárati leállás tesztelése",
"pg_stress_toggle_desc": "Szimulálja, hogy a készülék a felvétel befejezése után a készenléti fogyasztási szintjén marad - megmutatja, hogy és mikor állítja le a WashData a ciklust.",
"pg_sweep_2d": "2D hőtérkép",
"pg_sweep_param_y": "2. paraméter",
"pg_obj_match": "Egyezés pontossága",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Interaktív ciklusteljesítmény-grafikon: vigye rá a mutatót az idő/teljesítmény leolvasásához, görgessen a nagyításhoz, húzza az eltoláshoz",
"pg_alert_timeout_end": "Időtúllépés miatt ért véget, nem előrejelzés alapján",
"pg_alert_indefinite": "Végtelen ideig futna",
"pg_alert_stress_cap": "Elérte a biztonsági korlátot",
"pg_alert_stress_ok": "Üresjárati leállás: a ciklus megállt",
"pg_alert_stress_warn": "Üresjárati fogyasztás meghaladja a leállási küszöböt",
"task_pg_history": "Tesztelés előzményeken",
"task_pg_sweep": "Optimalizálás",
"task_reprocess": "Újrafeldolgozás",
@@ -566,7 +585,31 @@
"task_split": "Ciklus felosztása",
"task_trim": "Ciklus levágása",
"task_merge": "Ciklusok egyesítése",
"task_rebuild": "Burkológörbék újraépítése"
"task_rebuild": "Burkológörbék újraépítése",
"cat_profiles": "Profilok (programok)",
"cat_real_cycles": "Ciklusok (használati előzmények)",
"cat_reference_cycles": "Referenciaciklusok (importált)",
"cat_custom_phases": "Egyéni fázisok",
"cat_profile_groups": "Profilcsoportok",
"cat_settings": "Észlelési és illesztési beállítások",
"cat_matching_config": "Illesztés finomhangolása",
"cat_ml_models": "ML-modellek",
"cat_feedback": "Visszajelzési és ellenőrzési címkék",
"cat_suggestions": "Javaslatok",
"cat_maintenance_log": "Karbantartási napló",
"cat_history_logs": "Előzmény- és változásnaplók",
"cat_lifetime_stats": "Élettartam-összesítések",
"cycles_short": "ciklus",
"import_mode": "Hogyan egyesítse",
"mode_merge": "Egyesítés (a sajátom megtartása)",
"mode_replace": "Kiválasztottak cseréje",
"cycle_destination": "Hová kerüljenek az importált ciklusok?",
"dest_reference": "Referencia (csak görbe)",
"dest_real_history": "Valós előzmények (statisztikába számít)",
"conflict_resolution": "Névütközések",
"conflict_import_copy": "Importálás másolatként",
"conflict_keep_mine": "A sajátom megtartása",
"conflict_overwrite": "Felülírás"
},
"log": {
"all_levels": "Minden szinten",
@@ -643,7 +686,9 @@
"force_stop_msg": "Kényszerítve leállítod most az aktív ciklust? A ciklus megszakítottként lesz elmentve.",
"store_import": "Referenciaciklus importálása",
"store_share": "Megosztás a közösségi boltban",
"store_share_device": "Eszköz megosztása"
"store_share_device": "Eszköz megosztása",
"export_select": "Exportálás - adatok kiválasztása",
"import_wizard": "Importálás - adatok kiválasztása"
},
"msg": {
"appliance_monitor": "Készülék monitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} hasonló felcímkézetlen ciklus található hozzon létre egy profilt az egyeztetés megkezdéséhez.",
"cycles_deleted": "{count} ciklus törölve",
"enough_data": "Elegendő adat a tanuláshoz ({current}/{min} ciklus).",
"export_description": "Exportálja az összes profilt és ciklust JSON-ba, vagy állítsa vissza az előző exportálásból.",
"export_description": "Válaszd ki pontosan, mely profilokat, ciklusokat, beállításokat és egyebeket exportálsz JSON-ba, vagy elemezz egy fájlt, és csak a kívánt részeket importáld.",
"feedback_cycles_pending": "{n} ciklus{s} áttekintésre vár",
"feedback_prompt": "Erősítse meg, hogy helyes volt, javítsa ki a programot, vagy hagyja figyelmen kívül.",
"feedback_relabel_hint": "Ennek a ciklusnak az újracímkézése ezt is megoldja.",
"filter_by_profile": "Szűrés profil szerint…",
"group_modal_help": "Azonos alakú programok, amelyek hőmérséklete/centrifugálása különbözik (az időtartam változhat). Az egyezés pontozza a csoportot, mint egy jelöltet, majd kiválasztja a legmegfelelőbb tagot. Válasszon legalább 2-t; az átfedés megmutatja, mennyire hasonlítanak egymásra.",
"group_not_cohesive": "Ezek a profilok nem eléggé hasonlóak ahhoz, hogy megbízhatóan csoportosítsák, ezért az egyeztetés egyenként kezeli őket, amíg el nem távolítja a kiugró értéket, vagy fel nem osztja a csoportot.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Indítsa újra a Home Assistantot a szimulációs eszközök engedélyezéséhez.",
"pg_sim_progress": "{done} / {total} ciklus",
"pg_starting": "Indítás…",
"pg_stress_synthetic": "az üresjárati szimuláció itt kezdődik",
"pg_sweep_intro": "Mi lenne, ha a {param} más lenne? Teszteljen {steps} értéket az utolsó {cycles} cikluson, hogy megtalálja azt a beállítást, ahol a legtöbb ciklus helyesen egyezik.",
"pg_sweep_step": "{done} / {total}. lépés",
"pg_undetected": "{n} nem észlelt ciklus",
@@ -837,6 +884,9 @@
"pg_simulating": "Ciklus szimulálása…",
"pg_canvas_empty2": "Válassz ki egy ciklust fent, és nyomd meg a Futtatás gombot a szimulációhoz. Ezután vidd rá a mutatót az értékek leolvasásához, görgess a nagyításhoz, és húzd az eltoláshoz.",
"pg_apply_settings_confirm": "Elmenti ezt a(z) {n} beállítást erre az eszközre? {list}",
"pg_stress_terminated_detail": "Körülbelül {idle} W üresjárati szinten állandósult -- a ciklus {h} ó {m} p múlva {reason} révén ért véget.",
"pg_stress_above_threshold_detail": "Az üresjárati fogyasztás (kb. {idle} W) eléri vagy meghaladja a tényleges leállási küszöböt ({stop} W) -- a ciklust soha nem érzékelte csendesnek. A javításhoz növelje a stop_threshold_w értékét.",
"pg_stress_hit_cap_detail": "A ciklus {h} ó {m} p futott leállás nélkül -- a biztonsági korlát kényszerítette leállásra. Üresjárati fogyasztás {idle} W a(z) {stop} W leállási küszöbhöz képest.",
"no_logs_match": "Egyetlen naplóbejegyzés sem felel meg ezeknek a szűrőknek.",
"pg_analysis_hint2": "Válasszon egy ciklust, és nyomja meg a Futtatás gombot az egyezéselemzés betöltéséhez.",
"pg_analysis_empty2": "Nyomja meg a Futtatás gombot az egyezéselemzés megtekintéséhez.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Töltse fel a(z) {brand} {model} eszközt a kiválasztott referenciaciklusokkal. Más, ugyanolyan készülékkel rendelkezők átvehetik programjait. A bejegyzések felülvizsgálatra kerülnek nyilvánossá válás előtt.",
"share_profile_no_cycles": "Nincsenek referenciaciklusok -- jelöljön meg egy ciklust ⭐-gal a Ciklusok lapon, hogy belefoglalhassa ezt a profilt",
"advisory_phase_inconsistent": "Úgy tűnik, hogy a(z) '{name}' különböző programokat vagy hőmérsékleteket kever - ciklusai nagyon eltérő ideig fűtenek. Ha külön profilokra bontja (pl. hőmérsékletenként), az javítja a párosítást és az időbecsléseket.",
"advisory_phase_inconsistent_title": "⚠ Esetleg kevert programok"
"advisory_phase_inconsistent_title": "⚠ Esetleg kevert programok",
"export_select_intro": "Jelöld be pontosan, mit szeretnél belefoglalni. Ha a profilokat a ciklusaik nélkül választod ki, akkor is felismerhető program exportálódik (a megtanult görbéje vele utazik).",
"import_analyze_hint": "Tölts be egy exportált fájlt (vagy illeszd be a JSON-ját). A WashData elemzi, és pontosan megmutatja, mi importálható, mielőtt bármi megváltozna.",
"analyzing": "Elemzés…",
"device_type_mismatch_warn": "Ez az export egy másik készüléktípusból származik ({src} vs {local}). A programok és ciklusok továbbra is importálhatók referenciaadatként, de a készülékspecifikus beállítások és a valós előzmények importálása le van tiltva.",
"merge_hint": "Az importált elemek hozzáadódnak; semmi helyi adat nem vész el. A névütközéseket alább oldhatod fel.",
"replace_warn": "Minden bejelölt kategória törlődik, és a fájlból cserélődik le. A be nem jelölt kategóriák érintetlenül maradnak.",
"dest_reference_hint": "Az importált ciklusok csak a programfelismerést javítják, és soha nem befolyásolják a használati/energia-statisztikákat.",
"dest_real_history_hint": "Az importált ciklusok ennek a készüléknek a saját előzményeiként számítanak, és az energia-/használati statisztikákat táplálják. Egy készülék új telepítésre költöztetéséhez használd."
},
"phase_desc": {
"anti_crease": "Alkalmankénti rövid bukdácsolás a befejezés után a ráncok csökkentése érdekében.",
@@ -962,6 +1020,10 @@
"doc": "A teljesítménynek ez alá kell csökkennie az impulzusok között, hogy a ránctalanító üzemmód aktív maradjon.",
"label": "Kilépési teljesítmény küszöb"
},
"anti_wrinkle_idle_timeout": {
"doc": "Meddig maradhat a gép csendben két szárítópulzus között, mielőtt a ránctalanító üzemmód véget ér. Állítsa hosszabbra, mint a leghosszabb szünet, amit a szárítógép a pulzusok között hagy, különben minden későbbi pulzus téves indításként lesz értelmezve.",
"label": "Max. impulzusszünet"
},
"anti_wrinkle_max_duration": {
"doc": "Az ennél hosszabb impulzusokat valódi ciklusnak tekintik, nem pedig ránctalanítónak.",
"label": "Max. időtartam"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} ciklus megosztva; {failed} nem sikerült feltölteni.",
"store_device_shared_some_dup": "{created} ciklus megosztva; {dup} már a boltban volt.",
"store_download_failed": "Letöltés sikertelen: {error}",
"store_download_nothing": "Nincs új letöltendő - ez a beállítás már megvan az eszközön."
"store_download_nothing": "Nincs új letöltendő - ez a beállítás már megvan az eszközön.",
"export_selective_done": "Export letöltve",
"import_selective_done": "{profiles} profil és {cycles} ciklus importálva"
},
"suggestion": {
"both_agree": "A WashData javasolja",
@@ -1706,6 +1770,11 @@
"other": "Egyéb"
},
"pg_desc": {
"anti_wrinkle_enabled": "Nyelje el a fő fázis utáni szárítópulzusokat, ahelyett hogy új ciklusként olvasná őket",
"anti_wrinkle_exit_power": "A ránctalanítás aktívan tartásához a teljesítménynek az impulzusok között ez alá kell csökkennie",
"anti_wrinkle_idle_timeout": "Két szárítópulzus között engedélyezett csendes idő, mielőtt a ránctalanítás véget ér",
"anti_wrinkle_max_duration": "Az ennél hosszabb impulzus befejezi a ránctalanítást és új ciklust nyit",
"anti_wrinkle_max_power": "Az ennél magasabb impulzus befejezi a ránctalanítást és új ciklust nyit",
"completion_min_seconds": "A legrövidebb futás, ami valódi ciklusnak számít",
"end_repeat_count": "Egymást követő alacsony leolvasások a befejezés előtt",
"interrupted_min_seconds": "A rövid ciklusokat megszakítottként jelöli",
@@ -28,7 +28,9 @@
"awaiting_n": "Bíður samþykkis · {n} staðfestingar",
"approved": "Samþykkt",
"awaiting": "Bíður samþykkis",
"imported_tip": "Flutt inn úr samfélagsversluninni. Aðeins notað til samsvörunar, ekki talið með í tölfræði."
"imported_tip": "Flutt inn úr samfélagsversluninni. Aðeins notað til samsvörunar, ekki talið með í tölfræði.",
"not_importable": "ekki hér",
"exists": "til staðar"
},
"btn": {
"add_device": "+ Bæta við tæki",
@@ -169,7 +171,14 @@
"download_device": "Hlaða niður þessari uppsetningu",
"share_device": "⬆ Deila þessu tæki",
"share_device_tip": "Deila þessu tæki og uptökum viðmiðunarlota þess í samfélagsverslunina svo aðrir með sömu vél geti tekið þær yfir",
"share_n": "Deila {n} lotu/lotum"
"share_n": "Deila {n} lotu/lotum",
"export_selected": "Flytja út (velja gögn)",
"export_all": "Flytja allt út hratt",
"import_raw": "Ítarlegt: skipta öllu út úr JSON",
"download_export": "Sækja útflutning",
"analyze_import": "Greina skrá",
"import_selected": "Flytja inn valið",
"back": "Til baka"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Skynjunarkveikjur",
"pg_group_timing": "Tímasetningarreglur",
"pg_idle": "Aðgerðalaus",
"pg_stress_group": "Próf á aðgerðalausum lokum",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Aðgerðalaust stig (W)",
"pg_stress_idle_w_desc": "Hnekktu sjálfvirkt greindum biðstraumsgólfi (skildu eftir autt fyrir sjálfvirkt).",
"pg_stress_toggle": "Prófa aðgerðalausa lok",
"pg_stress_toggle_desc": "Hermir eftir því að tækið haldist við biðstrauminn sinn eftir lok upptöku - sýnir hvort og hvenær WashData stöðvar lotuna.",
"pg_last_run": "Síðasta keyrsla: {date}",
"pg_match_score": "Samsvörunareinkunn",
"pg_pct_of": "{p}% af {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Gagnvirkt aflrit fyrir lotuna: haltu bendlinum yfir til að lesa tíma/afl, skrunaðu til að þysja, dragðu til að hliðra",
"pg_alert_timeout_end": "Endaði vegna tímamarka, ekki vegna spár",
"pg_alert_indefinite": "Myndi ganga endalaust",
"pg_alert_stress_cap": "Öryggismörk náð",
"pg_alert_stress_ok": "Aðgerðalaus lok: lota stöðvuð",
"pg_alert_stress_warn": "Biðstraumur yfir stöðvunarþröskuldi",
"task_pg_history": "Prófa á sögu",
"task_pg_sweep": "Bestun",
"task_reprocess": "Endurvinnsla",
@@ -566,7 +584,32 @@
"task_split": "Skipti lotu upp",
"task_trim": "Klippi lotu til",
"task_merge": "Sameina lotur",
"task_rebuild": "Endurreikna aflhjúpa"
"task_rebuild": "Endurreikna aflhjúpa",
"cat_profiles": "Prófílar (forrit)",
"cat_real_cycles": "Lotur (keyrslusaga)",
"cat_reference_cycles": "Viðmiðunarlotur (innfluttar)",
"cat_custom_phases": "Sérsniðnir fasar",
"cat_profile_groups": "Prófílhópar",
"cat_settings": "Greiningar- & samsvörunarstillingar",
"cat_matching_config": "Fínstilling samsvörunar",
"cat_ml_models": "ML-líkön",
"cat_feedback": "Umsögn & yfirferðarmerki",
"cat_suggestions": "Tillögur",
"cat_maintenance_log": "Viðhaldsskrá",
"cat_history_logs": "Sögu- & breytingaskrár",
"cat_lifetime_stats": "Heildartölur (líftími)",
"cycles_short": "lotur",
"import_mode": "Hvernig á að sameina",
"mode_merge": "Sameina (halda mínum)",
"mode_replace": "Skipta út völdum",
"cycle_destination": "Hvert eiga innfluttar lotur að fara?",
"dest_reference": "Viðmiðun (aðeins ferill)",
"dest_real_history": "Raunveruleg saga (telur í tölfræði)",
"conflict_resolution": "Nafnaárekstrar",
"conflict_import_copy": "Flytja inn sem afrit",
"conflict_keep_mine": "Halda mínum",
"conflict_overwrite": "Skrifa yfir",
"pg_anti_wrinkle": "Hrukkuleysi"
},
"log": {
"all_levels": "Öll stig",
@@ -643,7 +686,9 @@
"force_stop_msg": "Þvinga virku lotuna til að stöðvast núna? Lotan verður vistuð sem trufluð.",
"store_import": "Flytja inn viðmiðunarlotu",
"store_share": "Deila í samfélagsverslun",
"store_share_device": "Deila þessu tæki"
"store_share_device": "Deila þessu tæki",
"export_select": "Útflutningur - velja gögn",
"import_wizard": "Innflutningur - velja gögn"
},
"msg": {
"appliance_monitor": "Vöktunartæki",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} svipaðar ómerktar lotur fundust búðu til prófíl til að byrja að samstemma þær.",
"cycles_deleted": "{count} lotu(m) eytt",
"enough_data": "Nóg gögn til að læra af ({current}/{min} lotur).",
"export_description": "Flyttu út öll snið og lotur í JSON, eða endurheimtu frá fyrri útflutningi.",
"export_description": "Veldu nákvæmlega hvaða prófíla, lotur, stillingar og fleira á að flytja út í JSON, eða greindu skrá og fluttu aðeins inn þá hluta sem þú vilt.",
"feedback_cycles_pending": "{n} lota{s} til yfirferðar",
"feedback_prompt": "Staðfestu að það væri rétt, leiðréttu forritið eða hunsa.",
"feedback_relabel_hint": "Að endurmerkja þessa lotu lýkur einnig yfirferðinni.",
"filter_by_profile": "Sía eftir prófíl…",
"group_modal_help": "Flokkaðu forrit með sömu lögun sem eru mismunandi hvað varðar hitastig/snúning (lengd getur verið mismunandi). Samsvörun skorar hópinn sem einn frambjóðanda og velur síðan þann sem hentar best. Veldu að minnsta kosti 2; yfirlagið sýnir hversu lík þau eru.",
"group_not_cohesive": "Þessir snið eru ekki nógu lík til að hópa á áreiðanlegan hátt, þannig að samsvörun meðhöndlar þá hver fyrir sig þar til þú fjarlægir frávikið eða skiptir hópnum.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Endurræstu Home Assistant til að virkja hermitólin.",
"pg_sim_progress": "{done} / {total} lotur",
"pg_starting": "Ræsir…",
"pg_stress_synthetic": "aðgerðalaus hermun hefst hér",
"pg_sweep_intro": "Hvað ef {param} væri öðruvísi? Prófaðu {steps} gildi yfir síðustu {cycles} loturnar þínar til að finna stillinguna þar sem flestar lotur samsvarast rétt.",
"pg_sweep_step": "Skref {done} / {total}",
"pg_undetected": "{n} lota{s} ógreind",
@@ -837,6 +884,9 @@
"pg_simulating": "Hermir lotu…",
"pg_canvas_empty2": "Veldu lotu að ofan og ýttu á Keyra til að herma hana. Haltu svo bendlinum yfir til að lesa gildi, skrunaðu til að þysja og dragðu til að hliðra.",
"pg_apply_settings_confirm": "Vista þessar {n} stilling(ar) á þetta tæki? {list}",
"pg_stress_terminated_detail": "Náði jafnvægi við ~{idle}W í aðgerðaleysi -- lotan endaði {h} klst. {m} mín. síðar með {reason}.",
"pg_stress_above_threshold_detail": "Biðstraumur ~{idle}W er við eða yfir virkum stöðvunarþröskuldi ({stop}W) -- lotan var aldrei skráð sem óvirk. Hækkaðu stop_threshold_w til að laga þetta.",
"pg_stress_hit_cap_detail": "Lotan keyrði {h} klst. {m} mín. án þess að stöðvast -- stöðvuð með valdi af öryggismörkunum. Biðstraumur {idle}W á móti stöðvunarþröskuldi {stop}W.",
"no_logs_match": "Engar annálafærslur passa við þessar síur.",
"pg_analysis_hint2": "Veldu lotu og ýttu á Keyra til að hlaða samsvörunargreiningu.",
"pg_analysis_empty2": "Ýttu á Keyra til að sjá samsvörunargreiningu.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Hlaðaðu upp {brand} {model} með viðmiðunarlotum sem þú velur. Aðrir með sama tæki geta tekið yfir forritin þín. Færslur eru yfirfarnar áður en þær birtast opinberlega.",
"share_profile_no_cycles": "Engar viðmiðunarlotir -- merktu lotu sem ⭐ í flipanum Lotir til að hafa þennan prófíl með",
"advisory_phase_inconsistent": "'{name}' virðist blanda saman ólíkum forritum eða hitastigum - lotur þess hita mislengi. Ef þú skiptir því upp í aðskilin snið (t.d. eftir hitastigi) batnar samsvörun og tímamat.",
"advisory_phase_inconsistent_title": "⚠ Hugsanlega blönduð forrit"
"advisory_phase_inconsistent_title": "⚠ Hugsanlega blönduð forrit",
"export_select_intro": "Merktu nákvæmlega við hvað á að hafa með. Að velja prófíla án lotanna þeirra flytur samt út greinanlegt forrit (lærði ferillinn fylgir með).",
"import_analyze_hint": "Hladdu inn útfluttri skrá (eða límdu inn JSON-ið hennar). WashData greinir hana og sýnir nákvæmlega hvað er hægt að flytja inn áður en nokkuð breytist.",
"analyzing": "Greini…",
"device_type_mismatch_warn": "Þessi útflutningur er frá annarri tegund tækis ({src} á móti {local}). Enn er hægt að flytja inn forrit og lotur sem viðmiðunargögn, en tækjabundnar stillingar og innflutningur á raunverulegri sögu eru óvirk.",
"merge_hint": "Innfluttum atriðum er bætt við; ekkert staðbundið tapast. Nafnaárekstrar eru leystir hér að neðan.",
"replace_warn": "Hver valinn flokkur er hreinsaður og settur í staðinn úr skránni. Óvaldir flokkar haldast óbreyttir.",
"dest_reference_hint": "Innfluttar lotur bæta aðeins forritasamsvörun og hafa aldrei áhrif á notkunar-/orkutölfræði.",
"dest_real_history_hint": "Innfluttar lotur teljast sem eigin saga þessa tækis og fæða orku-/notkunartölfræði. Notaðu þetta til að flytja eitt tæki yfir í nýja uppsetningu."
},
"pg_desc": {
"completion_min_seconds": "Stysta keyrsla sem telst raunveruleg lota",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Þrep 3: DDTW hálf-mettunarfjarlægð; minna = meira formnæmt (sjálfgefið 30)",
"dtw_refine_top_n": "Þrep 3: frambjóðendur sem DTW endurgerir skor fyrir; hækkið í 7-9 ef rétt snið lendir á 4.-5. sæti (sjálfgefið 5)",
"duration_scale": "Þrep 4: log-hlutfall þar sem samræmi í keyrslulengd helmingast; minna = strangara viðurlög (sjálfgefið 0,175)",
"energy_scale": "Þrep 4: log-hlutfall þar sem samræmi í orkunotkun helmingast; minna = strangara viðurlög (sjálfgefið 0,25)"
"energy_scale": "Þrep 4: log-hlutfall þar sem samræmi í orkunotkun helmingast; minna = strangara viðurlög (sjálfgefið 0,25)",
"anti_wrinkle_idle_timeout": "Leyfilegur kyrrðartími milli tveggja veltipúlsa áður en hrukkuleysi lýkur",
"anti_wrinkle_max_power": "Púls yfir þessu slekkur á hrukkuleysi og opnar nýja lotu",
"anti_wrinkle_enabled": "Fangar veltipúlsana eftir aðalfasann í stað þess að lesa þá sem nýjar lotur",
"anti_wrinkle_max_duration": "Púls sem er lengri en þetta slekkur á hrukkuleysi og opnar nýja lotu",
"anti_wrinkle_exit_power": "Aflið verður að falla undir þetta á milli púlsa til að hrukkuleysi haldist virkt"
},
"phase_desc": {
"anti_crease": "Stundum hrynur stutt eftir að því er lokið til að draga úr hrukkum.",
@@ -985,6 +1048,10 @@
"doc": "Krafturinn verður að falla undir þetta á milli púlsa til að hrukkuvarnarstilling haldist virkur.",
"label": "Útgönguaflþröskuldur"
},
"anti_wrinkle_idle_timeout": {
"doc": "Hversu lengi vélin má vera kyrr á milli tveggja veltipúlsa áður en hrukkuleysisstillingu lýkur. Stilltu það hærra en lengsta bilið sem þurrkarinn þinn skilur eftir á milli púlsa, annars er hver síðari púls lesinn sem röng ræsing.",
"label": "Hám. púlsbil"
},
"anti_wrinkle_max_duration": {
"doc": "Púlsar sem eru lengri en þetta eru meðhöndlaðir sem alvöru hringrás frekar en hrukkuvörn.",
"label": "Hámarkslengd"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} lota/lotir deilt; {failed} tókst ekki að hlaða upp.",
"store_device_shared_some_dup": "{created} lota/lotir deilt; {dup} voru þegar í verslun.",
"store_download_failed": "Niðurhal mistókst: {error}",
"store_download_nothing": "Ekkert nýtt til að hlaða niður -- þessi uppsetning er þegar á tækinu þínu."
"store_download_nothing": "Ekkert nýtt til að hlaða niður -- þessi uppsetning er þegar á tækinu þínu.",
"export_selective_done": "Útflutningur sóttur",
"import_selective_done": "Flutti inn {profiles} prófíl(a) og {cycles} lotu(r)"
},
"trend": {
"down": "Stefnir niður",
@@ -28,7 +28,9 @@
"awaiting_n": "In attesa di approvazione · {n} confermati",
"approved": "Approvato",
"awaiting": "In attesa di approvazione",
"imported_tip": "Importato dallo store della community. Usato solo per la corrispondenza, non conteggiato nelle statistiche."
"imported_tip": "Importato dallo store della community. Usato solo per la corrispondenza, non conteggiato nelle statistiche.",
"not_importable": "n/d qui",
"exists": "esiste già"
},
"btn": {
"add_device": "+ Aggiungi dispositivo",
@@ -169,7 +171,14 @@
"download_device": "Scarica questa configurazione",
"share_device": "⬆ Condividi questo dispositivo",
"share_device_tip": "Condividi questo elettrodomestico e i suoi cicli di riferimento registrati nello store della community così altri con lo stesso dispositivo possono adottarli",
"share_n": "Condividi {n} ciclo/i"
"share_n": "Condividi {n} ciclo/i",
"export_selected": "Esporta (scegli i dati)",
"export_all": "Esporta tutto rapidamente",
"import_raw": "Avanzato: sostituisci tutto da JSON",
"download_export": "Scarica l'esportazione",
"analyze_import": "Analizza il file",
"import_selected": "Importa la selezione",
"back": "Indietro"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Scheda predefinita",
"panel_language": "Lingua del pannello",
"per_cycle_short": "ciclo",
"pg_anti_wrinkle": "Antirughe",
"pg_autofill_tip": "Compila automaticamente l'intervallo dal valore attuale ({lo}{hi})",
"pg_best_value": "Miglior valore trovato",
"pg_current_value": "Valore attuale:",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Senza abbinamento",
"pg_alert_ambiguous": "Abbinamento ambiguo",
"pg_alert_energy": "Anomalia di energia",
"pg_alert_stress_cap": "Limite di sicurezza raggiunto",
"pg_alert_stress_ok": "Terminazione a riposo: ciclo interrotto",
"pg_alert_stress_warn": "Consumo in standby superiore alla soglia di arresto",
"pg_newly_correct": "ora corretto",
"pg_regressed": "peggiorato",
"pg_end_timing_changed": "momento di fine modificato",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Parametro",
"pg_objective": "Obiettivo",
"pg_steps": "Passaggi",
"pg_stress_group": "Test di terminazione a riposo",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Livello a riposo (W)",
"pg_stress_idle_w_desc": "Sostituisce il piano di standby rilevato automaticamente (lasciare vuoto per automatico).",
"pg_stress_toggle": "Testa la terminazione a riposo",
"pg_stress_toggle_desc": "Simula l'elettrodomestico mantenuto al suo consumo in standby dopo la fine della registrazione mostra se e quando WashData interrompe il ciclo.",
"pg_sweep_2d": "Mappa di calore 2D",
"pg_sweep_param_y": "2° parametro",
"pg_obj_match": "Precisione di abbinamento",
@@ -566,7 +585,31 @@
"task_split": "Divisione del ciclo",
"task_trim": "Ritaglio del ciclo",
"task_merge": "Unione dei cicli",
"task_rebuild": "Ricostruzione degli inviluppi"
"task_rebuild": "Ricostruzione degli inviluppi",
"cat_profiles": "Profili (programmi)",
"cat_real_cycles": "Cicli (cronologia di utilizzo)",
"cat_reference_cycles": "Cicli di riferimento (importati)",
"cat_custom_phases": "Fasi personalizzate",
"cat_profile_groups": "Gruppi di profili",
"cat_settings": "Impostazioni di rilevamento e riconoscimento",
"cat_matching_config": "Regolazione del riconoscimento",
"cat_ml_models": "Modelli ML",
"cat_feedback": "Feedback ed etichette di revisione",
"cat_suggestions": "Suggerimenti",
"cat_maintenance_log": "Registro di manutenzione",
"cat_history_logs": "Cronologia e registri delle modifiche",
"cat_lifetime_stats": "Totali complessivi",
"cycles_short": "cicli",
"import_mode": "Come combinare",
"mode_merge": "Unisci (mantieni i miei)",
"mode_replace": "Sostituisci la selezione",
"cycle_destination": "Dove devono andare i cicli importati?",
"dest_reference": "Riferimento (solo forma)",
"dest_real_history": "Cronologia reale (conta nelle statistiche)",
"conflict_resolution": "Conflitti di nome",
"conflict_import_copy": "Importa come copia",
"conflict_keep_mine": "Mantieni il mio",
"conflict_overwrite": "Sovrascrivi"
},
"log": {
"all_levels": "Tutti i livelli",
@@ -643,7 +686,9 @@
"force_stop_msg": "Forzare l'arresto del ciclo attivo ora? Il ciclo verrà salvato come interrotto.",
"store_import": "Importa ciclo di riferimento",
"store_share": "Condividi nello store della community",
"store_share_device": "Condividi questo dispositivo"
"store_share_device": "Condividi questo dispositivo",
"export_select": "Esporta - scegli i dati",
"import_wizard": "Importa - scegli i dati"
},
"msg": {
"appliance_monitor": "Monitor degli elettrodomestici",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} cicli simili senza etichetta trovati crea un profilo per iniziare a trovare corrispondenze.",
"cycles_deleted": "{count} ciclo/i eliminato/i",
"enough_data": "Dati sufficienti per apprendere ({current}/{min} cicli).",
"export_description": "Esporta tutti i profili e i cicli in JSON o ripristina da un'esportazione precedente.",
"export_description": "Scegli esattamente quali profili, cicli, impostazioni e altro esportare in JSON, oppure analizza un file e importa solo le parti che vuoi.",
"feedback_cycles_pending": "{n} ciclo{s} da revisionare",
"feedback_prompt": "Conferma che fosse corretto, correggi il programma o ignoralo.",
"feedback_relabel_hint": "Anche rietichettare questo ciclo lo risolve.",
"filter_by_profile": "Filtra per profilo…",
"group_modal_help": "Raggruppare programmi con la stessa forma che differiscono per temperatura/centrifuga (le durate possono variare). La corrispondenza valuta il gruppo come un candidato, quindi sceglie il membro più adatto. Scegline almeno 2; la sovrapposizione mostra quanto sono simili.",
"group_not_cohesive": "Questi profili non sono sufficientemente simili per essere raggruppati in modo affidabile, quindi la corrispondenza li tratta individualmente finché non rimuovi il valore anomalo o dividi il gruppo.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Riavvia Home Assistant per abilitare gli strumenti di simulazione.",
"pg_sim_progress": "{done} / {total} cicli",
"pg_starting": "Avvio…",
"pg_stress_synthetic": "la simulazione in standby inizia qui",
"pg_sweep_intro": "E se {param} fosse diverso? Testa {steps} valori sui tuoi ultimi {cycles} cicli per trovare l'impostazione in cui il maggior numero di cicli viene riconosciuto correttamente.",
"pg_sweep_step": "Passaggio {done} / {total}",
"pg_undetected": "{n} ciclo{s} non rilevato",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulazione del ciclo…",
"pg_canvas_empty2": "Scegli un ciclo qui sopra e premi Esegui per simularlo. Poi passa il puntatore per leggere i valori, scorri per ingrandire e trascina per spostarti.",
"pg_apply_settings_confirm": "Salvare queste {n} impostazioni su questo dispositivo? {list}",
"pg_stress_terminated_detail": "Stabilizzato a ~{idle}W in standby -- il ciclo è terminato {h}h {m}m dopo tramite {reason}.",
"pg_stress_above_threshold_detail": "Il consumo in standby ~{idle}W è pari o superiore alla soglia di arresto effettiva ({stop}W) -- il ciclo non è mai stato considerato inattivo. Aumenta stop_threshold_w per correggere.",
"pg_stress_hit_cap_detail": "Il ciclo è durato {h}h {m}m senza fermarsi -- interrotto forzatamente dal limite di sicurezza. Consumo in standby {idle}W rispetto alla soglia di arresto {stop}W.",
"no_logs_match": "Nessun record di log corrisponde a questi filtri.",
"pg_analysis_hint2": "Seleziona un ciclo e premi Esegui per caricare l'analisi di abbinamento.",
"pg_analysis_empty2": "Premi Esegui per vedere l'analisi di abbinamento.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Carica {brand} {model} con i cicli di riferimento che selezioni. Altri con lo stesso elettrodomestico possono adottare i tuoi programmi. Le voci vengono revisionate prima di apparire pubblicamente.",
"share_profile_no_cycles": "Nessun ciclo di riferimento -- contrassegna un ciclo come ⭐ nella scheda Cicli per includere questo profilo",
"advisory_phase_inconsistent": "'{name}' sembra mescolare programmi o temperature diversi - i suoi cicli riscaldano per intervalli di tempo molto diversi. Dividerlo in profili separati (ad es. per temperatura) migliorerà la corrispondenza e le stime dei tempi.",
"advisory_phase_inconsistent_title": "⚠ Possibili programmi misti"
"advisory_phase_inconsistent_title": "⚠ Possibili programmi misti",
"export_select_intro": "Spunta esattamente cosa includere. Selezionare i profili senza i loro cicli esporta comunque un programma riconoscibile (la sua forma appresa viaggia insieme).",
"import_analyze_hint": "Carica un file esportato (o incolla il suo JSON). WashData lo analizza e mostra esattamente cosa si può importare prima di qualsiasi modifica.",
"analyzing": "Analisi in corso…",
"device_type_mismatch_warn": "Questa esportazione proviene da un tipo di elettrodomestico diverso ({src} invece di {local}). Programmi e cicli possono comunque essere importati come dati di riferimento, ma le impostazioni specifiche del dispositivo e l'importazione nella cronologia reale sono disabilitate.",
"merge_hint": "Gli elementi importati vengono aggiunti; nulla di locale va perso. I conflitti di nome si risolvono qui sotto.",
"replace_warn": "Ogni categoria spuntata viene cancellata e sostituita dal contenuto del file. Le categorie non spuntate restano invariate.",
"dest_reference_hint": "I cicli importati migliorano solo il riconoscimento dei programmi e non influiscono mai sulle statistiche di utilizzo/energia.",
"dest_real_history_hint": "I cicli importati contano come cronologia propria di questo dispositivo e alimentano le statistiche di energia/utilizzo. Da usare per spostare un elettrodomestico su una nuova installazione."
},
"pg_desc": {
"anti_wrinkle_enabled": "Assorbire gli impulsi di rotazione dopo la fase principale invece di leggerli come nuovi cicli",
"anti_wrinkle_exit_power": "La potenza deve scendere sotto questo valore tra gli impulsi affinché la modalità antirughe resti attiva",
"anti_wrinkle_idle_timeout": "Tempo di silenzio consentito tra due impulsi di rotazione prima che la modalità antirughe termini",
"anti_wrinkle_max_duration": "Un impulso più lungo di questo termina la modalità antirughe e apre un nuovo ciclo",
"anti_wrinkle_max_power": "Un impulso superiore a questo termina la modalità antirughe e apre un nuovo ciclo",
"completion_min_seconds": "Esecuzione più breve che conta come ciclo reale",
"end_repeat_count": "Letture basse consecutive prima della fine",
"interrupted_min_seconds": "Cicli brevi contrassegnati come interrotti",
@@ -985,6 +1048,10 @@
"doc": "La potenza deve scendere al di sotto di questo valore tra gli impulsi affinché la modalità antirughe rimanga attiva.",
"label": "Soglia potenza uscita"
},
"anti_wrinkle_idle_timeout": {
"doc": "Per quanto tempo la macchina può rimanere silenziosa tra due impulsi di rotazione prima che la modalità antirughe termini. Impostalo sopra l'intervallo più lungo che la tua asciugatrice lascia tra gli impulsi, altrimenti ogni impulso successivo viene letto come una falsa partenza.",
"label": "Intervallo massimo tra impulsi"
},
"anti_wrinkle_max_duration": {
"doc": "Gli impulsi più lunghi vengono trattati come un vero e proprio ciclo piuttosto che come una caduta antirughe.",
"label": "Durata massima"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "Condiviso/i {n} ciclo/i; {failed} non è/sono stato/i caricato/i.",
"store_device_shared_some_dup": "Condiviso/i {created} ciclo/i; {dup} erano già nello store.",
"store_download_failed": "Download non riuscito: {error}",
"store_download_nothing": "Nulla di nuovo da scaricare - questa configurazione è già sul tuo dispositivo."
"store_download_nothing": "Nulla di nuovo da scaricare - questa configurazione è già sul tuo dispositivo.",
"export_selective_done": "Esportazione scaricata",
"import_selective_done": "Importati {profiles} profilo/i e {cycles} ciclo/i"
},
"suggestion": {
"both_agree": "WashData consiglia",
@@ -28,7 +28,9 @@
"awaiting_n": "承認待ち · {n} 件確認済み",
"approved": "承認済み",
"awaiting": "承認待ち",
"imported_tip": "コミュニティストアからインポートしました。マッチングにのみ使用され、統計には含まれません。"
"imported_tip": "コミュニティストアからインポートしました。マッチングにのみ使用され、統計には含まれません。",
"not_importable": "対象外",
"exists": "既存"
},
"btn": {
"add_device": "+ デバイスを追加",
@@ -169,7 +171,14 @@
"download_device": "このセットアップをダウンロード",
"share_device": "⬆ このデバイスを共有",
"share_device_tip": "この機器と記録済みリファレンスサイクルをコミュニティストアに共有して、同じ機種を持つ他のユーザーが利用できるようにします",
"share_n": "{n}件のサイクルを共有"
"share_n": "{n}件のサイクルを共有",
"export_selected": "エクスポート(データを選択)",
"export_all": "すべてをクイックエクスポート",
"import_raw": "詳細設定:JSONからすべて置き換え",
"download_export": "エクスポートをダウンロード",
"analyze_import": "ファイルを解析",
"import_selected": "選択項目をインポート",
"back": "戻る"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "デフォルトのタブ",
"panel_language": "パネル言語",
"per_cycle_short": "サイクル",
"pg_anti_wrinkle": "しわ防止",
"pg_autofill_tip": "現在の値から範囲を自動入力({lo}–{hi})",
"pg_best_value": "見つかった最適値",
"pg_current_value": "現在の値:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "パラメーター",
"pg_objective": "目的",
"pg_steps": "ステップ数",
"pg_stress_group": "待機終了テスト",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "待機レベル (W)",
"pg_stress_idle_w_desc": "自動検出された待機電力下限を上書きします(自動の場合は空欄)。",
"pg_stress_toggle": "待機終了をテスト",
"pg_stress_toggle_desc": "記録終了後に待機電力レベルで稼働し続ける機器をシミュレートします - WashData がサイクルを停止するかどうか、およびいつ停止するかを確認します。",
"pg_sweep_2d": "2Dヒートマップ",
"pg_sweep_param_y": "2番目のパラメーター",
"pg_obj_match": "マッチ精度",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "インタラクティブなサイクル電力グラフ: ホバーで時刻/電力を表示、スクロールでズーム、ドラッグで移動",
"pg_alert_timeout_end": "予測ではなくタイムアウトで終了しました",
"pg_alert_indefinite": "いつまでも終了しません",
"pg_alert_stress_cap": "安全制限に達しました",
"pg_alert_stress_ok": "待機終了: サイクルが停止しました",
"pg_alert_stress_warn": "待機電力が停止しきい値を超えています",
"task_pg_history": "履歴でテスト",
"task_pg_sweep": "最適化",
"task_reprocess": "再処理",
@@ -566,7 +585,31 @@
"task_split": "サイクルを分割中",
"task_trim": "サイクルをトリミング中",
"task_merge": "サイクルを結合中",
"task_rebuild": "エンベロープを再構築中"
"task_rebuild": "エンベロープを再構築中",
"cat_profiles": "プロファイル(プログラム)",
"cat_real_cycles": "サイクル(実行履歴)",
"cat_reference_cycles": "参照サイクル(インポート済み)",
"cat_custom_phases": "カスタムフェーズ",
"cat_profile_groups": "プロファイルグループ",
"cat_settings": "検出と認識の設定",
"cat_matching_config": "認識の調整",
"cat_ml_models": "MLモデル",
"cat_feedback": "フィードバックとレビューラベル",
"cat_suggestions": "提案",
"cat_maintenance_log": "メンテナンスログ",
"cat_history_logs": "履歴と変更ログ",
"cat_lifetime_stats": "累計値",
"cycles_short": "サイクル",
"import_mode": "結合方法",
"mode_merge": "マージ(自分のを保持)",
"mode_replace": "選択項目を置き換え",
"cycle_destination": "インポートしたサイクルの保存先は?",
"dest_reference": "参照(形状のみ)",
"dest_real_history": "実履歴(統計に計上)",
"conflict_resolution": "名前の衝突",
"conflict_import_copy": "コピーとしてインポート",
"conflict_keep_mine": "自分のを保持",
"conflict_overwrite": "上書き"
},
"log": {
"all_levels": "すべてのレベル",
@@ -643,7 +686,9 @@
"force_stop_msg": "アクティブなサイクルを今すぐ強制停止しますか?サイクルは中断として保存されます。",
"store_import": "基準サイクルをインポート",
"store_share": "コミュニティストアに共有",
"store_share_device": "このデバイスを共有"
"store_share_device": "このデバイスを共有",
"export_select": "エクスポート - データを選択",
"import_wizard": "インポート - データを選択"
},
"msg": {
"appliance_monitor": "アプライアンスモニター",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} 件の類似した未ラベルサイクルが見つかりました – プロファイルを作成してマッチングを開始してください。",
"cycles_deleted": "{count} 件のサイクルを削除しました",
"enough_data": "学習に十分なデータがあります({current}/{min} サイクル)。",
"export_description": "すべてのプロファイルサイクルを JSON にエクスポートするか、以前のエクスポートから復元します。",
"export_description": "JSONにエクスポートするプロファイルサイクル、設定などを正確に選択するか、ファイルを解析して必要な部分だけをインポートできます。",
"feedback_cycles_pending": "レビューが必要なサイクル: {n}件{s}",
"feedback_prompt": "それが正しかったことを確認し、プログラムを修正するか、無視してください。",
"feedback_relabel_hint": "このサイクルを再ラベル付けすると、これも解決します。",
"filter_by_profile": "プロファイルでフィルター…",
"group_modal_help": "温度/スピンが異なる同じ形状のグループ プログラム (期間は異なる場合があります)。マッチングでは、グループを 1 つの候補としてスコア付けし、最も適合するメンバーを選択します。少なくとも 2 つ選択してください。オーバーレイは、それらがどれだけ似ているかを示します。",
"group_not_cohesive": "これらのプロファイルは、確実にグループ化できるほど類似していないため、外れ値を削除するかグループを分割するまで、マッチングでは個別に扱われます。",
@@ -729,6 +775,7 @@
"pg_restart_note": "シミュレーションツールを有効にするには Home Assistant を再起動してください。",
"pg_sim_progress": "{done} / {total} サイクル",
"pg_starting": "開始中…",
"pg_stress_synthetic": "待機シミュレーションはここから開始",
"pg_sweep_intro": "もし {param} が違っていたら? 最も多くのサイクルが正しくマッチする設定を見つけるため、直近 {cycles} サイクルにわたって {steps} 個の値をテストします。",
"pg_sweep_step": "ステップ {done} / {total}",
"pg_undetected": "{n} 件のサイクル{s}が未検出",
@@ -837,6 +884,9 @@
"pg_simulating": "サイクルをシミュレーションしています…",
"pg_canvas_empty2": "上でサイクルを選び、「実行」を押してシミュレーションします。その後、ホバーで値を表示、スクロールでズーム、ドラッグで移動できます。",
"pg_apply_settings_confirm": "これら {n} 件の設定をこのデバイスに保存しますか? {list}",
"pg_stress_terminated_detail": "約 {idle}W の待機状態に落ち着きました -- {h}時間{m}分後に {reason} によってサイクルが終了しました。",
"pg_stress_above_threshold_detail": "待機電力 約 {idle}W が実効停止しきい値 ({stop}W) 以上です -- サイクルが一度も静音状態として認識されませんでした。修正するには stop_threshold_w を上げてください。",
"pg_stress_hit_cap_detail": "サイクルが停止せずに {h}時間{m}分 稼働しました -- 安全制限により強制停止されました。待機電力 {idle}W に対して停止しきい値 {stop}W です。",
"no_logs_match": "これらのフィルターに一致するログレコードはありません。",
"pg_analysis_hint2": "サイクルを選択して「実行」を押すと、マッチ分析が読み込まれます。",
"pg_analysis_empty2": "「実行」を押すとマッチ分析が表示されます。",
@@ -864,7 +914,15 @@
"store_share_device_intro": "選択したリファレンスサイクルとともに{brand} {model}をアップロードします。同じ機器を持つ他のユーザーがあなたのプログラムを引き継ぐことができます。エントリは公開前に審査されます。",
"share_profile_no_cycles": "リファレンスサイクルなし -- サイクルタブでサイクルを⭐としてマークしてこのプロファイルを含めてください",
"advisory_phase_inconsistent": "'{name}' は異なるプログラムや温度が混在しているようです。加熱時間がサイクルごとに大きく異なります。温度ごとなど別々のプロファイルに分割すると、マッチングと時間推定が改善されます。",
"advisory_phase_inconsistent_title": "⚠ プログラム混在の可能性"
"advisory_phase_inconsistent_title": "⚠ プログラム混在の可能性",
"export_select_intro": "含める項目を正確にチェックしてください。サイクルなしでプロファイルを選択しても、認識可能なプログラムはエクスポートされます(学習した形状も一緒に移行します)。",
"import_analyze_hint": "エクスポートしたファイルを読み込む(またはそのJSONを貼り付ける)と、WashDataが解析し、変更前にインポート可能な内容を正確に表示します。",
"analyzing": "解析中…",
"device_type_mismatch_warn": "このエクスポートは異なる機器タイプのものです({src} と {local})。プログラムとサイクルは参照データとしてインポートできますが、機器固有の設定と実履歴のインポートは無効になります。",
"merge_hint": "インポートした項目は追加され、ローカルのデータは失われません。名前の衝突は以下で解決します。",
"replace_warn": "チェックした各カテゴリは消去され、ファイルの内容で置き換えられます。チェックしていないカテゴリはそのまま残ります。",
"dest_reference_hint": "インポートしたサイクルはプログラムの認識精度を向上させるだけで、使用量やエネルギーの統計には影響しません。",
"dest_real_history_hint": "インポートしたサイクルはこの機器自身の履歴として扱われ、エネルギーや使用量の統計に反映されます。1台の機器を新しい環境に移行する際に使用します。"
},
"pg_desc": {
"completion_min_seconds": "実サイクルとみなす最短の稼働時間",
@@ -875,6 +933,11 @@
"start_duration_threshold": "開始を確定するしきい値超えの秒数",
"start_threshold_w": "開始とみなす最小ワット数",
"stop_threshold_w": "これを下回ると停止とみなす",
"anti_wrinkle_enabled": "メインフェーズ後のタンブルパルスを新しいサイクルと解釈せず吸収する",
"anti_wrinkle_exit_power": "しわ防止を有効に保つには、パルス間の電力がこの値を下回る必要がある",
"anti_wrinkle_idle_timeout": "しわ防止が終了するまでに2つのタンブルパルスの間に許容される静止時間",
"anti_wrinkle_max_duration": "これより長いパルスはしわ防止を終了し、新しいサイクルを開始する",
"anti_wrinkle_max_power": "これを超えるパルスはしわ防止を終了し、新しいサイクルを開始する",
"dtw_bandwidth": "ステージ 3: Sakoe-Chiba ワープバンド (0 = DTW 無効; デフォルト 0.2 = サイクル長の 20%)",
"corr_weight": "ステージ 2: 曲線形状(相関)と電力レベル(MAE)のバランス; デフォルト 0.45",
"duration_weight": "ステージ 4: 運転時間の一致が最終スコアにどれだけ影響するか(デフォルト 0.22)",
@@ -985,6 +1048,10 @@
"doc": "しわ防止モードがアクティブな状態を維持するには、パルス間の電力がこれを下回る必要があります。",
"label": "終了電力しきい値"
},
"anti_wrinkle_idle_timeout": {
"label": "最大パルス間隔",
"doc": "しわ防止モードが終了するまでに、機器が2つのタンブルパルスの間に静止していられる時間。乾燥機がパルス間に残す最長の間隔より長く設定してください。そうしないと、以降のパルスがすべて誤った開始として読み取られます。"
},
"anti_wrinkle_max_duration": {
"doc": "これより長いパルスは、しわ防止タンブルではなく実際のサイクルとして扱われます。",
"label": "最大継続時間"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n}件のサイクルを共有しました。{failed}件はアップロードできませんでした。",
"store_device_shared_some_dup": "{created}件のサイクルを共有しました。{dup}件は既にストアにありました。",
"store_download_failed": "ダウンロードに失敗しました: {error}",
"store_download_nothing": "ダウンロードする新しいものはありません - このセットアップは既にデバイスにあります。"
"store_download_nothing": "ダウンロードする新しいものはありません - このセットアップは既にデバイスにあります。",
"export_selective_done": "エクスポートをダウンロードしました",
"import_selective_done": "{profiles} 件のプロファイルと {cycles} 件のサイクルをインポートしました"
},
"suggestion": {
"both_agree": "WashDataの推奨",
@@ -28,7 +28,9 @@
"awaiting_n": "승인 대기 중 · {n}개 확인됨",
"approved": "승인됨",
"awaiting": "승인 대기 중",
"imported_tip": "커뮤니티 스토어에서 가져왔습니다. 매칭에만 사용되며 통계에는 포함되지 않습니다."
"imported_tip": "커뮤니티 스토어에서 가져왔습니다. 매칭에만 사용되며 통계에는 포함되지 않습니다.",
"not_importable": "해당 없음",
"exists": "존재함"
},
"btn": {
"add_device": "+ 장치 추가",
@@ -169,7 +171,14 @@
"download_device": "이 설정 다운로드",
"share_device": "⬆ 이 기기 공유",
"share_device_tip": "이 가전제품과 기록된 참조 사이클을 커뮤니티 스토어에 공유하여 같은 기기를 가진 다른 사용자가 채택할 수 있도록 합니다",
"share_n": "{n}개 사이클 공유"
"share_n": "{n}개 사이클 공유",
"export_selected": "내보내기 (데이터 선택)",
"export_all": "모두 빠르게 내보내기",
"import_raw": "고급: JSON에서 전체 교체",
"download_export": "내보내기 다운로드",
"analyze_import": "파일 분석",
"import_selected": "선택 항목 가져오기",
"back": "뒤로"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "기본 탭",
"panel_language": "패널 언어",
"per_cycle_short": "사이클",
"pg_anti_wrinkle": "구김 방지",
"pg_autofill_tip": "현재 값에서 범위 자동 채우기 ({lo}{hi})",
"pg_best_value": "찾은 최적 값",
"pg_current_value": "현재 값:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "매개변수",
"pg_objective": "목표",
"pg_steps": "단계 수",
"pg_stress_group": "대기 상태 종료 테스트",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "대기 레벨 (W)",
"pg_stress_idle_w_desc": "자동 감지된 대기 전력 하한값을 재정의합니다(자동의 경우 빈칸으로 두세요).",
"pg_stress_toggle": "대기 상태 종료 테스트",
"pg_stress_toggle_desc": "녹화 종료 후 기기가 대기 전력 수준에서 계속 작동하는 상황을 시뮬레이션합니다 - WashData가 사이클을 중지하는지, 언제 중지하는지 확인합니다.",
"pg_sweep_2d": "2D 히트맵",
"pg_sweep_param_y": "두 번째 매개변수",
"pg_obj_match": "매칭 정확도",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "대화형 사이클 전력 그래프: 마우스를 올리면 시간/전력 표시, 스크롤하여 확대/축소, 드래그하여 이동",
"pg_alert_timeout_end": "예측이 아닌 타임아웃으로 종료됨",
"pg_alert_indefinite": "무한정 계속 실행됨",
"pg_alert_stress_cap": "안전 한도에 도달했습니다",
"pg_alert_stress_ok": "대기 상태 종료: 사이클이 중지되었습니다",
"pg_alert_stress_warn": "대기 전력이 정지 임계값을 초과합니다",
"task_pg_history": "기록으로 테스트",
"task_pg_sweep": "최적화",
"task_reprocess": "재처리",
@@ -566,7 +585,31 @@
"task_split": "사이클 분할 중",
"task_trim": "사이클 트리밍 중",
"task_merge": "사이클 병합 중",
"task_rebuild": "엔벨로프 재구성 중"
"task_rebuild": "엔벨로프 재구성 중",
"cat_profiles": "프로필(프로그램)",
"cat_real_cycles": "사이클(실행 기록)",
"cat_reference_cycles": "참조 사이클(가져옴)",
"cat_custom_phases": "사용자 지정 단계",
"cat_profile_groups": "프로필 그룹",
"cat_settings": "감지 및 인식 설정",
"cat_matching_config": "인식기 조정",
"cat_ml_models": "ML 모델",
"cat_feedback": "피드백 및 검토 라벨",
"cat_suggestions": "제안",
"cat_maintenance_log": "유지관리 로그",
"cat_history_logs": "기록 및 변경 로그",
"cat_lifetime_stats": "누적 합계",
"cycles_short": "사이클",
"import_mode": "결합 방법",
"mode_merge": "병합(내 것 유지)",
"mode_replace": "선택 항목 교체",
"cycle_destination": "가져온 사이클을 어디에 저장할까요?",
"dest_reference": "참조(형태만)",
"dest_real_history": "실제 기록(통계에 집계)",
"conflict_resolution": "이름 충돌",
"conflict_import_copy": "사본으로 가져오기",
"conflict_keep_mine": "내 것 유지",
"conflict_overwrite": "덮어쓰기"
},
"log": {
"all_levels": "모든 레벨",
@@ -643,7 +686,9 @@
"force_stop_msg": "지금 실행 중인 사이클을 강제 정지하시겠습니까? 사이클은 중단됨으로 저장됩니다.",
"store_import": "참조 주기 가져오기",
"store_share": "커뮤니티 스토어에 공유",
"store_share_device": "이 기기 공유"
"store_share_device": "이 기기 공유",
"export_select": "내보내기 - 데이터 선택",
"import_wizard": "가져오기 - 데이터 선택"
},
"msg": {
"appliance_monitor": "가전제품 모니터",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "유사한 미라벨 사이클 {count}개를 발견했습니다 – 프로필을 만들어 매칭을 시작하세요.",
"cycles_deleted": "{count}개 사이클이 삭제되었습니다",
"enough_data": "학습할 충분한 데이터가 있습니다({current}/{min}사이클)。",
"export_description": "모든 프로필 사이클을 JSON으로 내보내거나 이전 내보내기에서 복원합니다.",
"export_description": "JSON으로 내보낼 프로필, 사이클, 설정 등을 정확히 선택하거나, 파일을 분석하여 원하는 부분만 가져올 수 있습니다.",
"feedback_cycles_pending": "검토가 필요한 사이클 {n}개{s}",
"feedback_prompt": "그것이 옳았는지 확인하고, 프로그램을 수정하거나, 무시하십시오.",
"feedback_relabel_hint": "이 사이클을 다시 라벨 지정하면 이것도 해결됩니다.",
"filter_by_profile": "프로필로 필터링…",
"group_modal_help": "온도/회전이 다른 동일한 모양의 프로그램을 그룹화합니다(기간은 다를 수 있음). 매칭은 그룹을 하나의 후보로 평가한 다음 가장 적합한 멤버를 선택합니다. 최소 2개를 선택하세요. 오버레이는 얼마나 유사한지 보여줍니다.",
"group_not_cohesive": "이러한 프로필은 안정적으로 그룹화할 만큼 유사하지 않으므로 일치는 이상값을 제거하거나 그룹을 분할할 때까지 프로필을 개별적으로 처리합니다.",
@@ -729,6 +775,7 @@
"pg_restart_note": "시뮬레이션 도구를 활성화하려면 Home Assistant를 다시 시작하세요.",
"pg_sim_progress": "{done} / {total} 사이클",
"pg_starting": "시작 중…",
"pg_stress_synthetic": "대기 시뮬레이션이 여기서 시작됩니다",
"pg_sweep_intro": "{param}이(가) 달랐다면 어땠을까요? 가장 많은 사이클이 올바르게 매칭되는 설정을 찾기 위해 최근 {cycles}개 사이클에서 {steps}개 값을 테스트합니다.",
"pg_sweep_step": "단계 {done} / {total}",
"pg_undetected": "{n}개 사이클{s} 미감지",
@@ -837,6 +884,9 @@
"pg_simulating": "사이클을 시뮬레이션하는 중…",
"pg_canvas_empty2": "위에서 사이클을 선택하고 실행을 눌러 시뮬레이션하세요. 그런 다음 마우스를 올려 값을 확인하고, 스크롤하여 확대/축소하고, 드래그하여 이동할 수 있습니다.",
"pg_apply_settings_confirm": "이 {n}개 설정을 이 기기에 저장하시겠습니까? {list}",
"pg_stress_terminated_detail": "약 {idle}W 대기 상태로 안정되었습니다 -- {h}시간 {m}분 후 {reason}(으)로 인해 사이클이 종료되었습니다.",
"pg_stress_above_threshold_detail": "대기 전력 약 {idle}W가 유효 정지 임계값 ({stop}W) 이상입니다 -- 사이클이 한 번도 조용한 상태로 인식되지 않았습니다. 해결하려면 stop_threshold_w 값을 높이세요.",
"pg_stress_hit_cap_detail": "사이클이 멈추지 않고 {h}시간 {m}분 동안 실행되었습니다 -- 안전 한도에 의해 강제 중지되었습니다. 대기 전력 {idle}W 대 정지 임계값 {stop}W.",
"no_logs_match": "이 필터와 일치하는 로그 기록이 없습니다.",
"pg_analysis_hint2": "사이클을 선택하고 실행을 누르면 매칭 분석이 로드됩니다.",
"pg_analysis_empty2": "실행을 누르면 매칭 분석이 표시됩니다.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "선택한 참조 사이클과 함께 {brand} {model}을 업로드합니다. 같은 가전제품을 가진 다른 사용자가 프로그램을 채택할 수 있습니다. 항목은 공개 전에 검토됩니다.",
"share_profile_no_cycles": "참조 사이클 없음 -- 이 프로파일을 포함하려면 사이클 탭에서 사이클을 ⭐로 표시하세요",
"advisory_phase_inconsistent": "'{name}'은(는) 서로 다른 프로그램이나 온도가 섞여 있는 것 같습니다. 사이클마다 가열 시간이 크게 다릅니다. 온도별 등으로 별도의 프로필로 나누면 매칭과 시간 추정이 개선됩니다.",
"advisory_phase_inconsistent_title": "⚠ 프로그램이 섞였을 수 있음"
"advisory_phase_inconsistent_title": "⚠ 프로그램이 섞였을 수 있음",
"export_select_intro": "포함할 항목을 정확히 선택하세요. 사이클 없이 프로필만 선택해도 인식 가능한 프로그램은 내보내집니다(학습된 형태도 함께 이동합니다).",
"import_analyze_hint": "내보낸 파일을 불러오거나 해당 JSON을 붙여넣으세요. WashData가 이를 분석하여 변경 전에 가져올 수 있는 항목을 정확히 보여줍니다.",
"analyzing": "분석 중…",
"device_type_mismatch_warn": "이 내보내기는 다른 기기 유형에서 생성되었습니다({src} 대 {local}). 프로그램과 사이클은 참조 데이터로 가져올 수 있지만, 기기별 설정과 실제 기록 가져오기는 비활성화됩니다.",
"merge_hint": "가져온 항목이 추가되며 로컬 데이터는 손실되지 않습니다. 이름 충돌은 아래에서 해결합니다.",
"replace_warn": "선택한 각 범주는 삭제되고 파일 내용으로 교체됩니다. 선택하지 않은 범주는 그대로 유지됩니다.",
"dest_reference_hint": "가져온 사이클은 프로그램 인식만 개선하며 사용량/에너지 통계에는 영향을 주지 않습니다.",
"dest_real_history_hint": "가져온 사이클은 이 기기 자체의 기록으로 집계되어 에너지/사용량 통계에 반영됩니다. 기기 하나를 새 설치 환경으로 옮길 때 사용하세요."
},
"pg_desc": {
"completion_min_seconds": "실제 사이클로 인정되는 최단 실행",
@@ -875,6 +933,11 @@
"start_duration_threshold": "시작 확정을 위한 임계값 초과 초",
"start_threshold_w": "시작으로 간주할 최소 와트",
"stop_threshold_w": "이 값 미만이면 기기가 꺼진 것으로 간주",
"anti_wrinkle_enabled": "메인 단계 이후의 회전 펄스를 새 사이클로 인식하지 않고 흡수",
"anti_wrinkle_exit_power": "구김 방지가 활성 상태를 유지하려면 펄스 사이 전력이 이 값 아래로 떨어져야 함",
"anti_wrinkle_idle_timeout": "구김 방지가 끝나기 전 두 회전 펄스 사이에 허용되는 정지 시간",
"anti_wrinkle_max_duration": "이보다 긴 펄스는 구김 방지를 끝내고 새 사이클을 시작",
"anti_wrinkle_max_power": "이보다 높은 펄스는 구김 방지를 끝내고 새 사이클을 시작",
"dtw_bandwidth": "Stage 3: Sakoe-Chiba 워프 대역폭 (0 = DTW 비활성; 기본값 0.2 = 사이클 길이의 20%)",
"corr_weight": "Stage 2: 곡선 형태(상관관계)와 전력 수준(MAE) 사이의 균형; 기본값 0.45",
"duration_weight": "Stage 4: 실행 시간 일치가 최종 점수에 미치는 영향(기본값 0.22)",
@@ -985,6 +1048,10 @@
"doc": "주름 방지 모드가 활성 상태를 유지하려면 펄스 사이에 전력이 이 값 아래로 낮아져야 합니다.",
"label": "종료 전력 임계값"
},
"anti_wrinkle_idle_timeout": {
"label": "최대 펄스 간격",
"doc": "구김 방지 모드가 종료되기 전에 기기가 두 회전 펄스 사이에서 정지 상태로 있을 수 있는 시간입니다. 건조기가 펄스 사이에 남기는 가장 긴 간격보다 높게 설정하십시오. 그렇지 않으면 이후의 모든 펄스가 잘못된 시작으로 인식됩니다."
},
"anti_wrinkle_max_duration": {
"doc": "이보다 긴 펄스는 주름 방지 텀블이 아닌 실제 주기로 간주됩니다.",
"label": "최대 지속 시간"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n}개 사이클 공유됨; {failed}개는 업로드할 수 없었습니다.",
"store_device_shared_some_dup": "{created}개 사이클 공유됨; {dup}개는 이미 스토어에 있었습니다.",
"store_download_failed": "다운로드 실패: {error}",
"store_download_nothing": "다운로드할 새 항목 없음 - 이 설정은 이미 기기에 있습니다."
"store_download_nothing": "다운로드할 새 항목 없음 - 이 설정은 이미 기기에 있습니다.",
"export_selective_done": "내보내기 다운로드 완료",
"import_selective_done": "{profiles}개 프로필과 {cycles}개 사이클을 가져왔습니다"
},
"suggestion": {
"both_agree": "WashData 권장",
@@ -28,7 +28,9 @@
"awaiting_n": "Laukiama patvirtinimo · {n} patvirtinimai",
"approved": "Patvirtinta",
"awaiting": "Laukiama patvirtinimo",
"imported_tip": "Importuota iš bendruomenės parduotuvės. Naudojama tik atitikčiai, neįskaičiuojama į statistiką."
"imported_tip": "Importuota iš bendruomenės parduotuvės. Naudojama tik atitikčiai, neįskaičiuojama į statistiką.",
"not_importable": "čia nepasiekiama",
"exists": "jau yra"
},
"btn": {
"add_device": "+ Pridėti įrenginį",
@@ -169,7 +171,14 @@
"download_device": "Atsisiųsti šią konfigūraciją",
"share_device": "⬆ Dalintis šiuo įrenginiu",
"share_device_tip": "Bendrinkite šį prietaisą ir jo įrašytus etaloniniais ciklus bendruomenės parduotuvėje, kad kiti, turintys tą patį aparatą, galėtų juos perimti",
"share_n": "Bendrinti {n} ciklą(-us)"
"share_n": "Bendrinti {n} ciklą(-us)",
"export_selected": "Eksportuoti (pasirinkti duomenis)",
"export_all": "Greitasis eksportas: viskas",
"import_raw": "Išplėstinis: pakeisti viską iš JSON",
"download_export": "Atsisiųsti eksportą",
"analyze_import": "Analizuoti failą",
"import_selected": "Importuoti pasirinktus",
"back": "Atgal"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -291,6 +300,7 @@
"poor": "⚠ Prasta profilio kokybė"
},
"lbl": {
"pg_anti_wrinkle": "Apsauga nuo raukšlių",
"actions": "Veiksmai",
"activity": "Veikla",
"administrators": "Administratoriai",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parametras",
"pg_objective": "Tikslas",
"pg_steps": "Žingsniai",
"pg_stress_group": "Budėjimo nutraukimo testas",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Budėjimo lygis (W)",
"pg_stress_idle_w_desc": "Perrašyti automatiškai aptiktą budėjimo lygio apatinę ribą (palikite tuščią automatiniam).",
"pg_stress_toggle": "Testuoti budėjimo nutraukimą",
"pg_stress_toggle_desc": "Imituoja prietaisą, kuris po įrašymo pasibaigimo lieka budėjimo suvartojimo lygyje - parodo, ar ir kada WashData sustabdys ciklą.",
"pg_sweep_2d": "2D šilumos žemėlapis",
"pg_sweep_param_y": "Antras parametras",
"pg_obj_match": "Atitikties tikslumas",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Interaktyvus ciklo galios grafikas: užveskite žymeklį, kad nuskaitytumėte laiką/galią, slinkite, kad priartintumėte, vilkite, kad pastumtumėte",
"pg_alert_timeout_end": "Baigta pasibaigus skirtajam laikui, o ne pagal prognozę",
"pg_alert_indefinite": "Veiktų neribotą laiką",
"pg_alert_stress_cap": "Pasiektas apsauginis limitas",
"pg_alert_stress_ok": "Budėjimo nutraukimas: ciklas sustabdytas",
"pg_alert_stress_warn": "Budėjimo suvartojimas viršija sustabdymo slenkstį",
"task_pg_history": "Testuoti su istorija",
"task_pg_sweep": "Optimizuoti",
"task_reprocess": "Pakartotinis apdorojimas",
@@ -566,7 +585,31 @@
"task_split": "Padalijamas ciklas",
"task_trim": "Apkarpomas ciklas",
"task_merge": "Sujungiami ciklai",
"task_rebuild": "Perkuriamos gaubtinės"
"task_rebuild": "Perkuriamos gaubtinės",
"cat_profiles": "Profiliai (programos)",
"cat_real_cycles": "Ciklai (naudojimo istorija)",
"cat_reference_cycles": "Atskaitos ciklai (importuoti)",
"cat_custom_phases": "Pasirinktinės fazės",
"cat_profile_groups": "Profilių grupės",
"cat_settings": "Aptikimo ir atpažinimo nustatymai",
"cat_matching_config": "Atpažinimo derinimas",
"cat_ml_models": "ML modeliai",
"cat_feedback": "Atsiliepimų ir peržiūros žymos",
"cat_suggestions": "Pasiūlymai",
"cat_maintenance_log": "Priežiūros žurnalas",
"cat_history_logs": "Istorijos ir pakeitimų žurnalai",
"cat_lifetime_stats": "Viso laiko sumos",
"cycles_short": "ciklų",
"import_mode": "Kaip sujungti",
"mode_merge": "Sujungti (išlaikyti manuosius)",
"mode_replace": "Pakeisti pasirinktus",
"cycle_destination": "Kur turėtų patekti importuoti ciklai?",
"dest_reference": "Atskaita (tik forma)",
"dest_real_history": "Tikra istorija (įskaičiuojama į statistiką)",
"conflict_resolution": "Pavadinimų konfliktai",
"conflict_import_copy": "Importuoti kaip kopiją",
"conflict_keep_mine": "Išlaikyti manuosius",
"conflict_overwrite": "Perrašyti"
},
"log": {
"all_levels": "Visi lygiai",
@@ -643,7 +686,9 @@
"force_stop_msg": "Priverstinai sustabdyti aktyvų ciklą dabar? Ciklas bus išsaugotas kaip nutrauktas.",
"store_import": "Importuoti etaloninį ciklą",
"store_share": "Bendrinti bendruomenės parduotuvėje",
"store_share_device": "Bendrinti šį įrenginį"
"store_share_device": "Bendrinti šį įrenginį",
"export_select": "Eksportas - pasirinkti duomenis",
"import_wizard": "Importas - pasirinkti duomenis"
},
"msg": {
"appliance_monitor": "Prietaisų monitorius",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Rasti {count} panašūs nepažymėti ciklai sukurkite profilį, kad pradėtumėte juos atpažinti.",
"cycles_deleted": "Ištrinta ciklų: {count}",
"enough_data": "Pakankamai duomenų mokymui ({current}/{min} ciklų).",
"export_description": "Eksportuokite visus profilius ir ciklus į JSON arba atkurkite iš ankstesnio eksportavimo.",
"export_description": "Pasirinkite tiksliai, kuriuos profilius, ciklus, nustatymus ir kita eksportuoti į JSON, arba išanalizuokite failą ir importuokite tik norimas dalis.",
"feedback_cycles_pending": "{n} cikl{s} peržiūrai",
"feedback_prompt": "Patvirtinkite, kad tai buvo teisinga, pataisykite programą arba ignoruokite.",
"feedback_relabel_hint": "Šio ciklo pažymėjimas iš naujo taip pat išsprendžia peržiūrą.",
"filter_by_profile": "Filtruoti pagal profilį…",
"group_modal_help": "Grupuoti tos pačios formos programas, kurios skiriasi temperatūra / sukimu (trukmė gali skirtis). Atitikimas įvertina grupę kaip vieną kandidatą, tada parenka geriausiai tinkantį narį. Pasirinkite bent 2; perdanga parodo, kokie jie panašūs.",
"group_not_cohesive": "Šie profiliai nėra pakankamai panašūs, kad būtų galima patikimai sugrupuoti, todėl derinant jie traktuojami atskirai, kol pašalinsite nuokrypį arba padalinsite grupę.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Iš naujo paleiskite „Home Assistant“, kad įjungtumėte simuliavimo įrankius.",
"pg_sim_progress": "{done} / {total} ciklai",
"pg_starting": "Pradedama…",
"pg_stress_synthetic": "budėjimo imitacija prasideda čia",
"pg_sweep_intro": "O kas, jei {param} būtų kitokia? Išbandykite {steps} reikšmes paskutiniuose {cycles} cikluose ir raskite nustatymą, su kuriuo teisingai atitinka daugiausiai ciklų.",
"pg_sweep_step": "Žingsnis {done} / {total}",
"pg_undetected": "Neaptikta ciklų: {n}",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuliuojamas ciklas…",
"pg_canvas_empty2": "Pasirinkite ciklą aukščiau ir paspauskite Vykdyti, kad jį simuliuotumėte. Tada užveskite žymeklį, kad nuskaitytumėte reikšmes, slinkite, kad priartintumėte, ir vilkite, kad pastumtumėte.",
"pg_apply_settings_confirm": "Ar išsaugoti šiuos {n} nustatymus šiame įrenginyje? {list}",
"pg_stress_terminated_detail": "Nusistovėjo ties maždaug {idle} W budėjimo lygiu -- ciklas baigėsi po {h} val. {m} min. dėl {reason}.",
"pg_stress_above_threshold_detail": "Budėjimo suvartojimas (~{idle} W) yra ties sustabdymo slenksčiu ({stop} W) arba jį viršija -- ciklas niekada nebuvo užregistruotas kaip tylus. Norėdami pataisyti, padidinkite stop_threshold_w.",
"pg_stress_hit_cap_detail": "Ciklas veikė {h} val. {m} min. nesustodamas -- priverstinai sustabdytas apsauginio limito. Budėjimo suvartojimas {idle} W, palyginti su sustabdymo slenksčiu {stop} W.",
"no_logs_match": "Nė vienas žurnalo įrašas neatitinka šių filtrų.",
"pg_analysis_hint2": "Pasirinkite ciklą ir paspauskite Vykdyti, kad įkeltumėte atitikties analizę.",
"pg_analysis_empty2": "Paspauskite Vykdyti, kad pamatytumėte atitikties analizę.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Įkelkite {brand} {model} su pasirinktais etaloniniais ciklais. Kiti, turintys tą patį prietaisą, gali perimti jūsų programas. Įrašai peržiūrimi prieš viešinant.",
"share_profile_no_cycles": "Nėra etaloninių ciklų -- pažymėkite ciklą kaip ⭐ Ciklų skirtuke, kad įtrauktumėte šį profilį",
"advisory_phase_inconsistent": "Atrodo, kad '{name}' maišo skirtingas programas ar temperatūras - jo ciklai kaista labai skirtingą laiką. Padalijus jį į atskirus profilius (pvz., pagal temperatūrą), pagerės atitikimas ir laiko įverčiai.",
"advisory_phase_inconsistent_title": "⚠ Galimai sumaišytos programos"
"advisory_phase_inconsistent_title": "⚠ Galimai sumaišytos programos",
"export_select_intro": "Pažymėkite tiksliai, ką įtraukti. Pasirinkus profilius be jų ciklų, vis tiek eksportuojama atpažįstama programa (jos išmokta forma keliauja kartu).",
"import_analyze_hint": "Įkelkite eksportuotą failą (arba įklijuokite jo JSON). WashData jį išanalizuoja ir tiksliai parodo, ką galima importuoti, prieš ką nors keičiant.",
"analyzing": "Analizuojama…",
"device_type_mismatch_warn": "Šis eksportas yra iš kito prietaiso tipo ({src} vs {local}). Programas ir ciklus vis tiek galima importuoti kaip atskaitos duomenis, tačiau konkrečiam prietaisui skirti nustatymai ir tikros istorijos importas yra išjungti.",
"merge_hint": "Importuoti elementai pridedami; jokie vietiniai duomenys neprarandami. Pavadinimų konfliktai sprendžiami toliau.",
"replace_warn": "Kiekviena pažymėta kategorija ištrinama ir pakeičiama iš failo. Nepažymėtos kategorijos lieka nepaliestos.",
"dest_reference_hint": "Importuoti ciklai tik pagerina programų atpažinimą ir niekada neveikia naudojimo/energijos statistikos.",
"dest_real_history_hint": "Importuoti ciklai laikomi šio prietaiso paties istorija ir papildo energijos/naudojimo statistiką. Naudokite perkeliant vieną prietaisą į naują įdiegimą."
},
"phase_desc": {
"anti_crease": "Po pagrindinio ciklo būgnas intermitiškai sukasi, kad išvengtų audinių raukšlių.",
@@ -962,6 +1020,10 @@
"doc": "Kad raukšlių mažinimo režimas išliktų aktyvus, tarp impulsų galia turi būti mažesnė.",
"label": "Išėjimo galios slenkstis"
},
"anti_wrinkle_idle_timeout": {
"doc": "Kiek ilgai mašina gali tylėti tarp dviejų būgninių impulsų, kol baigiasi apsaugos nuo raukšlių režimas. Nustatykite ilgesnį nei ilgiausias tarpas, kurį jūsų džiovyklė palieka tarp impulsų, antraip kiekvienas vėlesnis impulsas bus laikomas klaidingu paleidimu.",
"label": "Maks. tarpas tarp impulsų"
},
"anti_wrinkle_max_duration": {
"doc": "Ilgesni impulsai laikomi tikru ciklu, o ne raukšlių mažinimu.",
"label": "Maksimali trukmė"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} ciklai bendrinti; {failed} nepavyko įkelti.",
"store_device_shared_some_dup": "{created} ciklai bendrinti; {dup} jau buvo parduotuvėje.",
"store_download_failed": "Atsisiuntimas nepavyko: {error}",
"store_download_nothing": "Nieko naujo atsisiųsti -- ši konfigūracija jau yra jūsų įrenginyje."
"store_download_nothing": "Nieko naujo atsisiųsti -- ši konfigūracija jau yra jūsų įrenginyje.",
"export_selective_done": "Eksportas atsisiųstas",
"import_selective_done": "Importuota {profiles} profilių ir {cycles} ciklų"
},
"suggestion": {
"both_agree": "WashData rekomenduoja",
@@ -1706,6 +1770,11 @@
"other": "Kita"
},
"pg_desc": {
"anti_wrinkle_enabled": "Sugerkite būgninius impulsus po pagrindinės fazės, o ne skaitykite juos kaip naujus ciklus",
"anti_wrinkle_exit_power": "Kad apsauga nuo raukšlių išliktų aktyvi, galia tarp impulsų turi nukristi žemiau šios ribos",
"anti_wrinkle_idle_timeout": "Tylos laikas, leidžiamas tarp dviejų būgninių impulsų prieš pasibaigiant apsaugai nuo raukšlių",
"anti_wrinkle_max_duration": "Ilgesnis nei šis impulsas užbaigia apsaugą nuo raukšlių ir pradeda naują ciklą",
"anti_wrinkle_max_power": "Už šią didesnis impulsas užbaigia apsaugą nuo raukšlių ir pradeda naują ciklą",
"completion_min_seconds": "Trumpiausias veikimas, laikomas tikru ciklu",
"end_repeat_count": "Kiek žemų rodmenų iš eilės prieš pabaigą",
"interrupted_min_seconds": "Trumpi ciklai pažymimi kaip pertraukti",
@@ -28,7 +28,9 @@
"awaiting_n": "Gaida apstiprinājumu · {n} apstiprinājumi",
"approved": "Apstiprināts",
"awaiting": "Gaida apstiprinājumu",
"imported_tip": "Importēts no kopienas veikala. Izmanto tikai saskaņošanai, netiek ieskaitīts statistikā."
"imported_tip": "Importēts no kopienas veikala. Izmanto tikai saskaņošanai, netiek ieskaitīts statistikā.",
"not_importable": "šeit nav pieejams",
"exists": "jau eksistē"
},
"btn": {
"add_device": "+ Pievienot ierīci",
@@ -169,7 +171,14 @@
"download_device": "Lejupielādēt šo iestatījumu",
"share_device": "⬆ Kopīgot šo ierīci",
"share_device_tip": "Kopīgojiet šo ierīci un tās reģistrētos atskaites ciklus kopienas veikalā, lai citi ar to pašu mašīnu varētu tos pieņemt",
"share_n": "Kopīgot {n} ciklu(-s)"
"share_n": "Kopīgot {n} ciklu(-s)",
"export_selected": "Eksportēt (izvēlēties datus)",
"export_all": "Ātrā eksportēšana: viss",
"import_raw": "Papildu: aizstāt visu no JSON",
"download_export": "Lejupielādēt eksportu",
"analyze_import": "Analizēt failu",
"import_selected": "Importēt atlasītos",
"back": "Atpakaļ"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -291,6 +300,7 @@
"poor": "⚠ Slikta profila kvalitāte"
},
"lbl": {
"pg_anti_wrinkle": "Pretgrumbu režīms",
"actions": "Darbības",
"activity": "Aktivitāte",
"administrators": "Administratori",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parametrs",
"pg_objective": "Mērķis",
"pg_steps": "Soļi",
"pg_stress_group": "Dīkstāves apturēšanas tests",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Dīkstāves līmenis (W)",
"pg_stress_idle_w_desc": "Pārrakstīt automātiski noteikto gaidstāves jaudas apakšrobežu (atstājiet tukšu automātiskai).",
"pg_stress_toggle": "Pārbaudīt dīkstāves apturēšanu",
"pg_stress_toggle_desc": "Simulē ierīci, kas paliek gaidstāves patēriņa līmenī pēc ierakstīšanas beigām - rāda, vai un kad WashData aptur ciklu.",
"pg_sweep_2d": "2D siltuma karte",
"pg_sweep_param_y": "2. parametrs",
"pg_obj_match": "Atbilstības precizitāte",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Interaktīva cikla jaudas diagramma: turiet kursoru virs, lai nolasītu laiku/jaudu, ritiniet, lai tuvinātu, velciet, lai pārvietotu",
"pg_alert_timeout_end": "Beidzās pēc noildzes, nevis prognozes",
"pg_alert_indefinite": "Darbotos bezgalīgi",
"pg_alert_stress_cap": "Sasniegts drošības ierobežojums",
"pg_alert_stress_ok": "Dīkstāves apturēšana: cikls apturēts",
"pg_alert_stress_warn": "Dīkstāves patēriņš pārsniedz apstāšanās slieksni",
"task_pg_history": "Pārbaudīt vēsturē",
"task_pg_sweep": "Optimizēt",
"task_reprocess": "Atkārtota apstrāde",
@@ -566,7 +585,31 @@
"task_split": "Cikla sadalīšana",
"task_trim": "Cikla apgriešana",
"task_merge": "Ciklu apvienošana",
"task_rebuild": "Apvalklīkņu pārbūve"
"task_rebuild": "Apvalklīkņu pārbūve",
"cat_profiles": "Profili (programmas)",
"cat_real_cycles": "Cikli (lietojuma vēsture)",
"cat_reference_cycles": "Atsauces cikli (importēti)",
"cat_custom_phases": "Pielāgotās fāzes",
"cat_profile_groups": "Profilu grupas",
"cat_settings": "Noteikšanas un atpazīšanas iestatījumi",
"cat_matching_config": "Atpazīšanas pielāgošana",
"cat_ml_models": "ML modeļi",
"cat_feedback": "Atsauksmju un pārskatīšanas atzīmes",
"cat_suggestions": "Ieteikumi",
"cat_maintenance_log": "Apkopes žurnāls",
"cat_history_logs": "Vēstures un izmaiņu žurnāli",
"cat_lifetime_stats": "Visa laika kopsummas",
"cycles_short": "cikli",
"import_mode": "Kā apvienot",
"mode_merge": "Apvienot (paturēt manējos)",
"mode_replace": "Aizstāt atlasītos",
"cycle_destination": "Kur jānonāk importētajiem cikliem?",
"dest_reference": "Atsauce (tikai forma)",
"dest_real_history": "Reālā vēsture (ieskaita statistikā)",
"conflict_resolution": "Nosaukumu konflikti",
"conflict_import_copy": "Importēt kā kopiju",
"conflict_keep_mine": "Paturēt manējos",
"conflict_overwrite": "Pārrakstīt"
},
"log": {
"all_levels": "Visi līmeņi",
@@ -643,7 +686,9 @@
"force_stop_msg": "Piespiedu kārtā apturēt aktīvo ciklu tagad? Cikls tiks saglabāts kā pārtraukts.",
"store_import": "Importēt atsauces ciklu",
"store_share": "Kopīgot kopienas veikalā",
"store_share_device": "Kopīgot šo ierīci"
"store_share_device": "Kopīgot šo ierīci",
"export_select": "Eksports - izvēlēties datus",
"import_wizard": "Imports - izvēlēties datus"
},
"msg": {
"appliance_monitor": "Ierīces monitors",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Atrasti {count} līdzīgi nemarķēti cikli izveidojiet profilu, lai sāktu tos saskaņot.",
"cycles_deleted": "Dzēsti cikli: {count}",
"enough_data": "Pietiek datu mācīšanai ({current}/{min} cikli).",
"export_description": "Eksportējiet visus profilus un ciklus uz JSON vai atjaunojiet no iepriekšējās eksportēšanas.",
"export_description": "Izvēlieties precīzi, kurus profilus, ciklus, iestatījumus un citus datus eksportēt uz JSON, vai analizējiet failu un importējiet tikai vēlamās daļas.",
"feedback_cycles_pending": "{n} cikl{s} pārskatīšanai",
"feedback_prompt": "Apstipriniet, ka tas bija pareizi, labojiet programmu vai ignorējiet.",
"feedback_relabel_hint": "Šī cikla atkārtota marķēšana arī to atrisina.",
"filter_by_profile": "Filtrēt pēc profila…",
"group_modal_help": "Grupējiet programmas ar vienādu formu, kas atšķiras pēc temperatūras/griešanās (ilgums var atšķirties). Atbilstības gadījumā grupa tiek vērtēta kā viens kandidāts, pēc tam tiek izvēlēts atbilstošākais dalībnieks. Izvēlieties vismaz 2; pārklājums parāda, cik tie ir līdzīgi.",
"group_not_cohesive": "Šie profili nav pietiekami līdzīgi, lai tos varētu droši grupēt, tāpēc saskaņošanas funkcija tos apstrādā atsevišķi, līdz noņemat novirzi vai sadalāt grupu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Restartējiet Home Assistant, lai iespējotu simulācijas rīkus.",
"pg_sim_progress": "{done} / {total} cikli",
"pg_starting": "Sākas…",
"pg_stress_synthetic": "dīkstāves simulācija sākas šeit",
"pg_sweep_intro": "Kā būtu, ja {param} atšķirtos? Pārbaudiet {steps} vērtības pēdējos {cycles} ciklos, lai atrastu iestatījumu, kurā pareizi tiek saskaņoti visvairāk ciklu.",
"pg_sweep_step": "Solis {done} / {total}",
"pg_undetected": "Neatklāti cikli: {n}",
@@ -837,6 +884,9 @@
"pg_simulating": "Notiek cikla simulācija…",
"pg_canvas_empty2": "Izvēlieties ciklu augšā un nospiediet Palaist, lai to simulētu. Pēc tam turiet kursoru virs, lai nolasītu vērtības, ritiniet, lai tuvinātu, un velciet, lai pārvietotu.",
"pg_apply_settings_confirm": "Vai saglabāt šos {n} iestatījumus šajā ierīcē? {list}",
"pg_stress_terminated_detail": "Nostabilizējās aptuveni {idle} W dīkstāves līmenī -- cikls beidzās {h} h {m} min vēlāk {reason} dēļ.",
"pg_stress_above_threshold_detail": "Dīkstāves patēriņš (~{idle} W) ir vienāds ar faktisko apstāšanās slieksni ({stop} W) vai to pārsniedz -- cikls nekad netika reģistrēts kā kluss. Lai to labotu, palieliniet stop_threshold_w.",
"pg_stress_hit_cap_detail": "Cikls darbojās {h} h {m} min bez apstāšanās -- to piespiedu kārtā apturēja drošības ierobežojums. Dīkstāves patēriņš {idle} W pret apstāšanās slieksni {stop} W.",
"no_logs_match": "Neviens žurnāla ieraksts neatbilst šiem filtriem.",
"pg_analysis_hint2": "Atlasiet ciklu un nospiediet Palaist, lai ielādētu atbilstības analīzi.",
"pg_analysis_empty2": "Nospiediet Palaist, lai skatītu atbilstības analīzi.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Augšupielādējiet {brand} {model} ar atlasītajiem atskaites cikliem. Citi ar to pašu ierīci var pieņemt jūsu programmas. Ieraksti tiek pārskatīti pirms publiskošanas.",
"share_profile_no_cycles": "Nav atskaites ciklu -- atzīmējiet ciklu kā ⭐ cilnē Cikli, lai iekļautu šo profilu",
"advisory_phase_inconsistent": "Šķiet, ka '{name}' sajauc dažādas programmas vai temperatūras - tā cikli sildās ļoti atšķirīgu laiku. Sadalot to atsevišķos profilos (piemēram, pēc temperatūras), tiks uzlabota saskaņošana un laika aprēķini.",
"advisory_phase_inconsistent_title": "⚠ Iespējams, jauktas programmas"
"advisory_phase_inconsistent_title": "⚠ Iespējams, jauktas programmas",
"export_select_intro": "Atzīmējiet precīzi, ko iekļaut. Izvēloties profilus bez to cikliem, joprojām tiek eksportēta atpazīstama programma (tās apgūtā forma ceļo līdzi).",
"import_analyze_hint": "Ielādējiet eksportēto failu (vai ielīmējiet tā JSON). WashData to analizē un precīzi parāda, ko var importēt, pirms kaut kas mainās.",
"analyzing": "Notiek analīze…",
"device_type_mismatch_warn": "Šis eksports ir no cita ierīces tipa ({src} vs {local}). Programmas un ciklus joprojām var importēt kā atsauces datus, taču ierīcei specifiskie iestatījumi un reālās vēstures imports ir atspējoti.",
"merge_hint": "Importētie vienumi tiek pievienoti; nekas no vietējiem datiem netiek zaudēts. Nosaukumu konflikti tiek risināti tālāk.",
"replace_warn": "Katra atzīmētā kategorija tiek dzēsta un aizstāta no faila. Neatzīmētās kategorijas paliek neskartas.",
"dest_reference_hint": "Importētie cikli tikai uzlabo programmu atpazīšanu un nekad neietekmē lietojuma/enerģijas statistiku.",
"dest_real_history_hint": "Importētie cikli tiek uzskaitīti kā šīs ierīces pašas vēsture un papildina enerģijas/lietojuma statistiku. Izmantojiet, pārvietojot vienu ierīci uz jaunu instalāciju."
},
"phase_desc": {
"anti_crease": "Laiku pa laikam pēc pabeigšanas veiciet īsus gājienus, lai samazinātu grumbiņas.",
@@ -962,6 +1020,10 @@
"doc": "Lai pretgrumbu režīms paliktu aktīvs, starp impulsiem jaudai ir jāsamazinās zem šī.",
"label": "Izejas jaudas slieksnis"
},
"anti_wrinkle_idle_timeout": {
"doc": "Cik ilgi mašīna drīkst palikt klusa starp diviem griešanās impulsiem, pirms pretgrumbu režīms beidzas. Iestatiet to garāku par garāko atstarpi, ko jūsu žāvētājs atstāj starp impulsiem, citādi katrs nākamais impulss tiek uztverts kā kļūdains starts.",
"label": "Maks. atstarpe starp impulsiem"
},
"anti_wrinkle_max_duration": {
"doc": "Pākšaugi, kas ir garāki par šo, tiek uzskatīti par īstu ciklu, nevis kā pretgrumbu izkrišanu.",
"label": "Maks. ilgums"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} cikli kopīgoti; {failed} neizdevās augšupielādēt.",
"store_device_shared_some_dup": "{created} cikli kopīgoti; {dup} jau bija veikalā.",
"store_download_failed": "Lejupielāde neizdevās: {error}",
"store_download_nothing": "Nav nekā jauna lejupielādei - šis iestatījums jau ir jūsų ierīcē."
"store_download_nothing": "Nav nekā jauna lejupielādei - šis iestatījums jau ir jūsų ierīcē.",
"export_selective_done": "Eksports lejupielādēts",
"import_selective_done": "Importēti {profiles} profili un {cycles} cikli"
},
"suggestion": {
"both_agree": "WashData iesaka",
@@ -1706,6 +1770,11 @@
"other": "Cits"
},
"pg_desc": {
"anti_wrinkle_enabled": "Absorbējiet griešanās impulsus pēc galvenās fāzes, nevis nolasiet tos kā jaunus ciklus",
"anti_wrinkle_exit_power": "Lai pretgrumbu režīms paliktu aktīvs, jaudai starp impulsiem jānokrītas zem šīs vērtības",
"anti_wrinkle_idle_timeout": "Atļautais klusuma laiks starp diviem griešanās impulsiem, pirms pretgrumbu režīms beidzas",
"anti_wrinkle_max_duration": "Impulss, kas garāks par šo, beidz pretgrumbu režīmu un sāk jaunu ciklu",
"anti_wrinkle_max_power": "Impulss, kas pārsniedz šo, beidz pretgrumbu režīmu un sāk jaunu ciklu",
"completion_min_seconds": "Īsākais darbības laiks, kas skaitās par īstu ciklu",
"end_repeat_count": "Zemi rādījumi pēc kārtas pirms beigām",
"interrupted_min_seconds": "Īsi cikli tiek atzīmēti kā pārtraukti",
@@ -28,7 +28,9 @@
"awaiting_n": "Чека одобрување · {n} потврдени",
"approved": "Одобрено",
"awaiting": "Чека одобрување",
"imported_tip": "Увезено од продавницата на заедницата. Се користи само за совпаѓање, не се брои во статистиката."
"imported_tip": "Увезено од продавницата на заедницата. Се користи само за совпаѓање, не се брои во статистиката.",
"not_importable": "неприменливо",
"exists": "постои"
},
"btn": {
"add_device": "+ Додај уред",
@@ -169,7 +171,14 @@
"download_device": "Преземи поставки на уредот",
"share_device": "Сподели поставки на уредот",
"share_device_tip": "Споделете ги програмите и поставките на овој уред со заедницата",
"share_n": "Сподели {n} програм{s}"
"share_n": "Сподели {n} програм{s}",
"export_selected": "Извоз (избор на податоци)",
"export_all": "Брз извоз на сè",
"import_raw": "Напредно: замени сè од JSON",
"download_export": "Преземи извоз",
"analyze_import": "Анализирај датотека",
"import_selected": "Увези избрано",
"back": "Назад"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,7 @@
"pg_group_detection": "Тригери за откривање",
"pg_group_timing": "Правила за време",
"pg_idle": "Неактивен",
"pg_anti_wrinkle": "Против брчки",
"pg_last_run": "Последно извршување: {date}",
"pg_match_score": "Оценка на совпаѓање",
"pg_pct_of": "{p}% од {total}",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Интерактивен график на моќноста на циклусот: посочете за да видите време/моќност, лизгајте за зумирање, влечете за поместување",
"pg_alert_timeout_end": "Завршено поради истек на времето, а не според предвидување",
"pg_alert_indefinite": "Би работел бесконечно",
"pg_alert_stress_cap": "Достигнат заштитен лимит",
"pg_alert_stress_ok": "Откривање на мирување: циклусот е запрен",
"pg_alert_stress_warn": "Потрошувачката во мирување е над прагот за запирање",
"pg_stress_group": "Тест на откривање на мирување",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Ниво на мирување (W)",
"pg_stress_idle_w_desc": "Замени го автоматски откривениот минимален праг на потрошувачка во режим на мирување (оставете празно за автоматско откривање).",
"pg_stress_toggle": "Тестирај откривање на мирување",
"pg_stress_toggle_desc": "Симулира дека уредот останува при потрошувачката во режим на мирување по завршувањето на снимањето -- прикажува дали и кога WashData ќе го запре циклусот.",
"task_pg_history": "Тест на историја",
"task_pg_sweep": "Оптимизирај",
"task_reprocess": "Повторна обработка",
@@ -566,7 +585,31 @@
"task_split": "Разделување на циклус",
"task_trim": "Отсекување на циклус",
"task_merge": "Спојување на циклуси",
"task_rebuild": "Повторно градење на обвивки"
"task_rebuild": "Повторно градење на обвивки",
"cat_profiles": "Профили (програми)",
"cat_real_cycles": "Циклуси (историја на извршување)",
"cat_reference_cycles": "Референтни циклуси (увезени)",
"cat_custom_phases": "Прилагодени фази",
"cat_profile_groups": "Групи профили",
"cat_settings": "Поставки за детекција и препознавање",
"cat_matching_config": "Фино прилагодување на препознавањето",
"cat_ml_models": "ML модели",
"cat_feedback": "Повратни информации и ознаки за преглед",
"cat_suggestions": "Предлози",
"cat_maintenance_log": "Дневник за одржување",
"cat_history_logs": "Дневници за историја и промени",
"cat_lifetime_stats": "Вкупни суми за целиот период",
"cycles_short": "циклуси",
"import_mode": "Како да се комбинира",
"mode_merge": "Спој (задржи ги моите)",
"mode_replace": "Замени избрано",
"cycle_destination": "Каде треба да одат увезените циклуси?",
"dest_reference": "Референтни (само форма)",
"dest_real_history": "Реална историја (се брои во статистиката)",
"conflict_resolution": "Конфликти на имиња",
"conflict_import_copy": "Увези како копија",
"conflict_keep_mine": "Задржи ги моите",
"conflict_overwrite": "Презапиши"
},
"log": {
"all_levels": "Сите нивоа",
@@ -643,7 +686,9 @@
"force_stop_msg": "Присилно да се стопира активниот циклус сега? Циклусот ќе се зачува како прекинат.",
"store_import": "Увоз на референтен циклус",
"store_share": "Сподели во продавницата на заедницата",
"store_share_device": "Сподели поставки на уредот"
"store_share_device": "Сподели поставки на уредот",
"export_select": "Извоз - избор на податоци",
"import_wizard": "Увоз - избор на податоци"
},
"msg": {
"appliance_monitor": "Монитор на апаратот",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} слични необележани циклуси пронајдени – направете профил за да започнете да ги совпаѓате.",
"cycles_deleted": "Избришани се {count} циклуси",
"enough_data": "Доволно податоци за учење ({current}/{min} циклуси).",
"export_description": "Извезете ги сите профили и циклуси во JSON или вратете ги од претходен извоз.",
"export_description": "Изберете точно кои профили, циклуси, поставки и друго да се извезат во JSON, или анализирајте датотека и увезете само деловите што ги сакате.",
"feedback_cycles_pending": "{n} циклус{s} за преглед",
"feedback_prompt": "Потврдете дека е точно, поправете ја програмата или игнорирајте.",
"feedback_relabel_hint": "Повторното етикетирање на овој циклус исто така го решава.",
"filter_by_profile": "Филтрирај по профил…",
"group_modal_help": "Групирајте програми со иста форма што се разликуваат по температура/вртење (времетраењето може да варира). Со совпаѓање се добиваат бодови на групата како еден кандидат, а потоа се избира членот кој најдобро одговара. Изберете најмалку 2; преклопот покажува колку се слични.",
"group_not_cohesive": "Овие профили не се доволно слични за да се групираат веродостојно, така што совпаѓањето ги третира поединечно додека не го отстраните оддалеченото или не ја поделите групата.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Рестартирајте го Home Assistant за да ги овозможите алатките за симулација.",
"pg_sim_progress": "{done} / {total} циклуси",
"pg_starting": "Започнување…",
"pg_stress_synthetic": "симулацијата на мирување почнува тука",
"pg_sweep_intro": "Што ако {param} беше поинаков? Тестирајте {steps} вредности врз вашите последни {cycles} циклуси за да ја пронајдете поставката каде што најмногу циклуси се совпаѓаат правилно.",
"pg_sweep_step": "Чекор {done} / {total}",
"pg_undetected": "{n} неоткриен циклус{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "Симулирање на циклус…",
"pg_canvas_empty2": "Изберете циклус погоре и притиснете Стартувај за да го симулирате. Потоа посочете за да ги видите вредностите, лизгајте за зумирање и влечете за поместување.",
"pg_apply_settings_confirm": "Да се зачуваат ли овие {n} поставки на овој уред? {list}",
"pg_stress_terminated_detail": "Се стабилизира на ~{idle}W мирување -- циклусот заврши {h} ч {m} мин подоцна преку {reason}.",
"pg_stress_above_threshold_detail": "Потрошувачката во мирување ~{idle}W е еднаква или над ефективниот праг за запирање ({stop}W) -- циклусот никогаш не беше регистриран како мирен. За да го поправите ова, зголемете го stop_threshold_w.",
"pg_stress_hit_cap_detail": "Циклусот работеше {h} ч {m} мин без да запре -- принудно запрен од заштитниот лимит. Потрошувачка во мирување {idle}W наспроти прагот за запирање {stop}W.",
"no_logs_match": "Ниту еден запис од дневникот не одговара на овие филтри.",
"pg_analysis_hint2": "Изберете циклус и притиснете Изврши за да се вчита анализата на совпаѓање.",
"pg_analysis_empty2": "Притиснете Изврши за да ја видите анализата на совпаѓање.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Споделете ги програмите на вашиот уред (профили + референтни циклуси) со заедницата. Поставките се опционални.",
"share_profile_no_cycles": "Профилот '{p}' нема ⭐ референтни циклуси -- ќе биде прескокнат освен ако немате {n}+ потврдени стартувања",
"advisory_phase_inconsistent": "Изгледа дека '{name}' меша различни програми или температури - неговите циклуси се загреваат многу различно долго. Поделбата на посебни профили (на пр. по температура) ќе ги подобри совпаѓањето и проценките на времето.",
"advisory_phase_inconsistent_title": "⚠ Можеби измешани програми"
"advisory_phase_inconsistent_title": "⚠ Можеби измешани програми",
"export_select_intro": "Означете што точно да се вклучи. Изборот на профили без нивните циклуси сепак извезува препознатлива програма (нејзината научена форма се пренесува заедно со неа).",
"import_analyze_hint": "Вчитајте извезена датотека (или залепете го нејзиниот JSON). WashData ја анализира и покажува што точно може да се увезе пред да се промени нешто.",
"analyzing": "Анализирање…",
"device_type_mismatch_warn": "Овој извоз е од друг тип уред ({src} наместо {local}). Програмите и циклусите сепак можат да се увезат како референтни податоци, но поставките специфични за уредот и увозот на реална историја се оневозможени.",
"merge_hint": "Увезените ставки се додаваат; ништо локално не се губи. Конфликтите на имиња се решаваат подолу.",
"replace_warn": "Секоја означена категорија се брише и се заменува со податоците од датотеката. Неозначените категории остануваат непроменети.",
"dest_reference_hint": "Увезените циклуси само го подобруваат препознавањето на програми и никогаш не влијаат на статистиката за користење/енергија.",
"dest_real_history_hint": "Увезените циклуси се сметаат за сопствена историја на овој уред и ја хранат статистиката за енергија/користење. Користете за преместување на еден уред на нова инсталација."
},
"phase_desc": {
"anti_crease": "Повремени кратки превртувања по завршувањето за да се намалат брчките.",
@@ -962,6 +1020,10 @@
"doc": "Моќта мора да падне под ова помеѓу импулсите за режимот против брчки да остане активен.",
"label": "Праг на моќност за излез"
},
"anti_wrinkle_idle_timeout": {
"label": "Макс. пауза меѓу импулси",
"doc": "Колку долго машината смее да остане тивка меѓу два импулса на превртување на барабанот пред режимот против брчки да заврши. Поставете ја над најдолгата пауза што вашата машина за сушење ја остава меѓу импулсите, инаку секој нареден импулс се чита како лажен старт."
},
"anti_wrinkle_max_duration": {
"doc": "Пулсовите подолги од ова се третираат како вистински циклус, а не како средство против брчки.",
"label": "Макс. траење"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "Делумно споделување: {n} програм{s} прикачена, {failed} прескокнато",
"store_device_shared_some_dup": "Поставките на уредот се споделени: {n} програм{s} прикачена ({dup} веќе постоеше)",
"store_download_failed": "Неуспешно преземање: {error}",
"store_download_nothing": "Нема ништо за преземање -- сите профили веќе постојат на овој уред"
"store_download_nothing": "Нема ништо за преземање -- сите профили веќе постојат на овој уред",
"export_selective_done": "Извозот е преземен",
"import_selective_done": "Увезени се {profiles} профил(и) и {cycles} циклус(и)"
},
"suggestion": {
"both_agree": "WashData препорачува",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Секунди над прагот за потврда на стартот",
"start_threshold_w": "Минимум вати за да се смета за започнато",
"stop_threshold_w": "Под ова, машината се смета за исклучена",
"anti_wrinkle_enabled": "Апсорбирајте ги импулсите на превртување на барабанот по главната фаза, наместо да ги читате како нови циклуси",
"anti_wrinkle_exit_power": "Моќноста меѓу импулсите мора да падне под ова за режимот против брчки да остане активен",
"anti_wrinkle_idle_timeout": "Дозволена тишина меѓу два импулса на превртување на барабанот пред режимот против брчки да заврши",
"anti_wrinkle_max_duration": "Импулс подолг од ова го завршува режимот против брчки и отвора нов циклус",
"anti_wrinkle_max_power": "Импулс над ова го завршува режимот против брчки и отвора нов циклус",
"dtw_bandwidth": "Фаза 3: warp лента на Sakoe-Chiba (0 = DTW исклучен; стандардно 0,2 = 20% од должината на циклусот)",
"corr_weight": "Фаза 2: рамнотежа помеѓу обликот на кривата (корелација) и нивото на моќност (MAE); стандардно 0,45",
"duration_weight": "Фаза 4: колку силно согласноста на траење влијае на конечниот резултат (стандардно 0,22)",
@@ -28,7 +28,9 @@
"awaiting_n": "Venter på godkjenning · {n} bekreftet",
"approved": "Godkjent",
"awaiting": "Venter på godkjenning",
"imported_tip": "Importert fra fellesskapsbutikken. Brukes kun til matching, telles ikke med i statistikken."
"imported_tip": "Importert fra fellesskapsbutikken. Brukes kun til matching, telles ikke med i statistikken.",
"not_importable": "ikke her",
"exists": "finnes"
},
"btn": {
"add_device": "+ Legg til enhet",
@@ -169,7 +171,14 @@
"download_device": "Last ned dette oppsettet",
"share_device": "⬆ Del dette apparatet",
"share_device_tip": "Del dette apparatet og dets innspilte referansesykluser til fellesskapsbutikken slik at andre med samme maskin kan ta dem i bruk",
"share_n": "Del {n} syklus/sykluser"
"share_n": "Del {n} syklus/sykluser",
"export_selected": "Eksporter (velg data)",
"export_all": "Rask eksport av alt",
"import_raw": "Avansert: erstatt alt fra JSON",
"download_export": "Last ned eksport",
"analyze_import": "Analyser fil",
"import_selected": "Importer valgte",
"back": "Tilbake"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Deteksjonsutløsere",
"pg_group_timing": "Tidsregler",
"pg_idle": "Inaktiv",
"pg_stress_group": "Test av inaktiv avslutning",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Inaktivt nivå (W)",
"pg_stress_idle_w_desc": "Overstyr den automatisk oppdagede standby-gulvverdien (la stå tomt for automatisk).",
"pg_stress_toggle": "Test inaktiv avslutning",
"pg_stress_toggle_desc": "Simulerer at apparatet holder seg på standby-forbruket etter at opptaket slutter - viser om og når WashData stopper syklusen.",
"pg_last_run": "Siste kjøring: {date}",
"pg_match_score": "Treffscore",
"pg_pct_of": "{p}% av {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Interaktiv effektgraf for syklusen: hold pekeren over for å lese av tid/effekt, rull for å zoome, dra for å panorere",
"pg_alert_timeout_end": "Avsluttet ved tidsavbrudd, ikke ved forutsigelse",
"pg_alert_indefinite": "Ville kjøre i det uendelige",
"pg_alert_stress_cap": "Sikkerhetsgrense nådd",
"pg_alert_stress_ok": "Inaktiv avslutning: syklus stoppet",
"pg_alert_stress_warn": "Standby-forbruk over stoppterskel",
"task_pg_history": "Test på historikk",
"task_pg_sweep": "Optimaliser",
"task_reprocess": "Behandler på nytt",
@@ -566,7 +584,32 @@
"task_split": "Deler opp syklus",
"task_trim": "Beskjærer syklus",
"task_merge": "Slår sammen sykluser",
"task_rebuild": "Bygger opp envelopes på nytt"
"task_rebuild": "Bygger opp envelopes på nytt",
"cat_profiles": "Profiler (programmer)",
"cat_real_cycles": "Sykluser (kjørehistorikk)",
"cat_reference_cycles": "Referansesykluser (importert)",
"cat_custom_phases": "Egendefinerte faser",
"cat_profile_groups": "Profilgrupper",
"cat_settings": "Deteksjons- & gjenkjenningsinnstillinger",
"cat_matching_config": "Finjustering av gjenkjenning",
"cat_ml_models": "ML-modeller",
"cat_feedback": "Tilbakemelding & vurderingsmerker",
"cat_suggestions": "Forslag",
"cat_maintenance_log": "Vedlikeholdslogg",
"cat_history_logs": "Historikk- & endringslogger",
"cat_lifetime_stats": "Totaler (levetid)",
"cycles_short": "sykluser",
"import_mode": "Hvordan kombinere",
"mode_merge": "Slå sammen (behold mine)",
"mode_replace": "Erstatt valgte",
"cycle_destination": "Hvor skal importerte sykluser plasseres?",
"dest_reference": "Referanse (bare form)",
"dest_real_history": "Ekte historikk (teller i statistikk)",
"conflict_resolution": "Navnekonflikter",
"conflict_import_copy": "Importer som kopi",
"conflict_keep_mine": "Behold mine",
"conflict_overwrite": "Overskriv",
"pg_anti_wrinkle": "Anti-rynke"
},
"log": {
"all_levels": "Alle nivåer",
@@ -643,7 +686,9 @@
"force_stop_msg": "Tvangsstoppe den aktive syklusen nå? Syklusen lagres som avbrutt.",
"store_import": "Importer referansesyklus",
"store_share": "Del i fellesskapsbutikken",
"store_share_device": "Del dette apparatet"
"store_share_device": "Del dette apparatet",
"export_select": "Eksport - velg data",
"import_wizard": "Import - velg data"
},
"msg": {
"appliance_monitor": "Apparatmonitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} like umerkede sykluser funnet opprett en profil for å begynne å matche dem.",
"cycles_deleted": "{count} syklus(er) slettet",
"enough_data": "Nok data til å lære av ({current}/{min} sykluser).",
"export_description": "Eksporter alle profiler og sykluser til JSON, eller gjenopprett fra en tidligere eksport.",
"export_description": "Velg nøyaktig hvilke profiler, sykluser, innstillinger og mer som skal eksporteres til JSON, eller analyser en fil og importer bare delene du vil ha.",
"feedback_cycles_pending": "{n} syklus{s} til gjennomgang",
"feedback_prompt": "Bekreft at det var riktig, korriger programmet eller ignorer.",
"feedback_relabel_hint": "Å merke denne syklusen på nytt løser også gjennomgangen.",
"filter_by_profile": "Filtrer etter profil…",
"group_modal_help": "Grupper programmer med samme form som varierer i temperatur/spinn (varigheten kan variere). Matching scorer gruppen som én kandidat, og velger deretter det best passende medlemmet. Velg minst 2; overlegget viser hvor like de er.",
"group_not_cohesive": "Disse profilene er ikke like nok til å gruppere pålitelig, så matching behandler dem individuelt inntil du fjerner uteliggeren eller deler gruppen.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Start Home Assistant på nytt for å aktivere simuleringsverktøyene.",
"pg_sim_progress": "{done} / {total} sykluser",
"pg_starting": "Starter…",
"pg_stress_synthetic": "inaktiv simulering starter her",
"pg_sweep_intro": "Hva om {param} var annerledes? Test {steps} verdier på tvers av dine siste {cycles} sykluser for å finne innstillingen der flest sykluser blir riktig matchet.",
"pg_sweep_step": "Trinn {done} / {total}",
"pg_undetected": "{n} syklus{s} ikke oppdaget",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulerer syklus…",
"pg_canvas_empty2": "Velg en syklus ovenfor og trykk på Kjør for å simulere den. Hold deretter pekeren over for å lese av verdier, rull for å zoome, og dra for å panorere.",
"pg_apply_settings_confirm": "Lagre disse {n} innstilling(er) på denne enheten? {list}",
"pg_stress_terminated_detail": "Stabiliserte seg på ~{idle}W inaktiv -- syklusen endte {h} t {m} min senere via {reason}.",
"pg_stress_above_threshold_detail": "Standby-forbruket på ~{idle}W er på eller over den effektive stoppterskelen ({stop}W) -- syklusen ble aldri registrert som inaktiv. Øk stop_threshold_w for å rette dette.",
"pg_stress_hit_cap_detail": "Syklusen kjørte {h} t {m} min uten å stoppe -- tvangsstoppet av sikkerhetsgrensen. Standby-forbruk {idle}W mot stoppterskel {stop}W.",
"no_logs_match": "Ingen loggoppføringer samsvarer med disse filtrene.",
"pg_analysis_hint2": "Velg en syklus og trykk Kjør for å laste inn treffanalysen.",
"pg_analysis_empty2": "Trykk Kjør for å se treffanalysen.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Last opp {brand} {model} med referansesyklusene du velger. Andre med samme apparat kan ta i bruk programmene dine. Bidrag gjennomgås før de vises offentlig.",
"share_profile_no_cycles": "Ingen referansesykluser -- marker en syklus som ⭐ under fanen Sykluser for å inkludere denne profilen",
"advisory_phase_inconsistent": "'{name}' ser ut til å blande ulike programmer eller temperaturer - syklusene varmer opp i svært ulike tidsrom. Hvis du deler den opp i separate profiler (f.eks. per temperatur), blir samsvar og tidsestimater bedre.",
"advisory_phase_inconsistent_title": "⚠ Muligens blandede programmer"
"advisory_phase_inconsistent_title": "⚠ Muligens blandede programmer",
"export_select_intro": "Kryss av nøyaktig hva som skal inkluderes. Å velge profiler uten syklusene deres eksporterer likevel et gjenkjennbart program (den lærte formen følger med).",
"import_analyze_hint": "Last inn en eksportert fil (eller lim inn JSON-en). WashData analyserer den og viser nøyaktig hva som kan importeres før noe endres.",
"analyzing": "Analyserer…",
"device_type_mismatch_warn": "Denne eksporten er fra en annen apparattype ({src} vs {local}). Programmer og sykluser kan fortsatt importeres som referansedata, men apparatspesifikke innstillinger og import av ekte historikk er deaktivert.",
"merge_hint": "Importerte elementer legges til; ingenting lokalt går tapt. Navnekonflikter løses nedenfor.",
"replace_warn": "Hver avkrysset kategori slettes og erstattes fra filen. Ikke-avkryssede kategorier forblir uendret.",
"dest_reference_hint": "Importerte sykluser forbedrer bare programgjenkjenning og påvirker aldri bruks-/energistatistikk.",
"dest_real_history_hint": "Importerte sykluser teller som dette apparatets egen historikk og mater energi-/bruksstatistikk. Bruk dette til å flytte ett apparat til en ny installasjon."
},
"pg_desc": {
"completion_min_seconds": "Korteste kjøring som teller som en ekte syklus",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Trinn 3: DDTW-halvmetningsavstand; mindre = mer formfølsom (standard 30)",
"dtw_refine_top_n": "Trinn 3: kandidater DTW beregner på nytt; øk til 7-9 hvis riktig profil rangerer 4.-5. (standard 5)",
"duration_scale": "Trinn 4: log-forhold der varighetsoverensstemmelse halveres; lavere = strengere straff (standard 0,175)",
"energy_scale": "Trinn 4: log-forhold der energioverensstemmelse halveres; lavere = strengere straff (standard 0,25)"
"energy_scale": "Trinn 4: log-forhold der energioverensstemmelse halveres; lavere = strengere straff (standard 0,25)",
"anti_wrinkle_idle_timeout": "Tillatt stille tid mellom to trommelpulser før anti-rynke slutter",
"anti_wrinkle_max_power": "En puls over dette avslutter anti-rynke og åpner en ny syklus",
"anti_wrinkle_enabled": "Fange opp trommelpulsene etter hovedfasen i stedet for å lese dem som nye sykluser",
"anti_wrinkle_max_duration": "En puls lengre enn dette avslutter anti-rynke og åpner en ny syklus",
"anti_wrinkle_exit_power": "Effekten må falle under dette mellom pulsene for at anti-rynke skal forbli aktiv"
},
"phase_desc": {
"anti_crease": "Enkelte korte fall etter ferdigstillelse for å redusere rynker.",
@@ -985,6 +1048,10 @@
"doc": "Strømmen må falle under dette mellom pulsene for at anti-rynkemodus skal forbli aktiv.",
"label": "Utgangseffektterskel"
},
"anti_wrinkle_idle_timeout": {
"doc": "Hvor lenge maskinen kan forbli stille mellom to trommelpulser før anti-rynkemodus slutter. Sett den høyere enn den lengste pausen tørketrommelen din lar stå mellom to pulser, ellers leses hver senere puls som en falsk start.",
"label": "Maks. pulspause"
},
"anti_wrinkle_max_duration": {
"doc": "Pulser lengre enn dette behandles som en ekte syklus i stedet for en anti-rynke tumbling.",
"label": "Maks. varighet"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} syklus/sykluser delt; {failed} kunne ikke lastes opp.",
"store_device_shared_some_dup": "{created} syklus/sykluser delt; {dup} var allerede i butikken.",
"store_download_failed": "Nedlasting mislyktes: {error}",
"store_download_nothing": "Ingenting nytt å laste ned -- dette oppsettet er allerede på apparatet ditt."
"store_download_nothing": "Ingenting nytt å laste ned -- dette oppsettet er allerede på apparatet ditt.",
"export_selective_done": "Eksport lastet ned",
"import_selective_done": "Importerte {profiles} profil(er) og {cycles} syklus(er)"
},
"trend": {
"down": "Trender nedover",
@@ -28,7 +28,9 @@
"awaiting_n": "Wacht op goedkeuring · {n} bevestigd",
"approved": "Goedgekeurd",
"awaiting": "Wacht op goedkeuring",
"imported_tip": "Geïmporteerd uit de communitystore. Alleen gebruikt voor matchen, telt niet mee in statistieken."
"imported_tip": "Geïmporteerd uit de communitystore. Alleen gebruikt voor matchen, telt niet mee in statistieken.",
"not_importable": "hier n.v.t.",
"exists": "bestaat"
},
"btn": {
"add_device": "+ Apparaat toevoegen",
@@ -169,7 +171,14 @@
"download_device": "Deze instelling downloaden",
"share_device": "⬆ Dit apparaat delen",
"share_device_tip": "Dit apparaat en zijn opgenomen referentiecycli delen in de communitystore, zodat andere gebruikers met hetzelfde apparaat ze kunnen overnemen",
"share_n": "{n} cyclus/cycli delen"
"share_n": "{n} cyclus/cycli delen",
"export_selected": "Exporteren (gegevens kiezen)",
"export_all": "Alles snel exporteren",
"import_raw": "Geavanceerd: alles vervangen vanuit JSON",
"download_export": "Export downloaden",
"analyze_import": "Bestand analyseren",
"import_selected": "Selectie importeren",
"back": "Terug"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Detectietriggers",
"pg_group_timing": "Timingregels",
"pg_idle": "Inactief",
"pg_stress_group": "Test voor inactieve beëindiging",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Inactief vermogen (W)",
"pg_stress_idle_w_desc": "Overschrijf de automatisch gedetecteerde standby-drempelwaarde (laat leeg voor automatisch).",
"pg_stress_toggle": "Inactieve beëindiging testen",
"pg_stress_toggle_desc": "Simuleert dat het apparaat na het einde van de opname op zijn standby-verbruik blijft - toont of en wanneer WashData de cyclus stopt.",
"pg_last_run": "Laatste run: {date}",
"pg_match_score": "Matchscore",
"pg_pct_of": "{p}% van {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Interactieve vermogensgrafiek van de cyclus: beweeg de muis eroverheen om tijd/vermogen af te lezen, scroll om te zoomen, sleep om te verschuiven",
"pg_alert_timeout_end": "Beëindigd door time-out, niet door voorspelling",
"pg_alert_indefinite": "Zou eindeloos blijven draaien",
"pg_alert_stress_cap": "Veiligheidslimiet bereikt",
"pg_alert_stress_ok": "Inactieve beëindiging: cyclus gestopt",
"pg_alert_stress_warn": "Standby-verbruik boven stoptempel",
"task_pg_history": "Testen op geschiedenis",
"task_pg_sweep": "Optimaliseren",
"task_reprocess": "Opnieuw verwerken",
@@ -566,7 +584,32 @@
"task_split": "Cyclus splitsen",
"task_trim": "Cyclus bijsnijden",
"task_merge": "Cycli samenvoegen",
"task_rebuild": "Enveloppen opnieuw opbouwen"
"task_rebuild": "Enveloppen opnieuw opbouwen",
"cat_profiles": "Profielen (programma's)",
"cat_real_cycles": "Cycli (gebruiksgeschiedenis)",
"cat_reference_cycles": "Referentiecycli (geïmporteerd)",
"cat_custom_phases": "Aangepaste fasen",
"cat_profile_groups": "Profielgroepen",
"cat_settings": "Detectie- & herkenningsinstellingen",
"cat_matching_config": "Herkenningsafstemming",
"cat_ml_models": "ML-modellen",
"cat_feedback": "Feedback & beoordelingslabels",
"cat_suggestions": "Suggesties",
"cat_maintenance_log": "Onderhoudslogboek",
"cat_history_logs": "Geschiedenis- & wijzigingslogboeken",
"cat_lifetime_stats": "Totalen (levensduur)",
"cycles_short": "cycli",
"import_mode": "Hoe combineren",
"mode_merge": "Samenvoegen (die van mij behouden)",
"mode_replace": "Selectie vervangen",
"cycle_destination": "Waar moeten geïmporteerde cycli heen?",
"dest_reference": "Referentie (alleen vorm)",
"dest_real_history": "Echte geschiedenis (telt mee in statistieken)",
"conflict_resolution": "Naamconflicten",
"conflict_import_copy": "Importeren als kopie",
"conflict_keep_mine": "Die van mij behouden",
"conflict_overwrite": "Overschrijven",
"pg_anti_wrinkle": "Anti-kreuk"
},
"log": {
"all_levels": "Alle niveaus",
@@ -643,7 +686,9 @@
"force_stop_msg": "De actieve cyclus nu geforceerd stoppen? De cyclus wordt opgeslagen als onderbroken.",
"store_import": "Referentiecyclus importeren",
"store_share": "Delen in communitystore",
"store_share_device": "Dit apparaat delen"
"store_share_device": "Dit apparaat delen",
"export_select": "Export - gegevens kiezen",
"import_wizard": "Import - gegevens kiezen"
},
"msg": {
"appliance_monitor": "Apparaatmonitor",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} vergelijkbare niet-gelabelde cycli gevonden maak een profiel aan om ze te beginnen matchen.",
"cycles_deleted": "{count} cyclus(sen) verwijderd",
"enough_data": "Voldoende gegevens om van te leren ({current}/{min} cycli).",
"export_description": "Exporteer alle profielen en cycli naar JSON, of herstel vanuit een eerdere export.",
"export_description": "Kies precies welke profielen, cycli, instellingen en meer u naar JSON wilt exporteren, of analyseer een bestand en importeer alleen de onderdelen die u wilt.",
"feedback_cycles_pending": "{n} cyclus{s} te beoordelen",
"feedback_prompt": "Bevestig dat het goed was, corrigeer het programma of negeer het.",
"feedback_relabel_hint": "Deze cyclus opnieuw labelen lost de beoordeling ook op.",
"filter_by_profile": "Filteren op profiel…",
"group_modal_help": "Groepeer programma's met dezelfde vorm die verschillen in temperatuur/centrifugeren (duur kan variëren). Matching scoort de groep als één kandidaat en kiest vervolgens het best passende lid. Kies er minimaal 2; de overlay laat zien hoezeer ze op elkaar lijken.",
"group_not_cohesive": "Deze profielen lijken niet voldoende op elkaar om betrouwbaar te kunnen worden gegroepeerd, dus bij het matchen worden ze individueel behandeld totdat u de uitbijter verwijdert of de groep opsplitst.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Start Home Assistant opnieuw op om de simulatiehulpmiddelen in te schakelen.",
"pg_sim_progress": "{done} / {total} cycli",
"pg_starting": "Bezig met starten…",
"pg_stress_synthetic": "inactieve simulatie begint hier",
"pg_sweep_intro": "Wat als {param} anders was? Test {steps} waarden over je laatste {cycles} cycli om de instelling te vinden waarbij de meeste cycli correct worden herkend.",
"pg_sweep_step": "Stap {done} / {total}",
"pg_undetected": "{n} cyclus{s} niet gedetecteerd",
@@ -837,6 +884,9 @@
"pg_simulating": "Cyclus simuleren…",
"pg_canvas_empty2": "Kies hierboven een cyclus en druk op Uitvoeren om deze te simuleren. Beweeg vervolgens de muis erover om waarden af te lezen, scroll om te zoomen en sleep om te verschuiven.",
"pg_apply_settings_confirm": "Deze {n} instelling(en) opslaan voor dit apparaat? {list}",
"pg_stress_terminated_detail": "Gestabiliseerd op ~{idle}W standby -- cyclus eindigde {h} u {m} min later via {reason}.",
"pg_stress_above_threshold_detail": "Standby-verbruik ~{idle}W ligt op of boven de effectieve stopdrempel ({stop}W) -- de cyclus is nooit als inactief geregistreerd. Verhoog stop_threshold_w om dit op te lossen.",
"pg_stress_hit_cap_detail": "Cyclus liep {h} u {m} min zonder te stoppen -- geforceerd gestopt door de veiligheidslimiet. Standby-verbruik {idle}W versus stopdrempel {stop}W.",
"no_logs_match": "Geen logrecords komen overeen met deze filters.",
"pg_analysis_hint2": "Kies een cyclus en druk op Uitvoeren om de matchanalyse te laden.",
"pg_analysis_empty2": "Druk op Uitvoeren om de matchanalyse te zien.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Upload {brand} {model} met de referentiecycli die u selecteert. Anderen met hetzelfde apparaat kunnen uw programma's overnemen. Vermeldingen worden beoordeeld voordat ze openbaar verschijnen.",
"share_profile_no_cycles": "Geen referentiecycli -- markeer een cyclus als ⭐ op het tabblad Cycli om dit profiel op te nemen",
"advisory_phase_inconsistent": "'{name}' lijkt verschillende programma's of temperaturen te combineren - de cycli verwarmen gedurende sterk uiteenlopende tijden. Door het op te splitsen in aparte profielen (bijv. per temperatuur) verbeteren de matching en tijdschattingen.",
"advisory_phase_inconsistent_title": "⚠ Mogelijk gemengde programma's"
"advisory_phase_inconsistent_title": "⚠ Mogelijk gemengde programma's",
"export_select_intro": "Vink precies aan wat u wilt opnemen. Als u profielen zonder hun cycli selecteert, wordt toch een herkenbaar programma geëxporteerd (de geleerde vorm gaat mee).",
"import_analyze_hint": "Laad een geëxporteerd bestand (of plak de JSON ervan). WashData analyseert het en toont precies wat kan worden geïmporteerd voordat er iets verandert.",
"analyzing": "Analyseren…",
"device_type_mismatch_warn": "Deze export komt van een ander apparaattype ({src} vs {local}). Programma's en cycli kunnen nog steeds als referentiegegevens worden geïmporteerd, maar apparaatspecifieke instellingen en het importeren van echte geschiedenis zijn uitgeschakeld.",
"merge_hint": "Geïmporteerde items worden toegevoegd; er gaat lokaal niets verloren. Naamconflicten worden hieronder opgelost.",
"replace_warn": "Elke aangevinkte categorie wordt gewist en vervangen vanuit het bestand. Niet-aangevinkte categorieën blijven ongewijzigd.",
"dest_reference_hint": "Geïmporteerde cycli verbeteren alleen de programmaherkenning en hebben nooit invloed op de gebruiks-/energiestatistieken.",
"dest_real_history_hint": "Geïmporteerde cycli tellen als de eigen geschiedenis van dit apparaat en voeden de energie-/gebruiksstatistieken. Gebruik dit om één apparaat naar een nieuwe installatie te verplaatsen."
},
"pg_desc": {
"completion_min_seconds": "Kortste run die als een echte cyclus telt",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Stap 3: DDTW-halfsaturatieafstand; kleiner = gevoeliger voor vorm (standaard 30)",
"dtw_refine_top_n": "Stap 3: kandidaten die door DTW opnieuw beoordeeld worden; verhoog naar 7-9 als het juiste profiel op de 4e-5e plaats staat (standaard 5)",
"duration_scale": "Stap 4: log-verhouding waarbij de duurovereenkomst halveert; kleiner = strengere straf (standaard 0,175)",
"energy_scale": "Stap 4: log-verhouding waarbij de energieovereenkomst halveert; kleiner = strengere straf (standaard 0,25)"
"energy_scale": "Stap 4: log-verhouding waarbij de energieovereenkomst halveert; kleiner = strengere straf (standaard 0,25)",
"anti_wrinkle_idle_timeout": "Toegestane stille tijd tussen twee tuimelpulsen voordat anti-kreuk eindigt",
"anti_wrinkle_max_power": "Een puls boven dit niveau beëindigt anti-kreuk en opent een nieuwe cyclus",
"anti_wrinkle_enabled": "De tuimelpulsen na de hoofdfase opvangen in plaats van ze als nieuwe cycli te lezen",
"anti_wrinkle_max_duration": "Een puls die langer is dan dit beëindigt anti-kreuk en opent een nieuwe cyclus",
"anti_wrinkle_exit_power": "Het vermogen moet tussen de pulsen onder dit niveau vallen om anti-kreuk actief te houden"
},
"phase_desc": {
"anti_crease": "Af en toe kort tuimelen na voltooiing om rimpels te verminderen.",
@@ -985,6 +1048,10 @@
"doc": "Het vermogen moet tussen de pulsen onder dit niveau liggen, anders blijft de antirimpelmodus actief.",
"label": "Drempel uitgangsverwarming"
},
"anti_wrinkle_idle_timeout": {
"doc": "Hoe lang het apparaat tussen twee tuimelpulsen stil mag blijven voordat de anti-kreukmodus eindigt. Stel dit hoger in dan de langste pauze die uw droger tussen twee pulsen laat, anders wordt elke latere puls als valse start gelezen.",
"label": "Max. pulspauze"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsaties langer dan dit worden behandeld als een echte cyclus in plaats van als een anti-rimpel-tuimelen.",
"label": "Max. duur"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} cyclus/cycli gedeeld; {failed} konden niet worden geüpload.",
"store_device_shared_some_dup": "{created} cyclus/cycli gedeeld; {dup} stonden al in de store.",
"store_download_failed": "Downloaden mislukt: {error}",
"store_download_nothing": "Niets nieuws te downloaden -- deze instelling staat al op uw apparaat."
"store_download_nothing": "Niets nieuws te downloaden -- deze instelling staat al op uw apparaat.",
"export_selective_done": "Export gedownload",
"import_selective_done": "{profiles} profiel(en) en {cycles} cyclus(sen) geïmporteerd"
},
"trend": {
"down": "Trend naar beneden",
@@ -28,7 +28,9 @@
"awaiting_n": "Oczekuje na zatwierdzenie · potwierdzono {n}",
"approved": "Zatwierdzone",
"awaiting": "Oczekuje na zatwierdzenie",
"imported_tip": "Zaimportowano ze sklepu społecznościowego. Używane tylko do dopasowywania, nie liczy się do statystyk."
"imported_tip": "Zaimportowano ze sklepu społecznościowego. Używane tylko do dopasowywania, nie liczy się do statystyk.",
"not_importable": "niedostępne",
"exists": "istnieje"
},
"btn": {
"add_device": "+ Dodaj urządzenie",
@@ -169,7 +171,14 @@
"download_device": "Pobierz konfigurację urządzenia",
"share_device": "Udostępnij konfigurację urządzenia",
"share_device_tip": "Udostępnij programy i ustawienia tego urządzenia społeczności",
"share_n": "Udostępnij {n} program{s}"
"share_n": "Udostępnij {n} program{s}",
"export_selected": "Eksport (wybierz dane)",
"export_all": "Szybki eksport wszystkiego",
"import_raw": "Zaawansowane: zastąp wszystko z pliku JSON",
"download_export": "Pobierz eksport",
"analyze_import": "Analizuj plik",
"import_selected": "Importuj wybrane",
"back": "Wstecz"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Znaleziona najlepsza wartość",
"pg_current_value": "Bieżąca wartość:",
"pg_drag_hint": "Przeciągnij linię na wykresie",
"pg_anti_wrinkle": "Antygniotowy",
"pg_dtw": "DTW",
"pg_ev_ending": "Kończenie",
"pg_ev_running": "Działa",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktywny wykres mocy cyklu: najedź, aby odczytać czas/moc, przewiń, aby powiększyć, przeciągnij, aby przesunąć",
"pg_alert_timeout_end": "Zakończono z powodu limitu czasu, nie na podstawie prognozy",
"pg_alert_indefinite": "Działałby w nieskończoność",
"pg_alert_stress_cap": "Osiągnięto limit bezpieczeństwa",
"pg_alert_stress_ok": "Wykrycie stanu bezczynności: cykl zatrzymany",
"pg_alert_stress_warn": "Pobór mocy w trybie bezczynności powyżej progu zatrzymania",
"pg_stress_group": "Test zatrzymania w stanie bezczynności",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Poziom bezczynności (W)",
"pg_stress_idle_w_desc": "Zastąp automatycznie wykrytą wartość minimalną w trybie czuwania (pozostaw puste dla automatycznego wykrycia).",
"pg_stress_toggle": "Testuj wykrywanie stanu bezczynności",
"pg_stress_toggle_desc": "Symuluje, że urządzenie utrzymuje pobór mocy w trybie czuwania po zakończeniu nagrywania -- pokazuje, czy i kiedy WashData zatrzyma cykl.",
"task_pg_history": "Test na historii",
"task_pg_sweep": "Optymalizuj",
"task_reprocess": "Ponowne przetwarzanie",
@@ -566,7 +585,31 @@
"task_split": "Dzielenie cyklu",
"task_trim": "Przycinanie cyklu",
"task_merge": "Scalanie cykli",
"task_rebuild": "Przeliczanie obwiedni"
"task_rebuild": "Przeliczanie obwiedni",
"cat_profiles": "Profile (programy)",
"cat_real_cycles": "Cykle (historia pracy)",
"cat_reference_cycles": "Cykle referencyjne (zaimportowane)",
"cat_custom_phases": "Fazy niestandardowe",
"cat_profile_groups": "Grupy profili",
"cat_settings": "Ustawienia wykrywania i rozpoznawania",
"cat_matching_config": "Strojenie rozpoznawania",
"cat_ml_models": "Modele ML",
"cat_feedback": "Etykiety opinii i weryfikacji",
"cat_suggestions": "Sugestie",
"cat_maintenance_log": "Dziennik konserwacji",
"cat_history_logs": "Dzienniki historii i zmian",
"cat_lifetime_stats": "Sumy całkowite",
"cycles_short": "cykle",
"import_mode": "Jak połączyć",
"mode_merge": "Scal (zachowaj moje)",
"mode_replace": "Zastąp wybrane",
"cycle_destination": "Gdzie mają trafić zaimportowane cykle?",
"dest_reference": "Referencyjne (tylko kształt)",
"dest_real_history": "Rzeczywista historia (liczy się w statystykach)",
"conflict_resolution": "Konflikty nazw",
"conflict_import_copy": "Importuj jako kopię",
"conflict_keep_mine": "Zachowaj moje",
"conflict_overwrite": "Nadpisz"
},
"log": {
"all_levels": "Wszystkie poziomy",
@@ -643,7 +686,9 @@
"force_stop_msg": "Wymusić teraz zatrzymanie aktywnego cyklu? Cykl zostanie zapisany jako przerwany.",
"store_import": "Importuj cykl referencyjny",
"store_share": "Udostępnij w sklepie społecznościowym",
"store_share_device": "Udostępnij konfigurację urządzenia"
"store_share_device": "Udostępnij konfigurację urządzenia",
"export_select": "Eksport - wybierz dane",
"import_wizard": "Import - wybierz dane"
},
"msg": {
"appliance_monitor": "Monitor urządzenia",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Znaleziono {count} podobnych nieoznaczonych cykli utwórz profil, aby zacząć je dopasowywać.",
"cycles_deleted": "Usunięto cykle: {count}",
"enough_data": "Wystarczająco danych do nauki ({current}/{min} cykli).",
"export_description": "Eksportuj wszystkie profile i cykle do formatu JSON lub przywróć dane z poprzedniego eksportu.",
"export_description": "Wybierz dokładnie, które profile, cykle, ustawienia i inne dane wyeksportować do pliku JSON, albo przeanalizuj plik i zaimportuj tylko wybrane części.",
"feedback_cycles_pending": "{n} cykl{s} do sprawdzenia",
"feedback_prompt": "Potwierdź, że wszystko było w porządku, popraw program lub zignoruj.",
"feedback_relabel_hint": "Ponowne oznaczenie tego cyklu również go rozwiązuje.",
"filter_by_profile": "Filtruj według profilu…",
"group_modal_help": "Grupuj programy o tym samym kształcie, różniące się temperaturą/wirowaniem (czas trwania może się różnić). Dopasowanie ocenia grupę jako jednego kandydata, a następnie wybiera najlepiej dopasowanego członka. Wybierz co najmniej 2; nakładka pokazuje, jak bardzo są podobni.",
"group_not_cohesive": "Te profile nie są na tyle podobne, aby można było je bezpiecznie grupować, więc dopasowywanie traktuje je indywidualnie, dopóki nie usuniesz wartości odstającej lub nie podzielisz grupy.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Uruchom ponownie Home Assistant, aby włączyć narzędzia symulacji.",
"pg_sim_progress": "{done} / {total} cykli",
"pg_starting": "Rozpoczynanie…",
"pg_stress_synthetic": "symulacja bezczynności zaczyna się tutaj",
"pg_sweep_intro": "Co by było, gdyby {param} był inny? Przetestuj {steps} wartości na swoich ostatnich {cycles} cyklach, aby znaleźć ustawienie, przy którym najwięcej cykli jest poprawnie dopasowanych.",
"pg_sweep_step": "Krok {done} / {total}",
"pg_undetected": "{n} niewykrytych cykli",
@@ -837,6 +884,9 @@
"pg_simulating": "Symulowanie cyklu…",
"pg_canvas_empty2": "Wybierz cykl powyżej i naciśnij Uruchom, aby go zasymulować. Następnie najedź, aby odczytać wartości, przewiń, aby powiększyć, i przeciągnij, aby przesunąć.",
"pg_apply_settings_confirm": "Zapisać te ustawienia ({n}) w tym urządzeniu? {list}",
"pg_stress_terminated_detail": "Ustabilizował się na ~{idle}W w trybie bezczynności -- cykl zakończył się {h} godz {m} min później przez {reason}.",
"pg_stress_above_threshold_detail": "Pobór mocy w trybie bezczynności ~{idle}W jest równy lub wyższy od efektywnego progu zatrzymania ({stop}W) -- cykl nigdy nie został rozpoznany jako nieaktywny. Zwiększ stop_threshold_w, aby to naprawić.",
"pg_stress_hit_cap_detail": "Cykl działał {h} godz {m} min bez zatrzymania -- wymuszono zatrzymanie przez limit bezpieczeństwa. Pobór mocy w trybie bezczynności {idle}W wobec progu zatrzymania {stop}W.",
"no_logs_match": "Żadne wpisy dziennika nie pasują do tych filtrów.",
"pg_analysis_hint2": "Wybierz cykl i naciśnij Uruchom, aby wczytać analizę dopasowania.",
"pg_analysis_empty2": "Naciśnij Uruchom, aby zobaczyć analizę dopasowania.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Udostępnij programy swojego urządzenia (profile + cykle referencyjne) społeczności. Ustawienia są opcjonalne.",
"share_profile_no_cycles": "Profil '{p}' nie ma ⭐ cykli referencyjnych -- zostanie pominięty, chyba że masz {n}+ potwierdzonych uruchomień",
"advisory_phase_inconsistent": "Wygląda na to, że '{name}' miesza różne programy lub temperatury - jego cykle nagrzewają się przez bardzo różne czasy. Podzielenie go na osobne profile (np. dla każdej temperatury) poprawi dopasowanie i szacowanie czasu.",
"advisory_phase_inconsistent_title": "⚠ Prawdopodobnie pomieszane programy"
"advisory_phase_inconsistent_title": "⚠ Prawdopodobnie pomieszane programy",
"export_select_intro": "Zaznacz dokładnie to, co ma zostać uwzględnione. Wybranie profili bez ich cykli i tak eksportuje rozpoznawalny program (jego wyuczony kształt jest przenoszony razem z nim).",
"import_analyze_hint": "Wczytaj wyeksportowany plik (lub wklej jego JSON). WashData go przeanalizuje i pokaże dokładnie, co można zaimportować, zanim cokolwiek się zmieni.",
"analyzing": "Analizowanie…",
"device_type_mismatch_warn": "Ten eksport pochodzi z innego typu urządzenia ({src} vs {local}). Programy i cykle można nadal zaimportować jako dane referencyjne, ale ustawienia specyficzne dla urządzenia oraz import rzeczywistej historii są wyłączone.",
"merge_hint": "Zaimportowane elementy są dodawane; nic lokalnego nie zostaje utracone. Konflikty nazw rozwiązuje się poniżej.",
"replace_warn": "Każda zaznaczona kategoria zostaje wyczyszczona i zastąpiona danymi z pliku. Niezaznaczone kategorie pozostają nietknięte.",
"dest_reference_hint": "Zaimportowane cykle jedynie poprawiają rozpoznawanie programów i nigdy nie wpływają na statystyki zużycia/energii.",
"dest_real_history_hint": "Zaimportowane cykle liczą się jako własna historia tego urządzenia i zasilają statystyki energii/zużycia. Użyj przy przenoszeniu jednego urządzenia do nowej instalacji."
},
"phase_desc": {
"anti_crease": "Sporadyczne krótkie obroty bębna po zakończeniu w celu zapobiegania gnieceniu tkanin.",
@@ -962,6 +1020,10 @@
"doc": "Moc musi spaść poniżej tej wartości pomiędzy impulsami, aby tryb przeciwzmarszczkowy pozostał aktywny.",
"label": "Próg Wyjścia Mocy"
},
"anti_wrinkle_idle_timeout": {
"doc": "Jak długo maszyna może pozostać cicha między dwoma impulsami bębna, zanim tryb antygniotowy się zakończy. Ustaw powyżej najdłuższej przerwy, jaką suszarka zostawia między impulsami, w przeciwnym razie każdy kolejny impuls zostanie odczytany jako fałszywy start.",
"label": "Maks. Odstęp Impulsów"
},
"anti_wrinkle_max_duration": {
"doc": "Impulsy dłuższe niż ten traktowane są jako prawdziwy cykl, a nie przeciwzmarszczkowy upadek.",
"label": "Maks. Czas Trwania"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Częściowe udostępnienie: {n} program{s} przesłany, {failed} pominięte",
"store_device_shared_some_dup": "Konfiguracja urządzenia udostępniona: {n} program{s} przesłany ({dup} już istniało)",
"store_download_failed": "Pobieranie nie powiodło się: {error}",
"store_download_nothing": "Nic do pobrania -- wszystkie profile już istnieją na tym urządzeniu"
"store_download_nothing": "Nic do pobrania -- wszystkie profile już istnieją na tym urządzeniu",
"export_selective_done": "Eksport pobrany",
"import_selective_done": "Zaimportowano {profiles} profil(e) i {cycles} cykl(e)"
},
"trend": {
"down": "Trend w dół",
@@ -1706,6 +1770,11 @@
"other": "Inne"
},
"pg_desc": {
"anti_wrinkle_enabled": "Pochłaniaj impulsy bębna po głównej fazie, zamiast odczytywać je jako nowe cykle",
"anti_wrinkle_exit_power": "Moc musi spaść poniżej tej wartości pomiędzy impulsami, aby tryb antygniotowy pozostał aktywny",
"anti_wrinkle_idle_timeout": "Dozwolony czas ciszy między dwoma impulsami bębna, zanim tryb antygniotowy się zakończy",
"anti_wrinkle_max_duration": "Dłuższy impuls kończy tryb antygniotowy i otwiera nowy cykl",
"anti_wrinkle_max_power": "Impuls powyżej tej wartości kończy tryb antygniotowy i otwiera nowy cykl",
"completion_min_seconds": "Najkrótszy przebieg liczony jako prawdziwy cykl",
"end_repeat_count": "Kolejne niskie odczyty przed zakończeniem",
"interrupted_min_seconds": "Krótkie cykle oznaczane jako przerwane",
@@ -28,7 +28,9 @@
"awaiting_n": "Aguardando aprovação · {n} confirmados",
"approved": "Aprovado",
"awaiting": "Aguardando aprovação",
"imported_tip": "Importado da loja da comunidade. Usado apenas para correspondência, não contabilizado nas estatísticas."
"imported_tip": "Importado da loja da comunidade. Usado apenas para correspondência, não contabilizado nas estatísticas.",
"not_importable": "n/d aqui",
"exists": "já existe"
},
"btn": {
"add_device": "+ Adicionar dispositivo",
@@ -169,7 +171,14 @@
"download_device": "Baixar esta configuração",
"share_device": "⬆ Compartilhar este dispositivo",
"share_device_tip": "Compartilhe este eletrodoméstico e seus ciclos de referência gravados na loja da comunidade para que outros usuários com o mesmo aparelho possam adotá-los",
"share_n": "Compartilhar {n} ciclo(s)"
"share_n": "Compartilhar {n} ciclo(s)",
"export_selected": "Exportar (escolher dados)",
"export_all": "Exportar tudo rapidamente",
"import_raw": "Avançado: substituir tudo a partir de JSON",
"download_export": "Baixar exportação",
"analyze_import": "Analisar arquivo",
"import_selected": "Importar seleção",
"back": "Voltar"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Aba padrão",
"panel_language": "Idioma do painel",
"per_cycle_short": "ciclo",
"pg_anti_wrinkle": "Antiamasso",
"pg_autofill_tip": "Preencher automaticamente a faixa a partir do valor atual ({lo}{hi})",
"pg_best_value": "Melhor valor encontrado",
"pg_current_value": "Valor atual:",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Sem correspondência",
"pg_alert_ambiguous": "Correspondência ambígua",
"pg_alert_energy": "Anomalia de energia",
"pg_alert_stress_cap": "Limite de segurança atingido",
"pg_alert_stress_ok": "Encerramento em repouso: ciclo parado",
"pg_alert_stress_warn": "Consumo em espera acima do limite de parada",
"pg_newly_correct": "agora correto",
"pg_regressed": "regrediu",
"pg_end_timing_changed": "momento do fim alterado",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Parâmetro",
"pg_objective": "Objetivo",
"pg_steps": "Etapas",
"pg_stress_group": "Teste de encerramento em repouso",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Nível em espera (W)",
"pg_stress_idle_w_desc": "Substitui o nível mínimo de espera detectado automaticamente (deixe em branco para automático).",
"pg_stress_toggle": "Testar encerramento em repouso",
"pg_stress_toggle_desc": "Simula o eletrodoméstico mantido no seu consumo em espera após o fim da gravação mostra se e quando o WashData encerra o ciclo.",
"pg_sweep_2d": "Mapa de calor 2D",
"pg_sweep_param_y": "2º parâmetro",
"pg_obj_match": "Precisão de correspondência",
@@ -566,7 +585,31 @@
"task_split": "Dividindo o ciclo",
"task_trim": "Recortando o ciclo",
"task_merge": "Combinando ciclos",
"task_rebuild": "Reconstruindo envelopes"
"task_rebuild": "Reconstruindo envelopes",
"cat_profiles": "Perfis (programas)",
"cat_real_cycles": "Ciclos (histórico de uso)",
"cat_reference_cycles": "Ciclos de referência (importados)",
"cat_custom_phases": "Fases personalizadas",
"cat_profile_groups": "Grupos de perfis",
"cat_settings": "Configurações de detecção e reconhecimento",
"cat_matching_config": "Ajuste do reconhecimento",
"cat_ml_models": "Modelos de ML",
"cat_feedback": "Feedback e rótulos de revisão",
"cat_suggestions": "Sugestões",
"cat_maintenance_log": "Registro de manutenção",
"cat_history_logs": "Histórico e registros de alterações",
"cat_lifetime_stats": "Totais acumulados",
"cycles_short": "ciclos",
"import_mode": "Como combinar",
"mode_merge": "Mesclar (manter os meus)",
"mode_replace": "Substituir seleção",
"cycle_destination": "Para onde devem ir os ciclos importados?",
"dest_reference": "Referência (apenas forma)",
"dest_real_history": "Histórico real (conta nas estatísticas)",
"conflict_resolution": "Conflitos de nomes",
"conflict_import_copy": "Importar como cópia",
"conflict_keep_mine": "Manter o meu",
"conflict_overwrite": "Sobrescrever"
},
"log": {
"all_levels": "Todos os níveis",
@@ -643,7 +686,9 @@
"force_stop_msg": "Forçar a parada do ciclo ativo agora? O ciclo será salvo como interrompido.",
"store_import": "Importar ciclo de referência",
"store_share": "Compartilhar na loja da comunidade",
"store_share_device": "Compartilhar este dispositivo"
"store_share_device": "Compartilhar este dispositivo",
"export_select": "Exportar - escolher dados",
"import_wizard": "Importar - escolher dados"
},
"msg": {
"appliance_monitor": "Monitor de eletrodoméstico",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} ciclos similares sem rótulo encontrados crie um perfil para começar a correspondê-los.",
"cycles_deleted": "{count} ciclo(s) excluído(s)",
"enough_data": "Dados suficientes para aprender ({current}/{min} ciclos).",
"export_description": "Exporte todos os perfis e ciclos para JSON ou restaure de uma exportação anterior.",
"export_description": "Escolha exatamente quais perfis, ciclos, configurações e mais exportar para JSON, ou analise um arquivo e importe apenas as partes que quiser.",
"feedback_cycles_pending": "{n} ciclo{s} para revisar",
"feedback_prompt": "Confirme que estava correto, corrija o programa ou ignore.",
"feedback_relabel_hint": "Rotular novamente este ciclo também o resolve.",
"filter_by_profile": "Filtrar por perfil…",
"group_modal_help": "Agrupe programas com o mesmo formato que diferem em temperatura/centrifugação (as durações podem variar). A correspondência avalia o grupo como um candidato e depois escolhe o membro mais adequado. Escolha pelo menos 2; a sobreposição mostra o grau de semelhança.",
"group_not_cohesive": "Esses perfis não são similares o suficiente para serem agrupados de forma confiável, então a correspondência os trata individualmente até que você remova o valor discrepante ou divida o grupo.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Reinicie o Home Assistant para ativar as ferramentas de simulação.",
"pg_sim_progress": "{done} / {total} ciclos",
"pg_starting": "Iniciando…",
"pg_stress_synthetic": "a simulação de espera começa aqui",
"pg_sweep_intro": "E se {param} fosse diferente? Teste {steps} valores nos seus últimos {cycles} ciclos para encontrar a configuração em que o maior número de ciclos é correspondido corretamente.",
"pg_sweep_step": "Etapa {done} / {total}",
"pg_undetected": "{n} ciclo{s} sem detecção",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulando o ciclo…",
"pg_canvas_empty2": "Escolha um ciclo acima e pressione Executar para simulá-lo. Depois passe o cursor para ler os valores, role para ampliar e arraste para deslocar.",
"pg_apply_settings_confirm": "Salvar estas {n} configuração(ões) neste dispositivo? {list}",
"pg_stress_terminated_detail": "Estabilizou em ~{idle}W em espera -- o ciclo terminou {h}h {m}m depois via {reason}.",
"pg_stress_above_threshold_detail": "O consumo em espera ~{idle}W é igual ou superior ao limite de parada efetivo ({stop}W) -- o ciclo nunca foi considerado inativo. Aumente stop_threshold_w para corrigir.",
"pg_stress_hit_cap_detail": "O ciclo durou {h}h {m}m sem parar -- interrompido à força pelo limite de segurança. Consumo em espera {idle}W em relação ao limite de parada {stop}W.",
"no_logs_match": "Nenhum registro corresponde a estes filtros.",
"pg_analysis_hint2": "Selecione um ciclo e pressione Executar para carregar a análise de correspondência.",
"pg_analysis_empty2": "Pressione Executar para ver a análise de correspondência.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Envie {brand} {model} com os ciclos de referência que você selecionar. Outros usuários com o mesmo eletrodoméstico podem adotar seus programas. As entradas são revisadas antes de aparecerem publicamente.",
"share_profile_no_cycles": "Nenhum ciclo de referência -- marque um ciclo como ⭐ na aba Ciclos para incluir este perfil",
"advisory_phase_inconsistent": "'{name}' parece misturar diferentes programas ou temperaturas - seus ciclos aquecem por períodos de tempo muito diferentes. Dividi-lo em perfis separados (por ex. por temperatura) melhorará a correspondência e as estimativas de tempo.",
"advisory_phase_inconsistent_title": "⚠ Possíveis programas misturados"
"advisory_phase_inconsistent_title": "⚠ Possíveis programas misturados",
"export_select_intro": "Marque exatamente o que incluir. Selecionar perfis sem os ciclos deles ainda exporta um programa reconhecível (a forma aprendida vai junto).",
"import_analyze_hint": "Carregue um arquivo exportado (ou cole o JSON dele). O WashData o analisa e mostra exatamente o que pode ser importado antes de qualquer alteração.",
"analyzing": "Analisando…",
"device_type_mismatch_warn": "Esta exportação é de um tipo de eletrodoméstico diferente ({src} em vez de {local}). Os programas e ciclos ainda podem ser importados como dados de referência, mas as configurações específicas do dispositivo e a importação para o histórico real estão desativadas.",
"merge_hint": "Os itens importados são adicionados; nada local é perdido. Os conflitos de nomes são resolvidos abaixo.",
"replace_warn": "Cada categoria marcada é apagada e substituída pelo conteúdo do arquivo. As categorias não marcadas ficam intactas.",
"dest_reference_hint": "Os ciclos importados só melhoram o reconhecimento de programas e nunca afetam as estatísticas de uso/energia.",
"dest_real_history_hint": "Os ciclos importados contam como histórico próprio deste dispositivo e alimentam as estatísticas de energia/uso. Use para mover um eletrodoméstico para uma nova instalação."
},
"phase_desc": {
"anti_crease": "Giros curtos ocasionais após a conclusão para reduzir amassados.",
@@ -962,6 +1020,10 @@
"doc": "A potência deve cair abaixo disso entre os pulsos para que o modo antirrugas permaneça ativo.",
"label": "Limite de Saída de Potência"
},
"anti_wrinkle_idle_timeout": {
"doc": "Por quanto tempo a máquina pode permanecer em silêncio entre dois pulsos de giro antes de o modo antiamasso terminar. Defina-o acima do maior intervalo que sua secadora deixa entre pulsos; caso contrário, cada pulso posterior é lido como um início falso.",
"label": "Intervalo Máximo Entre Pulsos"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsos mais longos do que isso são tratados como um ciclo real, e não como uma queda anti-rugas.",
"label": "Duração Máxima"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} ciclo(s) compartilhado(s); {failed} não puderam ser enviados.",
"store_device_shared_some_dup": "{created} ciclo(s) compartilhado(s); {dup} já estavam na loja.",
"store_download_failed": "Falha no download: {error}",
"store_download_nothing": "Nada novo para baixar - esta configuração já está neste dispositivo."
"store_download_nothing": "Nada novo para baixar - esta configuração já está neste dispositivo.",
"export_selective_done": "Exportação baixada",
"import_selective_done": "Importados {profiles} perfil(is) e {cycles} ciclo(s)"
},
"suggestion": {
"both_agree": "WashData recomenda",
@@ -1706,6 +1770,11 @@
"other": "Outro"
},
"pg_desc": {
"anti_wrinkle_enabled": "Absorver os pulsos de giro após a fase principal em vez de lê-los como novos ciclos",
"anti_wrinkle_exit_power": "A potência deve cair abaixo deste valor entre os pulsos para que o modo antiamasso permaneça ativo",
"anti_wrinkle_idle_timeout": "Tempo de silêncio permitido entre dois pulsos de giro antes de o modo antiamasso terminar",
"anti_wrinkle_max_duration": "Um pulso mais longo do que isso encerra o modo antiamasso e abre um novo ciclo",
"anti_wrinkle_max_power": "Um pulso acima disso encerra o modo antiamasso e abre um novo ciclo",
"completion_min_seconds": "Menor duração que conta como ciclo real",
"end_repeat_count": "Leituras baixas seguidas antes de terminar",
"interrupted_min_seconds": "Ciclos curtos marcados como interrompidos",
@@ -28,7 +28,9 @@
"awaiting_n": "A aguardar aprovação · {n} confirmados",
"approved": "Aprovado",
"awaiting": "A aguardar aprovação",
"imported_tip": "Importado da loja da comunidade. Usado apenas para correspondência, não contabilizado nas estatísticas."
"imported_tip": "Importado da loja da comunidade. Usado apenas para correspondência, não contabilizado nas estatísticas.",
"not_importable": "n/d aqui",
"exists": "já existe"
},
"btn": {
"add_device": "+ Adicionar dispositivo",
@@ -169,7 +171,14 @@
"download_device": "Transferir esta configuração",
"share_device": "⬆ Partilhar este dispositivo",
"share_device_tip": "Partilhar este electrodoméstico e os seus ciclos de referência gravados na loja da comunidade para que outros utilizadores com o mesmo aparelho os possam adotar",
"share_n": "Partilhar {n} ciclo(s)"
"share_n": "Partilhar {n} ciclo(s)",
"export_selected": "Exportar (escolher dados)",
"export_all": "Exportar tudo rapidamente",
"import_raw": "Avançado: substituir tudo a partir de JSON",
"download_export": "Transferir exportação",
"analyze_import": "Analisar ficheiro",
"import_selected": "Importar seleção",
"back": "Voltar"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Separador predefinido",
"panel_language": "Idioma do painel",
"per_cycle_short": "ciclo",
"pg_anti_wrinkle": "Anti-vincos",
"pg_autofill_tip": "Preencher automaticamente o intervalo a partir do valor atual ({lo}{hi})",
"pg_best_value": "Melhor valor encontrado",
"pg_current_value": "Valor atual:",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Sem correspondência",
"pg_alert_ambiguous": "Correspondência ambígua",
"pg_alert_energy": "Anomalia de energia",
"pg_alert_stress_cap": "Limite de segurança atingido",
"pg_alert_stress_ok": "Terminação em repouso: ciclo parado",
"pg_alert_stress_warn": "Consumo em repouso acima do limiar de paragem",
"pg_newly_correct": "agora correto",
"pg_regressed": "regrediu",
"pg_end_timing_changed": "momento do fim alterado",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Parâmetro",
"pg_objective": "Objetivo",
"pg_steps": "Passos",
"pg_stress_group": "Teste de terminação em repouso",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Nível em repouso (W)",
"pg_stress_idle_w_desc": "Substitui o piso de repouso detectado automaticamente (deixe em branco para automático).",
"pg_stress_toggle": "Testar terminação em repouso",
"pg_stress_toggle_desc": "Simula o electrodoméstico mantido no seu consumo em repouso após o fim da gravação mostra se e quando o WashData para o ciclo.",
"pg_sweep_2d": "Mapa de calor 2D",
"pg_sweep_param_y": "2.º parâmetro",
"pg_obj_match": "Precisão de correspondência",
@@ -566,7 +585,31 @@
"task_split": "A dividir o ciclo",
"task_trim": "A recortar o ciclo",
"task_merge": "A combinar ciclos",
"task_rebuild": "A reconstruir envelopes"
"task_rebuild": "A reconstruir envelopes",
"cat_profiles": "Perfis (programas)",
"cat_real_cycles": "Ciclos (histórico de utilização)",
"cat_reference_cycles": "Ciclos de referência (importados)",
"cat_custom_phases": "Fases personalizadas",
"cat_profile_groups": "Grupos de perfis",
"cat_settings": "Definições de deteção e reconhecimento",
"cat_matching_config": "Ajuste do reconhecimento",
"cat_ml_models": "Modelos de ML",
"cat_feedback": "Comentários e etiquetas de revisão",
"cat_suggestions": "Sugestões",
"cat_maintenance_log": "Registo de manutenção",
"cat_history_logs": "Histórico e registos de alterações",
"cat_lifetime_stats": "Totais acumulados",
"cycles_short": "ciclos",
"import_mode": "Como combinar",
"mode_merge": "Combinar (manter os meus)",
"mode_replace": "Substituir seleção",
"cycle_destination": "Para onde devem ir os ciclos importados?",
"dest_reference": "Referência (apenas forma)",
"dest_real_history": "Histórico real (conta nas estatísticas)",
"conflict_resolution": "Conflitos de nomes",
"conflict_import_copy": "Importar como cópia",
"conflict_keep_mine": "Manter o meu",
"conflict_overwrite": "Substituir"
},
"log": {
"all_levels": "Todos os níveis",
@@ -643,7 +686,9 @@
"force_stop_msg": "Forçar a paragem do ciclo ativo agora? O ciclo será guardado como interrompido.",
"store_import": "Importar ciclo de referência",
"store_share": "Partilhar na loja da comunidade",
"store_share_device": "Partilhar este dispositivo"
"store_share_device": "Partilhar este dispositivo",
"export_select": "Exportar - escolher dados",
"import_wizard": "Importar - escolher dados"
},
"msg": {
"appliance_monitor": "Monitor de electrodoméstico",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} ciclos semelhantes sem rótulo encontrados crie um perfil para começar a correspondê-los.",
"cycles_deleted": "{count} ciclo(s) eliminado(s)",
"enough_data": "Dados suficientes para aprender ({current}/{min} ciclos).",
"export_description": "Exporte todos os perfis e ciclos para JSON ou restaure a partir de uma exportação anterior.",
"export_description": "Escolha exatamente que perfis, ciclos, definições e mais exportar para JSON, ou analise um ficheiro e importe apenas as partes que quiser.",
"feedback_cycles_pending": "{n} ciclo{s} para rever",
"feedback_prompt": "Confirme que estava correcto, corrija o programa ou ignore.",
"feedback_relabel_hint": "Voltar a rotular este ciclo também o resolve.",
"filter_by_profile": "Filtrar por perfil…",
"group_modal_help": "Agrupe programas com a mesma forma que diferem em temperatura/centrifugação (as durações podem variar). A correspondência pontua o grupo como um candidato e, em seguida, escolhe o membro mais adequado. Escolha pelo menos 2; a sobreposição mostra o grau de semelhança.",
"group_not_cohesive": "Estes perfis não são suficientemente semelhantes para serem agrupados de forma fiável, pelo que a correspondência os trata individualmente até remover o valor aberrante ou dividir o grupo.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Reinicie o Home Assistant para ativar as ferramentas de simulação.",
"pg_sim_progress": "{done} / {total} ciclos",
"pg_starting": "A iniciar…",
"pg_stress_synthetic": "a simulação de repouso começa aqui",
"pg_sweep_intro": "E se {param} fosse diferente? Teste {steps} valores nos seus últimos {cycles} ciclos para encontrar a definição em que mais ciclos são reconhecidos corretamente.",
"pg_sweep_step": "Passo {done} / {total}",
"pg_undetected": "{n} ciclo{s} não detetado{s}",
@@ -837,6 +884,9 @@
"pg_simulating": "A simular o ciclo…",
"pg_canvas_empty2": "Escolha um ciclo acima e prima Executar para o simular. Depois passe o cursor para ler os valores, percorra para ampliar e arraste para deslocar.",
"pg_apply_settings_confirm": "Guardar estas {n} definição(ões) neste dispositivo? {list}",
"pg_stress_terminated_detail": "Estabilizou em ~{idle}W em repouso -- o ciclo terminou {h}h {m}m depois através de {reason}.",
"pg_stress_above_threshold_detail": "O consumo em repouso ~{idle}W é igual ou superior ao limiar de paragem efetivo ({stop}W) -- o ciclo nunca foi considerado inativo. Aumente stop_threshold_w para corrigir.",
"pg_stress_hit_cap_detail": "O ciclo durou {h}h {m}m sem parar -- parado à força pelo limite de segurança. Consumo em repouso {idle}W face ao limiar de paragem {stop}W.",
"no_logs_match": "Nenhum registo corresponde a estes filtros.",
"pg_analysis_hint2": "Selecione um ciclo e prima Executar para carregar a análise de correspondência.",
"pg_analysis_empty2": "Prima Executar para ver a análise de correspondência.",
@@ -864,9 +914,22 @@
"store_share_device_intro": "Carregar {brand} {model} com os ciclos de referência que selecionar. Outros utilizadores com o mesmo electrodoméstico podem adotar os seus programas. As entradas são revistas antes de aparecerem publicamente.",
"share_profile_no_cycles": "Sem ciclos de referência -- marque um ciclo como ⭐ no separador Ciclos para incluir este perfil",
"advisory_phase_inconsistent": "'{name}' parece misturar diferentes programas ou temperaturas - os seus ciclos aquecem durante períodos de tempo muito diferentes. Dividi-lo em perfis separados (p. ex. por temperatura) melhorará a correspondência e as estimativas de tempo.",
"advisory_phase_inconsistent_title": "⚠ Possíveis programas misturados"
"advisory_phase_inconsistent_title": "⚠ Possíveis programas misturados",
"export_select_intro": "Marque exatamente o que incluir. Selecionar perfis sem os seus ciclos exporta na mesma um programa reconhecível (a forma aprendida vai junto).",
"import_analyze_hint": "Carregue um ficheiro exportado (ou cole o seu JSON). O WashData analisa-o e mostra exatamente o que pode ser importado antes de qualquer alteração.",
"analyzing": "A analisar…",
"device_type_mismatch_warn": "Esta exportação é de um tipo de eletrodoméstico diferente ({src} em vez de {local}). Os programas e ciclos ainda podem ser importados como dados de referência, mas as definições específicas do dispositivo e a importação para o histórico real estão desativadas.",
"merge_hint": "Os itens importados são adicionados; nada local é perdido. Os conflitos de nomes são resolvidos abaixo.",
"replace_warn": "Cada categoria marcada é apagada e substituída pelo conteúdo do ficheiro. As categorias não marcadas ficam intactas.",
"dest_reference_hint": "Os ciclos importados só melhoram o reconhecimento de programas e nunca afetam as estatísticas de utilização/energia.",
"dest_real_history_hint": "Os ciclos importados contam como histórico próprio deste dispositivo e alimentam as estatísticas de energia/utilização. Use para mover um eletrodoméstico para uma nova instalação."
},
"pg_desc": {
"anti_wrinkle_enabled": "Absorver os pulsos de rotação após a fase principal em vez de os ler como novos ciclos",
"anti_wrinkle_exit_power": "A potência deve descer abaixo deste valor entre pulsos para que o modo anti-vincos permaneça ativo",
"anti_wrinkle_idle_timeout": "Tempo de silêncio permitido entre dois pulsos de rotação antes de o modo anti-vincos terminar",
"anti_wrinkle_max_duration": "Um pulso mais longo do que isto termina o modo anti-vincos e abre um novo ciclo",
"anti_wrinkle_max_power": "Um pulso acima disto termina o modo anti-vincos e abre um novo ciclo",
"completion_min_seconds": "Execução mais curta que conta como um ciclo real",
"end_repeat_count": "Leituras baixas consecutivas antes de terminar",
"interrupted_min_seconds": "Ciclos curtos marcados como interrompidos",
@@ -985,6 +1048,10 @@
"doc": "A potência deve cair abaixo disso entre os pulsos para que o modo antirrugas permaneça ativo.",
"label": "Limiar de Saída de Potência"
},
"anti_wrinkle_idle_timeout": {
"doc": "Durante quanto tempo a máquina pode permanecer em silêncio entre dois pulsos de rotação antes de o modo anti-vincos terminar. Defina-o acima do intervalo mais longo que a sua secadora deixa entre pulsos, caso contrário cada pulso posterior é lido como um falso arranque.",
"label": "Intervalo Máximo Entre Pulsos"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsos mais longos do que isso são tratados como um ciclo real, e não como uma queda anti-rugas.",
"label": "Duração Máxima"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n} ciclo(s) partilhado(s); {failed} não puderam ser carregados.",
"store_device_shared_some_dup": "{created} ciclo(s) partilhado(s); {dup} já estavam na loja.",
"store_download_failed": "Falha na transferência: {error}",
"store_download_nothing": "Nada de novo para transferir -- esta configuração já está no seu dispositivo."
"store_download_nothing": "Nada de novo para transferir -- esta configuração já está no seu dispositivo.",
"export_selective_done": "Exportação transferida",
"import_selective_done": "Importados {profiles} perfil(is) e {cycles} ciclo(s)"
},
"suggestion": {
"both_agree": "WashData recomenda",
@@ -28,7 +28,9 @@
"awaiting_n": "În așteptarea aprobării · {n} confirmate",
"approved": "Aprobat",
"awaiting": "În așteptarea aprobării",
"imported_tip": "Importat din magazinul comunității. Folosit doar pentru potrivire, necontabilizat în statistici."
"imported_tip": "Importat din magazinul comunității. Folosit doar pentru potrivire, necontabilizat în statistici.",
"not_importable": "n/a aici",
"exists": "există deja"
},
"btn": {
"add_device": "+ Adăugați dispozitiv",
@@ -169,7 +171,14 @@
"download_device": "Descărcați această configurație",
"share_device": "⬆ Partajați acest dispozitiv",
"share_device_tip": "Partajați acest aparat și ciclurile sale de referință înregistrate în magazinul comunității, astfel încât alții cu același aparat să le poată adopta",
"share_n": "Partajați {n} ciclu(ri)"
"share_n": "Partajați {n} ciclu(ri)",
"export_selected": "Exportă (alege datele)",
"export_all": "Exportă rapid tot",
"import_raw": "Avansat: înlocuiește tot din JSON",
"download_export": "Descarcă exportul",
"analyze_import": "Analizează fișierul",
"import_selected": "Importă selecția",
"back": "Înapoi"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Fila implicită",
"panel_language": "Limba panoului",
"per_cycle_short": "ciclu",
"pg_anti_wrinkle": "Anti-șifonare",
"pg_autofill_tip": "Completează automat intervalul din valoarea curentă ({lo}{hi})",
"pg_best_value": "Cea mai bună valoare găsită",
"pg_current_value": "Valoarea curentă:",
@@ -515,6 +525,9 @@
"pg_alert_unmatched": "Fără potrivire",
"pg_alert_ambiguous": "Potrivire ambiguă",
"pg_alert_energy": "Anomalie de energie",
"pg_alert_stress_cap": "Limita de siguranță atinsă",
"pg_alert_stress_ok": "Terminare în repaus: ciclu oprit",
"pg_alert_stress_warn": "Consum în standby peste pragul de oprire",
"pg_newly_correct": "acum corect",
"pg_regressed": "regresat",
"pg_end_timing_changed": "moment de încheiere schimbat",
@@ -524,6 +537,12 @@
"pg_sweep_param": "Parametru",
"pg_objective": "Obiectiv",
"pg_steps": "Pași",
"pg_stress_group": "Test de terminare în repaus",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Nivel în repaus (W)",
"pg_stress_idle_w_desc": "Înlocuiește nivelul de repaus detectat automat (lăsați gol pentru automat).",
"pg_stress_toggle": "Testați terminarea în repaus",
"pg_stress_toggle_desc": "Simulează aparatul menținut la consumul său în standby după terminarea înregistrării arată dacă și când WashData oprește ciclul.",
"pg_sweep_2d": "Hartă termică 2D",
"pg_sweep_param_y": "Al 2-lea parametru",
"pg_obj_match": "Acuratețea potrivirii",
@@ -566,7 +585,31 @@
"task_split": "Se divide ciclul",
"task_trim": "Se decupează ciclul",
"task_merge": "Se îmbină ciclurile",
"task_rebuild": "Se reconstruiesc anvelopele"
"task_rebuild": "Se reconstruiesc anvelopele",
"cat_profiles": "Profiluri (programe)",
"cat_real_cycles": "Cicluri (istoric de utilizare)",
"cat_reference_cycles": "Cicluri de referință (importate)",
"cat_custom_phases": "Faze personalizate",
"cat_profile_groups": "Grupuri de profiluri",
"cat_settings": "Setări de detectare și recunoaștere",
"cat_matching_config": "Reglarea recunoașterii",
"cat_ml_models": "Modele ML",
"cat_feedback": "Feedback și etichete de revizuire",
"cat_suggestions": "Sugestii",
"cat_maintenance_log": "Jurnal de mentenanță",
"cat_history_logs": "Istoric și jurnale de modificări",
"cat_lifetime_stats": "Totaluri cumulate",
"cycles_short": "cicluri",
"import_mode": "Cum se combină",
"mode_merge": "Îmbină (păstrează-le pe ale mele)",
"mode_replace": "Înlocuiește selecția",
"cycle_destination": "Unde ar trebui să meargă ciclurile importate?",
"dest_reference": "Referință (doar forma)",
"dest_real_history": "Istoric real (contează în statistici)",
"conflict_resolution": "Conflicte de nume",
"conflict_import_copy": "Importă drept copie",
"conflict_keep_mine": "Păstrează-l pe al meu",
"conflict_overwrite": "Suprascrie"
},
"log": {
"all_levels": "Toate nivelurile",
@@ -643,7 +686,9 @@
"force_stop_msg": "Opriți forțat ciclul activ acum? Ciclul va fi salvat ca întrerupt.",
"store_import": "Importați ciclu de referință",
"store_share": "Partajați în magazinul comunității",
"store_share_device": "Partajați acest dispozitiv"
"store_share_device": "Partajați acest dispozitiv",
"export_select": "Exportă - alege datele",
"import_wizard": "Importă - alege datele"
},
"msg": {
"appliance_monitor": "Monitor de aparate",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} cicluri similare neetichetate găsite creați un profil pentru a începe potrivirea lor.",
"cycles_deleted": "{count} ciclu(ri) șterse",
"enough_data": "Date suficiente pentru a învăța ({current}/{min} cicluri).",
"export_description": "Exportați toate profilurile și ciclurile în JSON sau restaurați dintr-un export anterior.",
"export_description": "Alege exact ce profiluri, cicluri, setări și altele să exporți în JSON, sau analizează un fișier și importă doar părțile dorite.",
"feedback_cycles_pending": "{n} ciclu{s} de revizuit",
"feedback_prompt": "Confirmați că a fost corect, corectați programul sau ignorați.",
"feedback_relabel_hint": "Și reetichetarea acestui ciclu îl rezolvă.",
"filter_by_profile": "Filtrează după profil…",
"group_modal_help": "Grupați programe cu aceeași formă care diferă ca temperatură/centrifugare (duratele pot varia). Potrivirea evaluează grupul ca un singur candidat, apoi alege membrul cel mai potrivit. Alegeți cel puțin 2; suprapunerea arată cât de asemănătoare sunt.",
"group_not_cohesive": "Aceste profiluri nu sunt suficient de similare pentru a fi grupate în mod fiabil, deci potrivirea le tratează individual până când eliminați valoarea aberantă sau împărțiți grupul.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Reporniți Home Assistant pentru a activa instrumentele de simulare.",
"pg_sim_progress": "{done} / {total} cicluri",
"pg_starting": "Se pornește…",
"pg_stress_synthetic": "simularea în repaus începe aici",
"pg_sweep_intro": "Ce-ar fi dacă {param} ar fi diferit? Testați {steps} valori pe ultimele {cycles} cicluri pentru a găsi setarea la care se potrivesc corect cele mai multe cicluri.",
"pg_sweep_step": "Pasul {done} / {total}",
"pg_undetected": "{n} ciclu{s} nedetectat",
@@ -837,6 +884,9 @@
"pg_simulating": "Se simulează ciclul…",
"pg_canvas_empty2": "Alege un ciclu de mai sus și apasă Rulează pentru a-l simula. Apoi treci cu cursorul pentru a citi valorile, derulează pentru a mări și trage pentru a te deplasa.",
"pg_apply_settings_confirm": "Salvezi aceste {n} setări pe acest dispozitiv? {list}",
"pg_stress_terminated_detail": "S-a stabilizat la ~{idle}W în standby -- ciclul s-a încheiat {h}h {m}m mai târziu prin {reason}.",
"pg_stress_above_threshold_detail": "Consumul în standby ~{idle}W este egal sau mai mare decât pragul de oprire efectiv ({stop}W) -- ciclul nu a fost niciodată considerat inactiv. Măriți stop_threshold_w pentru a corecta.",
"pg_stress_hit_cap_detail": "Ciclul a rulat {h}h {m}m fără să se oprească -- oprit forțat de limita de siguranță. Consum în standby {idle}W față de pragul de oprire {stop}W.",
"no_logs_match": "Nicio înregistrare din jurnal nu corespunde acestor filtre.",
"pg_analysis_hint2": "Selectați un ciclu și apăsați Rulați pentru a încărca analiza potrivirii.",
"pg_analysis_empty2": "Apăsați Rulați pentru a vedea analiza potrivirii.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Încărcați {brand} {model} cu ciclurile de referință pe care le selectați. Alții cu același aparat vă pot adopta programele. Intrările sunt revizuite înainte de a apărea public.",
"share_profile_no_cycles": "Niciun ciclu de referință -- marcați un ciclu ca ⭐ în fila Cicluri pentru a include acest profil",
"advisory_phase_inconsistent": "'{name}' pare să amestece programe sau temperaturi diferite - ciclurile sale se încălzesc pe durate foarte diferite. Împărțirea sa în profiluri separate (de ex. pe temperatură) va îmbunătăți potrivirea și estimările de timp.",
"advisory_phase_inconsistent_title": "⚠ Posibil programe amestecate"
"advisory_phase_inconsistent_title": "⚠ Posibil programe amestecate",
"export_select_intro": "Bifează exact ce să incluzi. Selectarea profilurilor fără ciclurile lor exportă totuși un program recunoscibil (forma învățată îl însoțește).",
"import_analyze_hint": "Încarcă un fișier exportat (sau lipește JSON-ul acestuia). WashData îl analizează și arată exact ce poate fi importat înainte de orice modificare.",
"analyzing": "Se analizează…",
"device_type_mismatch_warn": "Acest export provine de la un alt tip de electrocasnic ({src} în loc de {local}). Programele și ciclurile pot fi totuși importate ca date de referință, dar setările specifice dispozitivului și importul în istoricul real sunt dezactivate.",
"merge_hint": "Elementele importate sunt adăugate; nimic local nu se pierde. Conflictele de nume se rezolvă mai jos.",
"replace_warn": "Fiecare categorie bifată este ștearsă și înlocuită cu conținutul fișierului. Categoriile nebifate rămân neatinse.",
"dest_reference_hint": "Ciclurile importate îmbunătățesc doar recunoașterea programelor și nu afectează niciodată statisticile de utilizare/energie.",
"dest_real_history_hint": "Ciclurile importate contează ca istoric propriu al acestui dispozitiv și alimentează statisticile de energie/utilizare. Folosește pentru a muta un electrocasnic pe o instalare nouă."
},
"phase_desc": {
"anti_crease": "Tumblări scurte ocazionale după finalizare pentru a reduce șifonarea.",
@@ -962,6 +1020,10 @@
"doc": "Puterea trebuie să scadă sub aceasta între impulsuri pentru ca modul antirid să rămână activ.",
"label": "Prag Putere de Ieșire"
},
"anti_wrinkle_idle_timeout": {
"doc": "Cât timp poate rămâne aparatul în liniște între două impulsuri de rotire înainte ca modul anti-șifonare să se încheie. Setați-l peste cel mai lung interval pe care uscătorul îl lasă între impulsuri, altfel fiecare impuls ulterior este citit ca o pornire falsă.",
"label": "Interval Maxim Între Impulsuri"
},
"anti_wrinkle_max_duration": {
"doc": "Pulsurile mai lungi decât aceasta sunt tratate ca un ciclu real, mai degrabă decât o răsturnare antirid.",
"label": "Durată Maximă"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} ciclu(ri) partajate; {failed} nu au putut fi încărcate.",
"store_device_shared_some_dup": "{created} ciclu(ri) partajate; {dup} erau deja în magazin.",
"store_download_failed": "Descărcare eșuată: {error}",
"store_download_nothing": "Nimic nou de descărcat - această configurație este deja pe dispozitivul dvs."
"store_download_nothing": "Nimic nou de descărcat - această configurație este deja pe dispozitivul dvs.",
"export_selective_done": "Export descărcat",
"import_selective_done": "Importate {profiles} profil(uri) și {cycles} ciclu(ri)"
},
"suggestion": {
"both_agree": "WashData recomandă",
@@ -1706,6 +1770,11 @@
"other": "Altele"
},
"pg_desc": {
"anti_wrinkle_enabled": "Absoarbe impulsurile de rotire de după faza principală în loc să le trateze drept cicluri noi",
"anti_wrinkle_exit_power": "Puterea trebuie să scadă sub această valoare între impulsuri pentru ca modul anti-șifonare să rămână activ",
"anti_wrinkle_idle_timeout": "Timp de liniște permis între două impulsuri de rotire înainte ca modul anti-șifonare să se încheie",
"anti_wrinkle_max_duration": "Un impuls mai lung decât acesta încheie modul anti-șifonare și deschide un ciclu nou",
"anti_wrinkle_max_power": "Un impuls peste această valoare încheie modul anti-șifonare și deschide un ciclu nou",
"completion_min_seconds": "Cea mai scurtă rulare care contează ca ciclu real",
"end_repeat_count": "Citiri scăzute consecutive înainte de încheiere",
"interrupted_min_seconds": "Ciclurile scurte sunt marcate ca întrerupte",
@@ -28,7 +28,9 @@
"awaiting_n": "Ожидает одобрения · {n} подтверждено",
"approved": "Одобрено",
"awaiting": "Ожидает одобрения",
"imported_tip": "Импортировано из магазина сообщества. Используется только для сопоставления, не учитывается в статистике."
"imported_tip": "Импортировано из магазина сообщества. Используется только для сопоставления, не учитывается в статистике.",
"not_importable": "недоступно",
"exists": "существует"
},
"btn": {
"add_device": "+ Добавить устройство",
@@ -169,7 +171,14 @@
"download_device": "Скачать конфигурацию устройства",
"share_device": "Поделиться конфигурацией устройства",
"share_device_tip": "Поделиться программами и настройками этого устройства с сообществом",
"share_n": "Поделиться: {n} программ{s}"
"share_n": "Поделиться: {n} программ{s}",
"export_selected": "Экспорт (выбрать данные)",
"export_all": "Быстрый экспорт всего",
"import_raw": "Расширенно: заменить всё из JSON",
"download_export": "Скачать экспорт",
"analyze_import": "Анализировать файл",
"import_selected": "Импортировать выбранное",
"back": "Назад"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,7 @@
"pg_group_detection": "Триггеры обнаружения",
"pg_group_timing": "Правила времени",
"pg_idle": "Ожидание",
"pg_anti_wrinkle": "Защита от морщин",
"pg_last_run": "Последний запуск: {date}",
"pg_match_score": "Оценка совпадения",
"pg_pct_of": "{p}% из {total}",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Интерактивный график мощности цикла: наведите, чтобы увидеть время/мощность, прокрутите для масштабирования, перетащите для панорамирования",
"pg_alert_timeout_end": "Завершено по тайм-ауту, а не по прогнозу",
"pg_alert_indefinite": "Работал бы бесконечно",
"pg_alert_stress_cap": "Достигнут защитный лимит",
"pg_alert_stress_ok": "Обнаружен режим ожидания: цикл остановлен",
"pg_alert_stress_warn": "Потребление в режиме ожидания выше порога остановки",
"pg_stress_group": "Тест обнаружения режима ожидания",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Уровень в режиме ожидания (Вт)",
"pg_stress_idle_w_desc": "Переопределить автоматически обнаруженный нижний порог потребления в режиме ожидания (оставьте пустым для автоматического определения).",
"pg_stress_toggle": "Тестировать обнаружение режима ожидания",
"pg_stress_toggle_desc": "Имитирует работу устройства в режиме ожидания после завершения записи -- показывает, остановит ли и когда WashData цикл.",
"task_pg_history": "Тест на истории",
"task_pg_sweep": "Оптимизация",
"task_reprocess": "Повторная обработка",
@@ -566,7 +585,31 @@
"task_split": "Разделение цикла",
"task_trim": "Обрезка цикла",
"task_merge": "Объединение циклов",
"task_rebuild": "Пересчёт огибающих"
"task_rebuild": "Пересчёт огибающих",
"cat_profiles": "Профили (программы)",
"cat_real_cycles": "Циклы (история запусков)",
"cat_reference_cycles": "Справочные циклы (импортированные)",
"cat_custom_phases": "Пользовательские фазы",
"cat_profile_groups": "Группы профилей",
"cat_settings": "Настройки обнаружения и распознавания",
"cat_matching_config": "Тонкая настройка распознавания",
"cat_ml_models": "ML-модели",
"cat_feedback": "Отзывы и метки проверки",
"cat_suggestions": "Предложения",
"cat_maintenance_log": "Журнал обслуживания",
"cat_history_logs": "Журналы истории и изменений",
"cat_lifetime_stats": "Итоги за всё время",
"cycles_short": "циклов",
"import_mode": "Как объединить",
"mode_merge": "Объединить (сохранить мои)",
"mode_replace": "Заменить выбранное",
"cycle_destination": "Куда поместить импортированные циклы?",
"dest_reference": "Справочные (только форма)",
"dest_real_history": "Реальная история (учитывается в статистике)",
"conflict_resolution": "Конфликты имён",
"conflict_import_copy": "Импортировать как копию",
"conflict_keep_mine": "Сохранить мои",
"conflict_overwrite": "Перезаписать"
},
"log": {
"all_levels": "Все уровни",
@@ -643,7 +686,9 @@
"force_stop_msg": "Принудительно остановить активный цикл сейчас? Цикл будет сохранён как прерванный.",
"store_import": "Импорт эталонного цикла",
"store_share": "Поделиться в магазине сообщества",
"store_share_device": "Поделиться конфигурацией устройства"
"store_share_device": "Поделиться конфигурацией устройства",
"export_select": "Экспорт - выбор данных",
"import_wizard": "Импорт - выбор данных"
},
"msg": {
"appliance_monitor": "Монитор прибора",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Найдено {count} похожих непомеченных циклов – создайте профиль, чтобы начать их сопоставление.",
"cycles_deleted": "Удалено циклов: {count}",
"enough_data": "Достаточно данных для обучения ({current}/{min} циклов).",
"export_description": "Экспортируйте все профили и циклы в JSON или восстановите данные из предыдущего экспорта.",
"export_description": "Выберите, какие именно профили, циклы, настройки и прочее экспортировать в JSON, или проанализируйте файл и импортируйте только нужные части.",
"feedback_cycles_pending": "{n} цикл{s} для проверки",
"feedback_prompt": "Подтвердите правильность, исправьте программу или проигнорируйте.",
"feedback_relabel_hint": "Повторная маркировка этого цикла также решает его.",
"filter_by_profile": "Фильтр по профилю…",
"group_modal_help": "Групповые программы одинаковой формы, отличающиеся температурой/отжимом (продолжительность может варьироваться). При сопоставлении группа оценивается как один кандидат, а затем выбирается наиболее подходящий член. Выберите хотя бы 2; наложение показывает, насколько они похожи.",
"group_not_cohesive": "Эти профили недостаточно похожи, чтобы их можно было надежно сгруппировать, поэтому сопоставление обрабатывает их индивидуально, пока вы не удалите выброс или не разделите группу.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Перезапустите Home Assistant, чтобы включить инструменты симуляции.",
"pg_sim_progress": "{done} / {total} циклов",
"pg_starting": "Запуск…",
"pg_stress_synthetic": "здесь начинается симуляция режима ожидания",
"pg_sweep_intro": "Что если бы {param} было другим? Проверьте {steps} значений на ваших последних {cycles} циклах, чтобы найти настройку, при которой правильно сопоставляется больше всего циклов.",
"pg_sweep_step": "Шаг {done} / {total}",
"pg_undetected": "{n} цикл{s} не обнаружено",
@@ -837,6 +884,9 @@
"pg_simulating": "Моделирование цикла…",
"pg_canvas_empty2": "Выберите цикл выше и нажмите «Запустить», чтобы смоделировать его. Затем наведите указатель, чтобы увидеть значения, прокрутите для масштабирования и перетащите для панорамирования.",
"pg_apply_settings_confirm": "Сохранить эти настройки ({n}) в это устройство? {list}",
"pg_stress_terminated_detail": "Стабилизировалось на ~{idle}W в режиме ожидания -- цикл завершился {h} ч {m} мин позже через {reason}.",
"pg_stress_above_threshold_detail": "Потребление в режиме ожидания ~{idle}W равно или выше фактического порога остановки ({stop}W) -- цикл так и не был зафиксирован как неактивный. Чтобы исправить, увеличьте stop_threshold_w.",
"pg_stress_hit_cap_detail": "Цикл работал {h} ч {m} мин без остановки -- принудительно остановлен защитным лимитом. Потребление в режиме ожидания {idle}W по сравнению с порогом остановки {stop}W.",
"no_logs_match": "Нет записей журнала, соответствующих этим фильтрам.",
"pg_analysis_hint2": "Выберите цикл и нажмите Запустить, чтобы загрузить анализ совпадения.",
"pg_analysis_empty2": "Нажмите Запустить, чтобы увидеть анализ совпадения.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Поделиться программами вашего устройства (профили + эталонные циклы) с сообществом. Настройки являются необязательными.",
"share_profile_no_cycles": "Профиль '{p}' не имеет ⭐ эталонных циклов -- он будет пропущен, если у вас нет {n}+ подтверждённых запусков",
"advisory_phase_inconsistent": "Похоже, что '{name}' смешивает разные программы или температуры - его циклы нагреваются в течение очень разного времени. Разделение на отдельные профили (напр. по температуре) улучшит сопоставление и оценки времени.",
"advisory_phase_inconsistent_title": "⚠ Возможно, смешанные программы"
"advisory_phase_inconsistent_title": "⚠ Возможно, смешанные программы",
"export_select_intro": "Отметьте, что именно включить. Выбор профилей без их циклов всё равно экспортирует распознаваемую программу (её изученная форма передаётся вместе с ней).",
"import_analyze_hint": "Загрузите экспортированный файл (или вставьте его JSON). WashData проанализирует его и покажет, что именно можно импортировать, прежде чем что-либо изменится.",
"analyzing": "Анализ…",
"device_type_mismatch_warn": "Этот экспорт получен от прибора другого типа ({src} вместо {local}). Программы и циклы всё равно можно импортировать как справочные данные, но настройки, зависящие от устройства, и импорт реальной истории отключены.",
"merge_hint": "Импортируемые элементы добавляются; ничего локального не теряется. Конфликты имён разрешаются ниже.",
"replace_warn": "Каждая отмеченная категория очищается и заменяется данными из файла. Неотмеченные категории остаются без изменений.",
"dest_reference_hint": "Импортированные циклы только улучшают распознавание программ и никогда не влияют на статистику использования/энергии.",
"dest_real_history_hint": "Импортированные циклы считаются собственной историей этого устройства и учитываются в статистике энергии/использования. Используйте для переноса одного прибора в новую установку."
},
"phase_desc": {
"anti_crease": "Периодические короткие вращения барабана после завершения для уменьшения складок.",
@@ -962,6 +1020,10 @@
"doc": "Чтобы режим защиты от морщин оставался активным, мощность должна падать ниже этого значения между импульсами.",
"label": "Порог Мощности Выхода"
},
"anti_wrinkle_idle_timeout": {
"label": "Макс. Пауза Между Импульсами",
"doc": "Как долго машина может оставаться в тишине между двумя импульсами вращения барабана, прежде чем режим защиты от морщин завершится. Установите выше самой длинной паузы, которую ваша сушильная машина оставляет между импульсами, иначе каждый последующий импульс будет воспринят как ложный запуск."
},
"anti_wrinkle_max_duration": {
"doc": "Пульс, превышающий этот, рассматривается как настоящий цикл, а не как борьба с морщинами.",
"label": "Макс. Длительность"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "Частичная публикация: {n} программ{s} загружено, {failed} пропущено",
"store_device_shared_some_dup": "Конфигурация устройства опубликована: {n} программ{s} загружено ({dup} уже существовало)",
"store_download_failed": "Ошибка загрузки: {error}",
"store_download_nothing": "Нечего загружать -- все профили уже существуют на этом устройстве"
"store_download_nothing": "Нечего загружать -- все профили уже существуют на этом устройстве",
"export_selective_done": "Экспорт скачан",
"import_selective_done": "Импортировано профилей: {profiles}, циклов: {cycles}"
},
"suggestion": {
"both_agree": "WashData рекомендует",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Секунды выше порога для подтверждения старта",
"start_threshold_w": "Минимум ватт, чтобы считать запущенным",
"stop_threshold_w": "Ниже этого машина считается выключенной",
"anti_wrinkle_enabled": "Поглощать импульсы вращения барабана после основной фазы, а не считать их новыми циклами",
"anti_wrinkle_exit_power": "Мощность между импульсами должна падать ниже этого, чтобы защита от морщин оставалась активной",
"anti_wrinkle_idle_timeout": "Допустимая тишина между двумя импульсами вращения барабана, прежде чем защита от морщин завершится",
"anti_wrinkle_max_duration": "Импульс длиннее этого завершает защиту от морщин и открывает новый цикл",
"anti_wrinkle_max_power": "Импульс выше этого завершает защиту от морщин и открывает новый цикл",
"dtw_bandwidth": "Этап 3: полоса деформации Sakoe-Chiba (0 = DTW выключен; по умолчанию 0,2 = 20% длины цикла)",
"corr_weight": "Этап 2: баланс между формой кривой (корреляция) и уровнем мощности (MAE); по умолчанию 0,45",
"duration_weight": "Этап 4: насколько сильно совпадение по длительности влияет на итоговый балл (по умолчанию 0,22)",
@@ -28,7 +28,9 @@
"awaiting_n": "Čaká na schválenie · potvrdené {n}",
"approved": "Schválené",
"awaiting": "Čaká na schválenie",
"imported_tip": "Importované z komunitného obchodu. Používa sa iba na porovnávanie, nezapočítava sa do štatistík."
"imported_tip": "Importované z komunitného obchodu. Používa sa iba na porovnávanie, nezapočítava sa do štatistík.",
"not_importable": "nedostupné",
"exists": "existuje"
},
"btn": {
"add_device": "+ Pridať zariadenie",
@@ -169,7 +171,14 @@
"download_device": "Stiahnuť nastavenie zariadenia",
"share_device": "Zdieľať nastavenie zariadenia",
"share_device_tip": "Zdieľať programy a nastavenia tohto zariadenia s komunitou",
"share_n": "Zdieľať {n} program{s}"
"share_n": "Zdieľať {n} program{s}",
"export_selected": "Export (vybrať údaje)",
"export_all": "Rýchly export všetkého",
"import_raw": "Pokročilé: nahradiť všetko zo súboru JSON",
"download_export": "Stiahnuť export",
"analyze_import": "Analyzovať súbor",
"import_selected": "Importovať vybrané",
"back": "Späť"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Nájdená najlepšia hodnota",
"pg_current_value": "Aktuálna hodnota:",
"pg_drag_hint": "Potiahnite čiaru v grafe",
"pg_anti_wrinkle": "Ochrana pred krčením",
"pg_dtw": "DTW",
"pg_ev_ending": "Ukončovanie",
"pg_ev_running": "Beží",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktívny graf výkonu cyklu: prejdením myšou zobrazíte čas/výkon, kolieskom priblížite, ťahaním posuniete",
"pg_alert_timeout_end": "Ukončené časovým limitom, nie predikciou",
"pg_alert_indefinite": "Bežalo by donekonečna",
"pg_alert_stress_cap": "Dosiahnutý bezpečnostný limit",
"pg_alert_stress_ok": "Detekcia nečinnosti: cyklus zastavený",
"pg_alert_stress_warn": "Príkon v nečinnosti nad prahom zastavenia",
"pg_stress_group": "Test detekcie nečinnosti",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Úroveň nečinnosti (W)",
"pg_stress_idle_w_desc": "Prepísať automaticky zistený spodný limit pohotovostného príkonu (ponechajte prázdne pre automatické zistenie).",
"pg_stress_toggle": "Testovať detekciu nečinnosti",
"pg_stress_toggle_desc": "Simuluje, že spotrebič zostane pri svojom pohotovostnom príkone po skončení záznamu -- zobrazí, či a kedy WashData cyklus zastaví.",
"task_pg_history": "Test na histórii",
"task_pg_sweep": "Optimalizovať",
"task_reprocess": "Opätovné spracovanie",
@@ -566,7 +585,31 @@
"task_split": "Rozdeľovanie cyklu",
"task_trim": "Orezávanie cyklu",
"task_merge": "Zlučovanie cyklov",
"task_rebuild": "Prepočítavanie obálok"
"task_rebuild": "Prepočítavanie obálok",
"cat_profiles": "Profily (programy)",
"cat_real_cycles": "Cykly (história prevádzky)",
"cat_reference_cycles": "Referenčné cykly (importované)",
"cat_custom_phases": "Vlastné fázy",
"cat_profile_groups": "Skupiny profilov",
"cat_settings": "Nastavenia detekcie a rozpoznávania",
"cat_matching_config": "Ladenie rozpoznávania",
"cat_ml_models": "Modely ML",
"cat_feedback": "Spätná väzba a hodnotiace štítky",
"cat_suggestions": "Návrhy",
"cat_maintenance_log": "Protokol údržby",
"cat_history_logs": "Protokoly histórie a zmien",
"cat_lifetime_stats": "Celkové súčty",
"cycles_short": "cyklov",
"import_mode": "Ako skombinovať",
"mode_merge": "Zlúčiť (ponechať moje)",
"mode_replace": "Nahradiť vybrané",
"cycle_destination": "Kam majú importované cykly patriť?",
"dest_reference": "Referenčné (iba krivka)",
"dest_real_history": "Skutočná história (počíta sa do štatistík)",
"conflict_resolution": "Konflikty názvov",
"conflict_import_copy": "Importovať ako kópiu",
"conflict_keep_mine": "Ponechať moje",
"conflict_overwrite": "Prepísať"
},
"log": {
"all_levels": "Všetky úrovne",
@@ -643,7 +686,9 @@
"force_stop_msg": "Vynútiť teraz zastavenie aktívneho cyklu? Cyklus sa uloží ako prerušený.",
"store_import": "Importovať referenčný cyklus",
"store_share": "Zdieľať do komunitného obchodu",
"store_share_device": "Zdieľať nastavenie zariadenia"
"store_share_device": "Zdieľať nastavenie zariadenia",
"export_select": "Export - vybrať údaje",
"import_wizard": "Import - vybrať údaje"
},
"msg": {
"appliance_monitor": "Monitor spotrebiča",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Nájdených {count} podobných neoznačených cyklov vytvorte profil, aby sa začali párovať.",
"cycles_deleted": "Odstránených cyklov: {count}",
"enough_data": "Dostatok dát na učenie ({current}/{min} cyklov).",
"export_description": "Exportujte všetky profily a cykly do JSON alebo obnovte z predchádzajúceho exportu.",
"export_description": "Vyberte presne, ktoré profily, cykly, nastavenia a ďalšie údaje exportovať do súboru JSON, alebo analyzujte súbor a importujte len tie časti, ktoré chcete.",
"feedback_cycles_pending": "{n} cyklov na kontrolu",
"feedback_prompt": "Potvrďte správnosť, opravte program alebo ignorujte.",
"feedback_relabel_hint": "Opätovné označenie tohto cyklu ho tiež vyrieši.",
"filter_by_profile": "Filtrovať podľa profilu…",
"group_modal_help": "Skupinové programy s rovnakým tvarom, ktoré sa líšia teplotou/odstreďovaním (trvanie sa môže líšiť). Porovnanie ohodnotí skupinu ako jedného kandidáta a potom vyberie najlepšieho člena. Vyberte aspoň 2; prekrytie ukazuje, nakoľko sú si podobné.",
"group_not_cohesive": "Tieto profily nie sú dostatočne podobné na to, aby sa dali spoľahlivo zoskupiť, takže zhoda s nimi zaobchádza individuálne, kým neodstránite odľahlú hodnotu alebo nerozdelíte skupinu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Reštartujte Home Assistant, aby ste povolili nástroje simulácie.",
"pg_sim_progress": "{done} / {total} cyklov",
"pg_starting": "Spúšťanie…",
"pg_stress_synthetic": "simulácia nečinnosti začína tu",
"pg_sweep_intro": "Čo keby bol {param} iný? Otestujte {steps} hodnôt na vašich posledných {cycles} cykloch a nájdite nastavenie, pri ktorom je správne priradených najviac cyklov.",
"pg_sweep_step": "Krok {done} / {total}",
"pg_undetected": "{n} nezistených cyklov",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulácia cyklu…",
"pg_canvas_empty2": "Vyberte cyklus vyššie a stlačením Spustiť ho nasimulujte. Potom prejdením myšou zobrazíte hodnoty, kolieskom priblížite a ťahaním posuniete.",
"pg_apply_settings_confirm": "Uložiť týchto {n} nastavení do tohto zariadenia? {list}",
"pg_stress_terminated_detail": "Ustálil sa na ~{idle}W v nečinnosti -- cyklus sa skončil o {h} h {m} min neskôr cez {reason}.",
"pg_stress_above_threshold_detail": "Príkon v nečinnosti ~{idle}W je rovný alebo vyšší než efektívny prah zastavenia ({stop}W) -- cyklus sa nikdy nezaregistroval ako pokojový. Opravte to zvýšením stop_threshold_w.",
"pg_stress_hit_cap_detail": "Cyklus bežal {h} h {m} min bez zastavenia -- vynútene zastavený bezpečnostným limitom. Príkon v nečinnosti {idle}W oproti prahu zastavenia {stop}W.",
"no_logs_match": "Týmto filtrom nezodpovedajú žiadne záznamy protokolu.",
"pg_analysis_hint2": "Vyberte cyklus a stlačte Spustiť na načítanie analýzy zhody.",
"pg_analysis_empty2": "Stlačte Spustiť na zobrazenie analýzy zhody.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Zdieľať programy vášho zariadenia (profily + referenčné cykly) s komunitou. Nastavenia sú voliteľné.",
"share_profile_no_cycles": "Profil '{p}' nemá žiadne ⭐ referenčné cykly -- bude preskočený, ak nemáte {n}+ potvrdených spustení",
"advisory_phase_inconsistent": "Zdá sa, že '{name}' mieša rôzne programy alebo teploty - jeho cykly sa ohrievajú veľmi rôzne dlho. Rozdelenie na samostatné profily (napr. podľa teploty) zlepší zhodu aj odhady času.",
"advisory_phase_inconsistent_title": "⚠ Možno zmiešané programy"
"advisory_phase_inconsistent_title": "⚠ Možno zmiešané programy",
"export_select_intro": "Zaškrtnite presne to, čo sa má zahrnúť. Výber profilov bez ich cyklov aj tak exportuje rozpoznateľný program (jeho naučená krivka sa prenesie spolu s ním).",
"import_analyze_hint": "Načítajte exportovaný súbor (alebo vložte jeho JSON). WashData ho analyzuje a presne ukáže, čo možno importovať, skôr než dôjde k akejkoľvek zmene.",
"analyzing": "Analyzujem…",
"device_type_mismatch_warn": "Tento export pochádza z iného typu spotrebiča ({src} vs {local}). Programy a cykly možno stále importovať ako referenčné údaje, ale nastavenia špecifické pre zariadenie a import skutočnej histórie sú zakázané.",
"merge_hint": "Importované položky sa pridajú; nič miestne sa nestratí. Konflikty názvov sa riešia nižšie.",
"replace_warn": "Každá zaškrtnutá kategória sa vymaže a nahradí údajmi zo súboru. Nezaškrtnuté kategórie zostanú bez zmeny.",
"dest_reference_hint": "Importované cykly len zlepšujú rozpoznávanie programov a nikdy neovplyvňujú štatistiky spotreby/energie.",
"dest_real_history_hint": "Importované cykly sa počítajú ako vlastná história tohto zariadenia a prispievajú do štatistík energie/spotreby. Použite pri presune jedného spotrebiča do novej inštalácie."
},
"phase_desc": {
"anti_crease": "Príležitostné krátke vírenia po dokončení na predchádzanie krčeniu oblečenia.",
@@ -962,6 +1020,10 @@
"doc": "Aby režim proti vráskam zostal aktívny, musí výkon medzi impulzmi klesnúť pod túto hodnotu.",
"label": "Prah výkonu ukončenia"
},
"anti_wrinkle_idle_timeout": {
"doc": "Ako dlho môže stroj medzi dvoma impulzmi bubna zostať v pokoji, než sa režim ochrany pred krčením skončí. Nastavte ju nad najdlhšiu medzeru, ktorú vaša sušička medzi impulzmi ponecháva, inak sa každý ďalší impulz vyhodnotí ako falošné spustenie.",
"label": "Max. medzera medzi impulzmi"
},
"anti_wrinkle_max_duration": {
"doc": "Dlhšie pulzy sa považujú za skutočný cyklus a nie za protivráskové.",
"label": "Max. trvanie"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Čiastočné zdieľanie: {n} program{s} nahraný, {failed} preskočené",
"store_device_shared_some_dup": "Nastavenie zariadenia zdieľané: {n} program{s} nahraný ({dup} už existovalo)",
"store_download_failed": "Sťahovanie zlyhalo: {error}",
"store_download_nothing": "Nič na stiahnutie -- všetky profily na tomto zariadení už existujú"
"store_download_nothing": "Nič na stiahnutie -- všetky profily na tomto zariadení už existujú",
"export_selective_done": "Export stiahnutý",
"import_selective_done": "Importovaných {profiles} profilov a {cycles} cyklov"
},
"trend": {
"down": "Klesajúci trend",
@@ -1706,6 +1770,11 @@
"other": "Iné"
},
"pg_desc": {
"anti_wrinkle_enabled": "Pohltiť impulzy bubna po hlavnej fáze namiesto ich čítania ako nové cykly",
"anti_wrinkle_exit_power": "Výkon musí medzi impulzmi klesnúť pod túto hodnotu, aby ochrana pred krčením zostala aktívna",
"anti_wrinkle_idle_timeout": "Povolený pokoj medzi dvoma impulzmi bubna, než sa ochrana pred krčením skončí",
"anti_wrinkle_max_duration": "Dlhší impulz ukončí ochranu pred krčením a otvorí nový cyklus",
"anti_wrinkle_max_power": "Impulz nad touto hodnotou ukončí ochranu pred krčením a otvorí nový cyklus",
"completion_min_seconds": "Najkratší beh, ktorý sa počíta ako skutočný cyklus",
"end_repeat_count": "Nízke čítania za sebou pred ukončením",
"interrupted_min_seconds": "Krátke cykly označené ako prerušené",
@@ -28,7 +28,9 @@
"awaiting_n": "Čaka na odobritev · potrjeno {n}",
"approved": "Odobreno",
"awaiting": "Čaka na odobritev",
"imported_tip": "Uvoženo iz skupnostne trgovine. Uporablja se samo za ujemanje, ne šteje se v statistiko."
"imported_tip": "Uvoženo iz skupnostne trgovine. Uporablja se samo za ujemanje, ne šteje se v statistiko.",
"not_importable": "ni na voljo",
"exists": "obstaja"
},
"btn": {
"add_device": "+ Dodaj napravo",
@@ -169,7 +171,14 @@
"download_device": "Prenesi nastavitve naprave",
"share_device": "Deli nastavitve naprave",
"share_device_tip": "Deli programe in nastavitve te naprave s skupnostjo",
"share_n": "Deli {n} program{s}"
"share_n": "Deli {n} program{s}",
"export_selected": "Izvoz (izberi podatke)",
"export_all": "Hitri izvoz vsega",
"import_raw": "Napredno: zamenjaj vse iz datoteke JSON",
"download_export": "Prenesi izvoz",
"analyze_import": "Analiziraj datoteko",
"import_selected": "Uvozi izbrano",
"back": "Nazaj"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -394,6 +403,7 @@
"pg_best_value": "Najdena najboljša vrednost",
"pg_current_value": "Trenutna vrednost:",
"pg_drag_hint": "Povlecite črto na grafu",
"pg_anti_wrinkle": "Zaščita pred gubami",
"pg_dtw": "DTW",
"pg_ev_ending": "Zaključevanje",
"pg_ev_running": "Deluje",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktivni graf moči cikla: pomaknite kazalec za prikaz časa/moči, drsajte za povečavo, povlecite za premikanje",
"pg_alert_timeout_end": "Končano zaradi časovne omejitve, ne na podlagi napovedi",
"pg_alert_indefinite": "Deloval bi v nedogled",
"pg_alert_stress_cap": "Dosežena varnostna meja",
"pg_alert_stress_ok": "Zaznavanje mirovanja: cikel ustavljen",
"pg_alert_stress_warn": "Poraba v mirovanju nad pragom ustavitve",
"pg_stress_group": "Preizkus zaznave mirovanja",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Raven mirovanja (W)",
"pg_stress_idle_w_desc": "Prepiši samodejno zaznano vrednost stanja pripravljenosti (pusti prazno za samodejno zaznavo).",
"pg_stress_toggle": "Preizkusi zaznavo mirovanja",
"pg_stress_toggle_desc": "Simulira, da naprava ostane pri porabi v stanju pripravljenosti po koncu snemanja -- prikaže, ali in kdaj WashData ustavi cikel.",
"task_pg_history": "Preizkus na zgodovini",
"task_pg_sweep": "Optimizacija",
"task_reprocess": "Ponovna obdelava",
@@ -566,7 +585,31 @@
"task_split": "Deljenje cikla",
"task_trim": "Obrezovanje cikla",
"task_merge": "Združevanje ciklov",
"task_rebuild": "Ponovni izračun ovojnic"
"task_rebuild": "Ponovni izračun ovojnic",
"cat_profiles": "Profili (programi)",
"cat_real_cycles": "Cikli (zgodovina delovanja)",
"cat_reference_cycles": "Referenčni cikli (uvoženi)",
"cat_custom_phases": "Faze po meri",
"cat_profile_groups": "Skupine profilov",
"cat_settings": "Nastavitve zaznavanja in prepoznavanja",
"cat_matching_config": "Uravnavanje prepoznavanja",
"cat_ml_models": "Modeli ML",
"cat_feedback": "Oznake povratnih informacij in pregleda",
"cat_suggestions": "Predlogi",
"cat_maintenance_log": "Dnevnik vzdrževanja",
"cat_history_logs": "Dnevniki zgodovine in sprememb",
"cat_lifetime_stats": "Skupni seštevki",
"cycles_short": "ciklov",
"import_mode": "Kako združiti",
"mode_merge": "Združi (obdrži moje)",
"mode_replace": "Zamenjaj izbrano",
"cycle_destination": "Kam naj gredo uvoženi cikli?",
"dest_reference": "Referenca (samo oblika)",
"dest_real_history": "Dejanska zgodovina (šteje v statistiko)",
"conflict_resolution": "Spori imen",
"conflict_import_copy": "Uvozi kot kopijo",
"conflict_keep_mine": "Obdrži moje",
"conflict_overwrite": "Prepiši"
},
"log": {
"all_levels": "Vse ravni",
@@ -643,7 +686,9 @@
"force_stop_msg": "Zdaj prisilno ustavim aktivni cikel? Cikel bo shranjen kot prekinjen.",
"store_import": "Uvozi referenčni cikel",
"store_share": "Deli v skupnostno trgovino",
"store_share_device": "Deli nastavitve naprave"
"store_share_device": "Deli nastavitve naprave",
"export_select": "Izvoz - izberi podatke",
"import_wizard": "Uvoz - izberi podatke"
},
"msg": {
"appliance_monitor": "Monitor naprave",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Najdenih {count} podobnih neoznačenih ciklov ustvarite profil za začetek ujemanja.",
"cycles_deleted": "Izbrisanih ciklov: {count}",
"enough_data": "Dovolj podatkov za učenje ({current}/{min} ciklov).",
"export_description": "Izvozite vse profile in cikle v JSON ali obnovite iz prejšnjega izvoza.",
"export_description": "Izberite natančno, katere profile, cikle, nastavitve in drugo želite izvoziti v datoteko JSON, ali analizirajte datoteko in uvozite samo želene dele.",
"feedback_cycles_pending": "{n} ciklov za pregled",
"feedback_prompt": "Potrdite pravilnost, popravite program ali prezrite.",
"feedback_relabel_hint": "Ponovno označevanje tega cikla ga prav tako razreši.",
"filter_by_profile": "Filtriraj po profilu…",
"group_modal_help": "Skupinske programe enake oblike, ki se razlikujejo po temperaturi/ožemanju (trajanje se lahko razlikuje). Ujemanje oceni skupino kot enega kandidata, nato pa izbere najbolj primernega člana. Izberite vsaj 2; prekrivanje kaže, kako si sta podobna.",
"group_not_cohesive": "Ti profili si niso dovolj podobni, da bi jih lahko zanesljivo združevali, zato jih ujemanje obravnava posamično, dokler ne odstranite odstopanja ali razdelite skupine.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Znova zaženite Home Assistant, da omogočite orodja simulacije.",
"pg_sim_progress": "{done} / {total} ciklov",
"pg_starting": "Zaganjanje…",
"pg_stress_synthetic": "simulacija mirovanja se začne tukaj",
"pg_sweep_intro": "Kaj če bi bil {param} drugačen? Preizkusite {steps} vrednosti na svojih zadnjih {cycles} ciklih in poiščite nastavitev, pri kateri se pravilno ujema največ ciklov.",
"pg_sweep_step": "Korak {done} / {total}",
"pg_undetected": "{n} nezaznanih ciklov",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuliranje cikla…",
"pg_canvas_empty2": "Izberite cikel zgoraj in pritisnite Zaženi za simulacijo. Nato pomaknite kazalec za prikaz vrednosti, drsajte za povečavo in povlecite za premikanje.",
"pg_apply_settings_confirm": "Shrani teh {n} nastavitev v to napravo? {list}",
"pg_stress_terminated_detail": "Ustalil se je na ~{idle}W mirovanja -- cikel se je končal {h} h {m} min pozneje prek {reason}.",
"pg_stress_above_threshold_detail": "Poraba v mirovanju ~{idle}W je enaka ali višja od dejanskega praga ustavitve ({stop}W) -- cikel se nikoli ni zaznal kot mirujoč. Za odpravo zvišajte stop_threshold_w.",
"pg_stress_hit_cap_detail": "Cikel je tekel {h} h {m} min brez ustavitve -- prisilno ustavljen z varnostno mejo. Poraba v mirovanju {idle}W v primerjavi s pragom ustavitve {stop}W.",
"no_logs_match": "Noben zapis dnevnika ne ustreza tem filtrom.",
"pg_analysis_hint2": "Izberite cikel in pritisnite Zaženi za nalaganje analize ujemanja.",
"pg_analysis_empty2": "Pritisnite Zaženi za prikaz analize ujemanja.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Deli programe svoje naprave (profile + referenčne cikle) s skupnostjo. Nastavitve so neobvezne.",
"share_profile_no_cycles": "Profil '{p}' nima ⭐ referenčnih ciklov -- preskočen bo, razen če imate {n}+ potrjenih zagonov",
"advisory_phase_inconsistent": "Videti je, da '{name}' meša različne programe ali temperature - njegovi cikli se segrevajo zelo različno dolgo. Če ga razdelite na ločene profile (npr. po temperaturi), boste izboljšali ujemanje in ocene časa.",
"advisory_phase_inconsistent_title": "⚠ Morda pomešani programi"
"advisory_phase_inconsistent_title": "⚠ Morda pomešani programi",
"export_select_intro": "Označite natančno, kaj naj bo vključeno. Izbira profilov brez njihovih ciklov še vedno izvozi prepoznaven program (njegova naučena oblika se prenese z njim).",
"import_analyze_hint": "Naložite izvoženo datoteko (ali prilepite njen JSON). WashData jo analizira in natančno pokaže, kaj je mogoče uvoziti, preden se karkoli spremeni.",
"analyzing": "Analiziranje…",
"device_type_mismatch_warn": "Ta izvoz je iz druge vrste naprave ({src} vs {local}). Programe in cikle je še vedno mogoče uvoziti kot referenčne podatke, vendar so nastavitve, specifične za napravo, in uvoz dejanske zgodovine onemogočeni.",
"merge_hint": "Uvoženi elementi se dodajo; nič lokalnega se ne izgubi. Spori imen se rešujejo spodaj.",
"replace_warn": "Vsaka označena kategorija se izbriše in zamenja s podatki iz datoteke. Neoznačene kategorije ostanejo nespremenjene.",
"dest_reference_hint": "Uvoženi cikli le izboljšajo prepoznavanje programov in nikoli ne vplivajo na statistiko porabe/energije.",
"dest_real_history_hint": "Uvoženi cikli se štejejo kot lastna zgodovina te naprave in napajajo statistiko energije/porabe. Uporabite pri prenosu ene naprave na novo namestitev."
},
"phase_desc": {
"anti_crease": "Občasni kratki premiki po zaključku za preprečevanje gub na oblačilih.",
@@ -962,6 +1020,10 @@
"doc": "Moč mora med impulzi pasti pod to vrednost, da način proti gubam ostane aktiven.",
"label": "Prag izhodne moči"
},
"anti_wrinkle_idle_timeout": {
"doc": "Kako dolgo lahko stroj med dvema vrtilnima impulzoma ostane tiho, preden se način zaščite pred gubami konča. Nastavite ga nad najdaljši razmik, ki ga vaš sušilni stroj pusti med impulzi, sicer se vsak nadaljnji impulz obravnava kot lažni zagon.",
"label": "Maks. razmik med impulzi"
},
"anti_wrinkle_max_duration": {
"doc": "Utripi, ki so daljši od tega, se obravnavajo kot pravi cikel in ne kot sredstvo proti gubam.",
"label": "Maks. trajanje"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Delno deljenje: {n} program{s} naložen, {failed} preskočenih",
"store_device_shared_some_dup": "Nastavitve naprave deljene: {n} program{s} naložen ({dup} že obstaja)",
"store_download_failed": "Prenos ni uspel: {error}",
"store_download_nothing": "Ni ničesar za prenos -- vsi profili že obstajajo na tej napravi"
"store_download_nothing": "Ni ničesar za prenos -- vsi profili že obstajajo na tej napravi",
"export_selective_done": "Izvoz prenesen",
"import_selective_done": "Uvoženih {profiles} profilov in {cycles} ciklov"
},
"trend": {
"down": "Padajoči trend",
@@ -1706,6 +1770,11 @@
"other": "Drugo"
},
"pg_desc": {
"anti_wrinkle_enabled": "Vsrkaj vrtilne impulze po glavni fazi, namesto da jih obravnavaš kot nove cikle",
"anti_wrinkle_exit_power": "Moč mora med impulzi pasti pod to vrednost, da zaščita pred gubami ostane aktivna",
"anti_wrinkle_idle_timeout": "Dovoljeni mir med dvema vrtilnima impulzoma, preden se zaščita pred gubami konča",
"anti_wrinkle_max_duration": "Daljši impulz konča zaščito pred gubami in odpre nov cikel",
"anti_wrinkle_max_power": "Impulz nad to vrednostjo konča zaščito pred gubami in odpre nov cikel",
"completion_min_seconds": "Najkrajši zagon, ki šteje kot pravi cikel",
"end_repeat_count": "Zaporedni nizki odčitki pred koncem",
"interrupted_min_seconds": "Kratki cikli, označeni kot prekinjeni",
@@ -28,7 +28,9 @@
"awaiting_n": "Në pritje të miratimit · {n} të konfirmuara",
"approved": "Miratuar",
"awaiting": "Në pritje të miratimit",
"imported_tip": "Importuar nga dyqani i komunitetit. Përdoret vetëm për përputhje, nuk llogaritet në statistika."
"imported_tip": "Importuar nga dyqani i komunitetit. Përdoret vetëm për përputhje, nuk llogaritet në statistika.",
"not_importable": "s'aplikohet këtu",
"exists": "ekziston"
},
"btn": {
"add_device": "+ Shto pajisje",
@@ -169,7 +171,14 @@
"download_device": "Shkarko këtë konfigurim",
"share_device": "⬆ Ndaj këtë pajisje",
"share_device_tip": "Ndani këtë pajisje dhe ciklet e saj të referencës të regjistruara në dyqanin e komunitetit, që të tjerët me të njëjtën makinë t'i adoptojnë",
"share_n": "Ndaj {n} cikël(-ë)"
"share_n": "Ndaj {n} cikël(-ë)",
"export_selected": "Eksporto (zgjidh të dhënat)",
"export_all": "Eksportim i shpejtë i gjithçkaje",
"import_raw": "I avancuar: zëvendëso gjithçka nga JSON",
"download_export": "Shkarko eksportin",
"analyze_import": "Analizo skedarin",
"import_selected": "Importo të zgjedhurat",
"back": "Prapa"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Skeda e parazgjedhur",
"panel_language": "Gjuha e panelit",
"per_cycle_short": "cikël",
"pg_anti_wrinkle": "Kundër rrudhave",
"pg_autofill_tip": "Mbush automatikisht intervalin nga vlera aktuale ({lo}{hi})",
"pg_best_value": "Vlera më e mirë e gjetur",
"pg_current_value": "Vlera aktuale:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parametër",
"pg_objective": "Objektivi",
"pg_steps": "Hapat",
"pg_stress_group": "Testi i ndërprerjes me gjendje statike",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Niveli i gjendjes statike (W)",
"pg_stress_idle_w_desc": "Anuloni vlerën minimale të gjendjes statike të zbuluar automatikisht (lini bosh për automatik).",
"pg_stress_toggle": "Testo ndërprerjen me gjendje statike",
"pg_stress_toggle_desc": "Simulon pajisjen që qëndron në nivelin e konsumit të gjendjes statike pas përfundimit të regjistrimit - tregon nëse dhe kur WashData ndalon ciklin.",
"pg_sweep_2d": "Hartë nxehtësie 2D",
"pg_sweep_param_y": "Parametri 2",
"pg_obj_match": "Saktësia e përputhjes",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Grafik interaktiv i fuqisë së ciklit: kaloni kursorin për të lexuar kohën/fuqinë, rrëshqitni për të zmadhuar, tërhiqni për të lëvizur",
"pg_alert_timeout_end": "Përfundoi nga skadimi i kohës, jo nga parashikimi",
"pg_alert_indefinite": "Do të vazhdonte pafundësisht",
"pg_alert_stress_cap": "U arrit kufiri i sigurisë",
"pg_alert_stress_ok": "Ndërprerja e gjendjes statike: cikli u ndal",
"pg_alert_stress_warn": "Konsumi i gjendjes statike mbi pragun e ndalimit",
"task_pg_history": "Testo në histori",
"task_pg_sweep": "Optimizo",
"task_reprocess": "Ripërpunim",
@@ -566,7 +585,31 @@
"task_split": "Ndarja e ciklit",
"task_trim": "Prerja e ciklit",
"task_merge": "Bashkimi i cikleve",
"task_rebuild": "Rindërtimi i zarfave"
"task_rebuild": "Rindërtimi i zarfave",
"cat_profiles": "Profilet (programet)",
"cat_real_cycles": "Ciklet (historiku i punës)",
"cat_reference_cycles": "Ciklet e referencës (të importuara)",
"cat_custom_phases": "Faza të personalizuara",
"cat_profile_groups": "Grupet e profileve",
"cat_settings": "Cilësimet e zbulimit & njohjes",
"cat_matching_config": "Rregullimi i njohësit",
"cat_ml_models": "Modelet ML",
"cat_feedback": "Etiketat e reagimit & rishikimit",
"cat_suggestions": "Sugjerime",
"cat_maintenance_log": "Regjistri i mirëmbajtjes",
"cat_history_logs": "Historiku & regjistrat e ndryshimeve",
"cat_lifetime_stats": "Totalet e përgjithshme",
"cycles_short": "cikle",
"import_mode": "Si të kombinohen",
"mode_merge": "Bashko (mba të miat)",
"mode_replace": "Zëvendëso të zgjedhurat",
"cycle_destination": "Ku duhet të shkojnë ciklet e importuara?",
"dest_reference": "Referencë (vetëm forma)",
"dest_real_history": "Historik real (numërohet në statistika)",
"conflict_resolution": "Përplasjet e emrave",
"conflict_import_copy": "Importo si kopje",
"conflict_keep_mine": "Mba të miat",
"conflict_overwrite": "Mbishkruaj"
},
"log": {
"all_levels": "Të gjitha nivelet",
@@ -643,7 +686,9 @@
"force_stop_msg": "Të ndalohet me forcë cikli aktiv tani? Cikli do të ruhet si i ndërprerë.",
"store_import": "Importo ciklin referues",
"store_share": "Ndaj në dyqanin e komunitetit",
"store_share_device": "Ndaj këtë pajisje"
"store_share_device": "Ndaj këtë pajisje",
"export_select": "Eksporto - zgjidh të dhënat",
"import_wizard": "Importo - zgjidh të dhënat"
},
"msg": {
"appliance_monitor": "Monitor i pajisjes",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "U gjetën {count} cikle të ngjashme të paetiketuara krijo një profil për të filluar përputhjen.",
"cycles_deleted": "{count} cikël(e) u fshinë",
"enough_data": "Të dhëna të mjaftueshme për të mësuar ({current}/{min} cikle).",
"export_description": "Eksporto të gjitha profilet dhe ciklet në JSON ose rivendos nga një eksportim i mëparshëm.",
"export_description": "Zgjidhni saktësisht cilat profile, cikle, cilësime dhe të tjera do të eksportohen në JSON, ose analizoni një skedar dhe importoni vetëm pjesët që dëshironi.",
"feedback_cycles_pending": "{n} cikël{s} për rishikim",
"feedback_prompt": "Konfirmo nëse ishte i saktë, korrigjo programin ose injorojë.",
"feedback_relabel_hint": "Rietiketimi i këtij cikli e zgjidh atë gjithashtu.",
"filter_by_profile": "Filtro sipas profilit…",
"group_modal_help": "Gruponi programe me të njëjtën formë që ndryshojnë në temperaturë/centrifugim (kohëzgjatja mund të ndryshojë). Përputhja shënon grupin si një kandidat dhe më pas zgjedh anëtarin më të përshtatshëm. Zgjidhni të paktën 2; mbivendosja tregon se sa të ngjashëm janë.",
"group_not_cohesive": "Këto profile nuk janë mjaft të ngjashme për t'u grupuar në mënyrë të besueshme, kështu që përputhja i trajton ato individualisht derisa të hiqni pjesën e jashtme ose të ndani grupin.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Rinisni Home Assistant për të aktivizuar mjetet e simulimit.",
"pg_sim_progress": "{done} / {total} cikle",
"pg_starting": "Duke filluar…",
"pg_stress_synthetic": "simulimi i gjendjes statike fillon këtu",
"pg_sweep_intro": "Po sikur {param} të ishte ndryshe? Testoni {steps} vlera në {cycles} ciklet tuaja të fundit për të gjetur cilësimin ku përputhen saktë sa më shumë cikle.",
"pg_sweep_step": "Hapi {done} / {total}",
"pg_undetected": "{n} cikël{s} të pazbuluar",
@@ -837,6 +884,9 @@
"pg_simulating": "Po simulohet cikli…",
"pg_canvas_empty2": "Zgjidhni një cikël më sipër dhe shtypni Ekzekuto për ta simuluar. Pastaj kaloni kursorin për të lexuar vlerat, rrëshqitni për të zmadhuar dhe tërhiqni për të lëvizur.",
"pg_apply_settings_confirm": "Të ruhen këto {n} cilësime në këtë pajisje? {list}",
"pg_stress_terminated_detail": "U stabilizua në rreth {idle} W gjendje statike -- cikli përfundoi {h} orë {m} min më vonë përmes {reason}.",
"pg_stress_above_threshold_detail": "Konsumi i gjendjes statike (~{idle} W) është në ose mbi pragun efektiv të ndalimit ({stop} W) -- cikli nuk u regjistrua kurrë si i qetë. Për ta rregulluar, rrisni stop_threshold_w.",
"pg_stress_hit_cap_detail": "Cikli funksionoi {h} orë {m} min pa u ndalur -- u ndal me forcë nga kufiri i sigurisë. Konsumi i gjendjes statike {idle} W kundrejt pragut të ndalimit {stop} W.",
"no_logs_match": "Asnjë regjistrim nuk përputhet me këta filtra.",
"pg_analysis_hint2": "Zgjidhni një cikël dhe shtypni Ekzekuto për të ngarkuar analizën e përputhjes.",
"pg_analysis_empty2": "Shtypni Ekzekuto për të parë analizën e përputhjes.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Ngarko {brand} {model} me ciklet e referencës që zgjidhni. Të tjerët me të njëjtën pajisje mund të adoptojnë programet tuaja. Hyrjet shqyrtohen para publikimit.",
"share_profile_no_cycles": "Nuk ka cikle referimi -- shënojeni një cikël si ⭐ tek skeda Ciklet për të përfshirë këtë profil",
"advisory_phase_inconsistent": "'{name}' duket se përzien programe ose temperatura të ndryshme - ciklet e tij ngrohin për periudha shumë të ndryshme kohore. Ndarja e tij në profile të veçanta (p.sh. sipas temperaturës) do të përmirësojë përputhjen dhe vlerësimet e kohës.",
"advisory_phase_inconsistent_title": "⚠ Ndoshta programe të përziera"
"advisory_phase_inconsistent_title": "⚠ Ndoshta programe të përziera",
"export_select_intro": "Shënoni saktësisht çfarë të përfshihet. Zgjedhja e profileve pa ciklet e tyre gjithsesi eksporton një program të njohshëm (forma e mësuar e tij udhëton bashkë me të).",
"import_analyze_hint": "Ngarkoni një skedar të eksportuar (ose ngjisni JSON-in e tij). WashData e analizon dhe tregon saktësisht çfarë mund të importohet përpara se të ndryshojë çdo gjë.",
"analyzing": "Duke analizuar…",
"device_type_mismatch_warn": "Ky eksport është nga një tip tjetër pajisjeje ({src} ndaj {local}). Programet dhe ciklet mund të importohen gjithsesi si të dhëna reference, por cilësimet specifike të pajisjes dhe importimi i historikut real janë çaktivizuar.",
"merge_hint": "Artikujt e importuar shtohen; asgjë lokale nuk humbet. Përplasjet e emrave zgjidhen më poshtë.",
"replace_warn": "Çdo kategori e shënuar fshihet dhe zëvendësohet nga skedari. Kategoritë e pashënuara mbeten të paprekura.",
"dest_reference_hint": "Ciklet e importuara vetëm përmirësojnë njohjen e programit dhe nuk ndikojnë kurrë në statistikat e përdorimit/energjisë.",
"dest_real_history_hint": "Ciklet e importuara llogariten si historik i vetë kësaj pajisjeje dhe ushqejnë statistikat e energjisë/përdorimit. Përdoreni për të zhvendosur një pajisje në një instalim të ri."
},
"phase_desc": {
"anti_crease": "Rrotullime të shkurtra të herëpashershme pas përfundimit për të reduktuar rrudhat.",
@@ -962,6 +1020,10 @@
"doc": "Fuqia duhet të bjerë nën këtë mes pulseve që modaliteti kundër rrudhave të qëndrojë aktiv.",
"label": "Pragu i fuqisë së daljes"
},
"anti_wrinkle_idle_timeout": {
"label": "Hendeku maks. i pulseve",
"doc": "Sa gjatë mund të qëndrojë e qetë makina mes dy pulseve të rrotullimit para se të mbarojë modaliteti kundër rrudhave. Vendoseni mbi hendekun më të gjatë që lë tharësja juaj mes pulseve, përndryshe çdo puls i mëvonshëm lexohet si nisje e gabuar."
},
"anti_wrinkle_max_duration": {
"doc": "Pulset më të gjata se kjo trajtohen si një cikël i vërtetë dhe jo si një rrëshqitje kundër rrudhave.",
"label": "Kohëzgjatja maks."
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "{n} cikle ndajur; {failed} nuk u ngarkuan.",
"store_device_shared_some_dup": "{created} cikle ndajur; {dup} ishin tashmë në dyqan.",
"store_download_failed": "Shkarkimi dështoi: {error}",
"store_download_nothing": "Asgjë e re për të shkarkuar - ky konfigurim është tashmë në pajisjen tuaj."
"store_download_nothing": "Asgjë e re për të shkarkuar - ky konfigurim është tashmë në pajisjen tuaj.",
"export_selective_done": "Eksporti u shkarkua",
"import_selective_done": "U importuan {profiles} profil(e) dhe {cycles} cikël(cikle)"
},
"suggestion": {
"both_agree": "WashData rekomandon",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Sekonda mbi prag për të konfirmuar nisjen",
"start_threshold_w": "Watt minimalë për t'u llogaritur si nisur",
"stop_threshold_w": "Nën këtë, makina llogaritet e fikur",
"anti_wrinkle_enabled": "Thith pulset e rrotullimit pas fazës kryesore në vend që t'i lexojë si cikle të reja",
"anti_wrinkle_exit_power": "Fuqia duhet të bjerë nën këtë mes pulseve që modaliteti kundër rrudhave të qëndrojë aktiv",
"anti_wrinkle_idle_timeout": "Koha e qetësisë e lejuar mes dy pulseve të rrotullimit para se të mbarojë modaliteti kundër rrudhave",
"anti_wrinkle_max_duration": "Një puls më i gjatë se ky e mbaron modalitetin kundër rrudhave dhe hap një cikël të ri",
"anti_wrinkle_max_power": "Një puls mbi këtë e mbaron modalitetin kundër rrudhave dhe hap një cikël të ri",
"dtw_bandwidth": "Etapa 3: brez deformimi Sakoe-Chiba (0 = DTW joaktiv; parazgjedhja 0.2 = 20% e gjatësisë së ciklit)",
"corr_weight": "Etapa 2: balancë mes formës së kurbës (korrelacion) dhe nivelit të fuqisë (MAE); parazgjedhja 0.45",
"duration_weight": "Etapa 4: sa fortë ndikon marrëveshja e kohëzgjatjes në rezultatin final (parazgjedhja 0.22)",
@@ -28,7 +28,9 @@
"awaiting_n": "Čeka odobrenje · potvrđeno {n}",
"approved": "Odobreno",
"awaiting": "Čeka odobrenje",
"imported_tip": "Uvezeno iz prodavnice zajednice. Koristi se samo za podudaranje, ne računa se u statistiku."
"imported_tip": "Uvezeno iz prodavnice zajednice. Koristi se samo za podudaranje, ne računa se u statistiku.",
"not_importable": "nije primenljivo",
"exists": "postoji"
},
"btn": {
"add_device": "+ Dodaj uređaj",
@@ -169,7 +171,14 @@
"download_device": "Preuzmi podešavanja uređaja",
"share_device": "Podeli podešavanja uređaja",
"share_device_tip": "Podelite programe i podešavanja ovog uređaja sa zajednicom",
"share_n": "Podeli {n} program{s}"
"share_n": "Podeli {n} program{s}",
"export_selected": "Izvoz (izbor podataka)",
"export_all": "Brzi izvoz svega",
"import_raw": "Napredno: zameni sve iz JSON-a",
"download_export": "Preuzmi izvoz",
"analyze_import": "Analiziraj datoteku",
"import_selected": "Uvezi izabrano",
"back": "Nazad"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,7 @@
"pg_group_detection": "Okidači otkrivanja",
"pg_group_timing": "Pravila vremena",
"pg_idle": "U mirovanju",
"pg_anti_wrinkle": "Zaštita od gužvanja",
"pg_last_run": "Poslednje pokretanje: {date}",
"pg_match_score": "Ocena poklapanja",
"pg_pct_of": "{p}% od {total}",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Interaktivni grafikon snage ciklusa: pređite mišem za prikaz vremena/snage, pomerajte točkić za zumiranje, prevucite za pomeranje",
"pg_alert_timeout_end": "Završeno zbog isteka vremena, a ne na osnovu predviđanja",
"pg_alert_indefinite": "Radio bi beskonačno",
"pg_alert_stress_cap": "Dostignut sigurnosni limit",
"pg_alert_stress_ok": "Detekcija mirovanja: ciklus zaustavljen",
"pg_alert_stress_warn": "Potrošnja u mirovanju iznad praga zaustavljanja",
"pg_stress_group": "Test detekcije mirovanja",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Nivo mirovanja (W)",
"pg_stress_idle_w_desc": "Prepiši automatski otkriven minimalni nivo potrošnje u stanju mirovanja (ostavi prazno za automatsko otkrivanje).",
"pg_stress_toggle": "Testiraj detekciju mirovanja",
"pg_stress_toggle_desc": "Simulira da uređaj ostaje na potrošnji u stanju mirovanja nakon završetka snimanja -- prikazuje da li i kada WashData zaustavlja ciklus.",
"task_pg_history": "Test na istoriji",
"task_pg_sweep": "Optimizuj",
"task_reprocess": "Ponovna obrada",
@@ -566,7 +585,31 @@
"task_split": "Deljenje ciklusa",
"task_trim": "Obrezivanje ciklusa",
"task_merge": "Spajanje ciklusa",
"task_rebuild": "Ponovna izgradnja omotača"
"task_rebuild": "Ponovna izgradnja omotača",
"cat_profiles": "Profili (programi)",
"cat_real_cycles": "Ciklusi (istorija izvršavanja)",
"cat_reference_cycles": "Referentni ciklusi (uvezeni)",
"cat_custom_phases": "Prilagođene faze",
"cat_profile_groups": "Grupe profila",
"cat_settings": "Podešavanja detekcije i prepoznavanja",
"cat_matching_config": "Fino podešavanje prepoznavanja",
"cat_ml_models": "ML modeli",
"cat_feedback": "Povratne informacije i oznake pregleda",
"cat_suggestions": "Predlozi",
"cat_maintenance_log": "Dnevnik održavanja",
"cat_history_logs": "Dnevnici istorije i izmena",
"cat_lifetime_stats": "Ukupni zbirovi za sve vreme",
"cycles_short": "ciklusa",
"import_mode": "Kako kombinovati",
"mode_merge": "Spoji (zadrži moje)",
"mode_replace": "Zameni izabrano",
"cycle_destination": "Gde treba da idu uvezeni ciklusi?",
"dest_reference": "Referentni (samo oblik)",
"dest_real_history": "Stvarna istorija (računa se u statistici)",
"conflict_resolution": "Sukobi imena",
"conflict_import_copy": "Uvezi kao kopiju",
"conflict_keep_mine": "Zadrži moje",
"conflict_overwrite": "Prepiši"
},
"log": {
"all_levels": "Svi nivoi",
@@ -643,7 +686,9 @@
"force_stop_msg": "Prisilno zaustaviti aktivni ciklus sada? Ciklus će biti sačuvan kao prekinut.",
"store_import": "Uvezi referentni ciklus",
"store_share": "Podeli u prodavnicu zajednice",
"store_share_device": "Podeli podešavanja uređaja"
"store_share_device": "Podeli podešavanja uređaja",
"export_select": "Izvoz - izbor podataka",
"import_wizard": "Uvoz - izbor podataka"
},
"msg": {
"appliance_monitor": "Monitor uređaja",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} sličnih neoznačenih ciklusa pronađeno napravite profil da biste počeli njihovo podudaranje.",
"cycles_deleted": "Obrisano ciklusa: {count}",
"enough_data": "Dovoljno podataka za učenje ({current}/{min} ciklusa).",
"export_description": "Izvezite sve profile i cikluse u JSON ili vratite iz prethodnog izvoza.",
"export_description": "Izaberite tačno koje profile, cikluse, podešavanja i drugo želite da izvezete u JSON, ili analizirajte datoteku i uvezite samo delove koje želite.",
"feedback_cycles_pending": "{n} ciklusa za pregled",
"feedback_prompt": "Potvrdi da je bio tačan, ispravi program ili ignoriši.",
"feedback_relabel_hint": "Ponovno označavanje ovog ciklusa takođe ga rešava.",
"filter_by_profile": "Filtriraj po profilu…",
"group_modal_help": "Grupni programi istog oblika koji se razlikuju po temperaturi/centrifugiranju (trajanja mogu varirati). Podudaranje ocenjuje grupu kao jednog kandidata, a zatim bira najfitijeg člana. Izaberite najmanje 2; preklop pokazuje koliko su slični.",
"group_not_cohesive": "Ovi profili nisu dovoljno slični da bi se pouzdano grupisali, tako da ih podudaranje tretira pojedinačno sve dok ne uklonite izuzetak ili ne podelite grupu.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Ponovo pokrenite Home Assistant da biste omogućili alatke za simulaciju.",
"pg_sim_progress": "{done} / {total} ciklusa",
"pg_starting": "Pokretanje…",
"pg_stress_synthetic": "simulacija mirovanja počinje ovde",
"pg_sweep_intro": "Šta ako bi {param} bio drugačiji? Testirajte {steps} vrednosti na vaših poslednjih {cycles} ciklusa da pronađete podešavanje pri kojem se najviše ciklusa ispravno poklapa.",
"pg_sweep_step": "Korak {done} / {total}",
"pg_undetected": "{n} neotkrivenih ciklusa",
@@ -837,6 +884,9 @@
"pg_simulating": "Simuliranje ciklusa…",
"pg_canvas_empty2": "Izaberite ciklus iznad i pritisnite Pokreni da ga simulirate. Zatim pređite mišem za prikaz vrednosti, pomerajte točkić za zumiranje i prevucite za pomeranje.",
"pg_apply_settings_confirm": "Sačuvati ovih {n} podešavanja na ovaj uređaj? {list}",
"pg_stress_terminated_detail": "Ustalilo se na ~{idle}W mirovanja -- ciklus se završio {h} h {m} min kasnije preko {reason}.",
"pg_stress_above_threshold_detail": "Potrošnja u mirovanju ~{idle}W jednaka je ili viša od stvarnog praga zaustavljanja ({stop}W) -- ciklus nikada nije zabeležen kao miran. Za ispravku povećajte stop_threshold_w.",
"pg_stress_hit_cap_detail": "Ciklus je radio {h} h {m} min bez zaustavljanja -- prisilno zaustavljen sigurnosnim limitom. Potrošnja u mirovanju {idle}W u odnosu na prag zaustavljanja {stop}W.",
"no_logs_match": "Nijedan zapis dnevnika ne odgovara ovim filterima.",
"pg_analysis_hint2": "Izaberite ciklus i pritisnite Pokreni da učitate analizu poklapanja.",
"pg_analysis_empty2": "Pritisnite Pokreni da vidite analizu poklapanja.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Podelite programe svog uređaja (profile + referentne cikluse) sa zajednicom. Podešavanja su opcionalna.",
"share_profile_no_cycles": "Profil '{p}' nema ⭐ referentnih ciklusa -- biće preskočen osim ako nemate {n}+ potvrđenih pokretanja",
"advisory_phase_inconsistent": "Izgleda da '{name}' meša različite programe ili temperature - njegovi ciklusi se greju veoma različito dugo. Podela na zasebne profile (npr. po temperaturi) poboljšaće podudaranje i procene vremena.",
"advisory_phase_inconsistent_title": "⚠ Možda pomešani programi"
"advisory_phase_inconsistent_title": "⚠ Možda pomešani programi",
"export_select_intro": "Označite tačno šta želite da uključite. Izbor profila bez njihovih ciklusa i dalje izvozi prepoznatljiv program (njegov naučeni oblik ide zajedno sa njim).",
"import_analyze_hint": "Učitajte izvezenu datoteku (ili nalepite njen JSON). WashData je analizira i prikazuje tačno šta može da se uveze pre bilo kakve promene.",
"analyzing": "Analiziranje…",
"device_type_mismatch_warn": "Ovaj izvoz je sa uređaja drugog tipa ({src} umesto {local}). Programi i ciklusi i dalje mogu da se uvezu kao referentni podaci, ali podešavanja specifična za uređaj i uvoz stvarne istorije su onemogućeni.",
"merge_hint": "Uvezene stavke se dodaju; ništa lokalno se ne gubi. Sukobi imena rešavaju se ispod.",
"replace_warn": "Svaka označena kategorija se briše i zamenjuje podacima iz datoteke. Neoznačene kategorije ostaju netaknute.",
"dest_reference_hint": "Uvezeni ciklusi samo poboljšavaju prepoznavanje programa i nikada ne utiču na statistiku potrošnje/energije.",
"dest_real_history_hint": "Uvezeni ciklusi se računaju kao sopstvena istorija ovog uređaja i pune statistiku energije/potrošnje. Koristite za premeštanje jednog uređaja na novu instalaciju."
},
"phase_desc": {
"anti_crease": "Povremena kratka okretanja nakon završetka radi smanjenja gužvanja.",
@@ -962,6 +1020,10 @@
"doc": "Snaga mora pasti ispod ove između impulsa da bi režim protiv bora ostao aktivan.",
"label": "Prag izlazne snage"
},
"anti_wrinkle_idle_timeout": {
"label": "Maks. razmak između impulsa",
"doc": "Koliko dugo mašina sme da ostane tiha između dva impulsa prevrtanja bubnja pre nego što se režim zaštite od gužvanja završi. Postavite je iznad najdužeg razmaka koji vaša mašina za sušenje ostavlja između impulsa, inače se svaki naredni impuls čita kao lažni start."
},
"anti_wrinkle_max_duration": {
"doc": "Pulsevi duži od ovoga se tretiraju kao pravi ciklus, a ne kao protiv bora.",
"label": "Maks. trajanje"
@@ -1692,7 +1754,9 @@
"store_device_shared_partial": "Delimično deljenje: {n} program{s} učitan, {failed} preskočeno",
"store_device_shared_some_dup": "Podešavanja uređaja podeljena: {n} program{s} učitan ({dup} već postoji)",
"store_download_failed": "Preuzimanje nije uspelo: {error}",
"store_download_nothing": "Nema ničeg za preuzimanje -- svi profili već postoje na ovom uređaju"
"store_download_nothing": "Nema ničeg za preuzimanje -- svi profili već postoje na ovom uređaju",
"export_selective_done": "Izvoz preuzet",
"import_selective_done": "Uvezeno {profiles} profil(a) i {cycles} ciklus(a)"
},
"trend": {
"down": "Opadajući trend",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Sekundi iznad praga za potvrdu pokretanja",
"start_threshold_w": "Minimum vati da bi se računalo kao pokrenuto",
"stop_threshold_w": "Ispod ovoga mašina se računa kao isključena",
"anti_wrinkle_enabled": "Upijte impulse prevrtanja bubnja nakon glavne faze umesto da ih čitate kao nove cikluse",
"anti_wrinkle_exit_power": "Snaga između impulsa mora pasti ispod ove da bi zaštita od gužvanja ostala aktivna",
"anti_wrinkle_idle_timeout": "Dozvoljena tišina između dva impulsa prevrtanja bubnja pre nego što se zaštita od gužvanja završi",
"anti_wrinkle_max_duration": "Impuls duži od ovoga završava zaštitu od gužvanja i otvara novi ciklus",
"anti_wrinkle_max_power": "Impuls iznad ovoga završava zaštitu od gužvanja i otvara novi ciklus",
"dtw_bandwidth": "Faza 3: warp opseg Sakoe-Chiba (0 = DTW isključen; podrazumevano 0,2 = 20% dužine ciklusa)",
"corr_weight": "Faza 2: ravnoteža između oblika krive (korelacija) i nivoa snage (MAE); podrazumevano 0,45",
"duration_weight": "Faza 4: koliko jako slaganje trajanja utiče na konačni rezultat (podrazumevano 0,22)",
@@ -28,7 +28,9 @@
"awaiting_n": "Väntar på godkännande · {n} bekräftade",
"approved": "Godkänd",
"awaiting": "Väntar på godkännande",
"imported_tip": "Importerad från gemenskapsbutiken. Används endast för matchning, räknas inte in i statistiken."
"imported_tip": "Importerad från gemenskapsbutiken. Används endast för matchning, räknas inte in i statistiken.",
"not_importable": "ej här",
"exists": "finns"
},
"btn": {
"add_device": "+ Lägg till enhet",
@@ -169,7 +171,14 @@
"download_device": "Ladda ner denna inställning",
"share_device": "⬆ Dela den här apparaten",
"share_device_tip": "Dela denna apparat och dess inspelade referenscykler till gemenskapsbutiken så att andra med samma maskin kan ta över dem",
"share_n": "Dela {n} cykel/cyklar"
"share_n": "Dela {n} cykel/cyklar",
"export_selected": "Exportera (välj data)",
"export_all": "Snabbexportera allt",
"import_raw": "Avancerat: ersätt allt från JSON",
"download_export": "Ladda ner export",
"analyze_import": "Analysera fil",
"import_selected": "Importera valda",
"back": "Tillbaka"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,12 @@
"pg_group_detection": "Detekteringsutlösare",
"pg_group_timing": "Tidsregler",
"pg_idle": "Inaktiv",
"pg_stress_group": "Test av inaktiv avslutning",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Inaktiv nivå (W)",
"pg_stress_idle_w_desc": "Åsidosätt den automatiskt identifierade standby-golvnivån (lämna tomt för automatisk).",
"pg_stress_toggle": "Testa inaktiv avslutning",
"pg_stress_toggle_desc": "Simulerar att apparaten stannar kvar vid sin standby-förbrukning efter att inspelningen slutar - visar om och när WashData stoppar cykeln.",
"pg_last_run": "Senaste körning: {date}",
"pg_match_score": "Matchningspoäng",
"pg_pct_of": "{p}% av {total}",
@@ -538,6 +553,9 @@
"aria_playground_chart2": "Interaktiv effektgraf för cykeln: hovra för att avläsa tid/effekt, skrolla för att zooma, dra för att panorera",
"pg_alert_timeout_end": "Avslutad genom timeout, inte genom förutsägelse",
"pg_alert_indefinite": "Skulle köra i all oändlighet",
"pg_alert_stress_cap": "Säkerhetsgräns nådd",
"pg_alert_stress_ok": "Inaktiv avslutning: cykel stoppad",
"pg_alert_stress_warn": "Standby-förbrukning över stopptröskel",
"task_pg_history": "Testa på historik",
"task_pg_sweep": "Optimera",
"task_reprocess": "Bearbetar igen",
@@ -566,7 +584,32 @@
"task_split": "Delar upp cykel",
"task_trim": "Beskär cykel",
"task_merge": "Slår ihop cykler",
"task_rebuild": "Bygger om enveloppar"
"task_rebuild": "Bygger om enveloppar",
"cat_profiles": "Profiler (program)",
"cat_real_cycles": "Cykler (körhistorik)",
"cat_reference_cycles": "Referenscykler (importerade)",
"cat_custom_phases": "Anpassade faser",
"cat_profile_groups": "Profilgrupper",
"cat_settings": "Detekterings- & matchningsinställningar",
"cat_matching_config": "Finjustering av matchning",
"cat_ml_models": "ML-modeller",
"cat_feedback": "Feedback & granskningsetiketter",
"cat_suggestions": "Förslag",
"cat_maintenance_log": "Underhållslogg",
"cat_history_logs": "Historik- & ändringsloggar",
"cat_lifetime_stats": "Totaler (livstid)",
"cycles_short": "cykler",
"import_mode": "Så kombineras",
"mode_merge": "Slå samman (behåll mina)",
"mode_replace": "Ersätt valda",
"cycle_destination": "Vart ska importerade cykler placeras?",
"dest_reference": "Referens (endast form)",
"dest_real_history": "Verklig historik (räknas i statistik)",
"conflict_resolution": "Namnkonflikter",
"conflict_import_copy": "Importera som kopia",
"conflict_keep_mine": "Behåll mina",
"conflict_overwrite": "Skriv över",
"pg_anti_wrinkle": "Skrynkelskydd"
},
"log": {
"all_levels": "Alla nivåer",
@@ -643,7 +686,9 @@
"force_stop_msg": "Tvångsstoppa den aktiva cykeln nu? Cykeln sparas som avbruten.",
"store_import": "Importera referenscykel",
"store_share": "Dela till gemenskapsbutiken",
"store_share_device": "Dela den här apparaten"
"store_share_device": "Dela den här apparaten",
"export_select": "Export - välj data",
"import_wizard": "Import - välj data"
},
"msg": {
"appliance_monitor": "Apparatövervakning",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} liknande omärkta cykler hittades skapa en profil för att börja matcha dem.",
"cycles_deleted": "{count} cykler borttagna",
"enough_data": "Tillräckligt med data för att lära sig av ({current}/{min} cykler).",
"export_description": "Exportera alla profiler och cykler till JSON, eller återställ från en tidigare export.",
"export_description": "Välj exakt vilka profiler, cykler, inställningar och mer som ska exporteras till JSON, eller analysera en fil och importera bara de delar du vill ha.",
"feedback_cycles_pending": "{n} cykel{s} att granska",
"feedback_prompt": "Bekräfta att det var rätt, korrigera programmet eller ignorera.",
"feedback_relabel_hint": "Att märka om den här cykeln löser också granskningen.",
"filter_by_profile": "Filtrera efter profil…",
"group_modal_help": "Gruppera program med samma form som skiljer sig i temperatur/centrifugering (längderna kan variera). Matchning ger gruppen poäng som en kandidat och väljer sedan den medlem som passar bäst. Välj minst 2; överlägget visar hur lika de är.",
"group_not_cohesive": "Dessa profiler är inte tillräckligt lika för att gruppera på ett tillförlitligt sätt, så matchning behandlar dem individuellt tills du tar bort avvikelsen eller delar upp gruppen.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Starta om Home Assistant för att aktivera simuleringsverktygen.",
"pg_sim_progress": "{done} / {total} cykler",
"pg_starting": "Startar…",
"pg_stress_synthetic": "inaktiv simulering börjar här",
"pg_sweep_intro": "Tänk om {param} vore annorlunda? Testa {steps} värden över dina senaste {cycles} cykler för att hitta inställningen där flest cykler matchas korrekt.",
"pg_sweep_step": "Steg {done} / {total}",
"pg_undetected": "{n} cykel{s} oupptäckt",
@@ -837,6 +884,9 @@
"pg_simulating": "Simulerar cykel…",
"pg_canvas_empty2": "Välj en cykel ovan och tryck på Kör för att simulera den. Hovra sedan för att avläsa värden, skrolla för att zooma och dra för att panorera.",
"pg_apply_settings_confirm": "Spara dessa {n} inställning(ar) till den här enheten? {list}",
"pg_stress_terminated_detail": "Stabiliserades på ~{idle}W inaktiv -- cykeln avslutades {h} h {m} min senare via {reason}.",
"pg_stress_above_threshold_detail": "Standby-förbrukningen på ~{idle}W ligger på eller över den effektiva stopptröskeln ({stop}W) -- cykeln registrerades aldrig som inaktiv. Höj stop_threshold_w för att åtgärda detta.",
"pg_stress_hit_cap_detail": "Cykeln kördes {h} h {m} min utan att stoppa -- tvångsstoppad av säkerhetsgränsen. Standby-förbrukning {idle}W mot stopptröskel {stop}W.",
"no_logs_match": "Inga loggposter matchar dessa filter.",
"pg_analysis_hint2": "Välj en cykel och tryck på Kör för att läsa in matchningsanalysen.",
"pg_analysis_empty2": "Tryck på Kör för att se matchningsanalysen.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Ladda upp {brand} {model} med de referenscykler du väljer. Andra med samma apparat kan ta över dina program. Poster granskas innan de visas offentligt.",
"share_profile_no_cycles": "Inga referenscykler -- markera en cykel som ⭐ på fliken Cykler för att inkludera den här profilen",
"advisory_phase_inconsistent": "'{name}' verkar blanda olika program eller temperaturer - dess cykler värms upp under mycket olika lång tid. Om du delar upp den i separata profiler (t.ex. per temperatur) förbättras matchningen och tidsuppskattningarna.",
"advisory_phase_inconsistent_title": "⚠ Möjligen blandade program"
"advisory_phase_inconsistent_title": "⚠ Möjligen blandade program",
"export_select_intro": "Kryssa i exakt vad som ska inkluderas. Att välja profiler utan deras cykler exporterar ändå ett matchningsbart program (den inlärda formen följer med).",
"import_analyze_hint": "Läs in en exporterad fil (eller klistra in dess JSON). WashData analyserar den och visar exakt vad som kan importeras innan något ändras.",
"analyzing": "Analyserar…",
"device_type_mismatch_warn": "Den här exporten kommer från en annan apparattyp ({src} vs {local}). Program och cykler kan fortfarande importeras som referensdata, men apparatspecifika inställningar och import av verklig historik är inaktiverade.",
"merge_hint": "Importerade objekt läggs till; inget lokalt går förlorat. Namnkonflikter löses nedan.",
"replace_warn": "Varje ikryssad kategori raderas och ersätts från filen. Icke ikryssade kategorier lämnas orörda.",
"dest_reference_hint": "Importerade cykler förbättrar bara programmatchning och påverkar aldrig användnings-/energistatistik.",
"dest_real_history_hint": "Importerade cykler räknas som den här apparatens egen historik och matar energi-/användningsstatistik. Använd detta för att flytta en apparat till en ny installation."
},
"pg_desc": {
"completion_min_seconds": "Kortaste körning som räknas som en riktig cykel",
@@ -887,7 +945,12 @@
"dtw_ddtw_scale": "Steg 3: DDTW-halvmättnadsavstånd; lägre = mer formkänslig (standard 30)",
"dtw_refine_top_n": "Steg 3: kandidater som DTW ompoängsätter; öka till 7-9 om rätt profil placerar sig 4-5:a (standard 5)",
"duration_scale": "Steg 4: log-kvot där varaktighetsöverensstämmelse halveras; lägre = striktare straff (standard 0,175)",
"energy_scale": "Steg 4: log-kvot där energiöverensstämmelse halveras; lägre = striktare straff (standard 0,25)"
"energy_scale": "Steg 4: log-kvot där energiöverensstämmelse halveras; lägre = striktare straff (standard 0,25)",
"anti_wrinkle_idle_timeout": "Tillåten tyst tid mellan två tumlingspulser innan skrynkelskyddet avslutas",
"anti_wrinkle_max_power": "En puls över detta avslutar skrynkelskyddet och öppnar en ny cykel",
"anti_wrinkle_enabled": "Fånga upp tumlingspulserna efter huvudfasen i stället för att läsa dem som nya cykler",
"anti_wrinkle_max_duration": "En puls längre än detta avslutar skrynkelskyddet och öppnar en ny cykel",
"anti_wrinkle_exit_power": "Effekten måste sjunka under detta mellan pulserna för att skrynkelskyddet ska förbli aktivt"
},
"phase_desc": {
"anti_crease": "Enstaka korta tumlar efter färdigställandet för att förhindra skrynklor i tvätten.",
@@ -985,6 +1048,10 @@
"doc": "Effekten måste sjunka under detta mellan pulserna för att anti-rynkläget ska förbli aktivt.",
"label": "Avslutningseffekttröskel"
},
"anti_wrinkle_idle_timeout": {
"doc": "Hur länge maskinen får förbli tyst mellan två tumlingspulser innan skrynkelskyddsläget avslutas. Ställ in det högre än det längsta uppehåll din torktumlare lämnar mellan två pulser, annars läses varje senare puls som en falsk start.",
"label": "Maks. pulspaus"
},
"anti_wrinkle_max_duration": {
"doc": "Pulser längre än detta behandlas som en riktig cykel snarare än en anti-rynktumling.",
"label": "Maks. varaktighet"
@@ -1715,7 +1782,9 @@
"store_device_shared_partial": "{n} cykel/cyklar delade; {failed} kunde inte laddas upp.",
"store_device_shared_some_dup": "{created} cykel/cyklar delade; {dup} fanns redan i butiken.",
"store_download_failed": "Nedladdning misslyckades: {error}",
"store_download_nothing": "Inget nytt att ladda ner -- den här inställningen finns redan på din apparat."
"store_download_nothing": "Inget nytt att ladda ner -- den här inställningen finns redan på din apparat.",
"export_selective_done": "Export nedladdad",
"import_selective_done": "Importerade {profiles} profil(er) och {cycles} cykel/cykler"
},
"trend": {
"down": "Nedåtgående trend",
@@ -28,7 +28,9 @@
"awaiting_n": "Onay bekleniyor · {n} onaylandı",
"approved": "Onaylandı",
"awaiting": "Onay bekleniyor",
"imported_tip": "Topluluk mağazasından içe aktarıldı. Yalnızca eşleştirme için kullanılır, istatistiklere sayılmaz."
"imported_tip": "Topluluk mağazasından içe aktarıldı. Yalnızca eşleştirme için kullanılır, istatistiklere sayılmaz.",
"not_importable": "burada geçersiz",
"exists": "mevcut"
},
"btn": {
"add_device": "+ Cihaz ekle",
@@ -169,7 +171,14 @@
"download_device": "Bu kurulumu indir",
"share_device": "⬆ Bu cihazı paylaş",
"share_device_tip": "Bu cihazı ve kayıtlı referans döngülerini topluluk mağazasına paylaşın; aynı makineye sahip diğer kullanıcılar bunları benimseyebilsin",
"share_n": "{n} döngü paylaş"
"share_n": "{n} döngü paylaş",
"export_selected": "Dışa aktar (veri seç)",
"export_all": "Her şeyi hızlı dışa aktar",
"import_raw": "Gelişmiş: JSON'dan tümünü değiştir",
"download_export": "Dışa aktarmayı indir",
"analyze_import": "Dosyayı analiz et",
"import_selected": "Seçileni içe aktar",
"back": "Geri"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "Varsayılan sekme",
"panel_language": "Panel dili",
"per_cycle_short": "döngü",
"pg_anti_wrinkle": "Kırışıklık önleme",
"pg_autofill_tip": "Geçerli değerden aralığı otomatik doldur ({lo}{hi})",
"pg_best_value": "Bulunan en iyi değer",
"pg_current_value": "Geçerli değer:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "Parametre",
"pg_objective": "Hedef",
"pg_steps": "Adımlar",
"pg_stress_group": "Bekleme sonlandırma testi",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Bekleme seviyesi (W)",
"pg_stress_idle_w_desc": "Otomatik algılanan bekleme tabanını geçersiz kıl (otomatik için boş bırakın).",
"pg_stress_toggle": "Bekleme sonlandırmasını test et",
"pg_stress_toggle_desc": "Kaydın bitmesinin ardından cihazın bekleme modundaki güç tüketimine devam ettiğini simüle eder - WashData'nın döngüyü durdurup durdurmayacağını ve ne zaman durduracağını gösterir.",
"pg_sweep_2d": "2D ısı haritası",
"pg_sweep_param_y": "2. parametre",
"pg_obj_match": "Eşleşme doğruluğu",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "Etkileşimli döngü güç grafiği: zamanı/gücü okumak için üzerine gelin, yakınlaştırmak için kaydırın, kaydırmak için sürükleyin",
"pg_alert_timeout_end": "Tahminle değil, zaman aşımıyla sona erdi",
"pg_alert_indefinite": "Süresiz olarak çalışırdı",
"pg_alert_stress_cap": "Güvenlik sınırına ulaşıldı",
"pg_alert_stress_ok": "Bekleme sonlandırması: döngü durduruldu",
"pg_alert_stress_warn": "Bekleme tüketimi durdurma eşiğinin üzerinde",
"task_pg_history": "Geçmişte test et",
"task_pg_sweep": "Optimize et",
"task_reprocess": "Yeniden işleme",
@@ -566,7 +585,31 @@
"task_split": "Döngü bölünüyor",
"task_trim": "Döngü kırpılıyor",
"task_merge": "Döngüler birleştiriliyor",
"task_rebuild": "Zarflar yeniden oluşturuluyor"
"task_rebuild": "Zarflar yeniden oluşturuluyor",
"cat_profiles": "Profiller (programlar)",
"cat_real_cycles": "Döngüler (çalışma geçmişi)",
"cat_reference_cycles": "Referans döngüler (içe aktarılan)",
"cat_custom_phases": "Özel aşamalar",
"cat_profile_groups": "Profil grupları",
"cat_settings": "Algılama ve tanıma ayarları",
"cat_matching_config": "Tanıyıcı ayarı",
"cat_ml_models": "ML modelleri",
"cat_feedback": "Geri bildirim ve inceleme etiketleri",
"cat_suggestions": "Öneriler",
"cat_maintenance_log": "Bakım günlüğü",
"cat_history_logs": "Geçmiş ve değişiklik günlükleri",
"cat_lifetime_stats": "Ömür boyu toplamlar",
"cycles_short": "döngü",
"import_mode": "Nasıl birleştirilsin",
"mode_merge": "Birleştir (benimkini koru)",
"mode_replace": "Seçileni değiştir",
"cycle_destination": "İçe aktarılan döngüler nereye gitsin?",
"dest_reference": "Referans (yalnızca şekil)",
"dest_real_history": "Gerçek geçmiş (istatistiklere sayılır)",
"conflict_resolution": "Ad çakışmaları",
"conflict_import_copy": "Kopya olarak içe aktar",
"conflict_keep_mine": "Benimkini koru",
"conflict_overwrite": "Üzerine yaz"
},
"log": {
"all_levels": "Tüm seviyeler",
@@ -643,7 +686,9 @@
"force_stop_msg": "Etkin döngü şimdi zorla durdurulsun mu? Döngü, kesintiye uğramış olarak kaydedilecek.",
"store_import": "Referans döngüyü içe aktar",
"store_share": "Topluluk mağazasında paylaş",
"store_share_device": "Bu cihazı paylaş"
"store_share_device": "Bu cihazı paylaş",
"export_select": "Dışa aktarma - veri seçin",
"import_wizard": "İçe aktarma - veri seçin"
},
"msg": {
"appliance_monitor": "Cihaz monitörü",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "{count} benzer etiketsiz döngü bulundu bunları eşleştirmeye başlamak için bir profil oluşturun.",
"cycles_deleted": "{count} döngü silindi",
"enough_data": "Öğrenmek için yeterli veri ({current}/{min} döngü).",
"export_description": "Tüm profilleri ve döngüleri JSON'a aktarın veya önceki bir dışa aktarma işleminden geri yükleyin.",
"export_description": "JSON'a dışa aktarılacak profilleri, döngüleri, ayarları ve daha fazlasını tam olarak seçin ya da bir dosyayı analiz edip yalnızca istediğiniz kısımları içe aktarın.",
"feedback_cycles_pending": "İncelenecek {n} döngü{s}",
"feedback_prompt": "Doğru olduğunu onaylayın, programı düzeltin veya yoksayın.",
"feedback_relabel_hint": "Bu döngüyü yeniden etiketlemek de bunu çözer.",
"filter_by_profile": "Profile göre filtrele…",
"group_modal_help": "Sıcaklık/dönme açısından farklı olan aynı şekle sahip programları gruplandırın (süreler değişebilir). Eşleştirme, grubu bir aday olarak puanlar ve ardından en uygun üyeyi seçer. En az 2 tanesini seçin; kaplama ne kadar benzer olduklarını gösteriyor.",
"group_not_cohesive": "Bu profiller güvenilir bir şekilde gruplandırılacak kadar benzer değildir; bu nedenle eşleştirme, siz aykırı değeri kaldırana veya grubu bölene kadar bunları ayrı ele alır.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Simülasyon araçlarını etkinleştirmek için Home Assistant'ı yeniden başlatın.",
"pg_sim_progress": "{done} / {total} döngü",
"pg_starting": "Başlatılıyor…",
"pg_stress_synthetic": "bekleme simülasyonu burada başlar",
"pg_sweep_intro": "{param} farklı olsaydı ne olurdu? En çok döngünün doğru şekilde eşleştirildiği ayarı bulmak için son {cycles} döngünüzde {steps} değeri test edin.",
"pg_sweep_step": "Adım {done} / {total}",
"pg_undetected": "{n} döngü{s} algılanmadı",
@@ -837,6 +884,9 @@
"pg_simulating": "Döngü simüle ediliyor…",
"pg_canvas_empty2": "Yukarıdan bir döngü seçin ve simüle etmek için Çalıştır'a basın. Ardından değerleri okumak için üzerine gelin, yakınlaştırmak için kaydırın ve kaydırmak için sürükleyin.",
"pg_apply_settings_confirm": "Bu {n} ayar bu cihaza kaydedilsin mi? {list}",
"pg_stress_terminated_detail": "Yaklaşık {idle} W bekleme seviyesine oturdu -- döngü {h} sa {m} dk sonra {reason} ile sona erdi.",
"pg_stress_above_threshold_detail": "Bekleme tüketimi (~{idle} W) etkin durdurma eşiğine ({stop} W) eşit veya bu eşiğin üzerinde -- döngü hiçbir zaman sessiz olarak kaydedilmedi. Düzeltmek için stop_threshold_w değerini artırın.",
"pg_stress_hit_cap_detail": "Döngü {h} sa {m} dk boyunca durmadan çalıştı -- güvenlik sınırı tarafından zorla durduruldu. Bekleme tüketimi {idle} W, durdurma eşiği {stop} W.",
"no_logs_match": "Bu filtrelerle eşleşen günlük kaydı yok.",
"pg_analysis_hint2": "Bir döngü seçin ve eşleşme analizini yüklemek için Çalıştır'a basın.",
"pg_analysis_empty2": "Eşleşme analizini görmek için Çalıştır'a basın.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Seçtiğiniz referans döngüleriyle {brand} {model} cihazını yükleyin. Aynı cihaza sahip diğer kullanıcılar programlarınızı benimseyebilir. Girişler kamuya açılmadan önce incelenir.",
"share_profile_no_cycles": "Referans döngüsü yok -- bu profili dahil etmek için Döngüler sekmesinde bir döngüyü ⭐ olarak işaretleyin",
"advisory_phase_inconsistent": "'{name}' farklı programları veya sıcaklıkları karıştırıyor gibi görünüyor - çevrimleri çok farklı sürelerde ısıtma yapıyor. Bunu ayrı profillere bölmek (örneğin her sıcaklık için) eşleştirmeyi ve süre tahminlerini iyileştirir.",
"advisory_phase_inconsistent_title": "⚠ Karışık olabilecek programlar"
"advisory_phase_inconsistent_title": "⚠ Karışık olabilecek programlar",
"export_select_intro": "Nelerin dahil edileceğini tam olarak işaretleyin. Profilleri döngüleri olmadan seçmek yine de tanınabilir bir program dışa aktarır (öğrenilen şekli de birlikte taşınır).",
"import_analyze_hint": "Dışa aktarılmış bir dosya yükleyin (veya JSON'unu yapıştırın). WashData bunu analiz eder ve herhangi bir değişiklik olmadan önce tam olarak neyin içe aktarılabileceğini gösterir.",
"analyzing": "Analiz ediliyor…",
"device_type_mismatch_warn": "Bu dışa aktarma farklı bir cihaz türünden ({src} ile {local}). Programlar ve döngüler yine de referans verisi olarak içe aktarılabilir, ancak cihaza özgü ayarlar ve gerçek geçmiş içe aktarımı devre dışıdır.",
"merge_hint": "İçe aktarılan öğeler eklenir; yerel hiçbir şey kaybolmaz. Ad çakışmaları aşağıda çözülür.",
"replace_warn": "İşaretlenen her kategori silinir ve dosyadan gelenle değiştirilir. İşaretlenmeyen kategorilere dokunulmaz.",
"dest_reference_hint": "İçe aktarılan döngüler yalnızca program tanımayı iyileştirir ve kullanım/enerji istatistiklerini asla etkilemez.",
"dest_real_history_hint": "İçe aktarılan döngüler bu cihazın kendi geçmişi olarak sayılır ve enerji/kullanım istatistiklerini besler. Bir cihazı yeni bir kuruluma taşırken kullanın."
},
"pg_desc": {
"completion_min_seconds": "Gerçek döngü sayılan en kısa çalışma",
@@ -875,6 +933,11 @@
"start_duration_threshold": "Başlangıcı onaylamak için eşik üstünde saniye",
"start_threshold_w": "Başladı sayılması için minimum watt",
"stop_threshold_w": "Bunun altında makine kapalı sayılır",
"anti_wrinkle_enabled": "Ana aşamadan sonraki takla darbelerini yeni döngü olarak okumak yerine emer",
"anti_wrinkle_exit_power": "Kırışıklık önlemenin etkin kalması için gücün darbeler arasında bunun altına düşmesi gerekir",
"anti_wrinkle_idle_timeout": "Kırışıklık önleme sona ermeden önce iki takla darbesi arasında izin verilen sessiz süre",
"anti_wrinkle_max_duration": "Bundan uzun bir darbe kırışıklık önlemeyi bitirir ve yeni bir döngü açar",
"anti_wrinkle_max_power": "Bunun üzerindeki bir darbe kırışıklık önlemeyi bitirir ve yeni bir döngü açar",
"dtw_bandwidth": "Aşama 3: Sakoe-Chiba büküm bandı (0 = DTW kapalı; varsayılan 0.2 = döngü uzunluğunun %20'si)",
"corr_weight": "Aşama 2: eğri şekli (korelasyon) ile güç düzeyi (MAE) arasındaki denge; varsayılan 0.45",
"duration_weight": "Aşama 4: çalışma süresi uyumunun nihai puanı ne kadar etkilediği (varsayılan 0.22)",
@@ -985,6 +1048,10 @@
"doc": "Kırışıklık önleme modunun aktif kalması için darbeler arasında gücün bu değerin altına düşmesi gerekir.",
"label": "Çıkış Gücü Eşiği"
},
"anti_wrinkle_idle_timeout": {
"label": "Maks. Darbe Aralığı",
"doc": "Kırışıklık önleme modu sona ermeden önce makinenin iki takla darbesi arasında ne kadar süre sessiz kalabileceği. Bunu kurutucunuzun darbeler arasında bıraktığı en uzun boşluğun üzerine ayarlayın, aksi halde sonraki her darbe hatalı başlangıç olarak okunur."
},
"anti_wrinkle_max_duration": {
"doc": "Bundan daha uzun darbeler, kırışıklık önleyici bir takla yerine gerçek bir döngü olarak değerlendirilir.",
"label": "Maks. Süre"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "{n} döngü paylaşıldı; {failed} yüklenemedi.",
"store_device_shared_some_dup": "{created} döngü paylaşıldı; {dup} zaten mağazadaydı.",
"store_download_failed": "İndirme başarısız: {error}",
"store_download_nothing": "İndirilecek yeni bir şey yok - bu kurulum zaten cihazınızda."
"store_download_nothing": "İndirilecek yeni bir şey yok - bu kurulum zaten cihazınızda.",
"export_selective_done": "Dışa aktarma indirildi",
"import_selective_done": "{profiles} profil ve {cycles} döngü içe aktarıldı"
},
"suggestion": {
"both_agree": "WashData öneriyor",
@@ -28,7 +28,9 @@
"awaiting_n": "Очікує схвалення · {n} підтверджено",
"approved": "Схвалено",
"awaiting": "Очікує схвалення",
"imported_tip": "Імпортовано з магазину спільноти. Використовується лише для зіставлення, не враховується у статистиці."
"imported_tip": "Імпортовано з магазину спільноти. Використовується лише для зіставлення, не враховується у статистиці.",
"not_importable": "недоступно",
"exists": "існує"
},
"btn": {
"add_device": "+ Додати пристрій",
@@ -169,7 +171,14 @@
"download_device": "Завантажити конфігурацію пристрою",
"share_device": "Поділитися конфігурацією пристрою",
"share_device_tip": "Поділитися програмами та налаштуваннями цього пристрою зі спільнотою",
"share_n": "Поділитися: {n} програм{s}"
"share_n": "Поділитися: {n} програм{s}",
"export_selected": "Експорт (вибрати дані)",
"export_all": "Швидкий експорт усього",
"import_raw": "Розширено: замінити все з JSON",
"download_export": "Завантажити експорт",
"analyze_import": "Проаналізувати файл",
"import_selected": "Імпортувати вибране",
"back": "Назад"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -401,6 +410,7 @@
"pg_group_detection": "Тригери виявлення",
"pg_group_timing": "Правила часу",
"pg_idle": "Бездіяльність",
"pg_anti_wrinkle": "Захист від зминання",
"pg_last_run": "Останній запуск: {date}",
"pg_match_score": "Оцінка збігу",
"pg_pct_of": "{p}% з {total}",
@@ -538,6 +548,15 @@
"aria_playground_chart2": "Інтерактивний графік потужності циклу: наведіть, щоб побачити час/потужність, прокрутіть для масштабування, перетягніть для панорамування",
"pg_alert_timeout_end": "Завершено за тайм-аутом, а не за прогнозом",
"pg_alert_indefinite": "Працював би нескінченно",
"pg_alert_stress_cap": "Досягнуто захисний ліміт",
"pg_alert_stress_ok": "Виявлено режим очікування: цикл зупинено",
"pg_alert_stress_warn": "Споживання в режимі очікування перевищує поріг зупинки",
"pg_stress_group": "Тест виявлення режиму очікування",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "Рівень у режимі очікування (Вт)",
"pg_stress_idle_w_desc": "Замінити автоматично визначений нижній поріг споживання в режимі очікування (залиште порожнім для автоматичного визначення).",
"pg_stress_toggle": "Тестувати виявлення режиму очікування",
"pg_stress_toggle_desc": "Імітує роботу пристрою в режимі очікування після завершення запису -- показує, чи і коли WashData зупинить цикл.",
"task_pg_history": "Тест на історії",
"task_pg_sweep": "Оптимізація",
"task_reprocess": "Повторна обробка",
@@ -566,7 +585,31 @@
"task_split": "Розділення циклу",
"task_trim": "Обрізання циклу",
"task_merge": "Об'єднання циклів",
"task_rebuild": "Перерахунок обвідних"
"task_rebuild": "Перерахунок обвідних",
"cat_profiles": "Профілі (програми)",
"cat_real_cycles": "Цикли (історія запусків)",
"cat_reference_cycles": "Довідкові цикли (імпортовані)",
"cat_custom_phases": "Користувацькі фази",
"cat_profile_groups": "Групи профілів",
"cat_settings": "Налаштування виявлення та розпізнавання",
"cat_matching_config": "Тонке налаштування розпізнавання",
"cat_ml_models": "ML-моделі",
"cat_feedback": "Відгуки та мітки перевірки",
"cat_suggestions": "Пропозиції",
"cat_maintenance_log": "Журнал обслуговування",
"cat_history_logs": "Журнали історії та змін",
"cat_lifetime_stats": "Загальні підсумки",
"cycles_short": "циклів",
"import_mode": "Як об'єднати",
"mode_merge": "Об'єднати (зберегти мої)",
"mode_replace": "Замінити вибране",
"cycle_destination": "Куди помістити імпортовані цикли?",
"dest_reference": "Довідкові (лише форма)",
"dest_real_history": "Реальна історія (враховується в статистиці)",
"conflict_resolution": "Конфлікти імен",
"conflict_import_copy": "Імпортувати як копію",
"conflict_keep_mine": "Зберегти мої",
"conflict_overwrite": "Перезаписати"
},
"log": {
"all_levels": "Усі рівні",
@@ -643,7 +686,9 @@
"force_stop_msg": "Примусово зупинити активний цикл зараз? Цикл буде збережено як перерваний.",
"store_import": "Імпорт еталонного циклу",
"store_share": "Поділитися в магазині спільноти",
"store_share_device": "Поділитися конфігурацією пристрою"
"store_share_device": "Поділитися конфігурацією пристрою",
"export_select": "Експорт - вибір даних",
"import_wizard": "Імпорт - вибір даних"
},
"msg": {
"appliance_monitor": "Монітор приладу",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "Знайдено {count} схожих непозначених циклів – створіть профіль, щоб почати їх зіставлення.",
"cycles_deleted": "Видалено циклів: {count}",
"enough_data": "Достатньо даних для навчання ({current}/{min} циклів).",
"export_description": "Експортуйте всі профілі та цикли в JSON або відновіть попередній експорт.",
"export_description": "Виберіть, які саме профілі, цикли, налаштування та інше експортувати в JSON, або проаналізуйте файл та імпортуйте лише потрібні частини.",
"feedback_cycles_pending": "{n} цикл{s} для перегляду",
"feedback_prompt": "Підтвердьте правильність, виправте програму або проігноруйте.",
"feedback_relabel_hint": "Повторне маркування цього циклу також вирішує його.",
"filter_by_profile": "Фільтр за профілем…",
"group_modal_help": "Групуйте програми однакової форми, які відрізняються температурою/віджиманням (тривалість може відрізнятися). Підбір оцінює групу як одного кандидата, а потім вибирає найкраще підходящого учасника. Виберіть принаймні 2; накладка показує, наскільки вони схожі.",
"group_not_cohesive": "Ці профілі недостатньо схожі для надійного групування, тому при зіставленні вони обробляються окремо, доки ви не видалите викид або не розділите групу.",
@@ -729,6 +775,7 @@
"pg_restart_note": "Перезапустіть Home Assistant, щоб увімкнути інструменти симуляції.",
"pg_sim_progress": "{done} / {total} циклів",
"pg_starting": "Запуск…",
"pg_stress_synthetic": "тут починається симуляція режиму очікування",
"pg_sweep_intro": "Що якби {param} було іншим? Перевірте {steps} значень на ваших останніх {cycles} циклах, щоб знайти налаштування, за якого правильно зіставляється найбільше циклів.",
"pg_sweep_step": "Крок {done} / {total}",
"pg_undetected": "{n} цикл{s} не виявлено",
@@ -837,6 +884,9 @@
"pg_simulating": "Моделювання циклу…",
"pg_canvas_empty2": "Виберіть цикл вище й натисніть «Запустити», щоб змоделювати його. Потім наведіть, щоб побачити значення, прокрутіть для масштабування та перетягніть для панорамування.",
"pg_apply_settings_confirm": "Зберегти ці налаштування ({n}) на цей пристрій? {list}",
"pg_stress_terminated_detail": "Стабілізувалося на ~{idle}W у режимі очікування -- цикл завершився {h} год {m} хв пізніше через {reason}.",
"pg_stress_above_threshold_detail": "Споживання в режимі очікування ~{idle}W дорівнює або перевищує фактичний поріг зупинки ({stop}W) -- цикл так і не був зафіксований як неактивний. Щоб виправити, збільште stop_threshold_w.",
"pg_stress_hit_cap_detail": "Цикл працював {h} год {m} хв без зупинки -- примусово зупинений захисним лімітом. Споживання в режимі очікування {idle}W порівняно з порогом зупинки {stop}W.",
"no_logs_match": "Немає записів журналу, що відповідають цим фільтрам.",
"pg_analysis_hint2": "Виберіть цикл і натисніть Запустити, щоб завантажити аналіз збігу.",
"pg_analysis_empty2": "Натисніть Запустити, щоб побачити аналіз збігу.",
@@ -864,7 +914,15 @@
"store_share_device_intro": "Поділитися програмами вашого пристрою (профілі + еталонні цикли) зі спільнотою. Налаштування необов'язкові.",
"share_profile_no_cycles": "Профіль '{p}' не має ⭐ еталонних циклів -- його буде пропущено, якщо у вас немає {n}+ підтверджених запусків",
"advisory_phase_inconsistent": "Схоже, що '{name}' поєднує різні програми або температури - його цикли нагріваються протягом дуже різного часу. Розділення на окремі профілі (напр. за температурою) покращить зіставлення та оцінки часу.",
"advisory_phase_inconsistent_title": "⚠ Можливо, змішані програми"
"advisory_phase_inconsistent_title": "⚠ Можливо, змішані програми",
"export_select_intro": "Позначте, що саме включити. Вибір профілів без їхніх циклів усе одно експортує розпізнавану програму (її вивчена форма передається разом із нею).",
"import_analyze_hint": "Завантажте експортований файл (або вставте його JSON). WashData проаналізує його та покаже, що саме можна імпортувати, перш ніж щось зміниться.",
"analyzing": "Аналіз…",
"device_type_mismatch_warn": "Цей експорт отримано від приладу іншого типу ({src} замість {local}). Програми та цикли все одно можна імпортувати як довідкові дані, але налаштування, специфічні для пристрою, та імпорт реальної історії вимкнено.",
"merge_hint": "Імпортовані елементи додаються; нічого локального не втрачається. Конфлікти імен вирішуються нижче.",
"replace_warn": "Кожна позначена категорія очищується та замінюється даними з файлу. Непозначені категорії залишаються без змін.",
"dest_reference_hint": "Імпортовані цикли лише покращують розпізнавання програм і ніколи не впливають на статистику використання/енергії.",
"dest_real_history_hint": "Імпортовані цикли зараховуються як власна історія цього пристрою та враховуються в статистиці енергії/використання. Використовуйте для перенесення одного приладу на нове встановлення."
},
"phase_desc": {
"anti_crease": "Час від часу короткі оберти після завершення для зменшення зморшок.",
@@ -962,6 +1020,10 @@
"doc": "Потужність має бути нижче цього між імпульсами, щоб режим проти зморшок залишався активним.",
"label": "Поріг потужності виходу"
},
"anti_wrinkle_idle_timeout": {
"label": "Макс. пауза між імпульсами",
"doc": "Як довго машина може залишатися тихою між двома імпульсами перекидання барабана, перш ніж режим захисту від зминання завершиться. Установіть вище за найдовшу паузу, яку ваша сушильна машина залишає між імпульсами, інакше кожен наступний імпульс буде зчитано як хибний запуск."
},
"anti_wrinkle_max_duration": {
"doc": "Імпульси, довші за цей, розглядаються як справжній цикл, а не як рух проти зморшок.",
"label": "Максимальна тривалість"
@@ -1580,7 +1642,9 @@
"store_device_shared_partial": "Часткова публікація: {n} програм{s} завантажено, {failed} пропущено",
"store_device_shared_some_dup": "Конфігурацію пристрою опубліковано: {n} програм{s} завантажено ({dup} вже існувало)",
"store_download_failed": "Помилка завантаження: {error}",
"store_download_nothing": "Нема що завантажувати -- всі профілі вже існують на цьому пристрої"
"store_download_nothing": "Нема що завантажувати -- всі профілі вже існують на цьому пристрої",
"export_selective_done": "Експорт завантажено",
"import_selective_done": "Імпортовано профілів: {profiles}, циклів: {cycles}"
},
"suggestion": {
"both_agree": "WashData рекомендує",
@@ -1714,6 +1778,11 @@
"start_duration_threshold": "Секунди вище порогу для підтвердження старту",
"start_threshold_w": "Мінімум ват, щоб вважати запущеним",
"stop_threshold_w": "Нижче цього машина вважається вимкненою",
"anti_wrinkle_enabled": "Поглинати імпульси перекидання барабана після основної фази, а не зчитувати їх як нові цикли",
"anti_wrinkle_exit_power": "Потужність між імпульсами має падати нижче цього, щоб захист від зминання залишався активним",
"anti_wrinkle_idle_timeout": "Дозволена тиша між двома імпульсами перекидання барабана, перш ніж захист від зминання завершиться",
"anti_wrinkle_max_duration": "Імпульс, довший за цей, завершує захист від зминання та відкриває новий цикл",
"anti_wrinkle_max_power": "Імпульс, вищий за цей, завершує захист від зминання та відкриває новий цикл",
"dtw_bandwidth": "Етап 3: смуга деформації Sakoe-Chiba (0 = DTW вимкнено; за замовчуванням 0,2 = 20% довжини циклу)",
"corr_weight": "Етап 2: баланс між формою кривої (кореляція) та рівнем потужності (MAE); за замовчуванням 0,45",
"duration_weight": "Етап 4: наскільки сильно збіг за тривалістю впливає на кінцеву оцінку (за замовчуванням 0,22)",
@@ -28,7 +28,9 @@
"awaiting_n": "等待批准 · 已确认 {n} 个",
"approved": "已批准",
"awaiting": "等待批准",
"imported_tip": "从社区商店导入。仅用于匹配,不计入统计。"
"imported_tip": "从社区商店导入。仅用于匹配,不计入统计。",
"not_importable": "此处不适用",
"exists": "已存在"
},
"btn": {
"add_device": "+ 添加设备",
@@ -169,7 +171,14 @@
"download_device": "下载此配置",
"share_device": "⬆ 分享此设备",
"share_device_tip": "将此设备及其录制的参考周期分享到社区商店,让拥有相同机器的其他用户可以采用",
"share_n": "分享 {n} 个周期"
"share_n": "分享 {n} 个周期",
"export_selected": "导出(选择数据)",
"export_all": "快速导出全部",
"import_raw": "高级:用 JSON 替换全部",
"download_export": "下载导出文件",
"analyze_import": "分析文件",
"import_selected": "导入所选",
"back": "返回"
},
"conflict": {
"anti_wrinkle_exit": {
@@ -390,6 +399,7 @@
"panel_default_tab": "默认标签",
"panel_language": "面板语言",
"per_cycle_short": "周期",
"pg_anti_wrinkle": "防皱",
"pg_autofill_tip": "根据当前值自动填充范围({lo}{hi})",
"pg_best_value": "找到的最佳值",
"pg_current_value": "当前值:",
@@ -524,6 +534,12 @@
"pg_sweep_param": "参数",
"pg_objective": "目标",
"pg_steps": "步数",
"pg_stress_group": "闲置终止测试",
"pg_stress_idle_auto": "Auto",
"pg_stress_idle_w": "待机功率 (W)",
"pg_stress_idle_w_desc": "覆盖自动检测的待机功率下限(留空则使用自动值)。",
"pg_stress_toggle": "测试闲置终止",
"pg_stress_toggle_desc": "模拟设备在录制结束后维持待机功耗水平,查看 WashData 是否以及何时停止运行周期。",
"pg_sweep_2d": "2D 热图",
"pg_sweep_param_y": "第二个参数",
"pg_obj_match": "匹配准确度",
@@ -538,6 +554,9 @@
"aria_playground_chart2": "交互式周期功率图:悬停查看时间/功率,滚动缩放,拖动平移",
"pg_alert_timeout_end": "因超时而结束,并非预测结束",
"pg_alert_indefinite": "将一直运行下去",
"pg_alert_stress_cap": "已达到安全上限",
"pg_alert_stress_ok": "闲置终止: 运行周期已停止",
"pg_alert_stress_warn": "待机功耗超过停止阈值",
"task_pg_history": "用历史测试",
"task_pg_sweep": "优化",
"task_reprocess": "重新处理",
@@ -566,7 +585,31 @@
"task_split": "正在拆分周期",
"task_trim": "正在裁剪周期",
"task_merge": "正在合并周期",
"task_rebuild": "正在重建包络"
"task_rebuild": "正在重建包络",
"cat_profiles": "配置文件(程序)",
"cat_real_cycles": "周期(运行历史)",
"cat_reference_cycles": "参考周期(已导入)",
"cat_custom_phases": "自定义阶段",
"cat_profile_groups": "配置文件组",
"cat_settings": "检测与识别设置",
"cat_matching_config": "识别器调优",
"cat_ml_models": "ML 模型",
"cat_feedback": "反馈与审阅标签",
"cat_suggestions": "建议",
"cat_maintenance_log": "维护日志",
"cat_history_logs": "历史与更改日志",
"cat_lifetime_stats": "累计总量",
"cycles_short": "周期",
"import_mode": "合并方式",
"mode_merge": "合并(保留我的)",
"mode_replace": "替换所选",
"cycle_destination": "导入的周期应存放到哪里?",
"dest_reference": "参考(仅形状)",
"dest_real_history": "真实历史(计入统计)",
"conflict_resolution": "名称冲突",
"conflict_import_copy": "作为副本导入",
"conflict_keep_mine": "保留我的",
"conflict_overwrite": "覆盖"
},
"log": {
"all_levels": "所有级别",
@@ -643,7 +686,9 @@
"force_stop_msg": "立即强制停止当前周期吗?该周期将被保存为已中断。",
"store_import": "导入参考周期",
"store_share": "分享到社区商店",
"store_share_device": "分享此设备"
"store_share_device": "分享此设备",
"export_select": "导出 - 选择数据",
"import_wizard": "导入 - 选择数据"
},
"msg": {
"appliance_monitor": "设备监控",
@@ -665,9 +710,10 @@
"coverage_gap_similar_cycles": "发现 {count} 个相似未标记周期 – 创建配置文件以开始匹配它们。",
"cycles_deleted": "已删除 {count} 个周期",
"enough_data": "有足够数据可供学习({current}/{min} 个循环)。",
"export_description": "将所有配置文件和周期导出 JSON,或从之前的导出中恢复。",
"export_description": "精确选择要导出 JSON 的配置文件、周期、设置等,或分析文件并仅导入所需的部分。",
"feedback_cycles_pending": "{n} 个周期{s}待审查",
"feedback_prompt": "确认正确、修正程序或忽略。",
"feedback_relabel_hint": "重新标记此周期也会解决它。",
"filter_by_profile": "按配置文件筛选…",
"group_modal_help": "将形状相同但温度/转速不同的程序分组(时长可能不同)。匹配将组作为一个候选项评分,然后选择最合适的成员。至少选 2 个;叠加显示相似程度。",
"group_not_cohesive": "这些配置文件不够相似,无法可靠分组,因此匹配会单独处理它们,直到您移除离群值或拆分组。",
@@ -729,6 +775,7 @@
"pg_restart_note": "重启 Home Assistant 以启用模拟工具。",
"pg_sim_progress": "{done} / {total} 个周期",
"pg_starting": "正在启动…",
"pg_stress_synthetic": "待机模拟从此处开始",
"pg_sweep_intro": "如果 {param} 不同会怎样?在最近 {cycles} 个周期上测试 {steps} 个值,找到能正确匹配最多周期的设置。",
"pg_sweep_step": "步骤 {done} / {total}",
"pg_undetected": "{n} 个周期{s}未检测到",
@@ -837,6 +884,9 @@
"pg_simulating": "正在模拟周期…",
"pg_canvas_empty2": "在上方选择一个周期并按“运行”进行模拟。然后悬停查看数值,滚动缩放,拖动平移。",
"pg_apply_settings_confirm": "将这 {n} 项设置保存到该设备吗? {list}",
"pg_stress_terminated_detail": "稳定在约 {idle}W 的待机功率 -- {h} 小时 {m} 分钟后通过 {reason} 结束了周期。",
"pg_stress_above_threshold_detail": "待机功耗约 {idle}W 达到或超过有效停止阈值 ({stop}W) -- 周期从未被判定为静止状态。请提高 stop_threshold_w 以修复。",
"pg_stress_hit_cap_detail": "周期运行了 {h} 小时 {m} 分钟仍未停止 -- 被安全上限强制停止。待机功耗 {idle}W 对比停止阈值 {stop}W。",
"no_logs_match": "没有日志记录符合这些筛选条件。",
"pg_analysis_hint2": "选择一个周期并点击运行以加载匹配分析。",
"pg_analysis_empty2": "点击运行以查看匹配分析。",
@@ -864,7 +914,15 @@
"store_share_device_intro": "上传 {brand} {model} 及您选择的参考周期。拥有相同设备的其他用户可以采用您的程序。条目在公开前经过审核。",
"share_profile_no_cycles": "无参考周期 -- 请在\"周期\"选项卡中将周期标记为 ⭐ 以包含此配置文件",
"advisory_phase_inconsistent": "'{name}' 似乎混合了不同的程序或温度,各周期的加热时长差异很大。将其拆分为独立的配置文件(例如按温度区分)可以改善匹配和时间估算。",
"advisory_phase_inconsistent_title": "⚠ 可能混合了不同程序"
"advisory_phase_inconsistent_title": "⚠ 可能混合了不同程序",
"export_select_intro": "精确勾选要包含的内容。即使选择配置文件而不包含其周期,仍会导出可识别的程序(其学习到的形状会一同迁移)。",
"import_analyze_hint": "加载导出的文件(或粘贴其 JSON)。WashData 会对其进行分析,并在任何更改之前准确显示可导入的内容。",
"analyzing": "正在分析…",
"device_type_mismatch_warn": "此导出文件来自不同的设备类型({src} 对 {local})。程序和周期仍可作为参考数据导入,但设备专属设置和真实历史导入将被禁用。",
"merge_hint": "导入的项目会被添加,本地数据不会丢失。名称冲突将在下方解决。",
"replace_warn": "每个勾选的类别都会被清除并用文件中的内容替换。未勾选的类别保持不变。",
"dest_reference_hint": "导入的周期仅用于改进程序识别,绝不影响用量/能耗统计。",
"dest_real_history_hint": "导入的周期计为本设备自身的历史记录,并纳入能耗/用量统计。适用于将一台设备迁移到新的安装环境。"
},
"pg_desc": {
"completion_min_seconds": "计为真实周期的最短运行时间",
@@ -875,6 +933,11 @@
"start_duration_threshold": "确认启动所需的超阈值秒数",
"start_threshold_w": "计为启动的最小瓦数",
"stop_threshold_w": "低于此值机器计为关闭",
"anti_wrinkle_enabled": "吸收主阶段之后的翻滚脉冲,而不是将其识别为新周期",
"anti_wrinkle_exit_power": "脉冲之间的功率必须低于此值,防皱才能保持活动",
"anti_wrinkle_idle_timeout": "防皱结束前两次翻滚脉冲之间允许的静止时间",
"anti_wrinkle_max_duration": "长于此值的脉冲将结束防皱并开启新周期",
"anti_wrinkle_max_power": "高于此值的脉冲将结束防皱并开启新周期",
"dtw_bandwidth": "Stage 3: Sakoe-Chiba 规整带宽 (0 = DTW 关闭; 默认 0.2 = 周期长度的 20%)",
"corr_weight": "Stage 2: 曲线形状(相关性)与功率水平(MAE)之间的平衡; 默认 0.45",
"duration_weight": "Stage 4: 运行时长一致性对最终得分的影响程度(默认 0.22)",
@@ -985,6 +1048,10 @@
"doc": "脉冲之间的功率必须低于此值,抗皱模式才能保持活动状态。",
"label": "退出功率阈值"
},
"anti_wrinkle_idle_timeout": {
"label": "最大脉冲间隔",
"doc": "防皱模式结束前,机器在两次翻滚脉冲之间可以保持静止的时长。请将其设置为高于烘干机在脉冲之间留出的最长间隔,否则之后的每个脉冲都会被识别为误启动。"
},
"anti_wrinkle_max_duration": {
"doc": "比这更长的脉冲被视为真正的周期,而不是抗皱滚动。",
"label": "最长时长"
@@ -1603,7 +1670,9 @@
"store_device_shared_partial": "已分享 {n} 个周期;{failed} 个无法上传。",
"store_device_shared_some_dup": "已分享 {created} 个周期;{dup} 个已在商店中。",
"store_download_failed": "下载失败:{error}",
"store_download_nothing": "没有新内容可下载 - 此配置已在您的设备上。"
"store_download_nothing": "没有新内容可下载 - 此配置已在您的设备上。",
"export_selective_done": "导出已下载",
"import_selective_done": "已导入 {profiles} 个配置文件和 {cycles} 个周期"
},
"suggestion": {
"both_agree": "WashData建议",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Rekonfiguracja WashData",
"description": "Zaktualizuj nazwę urządzenia, typ urządzenia lub czujnik mocy.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigurar WashData",
"description": "Actualizar o nome do dispositivo, tipo de electrodoméstico ou sensor de potência.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"min_power": "Показания мощности выше этого порога (в ваттах) указывают на то, что устройство работает. Начните с 2 Вт для большинства устройств."
}
},
"first_profile": {
"title": "Создать первый профиль",
"description": "**Цикл** - это полная работа вашего прибора (например, загрузка белья). **Профиль** группирует эти циклы по типу (например, «Хлопок», «Быстрая стирка»). Создание профиля сейчас помогает интеграции сразу оценить продолжительность.\n\nВы можете вручную выбрать этот профиль в элементах управления устройством, если он не обнаружен автоматически.\n\n**Оставьте поле «Имя профиля» пустым, чтобы пропустить этот шаг.**",
"data": {
"profile_name": "Имя профиля (необязательно)",
"manual_duration": "Предполагаемая продолжительность (минуты)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Обновите имя устройства, тип устройства или датчик мощности.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Ponovno konfigurirajte WashData",
"description": "Posodobite ime naprave, vrsto naprave ali senzor moči.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Rikonfiguro WashData",
"description": "Përditëso emrin e pajisjes, llojin e pajisjes ose sensorin e fuqisë.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Ponovo konfiguriši WashData",
"description": "Ažuriraj naziv uređaja, tip uređaja ili senzor snage.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Konfigurera om WashData",
"description": "Uppdatera enhetens namn, apparattyp eller effektsensor.",
@@ -17,14 +17,6 @@
"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)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Update the device name, appliance type, or power sensor.",
@@ -17,14 +17,6 @@
"min_power": "Показники потужності вище цього порогу (у ватах) вказують на те, що прилад працює. Почніть з 2 Вт для більшості пристроїв."
}
},
"first_profile": {
"title": "Створити перший профіль",
"description": "**Цикл** – це повний запуск вашого приладу (наприклад, завантаження білизни). **Профіль** групує ці цикли за типом (наприклад, «Бавовна», «Швидке прання»). Створення профілю тепер допомагає негайно оцінити тривалість інтеграції.\n\nВи можете вручну вибрати цей профіль в елементах керування пристроєм, якщо він не визначається автоматично.\n\n**Залиште поле «Назва профілю» порожнім, щоб пропустити цей крок.**",
"data": {
"profile_name": "Ім'я профілю (необов'язково)",
"manual_duration": "Орієнтовна тривалість (хвилини)"
}
},
"reconfigure": {
"title": "Reconfigure WashData",
"description": "Оновіть назву пристрою, тип пристрою або датчик потужності.",
@@ -17,14 +17,6 @@
"min_power": "功率读数高于此阈值(以瓦为单位)表明设备正在运行。大多数设备从 2W 开始。"
}
},
"first_profile": {
"title": "创建第一个配置文件",
"description": "**周期**是您的设备的完整运行(例如,一堆衣物)。 **配置文件**按类型(例如“棉质”、“快洗”)对这些周期进行分组。现在创建配置文件可以帮助集成立即估计持续时间。\n\n如果未自动检测到此配置文件,您可以在设备控件中手动选择它。\n\n**将“配置文件名称”留空以跳过此步骤。**",
"data": {
"profile_name": "配置文件名称(可选)",
"manual_duration": "预计持续时间(分钟)"
}
},
"reconfigure": {
"title": "重新配置 WashData",
"description": "更新设备名称、设备类型或功率传感器。",
+284 -9
View File
@@ -63,7 +63,6 @@ from .const import (
CONF_PROFILE_UNMATCH_THRESHOLD,
CONF_PUMP_STUCK_DURATION,
CONF_POWER_OFF_THRESHOLD_W,
CONF_RUNNING_DEAD_ZONE,
CONF_SAMPLING_INTERVAL,
CONF_SMOOTHING_WINDOW,
CONF_START_DURATION_THRESHOLD,
@@ -76,7 +75,6 @@ from .const import (
DEFAULT_MIN_POWER,
DEFAULT_OFF_DELAY,
DEFAULT_OFF_DELAY_BY_DEVICE,
DEFAULT_RUNNING_DEAD_ZONE,
DEVICE_TYPE_PUMP,
MAINTENANCE_EVENT_TYPES,
DEVICE_TYPES,
@@ -94,6 +92,17 @@ from .ws_schema import WS_OPEN_RESPONSES, WS_RESPONSE_TYPES
_LOGGER = logging.getLogger(__name__)
# Read once at import time (manifest.json is static) so ws_get_constants never does
# blocking I/O on the event loop. Falls back to "" if the file is absent.
try:
from pathlib import Path as _Path
_INTEGRATION_VERSION: str = json.loads(
(_Path(__file__).parent / "manifest.json").read_text(encoding="utf-8")
).get("version", "")
del _Path
except Exception: # pragma: no cover # pylint: disable=broad-exception-caught
_INTEGRATION_VERSION = ""
# ─── WS response contract (Group H1) ────────────────────────────────────────────
# Debug-only validation of every send_result payload against the TypedDict
# registered for its command in ws_schema.py. OFF by default so production has
@@ -175,7 +184,6 @@ _SUGGESTION_KEYS: tuple[str, ...] = (
CONF_START_THRESHOLD_W,
CONF_STOP_THRESHOLD_W,
CONF_END_ENERGY_THRESHOLD,
CONF_RUNNING_DEAD_ZONE,
# Stage 1 detection suggestions
CONF_SMOOTHING_WINDOW,
CONF_START_DURATION_THRESHOLD,
@@ -198,7 +206,6 @@ _SUGGESTION_INT_KEYS: frozenset[str] = frozenset({
CONF_NO_UPDATE_ACTIVE_TIMEOUT,
CONF_PROFILE_MATCH_INTERVAL,
CONF_MIN_OFF_GAP,
CONF_RUNNING_DEAD_ZONE,
CONF_SMOOTHING_WINDOW,
CONF_COMPLETION_MIN_SECONDS,
CONF_END_REPEAT_COUNT,
@@ -229,7 +236,6 @@ _ML_COMPARE_SETTINGS: tuple[tuple[str, str, str], ...] = (
(CONF_STOP_THRESHOLD_W, "Stop Threshold", "W"),
(CONF_START_THRESHOLD_W, "Start Threshold", "W"),
(CONF_END_ENERGY_THRESHOLD, "End Energy Threshold", "Wh"),
(CONF_RUNNING_DEAD_ZONE, "Running Dead Zone", "s"),
(CONF_MIN_POWER, "Min Power", "W"),
(CONF_MIN_OFF_GAP, "Min Off Gap", "s"),
(CONF_DURATION_TOLERANCE, "Duration Tolerance", ""),
@@ -436,6 +442,8 @@ _PREF_LANG_TAG_RE = re.compile(r"^[A-Za-z]{2,3}(-[A-Za-z0-9]{2,8})*$")
_FULL_COMMANDS = frozenset({
"wipe_history", "import_config", "export_config", "clear_debug_data", "reprocess_history",
"trigger_ml_training",
# Selective export/import wizard (same full-data reach as the wholesale variants).
"get_export_inventory", "analyze_import", "export_config_selective", "import_config_selective",
# Reverting on-device models / matcher tuning discards learned state -> full access.
"revert_matching_config", "revert_ml_models",
})
@@ -454,6 +462,10 @@ _ADMIN_COMMANDS = frozenset({
"wipe_history",
"import_config",
"export_config",
"get_export_inventory",
"analyze_import",
"export_config_selective",
"import_config_selective",
"reprocess_history",
"clear_debug_data",
# Global community-store mutations: these change the ONE integration-wide GitHub
@@ -1072,6 +1084,9 @@ def async_register_commands(hass: HomeAssistant) -> None:
# Diagnostics
ws_get_diagnostics, ws_reprocess_history, ws_clear_debug_data,
ws_wipe_history, ws_export_config, ws_import_config,
# Selective export/import wizard (inventory + analyze + selective export/import)
ws_get_export_inventory, ws_analyze_import,
ws_export_config_selective, ws_import_config_selective,
# Shared constants
ws_get_constants,
# Suggestions
@@ -2076,9 +2091,17 @@ async def ws_label_cycle(
if not new_profile_name or not new_profile_name.strip():
connection.send_error(msg["id"], "invalid_format", "New profile name required")
return
await manager.profile_store.create_profile(new_profile_name.strip(), cycle_id)
applied_profile: str | None = new_profile_name.strip()
await manager.profile_store.create_profile(applied_profile, cycle_id)
else:
applied_profile = profile_name
await manager.profile_store.assign_profile_to_cycle(cycle_id, profile_name)
# Manually (re)labelling a cycle awaiting verification IS the user's answer,
# so resolve any pending feedback and drop it from the review queue (#331).
if hasattr(manager, "learning_manager"):
await manager.learning_manager.async_resolve_pending_from_label(
cycle_id, applied_profile
)
manager.notify_update()
_send_result(connection, msg["id"], "label_cycle", {"success": True})
except ValueError as exc:
@@ -2966,6 +2989,234 @@ async def ws_import_config(
connection.send_error(msg["id"], "unknown_error", str(exc))
# Soft cap on a pasted/uploaded import blob so a huge string can't wedge the
# executor / event loop. Whole-store exports with full traces are a few MB; 64 MB
# is comfortably above any legitimate payload.
_MAX_IMPORT_JSON_BYTES = 64 * 1024 * 1024
@websocket_api.websocket_command(
{vol.Required("type"): "ha_washdata/get_export_inventory", vol.Required("entry_id"): str}
)
@websocket_api.async_response
async def ws_get_export_inventory(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Return this device's per-category data inventory for the export wizard tree."""
entry_id: str = msg["entry_id"]
manager = _get_manager(hass, entry_id)
if manager is None:
_err_not_found(connection, msg["id"], entry_id)
return
entry = _get_entry(hass, entry_id)
try:
opts = dict(entry.options) if entry else {}
manifest = await hass.async_add_executor_job(
manager.profile_store.get_export_inventory, opts
)
_send_result(connection, msg["id"], "get_export_inventory", {"manifest": manifest})
except Exception as exc: # pylint: disable=broad-exception-caught
connection.send_error(msg["id"], "unknown_error", str(exc))
@websocket_api.websocket_command(
{
vol.Required("type"): "ha_washdata/analyze_import",
vol.Required("entry_id"): str,
vol.Required("json_data"): str,
}
)
@websocket_api.async_response
async def ws_analyze_import(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Parse a pasted/uploaded export and describe what it contains (no mutation).
Synchronous (not a detached task): parsing + walking a few-MB file is tens of ms.
Returns ``{"manifest": {...}}``; a structural problem is reported inside the
manifest as ``manifest.error`` so the panel can render it inline.
"""
from .const import CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE
entry_id: str = msg["entry_id"]
manager = _get_manager(hass, entry_id)
if manager is None:
_err_not_found(connection, msg["id"], entry_id)
return
json_data: str = msg["json_data"]
if len(json_data.encode("utf-8", "ignore")) > _MAX_IMPORT_JSON_BYTES:
connection.send_error(msg["id"], "invalid_json", "Import payload is too large")
return
entry = _get_entry(hass, entry_id)
device_type = ""
if entry is not None:
device_type = entry.options.get(
CONF_DEVICE_TYPE, entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE)
)
try:
from .profile_store import build_import_manifest
local_names = list(manager.profile_store.get_profiles().keys())
def _analyze() -> dict[str, Any]:
payload = json.loads(json_data)
return build_import_manifest(
payload, local_device_type=device_type, local_profile_names=local_names
)
manifest = await hass.async_add_executor_job(_analyze)
_send_result(connection, msg["id"], "analyze_import", {"manifest": manifest})
except json.JSONDecodeError as exc:
connection.send_error(msg["id"], "invalid_json", str(exc))
except Exception as exc: # pylint: disable=broad-exception-caught
connection.send_error(msg["id"], "unknown_error", str(exc))
@websocket_api.websocket_command(
{
vol.Required("type"): "ha_washdata/export_config_selective",
vol.Required("entry_id"): str,
vol.Required("selection"): dict,
}
)
@websocket_api.async_response
async def ws_export_config_selective(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Export only the selected categories/items as a JSON string."""
entry_id: str = msg["entry_id"]
manager = _get_manager(hass, entry_id)
if manager is None:
_err_not_found(connection, msg["id"], entry_id)
return
entry = _get_entry(hass, entry_id)
selection = msg["selection"]
try:
payload = manager.profile_store.export_data(
entry_data=dict(entry.data) if entry else {},
entry_options=dict(entry.options) if entry else {},
selection=selection,
)
json_str = await hass.async_add_executor_job(
lambda: json.dumps(payload, indent=2)
)
_send_result(
connection, msg["id"], "export_config_selective", {"json_data": json_str}
)
except Exception as exc: # pylint: disable=broad-exception-caught
connection.send_error(msg["id"], "unknown_error", str(exc))
@websocket_api.websocket_command(
{
vol.Required("type"): "ha_washdata/import_config_selective",
vol.Required("entry_id"): str,
vol.Required("json_data"): str,
vol.Required("selection"): dict,
vol.Optional("mode", default="merge"): vol.In(["merge", "replace"]),
vol.Optional("conflict_resolutions", default=dict): dict,
vol.Optional("cycle_destination", default="reference"): vol.In(
["reference", "real_history"]
),
vol.Optional("apply_settings", default=True): bool,
}
)
@websocket_api.async_response
async def ws_import_config_selective(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Selectively import chosen categories/items, merging into existing data."""
from .const import CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE, SHAREABLE_SETTING_KEYS
entry_id: str = msg["entry_id"]
manager = _get_manager(hass, entry_id)
if manager is None:
_err_not_found(connection, msg["id"], entry_id)
return
json_data: str = msg["json_data"]
if len(json_data.encode("utf-8", "ignore")) > _MAX_IMPORT_JSON_BYTES:
connection.send_error(msg["id"], "invalid_json", "Import payload is too large")
return
# Serialize under the per-entry write lock so the import can't interleave with a
# concurrent reprocess / recording persist (which would corrupt the store).
async with _entry_write_lock(hass, entry_id):
try:
entry = _get_entry(hass, entry_id)
device_type = ""
if entry is not None:
device_type = entry.options.get(
CONF_DEVICE_TYPE, entry.data.get(CONF_DEVICE_TYPE, DEFAULT_DEVICE_TYPE)
)
payload = await hass.async_add_executor_job(json.loads, json_data)
summary = await manager.profile_store.async_import_data_selective(
payload,
selection=msg["selection"],
mode=msg["mode"],
conflict_resolutions=msg["conflict_resolutions"],
cycle_destination=msg["cycle_destination"],
apply_settings=msg["apply_settings"],
local_device_type=device_type,
)
# Re-validate after the awaits — the entry may have been reloaded (a new
# manager + store) during the import. Persisting through the detached
# manager/entry would clobber the live one, so bail out.
current_manager = _get_manager(hass, entry_id)
if current_manager is not manager:
_LOGGER.warning(
"Manager replaced during selective import for %s; aborting notify",
entry_id,
)
_send_result(
connection, msg["id"], "import_config_selective",
{"success": True, "summary": summary},
)
return
# Apply the imported settings subset onto this device's options. Only the
# SHAREABLE numeric allow-list ever reaches here (the store already filters),
# and identity keys are stripped for defense-in-depth. entry.data is never
# written (no power_sensor/device_type hijack).
entry = _get_entry(hass, entry_id)
settings = summary.get("settings") if isinstance(summary.get("settings"), dict) else {}
settings_applied = 0
if entry is not None and settings:
filtered = {
k: v for k, v in settings.items()
if k in SHAREABLE_SETTING_KEYS
and isinstance(v, (int, float)) and not isinstance(v, bool)
}
for key in _OPTIONS_IDENTITY_KEYS:
filtered.pop(key, None)
if filtered:
hass.config_entries.async_update_entry(
entry, options={**entry.options, **filtered}
)
settings_applied = len(filtered)
summary = {**summary, "settings_applied": settings_applied}
manager.notify_update()
_send_result(
connection, msg["id"], "import_config_selective",
{"success": True, "summary": summary},
)
except json.JSONDecodeError as exc:
connection.send_error(msg["id"], "invalid_json", str(exc))
except ValueError as exc:
connection.send_error(msg["id"], "invalid_format", str(exc))
except Exception as exc: # pylint: disable=broad-exception-caught
connection.send_error(msg["id"], "unknown_error", str(exc))
# ─── Shared constants ─────────────────────────────────────────────────────────
@websocket_api.websocket_command({vol.Required("type"): "ha_washdata/get_constants"})
@@ -2985,6 +3236,7 @@ def ws_get_constants(
{"id": key, "label": label}
for key, label in DEVICE_TYPES.items()
]
from .frontend import BRAND_ICON_URL as _BRAND_ICON_URL, BRAND_ICON_REGISTERED_KEY as _BRAND_ICON_KEY # pylint: disable=import-outside-toplevel
from .const import STORE_WEB_ORIGIN
from . import store_account
from .const import ( # pylint: disable=import-outside-toplevel
@@ -3003,6 +3255,8 @@ def ws_get_constants(
MATCH_ENERGY_SCALE,
)
_send_result(connection, msg["id"], "get_constants", {
"version": _INTEGRATION_VERSION,
"icon_url": _BRAND_ICON_URL if hass.data.get(_BRAND_ICON_KEY) else None,
"device_types": device_types,
"state_colors": dict(STATE_COLORS),
"ml_lab_enabled": SHOW_ML_LAB,
@@ -3962,7 +4216,19 @@ async def ws_get_power_history(
cycle_start = getattr(detector, "current_cycle_start", None) if detector else None
if cycle_start and trace:
start_dt = trace[0][0]
start_ts = start_dt.timestamp()
live = [[round((t - start_dt).total_seconds(), 1), round(float(p), 1)] for t, p in trace]
# Overlay any diag_buffer readings strictly newer than the last trace
# point. During STATE_ENDING the detector trace can lag the raw sensor
# by up to one sampling interval (readings are throttled; the diag_buffer
# records every reading before the throttle), so the chart would appear
# frozen at the last active reading until the next throttle-pass.
if diag is not None and live:
last_trace_ts = trace[-1][0].timestamp()
for raw_ts, raw_w in diag.power_samples(last_trace_ts + 0.1):
offset_s = round(raw_ts - start_ts, 1)
if offset_s > live[-1][0]:
live.append([offset_s, round(float(raw_w), 1)])
out["cycle_active"] = True
out["live"] = _downsample(live)
out["cycle_elapsed_s"] = live[-1][0] if live else 0.0
@@ -4923,7 +5189,6 @@ def _playground_base_config(manager: Any, entry: Any) -> CycleDetectorConfig:
completion_min_seconds=int(opts.get(CONF_COMPLETION_MIN_SECONDS, 600)),
end_repeat_count=int(opts.get(CONF_END_REPEAT_COUNT, 1)),
min_off_gap=int(opts.get(CONF_MIN_OFF_GAP, 60)),
running_dead_zone=int(opts.get(CONF_RUNNING_DEAD_ZONE, DEFAULT_RUNNING_DEAD_ZONE)),
start_threshold_w=float(opts.get(CONF_START_THRESHOLD_W, min_power)),
stop_threshold_w=float(
opts.get(CONF_STOP_THRESHOLD_W, min_power * 0.6 if min_power else 2.0)
@@ -5449,6 +5714,8 @@ _PG_DETAIL_CHUNK = 250
async def _pg_detail_task(
hass: HomeAssistant, task: Any, entry_id: str,
cycle_id: str, override: dict[str, Any] | None,
stress_tail: bool = False,
stress_idle_w: float | None = None,
) -> None:
reg = task_registry.get_registry(hass)
ctx = _playground_context(hass, entry_id)
@@ -5460,6 +5727,7 @@ async def _pg_detail_task(
sim = await hass.async_add_executor_job(
playground.build_cycle_detail_sim_by_id,
store, cycle_id, base_config, override, options, price,
stress_tail, stress_idle_w,
)
if isinstance(sim, dict): # {"error": ...} marker (not_found / build failure)
if sim.get("error") == "not_found":
@@ -5478,7 +5746,10 @@ async def _pg_detail_task(
await hass.async_add_executor_job(sim.step, i, i + _PG_DETAIL_CHUNK)
reg.update(task, done=min(total, i + _PG_DETAIL_CHUNK))
if not task.cancel_requested:
await hass.async_add_executor_job(sim.run_tail)
if sim.stress_tail:
await hass.async_add_executor_job(sim.run_stress_tail)
else:
await hass.async_add_executor_job(sim.run_tail)
payload = await hass.async_add_executor_job(sim.finalize)
payload["partial"] = task.cancel_requested
reg.finish(
@@ -5500,6 +5771,8 @@ async def _pg_detail_task(
vol.Required("entry_id"): str,
vol.Required("cycle_id"): str,
vol.Optional("settings_override", default=dict): dict,
vol.Optional("stress_tail", default=False): bool,
vol.Optional("stress_idle_w", default=None): vol.Any(vol.Coerce(float), None),
}
)
@callback
@@ -5522,8 +5795,10 @@ def ws_start_playground_cycle_detail(
label_key="task.pg_detail.simulate", label_params={},
)
override = dict(msg.get("settings_override") or {})
stress_tail = bool(msg.get("stress_tail", False))
stress_idle_w = msg.get("stress_idle_w")
_raw = hass.async_create_task(
_pg_detail_task(hass, task, entry_id, msg["cycle_id"], override)
_pg_detail_task(hass, task, entry_id, msg["cycle_id"], override, stress_tail, stress_idle_w)
)
if _raw is not None:
reg.link_asyncio_task(task.id, _raw)
@@ -200,6 +200,19 @@ class ExportConfigResponse(TypedDict):
json_data: str
class GetExportInventoryResponse(TypedDict):
manifest: dict[str, Any]
class AnalyzeImportResponse(TypedDict):
manifest: dict[str, Any]
class ImportConfigSelectiveResponse(TypedDict):
success: bool
summary: dict[str, Any]
# ─── Shared constants ──────────────────────────────────────────────────────────
class GetConstantsResponse(TypedDict):
@@ -630,6 +643,10 @@ WS_RESPONSE_TYPES: dict[str, type] = {
"wipe_history": SuccessResponse,
"export_config": ExportConfigResponse,
"import_config": SuccessResponse,
"get_export_inventory": GetExportInventoryResponse,
"analyze_import": AnalyzeImportResponse,
"export_config_selective": ExportConfigResponse,
"import_config_selective": ImportConfigSelectiveResponse,
"get_constants": GetConstantsResponse,
"get_suggestions": GetSuggestionsResponse,
"apply_suggestions": ApplySuggestionsResponse,
@@ -835,6 +852,18 @@ WS_COMMANDS: dict[str, dict] = {
"wipe_history": {"params": [_entry()]},
"export_config": {"params": [_entry()]},
"import_config": {"params": [_entry(), _p("json_data", "str")]},
"get_export_inventory": {"params": [_entry()]},
"analyze_import": {"params": [_entry(), _p("json_data", "str")]},
"export_config_selective": {"params": [_entry(), _p("selection", "dict")]},
"import_config_selective": {"params": [
_entry(),
_p("json_data", "str"),
_p("selection", "dict"),
_p("mode", "str", False, enum=["merge", "replace"]),
_p("conflict_resolutions", "dict", False),
_p("cycle_destination", "str", False, enum=["reference", "real_history"]),
_p("apply_settings", "bool", False),
]},
"get_constants": {"params": []},
"get_suggestions": {"params": [_entry()]},
"apply_suggestions": {"params": [_entry(), _p("keys", "list[str]")]},
@@ -946,6 +975,8 @@ WS_COMMANDS: dict[str, dict] = {
_entry(),
_p("cycle_id", "str"),
_p("settings_override", "dict", False),
_p("stress_tail", "bool", False),
_p("stress_idle_w", "float|null", False),
]},
# Community store (online features)
"store_status": {"params": [_entry()]},
@@ -109,8 +109,6 @@ const _SETTINGS_SECTIONS = [
doc: 'Energy (power x time) the appliance must consume before RUNNING. A brief high-power spike has very low energy and is ignored, preventing false starts.' },
{ key: 'completion_min_seconds', label: 'Min Cycle Duration', unit: 's', type: 'number', min: 0, def: 600, basic: true,
doc: 'Cycles shorter than this are discarded as ghost cycles (test runs, opening the door to add a sock).' },
{ key: 'running_dead_zone', label: 'Running Dead Zone', unit: 's', type: 'number', min: 0, def: 3,
doc: 'After a cycle starts, power dips within this window are ignored. Washing machines fill with cold water (dropping near 0 W before heating) - without this protection that fill phase looks like a cycle end. This does NOT skip data: the full power trace is recorded from T=0. The suggestion engine measures your machine\'s actual startup pattern and sizes this automatically.' },
] },
{ sub: 'Cycle End', fields: [
{ key: 'end_energy_threshold', label: 'End Energy', unit: 'Wh', type: 'number', step: 0.001, min: 0, def: 0.05,
@@ -190,6 +188,8 @@ const _SETTINGS_SECTIONS = [
doc: 'Pulses longer than this are treated as a real cycle rather than an anti-wrinkle tumble.' },
{ key: 'anti_wrinkle_exit_power', label: 'Exit Power Threshold', unit: 'W', type: 'number', step: 0.1, min: 0, def: 0.8,
doc: 'Power must fall below this between pulses for anti-wrinkle mode to stay active.' },
{ key: 'anti_wrinkle_idle_timeout', label: 'Max Pulse Gap', unit: 's', type: 'number', step: 30, min: 0, def: 120,
doc: 'How long the machine may stay quiet between two tumble pulses before anti-wrinkle mode ends. Set it above the longest gap your dryer leaves between pulses, otherwise every later pulse is read as a false start.' },
] },
{ id: 'delay', label: 'Delay Start', intro: 'Delayed-start detection identifies when an appliance is powered but has not yet begun its cycle.', fields: [
{ key: 'delay_start_detect_enabled', label: 'Enable Delay-Start Detection', type: 'checkbox',
@@ -1509,6 +1509,7 @@ const _DIAGRAM_BY_KEY = {
no_update_active_timeout: 'watchdog_timeout',
anti_wrinkle_enabled: 'anti_wrinkle', anti_wrinkle_max_power: 'anti_wrinkle',
anti_wrinkle_max_duration: 'anti_wrinkle', anti_wrinkle_exit_power: 'anti_wrinkle',
anti_wrinkle_idle_timeout: 'anti_wrinkle',
sampling_interval: 'sampling',
};
@@ -1694,7 +1695,7 @@ class HaWashdataPanel extends HTMLElement {
this._hoverRafId = null; // rAF handle for chart-hover coalescing
this._hoverPending = null; // last pending hover coords {px, py, id}
// Data
this._constants = { stateColors: {}, deviceTypes: [], mlLabEnabled: false, mlSuggestionsEnabled: false, mlTrainingAvailable: false, storeOnlineAvailable: false, storeOnlineEnabled: false, storeWebOrigin: '', storePrefs: {}, pgMatchDefaults: {} };
this._constants = { stateColors: {}, deviceTypes: [], mlLabEnabled: false, mlSuggestionsEnabled: false, mlTrainingAvailable: false, storeOnlineAvailable: false, storeOnlineEnabled: false, storeWebOrigin: '', storePrefs: {}, pgMatchDefaults: {}, version: '', iconUrl: '' };
this._constantsLoaded = false;
this._devices = [];
this._cycles = [];
@@ -1810,6 +1811,8 @@ class HaWashdataPanel extends HTMLElement {
this._pgView = null; // {min,max} time-axis zoom window (seconds); null = full
this._pgHoverT = null; // hovered time (seconds) for cursor readout; null = none
this._pgMap = null; // current time<->x mapping, set by _pgDrawCanvas
this._pgStressTail = false; // idle termination test toggle
this._pgStressIdleW = null; // null = auto-derive; number = manual override (W)
this._pgPanStart = null; // pan drag anchor {clientX, vMin, vMax, totalDur}
this._pgHoverEvent = null; // {t,type} of the event pin under the cursor (tooltip)
this._pgBatchProgress = null; // {done,total} for history/sweep chunked runs (determinate bar)
@@ -2354,7 +2357,7 @@ class HaWashdataPanel extends HTMLElement {
if (!this._constantsLoaded) {
try {
const c = await this._ws({ type: `${_DOMAIN}/get_constants` });
this._constants = { stateColors: c.state_colors || {}, deviceTypes: c.device_types || [], mlLabEnabled: !!(c.ml_lab_enabled), mlSuggestionsEnabled: !!(c.ml_suggestions_enabled), mlTrainingAvailable: !!(c.ml_training_available), storeOnlineAvailable: !!(c.store_online_available), storeOnlineEnabled: !!(c.store_online_enabled), storeWebOrigin: c.store_web_origin || '', storePrefs: c.store_prefs || {}, pgMatchDefaults: c.pg_match_defaults || {}, PROFILE_MIN_WARMUP_CYCLES: c.PROFILE_MIN_WARMUP_CYCLES };
this._constants = { stateColors: c.state_colors || {}, deviceTypes: c.device_types || [], mlLabEnabled: !!(c.ml_lab_enabled), mlSuggestionsEnabled: !!(c.ml_suggestions_enabled), mlTrainingAvailable: !!(c.ml_training_available), storeOnlineAvailable: !!(c.store_online_available), storeOnlineEnabled: !!(c.store_online_enabled), storeWebOrigin: c.store_web_origin || '', storePrefs: c.store_prefs || {}, pgMatchDefaults: c.pg_match_defaults || {}, PROFILE_MIN_WARMUP_CYCLES: c.PROFILE_MIN_WARMUP_CYCLES, version: c.version || '', iconUrl: c.icon_url || '' };
} catch (_) { /* fall back to humanized labels */ }
try {
this._panelCfg = await this._ws({ type: `${_DOMAIN}/get_panel_config` });
@@ -2858,6 +2861,7 @@ class HaWashdataPanel extends HTMLElement {
this._busy.delete('pg-history'); this._busy.delete('pg-sweep'); this._pgBatchProgress = null;
// Cancel any pending detail re-run so it can't repopulate the outgoing device.
if (this._pgDetailDebounceTimer) { clearTimeout(this._pgDetailDebounceTimer); this._pgDetailDebounceTimer = null; }
this._pgStressTail = false; this._pgStressIdleW = null;
// Reset the Community Store browse on device change (status re-fetched per-tab).
this._storeView = 'brands'; this._storeDevice = null; this._storeProfile = null; this._storeQuery = '';
this._storeDevices = []; this._storeProfiles = []; this._storeCycles = [];
@@ -3467,12 +3471,16 @@ class HaWashdataPanel extends HTMLElement {
const working = nonTaskBusy
? `<span class="wd-badge" style="margin:0 0 0 12px;color:var(--app-header-text-color,#fff);background:rgba(255,255,255,.15)">${this._t('status.working', {}, 'Working…')}</span>`
: '';
const logo = `<svg class="wd-logo" viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true">
const iconUrl = this._constants.iconUrl;
const logo = iconUrl
? `<img class="wd-logo" src="${_esc(iconUrl)}" width="30" height="30" alt="WashData" aria-hidden="true" style="border-radius:6px;object-fit:contain">`
: `<svg class="wd-logo" viewBox="0 0 24 24" width="26" height="26" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true">
<rect x="4" y="2.5" width="16" height="19" rx="2.5"/>
<line x1="7" y1="6" x2="9.5" y2="6"/>
<circle cx="12" cy="14" r="5"/>
<circle cx="12" cy="14" r="2"/>
</svg>`;
const ver = this._constants.version;
const burger = `<button class="wd-burger" id="wd-burger" aria-label="${_esc(this._t('hdr.toggle_sidebar', {}, 'Toggle Home Assistant sidebar'))}" title="${_esc(this._t('hdr.toggle_sidebar', {}, 'Toggle Home Assistant sidebar'))}">
<svg viewBox="0 0 24 24" width="24" height="24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><line x1="4" y1="7" x2="20" y2="7"/><line x1="4" y1="12" x2="20" y2="12"/><line x1="4" y1="17" x2="20" y2="17"/></svg>
</button>`;
@@ -3480,7 +3488,7 @@ class HaWashdataPanel extends HTMLElement {
<div class="wd-header">
${burger}
${logo}
<div><h1>WashData</h1><div class="wd-sub">${this._t('msg.appliance_monitor', {}, 'Appliance monitor')}</div></div>
<div><h1>WashData</h1><div class="wd-sub">${ver ? `v${_esc(ver)} &middot; ` : ''}${this._t('msg.appliance_monitor', {}, 'Appliance monitor')}</div></div>
${working}
<span class="wd-task-pills" id="wd-task-pills">${this._htmlTaskPills()}</span>
<span style="flex:1"></span>
@@ -5219,6 +5227,11 @@ class HaWashdataPanel extends HTMLElement {
['start_duration_threshold','Start Duration', 's', 'Seconds above threshold to confirm start', 'timing'],
['end_repeat_count', 'End Repeat Count', '', 'Low readings in a row before ending', 'advanced'],
['interrupted_min_seconds', 'Interrupted Min', 's', 'Short cycles flagged as interrupted', 'advanced'],
['anti_wrinkle_enabled', 'Enable Anti-Wrinkle Detection', '', 'Absorb the tumble pulses after the main phase instead of reading them as new cycles', 'advanced', 'bool'],
['anti_wrinkle_max_power', 'Max Anti-Wrinkle Power','W', 'A pulse above this ends anti-wrinkle and opens a new cycle', 'advanced'],
['anti_wrinkle_max_duration','Max Anti-Wrinkle Duration','s', 'A pulse longer than this ends anti-wrinkle and opens a new cycle', 'advanced'],
['anti_wrinkle_exit_power', 'Anti-Wrinkle Exit Power','W', 'Power must fall below this between pulses for anti-wrinkle to stay active', 'advanced'],
['anti_wrinkle_idle_timeout','Max Pulse Gap', 's', 'Quiet time allowed between two tumble pulses before anti-wrinkle ends', 'advanced'],
['profile_match_min_duration_ratio', 'Min Duration Ratio', '', 'Stage 1: shortest run (vs the profile) still allowed to match', 'matching'],
['profile_match_max_duration_ratio', 'Max Duration Ratio', '', 'Stage 1: longest run (vs the profile) still allowed to match', 'matching'],
['corr_weight', 'Correlation Weight', '', 'Stage 2: balance between curve shape (correlation) and power level (MAE); default 0.45', 'matching'],
@@ -5358,13 +5371,14 @@ class HaWashdataPanel extends HTMLElement {
matching: this._t('lbl.pg_group_matching', {}, 'Program matching'),
};
let lastGroup = '';
const paramRows = fields.map(([key, fb, unit, desc, group]) => {
const paramRows = fields.map(([key, fb, unit, desc, group, type]) => {
const lbl = this._t('setting.' + key + '.label', {}, fb);
const liveVal = this._pgFieldVal(key, {});
let curVal;
if (key === 'start_threshold_w') curVal = this._pgThreshStart ?? liveVal;
else if (key === 'stop_threshold_w') curVal = this._pgThreshStop ?? liveVal;
else curVal = this._pgParamOverrides[key] ?? liveVal;
const isBool = type === 'bool';
const isDrag = threshFields.has(key);
const unitTxt = unit ? unit : '';
const gc = groupColors[group] || '#2a78d6';
@@ -5383,7 +5397,9 @@ class HaWashdataPanel extends HTMLElement {
${desc ? `<div style="font-size:.72em;color:var(--secondary-text-color);line-height:1.3">${_esc(this._t('pg_desc.' + key, {}, desc))}</div>` : ''}
</div>
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
<input class="wd-pg-param-inp" type="number" data-pgkey="${_esc(key)}" value="${curVal !== '' ? _esc(String(curVal)) : ''}" placeholder="${liveVal !== '' ? _esc(String(liveVal)) : ''}" style="width:72px">
${isBool
? `<input class="wd-pg-param-chk" type="checkbox" data-pgkey="${_esc(key)}" data-pgtype="bool" aria-label="${_esc(lbl)}" ${curVal ? 'checked' : ''} style="width:18px;height:18px;margin:1px 27px 0 27px">`
: `<input class="wd-pg-param-inp" type="text" inputmode="decimal" data-pgkey="${_esc(key)}" value="${curVal !== '' ? _esc(String(curVal)) : ''}" placeholder="${liveVal !== '' ? _esc(String(liveVal)) : ''}" aria-label="${_esc(lbl)}" style="width:72px">`}
${unitTxt ? `<span style="font-size:.75em;color:var(--secondary-text-color);min-width:14px">${_esc(unitTxt)}</span>` : ''}
</div>
</div>`;
@@ -5393,9 +5409,34 @@ class HaWashdataPanel extends HTMLElement {
const applyBtn = (this._canEdit() && hasOverrides)
? `<button class="wd-btn wd-btn-sm wd-btn-primary" data-action="pg-apply-settings" title="${_esc(this._t('btn.pg_apply_to_settings_tip', {}, 'Copy the values you edited here into this device\'s live settings'))}">${this._t('btn.pg_apply_to_settings', {}, 'Save to settings')}</button>`
: '';
const stressGc = '#c0392b';
const stressHeader = `<div style="display:flex;align-items:center;gap:8px;margin:12px 0 5px">
<div style="width:3px;height:14px;border-radius:2px;background:${stressGc};flex-shrink:0"></div>
<span style="font-size:.7em;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--secondary-text-color)">${_esc(this._t('lbl.pg_stress_group', {}, 'Idle termination test'))}</span>
</div>`;
const stressToggle = `<div style="display:flex;align-items:flex-start;gap:6px;margin:0 0 6px 11px">
<div style="flex:1;min-width:0">
<div style="font-size:.82em;font-weight:600;margin-bottom:1px">${_esc(this._t('lbl.pg_stress_toggle', {}, 'Test idle termination'))}</div>
<div style="font-size:.72em;color:var(--secondary-text-color);line-height:1.3">${_esc(this._t('lbl.pg_stress_toggle_desc', {}, 'Simulates the appliance staying at its standby draw after recording ends — shows if and when WashData stops the cycle.'))}</div>
</div>
<label style="display:flex;align-items:center;gap:4px;flex-shrink:0;cursor:pointer">
<input type="checkbox" id="wd-pg-stress-toggle" ${this._pgStressTail ? 'checked' : ''} aria-label="${_esc(this._t('lbl.pg_stress_toggle', {}, 'Test idle termination'))}">
</label>
</div>`;
const stressIdleField = this._pgStressTail ? `<div style="display:flex;align-items:flex-start;gap:6px;margin:0 0 6px 11px">
<div style="flex:1;min-width:0">
<div style="font-size:.82em;font-weight:600;margin-bottom:1px">${_esc(this._t('lbl.pg_stress_idle_w', {}, 'Idle level (W)'))}</div>
<div style="font-size:.72em;color:var(--secondary-text-color);line-height:1.3">${_esc(this._t('lbl.pg_stress_idle_w_desc', {}, 'Override the auto-detected standby floor (leave blank for auto).'))}</div>
</div>
<div style="display:flex;align-items:center;gap:4px;flex-shrink:0">
<input class="wd-pg-param-inp" type="text" inputmode="decimal" id="wd-pg-stress-idle-w" value="${this._pgStressIdleW != null ? _esc(String(this._pgStressIdleW)) : ''}" placeholder="${_esc(this._t('lbl.pg_stress_idle_auto', {}, 'Auto'))}" aria-label="${_esc(this._t('lbl.pg_stress_idle_w', {}, 'Idle level (W)'))}" style="width:72px">
<span style="font-size:.75em;color:var(--secondary-text-color);min-width:14px">W</span>
</div>
</div>` : '';
const stressGroup = `${stressHeader}${stressToggle}${stressIdleField}`;
return `<div class="wd-pg-params">
<div class="wd-subhead" style="margin:0 0 6px">${this._t('hdr.pg_detection_params', {}, 'Detection settings')}</div>
${paramRows}
${paramRows}${stressGroup}
<div style="display:flex;gap:6px;margin:8px 0 4px;align-items:center;flex-wrap:wrap">
${applyBtn}
<button class="wd-btn wd-btn-sm" data-action="pg-reset-params">${this._t('btn.reset', {}, 'Reset')}</button>
@@ -5417,7 +5458,7 @@ class HaWashdataPanel extends HTMLElement {
const alertRows = alerts.length
? alerts.map(a => `<div class="wd-pg-alert" style="border-left-color:${sevColor[a.severity] || sevColor.info}">
<span style="font-weight:600">${_esc(this._pgAlertLabel(a.code))}</span>
<div style="font-size:.78em;color:var(--secondary-text-color)">${_esc(a.detail || '')}</div>
<div style="font-size:.78em;color:var(--secondary-text-color)">${_esc(a.detail_key ? this._t(a.detail_key, a.detail_params || {}, a.detail || '') : (a.detail || ''))}</div>
</div>`).join('')
: `<div style="font-size:.82em;color:var(--success-color,#4caf50)">✓ ${this._t('msg.pg_no_alerts', {}, 'No issues detected in this run.')}</div>`;
const term = o.termination_reason ? String(o.termination_reason) : '—';
@@ -5448,6 +5489,9 @@ class HaWashdataPanel extends HTMLElement {
energy_anomaly: this._t('lbl.pg_alert_energy', {}, 'Energy anomaly'),
timeout_end: this._t('lbl.pg_alert_timeout_end', {}, 'Ended by timeout, not prediction'),
would_run_indefinitely: this._t('lbl.pg_alert_indefinite', {}, 'Would run indefinitely'),
stress_terminated: this._t('lbl.pg_alert_stress_ok', {}, 'Idle termination: cycle stopped'),
stress_above_threshold: this._t('lbl.pg_alert_stress_warn', {}, 'Idle draw above stop threshold'),
stress_hit_cap: this._t('lbl.pg_alert_stress_cap', {}, 'Hit safety cap'),
};
return map[code] || code;
}
@@ -5587,7 +5631,8 @@ class HaWashdataPanel extends HTMLElement {
_htmlPgSweepMode() {
const busy = this._busy.has('pg-sweep');
const fields = this._pgOverrideFields();
// Sweeping walks a numeric range, so on/off fields are not offered here.
const fields = this._pgOverrideFields().filter(([, , , , , type]) => type !== 'bool');
const paramOpts = fields.map(([k, fb]) => `<option value="${k}" ${this._pgSweepParam === k ? 'selected' : ''}>${_esc(this._t('setting.' + k + '.label', {}, fb))}</option>`).join('');
const objOpts = this._pgSweepObjectives().map(([k, lbl]) => `<option value="${k}" ${this._pgSweepObjective === k ? 'selected' : ''}>${_esc(lbl)}</option>`).join('');
const intro = `<p class="wd-sec-intro" style="margin:0 0 8px">${this._t('msg.pg_sweep_intro2', {}, 'Find the setting that best meets an objective across your recent cycles. Nothing changes until you apply it.')}</p>`;
@@ -5689,7 +5734,6 @@ class HaWashdataPanel extends HTMLElement {
}
});
this._render();
this._pgRerunDetail();
}
async _pgApplySweepValue(val) {
@@ -5709,7 +5753,6 @@ class HaWashdataPanel extends HTMLElement {
else this._pgParamOverrides[paramKey] = +val;
this._showToast(this._t('toast.settings_saved', {}, 'Settings saved; integration reloading'));
this._render();
this._pgRerunDetail();
} catch (e) { this._showToast(this._t('msg.toast_save_failed', {error: e.message || e}, 'Save failed: ' + (e.message || e)), 'error'); }
});
}
@@ -5919,7 +5962,7 @@ class HaWashdataPanel extends HTMLElement {
// completion here (the caller drives the busy spinner + redraw), but the
// backend yields the event loop between chunks. A header pill shows progress.
try {
const r = await this._ws({ type: `${_DOMAIN}/start_playground_cycle_detail`, entry_id: entryId, cycle_id: cid, settings_override: override });
const r = await this._ws({ type: `${_DOMAIN}/start_playground_cycle_detail`, entry_id: entryId, cycle_id: cid, settings_override: override, stress_tail: this._pgStressTail, stress_idle_w: this._pgStressIdleW != null ? parseFloat(this._pgStressIdleW) : null });
const tid = r && r.task_id;
if (!tid) throw new Error('no task id');
this._addProvisionalTask(tid, 'pg_detail', entryId, 0);
@@ -5967,6 +6010,7 @@ class HaWashdataPanel extends HTMLElement {
if (st === 'running' || st === 'paused') return 'running';
if (st === 'ending') return 'ending';
if (st === 'starting') return 'detecting';
if (st === 'anti_wrinkle') return 'anti_wrinkle';
return 'idle';
}
@@ -6032,8 +6076,19 @@ class HaWashdataPanel extends HTMLElement {
return;
}
const totalDur = (this._cycles || []).find(c => c.id === this._pgCycleId)?._pg_duration || pts[pts.length-1].t || 1;
const maxW = Math.max(...pts.map(p => p.w), 1);
// Stress tail — extract outcome early so totalDur and maxW can include it.
const stressOut = this._pgDetail && this._pgDetail.outcome && this._pgDetail.outcome.stress;
const stressFrom = stressOut && stressOut.enabled ? stressOut.synthetic_from_s : null;
const stressSeries = (stressFrom != null && this._pgDetail && this._pgDetail.series)
? this._pgDetail.series.filter(pt => pt.t >= stressFrom)
: [];
let totalDur = (this._cycles || []).find(c => c.id === this._pgCycleId)?._pg_duration || pts[pts.length-1].t || 1;
if (stressOut && stressOut.terminated && stressOut.terminated_after_s != null && stressFrom != null) {
totalDur = Math.max(totalDur, stressFrom + stressOut.terminated_after_s);
} else if (stressSeries.length) {
totalDur = Math.max(totalDur, stressSeries[stressSeries.length - 1].t);
}
const maxW = Math.max(...pts.map(p => p.w), ...stressSeries.map(p => p.power || 0), 1);
const threshStart = this._pgThreshStart ?? this._pgFieldVal('start_threshold_w', {}) ?? 50;
const threshStop = this._pgThreshStop ?? this._pgFieldVal('stop_threshold_w', {}) ?? 5;
@@ -6139,6 +6194,37 @@ class HaWashdataPanel extends HTMLElement {
pts.forEach((p, i) => i ? ctx.lineTo(toX(p.t), toY(p.w)) : ctx.moveTo(toX(p.t), toY(p.w)));
ctx.stroke();
// Stress-tail synthetic power trace — dashed continuation of the real curve.
if (stressSeries.length) {
const lastReal = pts[pts.length - 1];
ctx.beginPath();
ctx.moveTo(toX(lastReal.t), toY(lastReal.w));
stressSeries.forEach(p => ctx.lineTo(toX(p.t), toY(p.power)));
ctx.lineTo(toX(stressSeries[stressSeries.length - 1].t), toY(0));
ctx.lineTo(toX(lastReal.t), toY(0));
ctx.closePath();
ctx.fillStyle = _withAlpha(primary, 0.06); ctx.fill();
ctx.beginPath();
ctx.strokeStyle = primary; ctx.lineWidth = 1.5 * dpr;
ctx.setLineDash([4 * dpr, 4 * dpr]);
ctx.moveTo(toX(lastReal.t), toY(lastReal.w));
stressSeries.forEach(p => ctx.lineTo(toX(p.t), toY(p.power)));
ctx.stroke(); ctx.setLineDash([]);
}
// Stress-tail synthetic region: tinted overlay from synthetic_from_s onward
if (stressFrom != null && stressFrom < vMax) {
const xFrom = toX(stressFrom);
ctx.fillStyle = 'rgba(192,57,43,0.07)';
ctx.fillRect(xFrom, padT, Math.max(0, padL + plotW - xFrom), powerH);
ctx.save();
ctx.strokeStyle = 'rgba(192,57,43,0.5)';
ctx.lineWidth = dpr;
ctx.setLineDash([4*dpr, 4*dpr]);
ctx.beginPath(); ctx.moveTo(xFrom, padT); ctx.lineTo(xFrom, padT + powerH); ctx.stroke();
ctx.restore();
}
ctx.restore(); // end plot clip
// Threshold lines
@@ -6158,8 +6244,8 @@ class HaWashdataPanel extends HTMLElement {
drawThrLine(+threshStop, '#e34948', this._t('btn.stop', {}, 'Stop'));
// State band
const stateColors = { idle: bgCol, detecting: '#42a5f566', running: '#66bb6a66', ending: '#ef535066' };
const stateLabels = { idle: this._t('lbl.pg_idle', {}, 'Idle'), detecting: this._t('lbl.pg_detecting', {}, 'Detecting'), running: this._t('lbl.pg_ev_running', {}, 'Running'), ending: this._t('lbl.pg_ev_ending', {}, 'Ending') };
const stateColors = { idle: bgCol, detecting: '#42a5f566', running: '#66bb6a66', ending: '#ef535066', anti_wrinkle: '#ab47bc66' };
const stateLabels = { idle: this._t('lbl.pg_idle', {}, 'Idle'), detecting: this._t('lbl.pg_detecting', {}, 'Detecting'), running: this._t('lbl.pg_ev_running', {}, 'Running'), ending: this._t('lbl.pg_ev_ending', {}, 'Ending'), anti_wrinkle: this._t('lbl.pg_anti_wrinkle', {}, 'Anti-wrinkle') };
const stateY = ch - stateBandH - phaseBandH;
ctx.fillStyle = bgCol; ctx.fillRect(padL, stateY, cw - padL - padR, stateBandH);
// Real detector state band from the backend simulation (no client-side copy).
@@ -6377,7 +6463,9 @@ class HaWashdataPanel extends HTMLElement {
_pgUpdateParamInput(key, val) {
const sr = this.shadowRoot;
const inp = sr && sr.querySelector(`[data-pgkey="${key}"]`);
if (inp) inp.value = typeof val === 'number' ? Math.round(val) : val;
if (!inp) return;
if (inp.dataset.pgtype === 'bool') inp.checked = !!val;
else inp.value = typeof val === 'number' ? Math.round(val) : val;
}
// Update the readout strip for a hovered time (seconds), or the final state
@@ -6399,6 +6487,7 @@ class HaWashdataPanel extends HTMLElement {
detecting: [this._t('lbl.pg_detecting', {}, 'Detecting'), '#42a5f5'],
running: [this._t('lbl.pg_ev_running', {}, 'Running'), '#66bb6a'],
ending: [this._t('lbl.pg_ev_ending', {}, 'Ending'), '#ef5350'],
anti_wrinkle: [this._t('lbl.pg_anti_wrinkle', {}, 'Anti-wrinkle'), '#ab47bc'],
};
const [stateText, stateColor] = stripStateMap[stateKey] || stripStateMap.idle;
const pct = sp && sp.progress != null ? Math.round(sp.progress) : null;
@@ -6530,10 +6619,12 @@ class HaWashdataPanel extends HTMLElement {
</div>
<div class="wd-card">
<div class="wd-card-title">${this._t('hdr.export_import', {}, 'Export / Import')}</div>
<p class="wd-info" style="margin-bottom:12px">${this._t('msg.export_description', {}, 'Export all profiles and cycles to JSON, or restore from a previous export.')}</p>
<p class="wd-info" style="margin-bottom:12px">${this._t('msg.export_description', {}, 'Choose exactly which profiles, cycles, settings and more to export to JSON, or analyze a file and import only the parts you want.')}</p>
<div class="wd-card-actions">
<button class="wd-btn wd-btn-secondary" data-action="export-config">${this._t('btn.export_json', {}, 'Export to JSON')}</button>
<button class="wd-btn wd-btn-secondary" data-action="import-config-open">${this._t('btn.import_json', {}, 'Import from JSON')}</button>
<button class="wd-btn wd-btn-primary" data-action="export-select-open">${this._t('btn.export_selected', {}, 'Export (choose data)')}</button>
<button class="wd-btn wd-btn-primary" data-action="import-config-open">${this._t('btn.import_json', {}, 'Import from JSON')}</button>
<button class="wd-btn wd-btn-secondary" data-action="export-config">${this._t('btn.export_all', {}, 'Quick export everything')}</button>
<button class="wd-btn wd-btn-secondary" data-action="import-config-raw">${this._t('btn.import_raw', {}, 'Advanced: replace all from JSON')}</button>
</div>
</div>` : `<div class="wd-card"><p class="wd-info">${this._t('msg.maintenance_requires_access', {}, 'Maintenance and export/import require full access.')}</p></div>`}`;
}
@@ -7401,6 +7492,8 @@ class HaWashdataPanel extends HTMLElement {
if (m.type === 'profile-group') return `<div class="wd-overlay"><div class="wd-modal wd-modal-lg" role="dialog" aria-modal="true" aria-labelledby="wd-modal-title" tabindex="-1">${this._htmlProfileGroupModal(m)}</div></div>`;
if (m.type === 'compare-cycles') return `<div class="wd-overlay"><div class="wd-modal wd-modal-lg" role="dialog" aria-modal="true" aria-labelledby="wd-modal-title" tabindex="-1">${this._htmlCompareModal(m)}</div></div>`;
if (m.type === 'gear-settings') return `<div class="wd-overlay"><div class="wd-modal wd-modal-lg" role="dialog" aria-modal="true" aria-labelledby="wd-modal-title" tabindex="-1">${this._htmlGearModal(m)}</div></div>`;
if (m.type === 'export-select') return `<div class="wd-overlay"><div class="wd-modal wd-modal-lg" role="dialog" aria-modal="true" aria-labelledby="wd-modal-title" tabindex="-1">${this._htmlExportSelectModal(m)}</div></div>`;
if (m.type === 'import-wizard') return `<div class="wd-overlay"><div class="wd-modal wd-modal-lg" role="dialog" aria-modal="true" aria-labelledby="wd-modal-title" tabindex="-1">${this._htmlImportWizardModal(m)}</div></div>`;
let body = '';
if (m.type === 'confirm') {
@@ -7611,6 +7704,248 @@ class HaWashdataPanel extends HTMLElement {
</div>`;
}
// ── Selective export/import wizard ──────────────────────────────────────────
// Fixed display order for the category tree (mirrors _EXPORT_CATEGORIES on the
// backend). Enumerable categories (profiles / cycles) come first.
_wizCatOrder() {
return ['profiles', 'real_cycles', 'reference_cycles', 'custom_phases',
'profile_groups', 'settings', 'matching_config', 'ml_models', 'feedback',
'suggestions', 'maintenance_log', 'history_logs', 'lifetime_stats'];
}
_wizCatLabel(catId) {
const map = {
profiles: this._t('lbl.cat_profiles', {}, 'Profiles (programs)'),
real_cycles: this._t('lbl.cat_real_cycles', {}, 'Cycles (run history)'),
reference_cycles: this._t('lbl.cat_reference_cycles', {}, 'Reference cycles (imported)'),
custom_phases: this._t('lbl.cat_custom_phases', {}, 'Custom phases'),
profile_groups: this._t('lbl.cat_profile_groups', {}, 'Profile groups'),
settings: this._t('lbl.cat_settings', {}, 'Detection & matching settings'),
matching_config: this._t('lbl.cat_matching_config', {}, 'Matcher tuning'),
ml_models: this._t('lbl.cat_ml_models', {}, 'ML models'),
feedback: this._t('lbl.cat_feedback', {}, 'Feedback & review labels'),
suggestions: this._t('lbl.cat_suggestions', {}, 'Suggestions'),
maintenance_log: this._t('lbl.cat_maintenance_log', {}, 'Maintenance log'),
history_logs: this._t('lbl.cat_history_logs', {}, 'History & change logs'),
lifetime_stats: this._t('lbl.cat_lifetime_stats', {}, 'Lifetime totals'),
};
return map[catId] || catId;
}
// Build a fresh selection model from a manifest. When importableOnly is set,
// categories/items the backend marked not-importable are left unselected.
_wizInitSel(manifest, importableOnly) {
const cats = new Set(), profiles = new Set(), realIds = new Set(), refIds = new Set();
const c = (manifest && manifest.categories) || {};
const ok = (info) => !!(info && info.present && (!importableOnly || info.importable !== false));
if (ok(c.profiles)) (c.profiles.items || []).forEach(i => profiles.add(i.name));
if (ok(c.real_cycles)) (c.real_cycles.groups || []).forEach(g => g.cycles.forEach(cy => { if (cy.id != null) realIds.add(String(cy.id)); }));
if (ok(c.reference_cycles)) (c.reference_cycles.groups || []).forEach(g => g.cycles.forEach(cy => { if (cy.id != null) refIds.add(String(cy.id)); }));
Object.keys(c).forEach(cid => {
if (['profiles', 'real_cycles', 'reference_cycles'].includes(cid)) return;
if (ok(c[cid])) cats.add(cid);
});
return { cats, profiles, realIds, refIds };
}
// The selection payload sent to the backend (categories + item subsets).
_wizSelectionPayload(m) {
const s = m.sel;
const categories = [];
if (s.profiles.size) categories.push('profiles');
if (s.realIds.size) categories.push('real_cycles');
if (s.refIds.size) categories.push('reference_cycles');
s.cats.forEach(cid => categories.push(cid));
const out = { categories };
if (s.profiles.size) out.profiles = Array.from(s.profiles);
if (s.realIds.size) out.real_cycle_ids = Array.from(s.realIds);
if (s.refIds.size) out.reference_cycle_ids = Array.from(s.refIds);
return out;
}
// Ids of the cycles in one manifest group (real_cycles / reference_cycles).
_wizGroupIds(manifest, catId, prof) {
const cat = (manifest.categories || {})[catId] || {};
const g = (cat.groups || []).find(gr => gr.profile === prof);
return g ? g.cycles.map(cy => String(cy.id)).filter(id => id !== 'null') : [];
}
// tri-state {sel, total, state} for a category, for the checkbox rendering.
_wizCatState(m, catId, manifest) {
const s = m.sel;
const cat = (manifest.categories || {})[catId] || {};
if (catId === 'profiles') {
const items = cat.items || [];
const total = items.length;
const sel = items.filter(i => s.profiles.has(i.name)).length;
return { sel, total, state: sel === 0 ? 'none' : (sel === total ? 'all' : 'some') };
}
if (catId === 'real_cycles' || catId === 'reference_cycles') {
const set = catId === 'real_cycles' ? s.realIds : s.refIds;
const total = cat.count || 0;
let sel = 0;
(cat.groups || []).forEach(g => g.cycles.forEach(cy => { if (set.has(String(cy.id))) sel++; }));
return { sel, total, state: sel === 0 ? 'none' : (sel === total ? 'all' : 'some') };
}
const on = s.cats.has(catId);
return { sel: on ? 1 : 0, total: 1, state: on ? 'all' : 'none' };
}
// Render the shared tri-state category tree from a manifest/inventory.
// opts = { importableOnly, conflicts } — conflicts marks profiles that clash.
_htmlSelectionTree(m, manifest, opts = {}) {
const cats = (manifest && manifest.categories) || {};
const conflictNames = new Set();
if (opts.conflicts) (cats.profiles && cats.profiles.items || []).forEach(i => { if (i.conflict) conflictNames.add(i.name); });
const order = this._wizCatOrder().filter(cid => cats[cid] && cats[cid].present);
// Master select-all state across every currently-selectable category.
let anySel = false, allSel = true;
order.forEach(cid => {
if (opts.importableOnly && cats[cid].importable === false) return;
const st = this._wizCatState(m, cid, manifest);
if (st.state !== 'none') anySel = true;
if (st.state !== 'all') allSel = false;
});
const master = `<label class="wd-sd-prof" style="border-bottom:1px solid var(--divider-color, #444)">
<input type="checkbox" data-maction="wiz-toggle-all" ${allSel ? 'checked' : ''} ${!allSel && anySel ? 'data-indeterminate="1"' : ''}>
<span class="wd-sd-prof-name"><strong>${this._t('btn.select_all', {}, 'Select all')}</strong></span>
</label>`;
const rows = order.map(cid => {
const info = cats[cid];
const blocked = opts.importableOnly && info.importable === false;
const st = this._wizCatState(m, cid, manifest);
const enumerable = ['profiles', 'real_cycles', 'reference_cycles'].includes(cid);
const countLbl = enumerable ? `${st.sel}/${info.count}` : '';
const header = `<label class="wd-sd-prof">
<input type="checkbox" data-maction="wiz-toggle-cat" data-cat="${cid}" ${st.state === 'all' ? 'checked' : ''} ${st.state === 'some' ? 'data-indeterminate="1"' : ''} ${blocked ? 'disabled' : ''}>
<span class="wd-sd-prof-name">${_esc(this._wizCatLabel(cid))}${blocked ? ` <span class="wd-tag">${this._t('badge.not_importable', {}, 'n/a here')}</span>` : ''}</span>
<span class="wd-sd-count">${countLbl}</span>
</label>`;
let items = '';
if (!blocked && cid === 'profiles') {
items = `<div class="wd-sd-cycles">${(info.items || []).map(i => `<label class="wd-sd-cyc">
<input type="checkbox" data-maction="wiz-toggle-profile" data-name="${_esc(i.name)}" ${m.sel.profiles.has(i.name) ? 'checked' : ''}>
<span class="wd-sd-cyc-meta">${_esc(i.name)} · ${(i.real_cycles || 0) + (i.reference_cycles || 0)} ${this._t('lbl.cycles_short', {}, 'cycles')}${conflictNames.has(i.name) ? ` <span class="wd-tag" style="color:var(--warning-color,#e6a700)">${this._t('badge.exists', {}, 'exists')}</span>` : ''}</span>
</label>`).join('')}</div>`;
} else if (!blocked && (cid === 'real_cycles' || cid === 'reference_cycles')) {
const set = cid === 'real_cycles' ? m.sel.realIds : m.sel.refIds;
items = `<div class="wd-sd-cycles">${(info.groups || []).map(g => {
const ids = g.cycles.map(cy => String(cy.id));
const gsel = ids.filter(id => set.has(id)).length;
const gall = gsel === ids.length && ids.length > 0;
const gsome = gsel > 0 && !gall;
const key = `${cid}:${g.profile}`;
const expanded = m.expanded && m.expanded.has(key);
const rows2 = expanded ? g.cycles.map(cy => `<label class="wd-sd-cyc" style="margin-left:18px">
<input type="checkbox" data-maction="wiz-toggle-cyc" data-cat="${cid}" data-cid="${_esc(String(cy.id))}" ${set.has(String(cy.id)) ? 'checked' : ''}>
<span class="wd-sd-cyc-meta">${_esc(cy.date ? _fmtDate(cy.date) : String(cy.id))}${cy.duration != null ? ' · ' + _fmtDuration(cy.duration) : ''}</span>
</label>`).join('') : '';
return `<div>
<label class="wd-sd-cyc">
<input type="checkbox" data-maction="wiz-toggle-cycgroup" data-cat="${cid}" data-prof="${_esc(g.profile)}" ${gall ? 'checked' : ''} ${gsome ? 'data-indeterminate="1"' : ''}>
<span class="wd-sd-cyc-meta"><strong>${_esc(g.profile || this._t('lbl.unlabelled', {}, 'Unlabelled'))}</strong> (${gsel}/${g.count})</span>
<button type="button" class="wd-linkbtn" data-maction="wiz-expand" data-key="${_esc(key)}" style="margin-left:auto;background:none;border:none;color:var(--primary-color);cursor:pointer">${expanded ? '▾' : '▸'}</button>
</label>
${rows2}
</div>`;
}).join('')}</div>`;
}
return `<div class="wd-sd-group">${header}${items}</div>`;
}).join('');
return `<div class="wd-sd-tree">${master}${rows}</div>`;
}
_htmlExportSelectModal(m) {
const busy = this._busy.has('export-select');
let body;
if (m.loading || !m.inventory) {
body = `<div class="wd-empty" style="padding:24px"><div class="wd-icon">⏳</div>${this._t('msg.loading', {}, 'Loading…')}</div>`;
} else {
body = this._htmlSelectionTree(m, { categories: m.inventory }, { importableOnly: false });
}
const anything = m.sel && (m.sel.profiles.size || m.sel.realIds.size || m.sel.refIds.size || m.sel.cats.size);
return `<h2 id="wd-modal-title">${this._t('modal.export_select', {}, 'Export - choose data')}</h2>
<p class="wd-info" style="margin-bottom:12px">${this._t('msg.export_select_intro', {}, 'Tick exactly what to include. Selecting profiles without their cycles still exports a matchable program (its learned shape travels along).')}</p>
${body}
<div class="wd-modal-actions">
<button class="wd-btn wd-btn-secondary" data-maction="cancel" ${busy ? 'disabled' : ''}>${this._t('btn.cancel', {}, 'Cancel')}</button>
<button class="wd-btn wd-btn-primary" data-maction="export-generate" ${busy || !anything ? 'disabled' : ''}>${busy ? '<span class="wd-spin"></span> ' : ''}${this._t('btn.download_export', {}, 'Download export')}</button>
</div>`;
}
_htmlImportWizardModal(m) {
const busy = this._busy.has('import-wizard');
const title = `<h2 id="wd-modal-title">${this._t('modal.import_wizard', {}, 'Import - choose data')}</h2>`;
if (m.step === 'input' || m.step === 'analyze') {
const analyzing = m.step === 'analyze';
return `${title}
<p class="wd-info" style="margin-bottom:12px">${this._t('msg.import_analyze_hint', {}, 'Load an exported file (or paste its JSON). WashData analyzes it and shows exactly what can be imported before anything changes.')}</p>
<div class="wd-field"><label>${this._t('lbl.load_from_file', {}, 'Load from file')}</label><input type="file" id="wd-import-file" accept=".json,application/json" ${analyzing ? 'disabled' : ''}></div>
<div class="wd-field"><label>${this._t('lbl.json_data', {}, 'JSON Data')}</label><textarea id="wd-import-json" style="min-height:120px;font-family:monospace;font-size:.78em" ${analyzing ? 'disabled' : ''}>${_esc(m.jsonText || '')}</textarea></div>
${m.error ? `<p class="wd-info" style="color:var(--error-color)">${_esc(m.error)}</p>` : ''}
<div class="wd-modal-actions">
<button class="wd-btn wd-btn-secondary" data-maction="cancel">${this._t('btn.cancel', {}, 'Cancel')}</button>
<button class="wd-btn wd-btn-primary" data-maction="import-analyze" ${analyzing ? 'disabled' : ''}>${analyzing ? '<span class="wd-spin"></span> ' + this._t('msg.analyzing', {}, 'Analyzing…') : this._t('btn.analyze_import', {}, 'Analyze file')}</button>
</div>`;
}
// step === 'select'
const man = m.manifest || {};
const mismatch = man.device_type_match === false;
// Escape: source_device_type comes verbatim from the imported/shared file's
// device_fingerprint and is unvalidated; _t() does raw {var} substitution, so an
// un-escaped value would inject markup into innerHTML (XSS via a crafted import).
const srcDt = _esc(man.source_device_type || '?');
const localDt = _esc(man.local_device_type || '?');
const warnBanner = mismatch ? `<div class="wd-banner wd-banner-warn" style="margin-bottom:12px;padding:8px 12px;border-radius:8px;background:var(--warning-color,#e6a700);color:#111">
${this._t('msg.device_type_mismatch_warn', { src: srcDt, local: localDt }, 'This export is from a different appliance type (' + srcDt + ' vs ' + localDt + '). Programs and cycles can still be imported as reference data, but device-specific settings and real-history import are disabled.')}
</div>` : '';
const tree = this._htmlSelectionTree(m, man, { importableOnly: true, conflicts: true });
// Merge / replace mode toggle.
const modeBar = `<div class="wd-field"><label>${this._t('lbl.import_mode', {}, 'How to combine')}</label>
<div class="wd-mode-bar" style="display:flex;gap:8px">
<button type="button" class="wd-btn ${m.mode === 'merge' ? 'wd-btn-primary' : 'wd-btn-secondary'}" data-maction="imp-mode-merge">${this._t('lbl.mode_merge', {}, 'Merge (keep mine)')}</button>
<button type="button" class="wd-btn ${m.mode === 'replace' ? 'wd-btn-primary' : 'wd-btn-secondary'}" data-maction="imp-mode-replace">${this._t('lbl.mode_replace', {}, 'Replace selected')}</button>
</div>
<div class="wd-field-hint">${m.mode === 'replace' ? this._t('msg.replace_warn', {}, 'Each ticked category is wiped and replaced from the file. Unticked categories are left untouched.') : this._t('msg.merge_hint', {}, 'Imported items are added; nothing local is lost. Name clashes are resolved below.')}</div>
</div>`;
// Cycle destination toggle (only relevant when cycles are being imported).
const realAllowed = man.real_history_allowed !== false;
const destBar = `<div class="wd-field"><label>${this._t('lbl.cycle_destination', {}, 'Where should imported cycles go?')}</label>
<div class="wd-mode-bar" style="display:flex;gap:8px">
<button type="button" class="wd-btn ${m.cycleDest === 'reference' ? 'wd-btn-primary' : 'wd-btn-secondary'}" data-maction="imp-dest-reference">${this._t('lbl.dest_reference', {}, 'Reference (shape only)')}</button>
<button type="button" class="wd-btn ${m.cycleDest === 'real_history' ? 'wd-btn-primary' : 'wd-btn-secondary'}" data-maction="imp-dest-real" ${realAllowed ? '' : 'disabled'}>${this._t('lbl.dest_real_history', {}, 'Real history (counts in stats)')}</button>
</div>
<div class="wd-field-hint">${m.cycleDest === 'real_history' ? this._t('msg.dest_real_history_hint', {}, 'Imported cycles count as this device\'s own history and feed energy/usage stats. Use for moving one appliance to a new install.') : this._t('msg.dest_reference_hint', {}, 'Imported cycles only improve program matching and never affect usage/energy statistics.')}</div>
</div>`;
// Per-conflict resolution (merge mode, selected conflicting profiles).
let conflictBar = '';
const profItems = (man.categories && man.categories.profiles && man.categories.profiles.items) || [];
const selConflicts = profItems.filter(i => i.conflict && m.sel.profiles.has(i.name));
if (m.mode === 'merge' && selConflicts.length) {
conflictBar = `<div class="wd-field"><label>${this._t('lbl.conflict_resolution', {}, 'Name clashes')}</label>
${selConflicts.map(i => `<div style="display:flex;align-items:center;gap:8px;margin:4px 0">
<span style="flex:1">${_esc(i.name)}</span>
<select data-maction="imp-conflict" data-prof="${_esc(i.name)}">
<option value="import_as_copy" ${(m.conflicts[i.name] || 'import_as_copy') === 'import_as_copy' ? 'selected' : ''}>${this._t('lbl.conflict_import_copy', {}, 'Import as copy')}</option>
<option value="keep_mine" ${m.conflicts[i.name] === 'keep_mine' ? 'selected' : ''}>${this._t('lbl.conflict_keep_mine', {}, 'Keep mine')}</option>
<option value="overwrite" ${m.conflicts[i.name] === 'overwrite' ? 'selected' : ''}>${this._t('lbl.conflict_overwrite', {}, 'Overwrite')}</option>
</select>
</div>`).join('')}
</div>`;
}
const anything = m.sel && (m.sel.profiles.size || m.sel.realIds.size || m.sel.refIds.size || m.sel.cats.size);
return `${title}
${warnBanner}
${tree}
${modeBar}
${destBar}
${conflictBar}
<div class="wd-modal-actions">
<button class="wd-btn wd-btn-secondary" data-maction="import-back" ${busy ? 'disabled' : ''}>${this._t('btn.back', {}, 'Back')}</button>
<button class="wd-btn wd-btn-primary" data-maction="import-apply-ok" ${busy || !anything ? 'disabled' : ''}>${busy ? '<span class="wd-spin"></span> ' : ''}${this._t('btn.import_selected', {}, 'Import selected')}</button>
</div>`;
}
// Interactive cycle inspector: view / trim / split.
_htmlCycleModal(m) {
if (!m.loaded) {
@@ -7659,6 +7994,23 @@ class HaWashdataPanel extends HTMLElement {
<button class="wd-btn wd-btn-sm ${m.mode === 'review' ? 'wd-btn-primary' : 'wd-btn-secondary'}" data-maction="cyc-review" title="${needsReview ? this._t('hdr.automation_needs_review', {}, 'This cycle needs review') : this._t('hdr.automation_review_this_cycle', {}, 'Review this cycle')}">${this._t('btn.review', {}, 'Review')}${reviewDot}</button>
</div>` : (isRef ? `<div class="wd-info" style="margin:0 0 8px"><span style="color:var(--info-color,#2196f3)">📥</span> ${this._t('msg.imported_readonly', {}, 'Imported from the community store. Shown for reference and matching. It is not counted in your stats and cannot be edited.')}</div>` : '');
// Pending-detection-feedback banner (Confirm / Correct… / Ignore). Built once
// and shown in BOTH Inspect and Review modes, so a cycle in the "needs review"
// queue exposes the resolve controls without the user having to discover Review
// mode (#331). Editors only; imported reference cycles never carry feedback.
const pendingFb = this._canEdit() ? (this._feedbacks || []).find(f => f.cycle_id === m.cycleId) : null;
const fbProf = pendingFb ? (pendingFb.detected_profile || pendingFb.profile_name || this._t('lbl.unknown', {}, 'Unknown')) : '';
const fbBanner = pendingFb ? `
<div class="wd-card" style="background:var(--secondary-background-color);border-left:3px solid var(--warning-color,#ff9800);margin:0 0 12px;padding:12px">
<div style="font-weight:600;margin-bottom:4px"> ${this._t('msg.pending_feedback', {}, 'Pending detection feedback')}</div>
<p class="wd-info" style="margin:0 0 8px">${this._t('msg.unsure_detected_prefix', {}, 'WashData is unsure it detected')} <strong>${_esc(fbProf)}</strong>${pendingFb.confidence != null ? ` (${this._t('lbl.confidence', {}, 'confidence').toLowerCase()} ${(pendingFb.confidence * 100).toFixed(0)}%)` : ''}. ${this._t('msg.feedback_prompt', {}, 'Confirm it was right, correct the program, or ignore.')} ${this._t('msg.feedback_relabel_hint', {}, 'Re-labelling this cycle resolves it too.')}</p>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="wd-btn wd-btn-primary wd-btn-sm" data-action="fb-confirm" data-cid="${_esc(m.cycleId)}">${this._t('btn.confirm', {}, 'Confirm')}</button>
<button class="wd-btn wd-btn-secondary wd-btn-sm" data-action="fb-correct" data-cid="${_esc(m.cycleId)}" data-prof="${_esc(fbProf)}">${this._t('btn.correct', {}, 'Correct…')}</button>
<button class="wd-btn wd-btn-secondary wd-btn-sm" data-action="fb-ignore" data-cid="${_esc(m.cycleId)}">${this._t('btn.ignore', {}, 'Ignore')}</button>
</div>
</div>` : '';
let controls = '';
if (m.mode === 'view') {
// Share to community store: only for recorded/golden reference cycles, and
@@ -7674,7 +8026,7 @@ class HaWashdataPanel extends HTMLElement {
: isRef ? `<button class="wd-btn wd-btn-danger" data-maction="cyc-delete">${this._t('btn.delete', {}, 'Delete')}</button>`
: `<button class="wd-btn wd-btn-danger" data-maction="cyc-delete">${this._t('btn.delete', {}, 'Delete')}</button>
<button class="wd-btn wd-btn-primary" data-maction="cyc-label">${this._t('btn.label', {}, 'Label')}</button>`;
controls = `<div class="wd-modal-actions">
controls = `${fbBanner}<div class="wd-modal-actions">
<button class="wd-btn wd-btn-secondary" data-maction="cancel">${this._t('btn.close', {}, 'Close')}</button>
${shareBtn}
${editBtns}</div>`;
@@ -7737,21 +8089,8 @@ class HaWashdataPanel extends HTMLElement {
];
const tagChecks = TAGS.map(([v, l]) => `<label class="wd-rev-tag"><input type="checkbox" class="wd-cyc-rev-tag" value="${v}" ${(rv.tags || []).includes(v) ? 'checked' : ''}> ${l}</label>`).join('');
const reviewedBadge = rv.reviewed_at ? `<span style="font-size:.75em;color:var(--secondary-text-color)">${this._t('lbl.reviewed_on', {date: new Date(rv.reviewed_at).toLocaleDateString()}, `reviewed ${new Date(rv.reviewed_at).toLocaleDateString()}`)}</span>` : '';
// If this cycle has a pending detection feedback (the learning loop is
// unsure of the program it matched), surface Confirm/Correct/Ignore right
// here. This folds the old Feedbacks subtab into the unified review flow.
const pendingFb = (this._feedbacks || []).find(f => f.cycle_id === m.cycleId);
const fbProf = pendingFb ? (pendingFb.detected_profile || pendingFb.profile_name || this._t('lbl.unknown', {}, 'Unknown')) : '';
const fbBanner = pendingFb ? `
<div class="wd-card" style="background:var(--secondary-background-color);border-left:3px solid var(--warning-color,#ff9800);margin:0 0 12px;padding:12px">
<div style="font-weight:600;margin-bottom:4px"> ${this._t('msg.pending_feedback', {}, 'Pending detection feedback')}</div>
<p class="wd-info" style="margin:0 0 8px">${this._t('msg.unsure_detected_prefix', {}, 'WashData is unsure it detected')} <strong>${_esc(fbProf)}</strong>${pendingFb.confidence != null ? ` (${this._t('lbl.confidence', {}, 'confidence').toLowerCase()} ${(pendingFb.confidence * 100).toFixed(0)}%)` : ''}. ${this._t('msg.feedback_prompt', {}, 'Confirm it was right, correct the program, or ignore.')}</p>
<div style="display:flex;gap:8px;flex-wrap:wrap">
<button class="wd-btn wd-btn-primary wd-btn-sm" data-action="fb-confirm" data-cid="${_esc(m.cycleId)}">${this._t('btn.confirm', {}, 'Confirm')}</button>
<button class="wd-btn wd-btn-secondary wd-btn-sm" data-action="fb-correct" data-cid="${_esc(m.cycleId)}" data-prof="${_esc(fbProf)}">${this._t('btn.correct', {}, 'Correct…')}</button>
<button class="wd-btn wd-btn-secondary wd-btn-sm" data-action="fb-ignore" data-cid="${_esc(m.cycleId)}">${this._t('btn.ignore', {}, 'Ignore')}</button>
</div>
</div>` : '';
// Pending-feedback banner (Confirm/Correct/Ignore) is built in the shared
// scope above and rendered here as well as in Inspect mode (#331).
const tProfile = _tip(this._t('msg.review_profile_tip', {}, 'The program this cycle is labelled as. If the auto-detected program was wrong, correct it here - labelling teaches matching for future cycles.'));
const tQuality = _tip(this._t('msg.review_quality_tip', {}, 'How clean this cycle is. Good = a textbook example of this program; Bad = detected but noisy or atypical; Unusable = mis-detected (merged, truncated or spurious). Drives the health score and which cycles are allowed to train the model.'));
const tRecorded = _tip(this._t('msg.review_recorded_tip', {}, 'Mark this as a hand-picked reference cycle for its program - the same role as a manually recorded cycle. Reference cycles are always kept, seed the matching template, and are never dropped by cleanup. (This is the "golden"/recorded flag; both are the same thing.)'));
@@ -8338,7 +8677,7 @@ class HaWashdataPanel extends HTMLElement {
const wasThr = this._pgDragging === 'start_thr' || this._pgDragging === 'stop_thr';
this._pgDragging = null; this._pgPanStart = null;
pgCanvas.classList.remove('wd-pg-panning');
if (wasThr) this._pgRerunDetail(); // re-run the sim under the new threshold
if (wasThr) this._pgDrawCanvas();
});
pgCanvas.addEventListener('pointerleave', () => {
if (this._pgDragging) return;
@@ -8363,18 +8702,65 @@ class HaWashdataPanel extends HTMLElement {
}
// F3: Param input fields → sync to threshold state + redraw
sr.querySelectorAll('[data-pgkey]').forEach(inp => inp.addEventListener('input', () => {
const key = inp.dataset.pgkey;
const val = parseFloat(inp.value);
if (isNaN(val)) return;
if (key === 'start_threshold_w') this._pgThreshStart = val;
else if (key === 'stop_threshold_w') this._pgThreshStop = val;
else this._pgParamOverrides[key] = val;
this._pgDrawCanvas();
// Re-run the faithful sim under the new setting so the state band, model
// estimates, events and alerts reflect it (debounced).
this._pgRerunDetail();
}));
sr.querySelectorAll('[data-pgkey]').forEach(inp => {
inp.addEventListener('input', () => {
const key = inp.dataset.pgkey;
if (inp.dataset.pgtype === 'bool') {
const val = inp.checked;
if (key === 'start_threshold_w') this._pgThreshStart = val;
else if (key === 'stop_threshold_w') this._pgThreshStop = val;
else this._pgParamOverrides[key] = val;
this._render();
const again = sr.querySelector(`[data-pgkey="${key}"]`);
if (again) again.focus();
requestAnimationFrame(() => this._pgDrawCanvas());
return;
}
const raw = inp.value.trim();
if (!raw) {
if (key === 'start_threshold_w') this._pgThreshStart = null;
else if (key === 'stop_threshold_w') this._pgThreshStop = null;
else delete this._pgParamOverrides[key];
} else {
const val = parseFloat(raw);
if (!isNaN(val)) {
if (key === 'start_threshold_w') this._pgThreshStart = val;
else if (key === 'stop_threshold_w') this._pgThreshStop = val;
else this._pgParamOverrides[key] = val;
}
// Partial value ("1.", ".", "-") — leave state unchanged until next keystroke.
}
// Save the raw text and caret before _render() destroys this input. With
// type="text" (not number), selectionStart is a real index and setSelectionRange
// works, so cursor restoration is reliable unlike the old type=number approach.
const caret = inp.selectionStart;
const rawVal = inp.value;
this._render();
const again = sr.querySelector(`[data-pgkey="${key}"]`);
if (again) {
again.value = rawVal; // restore raw text incl. trailing "." the browser strips
again.focus();
try { again.setSelectionRange(caret, caret); } catch (_) {}
}
requestAnimationFrame(() => this._pgDrawCanvas());
});
});
// F3: Idle termination test toggle
const pgStressToggle = sr.getElementById('wd-pg-stress-toggle');
if (pgStressToggle) pgStressToggle.addEventListener('change', () => {
this._pgStressTail = pgStressToggle.checked;
this._pgStressIdleW = null;
this._render();
});
// F3: Idle level override field (only present when toggle is on)
const pgStressIdleW = sr.getElementById('wd-pg-stress-idle-w');
if (pgStressIdleW) pgStressIdleW.addEventListener('input', () => {
const v = parseFloat(pgStressIdleW.value);
// Number.isFinite rejects Infinity (parseFloat("1e999")) which isNaN misses.
this._pgStressIdleW = (Number.isFinite(v) && v >= 0) ? v : null;
});
// F3: Cycle selector
const pgCycSel = sr.getElementById('wd-pg-cyc-sel');
@@ -8840,6 +9226,10 @@ class HaWashdataPanel extends HTMLElement {
sr.querySelectorAll('[data-action]').forEach(btn => btn.addEventListener('click', e => this._onAction(e.currentTarget)));
sr.querySelectorAll('[data-maction]').forEach(btn => btn.addEventListener('click', e => this._onModalAction(e.currentTarget.dataset.maction, e.currentTarget)));
// A <select data-maction> commits via `change` (not `click`, esp. on keyboard
// selection), so bind change too — otherwise e.g. the import conflict-resolution
// dropdown never records the user's pick and silently falls back to the default.
sr.querySelectorAll('select[data-maction]').forEach(sel => sel.addEventListener('change', e => this._onModalAction(e.currentTarget.dataset.maction, e.currentTarget)));
// indeterminate is a JS property (no HTML attribute); apply it after render for
// the share-device tree's partially-selected profile checkboxes.
sr.querySelectorAll('input[data-indeterminate]').forEach(cb => { cb.indeterminate = true; });
@@ -9679,6 +10069,21 @@ class HaWashdataPanel extends HTMLElement {
document.body.appendChild(a2); a2.click(); document.body.removeChild(a2); URL.revokeObjectURL(url);
this._showToast(this._t('toast.export_downloaded', {}, 'Export downloaded'));
}).catch(e => this._showToast(this._t('toast.export_failed', {error: e.message || e}, 'Export failed: ' + (e.message || e)), 'error'));
} else if (a === 'export-select-open') {
// Open the export wizard: fetch this device's inventory, default everything on.
this._modal = { type: 'export-select', inventory: null, loading: true, sel: { cats: new Set(), profiles: new Set(), realIds: new Set(), refIds: new Set() }, expanded: new Set() };
this._render();
(async () => {
let inv = null;
try { const r = await this._ws({ type: `${_DOMAIN}/get_export_inventory`, entry_id: eid }); inv = (r && r.manifest) || null; }
catch (e) { this._showToast(this._t('toast.export_failed', {error: e.message || e}, 'Export failed: ' + (e.message || e)), 'error'); }
if (!this._isActiveEntry(eid) || !this._modal || this._modal.type !== 'export-select') return;
if (!inv) { this._modal = null; this._render(); return; }
this._modal.inventory = inv;
this._modal.sel = this._wizInitSel({ categories: inv }, false);
this._modal.loading = false;
this._render();
})();
} else if (a === 'cyc-select-toggle') {
this._selectMode = !this._selectMode;
if (!this._selectMode) this._cycleSel.clear();
@@ -9752,6 +10157,7 @@ class HaWashdataPanel extends HTMLElement {
this._pgCancelRun();
} else if (a === 'pg-reset-params') {
this._pgThreshStart = null; this._pgThreshStop = null; this._pgParamOverrides = {};
this._pgStressTail = false; this._pgStressIdleW = null;
this._render(); requestAnimationFrame(() => this._pgDrawCanvas());
} else if (a === 'pg-apply-settings') {
this._pgApplyToSettings();
@@ -9802,7 +10208,7 @@ class HaWashdataPanel extends HTMLElement {
const val = JSON.parse(btn.dataset.val);
if (!key) return;
const eid = dev.entry_id;
this._ws({ type: `${_DOMAIN}/ws_set_options`, entry_id: eid, options: { [key]: val } })
this._ws({ type: `${_DOMAIN}/set_options`, entry_id: eid, options: { [key]: val } })
.then(() => this._ws({ type: `${_DOMAIN}/get_options`, entry_id: eid }))
.then(r => { this._opts = r.options || {}; return this._fetchSettingsChangelog(eid); })
.then(() => {
@@ -9845,6 +10251,13 @@ class HaWashdataPanel extends HTMLElement {
this._showToast(this._t('toast.logs_exported', {}, 'Logs exported'));
}).catch(e => this._showToast(this._t('toast.export_failed', {error: e.message || e}, 'Export failed: ' + (e.message || e)), 'error'));
} else if (a === 'import-config-open') {
// Open the import wizard at the paste/upload step.
this._modal = { type: 'import-wizard', step: 'input', jsonText: '', manifest: null, error: null,
sel: { cats: new Set(), profiles: new Set(), realIds: new Set(), refIds: new Set() },
expanded: new Set(), mode: 'merge', cycleDest: 'reference', conflicts: {} };
this._render();
} else if (a === 'import-config-raw') {
// Advanced fallback: the legacy raw-JSON whole-store replace.
this._modal = { type: 'import-config' }; this._render();
} else if (a === 'save-prefs') {
@@ -10105,6 +10518,146 @@ class HaWashdataPanel extends HTMLElement {
}
}
// ---- Selective export / import wizard ----
if (m && (m.type === 'export-select' || m.type === 'import-wizard')) {
// The manifest that backs the tree: inventory for export, analyze result for import.
const man = m.type === 'export-select' ? { categories: m.inventory || {} } : (m.manifest || { categories: {} });
if (action === 'wiz-toggle-all') {
const cats = man.categories || {};
const importableOnly = m.type === 'import-wizard';
// If everything is already selected, clear; otherwise select all selectable.
let allSel = true;
this._wizCatOrder().filter(cid => cats[cid] && cats[cid].present).forEach(cid => {
if (importableOnly && cats[cid].importable === false) return;
if (this._wizCatState(m, cid, man).state !== 'all') allSel = false;
});
m.sel = allSel
? { cats: new Set(), profiles: new Set(), realIds: new Set(), refIds: new Set() }
: this._wizInitSel(man, importableOnly);
this._render();
return;
}
if (action === 'wiz-toggle-cat') {
const cid = btn.dataset.cat;
const st = this._wizCatState(m, cid, man);
const turnOn = st.state !== 'all';
if (cid === 'profiles') {
const items = (man.categories.profiles && man.categories.profiles.items) || [];
m.sel.profiles = new Set(turnOn ? items.map(i => i.name) : []);
} else if (cid === 'real_cycles' || cid === 'reference_cycles') {
const set = new Set();
if (turnOn) ((man.categories[cid] && man.categories[cid].groups) || []).forEach(g => g.cycles.forEach(cy => { if (cy.id != null) set.add(String(cy.id)); }));
if (cid === 'real_cycles') m.sel.realIds = set; else m.sel.refIds = set;
} else if (turnOn) { m.sel.cats.add(cid); } else { m.sel.cats.delete(cid); }
this._render();
return;
}
if (action === 'wiz-toggle-profile') {
const name = btn.dataset.name;
if (m.sel.profiles.has(name)) m.sel.profiles.delete(name); else m.sel.profiles.add(name);
this._render();
return;
}
if (action === 'wiz-toggle-cycgroup') {
const cid = btn.dataset.cat; const prof = btn.dataset.prof;
const set = cid === 'real_cycles' ? m.sel.realIds : m.sel.refIds;
const ids = this._wizGroupIds(man, cid, prof);
const all = ids.length > 0 && ids.every(id => set.has(id));
ids.forEach(id => { if (all) set.delete(id); else set.add(id); });
this._render();
return;
}
if (action === 'wiz-toggle-cyc') {
const cid = btn.dataset.cat; const id = btn.dataset.cid;
const set = cid === 'real_cycles' ? m.sel.realIds : m.sel.refIds;
if (set.has(id)) set.delete(id); else set.add(id);
this._render();
return;
}
if (action === 'wiz-expand') {
const key = btn.dataset.key;
if (!m.expanded) m.expanded = new Set();
if (m.expanded.has(key)) m.expanded.delete(key); else m.expanded.add(key);
this._render();
return;
}
}
// Export wizard: generate + download the filtered JSON.
if (m && m.type === 'export-select' && action === 'export-generate' && eid) {
const selection = this._wizSelectionPayload(m);
await this._busyRun('export-select', async () => {
try {
const r = await this._ws({ type: `${_DOMAIN}/export_config_selective`, entry_id: eid, selection });
const blob = new Blob([r.json_data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a2 = document.createElement('a');
a2.href = url; a2.download = `washdata_export_${eid.slice(0, 8)}.json`;
document.body.appendChild(a2); a2.click(); document.body.removeChild(a2); URL.revokeObjectURL(url);
this._modal = null;
this._showToast(this._t('toast.export_selective_done', {}, 'Export downloaded'));
} catch (e) { this._showToast(this._t('toast.export_failed', {error: e.message || e}, 'Export failed: ' + (e.message || e)), 'error'); }
});
return;
}
// Import wizard: step machine + toggles + apply.
if (m && m.type === 'import-wizard') {
if (action === 'import-back') {
m.step = 'input'; m.error = null; this._render();
return;
}
if (action === 'imp-mode-merge') { m.mode = 'merge'; this._render(); return; }
if (action === 'imp-mode-replace') { m.mode = 'replace'; this._render(); return; }
if (action === 'imp-dest-reference') { m.cycleDest = 'reference'; this._render(); return; }
if (action === 'imp-dest-real') { if ((m.manifest || {}).real_history_allowed !== false) { m.cycleDest = 'real_history'; this._render(); } return; }
if (action === 'imp-conflict') { m.conflicts[btn.dataset.prof] = btn.value; return; }
if (action === 'import-analyze' && eid) {
const ta = sr.getElementById('wd-import-json');
const jsonText = ta ? ta.value : (m.jsonText || '');
m.jsonText = jsonText;
if (!jsonText.trim()) { this._showToast(this._t('toast.json_required', {}, 'JSON data is required'), 'error'); return; }
m.step = 'analyze'; m.error = null; this._render();
try {
const r = await this._ws({ type: `${_DOMAIN}/analyze_import`, entry_id: eid, json_data: jsonText });
if (!this._isActiveEntry(eid) || !this._modal || this._modal.type !== 'import-wizard') return;
const manifest = (r && r.manifest) || {};
if (manifest.error) { m.step = 'input'; m.error = manifest.error; this._render(); return; }
m.manifest = manifest;
m.sel = this._wizInitSel(manifest, true);
// Default every conflicting profile to the safest resolution.
m.conflicts = {};
((manifest.categories && manifest.categories.profiles && manifest.categories.profiles.items) || [])
.forEach(i => { if (i.conflict) m.conflicts[i.name] = 'import_as_copy'; });
m.step = 'select';
this._render();
} catch (e) {
if (!this._isActiveEntry(eid) || !this._modal || this._modal.type !== 'import-wizard') return;
m.step = 'input'; m.error = (e && e.message) || String(e); this._render();
}
return;
}
if (action === 'import-apply-ok' && eid) {
const selection = this._wizSelectionPayload(m);
await this._busyRun('import-wizard', async () => {
try {
const r = await this._ws({ type: `${_DOMAIN}/import_config_selective`, entry_id: eid,
json_data: m.jsonText, selection, mode: m.mode,
conflict_resolutions: m.conflicts, cycle_destination: m.cycleDest, apply_settings: true });
const s = (r && r.summary) || {};
this._modal = null;
this._showToast(this._t('toast.import_selective_done', {
profiles: s.profiles_imported || 0,
cycles: (s.real_cycles_imported || 0) + (s.reference_cycles_imported || 0),
}, `Imported ${s.profiles_imported || 0} profile(s) and ${(s.real_cycles_imported || 0) + (s.reference_cycles_imported || 0)} cycle(s)`));
await this._fetchCycles(eid);
await this._fetchProfiles(eid);
} catch (e) { this._showToast(this._t('toast.import_failed', {error: e.message || e}, 'Import failed: ' + (e.message || e)), 'error'); }
});
return;
}
}
// ---- Cycle inspector ----
if (m && m.type === 'cycle-detail') {
@@ -10128,6 +10681,9 @@ class HaWashdataPanel extends HTMLElement {
}
this._showToast(this._t('toast.review_saved', {}, 'Review saved'));
await this._fetchCycles(eid);
// A label change in review now resolves the pending feedback backend-side
// (#331), so refresh the queue rather than leaving a stale entry.
if (newLabel !== curLabel) await this._fetchFeedbacks(eid);
await this._loadMlIndex(eid);
if (this._modal && this._modal.cycleId === cid) this._modal.ml = (this._mlById || {})[cid] || this._modal.ml;
} catch (e) { this._showToast(this._t('msg.toast_save_failed', {error: e.message || e}, 'Save failed: ' + (e.message || e)), 'error'); }
@@ -10274,7 +10830,9 @@ class HaWashdataPanel extends HTMLElement {
? (sr.getElementById('wd-new-profile-name')?.value?.trim() || null)
: null;
this._modal = null;
try { await this._ws({ type: `${_DOMAIN}/label_cycle`, entry_id: eid, cycle_id: m.cycleId, profile_name: profileName || null, new_profile_name: newName }); this._showToast(this._t('toast.cycle_labelled', {}, 'Cycle labelled')); await this._fetchCycles(eid); await this._fetchProfiles(eid); }
// Re-fetch feedbacks too: labelling a review cycle now resolves its pending
// feedback backend-side (#331), so the "needs review" queue must refresh.
try { await this._ws({ type: `${_DOMAIN}/label_cycle`, entry_id: eid, cycle_id: m.cycleId, profile_name: profileName || null, new_profile_name: newName }); this._showToast(this._t('toast.cycle_labelled', {}, 'Cycle labelled')); await this._fetchCycles(eid); await this._fetchProfiles(eid); await this._fetchFeedbacks(eid); }
catch (e) { this._showToast(this._t('toast.label_failed', {error: e.message || e}, 'Label failed: ' + (e.message || e)), 'error'); }
this._render();
} else if (action === 'create-profile-ok' && eid) {
@@ -10364,7 +10922,8 @@ class HaWashdataPanel extends HTMLElement {
for (const cid of ids) await this._ws({ type: `${_DOMAIN}/label_cycle`, entry_id: eid, cycle_id: cid, profile_name: profileName || null });
this._showToast(this._t('toast.relabel_done', { count: ids.length }, `Relabelled ${ids.length} cycle(s)`));
this._cycleSel.clear(); this._selectMode = false;
await this._fetchCycles(eid); await this._fetchProfiles(eid);
// Bulk relabel resolves any pending feedback on those cycles (#331).
await this._fetchCycles(eid); await this._fetchProfiles(eid); await this._fetchFeedbacks(eid);
} catch (e) { this._showToast(this._t('toast.relabel_failed', { error: e.message || e }, 'Relabel failed: ' + (e.message || e)), 'error'); }
});
}
+47
View File
@@ -12,6 +12,10 @@ export interface AddMaintenanceEventResponse {
event: Record<string, unknown>;
}
export interface AnalyzeImportResponse {
manifest: Record<string, unknown>;
}
export interface AnalyzeSplitResponse {
segments: number[][];
split_offsets: number[];
@@ -142,6 +146,10 @@ export interface GetDtwDebugResponse {
warp_path: number[][];
}
export interface GetExportInventoryResponse {
manifest: Record<string, unknown>;
}
export interface GetFeedbacksResponse {
feedbacks: Record<string, unknown>[];
}
@@ -275,6 +283,11 @@ export interface GetSuggestionsResponse {
suggestions: Record<string, unknown>[];
}
export interface ImportConfigSelectiveResponse {
success: boolean;
summary: Record<string, unknown>;
}
export interface ListTasksResponse {
tasks: TaskSnapshot[];
}
@@ -671,6 +684,30 @@ export interface ImportConfigRequest {
json_data: string;
}
export interface GetExportInventoryRequest {
entry_id: string;
}
export interface AnalyzeImportRequest {
entry_id: string;
json_data: string;
}
export interface ExportConfigSelectiveRequest {
entry_id: string;
selection: Record<string, unknown>;
}
export interface ImportConfigSelectiveRequest {
entry_id: string;
json_data: string;
selection: Record<string, unknown>;
mode?: "merge" | "replace";
conflict_resolutions?: Record<string, unknown>;
cycle_destination?: "reference" | "real_history";
apply_settings?: boolean;
}
export interface GetConstantsRequest {
}
@@ -871,6 +908,8 @@ export interface StartPlaygroundCycleDetailRequest {
entry_id: string;
cycle_id: string;
settings_override?: Record<string, unknown>;
stress_tail?: boolean;
stress_idle_w?: number | null;
}
export interface StoreStatusRequest {
@@ -1020,6 +1059,10 @@ export interface WashDataWsRequests {
"ha_washdata/wipe_history": WipeHistoryRequest;
"ha_washdata/export_config": ExportConfigRequest;
"ha_washdata/import_config": ImportConfigRequest;
"ha_washdata/get_export_inventory": GetExportInventoryRequest;
"ha_washdata/analyze_import": AnalyzeImportRequest;
"ha_washdata/export_config_selective": ExportConfigSelectiveRequest;
"ha_washdata/import_config_selective": ImportConfigSelectiveRequest;
"ha_washdata/get_constants": GetConstantsRequest;
"ha_washdata/get_suggestions": GetSuggestionsRequest;
"ha_washdata/apply_suggestions": ApplySuggestionsRequest;
@@ -1121,6 +1164,10 @@ export interface WashDataWsResponses {
"ha_washdata/wipe_history": SuccessResponse;
"ha_washdata/export_config": ExportConfigResponse;
"ha_washdata/import_config": SuccessResponse;
"ha_washdata/get_export_inventory": GetExportInventoryResponse;
"ha_washdata/analyze_import": AnalyzeImportResponse;
"ha_washdata/export_config_selective": ExportConfigResponse;
"ha_washdata/import_config_selective": ImportConfigSelectiveResponse;
"ha_washdata/get_constants": GetConstantsResponse;
"ha_washdata/get_suggestions": GetSuggestionsResponse;
"ha_washdata/apply_suggestions": ApplySuggestionsResponse;